Compare commits

...

610 Commits

Author SHA1 Message Date
Dave Halter
86ae11eb43 Add a new release 0.13.2 2018-12-15 20:09:36 +01:00
Dave Halter
078595f8d7 Merge pull request #1262 from hoefling/pytest-marks
Use `pytest.param` when marking single parameters
2018-12-15 19:14:56 +01:00
Bet4
76417cc3c1 Fix environment cache regression (#1238)
The only remaining issue with this PR is that it does compare with executable instead of _start_executable (they don't need to be the same).
2018-12-15 18:37:28 +01:00
oleg.hoefling
70800a6dc2 bumped pytest dependency to 3.1.0 2018-12-07 18:22:29 +01:00
oleg.hoefling
4711b85b50 used pytest.param to comply with pytest>=4 2018-12-07 17:49:39 +01:00
micbou
368bf7e58a Improve docstring formatting 2018-11-26 00:26:34 +01:00
Jelte Fennema
3bdb941daa Add an exact_key_items method to DictComprehension fixes #1233 2018-10-14 17:08:44 +02:00
Dave Halter
bd1010bbd2 Create a new 0.13.1 release 2018-10-02 19:07:35 +02:00
Dave Halter
23b3327b1d Fixed completions of global vars and tensorflow slowness, fixes #1228, #1116 2018-10-02 15:28:51 +02:00
Dave Halter
075577d50c The changelog date was wrong 2018-10-02 15:25:31 +02:00
Dave Halter
96b57f46cb Release notes for 0.13.0 2018-10-02 01:14:28 +02:00
Dave Halter
c24eb4bd67 Fix tensorflow issues with a few hacks (temporary), fixes #1195 2018-10-02 00:52:11 +02:00
Dave Halter
862f611829 If the VIRTUAL_ENV variable changes, need to reload the default environment, fixes #1201, #1200 2018-09-30 19:07:48 +02:00
Dave Halter
f9cbc65f2d Return SameEnvironment as a default, fixes #1226, #1196 2018-09-30 14:07:37 +02:00
Dave Halter
e1f9624bd4 Document that using the REPL autocompletion is only available on Linux/Mac, fixes #1184 2018-09-30 13:36:05 +02:00
Dave Halter
6a2a2a9fa1 Fix an issue with f-strings, fixes #1224 2018-09-30 13:26:54 +02:00
Dave Halter
4545d91929 Ignore some errors that are happening when the Python process ends and its subprocesses are cleaned up 2018-09-30 13:26:26 +02:00
Claude
a2b984ce24 also remove crashes with pep 448 unpacking of lists and sets 2018-09-23 21:00:11 +02:00
Claude
6bc79b4933 Fixed crash (and now recognises correctly) {**d, "b": "b"}["b"] 2018-09-23 21:00:11 +02:00
Claude
b9127147e4 Recognize {**d} as a dict instead of set 2018-09-23 21:00:11 +02:00
Daniel Hahler
afb2755c27 Add extras_require=testing 2018-09-22 10:03:12 +02:00
Daniel Hahler
56bd795100 _get_virtual_env_from_var: use safe=False
Without this creating an env from VIRTUAL_ENV will always silently fail
if it is not the same/current environment.
2018-09-16 11:37:22 +02:00
Daniel Hahler
cdb760487b tests: venv_path: use session scope 2018-09-16 11:24:27 +02:00
Daniel Hahler
fc9a55b042 jedi/api/environment.py: minor flake8 fix 2018-09-16 11:22:02 +02:00
Daniel Hahler
1cf5b194ca jedi.api.environment._SUPPORTED_PYTHONS: add 3.7
The grammar is available in parso already, and it works in general.
2018-09-15 16:58:07 +02:00
Dave Halter
84b07a8809 Removing a test from doctests, becaues it shouldn't be one 2018-08-26 03:09:46 +02:00
Dave Halter
6c555e62aa Refactor argument clinic usage 2018-08-26 03:02:58 +02:00
Dave Halter
d6306a06a4 With the recent changes one performance optimization got lost 2018-08-07 02:47:25 +02:00
Dave Halter
8fc2add242 FunctionExecutionContext should use the parent if possible 2018-08-06 12:49:31 +02:00
Dave Halter
4a593f9693 Use anonymous instance arguments in a different way 2018-08-06 11:19:29 +02:00
Dave Halter
38a22a4ae8 Move some anonymous instance function execution stuff 2018-08-05 23:37:46 +02:00
Dave Halter
10ecb77673 Get rid of InstanceFunctionExecution, because it's really not needed 2018-08-05 23:26:15 +02:00
Dave Halter
357c86ad9c Use the InstanceArguments for super as well 2018-08-05 14:58:35 +02:00
Dave Halter
8cae517821 Use InstanceArguments directly and not via InstanceFunctionExecution 2018-08-05 14:34:44 +02:00
Dave Halter
0101fdd9da Remove old garbage code 2018-08-05 14:19:18 +02:00
Dave Halter
e17d7f5d42 Don't use arguments that are not needed 2018-08-05 14:17:46 +02:00
Dave Halter
7d16a35693 Also move the remaining get_params to get_executed_params
Remove the class's get_params entirely, because it is apparently not needed and contained a funny return.
2018-08-05 13:58:06 +02:00
Dave Halter
1456a156a6 get_params -> get_executed_params where possible 2018-08-05 13:53:57 +02:00
Dave Halter
3d55b2d826 Subprocess error reporting improvements 2018-08-05 12:50:17 +02:00
Dave Halter
1547177128 Fix a recursion issue about compiled objects 2018-08-04 23:20:51 +02:00
Dave Halter
bd43608f98 Use a CompiledInstanceNameFilter that wraps the class name as well 2018-08-04 13:10:14 +02:00
Dave Halter
72f2a9e4a5 Prefer Python 3 import over 2 2018-08-04 12:07:41 +02:00
Dave Halter
b91203820c Now it's actually possible to specify a pytest environment for the same Python version 2018-08-04 02:00:13 +02:00
Dave Halter
71572e63cd Note that Python 3.3 support was dropped in Changelog 2018-08-04 00:49:45 +02:00
Hugo
7c9f24a18e Drop support for EOL Python 3.3 (#1019) 2018-08-04 00:40:00 +02:00
Dave Halter
9ca7b30e38 Rewrite the pyc test 2018-08-03 23:59:55 +02:00
Dave Halter
fd8f254ce1 Fix an issue with stderr debugging of subprocesses 2018-08-03 23:51:58 +02:00
Dave Halter
1c76359291 stderr of the child processes should be printed in debug output
This fixes #1169. It might have a bit of a different intention, but at least it's now possible to see output of the subprocess and it's not just a black hole.
2018-08-03 13:35:21 +02:00
Dave Halter
ccb460b433 Use close_fds for posix. 2018-08-03 13:08:07 +02:00
Dave Halter
30d14ea016 Remove some redundant code 2018-08-03 12:33:35 +02:00
Dave Halter
bbb1502e06 Use names of classes to infer names of instances 2018-08-03 12:23:54 +02:00
Dave Halter
f34a9281b9 Don't have execute and execute_evaluated on name 2018-08-03 11:34:33 +02:00
Dave Halter
95a1a69771 Fix an issue where __ prefixed variables where not hidden when accessed from a class
Everything worked well when looking at it from an instance perspective.
2018-08-03 11:05:49 +02:00
Dave Halter
1a4be5c91c Bound methods are now working correctly in all Python versions. Therefore a test was wrong. 2018-08-03 00:25:25 +02:00
Dave Halter
40d3abe2b2 Remove a print in tests 2018-08-03 00:25:25 +02:00
Dave Halter
f25310e0b9 BoundMethods now have access to the function that they are using 2018-08-03 00:25:25 +02:00
Dave Halter
e576457a43 Remove another usage of is_class where it's not needed 2018-08-03 00:25:25 +02:00
Dave Halter
a1314ac3c1 FunctionContext should be created from a unified interface 2018-08-03 00:25:25 +02:00
Dave Halter
481e6bcff0 Don't create a FunctionExecutionContext if it's not used. 2018-08-03 00:25:25 +02:00
Dave Halter
9ff5050d01 Use TreeContext in a good way 2018-08-03 00:25:25 +02:00
Justin Moen
9a4a96b453 Fix broken link in documentation 2018-08-02 10:43:15 +02:00
Dave Halter
e7a019e628 The implicit namespace package test from 4b276bae87 can only be used for Python 3.4+ 2018-07-21 11:51:41 +02:00
Dave Halter
4b276bae87 The import resolution for namespace packages was wrong
With this change we can now include all parents of the script, which will make
relative imports always work.

Now the whole meta_path is scanned and not just importlib's PathFinder.

Fixes #1183.
2018-07-21 00:16:10 +02:00
Dave Halter
ad5170a37a Add a way to use the interpreter environment for tests 2018-07-20 19:16:02 +02:00
Dave Halter
d292333dab MergedExecutedParams -> DynamicExecutedParams 2018-07-18 10:02:57 +02:00
Dave Halter
a408fb3211 Fix a recursion error, fixes #1173 2018-07-18 10:01:41 +02:00
Dave Halter
3cabc4b969 Remove two recursion tests again that will belong into a commit at a point where it is not failing anymore 2018-07-17 18:34:42 +02:00
Dave Halter
fb360506fb Don't merge params if it's just one param 2018-07-17 09:53:26 +02:00
Dave Halter
fe1799d125 Add a repr for AnonymousArguments 2018-07-17 09:48:27 +02:00
Dave Halter
733919e34c Fix a doctest 2018-07-17 00:47:42 +02:00
Daniel Hahler
10b61c41f4 Some minor flake8 fixes 2018-07-16 23:41:42 +02:00
Daniel Hahler
08b0b668a6 Script.__repr__: include environment 2018-07-16 13:26:10 +02:00
Daniel Hahler
72a8ceed76 Add params to CallSignature.__repr__
Looks like this for `jedi.Script` then:

> <CallSignature: Script index=0 params=[source=None, line=None, column=None, path=None, encoding='utf-8', sys_path=None, environment=None]>

`_params_str` could be made public, and then could be used in jedi-vim,
which currently has this:

    params = [p.description.replace('\n', '').replace('param ', '', 1)
              for p in signature.params]

08792d3fd7/pythonx/jedi_vim.py (L492-L493)
2018-07-16 13:23:38 +02:00
Dave Halter
1e796fc08d Environments are now always created on request
The issue was that if something changed about the environment (e.g. version
switch) or sys.path change, re-creating the environment was possible, but did
not involve the change. The environments have now a __del__ function that
deletes the subprocess after every time an Environment is garbage collected.
2018-07-15 17:49:17 +02:00
Daniel Hahler
2fc91ceb64 Improve Environment
It only takes `executable` and gets all the information from the
subprocess directly.

Fixes https://github.com/davidhalter/jedi/issues/1107.
2018-07-15 17:49:17 +02:00
Dave Halter
f6bc166ea7 Add max line length 100 to the config for flake8 2018-07-13 09:25:51 +02:00
Daniel Hahler
08fa7941ce tests: use monkeypatch.setenv 2018-07-12 22:04:25 +02:00
Dave Halter
3a62d54403 Don't test Python 3.3 on appveyor anymore, it's getting really hard to get all the right dependencies for it, because 3.3 is deprecated everywhere. 2018-07-12 21:21:40 +02:00
Dave Halter
748946349f Mention that it's ok to have a line length of 100 characters in our files. 2018-07-12 21:18:54 +02:00
Dave Halter
71cea7200b Don't use invalid escape sequences in regex, see https://github.com/davidhalter/jedi-vim/issues/843 2018-07-12 21:13:26 +02:00
Daniel Hahler
87d7c59c6e subprocess: listen: exit normally with EOFError
This is an expected case, since the parent closed normally, and
therefore the subprocess should exit with 0.
2018-07-11 12:56:55 +02:00
Daniel Hahler
f3c1f4c548 Script: improve ValueError for column
Ref: https://github.com/davidhalter/jedi/issues/1168
2018-07-11 12:55:18 +02:00
Dave Halter
d06e55aab5 The sys path might be lazy or not in a venv 2018-07-10 10:07:18 +02:00
Dave Halter
cef769ecd8 The encoding parameter should be used again (includes test), fixes #1167 2018-07-09 18:25:28 +02:00
Dave Halter
aa4dcc1631 Remove source_encoding from documentation (see #1167) 2018-07-09 18:12:27 +02:00
Dave Halter
a59e5a016f Actually use the fast_parser setting again 2018-07-05 21:31:03 +02:00
Dave Halter
37a40d53a8 Use an import name list as long as possible 2018-07-05 18:11:58 +02:00
Dave Halter
d8c0d8e5d2 Different _load_module API 2018-07-05 10:15:49 +02:00
Dave Halter
508ed7e5b8 Directly load modules if possible, with this it's not necessary anymore to use dotted_from_fs_path, also fixes #1013. 2018-07-05 10:03:05 +02:00
Dave Halter
a12d62e9c9 Don't mutate the sys.path. This is pretty nasty bug that fixes #1148 2018-07-04 08:40:05 +02:00
Dave Halter
2500112f6c Don't follow builtin imports anymore by default when follow_imports is on (goto) 2018-07-04 00:01:03 +02:00
Dave Halter
6cdc1bcd8a Add a changelog entry for the include_builtins change 2018-07-03 23:00:46 +02:00
Dave Halter
80831d79c2 additional_module_paths in usages never actually worked 2018-07-03 22:54:47 +02:00
Dave Halter
d857668292 Add include_builtins to usages, fixes #1131. 2018-07-03 22:53:19 +02:00
Dave Halter
f4aad8bbfe Finally make it possible to use auto_import_modules for packages
This means that you can now write 'from gi.repository import Gtk' and Gtk completions work.

It also means that other libraries could be used like that for speed or other reasons.

Fixes #531
2018-07-03 00:58:43 +02:00
Dave Halter
5b7984c4d4 Test auto_import_modules in a very basic way 2018-07-02 09:57:18 +02:00
Dave Halter
2b1cbe4d42 Fix a bug about fstring completion 2018-07-02 01:26:17 +02:00
Dave Halter
8ffdf6746f Comprehensions are also possible arguments. Fixes 1146 2018-07-01 03:33:24 +02:00
Dave Halter
a79a1fbef5 Merge branch 'parso' 2018-06-30 14:27:30 +02:00
Dave Halter
58141f1e1e Don't use requirements for now, and use the git version instead in tox 2018-06-30 14:14:52 +02:00
Dave Halter
e0e2be3027 Add a better comment about why people need to upgrade parso 2018-06-29 18:17:29 +02:00
Dave Halter
1e7662c3e1 Prepare release of 0.12.1 2018-06-29 18:10:41 +02:00
Dave Halter
68974aee58 Don't use internal parso APIs if possible 2018-06-29 10:04:03 +02:00
Dave Halter
c208d37ac4 Remove code that is no longer used, because parso was refactored. 2018-06-29 09:56:56 +02:00
Dave Halter
38474061cf Make jedi work with the next parso release 2018-06-29 09:54:57 +02:00
micbou
95f835a014 Force unicode when listing module names
pkgutil.iter_modules may return the module name as str instead of unicode on
Python 2.
2018-06-24 22:41:14 +02:00
micbou
282c6a2ba1 Use highest possible pickle protocol 2018-06-23 14:45:34 +02:00
Daniel Hahler
ea71dedaa1 Include stderr with "subprocess has crashed" exception (#1124)
* Include stderr with "subprocess has crashed" exception

This does not add it to the other similar exception raised from `kill`,
since this should be something like "was killed already" anyway.

* fixup! Include stderr with "subprocess has crashed" exception
2018-06-23 11:37:43 +02:00
micbou
106b11f1af Set stdout and stdin to binary mode on Python 2 and Windows 2018-06-22 00:08:53 +02:00
micbou
f9e90e863b Use system default buffering on Python 2 2018-06-21 19:50:51 +02:00
micbou
197aa22f29 Use cPickle on Python 2 if available
Attempt to load the C version of pickle on Python 2 as it is way faster.
2018-06-21 19:39:08 +02:00
Tarcisio Eduardo Moreira Crocomo
e96ebbe88f Add tests for DefaultDict support. 2018-06-17 11:28:12 +02:00
Tarcisio Eduardo Moreira Crocomo
55941e506b Add support for DefaultDict on jedi_typing.py. 2018-06-17 11:28:12 +02:00
Carl George
ff4a77391a Parse correct AST attribute for version
Earlier development versions of Python 3.7 added the docstring field to
AST nodes.  This was later reverted in Python 3.7.0b5.

https://bugs.python.org/issue29463
https://github.com/python/cpython/pull/7121
2018-06-16 14:43:17 +02:00
micbou
70c2fce9c2 Replace distutils.spawn.find_executable with shutil.which
The distutils.spawn.find_executable function is not available on stock system
Python 3 in recent Debian-based distributions. Since shutil.which is a better
alternative but not available on Python 2.7, we include a copy of that function
and use it in place of find_executable.
2018-06-07 21:07:22 +02:00
Dave Halter
5dab97a303 Add an error message, see also #1139. 2018-06-07 21:01:41 +02:00
Dave Halter
e2cd228aad Dict comprehension items call should now work, fixes #1129 2018-06-07 21:00:23 +02:00
micbou
c1014e00ca Fix flow analysis test
There is no seekable method for file objects on Python 2. Use flush instead.
2018-06-07 01:01:18 +02:00
Dave Halter
62a3f99594 Fix a wrong branch check, fixes #1128 2018-06-01 08:59:16 +02:00
Dave Halter
6ebe3f87a3 Drop 3.3 tests from travis
They are causing only problems now that Python3.3 is deprecated. See e.g. https://travis-ci.org/davidhalter/jedi/jobs/381881020.
Also as a solution approach: https://github.com/davidhalter/jedi/pull/1125.
2018-05-23 11:24:39 +02:00
Dave Halter
50812b5836 A simple yield should not cause an error, fixes #1117 2018-05-23 11:12:19 +02:00
Daniel Hahler
d10eff5625 Travis: report coverage also to codecov.io 2018-05-21 23:40:42 +02:00
Daniel Hahler
6748faa071 Fix _get_numpy_doc_string_cls: use cache
I've noticed that Jedi tries to import numpydoc a lot when using
jedi-vim's goto method in jedi_vim.py itself (via printing in Neovim's
VimPathFinder.find_spec).

This patch uses the cache before trying the import again and again.
2018-05-06 10:54:49 +02:00
Maxim Novikov
fc14aad8f2 Fix namespace autocompletion error 2018-05-03 09:12:17 +02:00
Daniel Hahler
3c909a9849 Travis: remove TOXENV=cov from allowed failures 2018-05-02 20:04:46 +02:00
Daniel Hahler
b94b45cfa1 Environment._get_version: add msgs with exceptions 2018-05-02 00:09:40 +02:00
Dave Halter
a95274d66f None/False/True are atom non-terminals in the syntax tree, fixes #1103 2018-05-01 23:43:49 +02:00
Dave Halter
8d48e7453a When searching submodules, use all of __path__, fixes #1105 2018-05-01 23:17:42 +02:00
Dave Halter
91499565a9 Specially crafted docstrings sometimes lead to errors, fixes #1103 2018-04-25 21:04:05 +02:00
Dave Halter
ba96c21f83 Follow up from the last async issue, fixes more related things about #1092. 2018-04-24 01:02:31 +02:00
Dave Halter
8494164b22 Fix an async funcdef issue, fixes 1092. 2018-04-24 00:41:18 +02:00
Dave Halter
4075c384e6 In some very rare cases it was possible to get an interpreter crash because of this bug. Fixes #1087 2018-04-23 21:26:51 +02:00
Dave Halter
0bcd1701f0 Start using our own monkeypatch function for some things 2018-04-23 21:26:51 +02:00
Dima Gerasimov
ceb5509170 Include function return type annotation in docstring if it is present 2018-04-23 21:20:21 +02:00
Dave Halter
88243d2408 Don't catch IndexError where we don't have to 2018-04-20 01:46:32 +02:00
micbou
5f37d08761 Extend create_environment to accept an executable path
Assume environments specified by the user are safe.
2018-04-19 21:36:44 +02:00
Daniel Hahler
aa6857d22d check_fs: handle FileNotFoundError
Ref: https://github.com/davidhalter/jedi-vim/pull/801
2018-04-17 23:40:25 +02:00
Dave Halter
bd7c65d963 Finally fix all the get_system_environment issues 2018-04-15 16:43:53 +02:00
Dave Halter
fe0ad8f1da Fix a test 2018-04-15 16:23:29 +02:00
Dave Halter
a21d77e8ad There's really no bin/activate needed for an environment to work 2018-04-15 16:15:20 +02:00
Dave Halter
ed2a0a8218 Document get_sys_path and change the signature of get_system_environment a bit 2018-04-15 16:12:07 +02:00
Dave Halter
22b0c0f1fe Rework the time cache. 2018-04-15 15:51:16 +02:00
Dave Halter
a972d49e88 Cache default environment 2018-04-15 15:28:05 +02:00
Dave Halter
bcd05f560e Require parso 0.2.0 at least 2018-04-15 14:06:21 +02:00
Dave Halter
c7c95e7e6d Set a release date 2018-04-15 13:54:27 +02:00
Dave Halter
9dece93c13 Don't install the latest pip version anymore in appveyor
It caused problems. Somehow appveyor (or pip?) changed something. By removing
the update mechanism it all works again. I don't really see why we need to
update anyway, so I guess I'm fine with how it is now.

Passing:
https://ci.appveyor.com/project/davidhalter/jedi/build/32

Not Passing anymore:
https://ci.appveyor.com/project/davidhalter/jedi/build/36
2018-04-15 13:40:26 +02:00
Dave Halter
d2f9e83b25 Fix some references 2018-04-15 12:55:33 +02:00
Dave Halter
28004a9ed9 Mention Virtualenv support in readme and features 2018-04-15 12:18:17 +02:00
Dave Halter
92cd9a30e2 Title case for Mänu :) 2018-04-15 12:12:32 +02:00
Dave Halter
a711c29b59 Better overview over functions in the documentation 2018-04-15 12:11:06 +02:00
Dave Halter
b531b6f4fd A small docs correction 2018-04-15 11:56:24 +02:00
Dave Halter
97b5dd9312 Remove the old static analysis stuff. It was never really used 2018-04-15 11:53:07 +02:00
Dave Halter
7b15c70551 Fix a lot of old docs code that doesn't exist anymore 2018-04-15 11:52:45 +02:00
Dave Halter
698d50b65b Remove the old parser documentation (that's now part of parso) 2018-04-15 11:42:34 +02:00
Dave Halter
940a8c7c9c Don't call it the plugin API anymore, that's confusing 2018-04-15 11:35:58 +02:00
Dave Halter
9465eb7881 Reorder some functions 2018-04-15 11:30:35 +02:00
Dave Halter
bb979a040d Adda lot of environment documentation to sphinx 2018-04-15 11:25:46 +02:00
Dave Halter
336087fcf8 find_python_environments -> find_system_environments 2018-04-14 15:46:16 +02:00
Dave Halter
45fb770033 A small refactoring 2018-04-14 15:38:32 +02:00
Dave Halter
9f07e7e352 Remove from_executable, were not really using it, yet. 2018-04-14 15:13:02 +02:00
Dave Halter
43ab9563e2 For the second time in a row it's called creationflags not creation_flags 2018-04-14 11:06:24 +02:00
Dave Halter
db21942c61 Refactor something small 2018-04-14 01:48:52 +02:00
Dave Halter
737154d657 Remove an unnecessary else 2018-04-14 01:47:17 +02:00
Dave Halter
81771264e0 CREATE_NO_WINDOW was introduced in Python 3.7 and didn't exist before 2018-04-13 22:05:08 +02:00
Dave Halter
fac773a60d The SameEnvironment should not load by default if it's a portable
find_python_environments should only find Python versions if they are actually installed on the system. If people copy virtualenvs around etc. it will find nothing instead.
2018-04-13 21:53:06 +02:00
Dave Halter
8af4fc5728 Do binary comparisons to get virtualenvs working and not just venvs 2018-04-13 21:45:07 +02:00
Dave Halter
ed80ed9437 Use the correct parameter name for creation flags 2018-04-13 19:04:53 +02:00
Dave Halter
83d635cbac Add a way to generalize Popen 2018-04-13 10:17:30 +02:00
Dave Halter
81623c6b5d Check the windows environments in a better way 2018-04-12 14:26:17 +02:00
Dave Halter
27419be56d Fix some issues with the latest changes 2018-04-12 14:24:18 +02:00
Dave Halter
b8e879bc53 DefaultEnvironment -> SameEnvironment 2018-04-12 09:00:19 +02:00
Dave Halter
f4317dadc4 Better docs for Environment 2018-04-12 08:59:18 +02:00
Dave Halter
bf0169480d Some docstrings 2018-04-12 08:58:06 +02:00
Dave Halter
5bb3b8c122 Make the Environment clearly non-public 2018-04-12 08:56:07 +02:00
Dave Halter
9ac7182fea Make some names public 2018-04-12 08:52:24 +02:00
Dave Halter
93a28c4230 Make sure Windows environments are safe 2018-04-12 08:50:31 +02:00
Dave Halter
323a85db7c Fix the module_name issue again 2018-04-10 21:27:47 +02:00
Dave Halter
1c91cfa9d6 Write a test for #1079 to avoid a regression in the future. 2018-04-10 19:23:20 +02:00
Dave Halter
9b17be9ecf Cleanup some of the module cache stuff 2018-04-10 19:16:18 +02:00
micbou
cf5f06f378 Do not cache unimportable compiled module (#1079)
From the issue:

The issue can be reproduced by getting the description of the QtBluetooth module from PyQt5 on Windows:

import jedi
completions = jedi.Script('import PyQt5.QtBlueTooth').completions()
completions[0].description

It's hard to write a test for it so we don't write one for it.
2018-04-10 19:10:05 +02:00
Dave Halter
81aa70b168 Merge branch 'master' of github.com:davidhalter/jedi 2018-04-10 09:19:55 +02:00
micbou
286dd92e35 Fix permissions of Python 3.6 on Travis 2018-04-10 09:19:12 +02:00
micbou
903bdf5fef Fix virtual environment tests 2018-04-10 09:19:12 +02:00
Dave Halter
764b67d232 Multiple inheritance completion in Python 2 did not work
Fixes #1071.
2018-04-10 08:58:30 +02:00
Dave Halter
777d9defc5 Give the run.py script an environment parameter 2018-04-10 08:42:58 +02:00
Dave Halter
b74ba7cd01 Fix an import 2018-04-09 01:47:31 +02:00
Dave Halter
519f54321e Merge the environment changes for Windows 2018-04-09 01:43:57 +02:00
Dave Halter
f4c14864a5 Better tests for venvs 2018-04-09 01:28:43 +02:00
Dave Halter
81d8c49119 Write a test for venvs 2018-04-08 23:04:57 +02:00
Dave Halter
0c19219143 Obviously Python 3 syntax cannot be used in Python 2 2018-04-08 21:38:03 +02:00
micbou
b3b6b798ff Find Python environments on Windows using the registry 2018-04-08 19:04:11 +02:00
Dave Halter
aa9f7fd304 Update the changelog about f-strings 2018-04-08 01:38:09 +02:00
Dave Halter
7fca4c332d Use the latest parso version from master. 2018-04-07 16:06:29 +02:00
Dave Halter
806ae13b71 Better goto definition for fstrings 2018-04-07 12:40:52 +02:00
Dave Halter
ec1c6e1e4d Fix an issue around the new grammar 2018-04-05 09:52:08 +02:00
Dave Halter
567c8b8097 Fix some fstring issues for now 2018-04-05 01:11:04 +02:00
Dave Halter
af956d70a3 Make a few modifications to always use the latest environment available. 2018-04-04 09:53:23 +02:00
Dave Halter
6b75519145 Better tests for fstrings 2018-03-31 18:38:09 +02:00
Dave Halter
43df60ff7d With the changes in parso, f-strings are now completable
Parso now uses one syntax tree for f-strings and the classic syntax tree.
2018-03-31 17:51:27 +02:00
Dave Halter
27655db8a9 With the changes in parso, f-strings are now completable 2018-03-31 17:07:47 +02:00
Dave Halter
538996d8d3 Fix lambda dynamic param searches, fixes #1070 2018-03-25 23:54:43 +02:00
Dave Halter
f5ba6de38c Cleanup the namespace lookups so that it also works for Python 3.7 2018-03-25 23:25:23 +02:00
Dave Halter
a6b47141cc Add a note about the fixed Windows tests in the changelog 2018-03-24 23:27:14 +01:00
Dave Halter
49235f8910 Add micbou to AUTHORS 2018-03-24 23:25:49 +01:00
Dave Halter
73d0506fb0 Add a badge for AppVeyor. Running tests for Windows 2018-03-24 23:16:08 +01:00
micbou
0fd8e728f5 Add comment explaining why test_versions is disabled on Windows 2018-03-24 22:52:41 +01:00
micbou
bf57fa16fc Add JEDI_TEST_ENVIRONMENT_EXECUTABLE for AppVeyor 2018-03-24 22:52:41 +01:00
micbou
e8b301ebf9 Add AppVeyor configuration 2018-03-24 22:52:41 +01:00
micbou
65a8ec6abc Improve venv_and_pths test
Python is not necessarily installed in /usr/bin. Execute Python to find the
real prefix.
2018-03-24 20:52:51 +01:00
micbou
c6635ccc55 Properly raise broken pipe exception 2018-03-24 12:02:06 +01:00
Dave Halter
04708819fb Remove SourceLair from products, because it's a paid product 2018-03-23 01:47:05 +01:00
Dave Halter
53e011909d Add a note to the readme. 2018-03-23 01:32:46 +01:00
Dave Halter
b5bc25fc0b Fix another windows issue 2018-03-23 01:21:07 +01:00
Dave Halter
106573f20d Merge branch 'master' of github.com:davidhalter/jedi 2018-03-23 00:57:40 +01:00
Dave Halter
c8bb41662e Merge the windows fixes 2018-03-23 00:55:23 +01:00
micbou
51b44032bd Fix paths from assignment test on Windows 2018-03-23 00:35:57 +01:00
micbou
2283b67836 Specify executable extension to detect virtual environment on Windows 2018-03-22 23:17:23 +01:00
Dave Halter
4e5cbe8832 Some code cleanup 2018-03-20 01:40:16 +01:00
Dave Halter
e6a3a8882c Fix another error that surfaced in pandas 2018-03-20 01:04:00 +01:00
Dave Halter
a61742728b Fix an issue with docstrings that contain errors 2018-03-20 00:56:53 +01:00
Dave Halter
305fd66e1c Upgrade the wx widgets paths 2018-03-19 00:05:04 +01:00
Dave Halter
5c06d9871a Somehow forgot about subscriptlist. Just ignore those for now.
Fixes #1010.
2018-03-18 17:24:45 +01:00
Dave Halter
6042706922 Fix the first issue in #1010
Somehow it was still possible with lists to recurse.
2018-03-18 17:09:44 +01:00
Dave Halter
1672613d04 colorama should always color, even if it's not a shell
I need this for some_script.py | less -R
2018-03-18 01:05:59 +01:00
Dave Halter
11b7e95ecc os.path.join completion speed test is sometimes slow, so give it a bit more of time 2018-03-17 21:41:26 +01:00
Dave Halter
60da6034c0 Fix some code_lines issues 2018-03-17 19:41:26 +01:00
Dave Halter
094affaf84 Remove stdout/stderr from subprocesses (redirected to /dev/null)
This means that the subprocess should now not crash anymore because of people
writing to stdout in c modules and stderr should be empty.

Fixes #793.
2018-03-17 14:14:00 +01:00
Dave Halter
5f0b34a520 Add the module_path again 2018-03-16 10:30:11 +01:00
Dave Halter
cc9c9fc781 Clean up the namedtuple test for #1060 2018-03-16 10:28:51 +01:00
Dave Halter
90a226f898 All modules now have a code_lines attribute, see #1062 2018-03-16 10:20:26 +01:00
Dave Halter
24e1f7e6f0 The release date for 0.12.0 should not be set, yet. See #1061. 2018-03-15 15:16:27 +01:00
Dave Halter
1eeb7cb6aa And now remove a pep0484 function that is no longer needed 2018-03-14 21:51:06 +01:00
Dave Halter
053618edd0 Some more code to a function 2018-03-14 21:49:17 +01:00
Dave Halter
ce0aa224f1 More rewriting of the pep0484 logic 2018-03-14 21:34:01 +01:00
Dave Halter
ae6d01abf5 Start moving some of the pep0484 comment code around 2018-03-14 21:27:29 +01:00
Dave Halter
e6469f46c7 Cleanup some instance stuff 2018-03-14 21:04:55 +01:00
Dave Halter
e5546a8ae6 Better docs for funciton annotations 2018-03-14 19:19:38 +01:00
Dave Halter
f5cf4c1954 Fix an error in param comments 2018-03-14 09:53:25 +01:00
Dave Halter
13ba74515d Catch parser errors instead of error recovery when splitting param comments 2018-03-14 09:49:59 +01:00
Dave Halter
afda309cb9 Merge branch 'function_comment' of https://github.com/wilfred/jedi into mypy-comments 2018-03-14 00:55:06 +01:00
Dave Halter
144a1def6c Fix a few version issues in tests 2018-03-13 22:59:07 +01:00
Dave Halter
5d36114be4 Use inspect.Parameter.kind for better differentiation between param types
Refs #292
2018-03-13 22:47:08 +01:00
Dave Halter
f9ec989835 Fix REPL completion param name completion
There were two issues:
1. The filter for parameters was wrong
2. In general the equal sign would not be added in some circumstances
2018-03-13 21:36:04 +01:00
Dave Halter
0dda740c5d Add keyword argument test for #292 2018-03-13 19:09:33 +01:00
Lee Danilek
b9903ede1b Support mypy annotations using comment syntax
This allows us to use mypy annotations for completion in Python 2.

Closes #946
2018-03-13 17:55:28 +00:00
Dave Halter
d0b8f9e5a2 Fix an interpreter test in Python 2 2018-03-12 20:49:27 +01:00
Dave Halter
378a5846db Clean up zombie subprocesses, fixes #1048 2018-03-12 20:06:02 +01:00
Dave Halter
5c1d979522 Fix an issue around __dir__ in the interpreter
Fixes #1027.
2018-03-12 01:46:12 +01:00
Dave Halter
e0c682977c Fix doctest for replstartup 2018-03-11 22:19:35 +01:00
Dave Halter
54a8db503d Fix shell completion issues and documentation
This issue was raised in #990. The completer was never used in Python3.4+,
because it was overwritten by Python's completer. Oddly enough it has always
worked in Python2.7/3.3.

The documentation was also slightly modified. os.path.join was always a
complex beast.
2018-03-09 22:39:00 +01:00
Dave Halter
c4be83759c Merge branch 'master' of github.com:davidhalter/jedi 2018-03-08 10:09:08 +01:00
Dave Halter
51e0d5d12f Fix issues with default parameters in functions and classes
Default parameters were resolved at the wrong starting position. Fixes #1044
2018-03-08 09:59:09 +01:00
Dave Halter
14ac6b11b9 Correct mistakes of lambda names 2018-03-08 09:52:35 +01:00
Dave Halter
23e7c5bd2a eval_element -> eval_node 2018-03-07 20:11:19 +01:00
Oliver Newman
8586d2a995 Fix VS Code Python extension link 2018-03-07 10:11:16 +01:00
Dave Halter
a85f2d1049 Use the correct class for params when used in names. Fixes #1006 2018-03-07 09:59:31 +01:00
Dave Halter
72be3e5247 Get rid of a regex warning, where escaping was not properly used in a normal string 2018-03-05 10:56:27 +01:00
Dave Halter
9e9c62a5ab Get rid of the imp library import in Python3 to avoid warnings, fixes #1001 2018-03-05 10:55:21 +01:00
Dave Halter
d063dadcf7 Don't need the tests from #122 2018-03-05 01:01:43 +01:00
Dave Halter
0144de1290 Refactor the namespace package tests 2018-03-05 00:55:35 +01:00
Elvis Pranskevichus
3fb95e3a58 Add a failing test for nested PEP420 namespace packages 2018-03-05 00:18:30 +01:00
Dave Halter
074d0d6d07 Include __init__.py files in search for the project directory, fixes #773 2018-03-04 21:36:59 +01:00
Dave Halter
2885938e74 Add pytest cache to gitignore 2018-03-04 18:29:00 +01:00
Dave Halter
95d36473fc Improve some documentation/a failing doctest 2018-03-04 18:29:00 +01:00
Dave Halter
d4af314b65 Fix the recursion error with globals
This generalizes the fix to actually fix a lot of potential recursion issues
with if_stmt.
2018-03-04 18:29:00 +01:00
Dave Halter
a3a39c0757 Always pop nodes in recursion detector 2018-03-04 18:29:00 +01:00
Dave Halter
c9a64bd1d3 Globals should be looked up with the same priority as other defined nodes. 2018-03-04 18:29:00 +01:00
ggilmore
3c9aa9ef25 fix set.append syntax error 2018-03-03 10:37:56 +01:00
Dave Halter
89c616a475 Add a few bits to the changelog 2018-03-02 08:59:28 +01:00
Dave Halter
4dc10e0d4b Autocompletion in comments should at least not fail
Fixes #968
2018-03-01 08:57:32 +01:00
Dave Halter
cbcc95c671 Fix the last async issue 2018-02-28 23:47:59 +01:00
Dave Halter
2abcd0b6a6 Fix a few numpydocs tests 2018-02-28 23:44:50 +01:00
Dave Halter
3820111d1e Fix some more await things 2018-02-28 23:30:20 +01:00
Dave Halter
dfa383c744 Fix a yield from test 2018-02-28 23:01:07 +01:00
Dave Halter
a41a4562d2 AbstractIterableMixin -> IterableMixin 2018-02-28 22:51:27 +01:00
Dave Halter
0d0213ee4c Support generator returns when used with yield from. 2018-02-28 22:35:58 +01:00
Dave Halter
80ee3b8fcf Show in a test that something doesn't work properly around async analysis 2018-02-27 18:19:46 +01:00
Dave Halter
6e24c120cf A few documentation improvements 2018-02-27 18:06:47 +01:00
Dave Halter
eeacdc33a1 Try to make the whole Builtin overwriting more abstract 2018-02-26 23:09:18 +01:00
Dave Halter
8e26017a05 Fix a small remaining issue in Python 2 2018-02-21 01:38:30 +01:00
Dave Halter
4d980d8bd0 Reorder tests to make the async stuff pass on all python versions 2018-02-21 01:28:37 +01:00
Dave Halter
2d4636da5b Fix for all python versions 2018-02-21 01:23:50 +01:00
Dave Halter
c1d06f4638 Getting more edget cases work in 3.6 for async 2018-02-21 01:11:59 +01:00
Dave Halter
de5d7961e8 Fix an issue with async for 2018-02-21 00:41:59 +01:00
Dave Halter
bc0210af70 Use the await method properly and just use it instead of some crazy things 2018-02-21 00:27:15 +01:00
Dave Halter
bf01b9d47c Refactor the way builtins can be overwritten by jedi's own contexts 2018-02-21 00:09:41 +01:00
T.Rzepka
e869e700c7 Documented the misbehavior of Windows pipes in combination with Python. 2018-02-20 21:41:49 +01:00
Dave Halter
5c8300e62a Move all the asynchronous contexts to a separate module 2018-02-19 09:43:50 +01:00
Dave Halter
f1c2aef963 Fix the merge issues. Now async stuff should at least partially work 2018-02-19 01:35:37 +01:00
Dave Halter
8f4b68ae39 Merge the async branch 2018-02-18 13:45:08 +01:00
T.Rzepka
29be40ae3f Add author's name to AUTHORS.txt 2018-02-17 13:52:59 +01:00
T.Rzepka
99130e7664 Fix for Python 2 and 3 on Windows, see #1037. 2018-02-17 13:49:10 +01:00
T.Rzepka
afee465518 Merge remote-tracking branch 'origin/master' 2018-02-17 12:14:24 +01:00
T.Rzepka
446de51402 Revert "Fix for Python 2 on Windows, see #1037."
This reverts commit b38d31b99d.
2018-02-17 12:09:35 +01:00
Dave Halter
98761f6994 Get rid of an unused import 2018-02-16 21:16:43 +01:00
Dave Halter
88f521ad82 Add the name always to the script module 2018-02-16 21:15:53 +01:00
Dave Halter
24adebb69d Add the travere_parents function to a utility directory 2018-02-16 21:07:36 +01:00
Dave Halter
81a30d61d6 Fix Python 2 old-school relative imports 2018-02-16 20:53:31 +01:00
Dave Halter
5453566352 Use the project path as a prefix, because many times it's used as a higher priority than other stuff 2018-02-16 20:37:03 +01:00
Dave Halter
482b5e63db Move the buildout_project stuff to a separate examples folder 2018-02-16 15:01:40 +01:00
Dave Halter
424b6ae907 Rename of buildout stuff 2018-02-16 14:56:49 +01:00
Dave Halter
ab212cb8aa Small rename 2018-02-16 14:53:45 +01:00
Dave Halter
c23005f988 Use generators instead of complicated return of lists 2018-02-16 14:50:07 +01:00
Dave Halter
039e7ba07b Some more sys path corrections.
The sys path should be defined more or less in the beginning and not be different for all modules
2018-02-16 14:39:01 +01:00
Dave Halter
6a11b7d89e Generalize the use of smart import paths
Now a lot more parts of the current scripts path are used as a sys path.
2018-02-16 12:40:31 +01:00
Dave Halter
863fbb3702 Better handling of smart sys path 2018-02-16 11:57:58 +01:00
Dave Halter
30cfdee325 Some simplifications 2018-02-16 10:21:43 +01:00
Dave Halter
fa9364307f Add comments to implicit namespaces and fix some minor things.
See #1005.
2018-02-15 20:25:07 +01:00
Dave Halter
9177c120f4 Merge the implicit namespace improvement (pkgutils.itermodules modification)
There are still a few issues that need to be addressed.
2018-02-15 20:08:58 +01:00
Dave Halter
76df356628 Relative imports should be working again even when used in more special occasions. Fixes #973
There are more fixes needed. Some things are just very unclean and might lead to further bugs.
2018-02-15 14:10:01 +01:00
Dave Halter
276f2d0b52 parent_module is not needed for loading modules 2018-02-14 20:42:53 +01:00
Dave Halter
2a56323c16 Try to avoid CachedMetaClass for modules 2018-02-13 20:47:43 +01:00
Dave Halter
36699b77b2 DOn't check the parser cache, that's parso's responsibility 2018-02-13 19:19:00 +01:00
Dave Halter
a52b6edd01 Better module loading 2018-02-12 21:17:21 +01:00
Dave Halter
a33cbc8ae3 Try to put all module loading in one place including namespace packages 2018-02-12 20:49:45 +01:00
Dave Halter
9fec494e84 Unify load_module access 2018-02-12 20:39:42 +01:00
Dave Halter
514eaf89c3 Prepare a test to eventually solve a relative import problem 2018-02-12 20:33:48 +01:00
T.Rzepka
b38d31b99d Fix for Python 2 on Windows, see #1037. 2018-02-11 22:37:57 +01:00
Dave Halter
26774c79fb Add a module cache that has a bit more capabilites 2018-02-10 21:21:25 +01:00
Dave Halter
92c76537d6 print_to_stderr needs to be used with one argument
See #1010.
2018-02-05 19:19:05 +01:00
Dave Halter
ac597815d7 Print errors that happen when importing certain objects
See also #1010.
2018-02-04 23:50:28 +01:00
Dave Halter
1ca4d21359 Use unicode literals, to avoid potential issues 2018-02-04 00:55:45 +01:00
Dave Halter
a123d0ff3d Merge branch 'master' of github.com:davidhalter/jedi 2018-02-03 23:28:57 +01:00
Anton Zub
18819292e6 Add author's name to AUTHORS.txt 2018-02-03 11:55:53 +01:00
Anton Zub
c2bb795151 Fix typo in docstring for imports.py 2018-02-03 11:55:53 +01:00
Dave Halter
fe0e41e9d6 Fix some more dict.get/dict.values stuff 2018-02-02 18:24:18 +01:00
Dave Halter
8028138e8c Implememnt dict.values for FakeDict to avoid a recursion error. Fixes #1014. 2018-02-02 09:34:40 +01:00
Dave Halter
e50609c48b Add better error reporting 2018-02-01 09:58:28 +01:00
Dave Halter
a7e864638a Use a better string 2018-02-01 01:21:59 +01:00
Dave Halter
2c945488b3 Add better debugging for an assert, see also #1010 2018-02-01 01:20:17 +01:00
Dave Halter
24b4e725b5 Make some things clearer about lazy contexts 2018-01-31 23:52:56 +01:00
Dave Halter
ebe8123b4c Finding the autocompletion stack is a bit more complicated than I initially thought
Fixes #968.
2018-01-31 08:45:01 +01:00
Dave Halter
522e7123ed Move the ahead of time tests to the pep0526 file 2018-01-31 00:18:17 +01:00
Dave Halter
3ae0560f1c Fix an issue where a default value was wrongly used 2018-01-31 00:11:30 +01:00
Dave Halter
2b9429be38 Update the ahead of time tests 2018-01-30 23:09:42 +01:00
Dave Halter
6b535c0503 Fix the last remaining issues with ahead of time annotations, see #982 2018-01-30 01:19:55 +01:00
Dave Halter
24561759f6 Fix a bug related to a wrong parametrization at one point 2018-01-30 01:17:09 +01:00
Dave Halter
d2c0de3eb0 Merge branch 'master' of https://github.com/johannesmik/jedi 2018-01-30 01:02:07 +01:00
Dave Halter
91d3c1f6d3 Force unicode on django paths 2018-01-30 00:40:50 +01:00
Dave Halter
60f89522a7 Forgot to add the examples folder 2018-01-30 00:08:17 +01:00
Dave Halter
c9fa335145 Fix a goto_assignments issue with a better internal API
Fixes #996.
2018-01-29 08:58:59 +01:00
Dave Halter
82dc83e150 Merge remote-tracking branch 'origin/master' into virtualenv 2018-01-29 00:56:55 +01:00
Dave Halter
febe65f737 Disable predefined name analysis (if stmts) for all non-analysis tasks
It's really buggy and caused quite a few issues
2018-01-29 00:56:29 +01:00
Dave Halter
8149eabdf9 Remove something that obviously never happened 2018-01-28 20:56:04 +01:00
Dave Halter
1304b4f9e8 Reorder some open flags for Python 2 2018-01-26 01:31:47 +01:00
Dave Halter
fc458a3c2a inspect.signature throws weird errors sometimes, just make it a bit simpler
Fixes #1031
2018-01-26 01:30:10 +01:00
Dave Halter
d44385c25e Fix the implicit namespace test 2018-01-26 01:16:08 +01:00
Dave Halter
68f15c90ac Undo most of the namespace changes and use module again
Is a module like every other module, because if you import an empty
folder foobar it will be available as an object:
<module 'foobar' (namespace)>.

See #1033.
2018-01-25 20:51:55 +01:00
Dave Halter
04fba28d35 Differentiate between namespace and module as a type
Also fixed a bug related to implicit namespace contexts, fixes #1033.
2018-01-25 20:35:54 +01:00
Dave Halter
33c9d21e35 Use Scripts for virtualenvs instead of bin for windows
Thanks @blueyed for the hint.
2018-01-25 19:55:10 +01:00
Daniel Hahler
6bab112bb7 test/completion/imports.py: fix typo in comment 2018-01-25 07:57:43 +01:00
Dave Halter
68f840de60 Refactor django path support 2018-01-24 19:13:05 +01:00
Dave Halter
e4559bef51 Fix project path finding 2018-01-23 20:30:27 +01:00
Dave Halter
e6f934de11 Add a repr for Project
Also remove setstate from it, since we intend to serialize it with json.
2018-01-23 19:21:50 +01:00
Dave Halter
4653c30fa4 Use the PathFinder, because the FileFinder doesn't work without suffixes
This feels more like importlib was intended to be used anyway.
2018-01-21 23:52:44 +01:00
Dave Halter
7fcbf7b5f0 Create the importer stuff Python2.7 and 3.3 2018-01-21 15:46:40 +01:00
Dave Halter
baacb5ec0d Trying to use the import machinery to import jedi/parso in python3.4+
The problem was that adding stuff to sys.path is simply very risky, because it already caused import issues (because enum was installed in 2.7). It was bound to cause other issues
2018-01-21 15:25:59 +01:00
Dave Halter
fef594373a Better reporting of internal errors 2018-01-20 22:56:51 +01:00
Dave Halter
41b24ab46b Better error handling for subprocesses
I don't really understand why this wasn't an issue before, but it looks like we have to
catch both IOError and and socket.error in Python2.
2018-01-20 22:56:26 +01:00
Dave Halter
ddafe41bb6 Another merge with master 2018-01-20 22:01:57 +01:00
Dave Halter
98a3da674c Ahhh another bug... A bit stupid of me not to run the tests 2018-01-20 22:00:41 +01:00
Dave Halter
fc315108f0 Get rid of a cwd to tmpdir, because with the subprocess it doesn't behave the same depending on which tests you run first 2018-01-20 21:56:56 +01:00
Dave Halter
d3a5025635 Hopefully the last merge with master 2018-01-20 21:48:55 +01:00
Dave Halter
256f001480 Another small issue in the tests 2018-01-20 21:47:31 +01:00
Dave Halter
94ce54e776 Merge with master again
Some bugs were still present in master
2018-01-20 21:45:55 +01:00
Dave Halter
20d64cf2b3 Fix issues with a recent refactoring 2018-01-20 21:21:58 +01:00
Dave Halter
27a3be3b42 Merge a commit that adds the build folder to the ignored paths 2018-01-20 20:38:56 +01:00
Dave Halter
9c0b344962 Small mistake when opening a file 2018-01-20 20:30:44 +01:00
Dave Halter
1476551257 Add better error reporting for potential issues 2018-01-20 19:33:47 +01:00
Dave Halter
d986c44b94 Merge with master
The deprecation of Python2.6 and the insertion of environments made it quite difficult to merge.
2018-01-20 19:32:59 +01:00
Dave Halter
877383b110 Add a test to avoid encoding issues. Fixes #1003 2018-01-20 18:28:29 +01:00
Dave Halter
16b463a646 Refactor to avoid having unicode decode errors by default 2018-01-19 19:23:11 +01:00
Dave Halter
19b3580ba7 Get rid of some potential issues when using pandas interactively
The issue was that the python_object passed in was not hashable. Since it's not
used anyway and it doesn't make sense there, just ignore it.

Fixes #916, #875
2018-01-18 19:54:20 +01:00
Dave Halter
c1394a82b5 Better error reporting, see #944 2018-01-18 19:12:32 +01:00
Dave Halter
609f59ce41 Fix issues with random tuples in TreeArgument.
Thanks @micbou for noticing it.
b92c7d3351
2018-01-18 09:54:19 +01:00
Dave Halter
2b577fcd5c Clarity 2018-01-17 19:24:08 +01:00
Dave Halter
d61aa50399 Remove the get_default_project caching 2018-01-17 19:23:30 +01:00
Dave Halter
263989c0ab Add a comment about why the project is None in the subprocess 2018-01-17 19:12:58 +01:00
Dave Halter
4e4f75c882 evaluate.project doesn't exist anymore. Eliminated code that used it 2018-01-17 19:11:20 +01:00
Dave Halter
bf0b6741aa At the moment, don't allow projects as an input to script 2018-01-17 09:57:58 +01:00
Dave Halter
9b4abeac4e Remove the old project 2018-01-17 09:55:53 +01:00
Dave Halter
9b5e3447d9 Make the new project API fully work in tests 2018-01-17 09:54:11 +01:00
Dave Halter
fe813292cf Try to migrate to the new project API 2018-01-16 23:56:35 +01:00
Dave Halter
9b9587a9dd Refactor to make configuratios of sys paths easier 2018-01-16 19:20:55 +01:00
Dave Halter
ddaf175b11 Use the evaluate.project sys path stuff for api.project 2018-01-16 10:03:28 +01:00
Dave Halter
c6240d5453 Cache the default project 2018-01-16 00:20:33 +01:00
Dave Halter
2a0e8f91d3 A possible introduction for projects 2018-01-15 23:57:08 +01:00
Dave Halter
b92c7d3351 Some cleaning up of code 2018-01-13 18:59:03 +01:00
micbou
3a0ac37ee8 Fix error when using generators with variable-length arguments 2018-01-13 18:56:34 +01:00
Dave Halter
999fb35914 Check for safe and unsafe environments when searching for them 2018-01-11 08:59:39 +01:00
Dave Halter
d815470e54 Remove the copyright notice from docs 2018-01-09 23:29:39 +01:00
Dave Halter
598ea1b89b Add Python 2.6 removal to changelog. Refs #1018 2018-01-07 14:39:08 +01:00
Dave Halter
cc460a7126 Merge branch 'master' into rm-2.6 2018-01-07 14:32:47 +01:00
Dave Halter
4e52acbf26 Using setup.py build should not include part of tests
It looks like that we have to not only exclude the test package but also 'test.*'. Thanks to @david-geiger for noticing this. Fixes #1024.
2018-01-07 14:13:40 +01:00
Hugo
73c71d6475 This test will be removed in the virtualenv branch 2018-01-07 10:40:36 +02:00
Hugo
7e449af4bd Revert changes to test/completion and test/static_analysis except for 2.6 comment removal 2018-01-07 10:40:36 +02:00
Hugo
3e8cd9f128 Use set literals 2018-01-07 10:40:36 +02:00
Hugo
3644c72efe Add version badges, use SVG badges, fix typos 2018-01-07 10:40:36 +02:00
Hugo
abe0f27e6a Add python_requires to help pip 2018-01-07 10:40:06 +02:00
Hugo
f56035182c Remove trailing semicolons 2018-01-07 10:40:06 +02:00
Hugo
cc623218e5 Replace function call with set literal 2018-01-07 10:40:06 +02:00
Hugo
5755fcb900 Replace comparison with None with equality operator 2018-01-07 10:40:06 +02:00
Hugo
8cf708d0d4 Remove redundant parentheses 2018-01-07 10:40:06 +02:00
Hugo
a7ac647498 Remove redundant character escape 2018-01-07 10:40:06 +02:00
Hugo
7821203d8e Use automatic formatters 2018-01-07 10:40:05 +02:00
Hugo
7c31ea9042 Drop support for EOL Python 2.6 2018-01-07 10:40:05 +02:00
Hugo
0334918d73 Ignore IDE metadata 2018-01-07 10:40:05 +02:00
Dave Halter
d00b6ddd10 Sith still used NotFoundError which doesn't exist anymore in jedi 2018-01-06 14:14:16 +01:00
Dave Halter
9e1cce6111 Ignore pypy in travis for now
There are too many issues in there and I won't look at them.
2018-01-06 14:13:15 +01:00
Dave Halter
7c78882967 A path to ignore in coveragerc was wrong 2018-01-06 14:12:26 +01:00
Dave Halter
9fdf265a75 Allowing the cov tests did not properly work. Trying again. 2018-01-06 13:54:29 +01:00
Dave Halter
a3c7aaa65e Somehow previously removed the allowed failurs of TOXENV=cov 2018-01-06 13:52:31 +01:00
Dave Halter
5844ad0900 Try to put env variables on one line 2018-01-06 13:49:06 +01:00
Dave Halter
e3d399cb08 Coverage was unfortunately excluded 2018-01-06 13:39:03 +01:00
Dave Halter
f36f5ec234 Merge with master 2018-01-06 12:31:29 +01:00
Dave Halter
a8124b625c Add a comment to refactoring that it's not in active development 2018-01-06 12:29:03 +01:00
Dave Halter
bc57b08863 Change coveragerc a bit
Remove some exclude lines, because they don't matter and don't appear in our code base.
The files that are excluded are cannot be measured (because it's part of a subprocess) or are statically analyzed.

In addition refactoring.py hasn't been in use for a long time.
2018-01-06 12:27:48 +01:00
Dave Halter
14ac874e1a Use Python3.4 for coverage. 2018-01-06 12:14:32 +01:00
Dave Halter
db47686159 Correct the issue about has_zlib
It was never actually the case that travis has Python versions without zlib. I didn't realize that modifying the sys path made it impossible to import the zlib library.
2018-01-06 03:34:37 +01:00
Dave Halter
e42796ca10 Move the zip tests to the environment 2018-01-06 02:26:30 +01:00
Dave Halter
99eed91206 Only execute the zipimport tests fully if zlib is available for the environment Python. 2018-01-06 02:11:33 +01:00
MohamedAlFahim
ad5ac8c492 Made 'l' a string + added warning
One of the helper methods is missing, so be extra careful.
2018-01-05 22:49:47 +01:00
MohamedAlFahim
03961bf051 Fixed refactoring.py docstring mistake
Updated parameters in docstring
2018-01-05 22:42:29 +01:00
Hugo
4199ac1a6f http -> https 2018-01-05 11:39:42 +01:00
Dave Halter
db1a4415b3 Some tests that involved jedi were actually a bit wrong and only worked in certain environments. 2018-01-05 00:48:40 +01:00
Dave Halter
4d896892a3 Skip some 3.3 tests for travis
Python 3.3 on travis doesn't have zip support compiled.
Just ignore tests since 3.3 is End-of-Life anyway.
2018-01-04 01:46:07 +01:00
Dave Halter
3d39ffd16c Skipping was done wrong 2018-01-03 19:45:46 +01:00
Maxim Novikov
ff65cf8ebe Use compatible syntax 2018-01-02 19:14:12 +01:00
Maxim Novikov
7f21fdfbc7 Fallback 2018-01-02 19:10:15 +01:00
Maxim Novikov
a2031d89b1 Fix tests 2018-01-02 18:24:38 +01:00
Maxim Novikov
78cbad0d08 Fix implicit namespace autocompletion. Resolves: #959 2018-01-02 18:17:48 +01:00
Dave Halter
d2cf2e69c9 Try a bit more if modifying the PATH is now possible. 2018-01-02 16:53:48 +01:00
Dave Halter
5e8d7a3c87 A comparison was wrong 2018-01-02 16:34:58 +01:00
Dave Halter
b2b8607bd6 A new version of the travis install script 2018-01-02 16:33:05 +01:00
Dave Halter
9c5ce5a8d2 Try to use the virtual env that was defined in the VIRTUAL_ENV variable, if possible. 2018-01-02 01:28:02 +01:00
Dave Halter
bcb3f02a01 If a subprocess gets killed by an OOM killer or whatever it should respawn and raise an InternalError 2018-01-02 00:56:22 +01:00
Dave Halter
7ff6871548 Merge Subprocess and CompiledSubprocess 2018-01-02 00:33:30 +01:00
Dave Halter
927aa2bd91 Try to recover from errors that are happening in subprocesses 2018-01-02 00:24:15 +01:00
Dave Halter
d93b613fd9 Move the default environment around 2018-01-01 20:37:50 +01:00
Dave Halter
966bd53b40 More travis trying 2017-12-31 14:10:08 +01:00
Dave Halter
39f82bc5aa Better debugging for travis 2017-12-31 02:49:18 +01:00
Dave Halter
2fbdf0dc09 Forgot to add the executable bit to the travis installer. 2017-12-30 23:27:11 +01:00
Dave Halter
39a456be41 Experiment with travis and installing packages differently 2017-12-30 23:08:32 +01:00
Dave Halter
9b1d3ff207 The tags should be annotated if possible 2017-12-30 14:05:14 +01:00
Dave Halter
b5e0df0e8c Remove 2.6 from travis 2017-12-30 05:21:56 +01:00
Dave Halter
9c9b52422d Correct the travis file 2017-12-30 05:14:26 +01:00
Dave Halter
b901ab9b0d Some refactoring to finally get tests working with py27 and 3 environments 2017-12-30 05:01:50 +01:00
Dave Halter
b716fb7dc6 Use the parser to check for certain namedtuple features
This fixes tests that are used with python 2 but a different environment
2017-12-30 04:41:19 +01:00
Dave Halter
4514373de6 Use unicode strings in test to pass some tests in Python 2 2017-12-30 04:36:59 +01:00
Dave Halter
a14f665b5a Use Script everywhere where cwd_at is used, otherwise Python 2.7 is annoying 2017-12-30 03:55:23 +01:00
Dave Halter
0ed9e1c249 The given sys_path gets converted to unicode now in py2 2017-12-30 03:40:01 +01:00
Dave Halter
f17afc6519 Try to avoid the pth tests not working because of the created virtualenv in tox 2017-12-30 03:15:28 +01:00
Dave Halter
e2629b680f Test if virtualenvs and pth files work 2017-12-30 00:02:14 +01:00
Dave Halter
7de04fb28d Move the module name searching to the subprocess 2017-12-29 21:10:00 +01:00
Dave Halter
68381e09c9 Move the last test out of test_regressions and delete the file
This also deletes a test that probably has become useful because the issue it tested was caused by code that doesn't exist anymore
2017-12-29 20:38:30 +01:00
Dave Halter
01ffd2f981 Move most of the regression tests into other test files 2017-12-29 20:26:53 +01:00
Dave Halter
918153d55a Cleanup test_regression tests 2017-12-29 20:13:04 +01:00
Dave Halter
ff4f7d5471 Move test_integration_keywrod to test_api/test_keyword 2017-12-29 20:05:37 +01:00
Dave Halter
c7266d65c1 Cleanup the docstring tests 2017-12-29 19:47:28 +01:00
Dave Halter
bf73fcbed4 More test_evaluate Script fixtures 2017-12-29 19:36:05 +01:00
Dave Halter
5fc755b0cf stdlib fixture conversions 2017-12-29 19:13:15 +01:00
Dave Halter
ac21fc376e More Script fixture conversions in test_evaluate 2017-12-29 19:08:09 +01:00
Dave Halter
2493e6ea16 Migrate parso integration to script fixture 2017-12-29 18:47:13 +01:00
Dave Halter
181fe38c17 Use Script in more places 2017-12-29 18:43:10 +01:00
Dave Halter
da211aa63d Use the Script fixture more generally 2017-12-29 18:40:17 +01:00
Dave Halter
38cacba385 Differentiate between different Python versions in a specific test 2017-12-29 16:09:48 +01:00
Dave Halter
5efd67758e Start replacing Script calls with a fixture
This is important to migrate all tests to specific fixtures.
2017-12-29 15:51:16 +01:00
Dave Halter
05804f1768 Monkeypatch the Unpickler in Python3.3
Needed to get Python3.3 working. See the comment in the commit.
2017-12-29 15:37:37 +01:00
Dave Halter
408293085c Try to pass the environment variable for JEDI_TEST_ENVIRONMENT to pytest over tox 2017-12-29 13:49:24 +01:00
Dave Halter
ed57f6172f Correct the two last unicode issues 2017-12-29 12:59:06 +01:00
Dave Halter
2ba46759fc Some repr went crazy 2017-12-29 03:58:02 +01:00
Dave Halter
95bf858669 Make it more clear for debugging where dynamic search ended 2017-12-29 03:54:12 +01:00
Dave Halter
d7de3f3fec Fix pep0484 comments 2017-12-29 03:29:29 +01:00
Dave Halter
a1051bd5f2 Better display of descriptors 2017-12-29 03:29:08 +01:00
Dave Halter
35158f693d Remove some of the last py27 errors that were caused in combination with 3.6 2017-12-29 02:45:11 +01:00
Dave Halter
ec9b8e8c02 Forgot to cast a map to a list 2017-12-29 02:39:35 +01:00
Dave Halter
52298510ed Fixing more py27 stuff 2017-12-29 02:02:34 +01:00
Dave Halter
b4f301e082 More unicode literals 2017-12-29 01:42:22 +01:00
Dave Halter
59c44fe499 Use force_unicode for all sys paths 2017-12-29 01:28:23 +01:00
Dave Halter
6e69326aa9 Add a print_to_stderr function in compatibility 2017-12-29 01:26:15 +01:00
Dave Halter
05b2906dcc Some more small improvements for Python 2 2017-12-28 23:58:19 +01:00
Dave Halter
4b72a89379 There were a few bugs in the previous commit 2017-12-28 23:25:09 +01:00
Dave Halter
ba81aa16a2 Use unicode in way more cases 2017-12-28 23:19:17 +01:00
Dave Halter
5755d5a4ee Use unicode always for getting special objects 2017-12-28 22:41:20 +01:00
Dave Halter
9906c4f9fc Skip the correct tests 2017-12-28 22:03:02 +01:00
Dave Halter
37d282e67b Always use the parser of the environment 2017-12-28 21:19:26 +01:00
Dave Halter
7a7c93a2e5 Try to test on travis with different jedi test environment variables 2017-12-28 02:46:53 +01:00
Dave Halter
c946d421d6 Try adding more automated tests to travis 2017-12-28 02:28:44 +01:00
Dave Halter
2d3b15b485 Fix potential issues with py2 analysis 2017-12-28 02:19:42 +01:00
Dave Halter
5b8ed7f615 Check for bytes and unicode in dicts for Python 2 2017-12-28 02:15:27 +01:00
Dave Halter
d1d4986667 Eliminate is_py3 usages 2017-12-28 01:55:39 +01:00
Dave Halter
6b6795c40c Don't use python_version directly on evaluator anymore 2017-12-28 01:44:59 +01:00
Dave Halter
31f1913b07 Use unicode always in getattr 2017-12-28 01:42:58 +01:00
Dave Halter
7accd4fae3 Fix an issue with the new behavior of special methods 2017-12-28 01:38:16 +01:00
Dave Halter
a7dea9e821 Fix some more py36 to py27 issues 2017-12-28 01:33:51 +01:00
Dave Halter
a8d3c46e9d Refactor some things regarding Python 2 support 2017-12-27 02:09:58 +01:00
Dave Halter
7e063ff7af Also don't cast do a string for other names 2017-12-26 15:44:00 +01:00
Dave Halter
8a82a5237d Casting to str is not necessary 2017-12-26 15:32:25 +01:00
Dave Halter
e925661aff Skip tests according to the current environment 2017-12-26 15:07:57 +01:00
Dave Halter
a7168db1ea Remove unused keyword code 2017-12-26 14:13:56 +01:00
Dave Halter
c43009d5dc Do more comparisons in the subprocess 2017-12-26 13:38:47 +01:00
Dave Halter
ab42e856fb Use unicode in compiled access 2017-12-26 03:24:26 +01:00
Dave Halter
6d70bd7d5c Remove unused code 2017-12-26 03:18:16 +01:00
Dave Halter
c3483344fe Refactor allowed_getattr_callback a bit to not raise random errors. 2017-12-24 12:55:32 +01:00
Dave Halter
993b0973c5 The default of one function was not actually used 2017-12-24 12:12:27 +01:00
Dave Halter
f494bb5848 The string_name of a Name should always be unicode 2017-12-24 04:05:28 +01:00
Dave Halter
4a366ab728 Refactor a bit and force unicode in some places and use an appropriate function name for it 2017-12-24 04:05:02 +01:00
Dave Halter
96a4fd7bd6 Fix a test fail because of the unicode changes 2017-12-24 03:53:27 +01:00
Dave Halter
fdd405f552 The environment selection had a bug 2017-12-24 03:47:35 +01:00
Dave Halter
085a9e0e33 More unicode conversions 2017-12-24 03:46:33 +01:00
Dave Halter
ee099a4ff7 Don't use getattr, use the abstractions 2017-12-24 03:39:28 +01:00
Dave Halter
40f1354f67 More unicode conversions 2017-12-24 03:35:15 +01:00
Dave Halter
a117f9f2e7 Avoid execution of Jedi in test setup
This makes testing Jedi potentially faster.
2017-12-24 03:25:43 +01:00
Dave Halter
5a06ea2699 Start using a lot more unicode literals for Python 2 2017-12-24 03:11:28 +01:00
Dave Halter
1f4e0dd22e Make it possible to explicitly state the version in pytest for different envs 2017-12-24 03:01:47 +01:00
Dave Halter
a38acdbe08 Use unicode sys paths always 2017-12-24 02:42:14 +01:00
Dave Halter
7bfca5bcd7 Don't cast bytes to strings when unpickling 2017-12-23 21:18:04 +01:00
Dave Halter
c3520bea65 By default enable cross Python version tests in tox 2017-12-23 19:59:37 +01:00
Dave Halter
7ad37fb976 Skip more tests if it's necessary. 2017-12-23 19:56:47 +01:00
Dave Halter
87666d72a1 Move the import logic to the subprocess 2017-12-23 17:59:56 +01:00
Dave Halter
473be114f3 Move even more import stuff to a separate function 2017-12-23 17:10:57 +01:00
Dave Halter
e2f8d53ee4 Move some import parts around to refactor it 2017-12-23 16:16:17 +01:00
Dave Halter
4ab7f7a0b0 Make ImplicitNamespaceContext a bit cleaner 2017-12-21 23:43:47 +01:00
Dave Halter
723d6515ac Change two tests that were written in a strange way 2017-12-20 10:36:39 +01:00
Dave Halter
a96f2c43df Add a way to skip typing tests in non default environments 2017-12-20 10:07:16 +01:00
Dave Halter
890dd2213d Use better error messages for import errors 2017-12-19 23:51:05 +01:00
Dave Halter
456ae20aac Start using the new virtualenv code
There used to be a lot of code to kind of understand virtualenvs. This can all be removed now, because this is done in a subprocess with the correct interpreter
2017-12-19 21:05:04 +01:00
Daniel Hahler
adace8d7cb sys_path_with_modifications: append local file
This fixes "goto" preferring a local module instead of a global one.

Fixes https://github.com/davidhalter/jedi/issues/995.
2017-12-19 20:51:20 +01:00
Dave Halter
96a67f9a4c Start using the correct parser for each environment 2017-12-19 19:19:35 +01:00
Dave Halter
a9ebd92c20 Add a way to specify environments in tox 2017-12-19 19:02:57 +01:00
Dave Halter
6780eba157 Fix sys_path propagation for builtins load_module 2017-12-18 20:16:58 +01:00
Dave Halter
aa40ef3140 A small refactoring 2017-12-18 20:03:23 +01:00
Dave Halter
5f2b49d039 Merge branch 'master' into virtualenv 2017-12-18 01:41:29 +01:00
Dave Halter
46b62b7bed evaluate/docstrings.py
Make some docstring stuff easier
2017-12-18 01:40:21 +01:00
Dave Halter
f5c7e3bb06 Don't import numpydoc in the beginning
There were issues in combination with importing it with subprocesses
2017-12-18 01:34:19 +01:00
Dave Halter
8b3ee75654 Ignore the build directory for pytest 2017-12-17 21:35:39 +01:00
Dave Halter
fe3e8a0867 Refactor environments a bit 2017-12-17 18:47:28 +01:00
Dave Halter
1c62db04ba Make it possible to get the right version parser for a certain environment 2017-12-16 00:30:47 +01:00
Dave Halter
d0732e58cc api.virtualenv -> api.environment 2017-12-15 18:20:35 +01:00
Dave Halter
0d7f93c019 DefaultEnvironment -> get_default_environment 2017-12-15 18:13:21 +01:00
Dave Halter
3cd5fa3c20 Better support for searching python environments 2017-12-15 12:19:52 +01:00
Dave Halter
f37089e54b Bump the version number 2017-12-15 10:47:14 +01:00
Dave Halter
69237c4aa6 Add a changelog for jedi 0.11.1 2017-12-15 10:45:27 +01:00
Dave Halter
c3efde3bfa Add an optimization around compiled dir() 2017-12-14 22:28:22 +01:00
Dave Halter
950cab2849 Fix a potential issue in evaluate/stdlib 2017-12-14 22:24:37 +01:00
Dave Halter
9d094b68f3 Cache the subprocess results 2017-12-14 22:23:59 +01:00
Dave Halter
94e2e92888 Remove unit test class from speed tests 2017-12-13 19:22:45 +01:00
Dave Halter
e03afc60ef Make get_repr static in access. 2017-12-13 19:16:29 +01:00
Dave Halter
0acb7dcb18 There was a bug in creating modules in a subprocess 2017-12-12 18:08:49 +01:00
Dave Halter
8003d30b06 Fix the Python 2.7 tests 2017-12-11 21:39:30 +01:00
Dave Halter
b196c6849b Don't try to pickle ellipsis 2017-12-11 20:55:34 +01:00
Dave Halter
fa2712a128 Ignore __main__ modules 2017-12-11 09:23:13 +01:00
Dave Halter
3a7bc92863 Use builtins_module instead of BUILTINS 2017-12-10 18:52:51 +01:00
Dave Halter
afb73876ac Don't use the pickler modification anymore. That doesn't work in other python versions and was in general a bit hard to do 2017-12-10 18:39:03 +01:00
Dave Halter
aa7319dba5 Remove the last test failures. 2017-12-09 17:38:45 +01:00
Dave Halter
649225333f Get the subprocess mostly working 2017-12-08 09:44:12 +01:00
Dave Halter
a210be8198 Don't use the create function anymore in compiled
Now the whole creation of builtin objects is abstract and was moved to subprocesses etc.
2017-12-06 15:26:29 +01:00
Dave Halter
13f8f37547 Use even more subprocess accesses 2017-12-06 15:16:27 +01:00
Dave Halter
42fb93dc01 Use the subprocess access to create acceses 2017-12-06 15:06:48 +01:00
Dave Halter
f09ca9fc20 Use access handles everywhere 2017-12-06 14:46:27 +01:00
Dave Halter
7db6d11c49 Create a way of accessing access objects through a subprocess 2017-12-06 14:18:10 +01:00
Dave Halter
34bd19ee8d Use a class instead of a dict in get_special_objects 2017-12-05 08:44:36 +01:00
Dave Halter
79071790da Move get_special_object 2017-12-05 00:32:39 +01:00
Dave Halter
542644ad19 Move load_module a bit around 2017-12-04 19:18:30 +01:00
Dave Halter
617b11c92b Move another usage of create to builtin_from_name 2017-12-04 08:57:43 +01:00
Dave Halter
3f25ba436c Use sys.modules instead of __import__
The module should already have been imported at this point. Plus if the __module__ was wrong it won't just randomly import something.
2017-12-04 00:21:12 +01:00
Dave Halter
85abc55e89 Remove unused code 2017-12-03 19:39:31 +01:00
Dave Halter
15d9e64281 Start creating access objects in a different way 2017-12-03 19:37:03 +01:00
Dave Halter
3c78aad8b1 Use create_simple_object for a lot of use cases 2017-12-02 01:59:48 +01:00
Dave Halter
2aa2005502 Move some of the compiled.create calls to compiled.builtin_from_name 2017-12-01 09:54:29 +01:00
Dave Halter
543f4f7ff2 Move some stuff from compiled to context 2017-11-29 01:03:01 +01:00
Dave Halter
4f04f7f09c Remove stuff from CompiledObject that didn't belong there and wasn't used 2017-11-29 00:42:16 +01:00
Dave Halter
37c3f0904d create_from_access -> _create_from_access 2017-11-29 00:25:30 +01:00
Dave Halter
ba0768bab6 Refactor a bit more and remove the parent_context parameter from create_from_access 2017-11-29 00:24:28 +01:00
Dave Halter
187a523e05 Isolate fake stuff a bit more 2017-11-29 00:18:43 +01:00
Dave Halter
10e9dac758 Simplify an if 2017-11-28 21:39:00 +01:00
Dave Halter
6ec3e50a16 Rewrite bases 2017-11-28 21:20:55 +01:00
Dave Halter
cce9a1cf6a Use create only for non access objects 2017-11-28 21:15:55 +01:00
Dave Halter
c1f31e0328 Some simplification of _create_from_access 2017-11-28 20:35:49 +01:00
Dave Halter
74495d518f Remove the old now unused fake code 2017-11-28 18:39:05 +01:00
Dave Halter
47114178e9 Fake context python code is now not the base for a lot of things anymore. It just gets executed. 2017-11-28 18:26:12 +01:00
Dave Halter
a2b08eabc6 Rename SelfNameFilter to SelfAttributeFilter 2017-11-28 18:06:00 +01:00
Dave Halter
85bda448b1 Simplify one if statement 2017-11-28 08:43:56 +01:00
Dave Halter
e69509b1d9 Refactor LazyInstanceName -> SelfName 2017-11-27 21:08:39 +01:00
Dave Halter
b31d928704 Fix all tests except fake docstring stuff 2017-11-26 22:49:07 +01:00
Dave Halter
02fb73655c Fix a slice test with a better helper function 2017-11-26 22:18:51 +01:00
Dave Halter
accf20226d Fix a few more tests 2017-11-26 22:07:13 +01:00
Dave Halter
85ce57a863 Creating objects works now a bit better but is a huge mess. 2017-11-26 18:26:02 +01:00
Dave Halter
e71f0062dd Get a lot of tests passing 2017-11-26 17:48:00 +01:00
Dave Halter
c266fb301b Make params work with access 2017-11-26 01:48:43 +01:00
Dave Halter
7263d8565b Add an access abstraction (only array tests work, yet)
The access abstraction will be the new way of accessing builtin objects. This way it will be easier to move that to another process
2017-11-25 19:47:49 +01:00
Dave Halter
52bc1be84e The check if we should add type completions is now a bit more obvious 2017-11-24 08:55:16 +01:00
Dave Halter
1a7fc512bc Eliminate CompiledObject.type 2017-11-23 21:50:18 +01:00
Dave Halter
4dc2ad281d Make some faked things private 2017-11-22 19:22:18 +01:00
Dave Halter
37533c5d51 Cleanup some compiled stuff. 2017-11-22 19:04:02 +01:00
Dave Halter
96a0003cb5 Progress in executing builtin stuff in submodules. 2017-11-20 21:02:40 +01:00
Dave Halter
87452639ad Exceptions now also work over the subprocess. 2017-11-17 01:54:05 +01:00
Dave Halter
4a7d715a57 Finally got compiled_objects and the access to them working 2017-11-17 01:42:27 +01:00
Dave Halter
73576b2a8b Progress when working with evaluators 2017-11-17 01:21:38 +01:00
Dave Halter
4136dcaf08 Make the subprocesses work and return the right sys paths for the different versions 2017-11-15 08:58:13 +01:00
Dave Halter
96149d2e6a Make it possible to connect to a subprocess to get the sys path 2017-11-14 18:25:37 +01:00
Dave Halter
46b81dfa6d Subprocess progress
Also add an enviornment variable to Script
2017-11-13 00:40:32 +01:00
Dave Halter
3a4dc94ee6 Use types instead of special objects (see also #988) 2017-11-12 13:12:04 +01:00
Dave Halter
969d029499 Some subprocess progress 2017-11-12 11:46:35 +01:00
Dave Halter
421ea222d1 virtualenv progress 2017-11-05 15:02:40 +01:00
Robin Roth
5b184fbd0c Use python3.6 for tox/sith 2017-11-01 14:18:29 +01:00
Robin Roth
dc43eba07b Support async/await syntax 2017-11-01 13:44:38 +01:00
Johannes Mikulasch
d9dc4ac840 Merge branch 'master' of https://github.com/johannesmik/jedi 2017-10-31 14:02:35 +01:00
Johannes Mikulasch
a1b60a978d add testcases for pep0484 ahead of time annotations 2017-10-31 13:57:10 +01:00
Johannes Mikulasch
6feac2a0ec add ahead of time annotations PEP 526 2017-10-31 12:58:56 +01:00
186 changed files with 7581 additions and 4167 deletions

View File

@@ -1,19 +1,15 @@
[run]
omit =
jedi/_compatibility.py
jedi/evaluate/site.py
jedi/evaluate/compiled/subprocess/__main__.py
jedi/__main__.py
# Is statically analyzed and not actually executed.
jedi/evaluate/jedi_typing.py
# For now this is not being used.
jedi/refactoring.py
[report]
# Regexes for lines to exclude from consideration
exclude_lines =
# Don't complain about missing debug-only code:
def __repr__
if self\.debug
# Don't complain if tests don't hit defensive assertion code:
raise AssertionError
raise NotImplementedError
# Don't complain if non-runnable code isn't run:
if 0:
if __name__ == .__main__.:

2
.gitignore vendored
View File

@@ -5,9 +5,11 @@
.tox
.coveralls.yml
.coverage
.idea
/build/
/docs/_build/
/dist/
jedi.egg-info/
record.json
/.cache/
/.pytest_cache

View File

@@ -1,32 +1,57 @@
language: python
sudo: false
sudo: true
python:
- 2.6
- 2.7
- 3.3
- 3.4
- 3.5
- 3.6
- pypy
- "3.7-dev"
env:
- JEDI_TEST_ENVIRONMENT=27
- JEDI_TEST_ENVIRONMENT=34
- JEDI_TEST_ENVIRONMENT=35
- JEDI_TEST_ENVIRONMENT=36
addons:
apt:
packages:
# Required to properly create a virtual environment with system Python 3.4.
- python3.4-venv
matrix:
allow_failures:
- python: pypy
- env: TOXENV=cov
- env: TOXENV=sith
- python: 3.7-dev
include:
- python: 3.5
env: TOXENV=cov
- python: 3.5
- python: 3.6
env:
- TOXENV=cov
- JEDI_TEST_ENVIRONMENT=36
- python: 3.6
env: TOXENV=sith
# For now ignore pypy, there are so many issues that we don't really need
# to run it.
#- python: pypy
- python: "3.7-dev"
before_install:
- ./travis_install.sh
# Need to add the path to the Python versions in the end. This might add
# something twice, but it doesn't really matter, because they are appended.
- export PATH=$PATH:/opt/python/3.5/bin
# 3.6 was not installed manually, but already is on the system. However
# it's not on path (unless 3.6 is selected).
- export PATH=$PATH:/opt/python/3.6/bin
install:
- pip install --quiet tox-travis
script:
- tox
after_script:
- if [ $TOXENV == "cov" ]; then
pip install --quiet coveralls;
coveralls;
- |
if [ $TOXENV == "cov" ]; then
pip install --quiet codecov coveralls
coverage xml
coverage report -m
coveralls
bash <(curl -s https://codecov.io/bash) -X gcov -X coveragepy -X search -X fix -X xcode -f coverage.xml
fi

View File

@@ -1,4 +1,4 @@
Main Authors
Main Authors
============
David Halter (@davidhalter) <davidhalter88@gmail.com>
@@ -46,5 +46,10 @@ Simon Ruggier (@sruggier)
Élie Gouzien (@ElieGouzien)
Robin Roth (@robinro)
Malte Plath (@langsamer)
Anton Zub (@zabulazza)
Maksim Novikov (@m-novikov) <mnovikov.work@gmail.com>
Tobias Rzepka (@TobiasRzepka)
micbou (@micbou)
Dima Gerasimov (@karlicoss) <karlicoss@gmail.com>
Note: (@user) means a github user name.

View File

@@ -3,6 +3,54 @@
Changelog
---------
0.13.2 (2018-12-15)
+++++++++++++++++++
- Fixed a bug that led to Jedi spawning a lot of subprocesses.
0.13.1 (2018-10-02)
+++++++++++++++++++
- Bugfixes, because tensorflow completions were still slow.
0.13.0 (2018-10-02)
+++++++++++++++++++
- A small release. Some bug fixes.
- Remove Python 3.3 support. Python 3.3 support has been dropped by the Python
foundation.
- Default environments are now using the same Python version as the Python
process. In 0.12.x, we used to load the latest Python version on the system.
- Added ``include_builtins`` as a parameter to usages.
- ``goto_assignments`` has a new ``follow_builtin_imports`` parameter that
changes the previous behavior slightly.
0.12.1 (2018-06-30)
+++++++++++++++++++
- This release forces you to upgrade parso. If you don't, nothing will work
anymore. Otherwise changes should be limited to bug fixes. Unfortunately Jedi
still uses a few internals of parso that make it hard to keep compatibility
over multiple releases. Parso >=0.3.0 is going to be needed.
0.12.0 (2018-04-15)
+++++++++++++++++++
- Virtualenv/Environment support
- F-String Completion/Goto Support
- Cannot crash with segfaults anymore
- Cleaned up import logic
- Understand async/await and autocomplete it (including async generators)
- Better namespace completions
- Passing tests for Windows (including CI for Windows)
- Remove Python 2.6 support
0.11.1 (2017-12-14)
+++++++++++++++++++
- Parso update - the caching layer was broken
- Better usages - a lot of internal code was ripped out and improved.
0.11.0 (2017-09-20)
+++++++++++++++++++

View File

@@ -5,4 +5,4 @@ Pull Requests are great.
3. Add your name to AUTHORS.txt
4. Push to your fork and submit a pull request.
**Try to use the PEP8 style guide.**
**Try to use the PEP8 style guide** (and it's ok to have a line length of 100 characters).

View File

@@ -2,16 +2,28 @@
Jedi - an awesome autocompletion/static analysis library for Python
###################################################################
.. image:: https://secure.travis-ci.org/davidhalter/jedi.png?branch=master
:target: http://travis-ci.org/davidhalter/jedi
:alt: Travis-CI build status
.. image:: https://img.shields.io/pypi/v/jedi.svg?style=flat
:target: https://pypi.python.org/pypi/jedi
:alt: PyPI version
.. image:: https://coveralls.io/repos/davidhalter/jedi/badge.png?branch=master
.. image:: https://img.shields.io/pypi/pyversions/jedi.svg
:target: https://pypi.python.org/pypi/jedi
:alt: Supported Python versions
.. image:: https://travis-ci.org/davidhalter/jedi.svg?branch=master
:target: https://travis-ci.org/davidhalter/jedi
:alt: Linux Tests
.. image:: https://ci.appveyor.com/api/projects/status/mgva3bbawyma1new/branch/master?svg=true
:target: https://ci.appveyor.com/project/davidhalter/jedi/branch/master
:alt: Windows Tests
.. image:: https://coveralls.io/repos/davidhalter/jedi/badge.svg?branch=master
:target: https://coveralls.io/r/davidhalter/jedi
:alt: Coverage Status
:alt: Coverage status
*If you have specific questions, please add an issue or ask on* `stackoverflow
*If you have specific questions, please add an issue or ask on* `Stack Overflow
<https://stackoverflow.com/questions/tagged/python-jedi>`_ *with the label* ``python-jedi``.
@@ -25,7 +37,7 @@ related names and to list all names in a Python file and infer them. Jedi
understands docstrings and you can use Jedi autocompletion in your REPL as
well.
Jedi uses a very simple API to connect with IDE's. There's a reference
Jedi uses a very simple API to connect with IDEs. There's a reference
implementation as a `VIM-Plugin <https://github.com/davidhalter/jedi-vim>`_,
which uses Jedi's autocompletion. We encourage you to use Jedi in your IDEs.
It's really easy.
@@ -39,13 +51,12 @@ Jedi can currently be used with the following editors/projects:
- Kate_ version 4.13+ supports it natively, you have to enable it, though. [`proof
<https://projects.kde.org/projects/kde/applications/kate/repository/show?rev=KDE%2F4.13>`_]
- Atom_ (autocomplete-python-jedi_)
- SourceLair_
- `GNOME Builder`_ (with support for GObject Introspection)
- `Visual Studio Code`_ (via `Python Extension <https://marketplace.visualstudio.com/items?itemName=donjayamanne.python>`_)
- `Visual Studio Code`_ (via `Python Extension <https://marketplace.visualstudio.com/items?itemName=ms-python.python>`_)
- Gedit (gedi_)
- wdb_ - Web Debugger
- `Eric IDE`_ (Available as a plugin)
- `Ipython 6.0.0+ <http://ipython.readthedocs.io/en/stable/whatsnew/version6.html>`_
- `IPython 6.0.0+ <https://ipython.readthedocs.io/en/stable/whatsnew/version6.html>`_
and many more!
@@ -72,7 +83,11 @@ Get the latest version from `github <https://github.com/davidhalter/jedi>`_
Docs are available at `https://jedi.readthedocs.org/en/latest/
<https://jedi.readthedocs.org/en/latest/>`_. Pull requests with documentation
enhancements and/or fixes are awesome and most welcome. Jedi uses `semantic
versioning <http://semver.org/>`_.
versioning <https://semver.org/>`_.
If you want to stay up-to-date (News / RFCs), please subscribe to this `github
thread <https://github.com/davidhalter/jedi/issues/1063>`_.:
Installation
@@ -96,8 +111,10 @@ understands, see: `Features
<https://jedi.readthedocs.org/en/latest/docs/features.html>`_. A list of
caveats can be found on the same page.
You can run Jedi on cPython 2.6, 2.7, 3.3, 3.4 or 3.5 but it should also
understand/parse code older than those versions.
You can run Jedi on CPython 2.7 or 3.4+ but it should also
understand/parse code older than those versions. Additionally you should be able
to use `Virtualenvs <https://jedi.readthedocs.org/en/latest/docs/api.html#environments>`_
very well.
Tips on how to use Jedi efficiently can be found `here
<https://jedi.readthedocs.org/en/latest/docs/features.html#recipes>`_.
@@ -105,7 +122,7 @@ Tips on how to use Jedi efficiently can be found `here
API
---
You can find the documentation for the `API here <https://jedi.readthedocs.org/en/latest/docs/plugin-api.html>`_.
You can find the documentation for the `API here <https://jedi.readthedocs.org/en/latest/docs/api.html>`_.
Autocompletion / Goto / Pydoc
@@ -123,7 +140,7 @@ The returned objects are very powerful and really all you might need.
Autocompletion in your REPL (IPython, etc.)
-------------------------------------------
Starting with Ipython `6.0.0` Jedi is a dependency of IPython. Autocompletion
Starting with IPython `6.0.0` Jedi is a dependency of IPython. Autocompletion
in IPython is therefore possible without additional configuration.
It's possible to have Jedi autocompletion in REPL modes - `example video <https://vimeo.com/122332037>`_.
@@ -178,7 +195,7 @@ Tests are also run automatically on `Travis CI
<https://travis-ci.org/davidhalter/jedi/>`_.
For more detailed information visit the `testing documentation
<https://jedi.readthedocs.org/en/latest/docs/testing.html>`_
<https://jedi.readthedocs.org/en/latest/docs/testing.html>`_.
Acknowledgements
@@ -193,7 +210,7 @@ Acknowledgements
.. _jedi-vim: https://github.com/davidhalter/jedi-vim
.. _youcompleteme: http://valloric.github.io/YouCompleteMe/
.. _youcompleteme: https://valloric.github.io/YouCompleteMe/
.. _deoplete-jedi: https://github.com/zchee/deoplete-jedi
.. _completor.vim: https://github.com/maralla/completor.vim
.. _Jedi.el: https://github.com/tkf/emacs-jedi
@@ -205,11 +222,10 @@ Acknowledgements
.. _anaconda: https://github.com/DamnWidget/anaconda
.. _wdb: https://github.com/Kozea/wdb
.. _TextMate: https://github.com/lawrenceakka/python-jedi.tmbundle
.. _Kate: http://kate-editor.org
.. _Kate: https://kate-editor.org
.. _Atom: https://atom.io/
.. _autocomplete-python-jedi: https://atom.io/packages/autocomplete-python-jedi
.. _SourceLair: https://www.sourcelair.com
.. _GNOME Builder: https://wiki.gnome.org/Apps/Builder
.. _Visual Studio Code: https://code.visualstudio.com/
.. _gedi: https://github.com/isamert/gedi
.. _Eric IDE: http://eric-ide.python-projects.org
.. _Eric IDE: https://eric-ide.python-projects.org

58
appveyor.yml Normal file
View File

@@ -0,0 +1,58 @@
environment:
matrix:
- TOXENV: py27
PYTHON_PATH: C:\Python27
JEDI_TEST_ENVIRONMENT: 27
- TOXENV: py27
PYTHON_PATH: C:\Python27
JEDI_TEST_ENVIRONMENT: 34
- TOXENV: py27
PYTHON_PATH: C:\Python27
JEDI_TEST_ENVIRONMENT: 35
- TOXENV: py27
PYTHON_PATH: C:\Python27
JEDI_TEST_ENVIRONMENT: 36
- TOXENV: py34
PYTHON_PATH: C:\Python34
JEDI_TEST_ENVIRONMENT: 27
- TOXENV: py34
PYTHON_PATH: C:\Python34
JEDI_TEST_ENVIRONMENT: 34
- TOXENV: py34
PYTHON_PATH: C:\Python34
JEDI_TEST_ENVIRONMENT: 35
- TOXENV: py34
PYTHON_PATH: C:\Python34
JEDI_TEST_ENVIRONMENT: 36
- TOXENV: py35
PYTHON_PATH: C:\Python35
JEDI_TEST_ENVIRONMENT: 27
- TOXENV: py35
PYTHON_PATH: C:\Python35
JEDI_TEST_ENVIRONMENT: 34
- TOXENV: py35
PYTHON_PATH: C:\Python35
JEDI_TEST_ENVIRONMENT: 35
- TOXENV: py35
PYTHON_PATH: C:\Python35
JEDI_TEST_ENVIRONMENT: 36
- TOXENV: py36
PYTHON_PATH: C:\Python36
JEDI_TEST_ENVIRONMENT: 27
- TOXENV: py36
PYTHON_PATH: C:\Python36
JEDI_TEST_ENVIRONMENT: 34
- TOXENV: py36
PYTHON_PATH: C:\Python36
JEDI_TEST_ENVIRONMENT: 35
- TOXENV: py36
PYTHON_PATH: C:\Python36
JEDI_TEST_ENVIRONMENT: 36
install:
- set PATH=%PYTHON_PATH%;%PYTHON_PATH%\Scripts;%PATH%
- pip install tox
build_script:
- tox

View File

@@ -1,11 +1,21 @@
import tempfile
import shutil
import os
from functools import partial
import pytest
import jedi
from jedi.api.environment import get_system_environment, InterpreterEnvironment
from jedi._compatibility import py_version
collect_ignore = ["setup.py"]
collect_ignore = [
'setup.py',
'__main__.py',
'jedi/evaluate/compiled/subprocess/__main__.py',
'build/',
'test/examples',
]
# The following hooks (pytest_configure, pytest_unconfigure) are used
@@ -29,6 +39,12 @@ def pytest_addoption(parser):
parser.addoption("--warning-is-error", action='store_true',
help="Warnings are treated as errors.")
parser.addoption("--env", action='store',
help="Execute the tests in that environment (e.g. 35 for python3.5).")
parser.addoption("--interpreter-env", "-I", action='store_true',
help="Don't use subprocesses to guarantee having safe "
"code execution. Useful for debugging.")
def pytest_configure(config):
global jedi_cache_directory_orig, jedi_cache_directory_temp
@@ -70,3 +86,36 @@ def clean_jedi_cache(request):
def restore():
settings.cache_directory = old
shutil.rmtree(tmp)
@pytest.fixture(scope='session')
def environment(request):
if request.config.option.interpreter_env:
return InterpreterEnvironment()
version = request.config.option.env
if version is None:
version = os.environ.get('JEDI_TEST_ENVIRONMENT', str(py_version))
return get_system_environment(version[0] + '.' + version[1:])
@pytest.fixture(scope='session')
def Script(environment):
return partial(jedi.Script, environment=environment)
@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.goto_definitions())
@pytest.fixture(scope='session')
def jedi_path():
return os.path.dirname(__file__)

View File

@@ -36,7 +36,7 @@ if [[ $tag_ref ]]; then
exit 1
fi
else
git tag $tag
git tag -a $tag
git push --tags
fi

View File

@@ -1,4 +1,4 @@
<h3>Github</h3>
<iframe src="http://ghbtns.com/github-btn.html?user=davidhalter&repo=jedi&type=watch&count=true&size=large"
<iframe src="https://ghbtns.com/github-btn.html?user=davidhalter&repo=jedi&type=watch&count=true&size=large"
frameborder="0" scrolling="0" width="170" height="30" allowtransparency="true"></iframe>
<br><br>

View File

@@ -19,7 +19,6 @@
{% endblock %}
{%- block footer %}
<div class="footer">
&copy; Copyright {{ copyright }}.
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a>.
</div>
{% if pagename == 'index' %}

View File

@@ -45,7 +45,7 @@ master_doc = 'index'
# General information about the project.
project = u'Jedi'
copyright = u'2012 - {today.year}, Jedi contributors'.format(today=datetime.date.today())
copyright = u'jedi contributors'
import jedi
from jedi.utils import version_info
@@ -274,7 +274,7 @@ autodoc_default_flags = []
# -- Options for intersphinx module --------------------------------------------
intersphinx_mapping = {
'http://docs.python.org/': None,
'https://docs.python.org/': None,
}

View File

@@ -1,6 +1,6 @@
.. include:: ../global.rst
.. _plugin-api-classes:
.. _api-classes:
API Return Classes
------------------

View File

@@ -1,7 +1,7 @@
.. include:: ../global.rst
The Plugin API
==============
API Overview
============
.. currentmodule:: jedi
@@ -11,7 +11,7 @@ editors/IDE autocompletion
If you want to use |jedi|, you first need to ``import jedi``. You then have
direct access to the :class:`.Script`. You can then call the functions
documented here. These functions return :ref:`API classes
<plugin-api-classes>`.
<api-classes>`.
Deprecations
@@ -24,16 +24,48 @@ The deprecation process is as follows:
the deprecated functionality.
API documentation
API Documentation
-----------------
API Interface
~~~~~~~~~~~~~
The API consists of a few different parts:
.. automodule:: jedi.api
- The main starting points for completions/goto: :class:`.Script` and :class:`.Interpreter`
- Helpful functions: :func:`.names`, :func:`.preload_module` and
:func:`.set_debug_function`
- :ref:`API Result Classes <api-classes>`
- :ref:`Python Versions/Virtualenv Support <environments>` with functions like
:func:`.find_system_environments` and :func:`.find_virtualenvs`
.. _api:
Static Analysis Interface
~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: jedi
.. autoclass:: jedi.Script
:members:
:undoc-members:
.. autoclass:: jedi.Interpreter
:members:
.. autofunction:: jedi.names
.. autofunction:: jedi.preload_module
.. autofunction:: jedi.set_debug_function
.. _environments:
Environments
~~~~~~~~~~~~
.. automodule:: jedi.api.environment
.. autofunction:: jedi.find_system_environments
.. autofunction:: jedi.find_virtualenvs
.. autofunction:: jedi.get_system_environment
.. autofunction:: jedi.create_environment
.. autofunction:: jedi.get_default_environment
.. autoexception:: jedi.InvalidPythonEnvironment
.. autoclass:: jedi.api.environment.Environment
:members:
Examples
--------

View File

@@ -7,7 +7,7 @@ Jedi Development
.. note:: This documentation is for Jedi developers who want to improve Jedi
itself, but have no idea how Jedi works. If you want to use Jedi for
your IDE, look at the `plugin api <plugin-api.html>`_.
your IDE, look at the `plugin api <api.html>`_.
Introduction
@@ -54,31 +54,15 @@ because that's where all the magic happens. I need to introduce the :ref:`parser
.. _parser:
Parser (parser/__init__.py)
Parser
~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: jedi.parser
Jedi used to have it's internal parser, however this is now a separate project
and is called `parso <http://parso.readthedocs.io>`_.
Parser Tree (parser/tree.py)
++++++++++++++++++++++++++++++++++++++++++++++++
.. automodule:: jedi.parser.tree
Class inheritance diagram:
.. inheritance-diagram::
Module
Class
Function
Lambda
Flow
ForStmt
Import
ExprStmt
Param
Name
CompFor
:parts: 1
The parser creates a syntax tree that |jedi| analyses and tries to understand.
The grammar that this parsers uses is very similar to the official Python
`grammar files <https://docs.python.org/3/reference/grammar.html>`_.
.. _evaluate:
@@ -87,16 +71,16 @@ Evaluation of python code (evaluate/__init__.py)
.. automodule:: jedi.evaluate
Evaluation Representation (evaluate/representation.py)
Evaluation Contexts (evaluate/base_context.py)
++++++++++++++++++++++++++++++++++++++++++++++++++++++
.. automodule:: jedi.evaluate.representation
.. automodule:: jedi.evaluate.base_context
.. inheritance-diagram::
jedi.evaluate.instance.TreeInstance
jedi.evaluate.representation.ClassContext
jedi.evaluate.representation.FunctionContext
jedi.evaluate.representation.FunctionExecutionContext
jedi.evaluate.context.instance.TreeInstance
jedi.evaluate.context.klass.ClassContext
jedi.evaluate.context.function.FunctionContext
jedi.evaluate.context.function.FunctionExecutionContext
:parts: 1
@@ -110,11 +94,11 @@ Name resolution (evaluate/finder.py)
.. _dev-api:
API (api.py and api_classes.py)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
API (api/__init__.py and api/classes.py)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The API has been designed to be as easy to use as possible. The API
documentation can be found `here <plugin-api.html>`_. The API itself contains
documentation can be found `here <api.html>`_. The API itself contains
little code that needs to be mentioned here. Generally I'm trying to be
conservative with the API. I'd rather not add new API features if they are not
necessary, because it's much harder to deprecate stuff than to add it later.
@@ -129,7 +113,6 @@ Core Extensions is a summary of the following topics:
- :ref:`Iterables & Dynamic Arrays <iterables>`
- :ref:`Dynamic Parameters <dynamic>`
- :ref:`Diff Parser <diff-parser>`
- :ref:`Docstrings <docstrings>`
- :ref:`Refactoring <refactoring>`
@@ -139,13 +122,13 @@ without some features.
.. _iterables:
Iterables & Dynamic Arrays (evaluate/iterable.py)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Iterables & Dynamic Arrays (evaluate/context/iterable.py)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To understand Python on a deeper level, |jedi| needs to understand some of the
dynamic features of Python like lists that are filled after creation:
.. automodule:: jedi.evaluate.iterable
.. automodule:: jedi.evaluate.context.iterable
.. _dynamic:
@@ -156,13 +139,6 @@ Parameter completion (evaluate/dynamic.py)
.. automodule:: jedi.evaluate.dynamic
.. _diff-parser:
Diff Parser (parser/diff.py)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: jedi.parser.python.diff
.. _docstrings:
Docstrings (evaluate/docstrings.py)

View File

@@ -20,11 +20,11 @@ make it work.
General Features
----------------
- python 2.6+ and 3.3+ support
- ignores syntax errors and wrong indentation
- can deal with complex module / function / class structures
- virtualenv support
- can infer function arguments from sphinx, epydoc and basic numpydoc docstrings,
- Python 2.7 and 3.4+ support
- Ignores syntax errors and wrong indentation
- Can deal with complex module / function / class structures
- Great Virtualenv support
- Can infer function arguments from sphinx, epydoc and basic numpydoc docstrings,
and PEP0484-style type hints (:ref:`type hinting <type-hinting>`)
@@ -47,13 +47,12 @@ Supported Python Features
- (nested) list comprehensions / ternary expressions
- relative imports
- ``getattr()`` / ``__getattr__`` / ``__getattribute__``
- function annotations (py3k feature, are ignored right now, but being parsed.
I don't know what to do with them.)
- function annotations
- class decorators (py3k feature, are being ignored too, until I find a use
case, that doesn't work with |jedi|)
- simple/usual ``sys.path`` modifications
- ``isinstance`` checks for if/while/assert
- namespace packages (includes ``pkgutil`` and ``pkg_resources`` namespaces)
- namespace packages (includes ``pkgutil``, ``pkg_resources`` and PEP420 namespaces)
- Django / Flask / Buildout support
@@ -64,7 +63,6 @@ Not yet implemented:
- manipulations of instances outside the instance variables without using
methods
- implicit namespace packages (Python 3.3+, `PEP 420 <https://www.python.org/dev/peps/pep-0420/>`_)
Will probably never be implemented:
@@ -88,7 +86,7 @@ etc.
**Security**
Security is an important issue for |jedi|. Therefore no Python code is
executed. As long as you write pure python, everything is evaluated
executed. As long as you write pure Python, everything is evaluated
statically. But: If you use builtin modules (``c_builtin``) there is no other
option than to execute those modules. However: Execute isn't that critical (as
e.g. in pythoncomplete, which used to execute *every* import!), because it
@@ -117,8 +115,7 @@ one of the following docstring/annotation syntax styles:
https://www.python.org/dev/peps/pep-0484/
function annotations (python 3 only; python 2 function annotations with
comments in planned but not yet implemented)
function annotations
::
@@ -129,7 +126,7 @@ comments in planned but not yet implemented)
node.| # complete here
assignment, for-loop and with-statement type hints (all python versions).
assignment, for-loop and with-statement type hints (all Python versions).
Note that the type hints must be on the same line as the statement
::
@@ -142,7 +139,7 @@ Note that the type hints must be on the same line as the statement
print(f + 3)
Most of the features in PEP-0484 are supported including the typing module
(for python < 3.5 you have to do ``pip install typing`` to use these),
(for Python < 3.5 you have to do ``pip install typing`` to use these),
and forward references.
Things that are missing (and this is not an exhaustive list; some of these
@@ -157,7 +154,7 @@ are planned, others might be hard to implement and provide little worth):
**Sphinx style**
http://sphinx-doc.org/domains.html#info-field-lists
http://www.sphinx-doc.org/en/stable/domains.html#info-field-lists
::

View File

@@ -49,7 +49,7 @@ Debian
~~~~~~
Debian packages are available in the `unstable repository
<http://packages.debian.org/search?keywords=python%20jedi>`__.
<https://packages.debian.org/search?keywords=python%20jedi>`__.
Others
~~~~~~

View File

@@ -1,36 +0,0 @@
.. _xxx:
Parser Tree
===========
Usage
-----
.. automodule:: jedi.parser.python
:members:
:undoc-members:
Parser Tree Base Class
----------------------
All nodes and leaves have these methods/properties:
.. autoclass:: jedi.parser.tree.NodeOrLeaf
:members:
:undoc-members:
Python Parser Tree
------------------
.. automodule:: jedi.parser.python.tree
:members:
:undoc-members:
:show-inheritance:
Utility
-------
.. autofunction:: jedi.parser.tree.search_ancestor

View File

@@ -1,106 +0,0 @@
This file is the start of the documentation of how static analysis works.
Below is a list of parser names that are used within nodes_to_execute.
------------ cared for:
global_stmt
exec_stmt # no priority
assert_stmt
if_stmt
while_stmt
for_stmt
try_stmt
(except_clause)
with_stmt
(with_item)
(with_var)
print_stmt
del_stmt
return_stmt
raise_stmt
yield_expr
file_input
funcdef
param
old_lambdef
lambdef
import_name
import_from
(import_as_name)
(dotted_as_name)
(import_as_names)
(dotted_as_names)
(dotted_name)
classdef
comp_for
(comp_if) ?
decorator
----------- add basic
test
or_test
and_test
not_test
expr
xor_expr
and_expr
shift_expr
arith_expr
term
factor
power
atom
comparison
expr_stmt
testlist
testlist1
testlist_safe
----------- special care:
# mostly depends on how we handle the other ones.
testlist_star_expr # should probably just work with expr_stmt
star_expr
exprlist # just ignore? then names are just resolved. Strange anyway, bc expr is not really allowed in the list, typically.
----------- ignore:
suite
subscriptlist
subscript
simple_stmt
?? sliceop # can probably just be added.
testlist_comp # prob ignore and care about it with atom.
dictorsetmaker
trailer
decorators
decorated
# always execute function arguments? -> no problem with stars.
# Also arglist and argument are different in different grammars.
arglist
argument
----------- remove:
tname # only exists in current Jedi parser. REMOVE!
tfpdef # python 2: tuple assignment; python 3: annotation
vfpdef # reduced in python 3 and therefore not existing.
tfplist # not in 3
vfplist # not in 3
--------- not existing with parser reductions.
small_stmt
import_stmt
flow_stmt
compound_stmt
stmt
pass_stmt
break_stmt
continue_stmt
comp_op
augassign
old_test
typedargslist # afaik becomes [param]
varargslist # dito
vname
comp_iter
test_nocond

View File

@@ -56,10 +56,6 @@ Atom:
- autocomplete-python-jedi_
SourceLair:
- SourceLair_
GNOME Builder:
- `GNOME Builder`_ `supports it natively
@@ -103,7 +99,7 @@ Using a custom ``$HOME/.pythonrc.py``
.. autofunction:: jedi.utils.setup_readline
.. _jedi-vim: https://github.com/davidhalter/jedi-vim
.. _youcompleteme: http://valloric.github.io/YouCompleteMe/
.. _youcompleteme: https://valloric.github.io/YouCompleteMe/
.. _deoplete-jedi: https://github.com/zchee/deoplete-jedi
.. _Jedi.el: https://github.com/tkf/emacs-jedi
.. _elpy: https://github.com/jorgenschaefer/elpy
@@ -113,10 +109,9 @@ Using a custom ``$HOME/.pythonrc.py``
.. _SynJedi: http://uvviewsoft.com/synjedi/
.. _wdb: https://github.com/Kozea/wdb
.. _TextMate: https://github.com/lawrenceakka/python-jedi.tmbundle
.. _kate: http://kate-editor.org/
.. _kate: https://kate-editor.org/
.. _autocomplete-python-jedi: https://atom.io/packages/autocomplete-python-jedi
.. _SourceLair: https://www.sourcelair.com
.. _GNOME Builder: https://wiki.gnome.org/Apps/Builder/
.. _gedi: https://github.com/isamert/gedi
.. _Eric IDE: http://eric-ide.python-projects.org
.. _Eric IDE: https://eric-ide.python-projects.org
.. _Python Extension: https://marketplace.visualstudio.com/items?itemName=donjayamanne.python

View File

@@ -23,8 +23,8 @@ Docs
docs/usage
docs/installation
docs/features
docs/plugin-api
docs/plugin-api-classes
docs/api
docs/api-classes
docs/settings
docs/development
docs/testing
@@ -37,4 +37,4 @@ Resources
- `Source Code on Github <https://github.com/davidhalter/jedi>`_
- `Travis Testing <https://travis-ci.org/davidhalter/jedi>`_
- `Python Package Index <http://pypi.python.org/pypi/jedi/>`_
- `Python Package Index <https://pypi.python.org/pypi/jedi/>`_

View File

@@ -23,7 +23,7 @@ example for the autocompletion feature:
... datetime.da'''
>>> script = jedi.Script(source, 3, len('datetime.da'), 'example.py')
>>> script
<Script: 'example.py'>
<Script: 'example.py' ...>
>>> completions = script.completions()
>>> completions #doctest: +ELLIPSIS
[<Completion: date>, <Completion: datetime>, ...]
@@ -36,8 +36,12 @@ As you see Jedi is pretty simple and allows you to concentrate on writing a
good text editor, while still having very good IDE features for Python.
"""
__version__ = '0.11.1'
__version__ = '0.13.2'
from jedi.api import Script, Interpreter, set_debug_function, \
preload_module, names
from jedi import settings
from jedi.api.environment import find_virtualenvs, find_system_environments, \
get_default_environment, InvalidPythonEnvironment, create_environment, \
get_system_environment
from jedi.api.exceptions import InternalError

View File

@@ -1,25 +1,22 @@
"""
To ensure compatibility from Python ``2.6`` - ``3.3``, a module has been
To ensure compatibility from Python ``2.7`` - ``3.x``, a module has been
created. Clearly there is huge need to use conforming syntax.
"""
import errno
import sys
import imp
import os
import re
import pkgutil
import warnings
import inspect
import subprocess
try:
import importlib
except ImportError:
pass
# Cannot use sys.version.major and minor names, because in Python 2.6 it's not
# a namedtuple.
is_py3 = sys.version_info[0] >= 3
is_py33 = is_py3 and sys.version_info[1] >= 3
is_py34 = is_py3 and sys.version_info[1] >= 4
is_py35 = is_py3 and sys.version_info[1] >= 5
is_py26 = not is_py3 and sys.version_info[1] < 7
py_version = int(str(sys.version_info[0]) + str(sys.version_info[1]))
@@ -35,28 +32,36 @@ class DummyFile(object):
del self.loader
def find_module_py34(string, path=None, fullname=None):
implicit_namespace_pkg = False
def find_module_py34(string, path=None, full_name=None, is_global_search=True):
spec = None
loader = None
spec = importlib.machinery.PathFinder.find_spec(string, path)
if hasattr(spec, 'origin'):
origin = spec.origin
implicit_namespace_pkg = origin == 'namespace'
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
# We try to disambiguate implicit namespace pkgs with non implicit namespace pkgs
if implicit_namespace_pkg:
fullname = string if not path else fullname
implicit_ns_info = ImplicitNSInfo(fullname, spec.submodule_search_locations._path)
return None, implicit_ns_info, False
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 None, implicit_ns_info, False
break
# we have found the tail end of the dotted path
if hasattr(spec, 'loader'):
loader = spec.loader
return find_module_py33(string, path, loader)
def find_module_py33(string, path=None, loader=None, fullname=None):
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
@@ -74,7 +79,7 @@ def find_module_py33(string, path=None, loader=None, fullname=None):
raise ImportError("Originally " + repr(e))
if loader is None:
raise ImportError("Couldn't find a loader for {0}".format(string))
raise ImportError("Couldn't find a loader for {}".format(string))
try:
is_package = loader.is_package(string)
@@ -109,7 +114,10 @@ def find_module_py33(string, path=None, loader=None, fullname=None):
return module_file, module_path, is_package
def find_module_pre_py33(string, path=None, fullname=None):
def find_module_pre_py34(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]
@@ -127,14 +135,7 @@ def find_module_pre_py33(string, path=None, fullname=None):
if loader:
is_package = loader.is_package(string)
is_archive = hasattr(loader, 'archive')
try:
module_path = loader.get_filename(string)
except AttributeError:
# fallback for py26
try:
module_path = loader._get_filename(string)
except AttributeError:
continue
module_path = loader.get_filename(string)
if is_package:
module_path = os.path.dirname(module_path)
if is_archive:
@@ -142,14 +143,13 @@ def find_module_pre_py33(string, path=None, fullname=None):
file = None
if not is_package or is_archive:
file = DummyFile(loader, string)
return (file, module_path, is_package)
return file, module_path, is_package
except ImportError:
pass
raise ImportError("No module named {0}".format(string))
raise ImportError("No module named {}".format(string))
find_module = find_module_py33 if is_py33 else find_module_pre_py33
find_module = find_module_py34 if is_py34 else find_module
find_module = find_module_py34 if is_py3 else find_module_pre_py34
find_module.__doc__ = """
Provides information about a module.
@@ -161,12 +161,81 @@ if the module is contained in a package.
"""
def _iter_modules(paths, prefix=''):
# Copy of pkgutil.iter_modules adapted to work with namespaces
for path in paths:
importer = pkgutil.get_importer(path)
if not isinstance(importer, importlib.machinery.FileFinder):
# We're only modifying the case for FileFinder. All the other cases
# still need to be checked (like zip-importing). Do this by just
# calling the pkgutil version.
for mod_info in pkgutil.iter_modules([path], prefix):
yield mod_info
continue
# START COPY OF pkutils._iter_file_finder_modules.
if importer.path is None or not os.path.isdir(importer.path):
return
yielded = {}
try:
filenames = os.listdir(importer.path)
except OSError:
# ignore unreadable directories like import does
filenames = []
filenames.sort() # handle packages before same-named modules
for fn in filenames:
modname = inspect.getmodulename(fn)
if modname == '__init__' or modname in yielded:
continue
# jedi addition: Avoid traversing special directories
if fn.startswith('.') or fn == '__pycache__':
continue
path = os.path.join(importer.path, fn)
ispkg = False
if not modname and os.path.isdir(path) and '.' not in fn:
modname = fn
# A few jedi modifications: Don't check if there's an
# __init__.py
try:
os.listdir(path)
except OSError:
# ignore unreadable directories like import does
continue
ispkg = True
if modname and '.' not in modname:
yielded[modname] = 1
yield importer, prefix + modname, ispkg
# END COPY
iter_modules = _iter_modules if py_version >= 34 else pkgutil.iter_modules
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
@@ -193,6 +262,7 @@ Usage::
"""
class Python3Method(object):
def __init__(self, func):
self.func = func
@@ -208,7 +278,7 @@ def use_metaclass(meta, *bases):
""" Create a class with a metaclass. """
if not bases:
bases = (object,)
return meta("HackClass", bases, {})
return meta("Py2CompatibilityMetaClass", bases, {})
try:
@@ -219,47 +289,65 @@ except AttributeError:
encoding = 'ascii'
def u(string):
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 is_py3:
return str(string)
if not isinstance(string, unicode):
return unicode(str(string), 'UTF-8')
if isinstance(string, bytes):
return unicode(string, encoding='UTF-8', errors=errors)
return string
def cast_path(obj):
"""
Take a bytes or str path and cast it to unicode.
Apparently it is perfectly fine to pass both byte and unicode objects into
the sys.path. This probably means that byte paths are normal at other
places as well.
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
import __builtin__ as builtins # noqa: F401
import ast
import ast # noqa: F401
def literal_eval(string):
# py3.0, py3.1 and py32 don't support unicode literals. Support those, I
# don't want to write two versions of the tokenizer.
if is_py3 and sys.version_info.minor < 3:
if re.match('[uU][\'"]', string):
string = string[1:]
return ast.literal_eval(string)
try:
from itertools import zip_longest
except ImportError:
from itertools import izip_longest as zip_longest # Python 2
from itertools import izip_longest as zip_longest # Python 2 # noqa: F401
try:
FileNotFoundError = FileNotFoundError
except NameError:
FileNotFoundError = IOError
try:
NotADirectoryError = NotADirectoryError
except NameError:
NotADirectoryError = IOError
def no_unicode_pprint(dct):
"""
@@ -273,6 +361,14 @@ def no_unicode_pprint(dct):
print(re.sub("u'", "'", s))
def print_to_stderr(*args):
if is_py3:
eval("print(*args, file=sys.stderr)")
else:
print >> sys.stderr, args
sys.stderr.flush()
def utf8_repr(func):
"""
``__repr__`` methods in Python 2 don't allow unicode objects to be
@@ -289,3 +385,205 @@ def utf8_repr(func):
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 sys.version_info[:2] == (3, 3):
"""
Monkeypatch the unpickler in Python 3.3. This is needed, because the
argument `encoding='bytes'` is not supported in 3.3, but badly needed to
communicate with Python 2.
"""
class NewUnpickler(pickle._Unpickler):
dispatch = dict(pickle._Unpickler.dispatch)
def _decode_string(self, value):
# Used to allow strings from Python 2 to be decoded either as
# bytes or Unicode strings. This should be used only with the
# STRING, BINSTRING and SHORT_BINSTRING opcodes.
if self.encoding == "bytes":
return value
else:
return value.decode(self.encoding, self.errors)
def load_string(self):
data = self.readline()[:-1]
# Strip outermost quotes
if len(data) >= 2 and data[0] == data[-1] and data[0] in b'"\'':
data = data[1:-1]
else:
raise pickle.UnpicklingError("the STRING opcode argument must be quoted")
self.append(self._decode_string(pickle.codecs.escape_decode(data)[0]))
dispatch[pickle.STRING[0]] = load_string
def load_binstring(self):
# Deprecated BINSTRING uses signed 32-bit length
len, = pickle.struct.unpack('<i', self.read(4))
if len < 0:
raise pickle.UnpicklingError("BINSTRING pickle has negative byte count")
data = self.read(len)
self.append(self._decode_string(data))
dispatch[pickle.BINSTRING[0]] = load_binstring
def load_short_binstring(self):
len = self.read(1)[0]
data = self.read(len)
self.append(self._decode_string(data))
dispatch[pickle.SHORT_BINSTRING[0]] = load_short_binstring
def load(file, fix_imports=True, encoding="ASCII", errors="strict"):
return NewUnpickler(file, fix_imports=fix_imports,
encoding=encoding, errors=errors).load()
def loads(s, fix_imports=True, encoding="ASCII", errors="strict"):
if isinstance(s, str):
raise TypeError("Can't load pickle from unicode string")
file = pickle.io.BytesIO(s)
return NewUnpickler(file, fix_imports=fix_imports,
encoding=encoding, errors=errors).load()
pickle.Unpickler = NewUnpickler
pickle.load = load
pickle.loads = loads
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.
except OSError:
if sys.platform == 'win32':
raise EOFError()
raise
def pickle_dump(data, file, protocol):
try:
pickle.dump(data, file, protocol)
# On Python 3.3 flush throws sometimes an error even though the writing
# operation should be completed.
file.flush()
# Python on Windows don't throw EPIPE errors for pipes. So reraise them with
# the correct type and error number.
except OSError:
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

View File

@@ -11,12 +11,13 @@ arguments.
"""
import os
import sys
import warnings
import parso
from parso.python import tree
from parso import python_bytes_to_unicode, split_lines
from jedi.parser_utils import get_executable_nodes, get_statement_of_position
from jedi._compatibility import force_unicode, is_py3
from jedi.parser_utils import get_executable_nodes
from jedi import debug
from jedi import settings
from jedi import cache
@@ -24,17 +25,17 @@ from jedi.api import classes
from jedi.api import interpreter
from jedi.api import helpers
from jedi.api.completion import Completion
from jedi.api.environment import InterpreterEnvironment
from jedi.api.project import get_default_project
from jedi.evaluate import Evaluator
from jedi.evaluate import imports
from jedi.evaluate import usages
from jedi.evaluate.project import Project
from jedi.evaluate.arguments import try_iter_content
from jedi.evaluate.helpers import get_module_names, evaluate_call_of_leaf
from jedi.evaluate.sys_path import dotted_path_in_sys_path
from jedi.evaluate.filters import TreeNameDefinition
from jedi.evaluate.filters import TreeNameDefinition, ParamName
from jedi.evaluate.syntax_tree import tree_name_to_contexts
from jedi.evaluate.context import ModuleContext
from jedi.evaluate.context.module import ModuleName
from jedi.evaluate.context.iterable import unpack_tuple_to_dict
# Jedi uses lots and lots of recursion. By setting this a little bit higher, we
@@ -74,15 +75,13 @@ class Script(object):
:param encoding: The encoding of ``source``, if it is not a
``unicode`` object (default ``'utf-8'``).
:type encoding: str
:param source_encoding: The encoding of ``source``, if it is not a
``unicode`` object (default ``'utf-8'``).
:type encoding: str
:param sys_path: ``sys.path`` to use during analysis of the script
:type sys_path: list
:param environment: TODO
:type sys_path: Environment
"""
def __init__(self, source=None, line=None, column=None, path=None,
encoding='utf-8', sys_path=None):
encoding='utf-8', sys_path=None, environment=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
@@ -92,55 +91,77 @@ class Script(object):
with open(path, 'rb') as f:
source = f.read()
# TODO do we really want that?
self._source = python_bytes_to_unicode(source, encoding, errors='replace')
self._code_lines = split_lines(self._source)
# Load the Python grammar of the current interpreter.
self._grammar = parso.load_grammar()
if sys_path is not None and not is_py3:
sys_path = list(map(force_unicode, sys_path))
# Load the Python grammar of the current interpreter.
project = get_default_project(
os.path.dirname(self.path)if path else os.getcwd()
)
# TODO deprecate and remove sys_path from the Script API.
if sys_path is not None:
project._sys_path = sys_path
self._evaluator = Evaluator(
project, environment=environment, script_path=self.path
)
self._project = project
debug.speed('init')
self._module_node, source = self._evaluator.parse_and_get_code(
code=source,
path=self.path,
encoding=encoding,
cache=False, # No disk cache, because the current script often changes.
diff_cache=settings.fast_parser,
cache_path=settings.cache_directory,
)
debug.speed('parsed')
self._code_lines = parso.split_lines(source, keepends=True)
self._code = source
line = max(len(self._code_lines), 1) if line is None else line
if not (0 < line <= len(self._code_lines)):
raise ValueError('`line` parameter is not in a valid range.')
line_len = len(self._code_lines[line - 1])
line_string = self._code_lines[line - 1]
line_len = len(line_string)
if line_string.endswith('\r\n'):
line_len -= 1
if line_string.endswith('\n'):
line_len -= 1
column = line_len if column is None else column
if not (0 <= column <= line_len):
raise ValueError('`column` parameter is not in a valid range.')
raise ValueError('`column` parameter (%d) is not in a valid range '
'(0-%d) for line %d (%r).' % (
column, line_len, line, line_string))
self._pos = line, column
self._path = path
cache.clear_time_caches()
debug.reset_time()
# Load the Python grammar of the current interpreter.
self._grammar = parso.load_grammar()
project = Project(sys_path=sys_path)
self._evaluator = Evaluator(self._grammar, project)
project.add_script_path(self.path)
debug.speed('init')
@cache.memoize_method
def _get_module_node(self):
return self._grammar.parse(
code=self._source,
path=self.path,
cache=False, # No disk cache, because the current script often changes.
diff_cache=True,
cache_path=settings.cache_directory
)
@cache.memoize_method
def _get_module(self):
module = ModuleContext(
self._evaluator,
self._get_module_node(),
self.path
)
name = '__main__'
if self.path is not None:
name = dotted_path_in_sys_path(self._evaluator.project.sys_path, self.path)
if name is not None:
imports.add_module(self._evaluator, name, module)
import_names = dotted_path_in_sys_path(self._evaluator.get_sys_path(), self.path)
if import_names is not None:
name = '.'.join(import_names)
module = ModuleContext(
self._evaluator, self._module_node, self.path,
code_lines=self._code_lines
)
imports.add_module_to_cache(self._evaluator, name, module)
return module
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, repr(self._orig_path))
return '<%s: %s %r>' % (
self.__class__.__name__,
repr(self._orig_path),
self._evaluator.environment,
)
def completions(self):
"""
@@ -156,6 +177,24 @@ class Script(object):
self._pos, self.call_signatures
)
completions = completion.completions()
def iter_import_completions():
for c in completions:
tree_name = c._name.tree_name
if tree_name is None:
continue
definition = tree_name.get_definition()
if definition is not None \
and definition.type in ('import_name', 'import_from'):
yield c
if len(list(iter_import_completions())) > 10:
# For now disable completions if there's a lot of imports that
# might potentially be resolved. This is the case for tensorflow
# and has been fixed for it. This is obviously temporary until we
# have a better solution.
self._evaluator.infer_enabled = False
debug.speed('completions end')
return completions
@@ -171,10 +210,9 @@ class Script(object):
:rtype: list of :class:`classes.Definition`
"""
module_node = self._get_module_node()
leaf = module_node.get_name_of_position(self._pos)
leaf = self._module_node.get_name_of_position(self._pos)
if leaf is None:
leaf = module_node.get_leaf_for_position(self._pos)
leaf = self._module_node.get_leaf_for_position(self._pos)
if leaf is None:
return []
@@ -188,24 +226,37 @@ class Script(object):
# the API.
return helpers.sorted_definitions(set(defs))
def goto_assignments(self, follow_imports=False):
def goto_assignments(self, follow_imports=False, follow_builtin_imports=False):
"""
Return the first definition found, while optionally following imports.
Multiple objects may be returned, because Python itself is a
dynamic language, which means depending on an option you can have two
different versions of a function.
:param follow_imports: The goto call will follow imports.
:param follow_builtin_imports: If follow_imports is True will decide if
it follow builtin imports.
:rtype: list of :class:`classes.Definition`
"""
def filter_follow_imports(names, check):
for name in names:
if check(name):
for result in filter_follow_imports(name.goto(), check):
yield result
new_names = list(filter_follow_imports(name.goto(), check))
found_builtin = False
if follow_builtin_imports:
for new_name in new_names:
if new_name.start_pos is None:
found_builtin = True
if found_builtin and not isinstance(name, imports.SubModuleName):
yield name
else:
for new_name in new_names:
yield new_name
else:
yield name
tree_name = self._get_module_node().get_name_of_position(self._pos)
tree_name = self._module_node.get_name_of_position(self._pos)
if tree_name is None:
return []
context = self._evaluator.create_context(self._get_module(), tree_name)
@@ -213,9 +264,7 @@ class Script(object):
if follow_imports:
def check(name):
if isinstance(name, ModuleName):
return False
return name.api_type == 'module'
return name.is_import()
else:
def check(name):
return isinstance(name, imports.SubModuleName)
@@ -225,7 +274,7 @@ class Script(object):
defs = [classes.Definition(self._evaluator, d) for d in set(names)]
return helpers.sorted_definitions(defs)
def usages(self, additional_module_paths=()):
def usages(self, additional_module_paths=(), **kwargs):
"""
Return :class:`classes.Definition` objects, which contain all
names that point to the definition of the name under the cursor. This
@@ -234,17 +283,31 @@ class Script(object):
.. todo:: Implement additional_module_paths
:param additional_module_paths: Deprecated, never ever worked.
:param include_builtins: Default True, checks if a usage is a builtin
(e.g. ``sys``) and in that case does not return it.
:rtype: list of :class:`classes.Definition`
"""
tree_name = self._get_module_node().get_name_of_position(self._pos)
if tree_name is None:
# Must be syntax
return []
if additional_module_paths:
warnings.warn(
"Deprecated since version 0.12.0. This never even worked, just ignore it.",
DeprecationWarning,
stacklevel=2
)
names = usages.usages(self._get_module(), tree_name)
def _usages(include_builtins=True):
tree_name = self._module_node.get_name_of_position(self._pos)
if tree_name is None:
# Must be syntax
return []
definitions = [classes.Definition(self._evaluator, n) for n in names]
return helpers.sorted_definitions(definitions)
names = usages.usages(self._get_module(), tree_name)
definitions = [classes.Definition(self._evaluator, n) for n in names]
if not include_builtins:
definitions = [d for d in definitions if not d.in_builtin_module()]
return helpers.sorted_definitions(definitions)
return _usages(**kwargs)
def call_signatures(self):
"""
@@ -263,7 +326,7 @@ class Script(object):
:rtype: list of :class:`classes.CallSignature`
"""
call_signature_details = \
helpers.get_call_signature_details(self._get_module_node(), self._pos)
helpers.get_call_signature_details(self._module_node, self._pos)
if call_signature_details is None:
return []
@@ -288,11 +351,11 @@ class Script(object):
def _analysis(self):
self._evaluator.is_analysis = True
module_node = self._get_module_node()
self._evaluator.analysis_modules = [module_node]
self._evaluator.analysis_modules = [self._module_node]
module = self._get_module()
try:
for node in get_executable_nodes(module_node):
context = self._get_module().create_context(node)
for node in get_executable_nodes(self._module_node):
context = module.create_context(node)
if node.type in ('funcdef', 'classdef'):
# Resolve the decorators.
tree_name_to_contexts(self._evaluator, context, node.children[1])
@@ -356,21 +419,28 @@ class Interpreter(Script):
except Exception:
raise TypeError("namespaces must be a non-empty list of dicts.")
super(Interpreter, self).__init__(source, **kwds)
environment = kwds.get('environment', None)
if environment is None:
environment = InterpreterEnvironment()
else:
if not isinstance(environment, InterpreterEnvironment):
raise TypeError("The environment needs to be an InterpreterEnvironment subclass.")
super(Interpreter, self).__init__(source, environment=environment, **kwds)
self.namespaces = namespaces
def _get_module(self):
parser_module = super(Interpreter, self)._get_module_node()
return interpreter.MixedModuleContext(
self._evaluator,
parser_module,
self._module_node,
self.namespaces,
path=self.path
path=self.path,
code_lines=self._code_lines,
)
def names(source=None, path=None, encoding='utf-8', all_scopes=False,
definitions=True, references=False):
definitions=True, references=False, environment=None):
"""
Returns a list of `Definition` objects, containing name parts.
This means you can call ``Definition.goto_assignments()`` and get the
@@ -389,17 +459,25 @@ def names(source=None, path=None, encoding='utf-8', all_scopes=False,
is_def = _def._name.tree_name.is_definition()
return definitions and is_def or references and not is_def
def create_name(name):
if name.parent.type == 'param':
cls = ParamName
else:
cls = TreeNameDefinition
is_module = name.parent.type == 'file_input'
return cls(
module_context.create_context(name if is_module else name.parent),
name
)
# Set line/column to a random position, because they don't matter.
script = Script(source, line=1, column=0, path=path, encoding=encoding)
script = Script(source, line=1, column=0, path=path, encoding=encoding, environment=environment)
module_context = script._get_module()
defs = [
classes.Definition(
script._evaluator,
TreeNameDefinition(
module_context.create_context(name if name.parent.type == 'file_input' else name.parent),
name
)
) for name in get_module_names(script._get_module_node(), all_scopes)
create_name(name)
) for name in get_module_names(script._module_node, all_scopes)
]
return sorted(filter(def_ref_filter, defs), key=lambda x: (x.line, x.column))

View File

@@ -5,19 +5,16 @@ the interesting information about completion and goto operations.
"""
import re
from parso.cache import parser_cache
from parso.python.tree import search_ancestor
from jedi._compatibility import u
from jedi import settings
from jedi.evaluate.utils import ignored, unite
from jedi.cache import memoize_method
from jedi.evaluate import imports
from jedi.evaluate import compiled
from jedi.evaluate.filters import ParamName
from jedi.evaluate.imports import ImportName
from jedi.evaluate.context import instance
from jedi.evaluate.context import ClassContext, FunctionContext, FunctionExecutionContext
from jedi.evaluate.context import ClassContext, FunctionExecutionContext
from jedi.api.keywords import KeywordName
@@ -125,13 +122,14 @@ class BaseDefinition(object):
Finally, here is what you can get from :attr:`type`:
>>> defs[0].type
>>> defs = [str(d.type) for d in defs] # It's unicode and in Py2 has u before it.
>>> defs[0]
'module'
>>> defs[1].type
>>> defs[1]
'class'
>>> defs[2].type
>>> defs[2]
'instance'
>>> defs[3].type
>>> defs[3]
'function'
"""
@@ -159,7 +157,7 @@ class BaseDefinition(object):
except IndexError:
pass
if name.api_type == 'module':
if name.api_type in 'module':
module_contexts = name.infer()
if module_contexts:
module_context, = module_contexts
@@ -259,7 +257,7 @@ class BaseDefinition(object):
@property
def description(self):
"""A textual description of the object."""
return u(self._name.string_name)
return self._name.string_name
@property
def full_name(self):
@@ -324,9 +322,9 @@ class BaseDefinition(object):
param_names = param_names[1:]
elif isinstance(context, (instance.AbstractInstanceContext, ClassContext)):
if isinstance(context, ClassContext):
search = '__init__'
search = u'__init__'
else:
search = '__call__'
search = u'__call__'
names = context.get_function_slot_names(search)
if not names:
return []
@@ -344,7 +342,7 @@ class BaseDefinition(object):
followed = list(self._name.infer())
if not followed or not hasattr(followed[0], 'py__call__'):
raise AttributeError()
raise AttributeError('There are no params defined on this.')
context = followed[0] # only check the first one.
return [Definition(self._evaluator, n) for n in get_param_names(context)]
@@ -355,10 +353,7 @@ class BaseDefinition(object):
return None
if isinstance(context, FunctionExecutionContext):
# TODO the function context should be a part of the function
# execution context.
context = FunctionContext(
self._evaluator, context.parent_context, context.tree_node)
context = context.function_context
return Definition(self._evaluator, context.name)
def __repr__(self):
@@ -377,8 +372,7 @@ class BaseDefinition(object):
if self.in_builtin_module():
return ''
path = self._name.get_root_context().py__file__()
lines = parser_cache[self._evaluator.grammar._hashed][path].lines
lines = self._name.get_root_context().code_lines
index = self._name.start_pos[0] - 1
start_index = max(index - before, 0)
@@ -406,9 +400,10 @@ class Completion(BaseDefinition):
and self.type == 'Function':
append = '('
if isinstance(self._name, ParamName) and self._stack is not None:
node_names = list(self._stack.get_node_names(self._evaluator.grammar._pgen_grammar))
if 'trailer' in node_names and 'argument' not in node_names:
if self._name.api_type == 'param' and self._stack is not None:
nonterminals = [stack_node.nonterminal for stack_node in self._stack]
if 'trailer' in nonterminals and 'argument' not in nonterminals:
# TODO this doesn't work for nested calls.
append += '='
name = self._name.string_name
@@ -525,7 +520,7 @@ class Definition(BaseDefinition):
if typ == 'function':
# For the description we want a short and a pythonic way.
typ = 'def'
return typ + ' ' + u(self._name.string_name)
return typ + ' ' + self._name.string_name
elif typ == 'param':
code = search_ancestor(tree_name, 'param').get_code(
include_prefix=False,
@@ -533,15 +528,14 @@ class Definition(BaseDefinition):
)
return typ + ' ' + code
definition = tree_name.get_definition() or tree_name
# Remove the prefix, because that's not what we want for get_code
# here.
txt = definition.get_code(include_prefix=False)
# Delete comments:
txt = re.sub('#[^\n]+\n', ' ', txt)
txt = re.sub(r'#[^\n]+\n', ' ', txt)
# Delete multi spaces/newlines
txt = re.sub('\s+', ' ', txt).strip()
txt = re.sub(r'\s+', ' ', txt).strip()
return txt
@property
@@ -555,7 +549,7 @@ class Definition(BaseDefinition):
.. todo:: Add full path. This function is should return a
`module.class.function` path.
"""
position = '' if self.in_builtin_module else '@%s' % (self.line)
position = '' if self.in_builtin_module else '@%s' % self.line
return "%s:%s%s" % (self.module_name, self.description, position)
@memoize_method
@@ -641,9 +635,18 @@ class CallSignature(Definition):
"""
return self._bracket_start_pos
@property
def _params_str(self):
return ', '.join([p.description[6:]
for p in self.params])
def __repr__(self):
return '<%s: %s index %s>' % \
(type(self).__name__, self._name.string_name, self.index)
return '<%s: %s index=%r params=[%s]>' % (
type(self).__name__,
self._name.string_name,
self._index,
self._params_str,
)
class _Help(object):

View File

@@ -1,7 +1,8 @@
from parso.python import token
from parso.python.token import PythonTokenTypes
from parso.python import tree
from parso.tree import search_ancestor, Leaf
from jedi._compatibility import Parameter
from jedi import debug
from jedi import settings
from jedi.api import classes
@@ -18,24 +19,21 @@ def get_call_signature_param_names(call_signatures):
for call_sig in call_signatures:
for p in call_sig.params:
# Allow protected access, because it's a public API.
tree_name = p._name.tree_name
# Compiled modules typically don't allow keyword arguments.
if tree_name is not None:
# Allow access on _definition here, because it's a
# public API and we don't want to make the internal
# Name object public.
tree_param = tree.search_ancestor(tree_name, 'param')
if tree_param.star_count == 0: # no *args/**kwargs
yield p._name
if p._name.get_kind() in (Parameter.POSITIONAL_OR_KEYWORD,
Parameter.KEYWORD_ONLY):
yield p._name
def filter_names(evaluator, completion_names, stack, like_name):
comp_dct = {}
if settings.case_insensitive_completion:
like_name = like_name.lower()
for name in completion_names:
if settings.case_insensitive_completion \
and name.string_name.lower().startswith(like_name.lower()) \
or name.string_name.startswith(like_name):
string = name.string_name
if settings.case_insensitive_completion:
string = string.lower()
if string.startswith(like_name):
new = classes.Completion(
evaluator,
name,
@@ -59,7 +57,8 @@ def get_user_scope(module_context, position):
def scan(scope):
for s in scope.children:
if s.start_pos <= position <= s.end_pos:
if isinstance(s, (tree.Scope, tree.Flow)):
if isinstance(s, (tree.Scope, tree.Flow)) \
or s.type in ('async_stmt', 'async_funcdef'):
return scan(s) or s
elif s.type in ('suite', 'decorated'):
return scan(s)
@@ -123,11 +122,11 @@ class Completion:
grammar = self._evaluator.grammar
try:
self.stack = helpers.get_stack_at_position(
self.stack = stack = helpers.get_stack_at_position(
grammar, self._code_lines, self._module_node, self._position
)
except helpers.OnErrorLeaf as e:
self.stack = None
self.stack = stack = None
if e.error_leaf.value == '.':
# After ErrorLeaf's that are dots, we will not do any
# completions since this probably just confuses the user.
@@ -136,10 +135,10 @@ class Completion:
return self._global_completions()
allowed_keywords, allowed_tokens = \
helpers.get_possible_completion_types(grammar._pgen_grammar, self.stack)
allowed_transitions = \
list(stack._allowed_transition_names_and_token_types())
if 'if' in allowed_keywords:
if 'if' in allowed_transitions:
leaf = self._module_node.get_leaf_for_position(self._position, include_prefixes=True)
previous_leaf = leaf.get_previous_leaf()
@@ -165,50 +164,52 @@ class Completion:
# Compare indents
if stmt.start_pos[1] == indent:
if type_ == 'if_stmt':
allowed_keywords += ['elif', 'else']
allowed_transitions += ['elif', 'else']
elif type_ == 'try_stmt':
allowed_keywords += ['except', 'finally', 'else']
allowed_transitions += ['except', 'finally', 'else']
elif type_ == 'for_stmt':
allowed_keywords.append('else')
allowed_transitions.append('else')
completion_names = list(self._get_keyword_completion_names(allowed_keywords))
completion_names = list(self._get_keyword_completion_names(allowed_transitions))
if token.NAME in allowed_tokens or token.INDENT in allowed_tokens:
if any(t in allowed_transitions for t in (PythonTokenTypes.NAME,
PythonTokenTypes.INDENT)):
# This means that we actually have to do type inference.
symbol_names = list(self.stack.get_node_names(grammar._pgen_grammar))
nonterminals = [stack_node.nonterminal for stack_node in stack]
nodes = list(self.stack.get_nodes())
nodes = [node for stack_node in stack for node in stack_node.nodes]
if nodes and nodes[-1] in ('as', 'def', 'class'):
# No completions for ``with x as foo`` and ``import x as foo``.
# Also true for defining names as a class or function.
return list(self._get_class_context_completions(is_function=True))
elif "import_stmt" in symbol_names:
level, names = self._parse_dotted_names(nodes, "import_from" in symbol_names)
elif "import_stmt" in nonterminals:
level, names = self._parse_dotted_names(nodes, "import_from" in nonterminals)
only_modules = not ("import_from" in symbol_names and 'import' in nodes)
only_modules = not ("import_from" in nonterminals and 'import' in nodes)
completion_names += self._get_importer_names(
names,
level,
only_modules=only_modules,
)
elif symbol_names[-1] in ('trailer', 'dotted_name') and nodes[-1] == '.':
elif nonterminals[-1] in ('trailer', 'dotted_name') and nodes[-1] == '.':
dot = self._module_node.get_leaf_for_position(self._position)
completion_names += self._trailer_completions(dot.get_previous_leaf())
else:
completion_names += self._global_completions()
completion_names += self._get_class_context_completions(is_function=False)
if 'trailer' in symbol_names:
if 'trailer' in nonterminals:
call_signatures = self._call_signatures_method()
completion_names += get_call_signature_param_names(call_signatures)
return completion_names
def _get_keyword_completion_names(self, keywords_):
for k in keywords_:
yield keywords.keyword(self._evaluator, k).name
def _get_keyword_completion_names(self, allowed_transitions):
for k in allowed_transitions:
if isinstance(k, str) and k.isalpha():
yield keywords.KeywordName(self._evaluator, k)
def _global_completions(self):
context = get_user_scope(self._module_context, self._position)

407
jedi/api/environment.py Normal file
View File

@@ -0,0 +1,407 @@
"""
Environments are a way to activate different Python versions or Virtualenvs for
static analysis. The Python binary in that environment is going to be executed.
"""
import os
import sys
import hashlib
import filecmp
from collections import namedtuple
from jedi._compatibility import highest_pickle_protocol, which
from jedi.cache import memoize_method, time_cache
from jedi.evaluate.compiled.subprocess import CompiledSubprocess, \
EvaluatorSameProcess, EvaluatorSubprocess
import parso
_VersionInfo = namedtuple('VersionInfo', 'major minor micro')
_SUPPORTED_PYTHONS = ['3.7', '3.6', '3.5', '3.4', '3.3', '2.7']
_SAFE_PATHS = ['/usr/bin', '/usr/local/bin']
_CURRENT_VERSION = '%s.%s' % (sys.version_info.major, sys.version_info.minor)
class InvalidPythonEnvironment(Exception):
"""
If you see this exception, the Python executable or Virtualenv you have
been trying to use is probably not a correct Python version.
"""
class _BaseEnvironment(object):
@memoize_method
def get_grammar(self):
version_string = '%s.%s' % (self.version_info.major, self.version_info.minor)
return parso.load_grammar(version=version_string)
@property
def _sha256(self):
try:
return self._hash
except AttributeError:
self._hash = _calculate_sha256_for_file(self.executable)
return self._hash
def _get_info():
return (
sys.executable,
sys.prefix,
sys.version_info[:3],
)
class Environment(_BaseEnvironment):
"""
This class is supposed to be created by internal Jedi architecture. You
should not create it directly. Please use create_environment or the other
functions instead. It is then returned by that function.
"""
_subprocess = None
def __init__(self, executable):
self._start_executable = executable
# Initialize the environment
self._get_subprocess()
def _get_subprocess(self):
if self._subprocess is not None and not self._subprocess.is_crashed:
return self._subprocess
try:
self._subprocess = CompiledSubprocess(self._start_executable)
info = self._subprocess._send(None, _get_info)
except Exception as exc:
raise InvalidPythonEnvironment(
"Could not get version information for %r: %r" % (
self._start_executable,
exc))
# Since it could change and might not be the same(?) as the one given,
# set it here.
self.executable = info[0]
"""
The Python executable, matches ``sys.executable``.
"""
self.path = info[1]
"""
The path to an environment, matches ``sys.prefix``.
"""
self.version_info = _VersionInfo(*info[2])
"""
Like ``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):
version = '.'.join(str(i) for i in self.version_info)
return '<%s: %s in %s>' % (self.__class__.__name__, version, self.path)
def get_evaluator_subprocess(self, evaluator):
return EvaluatorSubprocess(evaluator, self._get_subprocess())
@memoize_method
def get_sys_path(self):
"""
The sys path for this environment. Does not include potential
modifications like ``sys.path.append``.
:returns: list of str
"""
# It's pretty much impossible to generate the sys path without actually
# executing Python. The sys path (when starting with -S) itself depends
# on how the Python version was compiled (ENV variables).
# If you omit -S when starting Python (normal case), additionally
# site.py gets executed.
return self._get_subprocess().get_sys_path()
class SameEnvironment(Environment):
def __init__(self):
self._start_executable = self.executable = sys.executable
self.path = sys.prefix
self.version_info = _VersionInfo(*sys.version_info[:3])
class InterpreterEnvironment(_BaseEnvironment):
def __init__(self):
self.version_info = _VersionInfo(*sys.version_info[:3])
def get_evaluator_subprocess(self, evaluator):
return EvaluatorSameProcess(evaluator)
def get_sys_path(self):
return sys.path
def _get_virtual_env_from_var():
"""Get virtualenv environment from VIRTUAL_ENV environment variable.
It uses `safe=False` with ``create_environment``, because the environment
variable is considered to be safe / controlled by the user solely.
"""
var = os.environ.get('VIRTUAL_ENV')
if var is not None:
if var == sys.prefix:
return SameEnvironment()
try:
return create_environment(var, safe=False)
except InvalidPythonEnvironment:
pass
def _calculate_sha256_for_file(path):
sha256 = hashlib.sha256()
with open(path, 'rb') as f:
for block in iter(lambda: f.read(filecmp.BUFSIZE), b''):
sha256.update(block)
return sha256.hexdigest()
def get_default_environment():
"""
Tries to return an active Virtualenv. If there is no VIRTUAL_ENV variable
set it will return the latest Python version installed on the system. This
makes it possible to use as many new Python features as possible when using
autocompletion and other functionality.
:returns: :class:`Environment`
"""
virtual_env = _get_virtual_env_from_var()
if virtual_env is not None:
return virtual_env
# If no VirtualEnv is found, use the environment we're already
# using.
return SameEnvironment()
def get_cached_default_environment():
var = os.environ.get('VIRTUAL_ENV')
environment = _get_cached_default_environment()
if var and var != environment.path:
_get_cached_default_environment.clear_cache()
return _get_cached_default_environment()
return environment
@time_cache(seconds=10 * 60) # 10 Minutes
def _get_cached_default_environment():
return get_default_environment()
def find_virtualenvs(paths=None, **kwargs):
"""
: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
Python binaries. Also the VIRTUAL_ENV variable will be checked if it
contains a valid Virtualenv.
:param safe: Default True. In case this is False, it will allow this
function to execute potential `python` environments. An attacker might
be able to drop an executable in a path this function is searching by
default. If the executable has not been installed by root, it will not
be executed.
:yields: :class:`Environment`
"""
def py27_comp(paths=None, safe=True):
if paths is None:
paths = []
_used_paths = set()
# 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)
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 evaluated 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)
def find_system_environments():
"""
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
a portable version.
The environments are sorted from latest to oldest Python version.
:yields: :class:`Environment`
"""
for version_string in _SUPPORTED_PYTHONS:
try:
yield get_system_environment(version_string)
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):
"""
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.
:raises: :exc:`.InvalidPythonEnvironment`
:returns: :class:`Environment`
"""
exe = which('python' + version)
if exe:
if exe == sys.executable:
return SameEnvironment()
return Environment(exe)
if os.name == 'nt':
for exe in _get_executables_from_windows_registry(version):
return Environment(exe)
raise InvalidPythonEnvironment("Cannot find executable python%s." % version)
def create_environment(path, safe=True):
"""
Make it possible to manually create an Environment object by specifying a
Virtualenv path or an executable path.
:raises: :exc:`.InvalidPythonEnvironment`
:returns: :class:`Environment`
"""
if os.path.isfile(path):
_assert_safe(path, safe)
return Environment(path)
return Environment(_get_executable_path(path, safe=safe))
def _get_executable_path(path, safe=True):
"""
Returns None if it's not actually a virtual env.
"""
if os.name == 'nt':
python = os.path.join(path, 'Scripts', 'python.exe')
else:
python = os.path.join(path, 'bin', 'python')
if not os.path.exists(python):
raise InvalidPythonEnvironment("%s seems to be missing." % python)
_assert_safe(python, safe)
return python
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
# TODO: support Python Anaconda.
sub_keys = [
r'SOFTWARE\Python\PythonCore\{version}\InstallPath',
r'SOFTWARE\Wow6432Node\Python\PythonCore\{version}\InstallPath',
r'SOFTWARE\Python\PythonCore\{version}-32\InstallPath',
r'SOFTWARE\Wow6432Node\Python\PythonCore\{version}-32\InstallPath'
]
for root_key in [winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE]:
for sub_key in sub_keys:
sub_key = sub_key.format(version=version)
try:
with winreg.OpenKey(root_key, sub_key) as key:
prefix = winreg.QueryValueEx(key, '')[0]
exe = os.path.join(prefix, 'python.exe')
if os.path.isfile(exe):
yield exe
except WindowsError:
pass
def _assert_safe(executable_path, safe):
if safe and not _is_safe(executable_path):
raise InvalidPythonEnvironment(
"The python binary is potentially unsafe.")
def _is_safe(executable_path):
# Resolve sym links. A venv typically is a symlink to a known Python
# binary. Only virtualenvs copy symlinks around.
real_path = os.path.realpath(executable_path)
if _is_unix_safe_simple(real_path):
return True
# Just check the list of known Python versions. If it's not in there,
# it's likely an attacker or some Python that was not properly
# installed in the system.
for environment in find_system_environments():
if environment.executable == real_path:
return True
# If the versions don't match, just compare the binary files. If we
# don't do that, only venvs will be working and not virtualenvs.
# venvs are symlinks while virtualenvs are actual copies of the
# Python files.
# This still means that if the system Python is updated and the
# virtualenv's Python is not (which is probably never going to get
# upgraded), it will not work with Jedi. IMO that's fine, because
# people should just be using venv. ~ dave
if environment._sha256 == _calculate_sha256_for_file(real_path):
return True
return False
def _is_unix_safe_simple(real_path):
if _is_unix_admin():
# In case we are root, just be conservative and
# only execute known paths.
return any(real_path.startswith(p) for p in _SAFE_PATHS)
uid = os.stat(real_path).st_uid
# The interpreter needs to be owned by root. This means that it wasn't
# written by a user and therefore attacking Jedi is not as simple.
# The attack could look like the following:
# 1. A user clones a repository.
# 2. The repository has an innocent looking folder called foobar. jedi
# searches for the folder and executes foobar/bin/python --version if
# there's also a foobar/bin/activate.
# 3. The bin/python is obviously not a python script but a bash script or
# whatever the attacker wants.
return uid == 0
def _is_unix_admin():
try:
return os.getuid() == 0
except AttributeError:
return False # Windows

10
jedi/api/exceptions.py Normal file
View File

@@ -0,0 +1,10 @@
class _JediError(Exception):
pass
class InternalError(_JediError):
pass
class WrongVersion(_JediError):
pass

View File

@@ -7,12 +7,12 @@ from textwrap import dedent
from parso.python.parser import Parser
from parso.python import tree
from parso import split_lines
from jedi._compatibility import u
from jedi.evaluate.syntax_tree import eval_atom
from jedi.evaluate.helpers import evaluate_call_of_leaf
from jedi.cache import time_cache
from jedi.evaluate.compiled import get_string_context_set
from jedi.cache import call_signature_time_cache
CompletionParts = namedtuple('CompletionParts', ['path', 'has_dot', 'name'])
@@ -44,7 +44,7 @@ def _get_code(code_lines, start_pos, end_pos):
lines[-1] = lines[-1][:end_pos[1]]
# Remove first line indentation.
lines[0] = lines[0][start_pos[1]:]
return '\n'.join(lines)
return ''.join(lines)
class OnErrorLeaf(Exception):
@@ -53,28 +53,11 @@ class OnErrorLeaf(Exception):
return self.args[0]
def _is_on_comment(leaf, position):
comment_lines = split_lines(leaf.prefix)
difference = leaf.start_pos[0] - position[0]
prefix_start_pos = leaf.get_start_pos_of_prefix()
if difference == 0:
indent = leaf.start_pos[1]
elif position[0] == prefix_start_pos[0]:
indent = prefix_start_pos[1]
else:
indent = 0
line = comment_lines[-difference - 1][:position[1] - indent]
return '#' in line
def _get_code_for_stack(code_lines, module_node, position):
leaf = module_node.get_leaf_for_position(position, include_prefixes=True)
# It might happen that we're on whitespace or on a comment. This means
# that we would not get the right leaf.
if leaf.start_pos >= position:
if _is_on_comment(leaf, position):
return u('')
# If we're not on a comment simply get the previous leaf and proceed.
leaf = leaf.get_previous_leaf()
if leaf is None:
@@ -122,11 +105,17 @@ def get_stack_at_position(grammar, code_lines, module_node, pos):
# TODO This is for now not an official parso API that exists purely
# for Jedi.
tokens = grammar._tokenize(code)
for token_ in tokens:
if token_.string == safeword:
for token in tokens:
if token.string == safeword:
raise EndMarkerReached()
elif token.prefix.endswith(safeword):
# This happens with comments.
raise EndMarkerReached()
elif token.string.endswith(safeword):
yield token # Probably an f-string literal that was not finished.
raise EndMarkerReached()
else:
yield token_
yield token
# The code might be indedented, just remove it.
code = dedent(_get_code_for_stack(code_lines, module_node, pos))
@@ -134,67 +123,16 @@ def get_stack_at_position(grammar, code_lines, module_node, pos):
# completion.
# Use Z as a prefix because it's not part of a number suffix.
safeword = 'ZZZ_USER_WANTS_TO_COMPLETE_HERE_WITH_JEDI'
code = code + safeword
code = code + ' ' + safeword
p = Parser(grammar._pgen_grammar, error_recovery=True)
try:
p.parse(tokens=tokenize_without_endmarker(code))
except EndMarkerReached:
return Stack(p.pgen_parser.stack)
return p.stack
raise SystemError("This really shouldn't happen. There's a bug in Jedi.")
class Stack(list):
def get_node_names(self, grammar):
for dfa, state, (node_number, nodes) in self:
yield grammar.number2symbol[node_number]
def get_nodes(self):
for dfa, state, (node_number, nodes) in self:
for node in nodes:
yield node
def get_possible_completion_types(pgen_grammar, stack):
def add_results(label_index):
try:
grammar_labels.append(inversed_tokens[label_index])
except KeyError:
try:
keywords.append(inversed_keywords[label_index])
except KeyError:
t, v = pgen_grammar.labels[label_index]
assert t >= 256
# See if it's a symbol and if we're in its first set
inversed_keywords
itsdfa = pgen_grammar.dfas[t]
itsstates, itsfirst = itsdfa
for first_label_index in itsfirst.keys():
add_results(first_label_index)
inversed_keywords = dict((v, k) for k, v in pgen_grammar.keywords.items())
inversed_tokens = dict((v, k) for k, v in pgen_grammar.tokens.items())
keywords = []
grammar_labels = []
def scan_stack(index):
dfa, state, node = stack[index]
states, first = dfa
arcs = states[state]
for label_index, new_state in arcs:
if label_index == 0:
# An accepting state, check the stack below.
scan_stack(index - 1)
else:
add_results(label_index)
scan_stack(-1)
return keywords, grammar_labels
def evaluate_goto_definition(evaluator, context, leaf):
if leaf.type == 'name':
# In case of a name we can just use goto_definition which does all the
@@ -208,6 +146,8 @@ def evaluate_goto_definition(evaluator, context, leaf):
return evaluate_call_of_leaf(context, leaf)
elif isinstance(leaf, tree.Literal):
return eval_atom(context, leaf)
elif leaf.type in ('fstring_string', 'fstring_start', 'fstring_end'):
return get_string_context_set(evaluator)
return []
@@ -229,7 +169,8 @@ def _get_index_and_key(nodes, position):
if nodes_before:
last = nodes_before[-1]
if last.type == 'argument' and last.children[1].end_pos <= position:
if last.type == 'argument' and last.children[1] == '=' \
and last.children[1].end_pos <= position:
# Checked if the argument
key_str = last.children[0].value
elif last == '=':
@@ -294,14 +235,14 @@ def get_call_signature_details(module, position):
return None
@time_cache("call_signatures_validity")
@call_signature_time_cache("call_signatures_validity")
def cache_call_signatures(evaluator, context, bracket_leaf, code_lines, user_pos):
"""This function calculates the cache key."""
index = user_pos[0] - 1
line_index = user_pos[0] - 1
before_cursor = code_lines[index][:user_pos[1]]
other_lines = code_lines[bracket_leaf.start_pos[0]:index]
whole = '\n'.join(other_lines + [before_cursor])
before_cursor = code_lines[line_index][:user_pos[1]]
other_lines = code_lines[bracket_leaf.start_pos[0]:line_index]
whole = ''.join(other_lines + [before_cursor])
before_bracket = re.match(r'.*\(', whole, re.DOTALL)
module_path = context.get_root_context().py__file__()

View File

@@ -5,24 +5,34 @@ TODO Some parts of this module are still not well documented.
from jedi.evaluate.context import ModuleContext
from jedi.evaluate import compiled
from jedi.evaluate.compiled import mixed
from jedi.evaluate.compiled.access import create_access_path
from jedi.evaluate.base_context import Context
def _create(evaluator, obj):
return compiled.create_from_access_path(
evaluator, create_access_path(evaluator, obj)
)
class NamespaceObject(object):
def __init__(self, dct):
self.__dict__ = dct
class MixedModuleContext(Context):
resets_positions = True
type = 'mixed_module'
def __init__(self, evaluator, tree_module, namespaces, path):
def __init__(self, evaluator, tree_module, namespaces, path, code_lines):
self.evaluator = evaluator
self._namespaces = namespaces
self._namespace_objects = [NamespaceObject(n) for n in namespaces]
self._module_context = ModuleContext(evaluator, tree_module, path=path)
self._module_context = ModuleContext(
evaluator, tree_module,
path=path,
code_lines=code_lines
)
self.tree_node = tree_module
def get_node(self):
@@ -33,7 +43,7 @@ class MixedModuleContext(Context):
yield filter
for namespace_obj in self._namespace_objects:
compiled_object = compiled.create(self.evaluator, namespace_obj)
compiled_object = _create(self.evaluator, namespace_obj)
mixed_object = mixed.MixedObject(
self.evaluator,
parent_context=self,
@@ -43,5 +53,9 @@ class MixedModuleContext(Context):
for filter in mixed_object.get_filters(*args, **kwargs):
yield filter
@property
def code_lines(self):
return self._module_context.code_lines
def __getattr__(self, name):
return getattr(self._module_context, name)

View File

@@ -1,10 +1,7 @@
import pydoc
import keyword
from jedi._compatibility import is_py3, is_py35
from jedi.evaluate.utils import ignored
from jedi.evaluate.filters import AbstractNameDefinition
from parso.python.tree import Leaf
try:
from pydoc_data import topics as pydoc_topics
@@ -17,87 +14,30 @@ except ImportError:
# pydoc_data module in its file python3x.zip.
pydoc_topics = None
if is_py3:
if is_py35:
# in python 3.5 async and await are not proper keywords, but for
# completion pursposes should as as though they are
keys = keyword.kwlist + ["async", "await"]
else:
keys = keyword.kwlist
else:
keys = keyword.kwlist + ['None', 'False', 'True']
def has_inappropriate_leaf_keyword(pos, module):
relevant_errors = filter(
lambda error: error.first_pos[0] == pos[0],
module.error_statement_stacks)
for error in relevant_errors:
if error.next_token in keys:
return True
return False
def completion_names(evaluator, stmt, pos, module):
keyword_list = all_keywords(evaluator)
if not isinstance(stmt, Leaf) or has_inappropriate_leaf_keyword(pos, module):
keyword_list = filter(
lambda keyword: not keyword.only_valid_as_leaf,
keyword_list
)
return [keyword.name for keyword in keyword_list]
def all_keywords(evaluator, pos=(0, 0)):
return set([Keyword(evaluator, k, pos) for k in keys])
def keyword(evaluator, string, pos=(0, 0)):
if string in keys:
return Keyword(evaluator, string, pos)
else:
return None
def get_operator(evaluator, string, pos):
return Keyword(evaluator, string, pos)
keywords_only_valid_as_leaf = (
'continue',
'break',
)
class KeywordName(AbstractNameDefinition):
api_type = 'keyword'
api_type = u'keyword'
def __init__(self, evaluator, name):
self.evaluator = evaluator
self.string_name = name
self.parent_context = evaluator.BUILTINS
def eval(self):
return set()
self.parent_context = evaluator.builtins_module
def infer(self):
return [Keyword(self.evaluator, self.string_name, (0, 0))]
class Keyword(object):
api_type = 'keyword'
api_type = u'keyword'
def __init__(self, evaluator, name, pos):
self.name = KeywordName(evaluator, name)
self.start_pos = pos
self.parent = evaluator.BUILTINS
@property
def only_valid_as_leaf(self):
return self.name.value in keywords_only_valid_as_leaf
self.parent = evaluator.builtins_module
@property
def names(self):

195
jedi/api/project.py Normal file
View File

@@ -0,0 +1,195 @@
import os
import json
from jedi._compatibility import FileNotFoundError, NotADirectoryError
from jedi.api.environment import SameEnvironment, \
get_cached_default_environment
from jedi.api.exceptions import WrongVersion
from jedi._compatibility import force_unicode
from jedi.evaluate.sys_path import discover_buildout_paths
from jedi.evaluate.cache import evaluator_as_method_param_cache
from jedi.common.utils import traverse_parents
_CONFIG_FOLDER = '.jedi'
_CONTAINS_POTENTIAL_PROJECT = 'setup.py', '.git', '.hg', 'requirements.txt', 'MANIFEST.in'
_SERIALIZER_VERSION = 1
def _remove_duplicates_from_path(path):
used = set()
for p in path:
if p in used:
continue
used.add(p)
yield p
def _force_unicode_list(lst):
return list(map(force_unicode, lst))
class Project(object):
# TODO serialize environment
_serializer_ignore_attributes = ('_environment',)
_environment = None
@staticmethod
def _get_json_path(base_path):
return os.path.join(base_path, _CONFIG_FOLDER, 'project.json')
@classmethod
def load(cls, path):
"""
:param path: The path of the directory you want to use as a project.
"""
with open(cls._get_json_path(path)) as f:
version, data = json.load(f)
if version == 1:
self = cls.__new__()
self.__dict__.update(data)
return self
else:
raise WrongVersion(
"The Jedi version of this project seems newer than what we can handle."
)
def __init__(self, path, **kwargs):
"""
:param path: The base path for this project.
:param sys_path: list of str. You can override the sys path if you
want. By default the ``sys.path.`` is generated from the
environment (virtualenvs, etc).
:param smart_sys_path: If this is enabled (default), adds paths from
local directories. Otherwise you will have to rely on your packages
being properly configured on the ``sys.path``.
"""
def py2_comp(path, environment=None, sys_path=None,
smart_sys_path=True, _django=False):
self._path = path
if isinstance(environment, SameEnvironment):
self._environment = environment
self._sys_path = sys_path
self._smart_sys_path = smart_sys_path
self._django = _django
py2_comp(path, **kwargs)
def _get_base_sys_path(self, environment=None):
if self._sys_path is not None:
return self._sys_path
# The sys path has not been set explicitly.
if environment is None:
environment = self.get_environment()
sys_path = list(environment.get_sys_path())
try:
sys_path.remove('')
except ValueError:
pass
return sys_path
@evaluator_as_method_param_cache()
def _get_sys_path(self, evaluator, environment=None):
"""
Keep this method private for all users of jedi. However internally this
one is used like a public method.
"""
suffixed = []
prefixed = []
sys_path = list(self._get_base_sys_path(environment))
if self._smart_sys_path:
prefixed.append(self._path)
if evaluator.script_path is not None:
suffixed += discover_buildout_paths(evaluator, evaluator.script_path)
traversed = list(traverse_parents(evaluator.script_path))
# AFAIK some libraries have imports like `foo.foo.bar`, which
# leads to the conclusion to by default prefer longer paths
# rather than shorter ones by default.
suffixed += reversed(traversed)
if self._django:
prefixed.append(self._path)
path = prefixed + sys_path + suffixed
return list(_force_unicode_list(_remove_duplicates_from_path(path)))
def save(self):
data = dict(self.__dict__)
for attribute in self._serializer_ignore_attributes:
data.pop(attribute, None)
with open(self._get_json_path(self._path), 'wb') as f:
return json.dump((_SERIALIZER_VERSION, data), f)
def get_environment(self):
if self._environment is None:
return get_cached_default_environment()
return self._environment
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self._path)
def _is_potential_project(path):
for name in _CONTAINS_POTENTIAL_PROJECT:
if os.path.exists(os.path.join(path, name)):
return True
return False
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:
return b"DJANGO_SETTINGS_MODULE" in f.read()
except (FileNotFoundError, NotADirectoryError):
return False
return False
def get_default_project(path=None):
if path is None:
path = os.getcwd()
check = os.path.realpath(path)
probable_path = None
first_no_init_file = None
for dir in traverse_parents(check, include_current=True):
try:
return Project.load(dir)
except (FileNotFoundError, NotADirectoryError):
pass
if first_no_init_file is None:
if os.path.exists(os.path.join(dir, '__init__.py')):
# 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:
first_no_init_file = dir
if _is_django_path(dir):
return Project(dir, _django=True)
if probable_path is None and _is_potential_project(dir):
probable_path = dir
if probable_path is not None:
# TODO search for setup.py etc
return Project(probable_path)
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)
return Project(curdir)

View File

@@ -1,6 +1,8 @@
"""
To use Jedi completion in Python interpreter, add the following in your shell
setup (e.g., ``.bashrc``)::
setup (e.g., ``.bashrc``). This works only on Linux/Mac, because readline is
not available on Windows. If you still want Jedi autocompletion in your REPL,
just use IPython instead::
export PYTHONSTARTUP="$(python -m jedi repl)"
@@ -11,8 +13,8 @@ Then you will be able to use Jedi completer in your Python interpreter::
[GCC 4.6.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> os.path.join().split().in<TAB> # doctest: +SKIP
os.path.join().split().index os.path.join().split().insert
>>> os.path.join('a', 'b').split().in<TAB> # doctest: +SKIP
..dex ..sert
"""
import jedi.utils

View File

@@ -12,6 +12,7 @@ there are global variables, which are holding the cache information. Some of
these variables are being cleaned after every API usage.
"""
import time
from functools import wraps
from jedi import settings
from parso.cache import parser_cache
@@ -74,7 +75,7 @@ def clear_time_caches(delete_all=False):
del tc[key]
def time_cache(time_add_setting):
def call_signature_time_cache(time_add_setting):
"""
This decorator works as follows: Call it with a setting and after that
use the function with a callable that returns the key.
@@ -106,8 +107,32 @@ def time_cache(time_add_setting):
return _temp
def time_cache(seconds):
def decorator(func):
cache = {}
@wraps(func)
def wrapper(*args, **kwargs):
key = (args, frozenset(kwargs.items()))
try:
created, result = cache[key]
if time.time() < created + seconds:
return result
except KeyError:
pass
result = func(*args, **kwargs)
cache[key] = time.time(), result
return result
wrapper.clear_cache = lambda: cache.clear()
return wrapper
return decorator
def memoize_method(method):
"""A normal memoize function."""
@wraps(method)
def wrapper(self, *args, **kwargs):
cache_dict = self.__dict__.setdefault('_memoize_method_dct', {})
dct = cache_dict.setdefault(method, {})

26
jedi/common/utils.py Normal file
View File

@@ -0,0 +1,26 @@
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):
"""
Like pytest's monkeypatch, but as a context manager.
"""
old_value = getattr(obj, attribute_name)
try:
setattr(obj, attribute_name, new_value)
yield
finally:
setattr(obj, attribute_name, old_value)

View File

@@ -2,16 +2,17 @@ from jedi._compatibility import encoding, is_py3, u
import os
import time
_inited = False
def _lazy_colorama_init():
"""
Lazily init colorama if necessary, not to screw up stdout is debug not
enabled.
Lazily init colorama if necessary, not to screw up stdout if debugging is
not enabled.
This version of the function does nothing.
"""
pass
_inited=False
try:
if os.name == 'nt':
@@ -21,7 +22,8 @@ try:
# Use colorama for nicer console output.
from colorama import Fore, init
from colorama import initialise
def _lazy_colorama_init():
def _lazy_colorama_init(): # noqa: F811
"""
Lazily init colorama if necessary, not to screw up stdout is
debug not enabled.
@@ -35,7 +37,7 @@ try:
# need this.
initialise.atexit_done = True
try:
init()
init(strip=False)
except Exception:
# Colorama fails with initializing under vim and is buggy in
# version 0.3.6.

View File

@@ -17,7 +17,8 @@ said, the typical entry point for static analysis is calling
``eval_expr_stmt``. There's separate logic for autocompletion in the API, the
evaluator is all about evaluating an expression.
TODO this paragraph is not what jedi does anymore.
TODO this paragraph is not what jedi does anymore, it's similar, but not the
same.
Now you need to understand what follows after ``eval_expr_stmt``. Let's
make an example::
@@ -62,10 +63,9 @@ only *evaluates* what needs to be *evaluated*. All the statements and modules
that are not used are just being ignored.
"""
import sys
from parso.python import tree
import parso
from parso import python_bytes_to_unicode
from jedi import debug
from jedi import parser_utils
@@ -86,32 +86,49 @@ from jedi.evaluate.syntax_tree import eval_trailer, eval_expr_stmt, \
class Evaluator(object):
def __init__(self, grammar, project):
self.grammar = grammar
def __init__(self, project, environment=None, script_path=None):
if environment is None:
environment = project.get_environment()
self.environment = environment
self.script_path = script_path
self.compiled_subprocess = environment.get_evaluator_subprocess(self)
self.grammar = environment.get_grammar()
self.latest_grammar = parso.load_grammar(version='3.6')
self.memoize_cache = {} # for memoize decorators
# To memorize modules -> equals `sys.modules`.
self.modules = {} # like `sys.modules`.
self.module_cache = imports.ModuleCache() # does the job of `sys.modules`.
self.compiled_cache = {} # see `evaluate.compiled.create()`
self.inferred_element_counts = {}
self.mixed_cache = {} # see `evaluate.compiled.mixed._create()`
self.analysis = []
self.dynamic_params_depth = 0
self.is_analysis = False
self.python_version = sys.version_info[:2]
self.project = project
project.add_evaluator(self)
self.access_cache = {}
# This setting is only temporary to limit the work we have to do with
# tensorflow and others.
self.infer_enabled = True
self.reset_recursion_limitations()
self.allow_different_encoding = True
# Constants
self.BUILTINS = compiled.get_special_object(self, 'BUILTINS')
@property
@evaluator_function_cache()
def builtins_module(self):
return compiled.get_special_object(self, u'BUILTINS')
def reset_recursion_limitations(self):
self.recursion_detector = recursion.RecursionDetector()
self.execution_recursion_detector = recursion.ExecutionRecursionDetector(self)
def get_sys_path(self):
"""Convenience function"""
return self.project._get_sys_path(self, environment=self.environment)
def eval_element(self, context, element):
if not self.infer_enabled:
return NO_CONTEXTS
if isinstance(context, CompForContext):
return eval_node(context, element)
@@ -124,7 +141,11 @@ class Evaluator(object):
if_stmt = None
break
predefined_if_name_dict = context.predefined_names.get(if_stmt)
if predefined_if_name_dict is None and if_stmt and if_stmt.type == 'if_stmt':
# TODO there's a lot of issues with this one. We actually should do
# this in a different way. Caching should only be active in certain
# cases and this all sucks.
if predefined_if_name_dict is None and if_stmt \
and if_stmt.type == 'if_stmt' and self.is_analysis:
if_stmt_test = if_stmt.children[1]
name_dicts = [{}]
# If we already did a check, we don't want to do it again -> If
@@ -201,7 +222,7 @@ class Evaluator(object):
if type_ == 'classdef':
return [ClassContext(self, context, name.parent)]
elif type_ == 'funcdef':
return [FunctionContext(self, context, name.parent)]
return [FunctionContext.from_context(context, name.parent)]
if type_ == 'expr_stmt':
is_simple_name = name.parent.type not in ('power', 'trailer')
@@ -319,16 +340,15 @@ class Evaluator(object):
parent_context = from_scope_node(parent_scope, child_is_funcdef=is_funcdef)
if is_funcdef:
func = FunctionContext.from_context(
parent_context,
scope_node
)
if isinstance(parent_context, AnonymousInstance):
func = BoundMethod(
self, parent_context, parent_context.class_context,
parent_context.parent_context, scope_node
)
else:
func = FunctionContext(
self,
parent_context,
scope_node
instance=parent_context,
klass=parent_context.class_context,
function=func
)
if is_nested and not node_is_object:
return func.get_function_execution()
@@ -357,3 +377,15 @@ class Evaluator(object):
node = node.parent
scope_node = parent_scope(node)
return from_scope_node(scope_node, is_nested=True, node_is_object=node_is_object)
def parse_and_get_code(self, code=None, path=None, encoding='utf-8', **kwargs):
if self.allow_different_encoding:
if code is None:
with open(path, 'rb') as f:
code = f.read()
code = python_bytes_to_unicode(code, encoding=encoding, errors='replace')
return self.grammar.parse(code=code, path=path, **kwargs), code
def parse(self, *args, **kwargs):
return self.parse_and_get_code(*args, **kwargs)[0]

View File

@@ -1,9 +1,12 @@
"""
Module for statical analysis.
"""
from jedi import debug
from parso.python import tree
from jedi._compatibility import force_unicode
from jedi import debug
from jedi.evaluate.compiled import CompiledObject
from jedi.evaluate.helpers import is_string
CODES = {
@@ -114,9 +117,10 @@ def add_attribute_error(name_context, lookup_context, name):
# instead of an error, if that happens.
typ = Error
if isinstance(lookup_context, AbstractInstanceContext):
slot_names = lookup_context.get_function_slot_names('__getattr__') + \
lookup_context.get_function_slot_names('__getattribute__')
slot_names = lookup_context.get_function_slot_names(u'__getattr__') + \
lookup_context.get_function_slot_names(u'__getattribute__')
for n in slot_names:
# TODO do we even get here?
if isinstance(name, CompiledInstanceName) and \
n.parent_context.obj == object:
typ = Warning
@@ -139,7 +143,7 @@ def _check_for_exception_catch(node_context, jedi_name, exception, payload=None)
"""
def check_match(cls, exception):
try:
return isinstance(cls, CompiledObject) and issubclass(exception, cls.obj)
return isinstance(cls, CompiledObject) and cls.is_super_class(exception)
except TypeError:
return False
@@ -160,7 +164,7 @@ def _check_for_exception_catch(node_context, jedi_name, exception, payload=None)
except_classes = node_context.eval_node(node)
for cls in except_classes:
from jedi.evaluate.context import iterable
if isinstance(cls, iterable.AbstractIterable) and \
if isinstance(cls, iterable.Sequence) and \
cls.array_type == 'tuple':
# multiple exceptions
for lazy_context in cls.py__iter__():
@@ -189,8 +193,8 @@ def _check_for_exception_catch(node_context, jedi_name, exception, payload=None)
# Check name
key, lazy_context = args[1]
names = list(lazy_context.infer())
assert len(names) == 1 and isinstance(names[0], CompiledObject)
assert names[0].obj == payload[1].value
assert len(names) == 1 and is_string(names[0])
assert force_unicode(names[0].get_safe_value()) == payload[1].value
# Check objects
key, lazy_context = args[0]

View File

@@ -1,3 +1,5 @@
import re
from parso.python import tree
from jedi._compatibility import zip_longest
@@ -8,7 +10,8 @@ from jedi.evaluate.lazy_context import LazyKnownContext, LazyKnownContexts, \
from jedi.evaluate.filters import ParamName
from jedi.evaluate.base_context import NO_CONTEXTS
from jedi.evaluate.context import iterable
from jedi.evaluate.param import get_params, ExecutedParam
from jedi.evaluate.param import get_executed_params, ExecutedParam
def try_iter_content(types, depth=0):
"""Helper method for static analysis."""
@@ -27,29 +30,82 @@ def try_iter_content(types, depth=0):
try_iter_content(lazy_context.infer(), depth + 1)
def repack_with_argument_clinic(string, keep_arguments_param=False):
"""
Transforms a function or method with arguments to the signature that is
given as an argument clinic notation.
Argument clinic is part of CPython and used for all the functions that are
implemented in C (Python 3.7):
str.split.__text_signature__
# Results in: '($self, /, sep=None, maxsplit=-1)'
"""
clinic_args = list(_parse_argument_clinic(string))
def decorator(func):
def wrapper(*args, **kwargs):
if keep_arguments_param:
arguments = kwargs['arguments']
else:
arguments = kwargs.pop('arguments')
try:
args += tuple(_iterate_argument_clinic(arguments, clinic_args))
except ValueError:
return NO_CONTEXTS
else:
return func(*args, **kwargs)
return wrapper
return decorator
def _iterate_argument_clinic(arguments, parameters):
"""Uses a list with argument clinic information (see PEP 436)."""
iterator = arguments.unpack()
for i, (name, optional, allow_kwargs) in enumerate(parameters):
key, argument = next(iterator, (None, None))
if key is not None:
debug.warning('Keyword arguments in argument clinic are currently not supported.')
raise ValueError
if argument is None and not optional:
debug.warning('TypeError: %s expected at least %s arguments, got %s',
name, len(parameters), i)
raise ValueError
context_set = NO_CONTEXTS if argument is None else argument.infer()
if not context_set and not optional:
# For the stdlib we always want values. If we don't get them,
# that's ok, maybe something is too hard to resolve, however,
# we will not proceed with the evaluation of that function.
debug.warning('argument_clinic "%s" not resolvable.', name)
raise ValueError
yield context_set
def _parse_argument_clinic(string):
allow_kwargs = False
optional = False
while string:
# Optional arguments have to begin with a bracket. And should always be
# at the end of the arguments. This is therefore not a proper argument
# clinic implementation. `range()` for exmple allows an optional start
# value at the beginning.
match = re.match('(?:(?:(\[),? ?|, ?|)(\w+)|, ?/)\]*', string)
string = string[len(match.group(0)):]
if not match.group(2): # A slash -> allow named arguments
allow_kwargs = True
continue
optional = optional or bool(match.group(1))
word = match.group(2)
yield (word, optional, allow_kwargs)
class AbstractArguments(object):
context = None
def eval_argument_clinic(self, parameters):
"""Uses a list with argument clinic information (see PEP 436)."""
iterator = self.unpack()
for i, (name, optional, allow_kwargs) in enumerate(parameters):
key, argument = next(iterator, (None, None))
if key is not None:
raise NotImplementedError
if argument is None and not optional:
debug.warning('TypeError: %s expected at least %s arguments, got %s',
name, len(parameters), i)
raise ValueError
values = NO_CONTEXTS if argument is None else argument.infer()
if not values and not optional:
# For the stdlib we always want values. If we don't get them,
# that's ok, maybe something is too hard to resolve, however,
# we will not proceed with the evaluation of that function.
debug.warning('argument_clinic "%s" not resolvable.', name)
raise ValueError
yield values
argument_node = None
trailer = None
def eval_all(self, funcdef=None):
"""
@@ -61,17 +117,17 @@ class AbstractArguments(object):
try_iter_content(types)
def get_calling_nodes(self):
raise NotImplementedError
return []
def unpack(self, funcdef=None):
raise NotImplementedError
def get_params(self, execution_context):
return get_params(execution_context, self)
def get_executed_params(self, execution_context):
return get_executed_params(execution_context, self)
class AnonymousArguments(AbstractArguments):
def get_params(self, execution_context):
def get_executed_params(self, execution_context):
from jedi.evaluate.dynamic import search_params
return search_params(
execution_context.evaluator,
@@ -79,6 +135,9 @@ class AnonymousArguments(AbstractArguments):
execution_context.tree_node
)
def __repr__(self):
return '%s()' % self.__class__.__name__
class TreeArguments(AbstractArguments):
def __init__(self, evaluator, context, argument_node, trailer=None):
@@ -95,29 +154,30 @@ class TreeArguments(AbstractArguments):
self.trailer = trailer # Can be None, e.g. in a class definition.
def _split(self):
if isinstance(self.argument_node, (tuple, list)):
for el in self.argument_node:
yield 0, el
else:
if not (self.argument_node.type == 'arglist' or (
# in python 3.5 **arg is an argument, not arglist
(self.argument_node.type == 'argument') and
self.argument_node.children[0] in ('*', '**'))):
yield 0, self.argument_node
return
if self.argument_node is None:
return
iterator = iter(self.argument_node.children)
for child in iterator:
if child == ',':
continue
elif child in ('*', '**'):
yield len(child.value), next(iterator)
elif child.type == 'argument' and \
child.children[0] in ('*', '**'):
assert len(child.children) == 2
yield len(child.children[0].value), child.children[1]
else:
yield 0, child
# Allow testlist here as well for Python2's class inheritance
# definitions.
if not (self.argument_node.type in ('arglist', 'testlist') or (
# in python 3.5 **arg is an argument, not arglist
(self.argument_node.type == 'argument') and
self.argument_node.children[0] in ('*', '**'))):
yield 0, self.argument_node
return
iterator = iter(self.argument_node.children)
for child in iterator:
if child == ',':
continue
elif child in ('*', '**'):
yield len(child.value), next(iterator)
elif child.type == 'argument' and \
child.children[0] in ('*', '**'):
assert len(child.children) == 2
yield len(child.children[0].value), child.children[1]
else:
yield 0, child
def unpack(self, funcdef=None):
named_args = []
@@ -126,7 +186,6 @@ class TreeArguments(AbstractArguments):
arrays = self.context.eval_node(el)
iterators = [_iterate_star_args(self.context, a, el, funcdef)
for a in arrays]
iterators = list(iterators)
for values in list(zip_longest(*iterators)):
# TODO zip_longest yields None, that means this would raise
# an exception?
@@ -134,7 +193,7 @@ class TreeArguments(AbstractArguments):
[v for v in values if v is not None]
)
elif star_count == 2:
arrays = self._evaluator.eval_element(self.context, el)
arrays = self.context.eval_node(el)
for dct in arrays:
for key, values in _star_star_dict(self.context, dct, el, funcdef):
yield key, values
@@ -168,7 +227,7 @@ class TreeArguments(AbstractArguments):
return '<%s: %s>' % (self.__class__.__name__, self.argument_node)
def get_calling_nodes(self):
from jedi.evaluate.dynamic import MergedExecutedParams
from jedi.evaluate.dynamic import DynamicExecutedParams
old_arguments_list = []
arguments = self
@@ -187,7 +246,7 @@ class TreeArguments(AbstractArguments):
if not isinstance(names[0], ParamName):
break
param = names[0].get_param()
if isinstance(param, MergedExecutedParams):
if isinstance(param, DynamicExecutedParams):
# For dynamic searches we don't even want to see errors.
return []
if not isinstance(param, ExecutedParam):
@@ -197,7 +256,11 @@ class TreeArguments(AbstractArguments):
arguments = param.var_args
break
return [arguments.argument_node or arguments.trailer]
if arguments.argument_node is not None:
return [arguments.argument_node]
if arguments.trailer is not None:
return [arguments.trailer]
return []
class ValuesArguments(AbstractArguments):
@@ -208,9 +271,6 @@ class ValuesArguments(AbstractArguments):
for values in self._values_list:
yield None, LazyKnownContexts(values)
def get_calling_nodes(self):
return []
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self._values_list)
@@ -235,7 +295,7 @@ def _star_star_dict(context, array, input_node, funcdef):
# For now ignore this case. In the future add proper iterators and just
# make one call without crazy isinstance checks.
return {}
elif isinstance(array, iterable.AbstractIterable) and array.array_type == 'dict':
elif isinstance(array, iterable.Sequence) and array.array_type == 'dict':
return array.exact_key_items()
else:
if funcdef is not None:

View File

@@ -1,9 +1,19 @@
"""
Contexts are the "values" that Python would return. However Contexts are at the
same time also the "contexts" that a user is currently sitting in.
A ContextSet is typically used to specify the return of a function or any other
static analysis operation. In jedi there are always multiple returns and not
just one.
"""
from parso.python.tree import ExprStmt, CompFor
from jedi import debug
from jedi._compatibility import Python3Method, zip_longest, unicode
from jedi.parser_utils import clean_scope_docstring, get_doc_with_call_signature
from jedi.common import BaseContextSet, BaseContext
from jedi.evaluate.helpers import EvaluatorIndexError, EvaluatorTypeError, \
EvaluatorKeyError
class Context(BaseContext):
@@ -63,10 +73,13 @@ class Context(BaseContext):
arguments = ValuesArguments([ContextSet(value) for value in value_list])
return self.execute(arguments)
def iterate(self, contextualized_node=None):
debug.dbg('iterate')
def iterate(self, contextualized_node=None, is_async=False):
debug.dbg('iterate %s', self)
try:
iter_method = self.py__iter__
if is_async:
iter_method = self.py__aiter__
else:
iter_method = self.py__iter__
except AttributeError:
if contextualized_node is not None:
from jedi.evaluate import analysis
@@ -81,17 +94,22 @@ class Context(BaseContext):
def get_item(self, index_contexts, contextualized_node):
from jedi.evaluate.compiled import CompiledObject
from jedi.evaluate.context.iterable import Slice, AbstractIterable
from jedi.evaluate.context.iterable import Slice, Sequence
result = ContextSet()
for index in index_contexts:
if isinstance(index, (CompiledObject, Slice)):
if isinstance(index, Slice):
index = index.obj
if isinstance(index, CompiledObject):
try:
index = index.get_safe_value()
except ValueError:
pass
if type(index) not in (float, int, str, unicode, slice, type(Ellipsis)):
if type(index) not in (float, int, str, unicode, slice, bytes):
# If the index is not clearly defined, we have to get all the
# possiblities.
if isinstance(self, AbstractIterable) and self.array_type == 'dict':
if isinstance(self, Sequence) and self.array_type == 'dict':
result |= self.dict_values()
else:
result |= iterate_contexts(ContextSet(self))
@@ -112,11 +130,15 @@ class Context(BaseContext):
else:
try:
result |= getitem(index)
except IndexError:
except EvaluatorIndexError:
result |= iterate_contexts(ContextSet(self))
except KeyError:
except EvaluatorKeyError:
# Must be a dict. Lists don't raise KeyErrors.
result |= self.dict_values()
except EvaluatorTypeError:
# The type is wrong and therefore it makes no sense to do
# anything anymore.
result = NO_CONTEXTS
return result
def eval_node(self, node):
@@ -139,10 +161,6 @@ class Context(BaseContext):
return f.filter_name(filters)
return f.find(filters, attribute_lookup=not search_global)
return self.evaluator.find_types(
self, name_or_str, name_context, position, search_global, is_goto,
analysis_errors)
def create_context(self, node, node_is_context=False, node_is_object=False):
return self.evaluator.create_context(self, node, node_is_context, node_is_object)
@@ -169,21 +187,22 @@ class Context(BaseContext):
return None
def iterate_contexts(contexts, contextualized_node=None):
def iterate_contexts(contexts, contextualized_node=None, is_async=False):
"""
Calls `iterate`, on all contexts but ignores the ordering and just returns
all contexts that the iterate functions yield.
"""
return ContextSet.from_sets(
lazy_context.infer()
for lazy_context in contexts.iterate(contextualized_node)
for lazy_context in contexts.iterate(contextualized_node, is_async=is_async)
)
class TreeContext(Context):
def __init__(self, evaluator, parent_context=None):
def __init__(self, evaluator, parent_context, tree_node):
super(TreeContext, self).__init__(evaluator, parent_context)
self.predefined_names = {}
self.tree_node = tree_node
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self.tree_node)
@@ -241,9 +260,9 @@ class ContextSet(BaseContextSet):
def py__class__(self):
return ContextSet.from_iterable(c.py__class__() for c in self._set)
def iterate(self, contextualized_node=None):
def iterate(self, contextualized_node=None, is_async=False):
from jedi.evaluate.lazy_context import get_merged_lazy_context
type_iters = [c.iterate(contextualized_node) for c in self._set]
type_iters = [c.iterate(contextualized_node, is_async=is_async) for c in self._set]
for lazy_contexts in zip_longest(*type_iters):
yield get_merged_lazy_context(
[l for l in lazy_contexts if l is not None]

View File

@@ -59,7 +59,7 @@ def evaluator_method_cache(default=_NO_DEFAULT):
return decorator
def _memoize_meta_class():
def evaluator_as_method_param_cache():
def decorator(call):
return _memoize_default(second_arg_is_evaluator=True)(call)
@@ -72,6 +72,6 @@ class CachedMetaClass(type):
class initializations. Either you do it this way or with decorators, but
with decorators you lose class access (isinstance, etc).
"""
@_memoize_meta_class()
@evaluator_as_method_param_cache()
def __call__(self, *args, **kwargs):
return super(CachedMetaClass, self).__call__(*args, **kwargs)

View File

@@ -1,638 +1,43 @@
"""
Imitate the parser representation.
"""
import inspect
import re
import sys
import os
import types
from functools import partial
from jedi._compatibility import builtins as _builtins, unicode, py_version
from jedi import debug
from jedi.cache import underscore_memoization, memoize_method
from jedi.evaluate.filters import AbstractFilter, AbstractNameDefinition, \
ContextNameMixin
from jedi.evaluate.base_context import Context, ContextSet
from jedi.evaluate.lazy_context import LazyKnownContext
from jedi.evaluate.compiled.getattr_static import getattr_static
from . import fake
_sep = os.path.sep
if os.path.altsep is not None:
_sep += os.path.altsep
_path_re = re.compile('(?:\.[^{0}]+|[{0}]__init__\.py)$'.format(re.escape(_sep)))
del _sep
# Those types don't exist in typing.
MethodDescriptorType = type(str.replace)
WrapperDescriptorType = type(set.__iter__)
# `object.__subclasshook__` is an already executed descriptor.
object_class_dict = type.__dict__["__dict__"].__get__(object)
ClassMethodDescriptorType = type(object_class_dict['__subclasshook__'])
ALLOWED_DESCRIPTOR_ACCESS = (
types.FunctionType,
types.GetSetDescriptorType,
types.MemberDescriptorType,
MethodDescriptorType,
WrapperDescriptorType,
ClassMethodDescriptorType,
staticmethod,
classmethod,
)
class CheckAttribute(object):
"""Raises an AttributeError if the attribute X isn't available."""
def __init__(self, func):
self.func = func
# Remove the py in front of e.g. py__call__.
self.check_name = func.__name__[2:]
def __get__(self, instance, owner):
# This might raise an AttributeError. That's wanted.
if self.check_name == '__iter__':
# 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.
try:
iter(instance.obj)
except TypeError:
raise AttributeError
else:
getattr(instance.obj, self.check_name)
return partial(self.func, instance)
class CompiledObject(Context):
path = None # modules have this attribute - set it to None.
used_names = lambda self: {} # To be consistent with modules.
def __init__(self, evaluator, obj, parent_context=None, faked_class=None):
super(CompiledObject, self).__init__(evaluator, parent_context)
self.obj = obj
# This attribute will not be set for most classes, except for fakes.
self.tree_node = faked_class
def get_root_node(self):
# To make things a bit easier with filters we add this method here.
return self.get_root_context()
@CheckAttribute
def py__call__(self, params):
if inspect.isclass(self.obj):
from jedi.evaluate.context import CompiledInstance
return ContextSet(CompiledInstance(self.evaluator, self.parent_context, self, params))
else:
return ContextSet.from_iterable(self._execute_function(params))
@CheckAttribute
def py__class__(self):
return create(self.evaluator, self.obj.__class__)
@CheckAttribute
def py__mro__(self):
return (self,) + tuple(create(self.evaluator, cls) for cls in self.obj.__mro__[1:])
@CheckAttribute
def py__bases__(self):
return tuple(create(self.evaluator, cls) for cls in self.obj.__bases__)
def py__bool__(self):
return bool(self.obj)
def py__file__(self):
try:
return self.obj.__file__
except AttributeError:
return None
def is_class(self):
return inspect.isclass(self.obj)
def py__doc__(self, include_call_signature=False):
return inspect.getdoc(self.obj) or ''
def get_param_names(self):
obj = self.obj
try:
if py_version < 33:
raise ValueError("inspect.signature was introduced in 3.3")
if py_version == 34:
# In 3.4 inspect.signature are wrong for str and int. This has
# been fixed in 3.5. The signature of object is returned,
# because no signature was found for str. Here we imitate 3.5
# logic and just ignore the signature if the magic methods
# don't match object.
# 3.3 doesn't even have the logic and returns nothing for str
# and classes that inherit from object.
user_def = inspect._signature_get_user_defined_method
if (inspect.isclass(obj)
and not user_def(type(obj), '__init__')
and not user_def(type(obj), '__new__')
and (obj.__init__ != object.__init__
or obj.__new__ != object.__new__)):
raise ValueError
signature = inspect.signature(obj)
except ValueError: # Has no signature
params_str, ret = self._parse_function_doc()
tokens = params_str.split(',')
if inspect.ismethoddescriptor(obj):
tokens.insert(0, 'self')
for p in tokens:
parts = p.strip().split('=')
yield UnresolvableParamName(self, parts[0])
else:
for signature_param in signature.parameters.values():
yield SignatureParamName(self, signature_param)
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, repr(self.obj))
@underscore_memoization
def _parse_function_doc(self):
doc = self.py__doc__()
if doc is None:
return '', ''
return _parse_function_doc(doc)
@property
def api_type(self):
obj = self.obj
if inspect.isclass(obj):
return 'class'
elif inspect.ismodule(obj):
return 'module'
elif inspect.isbuiltin(obj) or inspect.ismethod(obj) \
or inspect.ismethoddescriptor(obj) or inspect.isfunction(obj):
return 'function'
# Everything else...
return 'instance'
@property
def type(self):
"""Imitate the tree.Node.type values."""
cls = self._get_class()
if inspect.isclass(cls):
return 'classdef'
elif inspect.ismodule(cls):
return 'file_input'
elif inspect.isbuiltin(cls) or inspect.ismethod(cls) or \
inspect.ismethoddescriptor(cls):
return 'funcdef'
@underscore_memoization
def _cls(self):
"""
We used to limit the lookups for instantiated objects like list(), but
this is not the case anymore. Python itself
"""
# Ensures that a CompiledObject is returned that is not an instance (like list)
return self
def _get_class(self):
if not fake.is_class_instance(self.obj) or \
inspect.ismethoddescriptor(self.obj): # slots
return self.obj
try:
return self.obj.__class__
except AttributeError:
# happens with numpy.core.umath._UFUNC_API (you get it
# automatically by doing `import numpy`.
return type
def get_filters(self, search_global=False, is_instance=False,
until_position=None, origin_scope=None):
yield self._ensure_one_filter(is_instance)
@memoize_method
def _ensure_one_filter(self, is_instance):
"""
search_global shouldn't change the fact that there's one dict, this way
there's only one `object`.
"""
return CompiledObjectFilter(self.evaluator, self, is_instance)
@CheckAttribute
def py__getitem__(self, index):
if type(self.obj) not in (str, list, tuple, unicode, bytes, bytearray, dict):
# Get rid of side effects, we won't call custom `__getitem__`s.
return ContextSet()
return ContextSet(create(self.evaluator, self.obj[index]))
@CheckAttribute
def py__iter__(self):
if type(self.obj) not in (str, list, tuple, unicode, bytes, bytearray, dict):
# Get rid of side effects, we won't call custom `__getitem__`s.
return
for i, part in enumerate(self.obj):
if i > 20:
# Should not go crazy with large iterators
break
yield LazyKnownContext(create(self.evaluator, part))
def py__name__(self):
try:
return self._get_class().__name__
except AttributeError:
return None
@property
def name(self):
try:
name = self._get_class().__name__
except AttributeError:
name = repr(self.obj)
return CompiledContextName(self, name)
def _execute_function(self, params):
from jedi.evaluate import docstrings
if self.type != 'funcdef':
return
for name in self._parse_function_doc()[1].split():
try:
bltn_obj = getattr(_builtins, name)
except AttributeError:
continue
else:
if bltn_obj is None:
# We want to evaluate everything except None.
# TODO do we?
continue
bltn_obj = create(self.evaluator, bltn_obj)
for result in bltn_obj.execute(params):
yield result
for type_ in docstrings.infer_return_types(self):
yield type_
def get_self_attributes(self):
return [] # Instance compatibility
def get_imports(self):
return [] # Builtins don't have imports
def dict_values(self):
return ContextSet.from_iterable(
create(self.evaluator, v) for v in self.obj.values()
)
class CompiledName(AbstractNameDefinition):
def __init__(self, evaluator, parent_context, name):
self._evaluator = evaluator
self.parent_context = parent_context
self.string_name = name
def __repr__(self):
try:
name = self.parent_context.name # __name__ is not defined all the time
except AttributeError:
name = None
return '<%s: (%s).%s>' % (self.__class__.__name__, name, self.string_name)
@property
def api_type(self):
return next(iter(self.infer())).api_type
@underscore_memoization
def infer(self):
module = self.parent_context.get_root_context()
return ContextSet(_create_from_name(
self._evaluator, module, self.parent_context, self.string_name
))
class SignatureParamName(AbstractNameDefinition):
api_type = 'param'
def __init__(self, compiled_obj, signature_param):
self.parent_context = compiled_obj.parent_context
self._signature_param = signature_param
@property
def string_name(self):
return self._signature_param.name
def infer(self):
p = self._signature_param
evaluator = self.parent_context.evaluator
contexts = ContextSet()
if p.default is not p.empty:
contexts = ContextSet(create(evaluator, p.default))
if p.annotation is not p.empty:
annotation = create(evaluator, p.annotation)
contexts |= annotation.execute_evaluated()
return contexts
class UnresolvableParamName(AbstractNameDefinition):
api_type = 'param'
def __init__(self, compiled_obj, name):
self.parent_context = compiled_obj.parent_context
self.string_name = name
def infer(self):
return ContextSet()
class CompiledContextName(ContextNameMixin, AbstractNameDefinition):
def __init__(self, context, name):
self.string_name = name
self._context = context
self.parent_context = context.parent_context
class EmptyCompiledName(AbstractNameDefinition):
"""
Accessing some names will raise an exception. To avoid not having any
completions, just give Jedi the option to return this object. It infers to
nothing.
"""
def __init__(self, evaluator, name):
self.parent_context = evaluator.BUILTINS
self.string_name = name
def infer(self):
return ContextSet()
class CompiledObjectFilter(AbstractFilter):
name_class = CompiledName
def __init__(self, evaluator, compiled_object, is_instance=False):
self._evaluator = evaluator
self._compiled_object = compiled_object
self._is_instance = is_instance
@memoize_method
def get(self, name):
name = str(name)
obj = self._compiled_object.obj
try:
attr, is_get_descriptor = getattr_static(obj, name)
except AttributeError:
return []
else:
if is_get_descriptor \
and not type(attr) in ALLOWED_DESCRIPTOR_ACCESS:
# In case of descriptors that have get methods we cannot return
# it's value, because that would mean code execution.
return [EmptyCompiledName(self._evaluator, name)]
if self._is_instance and name not in dir(obj):
return []
return [self._create_name(name)]
def values(self):
obj = self._compiled_object.obj
names = []
for name in dir(obj):
names += self.get(name)
is_instance = self._is_instance or fake.is_class_instance(obj)
# ``dir`` doesn't include the type names.
if not inspect.ismodule(obj) and (obj is not type) and not is_instance:
for filter in create(self._evaluator, type).get_filters():
names += filter.values()
return names
def _create_name(self, name):
return self.name_class(self._evaluator, self._compiled_object, name)
def dotted_from_fs_path(fs_path, sys_path):
"""
Changes `/usr/lib/python3.4/email/utils.py` to `email.utils`. I.e.
compares the path with sys.path and then returns the dotted_path. If the
path is not in the sys.path, just returns None.
"""
if os.path.basename(fs_path).startswith('__init__.'):
# We are calculating the path. __init__ files are not interesting.
fs_path = os.path.dirname(fs_path)
# prefer
# - UNIX
# /path/to/pythonX.Y/lib-dynload
# /path/to/pythonX.Y/site-packages
# - Windows
# C:\path\to\DLLs
# C:\path\to\Lib\site-packages
# over
# - UNIX
# /path/to/pythonX.Y
# - Windows
# C:\path\to\Lib
path = ''
for s in sys_path:
if (fs_path.startswith(s) and len(path) < len(s)):
path = s
# - Window
# X:\path\to\lib-dynload/datetime.pyd => datetime
module_path = fs_path[len(path):].lstrip(os.path.sep).lstrip('/')
# - Window
# Replace like X:\path\to\something/foo/bar.py
return _path_re.sub('', module_path).replace(os.path.sep, '.').replace('/', '.')
def load_module(evaluator, path=None, name=None):
sys_path = list(evaluator.project.sys_path)
if path is not None:
dotted_path = dotted_from_fs_path(path, sys_path=sys_path)
else:
dotted_path = name
temp, sys.path = sys.path, sys_path
try:
__import__(dotted_path)
except RuntimeError:
if 'PySide' in dotted_path or 'PyQt' in dotted_path:
# RuntimeError: the PyQt4.QtCore and PyQt5.QtCore modules both wrap
# the QObject class.
# See https://github.com/davidhalter/jedi/pull/483
return None
raise
except ImportError:
# If a module is "corrupt" or not really a Python module or whatever.
debug.warning('Module %s not importable in path %s.', dotted_path, path)
return None
finally:
sys.path = temp
# Just access the cache after import, because of #59 as well as the very
# complicated import structure of Python.
module = sys.modules[dotted_path]
return create(evaluator, module)
docstr_defaults = {
'floating point number': 'float',
'character': 'str',
'integer': 'int',
'dictionary': 'dict',
'string': 'str',
}
def _parse_function_doc(doc):
"""
Takes a function and returns the params and return value as a tuple.
This is nothing more than a docstring parser.
TODO docstrings like utime(path, (atime, mtime)) and a(b [, b]) -> None
TODO docstrings like 'tuple of integers'
"""
# parse round parentheses: def func(a, (b,c))
try:
count = 0
start = doc.index('(')
for i, s in enumerate(doc[start:]):
if s == '(':
count += 1
elif s == ')':
count -= 1
if count == 0:
end = start + i
break
param_str = doc[start + 1:end]
except (ValueError, UnboundLocalError):
# ValueError for doc.index
# UnboundLocalError for undefined end in last line
debug.dbg('no brackets found - no param')
end = 0
param_str = ''
else:
# remove square brackets, that show an optional param ( = None)
def change_options(m):
args = m.group(1).split(',')
for i, a in enumerate(args):
if a and '=' not in a:
args[i] += '=None'
return ','.join(args)
while True:
param_str, changes = re.subn(r' ?\[([^\[\]]+)\]',
change_options, param_str)
if changes == 0:
break
param_str = param_str.replace('-', '_') # see: isinstance.__doc__
# parse return value
r = re.search('-[>-]* ', doc[end:end + 7])
if r is None:
ret = ''
else:
index = end + r.end()
# get result type, which can contain newlines
pattern = re.compile(r'(,\n|[^\n-])+')
ret_str = pattern.match(doc, index).group(0).strip()
# New object -> object()
ret_str = re.sub(r'[nN]ew (.*)', r'\1()', ret_str)
ret = docstr_defaults.get(ret_str, ret_str)
return param_str, ret
def _create_from_name(evaluator, module, compiled_object, name):
obj = compiled_object.obj
faked = None
try:
faked = fake.get_faked(evaluator, module, obj, parent_context=compiled_object, name=name)
if faked.type == 'funcdef':
from jedi.evaluate.context.function import FunctionContext
return FunctionContext(evaluator, compiled_object, faked)
except fake.FakeDoesNotExist:
pass
try:
obj = getattr(obj, name)
except AttributeError:
# Happens e.g. in properties of
# PyQt4.QtGui.QStyleOptionComboBox.currentText
# -> just set it to None
obj = None
return create(evaluator, obj, parent_context=compiled_object, faked=faked)
from jedi._compatibility import unicode
from jedi.evaluate.compiled.context import CompiledObject, CompiledName, \
CompiledObjectFilter, CompiledContextName, create_from_access_path, \
create_from_name
def builtin_from_name(evaluator, string):
bltn_obj = getattr(_builtins, string)
return create(evaluator, bltn_obj)
builtins = evaluator.builtins_module
return create_from_name(evaluator, builtins, string)
def _a_generator(foo):
"""Used to have an object to return for generators."""
yield 42
yield foo
_SPECIAL_OBJECTS = {
'FUNCTION_CLASS': type(load_module),
'METHOD_CLASS': type(CompiledObject.is_class),
'MODULE_CLASS': type(os),
'GENERATOR_OBJECT': _a_generator(1.0),
'BUILTINS': _builtins,
}
def create_simple_object(evaluator, obj):
"""
Only allows creations of objects that are easily picklable across Python
versions.
"""
assert isinstance(obj, (int, float, str, bytes, unicode, slice, complex))
return create_from_access_path(
evaluator,
evaluator.compiled_subprocess.create_simple_object(obj)
)
def get_special_object(evaluator, identifier):
obj = _SPECIAL_OBJECTS[identifier]
return create(evaluator, obj, parent_context=create(evaluator, _builtins))
return create_from_access_path(
evaluator,
evaluator.compiled_subprocess.get_special_object(identifier)
)
def compiled_objects_cache(attribute_name):
def decorator(func):
"""
This decorator caches just the ids, oopposed to caching the object itself.
Caching the id has the advantage that an object doesn't need to be
hashable.
"""
def wrapper(evaluator, obj, parent_context=None, module=None, faked=None):
cache = getattr(evaluator, attribute_name)
# Do a very cheap form of caching here.
key = id(obj), id(parent_context)
try:
return cache[key][0]
except KeyError:
# TODO this whole decorator is way too ugly
result = func(evaluator, obj, parent_context, module, faked)
# Need to cache all of them, otherwise the id could be overwritten.
cache[key] = result, obj, parent_context, module, faked
return result
return wrapper
return decorator
def get_string_context_set(evaluator):
return builtin_from_name(evaluator, u'str').execute_evaluated()
@compiled_objects_cache('compiled_cache')
def create(evaluator, obj, parent_context=None, module=None, faked=None):
"""
A very weird interface class to this module. The more options provided the
more acurate loading compiled objects is.
"""
if inspect.ismodule(obj):
if parent_context is not None:
# Modules don't have parents, be careful with caching: recurse.
return create(evaluator, obj)
else:
if parent_context is None and obj is not _builtins:
return create(evaluator, obj, create(evaluator, _builtins))
try:
faked = fake.get_faked(evaluator, module, obj, parent_context=parent_context)
if faked.type == 'funcdef':
from jedi.evaluate.context.function import FunctionContext
return FunctionContext(evaluator, parent_context, faked)
except fake.FakeDoesNotExist:
pass
return CompiledObject(evaluator, obj, parent_context, faked)
def load_module(evaluator, dotted_name, **kwargs):
# Temporary, some tensorflow builtins cannot be loaded, so it's tried again
# and again and it's really slow.
if dotted_name.startswith('tensorflow.'):
return None
access_path = evaluator.compiled_subprocess.load_module(dotted_name=dotted_name, **kwargs)
if access_path is None:
return None
return create_from_access_path(evaluator, access_path)

View File

@@ -0,0 +1,483 @@
import inspect
import types
import sys
from textwrap import dedent
import operator as op
from collections import namedtuple
from jedi._compatibility import unicode, is_py3, builtins, \
py_version, force_unicode, print_to_stderr
from jedi.evaluate.compiled.getattr_static import getattr_static
MethodDescriptorType = type(str.replace)
# These are not considered classes and access is granted even though they have
# a __class__ attribute.
NOT_CLASS_TYPES = (
types.BuiltinFunctionType,
types.CodeType,
types.FrameType,
types.FunctionType,
types.GeneratorType,
types.GetSetDescriptorType,
types.LambdaType,
types.MemberDescriptorType,
types.MethodType,
types.ModuleType,
types.TracebackType,
MethodDescriptorType
)
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__)
# `object.__subclasshook__` is an already executed descriptor.
object_class_dict = type.__dict__["__dict__"].__get__(object)
ClassMethodDescriptorType = type(object_class_dict['__subclasshook__'])
def _a_generator(foo):
"""Used to have an object to return for generators."""
yield 42
yield foo
_sentinel = object()
# Maps Python syntax to the operator module.
COMPARISON_OPERATORS = {
'==': op.eq,
'!=': op.ne,
'is': op.is_,
'is not': op.is_not,
'<': op.lt,
'<=': op.le,
'>': op.gt,
'>=': op.ge,
}
_OPERATORS = {
'+': op.add,
'-': op.sub,
}
_OPERATORS.update(COMPARISON_OPERATORS)
ALLOWED_DESCRIPTOR_ACCESS = (
types.FunctionType,
types.GetSetDescriptorType,
types.MemberDescriptorType,
MethodDescriptorType,
WrapperDescriptorType,
ClassMethodDescriptorType,
staticmethod,
classmethod,
)
def safe_getattr(obj, name, default=_sentinel):
try:
attr, is_get_descriptor = getattr_static(obj, name)
except AttributeError:
if default is _sentinel:
raise
return default
else:
if type(attr) in ALLOWED_DESCRIPTOR_ACCESS:
# In case of descriptors that have get methods we cannot return
# it's value, because that would mean code execution.
return getattr(obj, name)
return attr
SignatureParam = namedtuple(
'SignatureParam',
'name has_default default has_annotation annotation kind_name'
)
def compiled_objects_cache(attribute_name):
def decorator(func):
"""
This decorator caches just the ids, oopposed to caching the object itself.
Caching the id has the advantage that an object doesn't need to be
hashable.
"""
def wrapper(evaluator, obj, parent_context=None):
cache = getattr(evaluator, attribute_name)
# Do a very cheap form of caching here.
key = id(obj)
try:
cache[key]
return cache[key][0]
except KeyError:
# TODO wuaaaarrghhhhhhhh
if attribute_name == 'mixed_cache':
result = func(evaluator, obj, parent_context)
else:
result = func(evaluator, obj)
# Need to cache all of them, otherwise the id could be overwritten.
cache[key] = result, obj, parent_context
return result
return wrapper
return decorator
def create_access(evaluator, obj):
return evaluator.compiled_subprocess.get_or_create_access_handle(obj)
def load_module(evaluator, dotted_name, sys_path):
temp, sys.path = sys.path, sys_path
try:
__import__(dotted_name)
except ImportError:
# If a module is "corrupt" or not really a Python module or whatever.
print_to_stderr('Module %s not importable in path %s.' % (dotted_name, sys_path))
return None
except Exception:
# Since __import__ pretty much makes code execution possible, just
# catch any error here and print it.
import traceback
print_to_stderr("Cannot import:\n%s" % traceback.format_exc())
return None
finally:
sys.path = temp
# Just access the cache after import, because of #59 as well as the very
# complicated import structure of Python.
module = sys.modules[dotted_name]
return create_access_path(evaluator, module)
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(evaluator, obj):
access = create_access(evaluator, obj)
return AccessPath(access.get_access_path_tuples())
def _force_unicode_decorator(func):
return lambda *args, **kwargs: force_unicode(func(*args, **kwargs))
class DirectObjectAccess(object):
def __init__(self, evaluator, obj):
self._evaluator = evaluator
self._obj = obj
def __repr__(self):
return '%s(%s)' % (self.__class__.__name__, self.get_repr())
def _create_access(self, obj):
return create_access(self._evaluator, obj)
def _create_access_path(self, obj):
return create_access_path(self._evaluator, obj)
def py__bool__(self):
return bool(self._obj)
def py__file__(self):
try:
return self._obj.__file__
except AttributeError:
return None
def py__doc__(self, include_call_signature=False):
return force_unicode(inspect.getdoc(self._obj)) or u''
def py__name__(self):
if not _is_class_instance(self._obj) or \
inspect.ismethoddescriptor(self._obj): # slots
cls = self._obj
else:
try:
cls = self._obj.__class__
except AttributeError:
# happens with numpy.core.umath._UFUNC_API (you get it
# automatically by doing `import numpy`.
return None
try:
return force_unicode(cls.__name__)
except AttributeError:
return None
def py__mro__accesses(self):
return tuple(self._create_access_path(cls) for cls in self._obj.__mro__[1:])
def py__getitem__(self, index):
if type(self._obj) not in (str, list, tuple, unicode, bytes, bytearray, dict):
# Get rid of side effects, we won't call custom `__getitem__`s.
return None
return self._create_access_path(self._obj[index])
def py__iter__list(self):
if type(self._obj) not in (str, list, tuple, unicode, bytes, bytearray, dict):
# Get rid of side effects, we won't call custom `__getitem__`s.
return []
lst = []
for i, part in enumerate(self._obj):
if i > 20:
# Should not go crazy with large iterators
break
lst.append(self._create_access_path(part))
return lst
def py__class__(self):
return self._create_access_path(self._obj.__class__)
def py__bases__(self):
return [self._create_access_path(base) for base in self._obj.__bases__]
def py__path__(self):
return self._obj.__path__
@_force_unicode_decorator
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:
return repr(self._obj)
type_ = type(self._obj)
if type_ == type:
return type.__repr__(self._obj)
if safe_getattr(type_, '__module__', default='') in builtins:
# Allow direct execution of repr for builtins.
return repr(self._obj)
return object.__repr__(self._obj)
def is_class(self):
return inspect.isclass(self._obj)
def ismethoddescriptor(self):
return inspect.ismethoddescriptor(self._obj)
def dir(self):
return list(map(force_unicode, dir(self._obj)))
def has_iter(self):
try:
iter(self._obj)
return True
except TypeError:
return False
def is_allowed_getattr(self, name):
# TODO this API is ugly.
try:
attr, is_get_descriptor = getattr_static(self._obj, name)
except AttributeError:
return False, False
else:
if is_get_descriptor and type(attr) not in ALLOWED_DESCRIPTOR_ACCESS:
# In case of descriptors that have get methods we cannot return
# it's value, because that would mean code execution.
return True, True
return True, False
def getattr(self, name, default=_sentinel):
try:
return self._create_access(getattr(self._obj, name))
except AttributeError:
# Happens e.g. in properties of
# PyQt4.QtGui.QStyleOptionComboBox.currentText
# -> just set it to None
if default is _sentinel:
raise
return self._create_access(default)
def get_safe_value(self):
if type(self._obj) in (bool, bytes, float, int, str, unicode, slice):
return self._obj
raise ValueError("Object is type %s and not simple" % type(self._obj))
def get_api_type(self):
obj = self._obj
if self.is_class():
return u'class'
elif inspect.ismodule(obj):
return u'module'
elif inspect.isbuiltin(obj) or inspect.ismethod(obj) \
or inspect.ismethoddescriptor(obj) or inspect.isfunction(obj):
return u'function'
# Everything else...
return u'instance'
def get_access_path_tuples(self):
accesses = [create_access(self._evaluator, o) for o in self._get_objects_path()]
return [(access.py__name__(), access) for access in accesses]
def _get_objects_path(self):
def get():
obj = self._obj
yield obj
try:
obj = obj.__objclass__
except AttributeError:
pass
else:
yield obj
try:
# Returns a dotted string path.
imp_plz = obj.__module__
except AttributeError:
# Unfortunately in some cases like `int` there's no __module__
if not inspect.ismodule(obj):
yield builtins
else:
if imp_plz is None:
# Happens for example in `(_ for _ in []).send.__module__`.
yield builtins
else:
try:
# TODO use sys.modules, __module__ can be faked.
yield sys.modules[imp_plz]
except KeyError:
# __module__ can be something arbitrary that doesn't exist.
yield builtins
return list(reversed(list(get())))
def execute_operation(self, other_access_handle, operator):
other_access = other_access_handle.access
op = _OPERATORS[operator]
return self._create_access_path(op(self._obj, other_access._obj))
def needs_type_completions(self):
return inspect.isclass(self._obj) and self._obj != type
def get_signature_params(self):
obj = self._obj
if py_version < 33:
raise ValueError("inspect.signature was introduced in 3.3")
if py_version == 34:
# In 3.4 inspect.signature are wrong for str and int. This has
# been fixed in 3.5. The signature of object is returned,
# because no signature was found for str. Here we imitate 3.5
# logic and just ignore the signature if the magic methods
# don't match object.
# 3.3 doesn't even have the logic and returns nothing for str
# and classes that inherit from object.
user_def = inspect._signature_get_user_defined_method
if (inspect.isclass(obj)
and not user_def(type(obj), '__init__')
and not user_def(type(obj), '__new__')
and (obj.__init__ != object.__init__
or obj.__new__ != object.__new__)):
raise ValueError
try:
signature = inspect.signature(obj)
except (RuntimeError, TypeError):
# Reading the code of the function in Python 3.6 implies there are
# at least these errors that might occur if something is wrong with
# the signature. In that case we just want a simple escape for now.
raise ValueError
return [
SignatureParam(
name=p.name,
has_default=p.default is not p.empty,
default=self._create_access_path(p.default),
has_annotation=p.annotation is not p.empty,
annotation=self._create_access_path(p.annotation),
kind_name=str(p.kind)
) for p in signature.parameters.values()
]
def negate(self):
return self._create_access_path(-self._obj)
def dict_values(self):
return [self._create_access_path(v) for v in self._obj.values()]
def is_super_class(self, exception):
return issubclass(exception, self._obj)
def get_dir_infos(self):
"""
Used to return a couple of infos that are needed when accessing the sub
objects of an objects
"""
# TODO is_allowed_getattr might raise an AttributeError
tuples = dict(
(force_unicode(name), self.is_allowed_getattr(name))
for name in self.dir()
)
return self.needs_type_completions(), tuples
def _is_class_instance(obj):
"""Like inspect.* methods."""
try:
cls = obj.__class__
except AttributeError:
return False
else:
return cls != type and not issubclass(cls, NOT_CLASS_TYPES)
if py_version >= 35:
exec(compile(dedent("""
async def _coroutine(): pass
_coroutine = _coroutine()
CoroutineType = type(_coroutine)
_coroutine.close() # Prevent ResourceWarning
"""), 'blub', 'exec'))
_coroutine_wrapper = _coroutine.__await__()
else:
_coroutine = None
_coroutine_wrapper = None
if py_version >= 36:
exec(compile(dedent("""
async def _async_generator():
yield
_async_generator = _async_generator()
AsyncGeneratorType = type(_async_generator)
"""), 'blub', 'exec'))
else:
_async_generator = None
class _SPECIAL_OBJECTS(object):
FUNCTION_CLASS = types.FunctionType
BOUND_METHOD_CLASS = type(DirectObjectAccess(None, None).py__bool__)
MODULE_CLASS = types.ModuleType
GENERATOR_OBJECT = _a_generator(1.0)
BUILTINS = builtins
COROUTINE = _coroutine
COROUTINE_WRAPPER = _coroutine_wrapper
ASYNC_GENERATOR = _async_generator
def get_special_object(evaluator, identifier):
obj = getattr(_SPECIAL_OBJECTS, identifier)
return create_access_path(evaluator, obj)

View File

@@ -0,0 +1,483 @@
"""
Imitate the parser representation.
"""
import re
from functools import partial
from jedi import debug
from jedi._compatibility import force_unicode, Parameter
from jedi.cache import underscore_memoization, memoize_method
from jedi.evaluate.filters import AbstractFilter, AbstractNameDefinition, \
ContextNameMixin
from jedi.evaluate.base_context import Context, ContextSet
from jedi.evaluate.lazy_context import LazyKnownContext
from jedi.evaluate.compiled.access import _sentinel
from jedi.evaluate.cache import evaluator_function_cache
from jedi.evaluate.helpers import reraise_as_evaluator
from . import fake
class CheckAttribute(object):
"""Raises an AttributeError if the attribute X isn't available."""
def __init__(self, func):
self.func = func
# Remove the py in front of e.g. py__call__.
self.check_name = force_unicode(func.__name__[2:])
def __get__(self, instance, owner):
if instance is None:
return self
# This might raise an AttributeError. That's wanted.
if self.check_name == '__iter__':
# 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 instance.access_handle.has_iter():
raise AttributeError
else:
instance.access_handle.getattr(self.check_name)
return partial(self.func, instance)
class CompiledObject(Context):
def __init__(self, evaluator, access_handle, parent_context=None, faked_class=None):
super(CompiledObject, self).__init__(evaluator, parent_context)
self.access_handle = access_handle
# This attribute will not be set for most classes, except for fakes.
self.tree_node = faked_class
@CheckAttribute
def py__call__(self, params):
if self.tree_node is not None and self.tree_node.type == 'funcdef':
from jedi.evaluate.context.function import FunctionContext
return FunctionContext(
self.evaluator,
parent_context=self.parent_context,
tree_node=self.tree_node
).py__call__(params)
if self.access_handle.is_class():
from jedi.evaluate.context import CompiledInstance
return ContextSet(CompiledInstance(self.evaluator, self.parent_context, self, params))
else:
return ContextSet.from_iterable(self._execute_function(params))
@CheckAttribute
def py__class__(self):
return create_from_access_path(self.evaluator, self.access_handle.py__class__())
@CheckAttribute
def py__mro__(self):
return (self,) + tuple(
create_from_access_path(self.evaluator, access)
for access in self.access_handle.py__mro__accesses()
)
@CheckAttribute
def py__bases__(self):
return tuple(
create_from_access_path(self.evaluator, access)
for access in self.access_handle.py__bases__()
)
@CheckAttribute
def py__path__(self):
return self.access_handle.py__path__()
def py__bool__(self):
return self.access_handle.py__bool__()
def py__file__(self):
return self.access_handle.py__file__()
def is_class(self):
return self.access_handle.is_class()
def py__doc__(self, include_call_signature=False):
return self.access_handle.py__doc__()
def get_param_names(self):
try:
signature_params = self.access_handle.get_signature_params()
except ValueError: # Has no signature
params_str, ret = self._parse_function_doc()
tokens = params_str.split(',')
if self.access_handle.ismethoddescriptor():
tokens.insert(0, 'self')
for p in tokens:
parts = p.strip().split('=')
yield UnresolvableParamName(self, parts[0])
else:
for signature_param in signature_params:
yield SignatureParamName(self, signature_param)
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self.access_handle.get_repr())
@underscore_memoization
def _parse_function_doc(self):
doc = self.py__doc__()
if doc is None:
return '', ''
return _parse_function_doc(doc)
@property
def api_type(self):
return self.access_handle.get_api_type()
@underscore_memoization
def _cls(self):
"""
We used to limit the lookups for instantiated objects like list(), but
this is not the case anymore. Python itself
"""
# Ensures that a CompiledObject is returned that is not an instance (like list)
return self
def get_filters(self, search_global=False, is_instance=False,
until_position=None, origin_scope=None):
yield self._ensure_one_filter(is_instance)
@memoize_method
def _ensure_one_filter(self, is_instance):
"""
search_global shouldn't change the fact that there's one dict, this way
there's only one `object`.
"""
return CompiledObjectFilter(self.evaluator, self, is_instance)
@CheckAttribute
def py__getitem__(self, index):
with reraise_as_evaluator(IndexError, KeyError, TypeError):
access = self.access_handle.py__getitem__(index)
if access is None:
return ContextSet()
return ContextSet(create_from_access_path(self.evaluator, access))
@CheckAttribute
def py__iter__(self):
for access in self.access_handle.py__iter__list():
yield LazyKnownContext(create_from_access_path(self.evaluator, access))
def py__name__(self):
return self.access_handle.py__name__()
@property
def name(self):
name = self.py__name__()
if name is None:
name = self.access_handle.get_repr()
return CompiledContextName(self, name)
def _execute_function(self, params):
from jedi.evaluate import docstrings
from jedi.evaluate.compiled import builtin_from_name
if self.api_type != 'function':
return
for name in self._parse_function_doc()[1].split():
try:
# TODO wtf is this? this is exactly the same as the thing
# below. It uses getattr as well.
self.evaluator.builtins_module.access_handle.getattr(name)
except AttributeError:
continue
else:
bltn_obj = builtin_from_name(self.evaluator, name)
for result in bltn_obj.execute(params):
yield result
for type_ in docstrings.infer_return_types(self):
yield type_
def dict_values(self):
return ContextSet.from_iterable(
create_from_access_path(self.evaluator, access)
for access in self.access_handle.dict_values()
)
def get_safe_value(self, default=_sentinel):
try:
return self.access_handle.get_safe_value()
except ValueError:
if default == _sentinel:
raise
return default
def execute_operation(self, other, operator):
return create_from_access_path(
self.evaluator,
self.access_handle.execute_operation(other.access_handle, operator)
)
def negate(self):
return create_from_access_path(self.evaluator, self.access_handle.negate())
def is_super_class(self, exception):
return self.access_handle.is_super_class(exception)
class CompiledName(AbstractNameDefinition):
def __init__(self, evaluator, parent_context, name):
self._evaluator = evaluator
self.parent_context = parent_context
self.string_name = name
def __repr__(self):
try:
name = self.parent_context.name # __name__ is not defined all the time
except AttributeError:
name = None
return '<%s: (%s).%s>' % (self.__class__.__name__, name, self.string_name)
@property
def api_type(self):
return next(iter(self.infer())).api_type
@underscore_memoization
def infer(self):
return ContextSet(create_from_name(
self._evaluator, self.parent_context, self.string_name
))
class SignatureParamName(AbstractNameDefinition):
api_type = u'param'
def __init__(self, compiled_obj, signature_param):
self.parent_context = compiled_obj.parent_context
self._signature_param = signature_param
@property
def string_name(self):
return self._signature_param.name
def get_kind(self):
return getattr(Parameter, self._signature_param.kind_name)
def is_keyword_param(self):
return self._signature_param
def infer(self):
p = self._signature_param
evaluator = self.parent_context.evaluator
contexts = ContextSet()
if p.has_default:
contexts = ContextSet(create_from_access_path(evaluator, p.default))
if p.has_annotation:
annotation = create_from_access_path(evaluator, p.annotation)
contexts |= annotation.execute_evaluated()
return contexts
class UnresolvableParamName(AbstractNameDefinition):
api_type = u'param'
def __init__(self, compiled_obj, name):
self.parent_context = compiled_obj.parent_context
self.string_name = name
def get_kind(self):
return Parameter.POSITIONAL_ONLY
def infer(self):
return ContextSet()
class CompiledContextName(ContextNameMixin, AbstractNameDefinition):
def __init__(self, context, name):
self.string_name = name
self._context = context
self.parent_context = context.parent_context
class EmptyCompiledName(AbstractNameDefinition):
"""
Accessing some names will raise an exception. To avoid not having any
completions, just give Jedi the option to return this object. It infers to
nothing.
"""
def __init__(self, evaluator, name):
self.parent_context = evaluator.builtins_module
self.string_name = name
def infer(self):
return ContextSet()
class CompiledObjectFilter(AbstractFilter):
name_class = CompiledName
def __init__(self, evaluator, compiled_object, is_instance=False):
self._evaluator = evaluator
self._compiled_object = compiled_object
self._is_instance = is_instance
def get(self, name):
return self._get(
name,
lambda: self._compiled_object.access_handle.is_allowed_getattr(name),
lambda: self._compiled_object.access_handle.dir(),
check_has_attribute=True
)
def _get(self, name, allowed_getattr_callback, dir_callback, check_has_attribute=False):
"""
To remove quite a few access calls we introduced the callback here.
"""
has_attribute, is_descriptor = allowed_getattr_callback()
if check_has_attribute and not has_attribute:
return []
# Always use unicode objects in Python 2 from here.
name = force_unicode(name)
if is_descriptor or not has_attribute:
return [self._get_cached_name(name, is_empty=True)]
if self._is_instance and name not in dir_callback():
return []
return [self._get_cached_name(name)]
@memoize_method
def _get_cached_name(self, name, is_empty=False):
if is_empty:
return EmptyCompiledName(self._evaluator, name)
else:
return self._create_name(name)
def values(self):
from jedi.evaluate.compiled import builtin_from_name
names = []
needs_type_completions, dir_infos = self._compiled_object.access_handle.get_dir_infos()
for name in dir_infos:
names += self._get(
name,
lambda: dir_infos[name],
lambda: dir_infos.keys(),
)
# ``dir`` doesn't include the type names.
if not self._is_instance and needs_type_completions:
for filter in builtin_from_name(self._evaluator, u'type').get_filters():
names += filter.values()
return names
def _create_name(self, name):
return self.name_class(self._evaluator, self._compiled_object, name)
docstr_defaults = {
'floating point number': u'float',
'character': u'str',
'integer': u'int',
'dictionary': u'dict',
'string': u'str',
}
def _parse_function_doc(doc):
"""
Takes a function and returns the params and return value as a tuple.
This is nothing more than a docstring parser.
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
start = doc.index('(')
for i, s in enumerate(doc[start:]):
if s == '(':
count += 1
elif s == ')':
count -= 1
if count == 0:
end = start + i
break
param_str = doc[start + 1:end]
except (ValueError, UnboundLocalError):
# ValueError for doc.index
# UnboundLocalError for undefined end in last line
debug.dbg('no brackets found - no param')
end = 0
param_str = u''
else:
# remove square brackets, that show an optional param ( = None)
def change_options(m):
args = m.group(1).split(',')
for i, a in enumerate(args):
if a and '=' not in a:
args[i] += '=None'
return ','.join(args)
while True:
param_str, changes = re.subn(r' ?\[([^\[\]]+)\]',
change_options, param_str)
if changes == 0:
break
param_str = param_str.replace('-', '_') # see: isinstance.__doc__
# parse return value
r = re.search(u'-[>-]* ', doc[end:end + 7])
if r is None:
ret = u''
else:
index = end + r.end()
# get result type, which can contain newlines
pattern = re.compile(r'(,\n|[^\n-])+')
ret_str = pattern.match(doc, index).group(0).strip()
# New object -> object()
ret_str = re.sub(r'[nN]ew (.*)', r'\1()', ret_str)
ret = docstr_defaults.get(ret_str, ret_str)
return param_str, ret
def create_from_name(evaluator, compiled_object, name):
faked = None
try:
faked = fake.get_faked_with_parent_context(compiled_object, name)
except fake.FakeDoesNotExist:
pass
access = compiled_object.access_handle.getattr(name, default=None)
parent_context = compiled_object
if parent_context.is_class():
parent_context = parent_context.parent_context
return create_cached_compiled_object(
evaluator, access, parent_context=parent_context, faked=faked
)
def _normalize_create_args(func):
"""The cache doesn't care about keyword vs. normal args."""
def wrapper(evaluator, obj, parent_context=None, faked=None):
return func(evaluator, obj, parent_context, faked)
return wrapper
def create_from_access_path(evaluator, access_path):
parent_context = None
for name, access in access_path.accesses:
try:
if parent_context is None:
faked = fake.get_faked_module(evaluator, access_path.accesses[0][0])
else:
faked = fake.get_faked_with_parent_context(parent_context, name)
except fake.FakeDoesNotExist:
faked = None
parent_context = create_cached_compiled_object(evaluator, access, parent_context, faked)
return parent_context
@_normalize_create_args
@evaluator_function_cache()
def create_cached_compiled_object(evaluator, access_handle, parent_context, faked):
return CompiledObject(evaluator, access_handle, parent_context, faked)

View File

@@ -5,73 +5,59 @@ mixing in Python code, the autocompletion should work much better for builtins.
"""
import os
import inspect
import types
from itertools import chain
from parso.python import tree
from jedi._compatibility import unicode
from jedi._compatibility import is_py3, builtins, unicode, is_py34
modules = {}
fake_modules = {}
MethodDescriptorType = type(str.replace)
# These are not considered classes and access is granted even though they have
# a __class__ attribute.
NOT_CLASS_TYPES = (
types.BuiltinFunctionType,
types.CodeType,
types.FrameType,
types.FunctionType,
types.GeneratorType,
types.GetSetDescriptorType,
types.LambdaType,
types.MemberDescriptorType,
types.MethodType,
types.ModuleType,
types.TracebackType,
MethodDescriptorType
)
def _get_path_dict():
path = os.path.dirname(os.path.abspath(__file__))
base_path = os.path.join(path, 'fake')
dct = {}
for file_name in os.listdir(base_path):
if file_name.endswith('.pym'):
dct[file_name[:-4]] = os.path.join(base_path, file_name)
return dct
if is_py3:
NOT_CLASS_TYPES += (
types.MappingProxyType,
types.SimpleNamespace
)
if is_py34:
NOT_CLASS_TYPES += (types.DynamicClassAttribute,)
_path_dict = _get_path_dict()
class FakeDoesNotExist(Exception):
pass
def _load_faked_module(grammar, module):
module_name = module.__name__
if module_name == '__builtin__' and not is_py3:
module_name = 'builtins'
def _load_faked_module(evaluator, module_name):
try:
return fake_modules[module_name]
except KeyError:
pass
check_module_name = module_name
if module_name == '__builtin__' and evaluator.environment.version_info.major == 2:
check_module_name = 'builtins'
try:
return modules[module_name]
path = _path_dict[check_module_name]
except KeyError:
path = os.path.dirname(os.path.abspath(__file__))
try:
with open(os.path.join(path, 'fake', module_name) + '.pym') as f:
source = f.read()
except IOError:
modules[module_name] = None
return
modules[module_name] = m = grammar.parse(unicode(source))
fake_modules[module_name] = None
return
if module_name == 'builtins' and not is_py3:
# There are two implementations of `open` for either python 2/3.
# -> Rename the python2 version (`look at fake/builtins.pym`).
open_func = _search_scope(m, 'open')
open_func.children[1].value = 'open_python3'
open_func = _search_scope(m, 'open_python2')
open_func.children[1].value = 'open'
return m
with open(path) as f:
source = f.read()
fake_modules[module_name] = m = evaluator.latest_grammar.parse(unicode(source))
if check_module_name != module_name:
# There are two implementations of `open` for either python 2/3.
# -> Rename the python2 version (`look at fake/builtins.pym`).
open_func = _search_scope(m, 'open')
open_func.children[1].value = 'open_python3'
open_func = _search_scope(m, 'open_python2')
open_func.children[1].value = 'open'
return m
def _search_scope(scope, obj_name):
@@ -80,134 +66,17 @@ def _search_scope(scope, obj_name):
return s
def get_module(obj):
if inspect.ismodule(obj):
return obj
try:
obj = obj.__objclass__
except AttributeError:
pass
try:
imp_plz = obj.__module__
except AttributeError:
# Unfortunately in some cases like `int` there's no __module__
return builtins
else:
if imp_plz is None:
# Happens for example in `(_ for _ in []).send.__module__`.
return builtins
else:
try:
return __import__(imp_plz)
except ImportError:
# __module__ can be something arbitrary that doesn't exist.
return builtins
def _faked(grammar, module, obj, name):
# Crazy underscore actions to try to escape all the internal madness.
if module is None:
module = get_module(obj)
faked_mod = _load_faked_module(grammar, module)
if faked_mod is None:
return None, None
# Having the module as a `parser.python.tree.Module`, we need to scan
# for methods.
if name is None:
if inspect.isbuiltin(obj) or inspect.isclass(obj):
return _search_scope(faked_mod, obj.__name__), faked_mod
elif not inspect.isclass(obj):
# object is a method or descriptor
try:
objclass = obj.__objclass__
except AttributeError:
return None, None
else:
cls = _search_scope(faked_mod, objclass.__name__)
if cls is None:
return None, None
return _search_scope(cls, obj.__name__), faked_mod
else:
if obj is module:
return _search_scope(faked_mod, name), faked_mod
else:
try:
cls_name = obj.__name__
except AttributeError:
return None, None
cls = _search_scope(faked_mod, cls_name)
if cls is None:
return None, None
return _search_scope(cls, name), faked_mod
return None, None
def memoize_faked(obj):
"""
A typical memoize function that ignores issues with non hashable results.
"""
cache = obj.cache = {}
def memoizer(*args, **kwargs):
key = (obj, args, frozenset(kwargs.items()))
try:
result = cache[key]
except (TypeError, ValueError):
return obj(*args, **kwargs)
except KeyError:
result = obj(*args, **kwargs)
if result is not None:
cache[key] = obj(*args, **kwargs)
return result
else:
return result
return memoizer
@memoize_faked
def _get_faked(grammar, module, obj, name=None):
result, fake_module = _faked(grammar, module, obj, name)
if result is None:
# We're not interested in classes. What we want is functions.
raise FakeDoesNotExist
elif result.type == 'classdef':
return result, fake_module
else:
# Set the docstr which was previously not set (faked modules don't
# contain it).
assert result.type == 'funcdef'
doc = '"""%s"""' % obj.__doc__ # TODO need escapes.
suite = result.children[-1]
string = tree.String(doc, (0, 0), '')
new_line = tree.Newline('\n', (0, 0))
docstr_node = tree.PythonNode('simple_stmt', [string, new_line])
suite.children.insert(1, docstr_node)
return result, fake_module
def get_faked(evaluator, module, obj, name=None, parent_context=None):
if parent_context and parent_context.tree_node is not None:
def get_faked_with_parent_context(parent_context, name):
if parent_context.tree_node is not None:
# Try to search in already clearly defined stuff.
found = _search_scope(parent_context.tree_node, name)
if found is not None:
return found
else:
raise FakeDoesNotExist
faked, fake_module = _get_faked(evaluator.latest_grammar, module and module.obj, obj, name)
if module is not None:
module.get_used_names = fake_module.get_used_names
return faked
raise FakeDoesNotExist
def is_class_instance(obj):
"""Like inspect.* methods."""
try:
cls = obj.__class__
except AttributeError:
return False
else:
return cls != type and not issubclass(cls, NOT_CLASS_TYPES)
def get_faked_module(evaluator, string_name):
module = _load_faked_module(evaluator, string_name)
if module is None:
raise FakeDoesNotExist
return module

View File

@@ -201,10 +201,13 @@ class dict():
# has a strange docstr
pass
def __getitem__(self, obj):
return self.__elements[obj]
def get(self, k, d=None):
# TODO implement
try:
#return self.__elements[k]
return self.__elements[k]
pass
except KeyError:
return d

View File

@@ -10,6 +10,7 @@ from jedi._compatibility import py_version
_sentinel = object()
def _check_instance(obj, attr):
instance_dict = {}
try:
@@ -28,6 +29,7 @@ def _check_class(klass, attr):
pass
return _sentinel
def _is_type(obj):
try:
_static_getmro(obj)
@@ -87,7 +89,7 @@ else:
return getattr(klass, '__dict__', _sentinel)
return _shadowed_dict_newstyle(klass)
class _OldStyleClass():
class _OldStyleClass:
pass
_oldstyle_instance_type = type(_OldStyleClass())
@@ -122,7 +124,7 @@ def _safe_hasattr(obj, name):
def _safe_is_data_descriptor(obj):
return (_safe_hasattr(obj, '__set__') or _safe_hasattr(obj, '__delete__'))
return _safe_hasattr(obj, '__set__') or _safe_hasattr(obj, '__delete__')
def getattr_static(obj, attr, default=_sentinel):
@@ -143,8 +145,7 @@ def getattr_static(obj, attr, default=_sentinel):
if not _is_type(obj):
klass = _get_type(obj)
dict_attr = _shadowed_dict(klass)
if (dict_attr is _sentinel or
type(dict_attr) is types.MemberDescriptorType):
if (dict_attr is _sentinel or type(dict_attr) is types.MemberDescriptorType):
instance_result = _check_instance(obj, attr)
else:
klass = obj

View File

@@ -5,6 +5,8 @@ Used only for REPL Completion.
import inspect
import os
from jedi.parser_utils import get_cached_code_lines
from jedi import settings
from jedi.evaluate import compiled
from jedi.cache import underscore_memoization
@@ -13,6 +15,8 @@ from jedi.evaluate.base_context import Context, ContextSet
from jedi.evaluate.context import ModuleContext
from jedi.evaluate.cache import evaluator_function_cache
from jedi.evaluate.compiled.getattr_static import getattr_static
from jedi.evaluate.compiled.access import compiled_objects_cache
from jedi.evaluate.compiled.context import create_cached_compiled_object
class MixedObject(object):
@@ -37,7 +41,7 @@ class MixedObject(object):
self.parent_context = parent_context
self.compiled_object = compiled_object
self._context = tree_context
self.obj = compiled_object.obj
self.access_handle = compiled_object.access_handle
# We have to overwrite everything that has to do with trailers, name
# lookups and filters to make it possible to route name lookups towards
@@ -49,7 +53,7 @@ class MixedObject(object):
yield MixedObjectFilter(self.evaluator, self)
def __repr__(self):
return '<%s: %s>' % (type(self).__name__, repr(self.obj))
return '<%s: %s>' % (type(self).__name__, self.access_handle.get_repr())
def __getattr__(self, name):
return getattr(self._context, name)
@@ -64,7 +68,7 @@ class MixedName(compiled.CompiledName):
contexts = list(self.infer())
if not contexts:
# This means a start_pos that doesn't exist (compiled objects).
return (0, 0)
return 0, 0
return contexts[0].name.start_pos
@start_pos.setter
@@ -74,17 +78,11 @@ class MixedName(compiled.CompiledName):
@underscore_memoization
def infer(self):
obj = self.parent_context.obj
try:
# TODO use logic from compiled.CompiledObjectFilter
obj = getattr(obj, self.string_name)
except AttributeError:
# Happens e.g. in properties of
# PyQt4.QtGui.QStyleOptionComboBox.currentText
# -> just set it to None
obj = None
access_handle = self.parent_context.access_handle
# TODO use logic from compiled.CompiledObjectFilter
access_handle = access_handle.getattr(self.string_name, default=None)
return ContextSet(
_create(self._evaluator, obj, parent_context=self.parent_context)
_create(self._evaluator, access_handle, parent_context=self.parent_context)
)
@property
@@ -105,17 +103,17 @@ class MixedObjectFilter(compiled.CompiledObjectFilter):
@evaluator_function_cache()
def _load_module(evaluator, path, python_object):
module = evaluator.grammar.parse(
def _load_module(evaluator, path):
module_node = evaluator.grammar.parse(
path=path,
cache=True,
diff_cache=True,
diff_cache=settings.fast_parser,
cache_path=settings.cache_directory
).get_root_node()
python_module = inspect.getmodule(python_object)
evaluator.modules[python_module.__name__] = module
return module
# python_module = inspect.getmodule(python_object)
# TODO we should actually make something like this possible.
#evaluator.modules[python_module.__name__] = module_node
return module_node
def _get_object_to_check(python_object):
@@ -135,40 +133,43 @@ def _get_object_to_check(python_object):
raise TypeError # Prevents computation of `repr` within inspect.
def find_syntax_node_name(evaluator, python_object):
def _find_syntax_node_name(evaluator, access_handle):
# TODO accessing this is bad, but it probably doesn't matter that much,
# because we're working with interpreteters only here.
python_object = access_handle.access._obj
try:
python_object = _get_object_to_check(python_object)
path = inspect.getsourcefile(python_object)
except TypeError:
# The type might not be known (e.g. class_with_dict.__weakref__)
return None, None
return None
if path is None or not os.path.exists(path):
# The path might not exist or be e.g. <stdin>.
return None, None
return None
module = _load_module(evaluator, path, python_object)
module_node = _load_module(evaluator, path)
if inspect.ismodule(python_object):
# We don't need to check names for modules, because there's not really
# a way to write a module in a module in Python (and also __name__ can
# be something like ``email.utils``).
return module, path
code_lines = get_cached_code_lines(evaluator.grammar, path)
return module_node, module_node, path, code_lines
try:
name_str = python_object.__name__
except AttributeError:
# Stuff like python_function.__code__.
return None, None
return None
if name_str == '<lambda>':
return None, None # It's too hard to find lambdas.
return None # It's too hard to find lambdas.
# Doesn't always work (e.g. os.stat_result)
try:
names = module.get_used_names()[name_str]
except KeyError:
return None, None
names = module_node.get_used_names().get(name_str, [])
names = [n for n in names if n.is_definition()]
if not names:
return None
try:
code = python_object.__code__
@@ -184,33 +185,40 @@ def find_syntax_node_name(evaluator, python_object):
# There's a chance that the object is not available anymore, because
# the code has changed in the background.
if line_names:
return line_names[-1].parent, path
names = line_names
code_lines = get_cached_code_lines(evaluator.grammar, path)
# It's really hard to actually get the right definition, here as a last
# resort we just return the last one. This chance might lead to odd
# completions at some points but will lead to mostly correct type
# inference, because people tend to define a public name in a module only
# once.
return names[-1].parent, path
return module_node, names[-1].parent, path, code_lines
@compiled.compiled_objects_cache('mixed_cache')
def _create(evaluator, obj, parent_context=None, *args):
tree_node, path = find_syntax_node_name(evaluator, obj)
@compiled_objects_cache('mixed_cache')
def _create(evaluator, access_handle, parent_context, *args):
compiled_object = create_cached_compiled_object(
evaluator, access_handle, parent_context=parent_context.compiled_object)
compiled_object = compiled.create(
evaluator, obj, parent_context=parent_context.compiled_object)
if tree_node is None:
result = _find_syntax_node_name(evaluator, access_handle)
if result is None:
return compiled_object
module_node = tree_node.get_root_node()
module_node, tree_node, path, code_lines = result
if parent_context.tree_node.get_root_node() == module_node:
module_context = parent_context.get_root_context()
else:
module_context = ModuleContext(evaluator, module_node, path=path)
module_context = ModuleContext(
evaluator, module_node,
path=path,
code_lines=code_lines,
)
# TODO this __name__ is probably wrong.
name = compiled_object.get_root_context().py__name__()
imports.add_module(evaluator, name, module_context)
if name is not None:
imports.add_module_to_cache(evaluator, name, module_context)
tree_context = module_context.create_context(
tree_node,
@@ -218,7 +226,7 @@ def _create(evaluator, obj, parent_context=None, *args):
node_is_object=True
)
if tree_node.type == 'classdef':
if not inspect.isclass(obj):
if not access_handle.is_class():
# Is an instance, not a class.
tree_context, = tree_context.execute_evaluated()
@@ -228,4 +236,3 @@ def _create(evaluator, obj, parent_context=None, *args):
compiled_object,
tree_context=tree_context
)

View File

@@ -0,0 +1,397 @@
"""
Makes it possible to do the compiled analysis in a subprocess. This has two
goals:
1. Making it safer - Segfaults and RuntimeErrors as well as stdout/stderr can
be ignored and dealt with.
2. Make it possible to handle different Python versions as well as virtualenvs.
"""
import os
import sys
import subprocess
import socket
import errno
import weakref
import traceback
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, print_to_stderr
from jedi import debug
from jedi.cache import memoize_method
from jedi.evaluate.compiled.subprocess import functions
from jedi.evaluate.compiled.access import DirectObjectAccess, AccessPath, \
SignatureParam
from jedi.api.exceptions import InternalError
_MAIN_PATH = os.path.join(os.path.dirname(__file__), '__main__.py')
def _enqueue_output(out, queue):
for line in iter(out.readline, b''):
queue.put(line)
out.close()
def _add_stderr_to_debug(stderr_queue):
while True:
# Try to do some error reporting from the subprocess and print its
# stderr contents.
try:
line = stderr_queue.get_nowait()
line = line.decode('utf-8', 'replace')
debug.warning('stderr output: %s' % line.rstrip('\n'))
except Empty:
break
def _get_function(name):
return getattr(functions, name)
class _EvaluatorProcess(object):
def __init__(self, evaluator):
self._evaluator_weakref = weakref.ref(evaluator)
self._evaluator_id = id(evaluator)
self._handles = {}
def get_or_create_access_handle(self, obj):
id_ = id(obj)
try:
return self.get_access_handle(id_)
except KeyError:
access = DirectObjectAccess(self._evaluator_weakref(), obj)
handle = AccessHandle(self, access, id_)
self.set_access_handle(handle)
return handle
def get_access_handle(self, id_):
return self._handles[id_]
def set_access_handle(self, handle):
self._handles[handle.id] = handle
class EvaluatorSameProcess(_EvaluatorProcess):
"""
Basically just an easy access to functions.py. It has the same API
as EvaluatorSubprocess and does the same thing without using a subprocess.
This is necessary for the Interpreter process.
"""
def __getattr__(self, name):
return partial(_get_function(name), self._evaluator_weakref())
class EvaluatorSubprocess(_EvaluatorProcess):
def __init__(self, evaluator, compiled_subprocess):
super(EvaluatorSubprocess, self).__init__(evaluator)
self._used = False
self._compiled_subprocess = compiled_subprocess
def __getattr__(self, name):
func = _get_function(name)
def wrapper(*args, **kwargs):
self._used = True
result = self._compiled_subprocess.run(
self._evaluator_weakref(),
func,
args=args,
kwargs=kwargs,
)
# IMO it should be possible to create a hook in pickle.load to
# mess with the loaded objects. However it's extremely complicated
# to work around this so just do it with this call. ~ dave
return self._convert_access_handles(result)
return wrapper
def _convert_access_handles(self, obj):
if isinstance(obj, SignatureParam):
return SignatureParam(*self._convert_access_handles(tuple(obj)))
elif isinstance(obj, tuple):
return tuple(self._convert_access_handles(o) for o in obj)
elif isinstance(obj, list):
return [self._convert_access_handles(o) for o in obj]
elif isinstance(obj, AccessHandle):
try:
# Rewrite the access handle to one we're already having.
obj = self.get_access_handle(obj.id)
except KeyError:
obj.add_subprocess(self)
self.set_access_handle(obj)
elif isinstance(obj, AccessPath):
return AccessPath(self._convert_access_handles(obj.accesses))
return obj
def __del__(self):
if self._used and not self._compiled_subprocess.is_crashed:
self._compiled_subprocess.delete_evaluator(self._evaluator_id)
class CompiledSubprocess(object):
is_crashed = False
# Start with 2, gets set after _get_info.
_pickle_protocol = 2
def __init__(self, executable):
self._executable = executable
self._evaluator_deletion_queue = queue.deque()
def __repr__(self):
pid = os.getpid()
return '<%s _executable=%r, _pickle_protocol=%r, is_crashed=%r, pid=%r>' % (
self.__class__.__name__,
self._executable,
self._pickle_protocol,
self.is_crashed,
pid,
)
@property
@memoize_method
def _process(self):
debug.dbg('Start environment subprocess %s', self._executable)
parso_path = sys.modules['parso'].__file__
args = (
self._executable,
_MAIN_PATH,
os.path.dirname(os.path.dirname(parso_path)),
'.'.join(str(x) for x in sys.version_info[:3]),
)
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
)
self._stderr_queue = Queue()
self._stderr_thread = t = Thread(
target=_enqueue_output,
args=(process.stderr, self._stderr_queue)
)
t.daemon = True
t.start()
return process
def run(self, evaluator, function, args=(), kwargs={}):
# Delete old evaluators.
while True:
try:
evaluator_id = self._evaluator_deletion_queue.pop()
except IndexError:
break
else:
self._send(evaluator_id, None)
assert callable(function)
return self._send(id(evaluator), function, args, kwargs)
def get_sys_path(self):
return self._send(None, functions.get_sys_path, (), {})
def _kill(self):
self.is_crashed = True
try:
self._process.kill()
self._process.wait()
except (AttributeError, TypeError):
# If the Python process is terminating, it will remove some modules
# earlier than others and in general it's unclear how to deal with
# that so we just ignore the exceptions here.
pass
def __del__(self):
if not self.is_crashed:
self._kill()
def _send(self, evaluator_id, function, args=(), kwargs={}):
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 = evaluator_id, function, args, kwargs
try:
pickle_dump(data, self._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
self._kill()
raise InternalError("The subprocess %s was killed. Maybe out of memory?"
% self._executable)
try:
is_exception, traceback, result = pickle_load(self._process.stdout)
except EOFError as eof_error:
try:
stderr = self._process.stderr.read().decode('utf-8', 'replace')
except Exception as exc:
stderr = '<empty/not available (%r)>' % exc
self._kill()
_add_stderr_to_debug(self._stderr_queue)
raise InternalError(
"The subprocess %s has crashed (%r, stderr=%s)." % (
self._executable,
eof_error,
stderr,
))
_add_stderr_to_debug(self._stderr_queue)
if is_exception:
# Replace the attribute error message with a the traceback. It's
# way more informative.
result.args = (traceback,)
raise result
return result
def delete_evaluator(self, evaluator_id):
"""
Currently we are not deleting evalutors instantly. They only get
deleted once the subprocess is used again. It would probably a better
solution to move all of this into a thread. However, the memory usage
of a single evaluator shouldn't be that high.
"""
# With an argument - the evaluator gets deleted.
self._evaluator_deletion_queue.append(evaluator_id)
class Listener(object):
def __init__(self, pickle_protocol):
self._evaluators = {}
# TODO refactor so we don't need to process anymore just handle
# controlling.
self._process = _EvaluatorProcess(Listener)
self._pickle_protocol = pickle_protocol
def _get_evaluator(self, function, evaluator_id):
from jedi.evaluate import Evaluator
try:
evaluator = self._evaluators[evaluator_id]
except KeyError:
from jedi.api.environment import InterpreterEnvironment
evaluator = Evaluator(
# The project is not actually needed. Nothing should need to
# access it.
project=None,
environment=InterpreterEnvironment()
)
self._evaluators[evaluator_id] = evaluator
return evaluator
def _run(self, evaluator_id, function, args, kwargs):
if evaluator_id is None:
return function(*args, **kwargs)
elif function is None:
del self._evaluators[evaluator_id]
else:
evaluator = self._get_evaluator(function, evaluator_id)
# Exchange all handles
args = list(args)
for i, arg in enumerate(args):
if isinstance(arg, AccessHandle):
args[i] = evaluator.compiled_subprocess.get_access_handle(arg.id)
for key, value in kwargs.items():
if isinstance(value, AccessHandle):
kwargs[key] = evaluator.compiled_subprocess.get_access_handle(value.id)
return function(evaluator, *args, **kwargs)
def listen(self):
stdout = sys.stdout
# Mute stdout. Nobody should actually be able to write to it,
# 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)
while True:
try:
payload = pickle_load(stdin)
except EOFError:
# It looks like the parent process closed.
# Don't make a big fuss here and just exit.
exit(0)
try:
result = False, None, self._run(*payload)
except Exception as e:
result = True, traceback.format_exc(), e
pickle_dump(result, stdout, self._pickle_protocol)
class AccessHandle(object):
def __init__(self, subprocess, access, id_):
self.access = access
self._subprocess = subprocess
self.id = id_
def add_subprocess(self, subprocess):
self._subprocess = subprocess
def __repr__(self):
try:
detail = self.access
except AttributeError:
detail = '#' + str(self.id)
return '<%s of %s>' % (self.__class__.__name__, detail)
def __getstate__(self):
return self.id
def __setstate__(self, state):
self.id = state
def __getattr__(self, name):
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))
def _workaround(self, name, *args, **kwargs):
"""
TODO Currently we're passing slice objects around. This should not
happen. They are also the only unhashable objects that we're passing
around.
"""
if args and isinstance(args[0], slice):
return self._subprocess.get_compiled_method_return(self.id, name, *args, **kwargs)
return self._cached_results(name, *args, **kwargs)
@memoize_method
def _cached_results(self, name, *args, **kwargs):
#if type(self._subprocess) == EvaluatorSubprocess:
#print(name, args, kwargs,
#self._subprocess.get_compiled_method_return(self.id, name, *args, **kwargs)
#)
return self._subprocess.get_compiled_method_return(self.id, name, *args, **kwargs)

View File

@@ -0,0 +1,55 @@
import os
import sys
def _get_paths():
# Get the path to jedi.
_d = os.path.dirname
_jedi_path = _d(_d(_d(_d(_d(__file__)))))
_parso_path = sys.argv[1]
# The paths are the directory that jedi and parso lie in.
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]
if sys.version_info > (3, 4):
from importlib.machinery import PathFinder
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.evaluate.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.evaluate.compiled import subprocess # NOQA
from jedi._compatibility import highest_pickle_protocol # noqa: E402
# 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()

View File

@@ -0,0 +1,113 @@
import sys
import os
from jedi._compatibility import find_module, cast_path, force_unicode, \
iter_modules, all_suffixes, print_to_stderr
from jedi.evaluate.compiled import access
from jedi import parser_utils
def get_sys_path():
return list(map(cast_path, sys.path))
def load_module(evaluator, **kwargs):
return access.load_module(evaluator, **kwargs)
def get_compiled_method_return(evaluator, id, attribute, *args, **kwargs):
handle = evaluator.compiled_subprocess.get_access_handle(id)
return getattr(handle.access, attribute)(*args, **kwargs)
def get_special_object(evaluator, identifier):
return access.get_special_object(evaluator, identifier)
def create_simple_object(evaluator, obj):
return access.create_access_path(evaluator, obj)
def get_module_info(evaluator, sys_path=None, full_name=None, **kwargs):
if sys_path is not None:
sys.path, temp = sys_path, sys.path
try:
module_file, module_path, is_pkg = find_module(full_name=full_name, **kwargs)
except ImportError:
return None, None, None
finally:
if sys_path is not None:
sys.path = temp
code = None
if is_pkg:
# In this case, we don't have a file yet. Search for the
# __init__ file.
if module_path.endswith(('.zip', '.egg')):
code = module_file.loader.get_source(full_name)
else:
module_path = _get_init_path(module_path)
elif module_file:
if module_path.endswith(('.zip', '.egg')):
# Unfortunately we are reading unicode here already, not byes.
# It seems however 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.
code = module_file.read()
else:
# Read the code with a binary file, because the binary file
# might not be proper unicode. This is handled by the parser
# wrapper.
with open(module_path, 'rb') as f:
code = f.read()
module_file.close()
return code, cast_path(module_path), is_pkg
def list_module_names(evaluator, search_path):
return [
force_unicode(name)
for module_loader, name, is_pkg in iter_modules(search_path)
]
def get_builtin_module_names(evaluator):
return list(map(force_unicode, sys.builtin_module_names))
def _test_raise_error(evaluator, exception_type):
"""
Raise an error to simulate certain problems for unit tests.
"""
raise exception_type
def _test_print(evaluator, stderr=None, stdout=None):
"""
Force some prints in the subprocesses. This exists for unit tests.
"""
if stderr is not None:
print_to_stderr(stderr)
sys.stderr.flush()
if stdout is not None:
print(stdout)
sys.stdout.flush()
def _get_init_path(directory_path):
"""
The __init__ file can be searched in a directory. If found return it, else
None.
"""
for suffix in all_suffixes():
path = os.path.join(directory_path, '__init__' + suffix)
if os.path.exists(path):
return path
return None
def safe_literal_eval(evaluator, value):
return parser_utils.safe_literal_eval(value)

View File

@@ -0,0 +1,38 @@
from jedi.evaluate.filters import publish_method, BuiltinOverwrite
from jedi.evaluate.base_context import ContextSet
class AsyncBase(BuiltinOverwrite):
def __init__(self, evaluator, func_execution_context):
super(AsyncBase, self).__init__(evaluator)
self.func_execution_context = func_execution_context
@property
def name(self):
return self.get_object().name
def __repr__(self):
return "<%s of %s>" % (type(self).__name__, self.func_execution_context)
class Coroutine(AsyncBase):
special_object_identifier = u'COROUTINE'
@publish_method('__await__')
def _await(self):
return ContextSet(CoroutineWrapper(self.evaluator, self.func_execution_context))
class CoroutineWrapper(AsyncBase):
special_object_identifier = u'COROUTINE_WRAPPER'
def py__stop_iteration_returns(self):
return self.func_execution_context.get_return_values()
class AsyncGenerator(AsyncBase):
"""Handling of `yield` functions."""
special_object_identifier = u'ASYNC_GENERATOR'
def py__aiter__(self):
return self.func_execution_context.get_yield_lazy_contexts(is_async=True)

View File

@@ -17,17 +17,20 @@ from jedi.evaluate.base_context import ContextualizedNode, NO_CONTEXTS, \
from jedi.evaluate.lazy_context import LazyKnownContexts, LazyKnownContext, \
LazyTreeContext
from jedi.evaluate.context import iterable
from jedi.evaluate.context import asynchronous
from jedi import parser_utils
from jedi.evaluate.parser_cache import get_yield_exprs
class LambdaName(AbstractNameDefinition):
string_name = '<lambda>'
api_type = u'function'
def __init__(self, lambda_context):
self._lambda_context = lambda_context
self.parent_context = lambda_context.parent_context
@property
def start_pos(self):
return self._lambda_context.tree_node.start_pos
@@ -35,16 +38,8 @@ class LambdaName(AbstractNameDefinition):
return ContextSet(self._lambda_context)
class FunctionContext(use_metaclass(CachedMetaClass, TreeContext)):
"""
Needed because of decorators. Decorators are evaluated here.
"""
api_type = 'function'
def __init__(self, evaluator, parent_context, funcdef):
""" This should not be called directly """
super(FunctionContext, self).__init__(evaluator, parent_context)
self.tree_node = funcdef
class AbstractFunction(TreeContext):
api_type = u'function'
def get_filters(self, search_global, until_position=None, origin_scope=None):
if search_global:
@@ -59,34 +54,10 @@ class FunctionContext(use_metaclass(CachedMetaClass, TreeContext)):
for filter in scope.get_filters(search_global=False, origin_scope=origin_scope):
yield filter
def infer_function_execution(self, function_execution):
"""
Created to be used by inheritance.
"""
yield_exprs = get_yield_exprs(self.evaluator, self.tree_node)
if yield_exprs:
return ContextSet(iterable.Generator(self.evaluator, function_execution))
else:
return function_execution.get_return_values()
def get_function_execution(self, arguments=None):
if arguments is None:
arguments = AnonymousArguments()
return FunctionExecutionContext(self.evaluator, self.parent_context, self, arguments)
def py__call__(self, arguments):
function_execution = self.get_function_execution(arguments)
return self.infer_function_execution(function_execution)
def py__class__(self):
# This differentiation is only necessary for Python2. Python3 does not
# use a different method class.
if isinstance(parser_utils.get_parent_scope(self.tree_node), tree.Class):
name = 'METHOD_CLASS'
else:
name = 'FUNCTION_CLASS'
return compiled.get_special_object(self.evaluator, name)
def get_param_names(self):
function_execution = self.get_function_execution()
return [ParamName(function_execution, param.name)
for param in self.tree_node.get_params()]
@property
def name(self):
@@ -94,10 +65,60 @@ class FunctionContext(use_metaclass(CachedMetaClass, TreeContext)):
return LambdaName(self)
return ContextName(self, self.tree_node.name)
def get_param_names(self):
function_execution = self.get_function_execution()
return [ParamName(function_execution, param.name)
for param in self.tree_node.get_params()]
def get_function_execution(self, arguments=None):
raise NotImplementedError
def py__call__(self, arguments):
function_execution = self.get_function_execution(arguments)
return self.infer_function_execution(function_execution)
def infer_function_execution(self, function_execution):
"""
Created to be used by inheritance.
"""
is_coroutine = self.tree_node.parent.type == 'async_stmt'
is_generator = bool(get_yield_exprs(self.evaluator, self.tree_node))
if is_coroutine:
if is_generator:
if self.evaluator.environment.version_info < (3, 6):
return NO_CONTEXTS
return ContextSet(asynchronous.AsyncGenerator(self.evaluator, function_execution))
else:
if self.evaluator.environment.version_info < (3, 5):
return NO_CONTEXTS
return ContextSet(asynchronous.Coroutine(self.evaluator, function_execution))
else:
if is_generator:
return ContextSet(iterable.Generator(self.evaluator, function_execution))
else:
return function_execution.get_return_values()
def py__name__(self):
return self.name.string_name
class FunctionContext(use_metaclass(CachedMetaClass, AbstractFunction)):
"""
Needed because of decorators. Decorators are evaluated here.
"""
@classmethod
def from_context(cls, context, tree_node):
from jedi.evaluate.context import AbstractInstanceContext
while context.is_class() or isinstance(context, AbstractInstanceContext):
context = context.parent_context
return cls(context.evaluator, parent_context=context, tree_node=tree_node)
def get_function_execution(self, arguments=None):
if arguments is None:
arguments = AnonymousArguments()
return FunctionExecutionContext(self.evaluator, self.parent_context, self, arguments)
def py__class__(self):
return compiled.get_special_object(self.evaluator, u'FUNCTION_CLASS')
class FunctionExecutionContext(TreeContext):
@@ -112,9 +133,12 @@ class FunctionExecutionContext(TreeContext):
function_execution_filter = FunctionExecutionFilter
def __init__(self, evaluator, parent_context, function_context, var_args):
super(FunctionExecutionContext, self).__init__(evaluator, parent_context)
super(FunctionExecutionContext, self).__init__(
evaluator,
parent_context,
function_context.tree_node,
)
self.function_context = function_context
self.tree_node = function_context.tree_node
self.var_args = var_args
@evaluator_method_cache(default=NO_CONTEXTS)
@@ -122,7 +146,7 @@ class FunctionExecutionContext(TreeContext):
def get_return_values(self, check_yields=False):
funcdef = self.tree_node
if funcdef.type == 'lambdef':
return self.evaluator.eval_element(self, funcdef.children[-1])
return self.eval_node(funcdef.children[-1])
if check_yields:
context_set = NO_CONTEXTS
@@ -140,13 +164,14 @@ class FunctionExecutionContext(TreeContext):
if check_yields:
context_set |= ContextSet.from_sets(
lazy_context.infer()
for lazy_context in self._eval_yield(r)
for lazy_context in self._get_yield_lazy_context(r)
)
else:
try:
children = r.children
except AttributeError:
context_set |= ContextSet(compiled.create(self.evaluator, None))
ctx = compiled.builtin_from_name(self.evaluator, u'None')
context_set |= ContextSet(ctx)
else:
context_set |= self.eval_node(children[1])
if check is flow_analysis.REACHABLE:
@@ -154,10 +179,11 @@ class FunctionExecutionContext(TreeContext):
break
return context_set
def _eval_yield(self, yield_expr):
def _get_yield_lazy_context(self, yield_expr):
if yield_expr.type == 'keyword':
# `yield` just yields None.
yield LazyKnownContext(compiled.create(self.evaluator, None))
ctx = compiled.builtin_from_name(self.evaluator, u'None')
yield LazyKnownContext(ctx)
return
node = yield_expr.children[1]
@@ -169,7 +195,8 @@ class FunctionExecutionContext(TreeContext):
yield LazyTreeContext(self, node)
@recursion.execution_recursion_decorator(default=iter([]))
def get_yield_values(self):
def get_yield_lazy_contexts(self, is_async=False):
# TODO: if is_async, wrap yield statements in Awaitable/async_generator_asend
for_parents = [(y, tree.search_ancestor(y, 'for_stmt', 'funcdef',
'while_stmt', 'if_stmt'))
for y in get_yield_exprs(self.evaluator, self.tree_node)]
@@ -202,7 +229,7 @@ class FunctionExecutionContext(TreeContext):
if for_stmt is None:
# No for_stmt, just normal yields.
for yield_ in yields:
for result in self._eval_yield(yield_):
for result in self._get_yield_lazy_context(yield_):
yield result
else:
input_node = for_stmt.get_testlist()
@@ -213,7 +240,7 @@ class FunctionExecutionContext(TreeContext):
dct = {str(for_stmt.children[1].value): lazy_context.infer()}
with helpers.predefine_names(self, for_stmt, dct):
for yield_in_same_for_stmt in yields:
for result in self._eval_yield(yield_in_same_for_stmt):
for result in self._get_yield_lazy_context(yield_in_same_for_stmt):
yield result
def get_filters(self, search_global, until_position=None, origin_scope=None):
@@ -222,5 +249,5 @@ class FunctionExecutionContext(TreeContext):
origin_scope=origin_scope)
@evaluator_method_cache()
def get_params(self):
return self.var_args.get_params(self)
def get_executed_params(self):
return self.var_args.get_executed_params(self)

View File

@@ -1,7 +1,7 @@
from abc import abstractproperty
from jedi._compatibility import is_py3
from jedi import debug
from jedi import settings
from jedi.evaluate import compiled
from jedi.evaluate import filters
from jedi.evaluate.base_context import Context, NO_CONTEXTS, ContextSet, \
@@ -9,38 +9,47 @@ from jedi.evaluate.base_context import Context, NO_CONTEXTS, ContextSet, \
from jedi.evaluate.lazy_context import LazyKnownContext, LazyKnownContexts
from jedi.evaluate.cache import evaluator_method_cache
from jedi.evaluate.arguments import AbstractArguments, AnonymousArguments
from jedi.cache import memoize_method
from jedi.evaluate.context.function import FunctionExecutionContext, FunctionContext
from jedi.evaluate.context.klass import ClassContext, apply_py__get__
from jedi.evaluate.context.function import FunctionExecutionContext, \
FunctionContext, AbstractFunction
from jedi.evaluate.context.klass import ClassContext, apply_py__get__, ClassFilter
from jedi.evaluate.context import iterable
from jedi.parser_utils import get_parent_scope
class InstanceExecutedParam(object):
def __init__(self, instance):
self._instance = instance
class InstanceFunctionExecution(FunctionExecutionContext):
def __init__(self, instance, parent_context, function_context, var_args):
self.instance = instance
var_args = InstanceVarArgs(self, var_args)
super(InstanceFunctionExecution, self).__init__(
instance.evaluator, parent_context, function_context, var_args)
def infer(self):
return ContextSet(self._instance)
class AnonymousInstanceFunctionExecution(FunctionExecutionContext):
function_execution_filter = filters.AnonymousInstanceFunctionExecutionFilter
class AnonymousInstanceArguments(AnonymousArguments):
def __init__(self, instance):
self._instance = instance
def __init__(self, instance, parent_context, function_context, var_args):
self.instance = instance
super(AnonymousInstanceFunctionExecution, self).__init__(
instance.evaluator, parent_context, function_context, var_args)
def get_executed_params(self, execution_context):
from jedi.evaluate.dynamic import search_params
self_param = InstanceExecutedParam(self._instance)
tree_params = execution_context.tree_node.get_params()
if len(tree_params) == 1:
# If the only param is self, we don't need to try to find
# executions of this function, we have all the params already.
return [self_param]
executed_params = list(search_params(
execution_context.evaluator,
execution_context,
execution_context.tree_node
))
executed_params[0] = self_param
return executed_params
class AbstractInstanceContext(Context):
"""
This class is used to evaluate instances.
"""
api_type = 'instance'
function_execution_cls = InstanceFunctionExecution
api_type = u'instance'
def __init__(self, evaluator, parent_context, class_context, var_args):
super(AbstractInstanceContext, self).__init__(evaluator, parent_context)
@@ -54,13 +63,13 @@ class AbstractInstanceContext(Context):
@property
def py__call__(self):
names = self.get_function_slot_names('__call__')
names = self.get_function_slot_names(u'__call__')
if not names:
# Means the Instance is not callable.
raise AttributeError
def execute(arguments):
return ContextSet.from_sets(name.execute(arguments) for name in names)
return ContextSet.from_sets(name.infer().execute(arguments) for name in names)
return execute
@@ -83,19 +92,19 @@ class AbstractInstanceContext(Context):
def execute_function_slots(self, names, *evaluated_args):
return ContextSet.from_sets(
name.execute_evaluated(*evaluated_args)
name.infer().execute_evaluated(*evaluated_args)
for name in names
)
def py__get__(self, obj):
# Arguments in __get__ descriptors are obj, class.
# `method` is the new parent of the array, don't know if that's good.
names = self.get_function_slot_names('__get__')
names = self.get_function_slot_names(u'__get__')
if names:
if isinstance(obj, AbstractInstanceContext):
return self.execute_function_slots(names, obj, obj.class_context)
else:
none_obj = compiled.create(self.evaluator, None)
none_obj = compiled.builtin_from_name(self.evaluator, u'None')
return self.execute_function_slots(names, none_obj, obj)
else:
return ContextSet(self)
@@ -104,14 +113,12 @@ class AbstractInstanceContext(Context):
origin_scope=None, include_self_names=True):
if include_self_names:
for cls in self.class_context.py__mro__():
if isinstance(cls, compiled.CompiledObject):
if cls.tree_node is not None:
# In this case we're talking about a fake object, it
# doesn't make sense for normal compiled objects to
# search for self variables.
yield SelfNameFilter(self.evaluator, self, cls, origin_scope)
else:
yield SelfNameFilter(self.evaluator, self, cls, origin_scope)
if not isinstance(cls, compiled.CompiledObject) \
or cls.tree_node is not None:
# In this case we're excluding compiled objects that are
# not fake objects. It doesn't make sense for normal
# compiled objects to search for self variables.
yield SelfAttributeFilter(self.evaluator, self, cls, origin_scope)
for cls in self.class_context.py__mro__():
if isinstance(cls, compiled.CompiledObject):
@@ -121,16 +128,16 @@ class AbstractInstanceContext(Context):
def py__getitem__(self, index):
try:
names = self.get_function_slot_names('__getitem__')
names = self.get_function_slot_names(u'__getitem__')
except KeyError:
debug.warning('No __getitem__, cannot access the array.')
return NO_CONTEXTS
else:
index_obj = compiled.create(self.evaluator, index)
index_obj = compiled.create_simple_object(self.evaluator, index)
return self.execute_function_slots(names, index_obj)
def py__iter__(self):
iter_slot_names = self.get_function_slot_names('__iter__')
iter_slot_names = self.get_function_slot_names(u'__iter__')
if not iter_slot_names:
debug.warning('No __iter__ on %s.' % self)
return
@@ -138,7 +145,10 @@ class AbstractInstanceContext(Context):
for generator in self.execute_function_slots(iter_slot_names):
if isinstance(generator, AbstractInstanceContext):
# `__next__` logic.
name = '__next__' if is_py3 else 'next'
if self.evaluator.environment.version_info.major == 2:
name = u'next'
else:
name = u'__next__'
iter_slot_names = generator.get_function_slot_names(name)
if iter_slot_names:
yield LazyKnownContexts(
@@ -154,21 +164,18 @@ class AbstractInstanceContext(Context):
def name(self):
pass
def _create_init_execution(self, class_context, func_node):
bound_method = BoundMethod(
self.evaluator, self, class_context, self.parent_context, func_node
)
return self.function_execution_cls(
self,
class_context.parent_context,
bound_method,
self.var_args
)
def _create_init_execution(self, class_context, bound_method):
return bound_method.get_function_execution(self.var_args)
def create_init_executions(self):
for name in self.get_function_slot_names('__init__'):
if isinstance(name, LazyInstanceName):
yield self._create_init_execution(name.class_context, name.tree_name.parent)
for name in self.get_function_slot_names(u'__init__'):
if isinstance(name, LazyInstanceClassName):
function = FunctionContext.from_context(
self.parent_context,
name.tree_name.parent
)
bound_method = BoundMethod(self, name.class_context, function)
yield self._create_init_execution(name.class_context, bound_method)
@evaluator_method_cache()
def create_instance_context(self, class_context, node):
@@ -180,16 +187,17 @@ class AbstractInstanceContext(Context):
else:
parent_context = self.create_instance_context(class_context, scope)
if scope.type == 'funcdef':
func = FunctionContext.from_context(
parent_context,
scope,
)
bound_method = BoundMethod(self, class_context, func)
if scope.name.value == '__init__' and parent_context == class_context:
return self._create_init_execution(class_context, scope)
return self._create_init_execution(class_context, bound_method)
else:
bound_method = BoundMethod(
self.evaluator, self, class_context,
parent_context, scope
)
return bound_method.get_function_execution()
elif scope.type == 'classdef':
class_context = ClassContext(self.evaluator, scope, parent_context)
class_context = ClassContext(self.evaluator, parent_context, scope)
return class_context
elif scope.type == 'comp_for':
# Comprehensions currently don't have a special scope in Jedi.
@@ -204,14 +212,18 @@ class AbstractInstanceContext(Context):
class CompiledInstance(AbstractInstanceContext):
def __init__(self, *args, **kwargs):
super(CompiledInstance, self).__init__(*args, **kwargs)
def __init__(self, evaluator, parent_context, class_context, var_args):
self._original_var_args = var_args
# I don't think that dynamic append lookups should happen here. That
# sounds more like something that should go to py__iter__.
if self.class_context.name.string_name in ['list', 'set'] \
and self.parent_context.get_root_context() == self.evaluator.BUILTINS:
if class_context.py__name__() in ['list', 'set'] \
and parent_context.get_root_context() == evaluator.builtins_module:
# compare the module path with the builtin name.
self.var_args = iterable.get_dynamic_array_instance(self)
if settings.dynamic_array_additions:
var_args = iterable.get_dynamic_array_instance(self, var_args)
super(CompiledInstance, self).__init__(evaluator, parent_context, class_context, var_args)
@property
def name(self):
@@ -223,6 +235,13 @@ class CompiledInstance(AbstractInstanceContext):
else:
return super(CompiledInstance, self).create_instance_context(class_context, node)
def get_first_non_keyword_argument_contexts(self):
key, lazy_context = next(self._original_var_args.unpack(), ('', None))
if key is not None:
return NO_CONTEXTS
return lazy_context.infer()
class TreeInstance(AbstractInstanceContext):
def __init__(self, evaluator, parent_context, class_context, var_args):
@@ -236,88 +255,105 @@ class TreeInstance(AbstractInstanceContext):
class AnonymousInstance(TreeInstance):
function_execution_cls = AnonymousInstanceFunctionExecution
def __init__(self, evaluator, parent_context, class_context):
super(AnonymousInstance, self).__init__(
evaluator,
parent_context,
class_context,
var_args=AnonymousArguments(),
var_args=AnonymousInstanceArguments(self),
)
class CompiledInstanceName(compiled.CompiledName):
def __init__(self, evaluator, instance, parent_context, name):
super(CompiledInstanceName, self).__init__(evaluator, parent_context, name)
def __init__(self, evaluator, instance, klass, name):
super(CompiledInstanceName, self).__init__(
evaluator,
klass.parent_context,
name.string_name
)
self._instance = instance
self._class = klass
self._class_member_name = name
@iterator_to_context_set
def infer(self):
for result_context in super(CompiledInstanceName, self).infer():
if isinstance(result_context, FunctionContext):
parent_context = result_context.parent_context
while parent_context.is_class():
parent_context = parent_context.parent_context
yield BoundMethod(
result_context.evaluator, self._instance, self.parent_context,
parent_context, result_context.tree_node
)
for result_context in self._class_member_name.infer():
is_function = result_context.api_type == 'function'
if result_context.tree_node is not None and is_function:
yield BoundMethod(self._instance, self._class, result_context)
else:
if result_context.api_type == 'function':
if is_function:
yield CompiledBoundMethod(result_context)
else:
yield result_context
class CompiledInstanceClassFilter(compiled.CompiledObjectFilter):
class CompiledInstanceClassFilter(filters.AbstractFilter):
name_class = CompiledInstanceName
def __init__(self, evaluator, instance, compiled_object):
super(CompiledInstanceClassFilter, self).__init__(
evaluator,
compiled_object,
is_instance=True,
def __init__(self, evaluator, instance, klass):
self._evaluator = evaluator
self._instance = instance
self._class = klass
self._class_filter = next(klass.get_filters(is_instance=True))
def get(self, name):
return self._convert(self._class_filter.get(name))
def values(self):
return self._convert(self._class_filter.values())
def _convert(self, names):
return [
CompiledInstanceName(self._evaluator, self._instance, self._class, n)
for n in names
]
class BoundMethod(AbstractFunction):
def __init__(self, instance, klass, function):
super(BoundMethod, self).__init__(
function.evaluator,
function.parent_context,
function.tree_node,
)
self._instance = instance
self._class = klass
self._function = function
def _create_name(self, name):
return self.name_class(
self._evaluator, self._instance, self._compiled_object, name)
class BoundMethod(FunctionContext):
def __init__(self, evaluator, instance, class_context, *args, **kwargs):
super(BoundMethod, self).__init__(evaluator, *args, **kwargs)
self._instance = instance
self._class_context = class_context
def py__class__(self):
return compiled.get_special_object(self.evaluator, u'BOUND_METHOD_CLASS')
def get_function_execution(self, arguments=None):
if arguments is None:
arguments = AnonymousArguments()
return AnonymousInstanceFunctionExecution(
self._instance, self.parent_context, self, arguments)
else:
return InstanceFunctionExecution(
self._instance, self.parent_context, self, arguments)
arguments = AnonymousInstanceArguments(self._instance)
arguments = InstanceArguments(self._instance, arguments)
if isinstance(self._function, compiled.CompiledObject):
# This is kind of weird, because it's coming from a compiled object
# and we're not sure if we want that in the future.
return FunctionExecutionContext(
self.evaluator, self.parent_context, self, arguments
)
return self._function.get_function_execution(arguments)
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self._function)
class CompiledBoundMethod(compiled.CompiledObject):
def __init__(self, func):
super(CompiledBoundMethod, self).__init__(
func.evaluator, func.obj, func.parent_context, func.tree_node)
func.evaluator, func.access_handle, func.parent_context, func.tree_node)
def get_param_names(self):
return list(super(CompiledBoundMethod, self).get_param_names())[1:]
class InstanceNameDefinition(filters.TreeNameDefinition):
def infer(self):
return super(InstanceNameDefinition, self).infer()
class LazyInstanceName(filters.TreeNameDefinition):
class SelfName(filters.TreeNameDefinition):
"""
This name calculates the parent_context lazily.
"""
@@ -331,62 +367,69 @@ class LazyInstanceName(filters.TreeNameDefinition):
return self._instance.create_instance_context(self.class_context, self.tree_name)
class LazyInstanceClassName(LazyInstanceName):
class LazyInstanceClassName(object):
def __init__(self, instance, class_context, class_member_name):
self._instance = instance
self.class_context = class_context
self._class_member_name = class_member_name
@iterator_to_context_set
def infer(self):
for result_context in super(LazyInstanceClassName, self).infer():
for result_context in self._class_member_name.infer():
if isinstance(result_context, FunctionContext):
# Classes are never used to resolve anything within the
# functions. Only other functions and modules will resolve
# those things.
parent_context = result_context.parent_context
while parent_context.is_class():
parent_context = parent_context.parent_context
yield BoundMethod(
result_context.evaluator, self._instance, self.class_context,
parent_context, result_context.tree_node
)
yield BoundMethod(self._instance, self.class_context, result_context)
else:
for c in apply_py__get__(result_context, self._instance):
yield c
def __getattr__(self, name):
return getattr(self._class_member_name, name)
class InstanceClassFilter(filters.ParserTreeFilter):
name_class = LazyInstanceClassName
class InstanceClassFilter(filters.AbstractFilter):
"""
This filter is special in that it uses the class filter and wraps the
resulting names in LazyINstanceClassName. The idea is that the class name
filtering can be very flexible and always be reflected in instances.
"""
def __init__(self, evaluator, context, class_context, origin_scope):
self._instance = context
self._class_context = class_context
self._class_filter = next(class_context.get_filters(
search_global=False,
origin_scope=origin_scope,
is_instance=True,
))
def get(self, name):
return self._convert(self._class_filter.get(name))
def values(self):
return self._convert(self._class_filter.values())
def _convert(self, names):
return [LazyInstanceClassName(self._instance, self._class_context, n) for n in names]
class SelfAttributeFilter(ClassFilter):
"""
This class basically filters all the use cases where `self.*` was assigned.
"""
name_class = SelfName
def __init__(self, evaluator, context, class_context, origin_scope):
super(InstanceClassFilter, self).__init__(
super(SelfAttributeFilter, self).__init__(
evaluator=evaluator,
context=context,
node_context=class_context,
origin_scope=origin_scope
origin_scope=origin_scope,
is_instance=True,
)
self._class_context = class_context
def _equals_origin_scope(self):
node = self._origin_scope
while node is not None:
if node == self._parser_scope or node == self.context:
return True
node = get_parent_scope(node)
return False
def _access_possible(self, name):
return not name.value.startswith('__') or name.value.endswith('__') \
or self._equals_origin_scope()
def _filter(self, names):
names = super(InstanceClassFilter, self)._filter(names)
return [name for name in names if self._access_possible(name)]
def _convert_names(self, names):
return [self.name_class(self.context, self._class_context, name) for name in names]
class SelfNameFilter(InstanceClassFilter):
name_class = LazyInstanceName
def _filter(self, names):
names = self._filter_self_names(names)
if isinstance(self._parser_scope, compiled.CompiledObject) and False:
@@ -405,19 +448,18 @@ class SelfNameFilter(InstanceClassFilter):
if name.is_definition() and self._access_possible(name):
yield name
def _convert_names(self, names):
return [self.name_class(self.context, self._class_context, name) for name in names]
def _check_flows(self, names):
return names
class InstanceVarArgs(AbstractArguments):
def __init__(self, execution_context, var_args):
self._execution_context = execution_context
class InstanceArguments(AbstractArguments):
def __init__(self, instance, var_args):
self.instance = instance
self._var_args = var_args
@memoize_method
def _get_var_args(self):
return self._var_args
@property
def argument_node(self):
return self._var_args.argument_node
@@ -427,9 +469,15 @@ class InstanceVarArgs(AbstractArguments):
return self._var_args.trailer
def unpack(self, func=None):
yield None, LazyKnownContext(self._execution_context.instance)
for values in self._get_var_args().unpack(func):
yield None, LazyKnownContext(self.instance)
for values in self._var_args.unpack(func):
yield values
def get_calling_nodes(self):
return self._get_var_args().get_calling_nodes()
return self._var_args.get_calling_nodes()
def get_executed_params(self, execution_context):
if isinstance(self._var_args, AnonymousInstanceArguments):
return self._var_args.get_executed_params(execution_context)
return super(InstanceArguments, self).get_executed_params(execution_context)

View File

@@ -22,74 +22,57 @@ It is important to note that:
"""
from jedi import debug
from jedi import settings
from jedi._compatibility import force_unicode, is_py3
from jedi.cache import memoize_method
from jedi.evaluate import compiled
from jedi.evaluate import analysis
from jedi.evaluate import recursion
from jedi.evaluate.lazy_context import LazyKnownContext, LazyKnownContexts, \
LazyTreeContext
from jedi.evaluate.helpers import is_string, predefine_names, evaluate_call_of_leaf
from jedi.evaluate.helpers import get_int_or_none, is_string, \
predefine_names, evaluate_call_of_leaf, reraise_as_evaluator, \
EvaluatorKeyError
from jedi.evaluate.utils import safe_property
from jedi.evaluate.utils import to_list
from jedi.evaluate.cache import evaluator_method_cache
from jedi.evaluate.filters import ParserTreeFilter, has_builtin_methods, \
register_builtin_method, SpecialMethodFilter
from jedi.evaluate.filters import ParserTreeFilter, BuiltinOverwrite, \
publish_method
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS, Context, \
TreeContext, ContextualizedNode
from jedi.parser_utils import get_comp_fors
class AbstractIterable(Context):
builtin_methods = {}
api_type = 'instance'
def __init__(self, evaluator):
super(AbstractIterable, self).__init__(evaluator, evaluator.BUILTINS)
def get_filters(self, search_global, until_position=None, origin_scope=None):
raise NotImplementedError
@property
def name(self):
return compiled.CompiledContextName(self, self.array_type)
class IterableMixin(object):
def py__stop_iteration_returns(self):
return ContextSet(compiled.builtin_from_name(self.evaluator, u'None'))
@has_builtin_methods
class GeneratorMixin(object):
class GeneratorBase(BuiltinOverwrite, IterableMixin):
array_type = None
special_object_identifier = u'GENERATOR_OBJECT'
@register_builtin_method('send')
@register_builtin_method('next', python_version_match=2)
@register_builtin_method('__next__', python_version_match=3)
@publish_method('send')
@publish_method('next', python_version_match=2)
@publish_method('__next__', python_version_match=3)
def py__next__(self):
# TODO add TypeError if params are given.
return ContextSet.from_sets(lazy_context.infer() for lazy_context in self.py__iter__())
def get_filters(self, search_global, until_position=None, origin_scope=None):
gen_obj = compiled.get_special_object(self.evaluator, 'GENERATOR_OBJECT')
yield SpecialMethodFilter(self, self.builtin_methods, gen_obj)
for filter in gen_obj.get_filters(search_global):
yield filter
def py__bool__(self):
return True
def py__class__(self):
gen_obj = compiled.get_special_object(self.evaluator, 'GENERATOR_OBJECT')
return gen_obj.py__class__()
@property
def name(self):
return compiled.CompiledContextName(self, 'generator')
class Generator(GeneratorMixin, Context):
class Generator(GeneratorBase):
"""Handling of `yield` functions."""
def __init__(self, evaluator, func_execution_context):
super(Generator, self).__init__(evaluator, parent_context=evaluator.BUILTINS)
super(Generator, self).__init__(evaluator)
self._func_execution_context = func_execution_context
def py__iter__(self):
return self._func_execution_context.get_yield_values()
return self._func_execution_context.get_yield_lazy_contexts()
def py__stop_iteration_returns(self):
return self._func_execution_context.get_return_values()
def __repr__(self):
return "<%s of %s>" % (type(self).__name__, self._func_execution_context)
@@ -100,10 +83,6 @@ class CompForContext(TreeContext):
def from_comp_for(cls, parent_context, comp_for):
return cls(parent_context.evaluator, parent_context, comp_for)
def __init__(self, evaluator, parent_context, comp_for):
super(CompForContext, self).__init__(evaluator, parent_context)
self.tree_node = comp_for
def get_node(self):
return self.tree_node
@@ -111,32 +90,33 @@ class CompForContext(TreeContext):
yield ParserTreeFilter(self.evaluator, self)
class Comprehension(AbstractIterable):
@staticmethod
def from_atom(evaluator, context, atom):
bracket = atom.children[0]
if bracket == '{':
if atom.children[1].children[1] == ':':
cls = DictComprehension
else:
cls = SetComprehension
elif bracket == '(':
cls = GeneratorComprehension
elif bracket == '[':
cls = ListComprehension
return cls(evaluator, context, atom)
def comprehension_from_atom(evaluator, context, atom):
bracket = atom.children[0]
if bracket == '{':
if atom.children[1].children[1] == ':':
cls = DictComprehension
else:
cls = SetComprehension
elif bracket == '(':
cls = GeneratorComprehension
elif bracket == '[':
cls = ListComprehension
return cls(evaluator, context, atom)
class ComprehensionMixin(object):
def __init__(self, evaluator, defining_context, atom):
super(Comprehension, self).__init__(evaluator)
super(ComprehensionMixin, self).__init__(evaluator)
self._defining_context = defining_context
self._atom = atom
def _get_comprehension(self):
"return 'a for a in b'"
# The atom contains a testlist_comp
return self._atom.children[1]
def _get_comp_for(self):
# The atom contains a testlist_comp
"return CompFor('for a in b')"
return self._get_comprehension().children[1]
def _eval_node(self, index=0):
@@ -154,13 +134,17 @@ class Comprehension(AbstractIterable):
def _nested(self, comp_fors, parent_context=None):
comp_for = comp_fors[0]
input_node = comp_for.children[3]
is_async = 'async' == comp_for.children[comp_for.children.index('for') - 1]
input_node = comp_for.children[comp_for.children.index('in') + 1]
parent_context = parent_context or self._defining_context
input_types = parent_context.eval_node(input_node)
# TODO: simulate await if self.is_async
cn = ContextualizedNode(parent_context, input_node)
iterated = input_types.iterate(cn)
exprlist = comp_for.children[1]
iterated = input_types.iterate(cn, is_async=is_async)
exprlist = comp_for.children[comp_for.children.index('for') + 1]
for i, lazy_context in enumerate(iterated):
types = lazy_context.infer()
dct = unpack_tuple_to_dict(parent_context, types, exprlist)
@@ -194,14 +178,18 @@ class Comprehension(AbstractIterable):
return "<%s of %s>" % (type(self).__name__, self._atom)
class ArrayMixin(object):
def get_filters(self, search_global, until_position=None, origin_scope=None):
# `array.type` is a string with the type, e.g. 'list'.
class Sequence(BuiltinOverwrite, IterableMixin):
api_type = u'instance'
@property
def name(self):
return compiled.CompiledContextName(self, self.array_type)
@memoize_method
def get_object(self):
compiled_obj = compiled.builtin_from_name(self.evaluator, self.array_type)
yield SpecialMethodFilter(self, self.builtin_methods, compiled_obj)
for typ in compiled_obj.execute_evaluated(self):
for filter in typ.get_filters():
yield filter
only_obj, = compiled_obj.execute_evaluated(self)
return only_obj
def py__bool__(self):
return None # We don't know the length, because of appends.
@@ -211,7 +199,7 @@ class ArrayMixin(object):
@safe_property
def parent(self):
return self.evaluator.BUILTINS
return self.evaluator.builtins_module
def dict_values(self):
return ContextSet.from_sets(
@@ -220,24 +208,25 @@ class ArrayMixin(object):
)
class ListComprehension(ArrayMixin, Comprehension):
array_type = 'list'
class ListComprehension(ComprehensionMixin, Sequence):
array_type = u'list'
def py__getitem__(self, index):
if isinstance(index, slice):
return ContextSet(self)
all_types = list(self.py__iter__())
return all_types[index].infer()
with reraise_as_evaluator(IndexError, TypeError):
lazy_context = all_types[index]
return lazy_context.infer()
class SetComprehension(ArrayMixin, Comprehension):
array_type = 'set'
class SetComprehension(ComprehensionMixin, Sequence):
array_type = u'set'
@has_builtin_methods
class DictComprehension(ArrayMixin, Comprehension):
array_type = 'dict'
class DictComprehension(ComprehensionMixin, Sequence):
array_type = u'dict'
def _get_comp_for(self):
return self._get_comprehension().children[3]
@@ -250,38 +239,48 @@ class DictComprehension(ArrayMixin, Comprehension):
for keys, values in self._iterate():
for k in keys:
if isinstance(k, compiled.CompiledObject):
if k.obj == index:
if k.get_safe_value(default=object()) == index:
return values
return self.dict_values()
def dict_values(self):
return ContextSet.from_sets(values for keys, values in self._iterate())
@register_builtin_method('values')
@publish_method('values')
def _imitate_values(self):
lazy_context = LazyKnownContexts(self.dict_values())
return ContextSet(FakeSequence(self.evaluator, 'list', [lazy_context]))
return ContextSet(FakeSequence(self.evaluator, u'list', [lazy_context]))
@register_builtin_method('items')
@publish_method('items')
def _imitate_items(self):
items = ContextSet.from_iterable(
FakeSequence(
self.evaluator, 'tuple'
(LazyKnownContexts(keys), LazyKnownContexts(values))
) for keys, values in self._iterate()
)
lazy_contexts = [
LazyKnownContext(
FakeSequence(
self.evaluator,
u'tuple',
[LazyKnownContexts(key),
LazyKnownContexts(value)]
)
)
for key, value in self._iterate()
]
return create_evaluated_sequence_set(self.evaluator, items, sequence_type='list')
return ContextSet(FakeSequence(self.evaluator, u'list', lazy_contexts))
def exact_key_items(self):
# NOTE: A smarter thing can probably done here to achieve better
# completions, but at least like this jedi doesn't crash
return []
class GeneratorComprehension(GeneratorMixin, Comprehension):
class GeneratorComprehension(ComprehensionMixin, GeneratorBase):
pass
class SequenceLiteralContext(ArrayMixin, AbstractIterable):
mapping = {'(': 'tuple',
'[': 'list',
'{': 'set'}
class SequenceLiteralContext(Sequence):
mapping = {'(': u'tuple',
'[': u'list',
'{': u'set'}
def __init__(self, evaluator, defining_context, atom):
super(SequenceLiteralContext, self).__init__(evaluator)
@@ -289,33 +288,36 @@ class SequenceLiteralContext(ArrayMixin, AbstractIterable):
self._defining_context = defining_context
if self.atom.type in ('testlist_star_expr', 'testlist'):
self.array_type = 'tuple'
self.array_type = u'tuple'
else:
self.array_type = SequenceLiteralContext.mapping[atom.children[0]]
"""The builtin name of the array (list, set, tuple or dict)."""
def py__getitem__(self, index):
"""Here the index is an int/str. Raises IndexError/KeyError."""
if self.array_type == 'dict':
if self.array_type == u'dict':
compiled_obj_index = compiled.create_simple_object(self.evaluator, index)
for key, value in self._items():
for k in self._defining_context.eval_node(key):
if isinstance(k, compiled.CompiledObject) \
and index == k.obj:
and k.execute_operation(compiled_obj_index, u'==').get_safe_value():
return self._defining_context.eval_node(value)
raise KeyError('No key found in dictionary %s.' % self)
raise EvaluatorKeyError('No key found in dictionary %s.' % self)
# Can raise an IndexError
if isinstance(index, slice):
return ContextSet(self)
else:
return self._defining_context.eval_node(self._items()[index])
with reraise_as_evaluator(TypeError, KeyError, IndexError):
node = self._items()[index]
return self._defining_context.eval_node(node)
def py__iter__(self):
"""
While values returns the possible values for any array field, this
function returns the value for a certain index.
"""
if self.array_type == 'dict':
if self.array_type == u'dict':
# Get keys.
types = ContextSet()
for k, _ in self._items():
@@ -333,7 +335,7 @@ class SequenceLiteralContext(ArrayMixin, AbstractIterable):
def _values(self):
"""Returns a list of a list of node."""
if self.array_type == 'dict':
if self.array_type == u'dict':
return ContextSet.from_sets(v for k, v in self._items())
else:
return self._items()
@@ -349,21 +351,39 @@ class SequenceLiteralContext(ArrayMixin, AbstractIterable):
return [] # Direct closing bracket, doesn't contain items.
if array_node.type == 'testlist_comp':
return array_node.children[::2]
# filter out (for now) pep 448 single-star unpacking
return [value for value in array_node.children[::2]
if value.type != "star_expr"]
elif array_node.type == 'dictorsetmaker':
kv = []
iterator = iter(array_node.children)
for key in iterator:
op = next(iterator, None)
if op is None or op == ',':
kv.append(key) # A set.
else:
assert op == ':' # A dict.
kv.append((key, next(iterator)))
if key == "**":
# dict with pep 448 double-star unpacking
# for now ignoring the values imported by **
next(iterator)
next(iterator, None) # Possible comma.
else:
op = next(iterator, None)
if op is None or op == ',':
if key.type == "star_expr":
# pep 448 single-star unpacking
# for now ignoring values imported by *
pass
else:
kv.append(key) # A set.
else:
assert op == ':' # A dict.
kv.append((key, next(iterator)))
next(iterator, None) # Possible comma.
return kv
else:
return [array_node]
if array_node.type == "star_expr":
# pep 448 single-star unpacking
# for now ignoring values imported by *
return []
else:
return [array_node]
def exact_key_items(self):
"""
@@ -373,37 +393,36 @@ class SequenceLiteralContext(ArrayMixin, AbstractIterable):
for key_node, value in self._items():
for key in self._defining_context.eval_node(key_node):
if is_string(key):
yield key.obj, LazyTreeContext(self._defining_context, value)
yield key.get_safe_value(), LazyTreeContext(self._defining_context, value)
def __repr__(self):
return "<%s of %s>" % (self.__class__.__name__, self.atom)
@has_builtin_methods
class DictLiteralContext(SequenceLiteralContext):
array_type = 'dict'
array_type = u'dict'
def __init__(self, evaluator, defining_context, atom):
super(SequenceLiteralContext, self).__init__(evaluator)
self._defining_context = defining_context
self.atom = atom
@register_builtin_method('values')
@publish_method('values')
def _imitate_values(self):
lazy_context = LazyKnownContexts(self.dict_values())
return ContextSet(FakeSequence(self.evaluator, 'list', [lazy_context]))
return ContextSet(FakeSequence(self.evaluator, u'list', [lazy_context]))
@register_builtin_method('items')
@publish_method('items')
def _imitate_items(self):
lazy_contexts = [
LazyKnownContext(FakeSequence(
self.evaluator, 'tuple',
self.evaluator, u'tuple',
(LazyTreeContext(self._defining_context, key_node),
LazyTreeContext(self._defining_context, value_node))
)) for key_node, value_node in self._items()
]
return ContextSet(FakeSequence(self.evaluator, 'list', lazy_contexts))
return ContextSet(FakeSequence(self.evaluator, u'list', lazy_contexts))
class _FakeArray(SequenceLiteralContext):
@@ -423,7 +442,9 @@ class FakeSequence(_FakeArray):
self._lazy_context_list = lazy_context_list
def py__getitem__(self, index):
return self._lazy_context_list[index].infer()
with reraise_as_evaluator(IndexError, TypeError):
lazy_context = self._lazy_context_list[index]
return lazy_context.infer()
def py__iter__(self):
return self._lazy_context_list
@@ -437,15 +458,39 @@ class FakeSequence(_FakeArray):
class FakeDict(_FakeArray):
def __init__(self, evaluator, dct):
super(FakeDict, self).__init__(evaluator, dct, 'dict')
super(FakeDict, self).__init__(evaluator, dct, u'dict')
self._dct = dct
def py__iter__(self):
for key in self._dct:
yield LazyKnownContext(compiled.create(self.evaluator, key))
yield LazyKnownContext(compiled.create_simple_object(self.evaluator, key))
def py__getitem__(self, index):
return self._dct[index].infer()
if is_py3 and self.evaluator.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_as_evaluator(KeyError):
lazy_context = self._dct[index]
return lazy_context.infer()
@publish_method('values')
def _values(self):
return ContextSet(FakeSequence(
self.evaluator, u'tuple',
[LazyKnownContexts(self.dict_values())]
))
def dict_values(self):
return ContextSet.from_sets(lazy_context.infer() for lazy_context in self._dct.values())
@@ -608,12 +653,9 @@ def _check_array_additions(context, sequence):
return added_types
def get_dynamic_array_instance(instance):
def get_dynamic_array_instance(instance, arguments):
"""Used for set() and list() instances."""
if not settings.dynamic_array_additions:
return instance.var_args
ai = _ArrayInstance(instance)
ai = _ArrayInstance(instance, arguments)
from jedi.evaluate import arguments
return arguments.ValuesArguments([ContextSet(ai)])
@@ -629,9 +671,9 @@ class _ArrayInstance(object):
and therefore doesn't need filters, `py__bool__` and so on, because
we don't use these operations in `builtins.py`.
"""
def __init__(self, instance):
def __init__(self, instance, var_args):
self.instance = instance
self.var_args = instance.var_args
self.var_args = var_args
def py__iter__(self):
var_args = self.var_args
@@ -649,7 +691,7 @@ class _ArrayInstance(object):
for addition in additions:
yield addition
def iterate(self, contextualized_node=None):
def iterate(self, contextualized_node=None, is_async=False):
return self.py__iter__()
@@ -657,7 +699,7 @@ class Slice(Context):
def __init__(self, context, start, stop, step):
super(Slice, self).__init__(
context.evaluator,
parent_context=context.evaluator.BUILTINS
parent_context=context.evaluator.builtins_module
)
self._context = context
# all of them are either a Precedence or None.
@@ -680,10 +722,9 @@ class Slice(Context):
# For simplicity, we want slices to be clear defined with just
# one type. Otherwise we will return an empty slice object.
raise IndexError
try:
return list(result)[0].obj
except AttributeError:
return None
context, = result
return get_int_or_none(context)
try:
return slice(get(self._start), get(self._stop), get(self._step))

View File

@@ -38,11 +38,12 @@ py__doc__(include_call_signature: Returns the docstring for a context.
"""
from jedi._compatibility import use_metaclass
from jedi.parser_utils import get_parent_scope
from jedi.evaluate.cache import evaluator_method_cache, CachedMetaClass
from jedi.evaluate import compiled
from jedi.evaluate.lazy_context import LazyKnownContext
from jedi.evaluate.filters import ParserTreeFilter, TreeNameDefinition, \
ContextName, AnonymousInstanceParamName
ContextName
from jedi.evaluate.base_context import ContextSet, iterator_to_context_set, \
TreeContext
@@ -58,9 +59,10 @@ def apply_py__get__(context, base_context):
class ClassName(TreeNameDefinition):
def __init__(self, parent_context, tree_name, name_context):
def __init__(self, parent_context, tree_name, name_context, apply_decorators):
super(ClassName, self).__init__(parent_context, tree_name)
self._name_context = name_context
self._apply_decorators = apply_decorators
@iterator_to_context_set
def infer(self):
@@ -72,16 +74,45 @@ class ClassName(TreeNameDefinition):
self.parent_context.evaluator, self._name_context, self.tree_name)
for result_context in inferred:
for c in apply_py__get__(result_context, self.parent_context):
yield c
if self._apply_decorators:
for c in apply_py__get__(result_context, self.parent_context):
yield c
else:
yield result_context
class ClassFilter(ParserTreeFilter):
name_class = ClassName
def __init__(self, *args, **kwargs):
self._is_instance = kwargs.pop('is_instance') # Python 2 :/
super(ClassFilter, self).__init__(*args, **kwargs)
def _convert_names(self, names):
return [self.name_class(self.context, name, self._node_context)
for name in names]
return [
self.name_class(
parent_context=self.context,
tree_name=name,
name_context=self._node_context,
apply_decorators=not self._is_instance,
) for name in names
]
def _equals_origin_scope(self):
node = self._origin_scope
while node is not None:
if node == self._parser_scope or node == self.context:
return True
node = get_parent_scope(node)
return False
def _access_possible(self, name):
return not name.value.startswith('__') or name.value.endswith('__') \
or self._equals_origin_scope()
def _filter(self, names):
names = super(ClassFilter, self)._filter(names)
return [name for name in names if self._access_possible(name)]
class ClassContext(use_metaclass(CachedMetaClass, TreeContext)):
@@ -89,11 +120,7 @@ class ClassContext(use_metaclass(CachedMetaClass, TreeContext)):
This class is not only important to extend `tree.Class`, it is also a
important for descriptors (if the descriptor methods are evaluated or not).
"""
api_type = 'class'
def __init__(self, evaluator, parent_context, classdef):
super(ClassContext, self).__init__(evaluator, parent_context=parent_context)
self.tree_node = classdef
api_type = u'class'
@evaluator_method_cache(default=())
def py__mro__(self):
@@ -136,22 +163,17 @@ class ClassContext(use_metaclass(CachedMetaClass, TreeContext)):
arglist = self.tree_node.get_super_arglist()
if arglist:
from jedi.evaluate import arguments
args = arguments.TreeArguments(self.evaluator, self, arglist)
args = arguments.TreeArguments(self.evaluator, self.parent_context, arglist)
return [value for key, value in args.unpack() if key is None]
else:
return [LazyKnownContext(compiled.create(self.evaluator, object))]
return [LazyKnownContext(compiled.builtin_from_name(self.evaluator, u'object'))]
def py__call__(self, params):
from jedi.evaluate.context import TreeInstance
return ContextSet(TreeInstance(self.evaluator, self.parent_context, self, params))
def py__class__(self):
return compiled.create(self.evaluator, type)
def get_params(self):
from jedi.evaluate.context import AnonymousInstance
anon = AnonymousInstance(self.evaluator, self.parent_context, self)
return [AnonymousInstanceParamName(anon, param.name) for param in self.funcdef.get_params()]
return compiled.builtin_from_name(self.evaluator, u'type')
def get_filters(self, search_global, until_position=None, origin_scope=None, is_instance=False):
if search_global:
@@ -169,7 +191,9 @@ class ClassContext(use_metaclass(CachedMetaClass, TreeContext)):
else:
yield ClassFilter(
self.evaluator, self, node_context=cls,
origin_scope=origin_scope)
origin_scope=origin_scope,
is_instance=is_instance
)
def is_class(self):
return True
@@ -182,7 +206,7 @@ class ClassContext(use_metaclass(CachedMetaClass, TreeContext)):
return []
def get_param_names(self):
for name in self.get_function_slot_names('__init__'):
for name in self.get_function_slot_names(u'__init__'):
for context_ in name.infer():
try:
method = context_.get_param_names

View File

@@ -1,14 +1,12 @@
import pkgutil
import imp
import re
import os
from parso import python_bytes_to_unicode
from jedi._compatibility import use_metaclass
from jedi.evaluate.cache import CachedMetaClass, evaluator_method_cache
from jedi.evaluate.cache import evaluator_method_cache
from jedi._compatibility import iter_modules, all_suffixes
from jedi.evaluate.filters import GlobalNameFilter, ContextNameMixin, \
AbstractNameDefinition, ParserTreeFilter, DictFilter
AbstractNameDefinition, ParserTreeFilter, DictFilter, MergedFilter
from jedi.evaluate import compiled
from jedi.evaluate.base_context import TreeContext
from jedi.evaluate.imports import SubModuleName, infer_import
@@ -18,14 +16,14 @@ class _ModuleAttributeName(AbstractNameDefinition):
"""
For module attributes like __file__, __str__ and so on.
"""
api_type = 'instance'
api_type = u'instance'
def __init__(self, parent_module, string_name):
self.parent_context = parent_module
self.string_name = string_name
def infer(self):
return compiled.create(self.parent_context.evaluator, str).execute_evaluated()
return compiled.get_string_context_set(self.parent_context.evaluator)
class ModuleName(ContextNameMixin, AbstractNameDefinition):
@@ -40,23 +38,29 @@ class ModuleName(ContextNameMixin, AbstractNameDefinition):
return self._name
class ModuleContext(use_metaclass(CachedMetaClass, TreeContext)):
api_type = 'module'
class ModuleContext(TreeContext):
api_type = u'module'
parent_context = None
def __init__(self, evaluator, module_node, path):
super(ModuleContext, self).__init__(evaluator, parent_context=None)
self.tree_node = module_node
def __init__(self, evaluator, module_node, path, code_lines):
super(ModuleContext, self).__init__(
evaluator,
parent_context=None,
tree_node=module_node
)
self._path = path
self.code_lines = code_lines
def get_filters(self, search_global, until_position=None, origin_scope=None):
yield ParserTreeFilter(
self.evaluator,
context=self,
until_position=until_position,
origin_scope=origin_scope
yield MergedFilter(
ParserTreeFilter(
self.evaluator,
context=self,
until_position=until_position,
origin_scope=origin_scope
),
GlobalNameFilter(self, self.tree_node),
)
yield GlobalNameFilter(self, self.tree_node)
yield DictFilter(self._sub_modules_dict())
yield DictFilter(self._module_attributes_dict())
for star_module in self.star_imports():
@@ -64,7 +68,7 @@ class ModuleContext(use_metaclass(CachedMetaClass, TreeContext)):
# I'm not sure if the star import cache is really that effective anymore
# with all the other really fast import caches. Recheck. Also we would need
# to push the star imports into Evaluator.modules, if we reenable this.
# to push the star imports into Evaluator.module_cache, if we reenable this.
@evaluator_method_cache([])
def star_imports(self):
modules = []
@@ -93,7 +97,7 @@ class ModuleContext(use_metaclass(CachedMetaClass, TreeContext)):
sep = (re.escape(os.path.sep),) * 2
r = re.search(r'([^%s]*?)(%s__init__)?(\.py|\.so)?$' % sep, self._path)
# Remove PEP 3149 names
return re.sub('\.[a-z]+-\d{2}[mud]{0,3}$', '', r.group(1))
return re.sub(r'\.[a-z]+-\d{2}[mud]{0,3}$', '', r.group(1))
@property
@evaluator_method_cache()
@@ -105,7 +109,7 @@ class ModuleContext(use_metaclass(CachedMetaClass, TreeContext)):
:return: The path to the directory of a package. None in case it's not
a package.
"""
for suffix, _, _ in imp.get_suffixes():
for suffix in all_suffixes():
ending = '__init__' + suffix
py__file__ = self.py__file__()
if py__file__ is not None and py__file__.endswith(ending):
@@ -114,7 +118,7 @@ class ModuleContext(use_metaclass(CachedMetaClass, TreeContext)):
return None
def py__name__(self):
for name, module in self.evaluator.modules.items():
for name, module in self.evaluator.module_cache.iterate_modules_with_names():
if module == self and name != '':
return name
@@ -131,12 +135,12 @@ class ModuleContext(use_metaclass(CachedMetaClass, TreeContext)):
def py__package__(self):
if self._get_init_directory() is None:
return re.sub(r'\.?[^\.]+$', '', self.py__name__())
return re.sub(r'\.?[^.]+$', '', self.py__name__())
else:
return self.py__name__()
def _py__path__(self):
search_path = self.evaluator.project.sys_path
search_path = self.evaluator.get_sys_path()
init_path = self.py__file__()
if os.path.basename(init_path) == '__init__.py':
with open(init_path, 'rb') as f:
@@ -185,13 +189,17 @@ class ModuleContext(use_metaclass(CachedMetaClass, TreeContext)):
Lists modules in the directory of this module (if this module is a
package).
"""
path = self._path
names = {}
if path is not None and path.endswith(os.path.sep + '__init__.py'):
mods = pkgutil.iter_modules([os.path.dirname(path)])
for module_loader, name, is_pkg in mods:
# It's obviously a relative import to the current module.
names[name] = SubModuleName(self, name)
try:
method = self.py__path__
except AttributeError:
pass
else:
for path in method():
mods = iter_modules([path])
for module_loader, name, is_pkg in mods:
# It's obviously a relative import to the current module.
names[name] = SubModuleName(self, name)
# TODO add something like this in the future, its cleaner than the
# import hacks.
@@ -203,11 +211,9 @@ class ModuleContext(use_metaclass(CachedMetaClass, TreeContext)):
return names
def py__class__(self):
return compiled.get_special_object(self.evaluator, 'MODULE_CLASS')
return compiled.get_special_object(self.evaluator, u'MODULE_CLASS')
def __repr__(self):
return "<%s: %s@%s-%s>" % (
self.__class__.__name__, self._string_name,
self.tree_node.start_pos[0], self.tree_node.end_pos[0])

View File

@@ -1,40 +1,37 @@
import os
from itertools import chain
from jedi._compatibility import use_metaclass
from jedi.evaluate.cache import evaluator_method_cache, CachedMetaClass
from jedi.evaluate.cache import evaluator_method_cache
from jedi.evaluate import imports
from jedi.evaluate.filters import DictFilter, AbstractNameDefinition
from jedi.evaluate.base_context import NO_CONTEXTS, TreeContext
from jedi.evaluate.filters import DictFilter, AbstractNameDefinition, ContextNameMixin
from jedi.evaluate.base_context import Context
class ImplicitNSName(AbstractNameDefinition):
class ImplicitNSName(ContextNameMixin, AbstractNameDefinition):
"""
Accessing names for implicit namespace packages should infer to nothing.
This object will prevent Jedi from raising exceptions
"""
def __init__(self, implicit_ns_context, string_name):
self.implicit_ns_context = implicit_ns_context
self._context = implicit_ns_context
self.string_name = string_name
def infer(self):
return NO_CONTEXTS
def get_root_context(self):
return self.implicit_ns_context
class ImplicitNamespaceContext(use_metaclass(CachedMetaClass, TreeContext)):
class ImplicitNamespaceContext(Context):
"""
Provides support for implicit namespace packages
"""
api_type = 'module'
# Is a module like every other module, because if you import an empty
# folder foobar it will be available as an object:
# <module 'foobar' (namespace)>.
api_type = u'module'
parent_context = None
def __init__(self, evaluator, fullname):
def __init__(self, evaluator, fullname, paths):
super(ImplicitNamespaceContext, self).__init__(evaluator, parent_context=None)
self.evaluator = evaluator
self.fullname = fullname
self._fullname = fullname
self.paths = paths
def get_filters(self, search_global, until_position=None, origin_scope=None):
yield DictFilter(self._sub_modules_dict())
@@ -51,18 +48,19 @@ class ImplicitNamespaceContext(use_metaclass(CachedMetaClass, TreeContext)):
def py__package__(self):
"""Return the fullname
"""
return self.fullname
return self._fullname
@property
def py__path__(self):
return lambda: [self.paths]
return [self.paths]
def py__name__(self):
return self._fullname
@evaluator_method_cache()
def _sub_modules_dict(self):
names = {}
paths = self.paths
file_names = chain.from_iterable(os.listdir(path) for path in paths)
file_names = chain.from_iterable(os.listdir(path) for path in self.paths)
mods = [
file_name.rpartition('.')[0] if '.' in file_name else file_name
for file_name in file_names

View File

@@ -18,7 +18,7 @@ annotations.
import re
from textwrap import dedent
from parso import parse
from parso import parse, ParserSyntaxError
from jedi._compatibility import u
from jedi.evaluate.utils import indent_block
@@ -42,49 +42,60 @@ DOCSTRING_RETURN_PATTERNS = [
REST_ROLE_PATTERN = re.compile(r':[^`]+:`([^`]+)`')
try:
from numpydoc.docscrape import NumpyDocString
except ImportError:
def _search_param_in_numpydocstr(docstr, param_str):
return []
_numpy_doc_string_cache = None
def _search_return_in_numpydocstr(docstr):
return []
else:
def _search_param_in_numpydocstr(docstr, param_str):
"""Search `docstr` (in numpydoc format) for type(-s) of `param_str`."""
try:
# This is a non-public API. If it ever changes we should be
# prepared and return gracefully.
params = NumpyDocString(docstr)._parsed_data['Parameters']
except (KeyError, AttributeError):
return []
for p_name, p_type, p_descr in params:
if p_name == param_str:
m = re.match('([^,]+(,[^,]+)*?)(,[ ]*optional)?$', p_type)
if m:
p_type = m.group(1)
return list(_expand_typestr(p_type))
return []
def _search_return_in_numpydocstr(docstr):
"""
Search `docstr` (in numpydoc format) for type(-s) of function returns.
"""
doc = NumpyDocString(docstr)
try:
# This is a non-public API. If it ever changes we should be
# prepared and return gracefully.
returns = doc._parsed_data['Returns']
returns += doc._parsed_data['Yields']
except (KeyError, AttributeError):
raise StopIteration
for r_name, r_type, r_descr in returns:
#Return names are optional and if so the type is in the name
if not r_type:
r_type = r_name
for type_ in _expand_typestr(r_type):
yield type_
def _get_numpy_doc_string_cls():
global _numpy_doc_string_cache
if isinstance(_numpy_doc_string_cache, ImportError):
raise _numpy_doc_string_cache
try:
from numpydoc.docscrape import NumpyDocString
_numpy_doc_string_cache = NumpyDocString
except ImportError as e:
_numpy_doc_string_cache = e
raise
return _numpy_doc_string_cache
def _search_param_in_numpydocstr(docstr, param_str):
"""Search `docstr` (in numpydoc format) for type(-s) of `param_str`."""
try:
# This is a non-public API. If it ever changes we should be
# prepared and return gracefully.
params = _get_numpy_doc_string_cls()(docstr)._parsed_data['Parameters']
except (KeyError, AttributeError, ImportError):
return []
for p_name, p_type, p_descr in params:
if p_name == param_str:
m = re.match(r'([^,]+(,[^,]+)*?)(,[ ]*optional)?$', p_type)
if m:
p_type = m.group(1)
return list(_expand_typestr(p_type))
return []
def _search_return_in_numpydocstr(docstr):
"""
Search `docstr` (in numpydoc format) for type(-s) of function returns.
"""
try:
doc = _get_numpy_doc_string_cls()(docstr)
except ImportError:
return
try:
# This is a non-public API. If it ever changes we should be
# prepared and return gracefully.
returns = doc._parsed_data['Returns']
returns += doc._parsed_data['Yields']
except (KeyError, AttributeError):
return
for r_name, r_type, r_descr in returns:
# Return names are optional and if so the type is in the name
if not r_type:
r_type = r_name
for type_ in _expand_typestr(r_type):
yield type_
def _expand_typestr(type_str):
@@ -92,11 +103,11 @@ def _expand_typestr(type_str):
Attempts to interpret the possible types in `type_str`
"""
# Check if alternative types are specified with 'or'
if re.search('\\bor\\b', type_str):
if re.search(r'\bor\b', type_str):
for t in type_str.split('or'):
yield t.split('of')[0].strip()
# Check if like "list of `type`" and set type to list
elif re.search('\\bof\\b', type_str):
elif re.search(r'\bof\b', type_str):
yield type_str.split('of')[0]
# Check if type has is a set of valid literal values eg: {'C', 'F', 'A'}
elif type_str.startswith('{'):
@@ -145,8 +156,7 @@ def _search_param_in_docstr(docstr, param_str):
if match:
return [_strip_rst_role(match.group(1))]
return (_search_param_in_numpydocstr(docstr, param_str) or
[])
return _search_param_in_numpydocstr(docstr, param_str)
def _strip_rst_role(type_str):
@@ -179,12 +189,12 @@ def _evaluate_for_statement_string(module_context, string):
Need this docstring so that if the below part is not valid Python this
is still a function.
'''
{0}
{}
"""))
if string is None:
return []
for element in re.findall('((?:\w+\.)*\w+)\.', string):
for element in re.findall(r'((?:\w+\.)*\w+)\.', string):
# Try to import module part in dotted name.
# (e.g., 'threading' in 'threading.Thread').
string = 'import %s\n' % element + string
@@ -193,7 +203,10 @@ def _evaluate_for_statement_string(module_context, string):
# will be impossible to use `...` (Ellipsis) as a token. Docstring types
# don't need to conform with the current grammar.
grammar = module_context.evaluator.latest_grammar
module = grammar.parse(code.format(indent_block(string)))
try:
module = grammar.parse(code.format(indent_block(string)), error_recovery=False)
except ParserSyntaxError:
return []
try:
funcdef = next(module.iter_funcdefs())
# First pick suite, then simple_stmt and then the node,
@@ -202,6 +215,9 @@ def _evaluate_for_statement_string(module_context, string):
except (AttributeError, IndexError):
return []
if stmt.type not in ('name', 'atom', 'atom_expr'):
return []
from jedi.evaluate.context import FunctionContext
function_context = FunctionContext(
module_context.evaluator,
@@ -243,14 +259,15 @@ def _execute_array_values(evaluator, array):
for typ in lazy_context.infer()
)
values.append(LazyKnownContexts(objects))
return set([FakeSequence(evaluator, array.array_type, values)])
return {FakeSequence(evaluator, array.array_type, values)}
else:
return array.execute_evaluated()
@evaluator_method_cache()
def infer_param(execution_context, param):
from jedi.evaluate.context.instance import AnonymousInstanceFunctionExecution
from jedi.evaluate.context.instance import InstanceArguments
from jedi.evaluate.context import FunctionExecutionContext
def eval_docstring(docstring):
return ContextSet.from_iterable(
@@ -264,9 +281,10 @@ def infer_param(execution_context, param):
return NO_CONTEXTS
types = eval_docstring(execution_context.py__doc__())
if isinstance(execution_context, AnonymousInstanceFunctionExecution) and \
execution_context.function_context.name.string_name == '__init__':
class_context = execution_context.instance.class_context
if isinstance(execution_context, FunctionExecutionContext) \
and isinstance(execution_context.var_args, InstanceArguments) \
and execution_context.function_context.py__name__() == '__init__':
class_context = execution_context.var_args.instance.class_context
types |= eval_docstring(class_context.py__doc__())
return types

View File

@@ -28,22 +28,30 @@ from jedi.evaluate.helpers import is_stdlib_path
from jedi.evaluate.utils import to_list
from jedi.parser_utils import get_parent_scope
from jedi.evaluate.context import ModuleContext, instance
from jedi.evaluate.base_context import ContextSet
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS
from jedi.evaluate import recursion
MAX_PARAM_SEARCHES = 20
class MergedExecutedParams(object):
class DynamicExecutedParams(object):
"""
Simulates being a parameter while actually just being multiple params.
"""
def __init__(self, executed_params):
def __init__(self, evaluator, executed_params):
self.evaluator = evaluator
self._executed_params = executed_params
def infer(self):
return ContextSet.from_sets(p.infer() for p in self._executed_params)
with recursion.execution_allowed(self.evaluator, self) as allowed:
# We need to catch recursions that may occur, because an
# anonymous functions can create an anonymous parameter that is
# more or less self referencing.
if allowed:
return ContextSet.from_sets(p.infer() for p in self._executed_params)
return NO_CONTEXTS
@debug.increase_indent
@@ -73,24 +81,33 @@ def search_params(evaluator, execution_context, funcdef):
# you will see the slowdown, especially in 3.6.
return create_default_params(execution_context, funcdef)
debug.dbg('Dynamic param search in %s.', funcdef.name.value, color='MAGENTA')
module_context = execution_context.get_root_context()
function_executions = _search_function_executions(
evaluator,
module_context,
funcdef
)
if function_executions:
zipped_params = zip(*list(
function_execution.get_params()
for function_execution in function_executions
))
params = [MergedExecutedParams(executed_params) for executed_params in zipped_params]
# Evaluate the ExecutedParams to types.
if funcdef.type == 'lambdef':
string_name = _get_lambda_name(funcdef)
if string_name is None:
return create_default_params(execution_context, funcdef)
else:
return create_default_params(execution_context, funcdef)
debug.dbg('Dynamic param result finished', color='MAGENTA')
string_name = funcdef.name.value
debug.dbg('Dynamic param search in %s.', string_name, color='MAGENTA')
try:
module_context = execution_context.get_root_context()
function_executions = _search_function_executions(
evaluator,
module_context,
funcdef,
string_name=string_name,
)
if function_executions:
zipped_params = zip(*list(
function_execution.get_executed_params()
for function_execution in function_executions
))
params = [DynamicExecutedParams(evaluator, executed_params) for executed_params in zipped_params]
# Evaluate the ExecutedParams to types.
else:
return create_default_params(execution_context, funcdef)
finally:
debug.dbg('Dynamic param result finished', color='MAGENTA')
return params
finally:
evaluator.dynamic_params_depth -= 1
@@ -98,25 +115,24 @@ def search_params(evaluator, execution_context, funcdef):
@evaluator_function_cache(default=None)
@to_list
def _search_function_executions(evaluator, module_context, funcdef):
def _search_function_executions(evaluator, module_context, funcdef, string_name):
"""
Returns a list of param names.
"""
func_string_name = funcdef.name.value
compare_node = funcdef
if func_string_name == '__init__':
if string_name == '__init__':
cls = get_parent_scope(funcdef)
if isinstance(cls, tree.Class):
func_string_name = cls.name.value
string_name = cls.name.value
compare_node = cls
found_executions = False
i = 0
for for_mod_context in imports.get_modules_containing_name(
evaluator, [module_context], func_string_name):
evaluator, [module_context], string_name):
if not isinstance(module_context, ModuleContext):
return
for name, trailer in _get_possible_nodes(for_mod_context, func_string_name):
for name, trailer in _get_possible_nodes(for_mod_context, string_name):
i += 1
# This is a simple way to stop Jedi's dynamic param recursion
@@ -137,6 +153,18 @@ def _search_function_executions(evaluator, module_context, funcdef):
return
def _get_lambda_name(node):
stmt = node.parent
if stmt.type == 'expr_stmt':
first_operator = next(stmt.yield_operators(), None)
if first_operator == '=':
first = stmt.children[0]
if first.type == 'name':
return first.value
return None
def _get_possible_nodes(module_context, func_string_name):
try:
names = module_context.tree_node.get_used_names()[func_string_name]
@@ -156,11 +184,9 @@ def _check_name_for_execution(evaluator, context, compare_node, name, trailer):
def create_func_excs():
arglist = trailer.children[1]
if arglist == ')':
arglist = ()
arglist = None
args = TreeArguments(evaluator, context, arglist, trailer)
if value_node.type == 'funcdef':
yield value.get_function_execution(args)
else:
if value_node.type == 'classdef':
created_instance = instance.TreeInstance(
evaluator,
value.parent_context,
@@ -169,6 +195,8 @@ def _check_name_for_execution(evaluator, context, compare_node, name, trailer):
)
for execution in created_instance.create_init_executions():
yield execution
else:
yield value.get_function_execution(args)
for value in evaluator.goto_definitions(context, name):
value_node = value.tree_node
@@ -180,7 +208,7 @@ def _check_name_for_execution(evaluator, context, compare_node, name, trailer):
# Here we're trying to find decorators by checking the first
# parameter. It's not very generic though. Should find a better
# solution that also applies to nested decorators.
params = value.parent_context.get_params()
params = value.parent_context.get_executed_params()
if len(params) != 1:
continue
values = params[0].infer()

View File

@@ -6,7 +6,8 @@ from abc import abstractmethod
from parso.tree import search_ancestor
from jedi._compatibility import is_py3
from jedi._compatibility import use_metaclass, Parameter
from jedi.cache import memoize_method
from jedi.evaluate import flow_analysis
from jedi.evaluate.base_context import ContextSet, Context
from jedi.parser_utils import get_parent_scope
@@ -27,7 +28,7 @@ class AbstractNameDefinition(object):
def goto(self):
# Typically names are already definitions and therefore a goto on that
# name will always result on itself.
return set([self])
return {self}
def get_root_context(self):
return self.parent_context.get_root_context()
@@ -37,11 +38,8 @@ class AbstractNameDefinition(object):
return '<%s: %s>' % (self.__class__.__name__, self.string_name)
return '<%s: %s@%s>' % (self.__class__.__name__, self.string_name, self.start_pos)
def execute(self, arguments):
return self.infer().execute(arguments)
def execute_evaluated(self, *args, **kwargs):
return self.infer().execute_evaluated(*args, **kwargs)
def is_import(self):
return False
@property
def api_type(self):
@@ -56,6 +54,10 @@ class AbstractTreeName(AbstractNameDefinition):
def goto(self):
return self.parent_context.evaluator.goto(self.parent_context, self.tree_name)
def is_import(self):
imp = search_ancestor(self.tree_name, 'import_from', 'import_name')
return imp is not None
@property
def string_name(self):
return self.tree_name.value
@@ -108,34 +110,37 @@ class TreeNameDefinition(AbstractTreeName):
class ParamName(AbstractTreeName):
api_type = 'param'
api_type = u'param'
def __init__(self, parent_context, tree_name):
self.parent_context = parent_context
self.tree_name = tree_name
def get_kind(self):
tree_param = search_ancestor(self.tree_name, 'param')
if tree_param.star_count == 1: # *args
return Parameter.VAR_POSITIONAL
if tree_param.star_count == 2: # **kwargs
return Parameter.VAR_KEYWORD
parent = tree_param.parent
for p in parent.children:
if p.type == 'param':
if p.star_count:
return Parameter.KEYWORD_ONLY
if p == tree_param:
break
return Parameter.POSITIONAL_OR_KEYWORD
def infer(self):
return self.get_param().infer()
def get_param(self):
params = self.parent_context.get_params()
params = self.parent_context.get_executed_params()
param_node = search_ancestor(self.tree_name, 'param')
return params[param_node.position_index]
class AnonymousInstanceParamName(ParamName):
def infer(self):
param_node = search_ancestor(self.tree_name, 'param')
# TODO I think this should not belong here. It's not even really true,
# because classmethod and other descriptors can change it.
if param_node.position_index == 0:
# This is a speed optimization, to return the self param (because
# it's known). This only affects anonymous instances.
return ContextSet(self.parent_context.instance)
else:
return self.get_param().infer()
class AbstractFilter(object):
_until_position = None
@@ -163,7 +168,7 @@ class AbstractUsedNamesFilter(AbstractFilter):
def get(self, name):
try:
names = self._used_names[str(name)]
names = self._used_names[name]
except KeyError:
return []
@@ -213,7 +218,10 @@ class ParserTreeFilter(AbstractUsedNamesFilter):
def _check_flows(self, names):
for name in sorted(names, key=lambda name: name.start_pos, reverse=True):
check = flow_analysis.reachability_check(
self._node_context, self._parser_scope, name, self._origin_scope
context=self._node_context,
context_scope=self._parser_scope,
node=name,
origin_scope=self._origin_scope
)
if check is not flow_analysis.UNREACHABLE:
yield name
@@ -245,10 +253,6 @@ class FunctionExecutionFilter(ParserTreeFilter):
yield TreeNameDefinition(self.context, name)
class AnonymousInstanceFunctionExecutionFilter(FunctionExecutionFilter):
param_name = AnonymousInstanceParamName
class GlobalNameFilter(AbstractUsedNamesFilter):
def __init__(self, context, parser_scope):
super(GlobalNameFilter, self).__init__(context, parser_scope)
@@ -266,22 +270,42 @@ class DictFilter(AbstractFilter):
def get(self, name):
try:
value = self._convert(name, self._dct[str(name)])
value = self._convert(name, self._dct[name])
except KeyError:
return []
return list(self._filter([value]))
else:
return list(self._filter([value]))
def values(self):
return self._filter(self._convert(*item) for item in self._dct.items())
def yielder():
for item in self._dct.items():
try:
yield self._convert(*item)
except KeyError:
pass
return self._filter(yielder())
def _convert(self, name, value):
return value
class MergedFilter(object):
def __init__(self, *filters):
self._filters = filters
def get(self, name):
return [n for filter in self._filters for n in filter.get(name)]
def values(self):
return [n for filter in self._filters for n in filter.values()]
def __repr__(self):
return '%s(%s)' % (self.__class__.__name__, ', '.join(str(f) for f in self._filters))
class _BuiltinMappedMethod(Context):
"""``Generator.__next__`` ``dict.values`` methods and so on."""
api_type = 'function'
api_type = u'function'
def __init__(self, builtin_context, method, builtin_func):
super(_BuiltinMappedMethod, self).__init__(
@@ -292,6 +316,7 @@ class _BuiltinMappedMethod(Context):
self._builtin_func = builtin_func
def py__call__(self, params):
# TODO add TypeError if params are given/or not correct.
return self._method(self.parent_context)
def __getattr__(self, name):
@@ -304,21 +329,33 @@ class SpecialMethodFilter(DictFilter):
classes like Generator (for __next__, etc).
"""
class SpecialMethodName(AbstractNameDefinition):
api_type = 'function'
api_type = u'function'
def __init__(self, parent_context, string_name, value, builtin_context):
callable_, python_version = value
if python_version is not None and \
python_version != parent_context.evaluator.environment.version_info.major:
raise KeyError
def __init__(self, parent_context, string_name, callable_, builtin_context):
self.parent_context = parent_context
self.string_name = string_name
self._callable = callable_
self._builtin_context = builtin_context
def infer(self):
filter = next(self._builtin_context.get_filters())
# We can take the first index, because on builtin methods there's
# always only going to be one name. The same is true for the
# inferred values.
builtin_func = next(iter(filter.get(self.string_name)[0].infer()))
return ContextSet(_BuiltinMappedMethod(self.parent_context, self._callable, builtin_func))
for filter in self._builtin_context.get_filters():
# We can take the first index, because on builtin methods there's
# always only going to be one name. The same is true for the
# inferred values.
for name in filter.get(self.string_name):
builtin_func = next(iter(name.infer()))
break
else:
continue
break
return ContextSet(
_BuiltinMappedMethod(self.parent_context, self._callable, builtin_func)
)
def __init__(self, context, dct, builtin_context):
super(SpecialMethodFilter, self).__init__(dct)
@@ -335,34 +372,58 @@ class SpecialMethodFilter(DictFilter):
return self.SpecialMethodName(self.context, name, value, self._builtin_context)
def has_builtin_methods(cls):
base_dct = {}
# Need to care properly about inheritance. Builtin Methods should not get
# lost, just because they are not mentioned in a class.
for base_cls in reversed(cls.__bases__):
try:
base_dct.update(base_cls.builtin_methods)
except AttributeError:
pass
class _OverwriteMeta(type):
def __init__(cls, name, bases, dct):
super(_OverwriteMeta, cls).__init__(name, bases, dct)
cls.builtin_methods = base_dct
for func in cls.__dict__.values():
try:
cls.builtin_methods.update(func.registered_builtin_methods)
except AttributeError:
pass
return cls
base_dct = {}
for base_cls in reversed(cls.__bases__):
try:
base_dct.update(base_cls.overwritten_methods)
except AttributeError:
pass
for func in cls.__dict__.values():
try:
base_dct.update(func.registered_overwritten_methods)
except AttributeError:
pass
cls.overwritten_methods = base_dct
def register_builtin_method(method_name, python_version_match=None):
def wrapper(func):
if python_version_match and python_version_match != 2 + int(is_py3):
# Some functions do only apply to certain versions.
return func
dct = func.__dict__.setdefault('registered_builtin_methods', {})
dct[method_name] = func
class AbstractObjectOverwrite(use_metaclass(_OverwriteMeta, object)):
def get_object(self):
raise NotImplementedError
def get_filters(self, search_global, *args, **kwargs):
yield SpecialMethodFilter(self, self.overwritten_methods, self.get_object())
for filter in self.get_object().get_filters(search_global):
yield filter
class BuiltinOverwrite(Context, AbstractObjectOverwrite):
special_object_identifier = None
def __init__(self, evaluator):
super(BuiltinOverwrite, self).__init__(evaluator, evaluator.builtins_module)
@memoize_method
def get_object(self):
from jedi.evaluate import compiled
assert self.special_object_identifier
return compiled.get_special_object(self.evaluator, self.special_object_identifier)
def py__class__(self):
return self.get_object().py__class__()
def publish_method(method_name, python_version_match=None):
def decorator(func):
dct = func.__dict__.setdefault('registered_overwritten_methods', {})
dct[method_name] = func, python_version_match
return func
return wrapper
return decorator
def get_global_filters(evaluator, context, until_position, origin_scope):
@@ -379,40 +440,37 @@ def get_global_filters(evaluator, context, until_position, origin_scope):
... def func():
... y = None
... '''))
>>> module_node = script._get_module_node()
>>> module_node = script._module_node
>>> scope = next(module_node.iter_funcdefs())
>>> scope
<Function: func@3-5>
>>> context = script._get_module().create_context(scope)
>>> filters = list(get_global_filters(context.evaluator, context, (4, 0), None))
First we get the names names from the function scope.
First we get the names from the function scope.
>>> no_unicode_pprint(filters[0])
<ParserTreeFilter: <ModuleContext: @2-5>>
>>> no_unicode_pprint(filters[0]) #doctest: +ELLIPSIS
MergedFilter(<ParserTreeFilter: ...>, <GlobalNameFilter: ...>)
>>> sorted(str(n) for n in filters[0].values())
['<TreeNameDefinition: func@(3, 4)>', '<TreeNameDefinition: x@(2, 0)>']
>>> filters[0]._until_position
>>> filters[0]._filters[0]._until_position
(4, 0)
>>> filters[0]._filters[1]._until_position
Then it yields the names from one level "lower". In this example, this is
the module scope. As a side note, you can see, that the position in the
filter is now None, because typically the whole module is loaded before the
function is called.
the module scope (including globals).
As a side note, you can see, that the position in the filter is None on the
globals filter, because there the whole module is searched.
>>> filters[1].values() # global names -> there are none in our example.
>>> list(filters[1].values()) # package modules -> Also empty.
[]
>>> list(filters[2].values()) # package modules -> Also empty.
[]
>>> sorted(name.string_name for name in filters[3].values()) # Module attributes
>>> sorted(name.string_name for name in filters[2].values()) # Module attributes
['__doc__', '__file__', '__name__', '__package__']
>>> print(filters[1]._until_position)
None
Finally, it yields the builtin filter, if `include_builtin` is
true (default).
>>> filters[4].values() #doctest: +ELLIPSIS
>>> filters[3].values() #doctest: +ELLIPSIS
[<CompiledName: ...>, ...]
"""
from jedi.evaluate.context.function import FunctionExecutionContext
@@ -430,5 +488,5 @@ def get_global_filters(evaluator, context, until_position, origin_scope):
context = context.parent_context
# Add builtins to the global scope.
for filter in evaluator.BUILTINS.get_filters(search_global=True):
for filter in evaluator.builtins_module.get_filters(search_global=True):
yield filter

View File

@@ -56,7 +56,10 @@ class NameFinder(object):
names = self.filter_name(filters)
if self._found_predefined_types is not None and names:
check = flow_analysis.reachability_check(
self._context, self._context.tree_node, self._name)
context=self._context,
context_scope=self._context.tree_node,
node=self._name,
)
if check is flow_analysis.UNREACHABLE:
return ContextSet()
return self._found_predefined_types
@@ -92,7 +95,26 @@ class NameFinder(object):
def get_filters(self, search_global=False):
origin_scope = self._get_origin_scope()
if search_global:
return get_global_filters(self._evaluator, self._context, self._position, origin_scope)
position = self._position
# For functions and classes the defaults don't belong to the
# function and get evaluated in the context before the function. So
# make sure to exclude the function/class name.
if origin_scope is not None:
ancestor = search_ancestor(origin_scope, 'funcdef', 'classdef', 'lambdef')
lambdef = None
if ancestor == 'lambdef':
# For lambdas it's even more complicated since parts will
# be evaluated later.
lambdef = ancestor
ancestor = search_ancestor(origin_scope, 'funcdef', 'classdef')
if ancestor is not None:
colon = ancestor.children[-2]
if position < colon.start_pos:
if lambdef is None or position < lambdef.children[-2].start_pos:
position = ancestor.start_pos
return get_global_filters(self._evaluator, self._context, position, origin_scope)
else:
return self._context.get_filters(search_global, self._position, origin_scope=origin_scope)
@@ -102,8 +124,7 @@ class NameFinder(object):
``filters``), until a name fits.
"""
names = []
if self._context.predefined_names:
# TODO is this ok? node might not always be a tree.Name
if self._context.predefined_names and isinstance(self._name, tree.Name):
node = self._name
while node is not None and not is_scope(node):
node = node.parent
@@ -133,14 +154,14 @@ class NameFinder(object):
continue
break
debug.dbg('finder.filter_name "%s" in (%s): %s@%s', self._string_name,
self._context, names, self._position)
debug.dbg('finder.filter_name %s in (%s): %s@%s',
self._string_name, self._context, names, self._position)
return list(names)
def _check_getattr(self, inst):
"""Checks for both __getattr__ and __getattribute__ methods"""
# str is important, because it shouldn't be `Name`!
name = compiled.create(self._evaluator, self._string_name)
name = compiled.create_simple_object(self._evaluator, self._string_name)
# This is a little bit special. `__getattribute__` is in Python
# executed before `__getattr__`. But: I know no use case, where
@@ -149,8 +170,8 @@ class NameFinder(object):
# We are inversing this, because a hand-crafted `__getattribute__`
# could still call another hand-crafted `__getattr__`, but not the
# other way around.
names = (inst.get_function_slot_names('__getattr__') or
inst.get_function_slot_names('__getattribute__'))
names = (inst.get_function_slot_names(u'__getattr__') or
inst.get_function_slot_names(u'__getattribute__'))
return inst.execute_function_slots(names, name)
def _names_to_types(self, names, attribute_lookup):
@@ -248,8 +269,7 @@ def _check_isinstance_type(context, element, search_name):
context_set = ContextSet()
for cls_or_tup in lazy_context_cls.infer():
if isinstance(cls_or_tup, iterable.AbstractIterable) and \
cls_or_tup.array_type == 'tuple':
if isinstance(cls_or_tup, iterable.Sequence) and cls_or_tup.array_type == 'tuple':
for lazy_context in cls_or_tup.py__iter__():
for context in lazy_context.infer():
context_set |= context.execute_evaluated()

View File

@@ -1,4 +1,5 @@
from jedi.parser_utils import get_flow_branch_keyword, is_scope, get_parent_scope
from jedi.evaluate.recursion import execution_allowed
class Status(object):
@@ -59,7 +60,8 @@ def reachability_check(context, context_scope, node, origin_scope=None):
if not branch_matches and origin_keyword == 'else' \
and node_keyword == 'except':
return UNREACHABLE
break
if branch_matches:
break
# Direct parents get resolved, we filter scopes that are separate
# branches. This makes sense for autocompletion and static analysis.
@@ -104,9 +106,13 @@ def _break_check(context, context_scope, flow_scope, node):
def _check_if(context, node):
types = context.eval_node(node)
values = set(x.py__bool__() for x in types)
if len(values) == 1:
return Status.lookup_table[values.pop()]
else:
return UNSURE
with execution_allowed(context.evaluator, node) as allowed:
if not allowed:
return UNSURE
types = context.eval_node(node)
values = set(x.py__bool__() for x in types)
if len(values) == 1:
return Status.lookup_table[values.pop()]
else:
return UNSURE

View File

@@ -9,7 +9,6 @@ from parso.python import tree
from jedi._compatibility import unicode
from jedi.parser_utils import get_parent_scope
from jedi.evaluate.compiled import CompiledObject
def is_stdlib_path(path):
@@ -20,7 +19,7 @@ def is_stdlib_path(path):
return False
base_path = os.path.join(sys.prefix, 'lib', 'python')
return bool(re.match(re.escape(base_path) + '\d.\d', path))
return bool(re.match(re.escape(base_path) + r'\d.\d', path))
def deep_ast_copy(obj):
@@ -65,6 +64,10 @@ def evaluate_call_of_leaf(context, leaf, cut_own_trailer=False):
The option ``cut_own_trailer`` must be set to true for the second purpose.
"""
trailer = leaf.parent
if trailer.type == 'fstring':
from jedi.evaluate import compiled
return compiled.get_string_context_set(context.evaluator)
# The leaf may not be the last or first child, because there exist three
# different trailers: `( x )`, `[ x ]` and `.x`. In the first two examples
# we should not match anything more than x.
@@ -176,8 +179,6 @@ def get_module_names(module, all_scopes):
@contextmanager
def predefine_names(context, flow_scope, dct):
predefined = context.predefined_names
if flow_scope in predefined:
raise NotImplementedError('Why does this happen?')
predefined[flow_scope] = dct
try:
yield
@@ -186,16 +187,53 @@ def predefine_names(context, flow_scope, dct):
def is_compiled(context):
from jedi.evaluate.compiled import CompiledObject
return isinstance(context, CompiledObject)
def is_string(context):
return is_compiled(context) and isinstance(context.obj, (str, unicode))
if context.evaluator.environment.version_info.major == 2:
str_classes = (unicode, bytes)
else:
str_classes = (unicode,)
return is_compiled(context) and isinstance(context.get_safe_value(default=None), str_classes)
def is_literal(context):
return is_number(context) or is_string(context)
def _get_safe_value_or_none(context, accept):
if is_compiled(context):
value = context.get_safe_value(default=None)
if isinstance(value, accept):
return value
def get_int_or_none(context):
return _get_safe_value_or_none(context, int)
def is_number(context):
return is_compiled(context) and isinstance(context.obj, (int, float))
return _get_safe_value_or_none(context, (int, float)) is not None
class EvaluatorTypeError(Exception):
pass
class EvaluatorIndexError(Exception):
pass
class EvaluatorKeyError(Exception):
pass
@contextmanager
def reraise_as_evaluator(*exception_classes):
try:
yield
except exception_classes as e:
new_exc_cls = globals()['Evaluator' + e.__class__.__name__]
raise new_exc_cls(e)

View File

@@ -9,21 +9,19 @@ 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`` (curser at the end would return ``datetime``).
statements like ``from datetim`` (cursor at the end would return ``datetime``).
"""
import imp
import os
import pkgutil
import sys
from parso.python import tree
from parso.tree import search_ancestor
from parso.cache import parser_cache
from parso import python_bytes_to_unicode
from jedi._compatibility import find_module, unicode, ImplicitNSInfo
from jedi._compatibility import (FileNotFoundError, ImplicitNSInfo,
force_unicode, unicode)
from jedi import debug
from jedi import settings
from jedi.parser_utils import get_cached_code_lines
from jedi.evaluate import sys_path
from jedi.evaluate import helpers
from jedi.evaluate import compiled
@@ -34,6 +32,26 @@ from jedi.evaluate.filters import AbstractNameDefinition
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS
class ModuleCache(object):
def __init__(self):
self._path_cache = {}
self._name_cache = {}
def add(self, module, name):
path = module.py__file__()
self._path_cache[path] = module
self._name_cache[name] = module
def iterate_modules_with_names(self):
return self._name_cache.items()
def get(self, name):
return self._name_cache[name]
def get_from_path(self, path):
return self._path_cache[path]
# This memoization is needed, because otherwise we will infinitely loop on
# certain imports.
@evaluator_method_cache(default=NO_CONTEXTS)
@@ -130,20 +148,13 @@ class NestedImportModule(tree.Module):
def _add_error(context, name, message=None):
# Should be a name, not a string!
if message is None:
name_str = str(name.value) if isinstance(name, tree.Name) else name
message = 'No module named ' + name_str
if hasattr(name, 'parent'):
analysis.add(context, 'import-error', name, message)
def get_init_path(directory_path):
"""
The __init__ file can be searched in a directory. If found return it, else
None.
"""
for suffix, _, _ in imp.get_suffixes():
path = os.path.join(directory_path, '__init__' + suffix)
if os.path.exists(path):
return path
return None
else:
debug.warning('ImportError without origin: ' + message)
class ImportName(AbstractNameDefinition):
@@ -204,7 +215,7 @@ class Importer(object):
if level:
base = module_context.py__package__().split('.')
if base == ['']:
if base == [''] or base == ['__main__']:
base = []
if level > len(base):
path = module_context.py__file__()
@@ -226,10 +237,11 @@ class Importer(object):
else:
import_path.insert(0, dir_name)
else:
_add_error(module_context, import_path[-1])
_add_error(
module_context, import_path[-1],
message='Attempted relative import beyond top-level package.'
)
import_path = []
# TODO add import error.
debug.warning('Attempted relative import beyond top-level package.')
# If no path is defined in the module we have no ideas where we
# are in the file system. Therefore we cannot know what to do.
# In this case we just let the path there and ignore that it's
@@ -248,31 +260,27 @@ class Importer(object):
"""Returns the import path as pure strings instead of `Name`."""
return tuple(
name.value if isinstance(name, tree.Name) else name
for name in self.import_path)
for name in self.import_path
)
def sys_path_with_modifications(self):
in_path = []
sys_path_mod = self._evaluator.project.sys_path \
+ sys_path.check_sys_path_modifications(self.module_context)
if self.file_path is not None:
# If you edit e.g. gunicorn, there will be imports like this:
# `from gunicorn import something`. But gunicorn is not in the
# sys.path. Therefore look if gunicorn is a parent directory, #56.
if self.import_path: # TODO is this check really needed?
for path in sys_path.traverse_parents(self.file_path):
if os.path.basename(path) == self.str_import_path[0]:
in_path.append(os.path.dirname(path))
# Since we know nothing about the call location of the sys.path,
# it's a possibility that the current directory is the origin of
# the Python execution.
sys_path_mod.insert(0, os.path.dirname(self.file_path))
sys_path_mod = (
self._evaluator.get_sys_path()
+ sys_path.check_sys_path_modifications(self.module_context)
)
return in_path + sys_path_mod
if self.import_path and self.file_path is not None \
and self._evaluator.environment.version_info.major == 2:
# Python2 uses an old strange way of importing relative imports.
sys_path_mod.append(force_unicode(os.path.dirname(self.file_path)))
return sys_path_mod
def follow(self):
if not self.import_path:
if not self.import_path or not self._evaluator.infer_enabled:
return NO_CONTEXTS
return self._do_import(self.import_path, self.sys_path_with_modifications())
def _do_import(self, import_path, sys_path):
@@ -280,7 +288,7 @@ class Importer(object):
This method is very similar to importlib's `_gcd_import`.
"""
import_parts = [
i.value if isinstance(i, tree.Name) else i
force_unicode(i.value if isinstance(i, tree.Name) else i)
for i in import_path
]
@@ -296,9 +304,17 @@ class Importer(object):
# Old style
return self._do_import(('flaskext',) + import_path[2:], sys_path)
if import_parts[0] in settings.auto_import_modules:
module = _load_module(
self._evaluator,
import_names=import_parts,
sys_path=sys_path,
)
return ContextSet(module)
module_name = '.'.join(import_parts)
try:
return ContextSet(self._evaluator.modules[module_name])
return ContextSet(self._evaluator.module_cache.get(module_name))
except KeyError:
pass
@@ -332,62 +348,45 @@ class Importer(object):
for path in paths:
# At the moment we are only using one path. So this is
# not important to be correct.
try:
if not isinstance(path, list):
path = [path]
module_file, module_path, is_pkg = \
find_module(import_parts[-1], path, fullname=module_name)
if not isinstance(path, list):
path = [path]
code, module_path, is_pkg = self._evaluator.compiled_subprocess.get_module_info(
string=import_parts[-1],
path=path,
full_name=module_name,
is_global_search=False,
)
if module_path is not None:
break
except ImportError:
module_path = None
if module_path is None:
else:
_add_error(self.module_context, import_path[-1])
return NO_CONTEXTS
else:
parent_module = None
try:
debug.dbg('search_module %s in %s', import_parts[-1], self.file_path)
# Override the sys.path. It works only good that way.
# Injecting the path directly into `find_module` did not work.
sys.path, temp = sys_path, sys.path
try:
module_file, module_path, is_pkg = \
find_module(import_parts[-1], fullname=module_name)
finally:
sys.path = temp
except ImportError:
debug.dbg('global search_module %s in %s', import_parts[-1], self.file_path)
# Override the sys.path. It works only good that way.
# Injecting the path directly into `find_module` did not work.
code, module_path, is_pkg = self._evaluator.compiled_subprocess.get_module_info(
string=import_parts[-1],
full_name=module_name,
sys_path=sys_path,
is_global_search=True,
)
if module_path is None:
# The module is not a package.
_add_error(self.module_context, import_path[-1])
return NO_CONTEXTS
code = None
if is_pkg:
# In this case, we don't have a file yet. Search for the
# __init__ file.
if module_path.endswith(('.zip', '.egg')):
code = module_file.loader.get_source(module_name)
else:
module_path = get_init_path(module_path)
elif module_file:
code = module_file.read()
module_file.close()
if isinstance(module_path, ImplicitNSInfo):
from jedi.evaluate.context.namespace import ImplicitNamespaceContext
fullname, paths = module_path.name, module_path.paths
module = ImplicitNamespaceContext(self._evaluator, fullname=fullname)
module.paths = paths
elif module_file is None and not module_path.endswith(('.py', '.zip', '.egg')):
module = compiled.load_module(self._evaluator, module_path)
else:
module = _load_module(self._evaluator, module_path, code, sys_path, parent_module)
module = _load_module(
self._evaluator, module_path, code, sys_path,
import_names=import_parts,
safe_module_name=True,
)
if module is None:
# The file might raise an ImportError e.g. and therefore not be
# importable.
return NO_CONTEXTS
self._evaluator.modules[module_name] = module
return ContextSet(module)
def _generate_name(self, name, in_module=None):
@@ -401,15 +400,17 @@ class Importer(object):
Get the names of all modules in the search_path. This means file names
and not names defined in the files.
"""
sub = self._evaluator.compiled_subprocess
names = []
# add builtin module names
if search_path is None and in_module is None:
names += [self._generate_name(name) for name in sys.builtin_module_names]
names += [self._generate_name(name) for name in sub.get_builtin_module_names()]
if search_path is None:
search_path = self.sys_path_with_modifications()
for module_loader, name, is_pkg in pkgutil.iter_modules(search_path):
for name in sub.list_module_names(search_path):
names.append(self._generate_name(name, in_module=in_module))
return names
@@ -448,7 +449,7 @@ class Importer(object):
# implicit namespace packages
elif isinstance(context, ImplicitNamespaceContext):
paths = context.paths
names += self._get_module_names(paths)
names += self._get_module_names(paths, in_module=context)
if only_modules:
# In the case of an import like `from x.` we don't need to
@@ -476,38 +477,68 @@ class Importer(object):
return names
def _load_module(evaluator, path=None, code=None, sys_path=None, parent_module=None):
if sys_path is None:
sys_path = evaluator.project.sys_path
dotted_path = path and compiled.dotted_from_fs_path(path, sys_path)
if path is not None and path.endswith(('.py', '.zip', '.egg')) \
and dotted_path not in settings.auto_import_modules:
module_node = evaluator.grammar.parse(
code=code, path=path, cache=True, diff_cache=True,
cache_path=settings.cache_directory)
from jedi.evaluate.context import ModuleContext
return ModuleContext(evaluator, module_node, path=path)
def _load_module(evaluator, path=None, code=None, sys_path=None,
import_names=None, safe_module_name=False):
if import_names is None:
dotted_name = None
else:
return compiled.load_module(evaluator, path)
dotted_name = '.'.join(import_names)
try:
return evaluator.module_cache.get(dotted_name)
except KeyError:
pass
try:
return evaluator.module_cache.get_from_path(path)
except KeyError:
pass
if isinstance(path, ImplicitNSInfo):
from jedi.evaluate.context.namespace import ImplicitNamespaceContext
module = ImplicitNamespaceContext(
evaluator,
fullname=path.name,
paths=path.paths,
)
else:
if sys_path is None:
sys_path = evaluator.get_sys_path()
if path is not None and path.endswith(('.py', '.zip', '.egg')):
module_node = evaluator.parse(
code=code, path=path, cache=True,
diff_cache=settings.fast_parser,
cache_path=settings.cache_directory)
from jedi.evaluate.context import ModuleContext
module = ModuleContext(
evaluator, module_node,
path=path,
code_lines=get_cached_code_lines(evaluator.grammar, path),
)
else:
assert dotted_name is not None
module = compiled.load_module(evaluator, dotted_name=dotted_name, sys_path=sys_path)
if module is not None and dotted_name is not None:
add_module_to_cache(evaluator, dotted_name, module, safe=safe_module_name)
return module
def add_module(evaluator, module_name, module):
if '.' not in module_name:
def add_module_to_cache(evaluator, module_name, module, safe=False):
if not safe and '.' not in module_name:
# We cannot add paths with dots, because that would collide with
# the sepatator dots for nested packages. Therefore we return
# `__main__` in ModuleWrapper.py__name__(), which is similar to
# Python behavior.
evaluator.modules[module_name] = module
return
evaluator.module_cache.add(module, module_name)
def get_modules_containing_name(evaluator, modules, name):
"""
Search a name in the directories of modules.
"""
from jedi.evaluate.context import ModuleContext
def check_directories(paths):
for p in paths:
if p is not None:
@@ -519,28 +550,21 @@ def get_modules_containing_name(evaluator, modules, name):
if file_name.endswith('.py'):
yield path
def check_python_file(path):
try:
# TODO I don't think we should use the cache here?!
node_cache_item = parser_cache[evaluator.grammar._hashed][path]
except KeyError:
try:
return check_fs(path)
except IOError:
return None
else:
module_node = node_cache_item.node
return ModuleContext(evaluator, module_node, path=path)
def check_fs(path):
with open(path, 'rb') as f:
try:
f = open(path, 'rb')
except FileNotFoundError:
return
with f:
code = python_bytes_to_unicode(f.read(), errors='replace')
if name in code:
module = _load_module(evaluator, path, code)
module_name = sys_path.dotted_path_in_sys_path(evaluator.project.sys_path, path)
if module_name is not None:
add_module(evaluator, module_name, module)
e_sys_path = evaluator.get_sys_path()
import_names = sys_path.dotted_path_in_sys_path(e_sys_path, path)
module = _load_module(
evaluator, path, code,
sys_path=e_sys_path,
import_names=import_names,
)
return module
# skip non python modules
@@ -565,6 +589,6 @@ def get_modules_containing_name(evaluator, modules, name):
# Sort here to make issues less random.
for p in sorted(paths):
# make testing easier, sort it - same results on every interpreter
m = check_python_file(p)
m = check_fs(p)
if m is not None and not isinstance(m, compiled.CompiledObject):
yield m

View File

@@ -81,6 +81,9 @@ def factory(typing_name, indextypes):
class Dict(MutableMapping, dict):
pass
class DefaultDict(MutableMapping, dict):
pass
dct = {
"Sequence": Sequence,
"MutableSequence": MutableSequence,
@@ -96,5 +99,6 @@ def factory(typing_name, indextypes):
"ItemsView": ItemsView,
"ValuesView": ValuesView,
"Dict": Dict,
"DefaultDict": DefaultDict,
}
return dct[typing_name]

View File

@@ -1,4 +1,6 @@
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS
from jedi.common.utils import monkeypatch
class AbstractLazyContext(object):
def __init__(self, data):
@@ -40,12 +42,8 @@ class LazyTreeContext(AbstractLazyContext):
self._predefined_names = dict(context.predefined_names)
def infer(self):
old, self._context.predefined_names = \
self._context.predefined_names, self._predefined_names
try:
with monkeypatch(self._context, 'predefined_names', self._predefined_names):
return self._context.eval_node(self.data)
finally:
self._context.predefined_names = old
def get_merged_lazy_context(lazy_contexts):

View File

@@ -41,7 +41,7 @@ class ExecutedParam(object):
return '<%s: %s>' % (self.__class__.__name__, self.string_name)
def get_params(execution_context, var_args):
def get_executed_params(execution_context, var_args):
result_params = []
param_dict = {}
funcdef = execution_context.tree_node
@@ -97,7 +97,7 @@ def get_params(execution_context, var_args):
var_arg_iterator.push_back((key, argument))
break
lazy_context_list.append(argument)
seq = iterable.FakeSequence(execution_context.evaluator, 'tuple', lazy_context_list)
seq = iterable.FakeSequence(execution_context.evaluator, u'tuple', lazy_context_list)
result_arg = LazyKnownContext(seq)
elif param.star_count == 2:
# **kwargs param
@@ -176,7 +176,7 @@ def _error_argument_count(funcdef, actual_count):
def _create_default_param(execution_context, param):
if param.star_count == 1:
result_arg = LazyKnownContext(
iterable.FakeSequence(execution_context.evaluator, 'tuple', [])
iterable.FakeSequence(execution_context.evaluator, u'tuple', [])
)
elif param.star_count == 2:
result_arg = LazyKnownContext(
@@ -192,4 +192,3 @@ def _create_default_param(execution_context, param):
def create_default_params(execution_context, funcdef):
return [_create_default_param(execution_context, p)
for p in funcdef.get_params()]

View File

@@ -22,16 +22,17 @@ x support for type hint comments for functions, `# type: (int, str) -> int`.
import os
import re
from parso import ParserSyntaxError
from parso import ParserSyntaxError, parse, split_lines
from parso.python import tree
from jedi._compatibility import unicode, force_unicode
from jedi.evaluate.cache import evaluator_method_cache
from jedi.evaluate import compiled
from jedi.evaluate.base_context import NO_CONTEXTS, ContextSet
from jedi.evaluate.lazy_context import LazyTreeContext
from jedi.evaluate.context import ModuleContext
from jedi.evaluate.helpers import is_string
from jedi import debug
from jedi import _compatibility
from jedi import parser_utils
@@ -41,17 +42,23 @@ def _evaluate_for_annotation(context, annotation, index=None):
If index is not None, the annotation is expected to be a tuple
and we're interested in that index
"""
if annotation is not None:
context_set = context.eval_node(_fix_forward_reference(context, annotation))
if index is not None:
context_set = context_set.filter(
lambda context: context.array_type == 'tuple' \
and len(list(context.py__iter__())) >= index
).py__getitem__(index)
return context_set.execute_evaluated()
else:
context_set = context.eval_node(_fix_forward_reference(context, annotation))
return context_set.execute_evaluated()
def _evaluate_annotation_string(context, string, index=None):
node = _get_forward_reference_node(context, string)
if node is None:
return NO_CONTEXTS
context_set = context.eval_node(node)
if index is not None:
context_set = context_set.filter(
lambda context: context.array_type == u'tuple'
and len(list(context.py__iter__())) >= index
).py__getitem__(index)
return context_set.execute_evaluated()
def _fix_forward_reference(context, node):
evaled_nodes = context.eval_node(node)
@@ -59,30 +66,111 @@ def _fix_forward_reference(context, node):
debug.warning("Eval'ed typing index %s should lead to 1 object, "
" not %s" % (node, evaled_nodes))
return node
evaled_node = list(evaled_nodes)[0]
if isinstance(evaled_node, compiled.CompiledObject) and \
isinstance(evaled_node.obj, str):
try:
new_node = context.evaluator.grammar.parse(
_compatibility.unicode(evaled_node.obj),
start_symbol='eval_input',
error_recovery=False
)
except ParserSyntaxError:
debug.warning('Annotation not parsed: %s' % evaled_node.obj)
return node
else:
module = node.get_root_node()
parser_utils.move(new_node, module.end_pos[0])
new_node.parent = context.tree_node
return new_node
evaled_context = list(evaled_nodes)[0]
if is_string(evaled_context):
result = _get_forward_reference_node(context, evaled_context.get_safe_value())
if result is not None:
return result
return node
def _get_forward_reference_node(context, string):
try:
new_node = context.evaluator.grammar.parse(
force_unicode(string),
start_symbol='eval_input',
error_recovery=False
)
except ParserSyntaxError:
debug.warning('Annotation not parsed: %s' % string)
return None
else:
return node
module = context.tree_node.get_root_node()
parser_utils.move(new_node, module.end_pos[0])
new_node.parent = context.tree_node
return new_node
def _split_comment_param_declaration(decl_text):
"""
Split decl_text on commas, but group generic expressions
together.
For example, given "foo, Bar[baz, biz]" we return
['foo', 'Bar[baz, biz]'].
"""
try:
node = parse(decl_text, error_recovery=False).children[0]
except ParserSyntaxError:
debug.warning('Comment annotation is not valid Python: %s' % decl_text)
return []
if node.type == 'name':
return [node.get_code().strip()]
params = []
try:
children = node.children
except AttributeError:
return []
else:
for child in children:
if child.type in ['name', 'atom_expr', 'power']:
params.append(child.get_code().strip())
return params
@evaluator_method_cache()
def infer_param(execution_context, param):
"""
Infers the type of a function parameter, using type annotations.
"""
annotation = param.annotation
if annotation is None:
# If no Python 3-style annotation, look for a Python 2-style 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
if child.type == 'param']
node = param.parent.parent
comment = parser_utils.get_following_comment_same_line(node)
if comment is None:
return NO_CONTEXTS
match = re.match(r"^#\s*type:\s*\(([^#]*)\)\s*->", comment)
if not match:
return NO_CONTEXTS
params_comments = _split_comment_param_declaration(match.group(1))
# Find the specific param being investigated
index = all_params.index(param)
# If the number of parameters doesn't match length of type comment,
# ignore first parameter (assume it's self).
if len(params_comments) != len(all_params):
debug.warning(
"Comments length != Params length %s %s",
params_comments, all_params
)
from jedi.evaluate.context.instance import InstanceArguments
if isinstance(execution_context.var_args, InstanceArguments):
if index == 0:
# Assume it's self, which is already handled
return NO_CONTEXTS
index -= 1
if index >= len(params_comments):
return NO_CONTEXTS
param_comment = params_comments[index]
return _evaluate_annotation_string(
execution_context.get_root_context(),
param_comment
)
module_context = execution_context.get_root_context()
return _evaluate_for_annotation(module_context, annotation)
@@ -102,12 +190,33 @@ def py__annotations__(funcdef):
@evaluator_method_cache()
def infer_return_types(function_context):
"""
Infers the type of a function's return value,
according to type annotations.
"""
annotation = py__annotations__(function_context.tree_node).get("return", None)
if annotation is None:
# If there is no Python 3-type annotation, look for a Python 2-type annotation
node = function_context.tree_node
comment = parser_utils.get_following_comment_same_line(node)
if comment is None:
return NO_CONTEXTS
match = re.match(r"^#\s*type:\s*\([^#]*\)\s*->\s*([^#]*)", comment)
if not match:
return NO_CONTEXTS
return _evaluate_annotation_string(
function_context.get_root_context(),
match.group(1).strip()
)
module_context = function_context.get_root_context()
return _evaluate_for_annotation(module_context, annotation)
_typing_module = None
_typing_module_code_lines = None
def _get_typing_replacement_module(grammar):
@@ -115,14 +224,15 @@ def _get_typing_replacement_module(grammar):
The idea is to return our jedi replacement for the PEP-0484 typing module
as discussed at https://github.com/davidhalter/jedi/issues/663
"""
global _typing_module
global _typing_module, _typing_module_code_lines
if _typing_module is None:
typing_path = \
os.path.abspath(os.path.join(__file__, "../jedi_typing.py"))
with open(typing_path) as f:
code = _compatibility.unicode(f.read())
code = unicode(f.read())
_typing_module = grammar.parse(code)
return _typing_module
_typing_module_code_lines = split_lines(code, keepends=True)
return _typing_module, _typing_module_code_lines
def py__getitem__(context, typ, node):
@@ -152,10 +262,12 @@ def py__getitem__(context, typ, node):
# check for the instance typing._Optional (Python 3.6).
return context.eval_node(nodes[0])
module_node, code_lines = _get_typing_replacement_module(context.evaluator.latest_grammar)
typing = ModuleContext(
context.evaluator,
module_node=_get_typing_replacement_module(context.evaluator.latest_grammar),
path=None
module_node=module_node,
path=None,
code_lines=code_lines,
)
factories = typing.py__getattribute__("factory")
assert len(factories) == 1
@@ -167,12 +279,12 @@ def py__getitem__(context, typ, node):
if isinstance(child, tree.Class))
if type_name not in valid_classnames:
return None
compiled_classname = compiled.create(context.evaluator, type_name)
compiled_classname = compiled.create_simple_object(context.evaluator, type_name)
from jedi.evaluate.context.iterable import FakeSequence
args = FakeSequence(
context.evaluator,
"tuple",
u'tuple',
[LazyTreeContext(context, n) for n in nodes]
)
@@ -213,10 +325,6 @@ def _find_type_from_comment_hint(context, node, varlist, name):
if comment is None:
return []
match = re.match(r"^#\s*type:\s*([^#]*)", comment)
if not match:
if match is None:
return []
annotation = tree.String(
repr(str(match.group(1).strip())),
node.start_pos)
annotation.parent = node.parent
return _evaluate_for_annotation(context, annotation, index)
return _evaluate_annotation_string(context, match.group(1).strip(), index)

View File

@@ -1,40 +0,0 @@
import os
import sys
from jedi.evaluate.sys_path import get_venv_path, detect_additional_paths
from jedi.cache import underscore_memoization
class Project(object):
def __init__(self, sys_path=None):
if sys_path is not None:
self._sys_path = sys_path
venv = os.getenv('VIRTUAL_ENV')
if venv:
sys_path = get_venv_path(venv)
if sys_path is None:
sys_path = sys.path
base_sys_path = list(sys_path)
try:
base_sys_path.remove('')
except ValueError:
pass
self._base_sys_path = base_sys_path
def add_script_path(self, script_path):
self._script_path = script_path
def add_evaluator(self, evaluator):
self._evaluator = evaluator
@property
@underscore_memoization
def sys_path(self):
if self._script_path is None:
return self._base_sys_path
return self._base_sys_path + detect_additional_paths(self._evaluator, self._script_path)

View File

@@ -49,6 +49,7 @@ per_function_recursion_limit = 2
A function may not be executed more than this number of times recursively.
"""
class RecursionDetector(object):
def __init__(self):
self.pushed_nodes = []
@@ -64,24 +65,26 @@ def execution_allowed(evaluator, node):
if node in pushed_nodes:
debug.warning('catched stmt recursion: %s @%s', node,
node.start_pos)
getattr(node, 'start_pos', None))
yield False
else:
pushed_nodes.append(node)
yield True
pushed_nodes.pop()
try:
pushed_nodes.append(node)
yield True
finally:
pushed_nodes.pop()
def execution_recursion_decorator(default=NO_CONTEXTS):
def decorator(func):
def wrapper(execution, **kwargs):
detector = execution.evaluator.execution_recursion_detector
allowed = detector.push_execution(execution)
def wrapper(self, **kwargs):
detector = self.evaluator.execution_recursion_detector
allowed = detector.push_execution(self)
try:
if allowed:
result = default
else:
result = func(execution, **kwargs)
result = func(self, **kwargs)
finally:
detector.pop_execution()
return result
@@ -113,7 +116,7 @@ class ExecutionRecursionDetector(object):
self._parent_execution_funcs.append(funcdef)
module = execution.get_root_context()
if module == self._evaluator.BUILTINS:
if module == self._evaluator.builtins_module:
# We have control over builtins so we know they are not recursing
# like crazy. Therefore we just let them execute always, because
# they usually just help a lot with getting good results.

View File

@@ -1,110 +0,0 @@
"""An adapted copy of relevant site-packages functionality from Python stdlib.
This file contains some functions related to handling site-packages in Python
with jedi-specific modifications:
- the functions operate on sys_path argument rather than global sys.path
- in .pth files "import ..." lines that allow execution of arbitrary code are
skipped to prevent code injection into jedi interpreter
"""
# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
# 2011, 2012, 2013, 2014, 2015 Python Software Foundation; All Rights Reserved
from __future__ import print_function
import sys
import os
def makepath(*paths):
dir = os.path.join(*paths)
try:
dir = os.path.abspath(dir)
except OSError:
pass
return dir, os.path.normcase(dir)
def _init_pathinfo(sys_path):
"""Return a set containing all existing directory entries from sys_path"""
d = set()
for dir in sys_path:
try:
if os.path.isdir(dir):
dir, dircase = makepath(dir)
d.add(dircase)
except TypeError:
continue
return d
def addpackage(sys_path, sitedir, name, known_paths):
"""Process a .pth file within the site-packages directory:
For each line in the file, either combine it with sitedir to a path
and add that to known_paths, or execute it if it starts with 'import '.
"""
if known_paths is None:
known_paths = _init_pathinfo(sys_path)
reset = 1
else:
reset = 0
fullname = os.path.join(sitedir, name)
try:
f = open(fullname, "r")
except OSError:
return
with f:
for n, line in enumerate(f):
if line.startswith("#"):
continue
try:
if line.startswith(("import ", "import\t")):
# Change by immerrr: don't evaluate import lines to prevent
# code injection into jedi through pth files.
#
# exec(line)
continue
line = line.rstrip()
dir, dircase = makepath(sitedir, line)
if not dircase in known_paths and os.path.exists(dir):
sys_path.append(dir)
known_paths.add(dircase)
except Exception:
print("Error processing line {:d} of {}:\n".format(n+1, fullname),
file=sys.stderr)
import traceback
for record in traceback.format_exception(*sys.exc_info()):
for line in record.splitlines():
print(' '+line, file=sys.stderr)
print("\nRemainder of file ignored", file=sys.stderr)
break
if reset:
known_paths = None
return known_paths
def addsitedir(sys_path, sitedir, known_paths=None):
"""Add 'sitedir' argument to sys_path if missing and handle .pth files in
'sitedir'"""
if known_paths is None:
known_paths = _init_pathinfo(sys_path)
reset = 1
else:
reset = 0
sitedir, sitedircase = makepath(sitedir)
if not sitedircase in known_paths:
sys_path.append(sitedir) # Add path component
known_paths.add(sitedircase)
try:
names = os.listdir(sitedir)
except OSError:
return
names = [name for name in names if name.endswith(".pth")]
for name in sorted(names):
addpackage(sys_path, sitedir, name, known_paths)
if reset:
known_paths = None
return known_paths

View File

@@ -9,19 +9,18 @@ Note that this module exists only to implement very specific functionality in
the standard library. The usual way to understand the standard library is the
compiled module that returns the types for C-builtins.
"""
import collections
import re
import parso
from jedi._compatibility import force_unicode
from jedi import debug
from jedi.evaluate.arguments import ValuesArguments
from jedi.evaluate.arguments import ValuesArguments, repack_with_argument_clinic
from jedi.evaluate import analysis
from jedi.evaluate import compiled
from jedi.evaluate.context.instance import InstanceFunctionExecution, \
AbstractInstanceContext, CompiledInstance, BoundMethod, \
AnonymousInstanceFunctionExecution
from jedi.evaluate.context.instance import \
AbstractInstanceContext, CompiledInstance, BoundMethod, InstanceArguments
from jedi.evaluate.base_context import ContextualizedNode, \
NO_CONTEXTS, ContextSet
from jedi.evaluate.context import ClassContext, ModuleContext
from jedi.evaluate.context import ClassContext, ModuleContext, FunctionExecutionContext
from jedi.evaluate.context import iterable
from jedi.evaluate.lazy_context import LazyTreeContext
from jedi.evaluate.syntax_tree import is_string
@@ -57,7 +56,7 @@ def execute(evaluator, obj, arguments):
except AttributeError:
pass
else:
if obj.parent_context == evaluator.BUILTINS:
if obj.parent_context == evaluator.builtins_module:
module_name = 'builtins'
elif isinstance(obj.parent_context, ModuleContext):
module_name = obj.parent_context.name.string_name
@@ -70,7 +69,7 @@ def execute(evaluator, obj, arguments):
except KeyError:
pass
else:
return func(evaluator, obj, arguments)
return func(evaluator, obj, arguments=arguments)
raise NotInStdLib()
@@ -87,41 +86,23 @@ def argument_clinic(string, want_obj=False, want_context=False, want_arguments=F
"""
Works like Argument Clinic (PEP 436), to validate function params.
"""
clinic_args = []
allow_kwargs = False
optional = False
while string:
# Optional arguments have to begin with a bracket. And should always be
# at the end of the arguments. This is therefore not a proper argument
# clinic implementation. `range()` for exmple allows an optional start
# value at the beginning.
match = re.match('(?:(?:(\[),? ?|, ?|)(\w+)|, ?/)\]*', string)
string = string[len(match.group(0)):]
if not match.group(2): # A slash -> allow named arguments
allow_kwargs = True
continue
optional = optional or bool(match.group(1))
word = match.group(2)
clinic_args.append((word, optional, allow_kwargs))
def f(func):
def wrapper(evaluator, obj, arguments):
@repack_with_argument_clinic(string, keep_arguments_param=True)
def wrapper(evaluator, obj, *args, **kwargs):
arguments = kwargs.pop('arguments')
assert not kwargs # Python 2...
debug.dbg('builtin start %s' % obj, color='MAGENTA')
try:
lst = list(arguments.eval_argument_clinic(clinic_args))
except ValueError:
return NO_CONTEXTS
else:
kwargs = {}
if want_context:
kwargs['context'] = arguments.context
if want_obj:
kwargs['obj'] = obj
if want_arguments:
kwargs['arguments'] = arguments
return func(evaluator, *lst, **kwargs)
finally:
debug.dbg('builtin end', color='MAGENTA')
result = NO_CONTEXTS
if want_context:
kwargs['context'] = arguments.context
if want_obj:
kwargs['obj'] = obj
if want_arguments:
kwargs['arguments'] = arguments
result = func(evaluator, *args, **kwargs)
debug.dbg('builtin end: %s', result, color='MAGENTA')
return result
return wrapper
return f
@@ -133,7 +114,7 @@ def builtins_next(evaluator, iterators, defaults):
TODO this function is currently not used. It's a stab at implementing next
in a different way than fake objects. This would be a bit more flexible.
"""
if evaluator.python_version[0] == 2:
if evaluator.environment.version_info.major == 2:
name = 'next'
else:
name = '__next__'
@@ -157,7 +138,7 @@ def builtins_getattr(evaluator, objects, names, defaults=None):
for obj in objects:
for name in names:
if is_string(name):
return obj.py__getattribute__(name.obj)
return obj.py__getattribute__(force_unicode(name.get_safe_value()))
else:
debug.warning('getattr called without str')
continue
@@ -183,10 +164,11 @@ class SuperInstance(AbstractInstanceContext):
@argument_clinic('[type[, obj]], /', want_context=True)
def builtins_super(evaluator, types, objects, context):
# TODO make this able to detect multiple inheritance super
if isinstance(context, (InstanceFunctionExecution,
AnonymousInstanceFunctionExecution)):
su = context.instance.py__class__().py__bases__()
return su[0].infer().execute_evaluated()
if isinstance(context, FunctionExecutionContext):
if isinstance(context.var_args, InstanceArguments):
su = context.var_args.instance.py__class__().py__bases__()
return su[0].infer().execute_evaluated()
return NO_CONTEXTS
@@ -207,22 +189,24 @@ def builtins_reversed(evaluator, sequences, obj, arguments):
# necessary, because `reversed` is a function and autocompletion
# would fail in certain cases like `reversed(x).__iter__` if we
# just returned the result directly.
seq = iterable.FakeSequence(evaluator, 'list', rev)
seq = iterable.FakeSequence(evaluator, u'list', rev)
arguments = ValuesArguments([ContextSet(seq)])
return ContextSet(CompiledInstance(evaluator, evaluator.BUILTINS, obj, arguments))
return ContextSet(CompiledInstance(evaluator, evaluator.builtins_module, obj, arguments))
@argument_clinic('obj, type, /', want_arguments=True)
def builtins_isinstance(evaluator, objects, types, arguments):
bool_results = set()
for o in objects:
cls = o.py__class__()
try:
mro_func = o.py__class__().py__mro__
mro_func = cls.py__mro__
except AttributeError:
# This is temporary. Everything should have a class attribute in
# Python?! Maybe we'll leave it here, because some numpy objects or
# whatever might not.
return ContextSet(compiled.create(True), compiled.create(False))
bool_results = set([True, False])
break
mro = mro_func()
@@ -230,7 +214,7 @@ def builtins_isinstance(evaluator, objects, types, arguments):
if cls_or_tup.is_class():
bool_results.add(cls_or_tup in mro)
elif cls_or_tup.name.string_name == 'tuple' \
and cls_or_tup.get_root_context() == evaluator.BUILTINS:
and cls_or_tup.get_root_context() == evaluator.builtins_module:
# Check for tuples.
classes = ContextSet.from_sets(
lazy_context.infer()
@@ -246,7 +230,10 @@ def builtins_isinstance(evaluator, objects, types, arguments):
'not %s.' % cls_or_tup
analysis.add(lazy_context._context, 'type-error-isinstance', node, message)
return ContextSet.from_iterable(compiled.create(evaluator, x) for x in bool_results)
return ContextSet.from_iterable(
compiled.builtin_from_name(evaluator, force_unicode(str(b)))
for b in bool_results
)
def collections_namedtuple(evaluator, obj, arguments):
@@ -256,45 +243,54 @@ def collections_namedtuple(evaluator, obj, arguments):
This has to be done by processing the namedtuple class template and
evaluating the result.
.. note:: |jedi| only supports namedtuples on Python >2.6.
"""
# Namedtuples are not supported on Python 2.6
if not hasattr(collections, '_class_template'):
collections_context = obj.parent_context
_class_template_set = collections_context.py__getattribute__(u'_class_template')
if not _class_template_set:
# Namedtuples are not supported on Python 2.6, early 2.7, because the
# _class_template variable is not defined, there.
return NO_CONTEXTS
# Process arguments
# TODO here we only use one of the types, we should use all.
name = list(_follow_param(evaluator, arguments, 0))[0].obj
# TODO this is buggy, doesn't need to be a string
name = list(_follow_param(evaluator, arguments, 0))[0].get_safe_value()
_fields = list(_follow_param(evaluator, arguments, 1))[0]
if isinstance(_fields, compiled.CompiledObject):
fields = _fields.obj.replace(',', ' ').split()
elif isinstance(_fields, iterable.AbstractIterable):
fields = _fields.get_safe_value().replace(',', ' ').split()
elif isinstance(_fields, iterable.Sequence):
fields = [
v.obj
v.get_safe_value()
for lazy_context in _fields.py__iter__()
for v in lazy_context.infer() if hasattr(v, 'obj')
for v in lazy_context.infer() if is_string(v)
]
else:
return NO_CONTEXTS
base = collections._class_template
def get_var(name):
x, = collections_context.py__getattribute__(name)
return x.get_safe_value()
base = next(iter(_class_template_set)).get_safe_value()
base += _NAMEDTUPLE_INIT
# Build source
source = base.format(
# Build source code
code = base.format(
typename=name,
field_names=tuple(fields),
num_fields=len(fields),
arg_list = repr(tuple(fields)).replace("'", "")[1:-1],
repr_fmt=', '.join(collections._repr_template.format(name=name) for name in fields),
field_defs='\n'.join(collections._field_template.format(index=index, name=name)
arg_list=repr(tuple(fields)).replace("u'", "").replace("'", "")[1:-1],
repr_fmt=', '.join(get_var(u'_repr_template').format(name=name) for name in fields),
field_defs='\n'.join(get_var(u'_field_template').format(index=index, name=name)
for index, name in enumerate(fields))
)
# Parse source
module = evaluator.grammar.parse(source)
# Parse source code
module = evaluator.grammar.parse(code)
generated_class = next(module.iter_classdefs())
parent_context = ModuleContext(evaluator, module, '')
parent_context = ModuleContext(
evaluator, module, None,
code_lines=parso.split_lines(code, keepends=True),
)
return ContextSet(ClassContext(evaluator, parent_context, generated_class))
@@ -316,8 +312,8 @@ _implemented = {
'deepcopy': _return_first_param,
},
'json': {
'load': lambda *args: NO_CONTEXTS,
'loads': lambda *args: NO_CONTEXTS,
'load': lambda evaluator, obj, arguments: NO_CONTEXTS,
'loads': lambda evaluator, obj, arguments: NO_CONTEXTS,
},
'collections': {
'namedtuple': collections_namedtuple,

View File

@@ -2,10 +2,10 @@
Functions evaluating the syntax tree.
"""
import copy
import operator as op
from parso.python import tree
from jedi._compatibility import force_unicode, unicode
from jedi import debug
from jedi import parser_utils
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS, ContextualizedNode, \
@@ -17,11 +17,13 @@ from jedi.evaluate import helpers
from jedi.evaluate import analysis
from jedi.evaluate import imports
from jedi.evaluate import arguments
from jedi.evaluate.pep0484 import _evaluate_for_annotation
from jedi.evaluate.context import ClassContext, FunctionContext
from jedi.evaluate.context import iterable
from jedi.evaluate.context import TreeInstance, CompiledInstance
from jedi.evaluate.finder import NameFinder
from jedi.evaluate.helpers import is_string, is_literal, is_number, is_compiled
from jedi.evaluate.compiled.access import COMPARISON_OPERATORS
def _limit_context_infers(func):
@@ -48,42 +50,59 @@ def _limit_context_infers(func):
return wrapper
def _py__stop_iteration_returns(generators):
results = ContextSet()
for generator in generators:
try:
method = generator.py__stop_iteration_returns
except AttributeError:
debug.warning('%s is not actually a generator', generator)
else:
results |= method()
return results
@debug.increase_indent
@_limit_context_infers
def eval_node(context, element):
debug.dbg('eval_element %s@%s', element, element.start_pos)
debug.dbg('eval_node %s@%s', element, element.start_pos)
evaluator = context.evaluator
typ = element.type
if typ in ('name', 'number', 'string', 'atom'):
if typ in ('name', 'number', 'string', 'atom', 'strings', 'keyword'):
return eval_atom(context, element)
elif typ == 'keyword':
# For False/True/None
if element.value in ('False', 'True', 'None'):
return ContextSet(compiled.builtin_from_name(evaluator, element.value))
# else: print e.g. could be evaluated like this in Python 2.7
return NO_CONTEXTS
elif typ == 'lambdef':
return ContextSet(FunctionContext(evaluator, context, element))
return ContextSet(FunctionContext.from_context(context, element))
elif typ == 'expr_stmt':
return eval_expr_stmt(context, element)
elif typ in ('power', 'atom_expr'):
first_child = element.children[0]
if not (first_child.type == 'keyword' and first_child.value == 'await'):
context_set = eval_atom(context, first_child)
for trailer in element.children[1:]:
if trailer == '**': # has a power operation.
right = evaluator.eval_element(context, element.children[2])
context_set = _eval_comparison(
evaluator,
context,
context_set,
trailer,
right
)
break
context_set = eval_trailer(context, context_set, trailer)
return context_set
return NO_CONTEXTS
children = element.children[1:]
had_await = False
if first_child.type == 'keyword' and first_child.value == 'await':
had_await = True
first_child = children.pop(0)
context_set = eval_atom(context, first_child)
for trailer in children:
if trailer == '**': # has a power operation.
right = context.eval_node(children[1])
context_set = _eval_comparison(
evaluator,
context,
context_set,
trailer,
right
)
break
context_set = eval_trailer(context, context_set, trailer)
if had_await:
await_context_set = context_set.py__getattribute__(u"__await__")
if not await_context_set:
debug.warning('Tried to run py__await__ on context %s', context)
context_set = ContextSet()
return _py__stop_iteration_returns(await_context_set.execute_evaluated())
return context_set
elif typ in ('testlist_star_expr', 'testlist',):
# The implicit tuple in statements.
return ContextSet(iterable.SequenceLiteralContext(evaluator, context, element))
@@ -100,8 +119,10 @@ def eval_node(context, element):
# Must be an ellipsis, other operators are not evaluated.
# In Python 2 ellipsis is coded as three single dot tokens, not
# as one token 3 dot token.
assert element.value in ('.', '...')
return ContextSet(compiled.create(evaluator, Ellipsis))
if element.value not in ('.', '...'):
origin = element.parent
raise AssertionError("unhandled operator %s in %s " % (repr(element.value), origin))
return ContextSet(compiled.builtin_from_name(evaluator, u'Ellipsis'))
elif typ == 'dotted_name':
context_set = eval_atom(context, element.children[0])
for next_name in element.children[2::2]:
@@ -112,6 +133,15 @@ def eval_node(context, element):
return eval_node(context, element.children[0])
elif typ == 'annassign':
return pep0484._evaluate_for_annotation(context, element.children[1])
elif typ == 'yield_expr':
if len(element.children) and element.children[1].type == 'yield_arg':
# Implies that it's a yield from.
element = element.children[1].children[1]
generators = context.eval_node(element)
return _py__stop_iteration_returns(generators)
# Generator.send() is not implemented.
return NO_CONTEXTS
else:
return eval_or_test(context, element)
@@ -119,7 +149,7 @@ def eval_node(context, element):
def eval_trailer(context, base_contexts, trailer):
trailer_op, node = trailer.children[:2]
if node == ')': # `arglist` is optional.
node = ()
node = None
if trailer_op == '[':
trailer_op, node, _ = trailer.children
@@ -148,7 +178,7 @@ def eval_trailer(context, base_contexts, trailer):
name_or_str=node
)
else:
assert trailer_op == '('
assert trailer_op == '(', 'trailer_op is actually %s' % trailer_op
args = arguments.TreeArguments(context.evaluator, context, node, trailer)
return base_contexts.execute(args)
@@ -171,21 +201,33 @@ def eval_atom(context, atom):
position=stmt.start_pos,
search_global=True
)
elif atom.type == 'keyword':
# For False/True/None
if atom.value in ('False', 'True', 'None'):
return ContextSet(compiled.builtin_from_name(context.evaluator, atom.value))
elif atom.value == 'print':
# print e.g. could be evaluated like this in Python 2.7
return NO_CONTEXTS
elif atom.value == 'yield':
# Contrary to yield from, yield can just appear alone to return a
# value when used with `.send()`.
return NO_CONTEXTS
assert False, 'Cannot evaluate the keyword %s' % atom
elif isinstance(atom, tree.Literal):
string = parser_utils.safe_literal_eval(atom.value)
return ContextSet(compiled.create(context.evaluator, string))
string = context.evaluator.compiled_subprocess.safe_literal_eval(atom.value)
return ContextSet(compiled.create_simple_object(context.evaluator, string))
elif atom.type == 'strings':
# Will be multiple string.
context_set = eval_atom(context, atom.children[0])
for string in atom.children[1:]:
right = eval_atom(context, string)
context_set = _eval_comparison(context.evaluator, context, context_set, u'+', right)
return context_set
else:
c = atom.children
if c[0].type == 'string':
# Will be one string.
context_set = eval_atom(context, c[0])
for string in c[1:]:
right = eval_atom(context, string)
context_set = _eval_comparison(context.evaluator, context, context_set, '+', right)
return context_set
# Parentheses without commas are not tuples.
elif c[0] == '(' and not len(c) == 2 \
if c[0] == '(' and not len(c) == 2 \
and not(c[1].type == 'testlist_comp' and
len(c[1].children) > 1):
return context.eval_node(c[1])
@@ -203,7 +245,9 @@ def eval_atom(context, atom):
pass
if comp_for.type == 'comp_for':
return ContextSet(iterable.Comprehension.from_atom(context.evaluator, context, atom))
return ContextSet(iterable.comprehension_from_atom(
context.evaluator, context, atom
))
# It's a dict/list/tuple literal.
array_node = c[1]
@@ -211,7 +255,8 @@ def eval_atom(context, atom):
array_node_c = array_node.children
except AttributeError:
array_node_c = []
if c[0] == '{' and (array_node == '}' or ':' in array_node_c):
if c[0] == '{' and (array_node == '}' or ':' in array_node_c or
'**' in array_node_c):
context = iterable.DictLiteralContext(context.evaluator, context, atom)
else:
context = iterable.SequenceLiteralContext(context.evaluator, context, atom)
@@ -221,7 +266,21 @@ def eval_atom(context, atom):
@_limit_context_infers
def eval_expr_stmt(context, stmt, seek_name=None):
with recursion.execution_allowed(context.evaluator, stmt) as allowed:
if allowed or context.get_root_context() == context.evaluator.BUILTINS:
# Here we allow list/set to recurse under certain conditions. To make
# it possible to resolve stuff like list(set(list(x))), this is
# necessary.
if not allowed and context.get_root_context() == context.evaluator.builtins_module:
try:
instance = context.var_args.instance
except AttributeError:
pass
else:
if instance.name.string_name in ('list', 'set'):
c = instance.get_first_non_keyword_argument_contexts()
if instance not in c:
allowed = True
if allowed:
return _eval_expr_stmt(context, stmt, seek_name)
return NO_CONTEXTS
@@ -286,16 +345,16 @@ def eval_or_test(context, or_test):
# handle lazy evaluation of and/or here.
if operator in ('and', 'or'):
left_bools = set(left.py__bool__() for left in types)
if left_bools == set([True]):
if left_bools == {True}:
if operator == 'and':
types = context.eval_node(right)
elif left_bools == set([False]):
elif left_bools == {False}:
if operator != 'and':
types = context.eval_node(right)
# Otherwise continue, because of uncertainty.
else:
types = _eval_comparison(context.evaluator, context, types, operator,
context.eval_node(right))
context.eval_node(right))
debug.dbg('eval_or_test types %s', types)
return types
@@ -308,29 +367,16 @@ def eval_factor(context_set, operator):
for context in context_set:
if operator == '-':
if is_number(context):
yield compiled.create(context.evaluator, -context.obj)
yield context.negate()
elif operator == 'not':
value = context.py__bool__()
if value is None: # Uncertainty.
return
yield compiled.create(context.evaluator, not value)
yield compiled.create_simple_object(context.evaluator, not value)
else:
yield context
# Maps Python syntax to the operator module.
COMPARISON_OPERATORS = {
'==': op.eq,
'!=': op.ne,
'is': op.is_,
'is not': op.is_not,
'<': op.lt,
'<=': op.le,
'>': op.gt,
'>=': op.ge,
}
def _literals_to_types(evaluator, result):
# Changes literals ('a', 1, 1.0, etc) to its type instances (str(),
# int(), float(), etc).
@@ -366,49 +412,59 @@ def _eval_comparison(evaluator, context, left_contexts, operator, right_contexts
def _is_tuple(context):
return isinstance(context, iterable.AbstractIterable) and context.array_type == 'tuple'
return isinstance(context, iterable.Sequence) and context.array_type == 'tuple'
def _is_list(context):
return isinstance(context, iterable.AbstractIterable) and context.array_type == 'list'
return isinstance(context, iterable.Sequence) and context.array_type == 'list'
def _bool_to_context(evaluator, bool_):
return compiled.builtin_from_name(evaluator, force_unicode(str(bool_)))
def _eval_comparison_part(evaluator, context, left, operator, right):
l_is_num = is_number(left)
r_is_num = is_number(right)
if operator == '*':
if isinstance(operator, unicode):
str_operator = operator
else:
str_operator = force_unicode(str(operator.value))
if str_operator == '*':
# for iterables, ignore * operations
if isinstance(left, iterable.AbstractIterable) or is_string(left):
if isinstance(left, iterable.Sequence) or is_string(left):
return ContextSet(left)
elif isinstance(right, iterable.AbstractIterable) or is_string(right):
elif isinstance(right, iterable.Sequence) or is_string(right):
return ContextSet(right)
elif operator == '+':
elif str_operator == '+':
if l_is_num and r_is_num or is_string(left) and is_string(right):
return ContextSet(compiled.create(evaluator, left.obj + right.obj))
return ContextSet(left.execute_operation(right, str_operator))
elif _is_tuple(left) and _is_tuple(right) or _is_list(left) and _is_list(right):
return ContextSet(iterable.MergedArray(evaluator, (left, right)))
elif operator == '-':
elif str_operator == '-':
if l_is_num and r_is_num:
return ContextSet(compiled.create(evaluator, left.obj - right.obj))
elif operator == '%':
return ContextSet(left.execute_operation(right, str_operator))
elif str_operator == '%':
# With strings and numbers the left type typically remains. Except for
# `int() % float()`.
return ContextSet(left)
elif operator in COMPARISON_OPERATORS:
operation = COMPARISON_OPERATORS[operator]
elif str_operator in COMPARISON_OPERATORS:
if is_compiled(left) and is_compiled(right):
# Possible, because the return is not an option. Just compare.
left = left.obj
right = right.obj
try:
result = operation(left, right)
except TypeError:
# Could be True or False.
return ContextSet(compiled.create(evaluator, True), compiled.create(evaluator, False))
try:
return ContextSet(left.execute_operation(right, str_operator))
except TypeError:
# Could be True or False.
pass
else:
return ContextSet(compiled.create(evaluator, result))
elif operator == 'in':
if str_operator in ('is', '!=', '==', 'is not'):
operation = COMPARISON_OPERATORS[str_operator]
bool_ = operation(left, right)
return ContextSet(_bool_to_context(evaluator, bool_))
return ContextSet(_bool_to_context(evaluator, True), _bool_to_context(evaluator, False))
elif str_operator == 'in':
return NO_CONTEXTS
def check(obj):
@@ -417,7 +473,7 @@ def _eval_comparison_part(evaluator, context, left, operator, right):
obj.name.string_name in ('int', 'float')
# Static analysis, one is a number, the other one is not.
if operator in ('+', '-') and l_is_num != r_is_num \
if str_operator in ('+', '-') and l_is_num != r_is_num \
and not (check(left) or check(right)):
message = "TypeError: unsupported operand type(s) for +: %s and %s"
analysis.add(context, 'type-error-operation', operator,
@@ -442,6 +498,22 @@ def _remove_statements(evaluator, context, stmt, name):
def tree_name_to_contexts(evaluator, context, tree_name):
context_set = ContextSet()
module_node = context.get_root_context().tree_node
if module_node is not None:
names = module_node.get_used_names().get(tree_name.value, [])
for name in names:
expr_stmt = name.parent
correct_scope = parser_utils.get_parent_scope(name) == context.tree_node
if expr_stmt.type == "expr_stmt" and expr_stmt.children[1].type == "annassign" and correct_scope:
context_set |= _evaluate_for_annotation(context, expr_stmt.children[1].children[1])
if context_set:
return context_set
types = []
node = tree_name.get_definition(import_name_always=True)
if node is None:
@@ -455,7 +527,7 @@ def tree_name_to_contexts(evaluator, context, tree_name):
filters = [next(filters)]
return finder.find(filters, attribute_lookup=False)
elif node.type not in ('import_from', 'import_name'):
raise ValueError("Should not happen.")
raise ValueError("Should not happen. type: %s", node.type)
typ = node.type
if typ == 'for_stmt':
@@ -472,14 +544,18 @@ def tree_name_to_contexts(evaluator, context, tree_name):
types = context.predefined_names[node][tree_name.value]
except KeyError:
cn = ContextualizedNode(context, node.children[3])
for_types = iterate_contexts(cn.infer(), cn)
for_types = iterate_contexts(
cn.infer(),
contextualized_node=cn,
is_async=node.parent.type == 'async_stmt',
)
c_node = ContextualizedName(context, tree_name)
types = check_tuple_assignments(evaluator, c_node, for_types)
elif typ == 'expr_stmt':
types = _remove_statements(evaluator, context, node, tree_name)
elif typ == 'with_stmt':
context_managers = context.eval_node(node.get_test_node_from_name(tree_name))
enter_methods = context_managers.py__getattribute__('__enter__')
enter_methods = context_managers.py__getattribute__(u'__enter__')
return enter_methods.execute_evaluated()
elif typ in ('import_from', 'import_name'):
types = imports.infer_import(context, tree_name)
@@ -492,7 +568,7 @@ def tree_name_to_contexts(evaluator, context, tree_name):
exceptions = context.eval_node(tree_name.get_previous_sibling().get_previous_sibling())
types = exceptions.execute_evaluated()
else:
raise ValueError("Should not happen.")
raise ValueError("Should not happen. type: %s" % typ)
return types
@@ -505,14 +581,10 @@ def _apply_decorators(context, node):
decoratee_context = ClassContext(
context.evaluator,
parent_context=context,
classdef=node
tree_node=node
)
else:
decoratee_context = FunctionContext(
context.evaluator,
parent_context=context,
funcdef=node
)
decoratee_context = FunctionContext.from_context(context, node)
initial = values = ContextSet(decoratee_context)
for dec in reversed(node.get_decorators()):
debug.dbg('decorator: %s %s', dec, values)
@@ -583,6 +655,8 @@ def eval_subscript_list(evaluator, context, index):
result += [None] * (3 - len(result))
return ContextSet(iterable.Slice(context, *result))
elif index.type == 'subscriptlist':
return NO_CONTEXTS
# No slices
return context.eval_node(index)

View File

@@ -1,85 +1,27 @@
import glob
import os
import sys
import imp
from jedi.evaluate.site import addsitedir
from jedi._compatibility import unicode
from jedi._compatibility import unicode, force_unicode, all_suffixes
from jedi.evaluate.cache import evaluator_method_cache
from jedi.evaluate.base_context import ContextualizedNode
from jedi.evaluate.helpers import is_string
from jedi.common.utils import traverse_parents
from jedi.parser_utils import get_cached_code_lines
from jedi import settings
from jedi import debug
from jedi.evaluate.utils import ignored
def get_venv_path(venv):
"""Get sys.path for specified virtual environment."""
sys_path = _get_venv_path_dirs(venv)
with ignored(ValueError):
sys_path.remove('')
sys_path = _get_sys_path_with_egglinks(sys_path)
# As of now, get_venv_path_dirs does not scan built-in pythonpath and
# user-local site-packages, let's approximate them using path from Jedi
# interpreter.
return sys_path + sys.path
def _get_sys_path_with_egglinks(sys_path):
"""Find all paths including those referenced by egg-links.
Egg-link-referenced directories are inserted into path immediately before
the directory on which their links were found. Such directories are not
taken into consideration by normal import mechanism, but they are traversed
when doing pkg_resources.require.
"""
result = []
for p in sys_path:
# pkg_resources does not define a specific order for egg-link files
# using os.listdir to enumerate them, we're sorting them to have
# reproducible tests.
for egg_link in sorted(glob.glob(os.path.join(p, '*.egg-link'))):
with open(egg_link) as fd:
for line in fd:
line = line.strip()
if line:
result.append(os.path.join(p, line))
# pkg_resources package only interprets the first
# non-empty line in egg-link files.
break
result.append(p)
return result
def _get_venv_path_dirs(venv):
"""Get sys.path for venv without starting up the interpreter."""
venv = os.path.abspath(venv)
sitedir = _get_venv_sitepackages(venv)
sys_path = []
addsitedir(sys_path, sitedir)
return sys_path
def _get_venv_sitepackages(venv):
if os.name == 'nt':
p = os.path.join(venv, 'lib', 'site-packages')
else:
p = os.path.join(venv, 'lib', 'python%d.%d' % sys.version_info[:2],
'site-packages')
return p
def _abs_path(module_context, path):
module_path = module_context.py__file__()
if os.path.isabs(path):
return path
module_path = module_context.py__file__()
if module_path is None:
# In this case we have no idea where we actually are in the file
# system.
return None
base_dir = os.path.dirname(module_path)
path = force_unicode(path)
return os.path.abspath(os.path.join(base_dir, path))
@@ -87,7 +29,7 @@ def _paths_from_assignment(module_context, expr_stmt):
"""
Extracts the assigned strings from an assignment that looks as follows::
>>> sys.path[0:0] = ['module/path', 'another/module/path']
sys.path[0:0] = ['module/path', 'another/module/path']
This function is in general pretty tolerant (and therefore 'buggy').
However, it's not a big issue usually to add more paths to Jedi's sys_path,
@@ -121,7 +63,7 @@ def _paths_from_assignment(module_context, expr_stmt):
for lazy_context in cn.infer().iterate(cn):
for context in lazy_context.infer():
if is_string(context):
abs_path = _abs_path(module_context, context.obj)
abs_path = _abs_path(module_context, context.get_safe_value())
if abs_path is not None:
yield abs_path
@@ -144,7 +86,7 @@ def _paths_from_list_modifications(module_context, trailer1, trailer2):
for context in module_context.create_context(arg).eval_node(arg):
if is_string(context):
abs_path = _abs_path(module_context, context.obj)
abs_path = _abs_path(module_context, context.get_safe_value())
if abs_path is not None:
yield abs_path
@@ -187,24 +129,19 @@ def check_sys_path_modifications(module_context):
return added
def sys_path_with_modifications(evaluator, module_context):
return evaluator.project.sys_path + check_sys_path_modifications(module_context)
def detect_additional_paths(evaluator, script_path):
django_paths = _detect_django_path(script_path)
def discover_buildout_paths(evaluator, script_path):
buildout_script_paths = set()
for buildout_script_path in _get_buildout_script_paths(script_path):
for path in _get_paths_from_buildout_script(evaluator, buildout_script_path):
buildout_script_paths.add(path)
return django_paths + list(buildout_script_paths)
return buildout_script_paths
def _get_paths_from_buildout_script(evaluator, buildout_script_path):
try:
module_node = evaluator.grammar.parse(
module_node = evaluator.parse(
path=buildout_script_path,
cache=True,
cache_path=settings.cache_directory
@@ -214,20 +151,14 @@ def _get_paths_from_buildout_script(evaluator, buildout_script_path):
return
from jedi.evaluate.context import ModuleContext
module = ModuleContext(evaluator, module_node, buildout_script_path)
module = ModuleContext(
evaluator, module_node, buildout_script_path,
code_lines=get_cached_code_lines(evaluator.grammar, buildout_script_path),
)
for path in check_sys_path_modifications(module):
yield path
def traverse_parents(path):
while True:
new = os.path.dirname(path)
if new == path:
return
path = new
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)):
@@ -235,55 +166,42 @@ def _get_parent_dir_with_file(path, filename):
return None
def _detect_django_path(module_path):
""" Detects the path of the very well known Django library (if used) """
result = []
for parent in traverse_parents(module_path):
with ignored(IOError):
with open(parent + os.path.sep + 'manage.py'):
debug.dbg('Found django path: %s', module_path)
result.append(parent)
return result
def _get_buildout_script_paths(module_path):
def _get_buildout_script_paths(search_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
directory that look like python files.
:param module_path: absolute path to the module.
:type module_path: str
:param search_path: absolute path to the module.
:type search_path: str
"""
project_root = _get_parent_dir_with_file(module_path, 'buildout.cfg')
project_root = _get_parent_dir_with_file(search_path, 'buildout.cfg')
if not project_root:
return []
return
bin_path = os.path.join(project_root, 'bin')
if not os.path.exists(bin_path):
return []
extra_module_paths = []
return
for filename in os.listdir(bin_path):
try:
filepath = os.path.join(bin_path, filename)
with open(filepath, 'r') as f:
firstline = f.readline()
if firstline.startswith('#!') and 'python' in firstline:
extra_module_paths.append(filepath)
yield filepath
except (UnicodeDecodeError, IOError) as e:
# Probably a binary file; permission error or race cond. because file got deleted
# ignore
# Probably a binary file; permission error or race cond. because
# file got deleted. Ignore it.
debug.warning(unicode(e))
continue
return extra_module_paths
def dotted_path_in_sys_path(sys_path, module_path):
"""
Returns the dotted path inside a sys.path.
Returns the dotted path inside a sys.path as a list of names.
"""
# First remove the suffix.
for suffix, _, _ in imp.get_suffixes():
for suffix in all_suffixes():
if module_path.endswith(suffix):
module_path = module_path[:-len(suffix)]
break
@@ -303,6 +221,6 @@ def dotted_path_in_sys_path(sys_path, module_path):
for string in split:
if not string or '.' in string:
return None
return '.'.join(split)
return split
return None

View File

@@ -2,10 +2,19 @@
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:
_sep += os.path.altsep
_path_re = re.compile(r'(?:\.[^{0}]+|[{0}]__init__\.py)$'.format(re.escape(_sep)))
del _sep
def to_list(func):
def wrapper(*args, **kwargs):
return list(func(*args, **kwargs))

View File

@@ -1,14 +1,16 @@
import re
import textwrap
from inspect import cleandoc
from jedi._compatibility import literal_eval, is_py3
from parso.python import tree
from parso.cache import parser_cache
_EXECUTE_NODES = set([
'funcdef', 'classdef', 'import_from', 'import_name', 'test', 'or_test',
'and_test', 'not_test', 'comparison', 'expr', 'xor_expr', 'and_expr',
'shift_expr', 'arith_expr', 'atom_expr', 'term', 'factor', 'power', 'atom'
])
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',
'atom_expr', 'term', 'factor', 'power', 'atom'}
_FLOW_KEYWORDS = (
'try', 'except', 'finally', 'else', 'if', 'elif', 'with', 'for', 'while'
@@ -87,10 +89,12 @@ def get_flow_branch_keyword(flow_node, node):
keyword = first_leaf
return 0
def get_statement_of_position(node, pos):
for c in node.children:
if c.start_pos <= pos <= c.end_pos:
if c.type not in ('decorated', 'simple_stmt', 'suite') \
if c.type not in ('decorated', 'simple_stmt', 'suite',
'async_stmt', 'async_funcdef') \
and not isinstance(c, (tree.Flow, tree.ClassOrFunc)):
return c
else:
@@ -112,10 +116,7 @@ def clean_scope_docstring(scope_node):
cleaned = cleandoc(safe_literal_eval(node.value))
# Since we want the docstr output to be always unicode, just
# force it.
if is_py3 or isinstance(cleaned, unicode):
return cleaned
else:
return unicode(cleaned, 'UTF-8', 'replace')
return force_unicode(cleaned)
return ''
@@ -158,7 +159,12 @@ def get_call_signature(funcdef, width=72, call_string=None):
p = '(' + ''.join(param.get_code() for param in funcdef.get_params()).strip() + ')'
else:
p = funcdef.children[2].get_code()
code = call_string + p
p = re.sub(r'\s+', ' ', p)
if funcdef.annotation:
rtype = " ->" + funcdef.annotation.get_code()
else:
rtype = ""
code = call_string + p + rtype
return '\n'.join(textwrap.wrap(code, width))
@@ -179,6 +185,8 @@ def get_doc_with_call_signature(scope_node):
doc = clean_scope_docstring(scope_node)
if call_signature is None:
return doc
if not doc:
return call_signature
return '%s\n\n%s' % (call_signature, doc)
@@ -205,6 +213,9 @@ def get_following_comment_same_line(node):
whitespace = node.children[5].get_first_leaf().prefix
elif node.type == 'with_stmt':
whitespace = node.children[3].get_first_leaf().prefix
elif node.type == 'funcdef':
# actually on the next line
whitespace = node.children[4].get_first_leaf().get_next_leaf().prefix
else:
whitespace = node.get_last_leaf().get_next_leaf().prefix
except AttributeError:
@@ -239,3 +250,11 @@ def get_parent_scope(node, include_flows=False):
break
scope = scope.parent
return scope
def get_cached_code_lines(grammar, path):
"""
Basically access the cached code lines in parso. This is not the nicest way
to do this, but we avoid splitting all the lines again.
"""
return parser_cache[grammar._hashed][path].lines

View File

@@ -1,11 +1,14 @@
"""
THIS is not in active development, please check
https://github.com/davidhalter/jedi/issues/667 first before editing.
Introduce some basic refactoring functions to |jedi|. This module is still in a
very early development stage and needs much testing and improvement.
.. warning:: I won't do too much here, but if anyone wants to step in, please
do. Refactoring is none of my priorities
It uses the |jedi| `API <plugin-api.html>`_ and supports currently the
It uses the |jedi| `API <api.html>`_ and supports currently the
following functions (sometimes bug-prone):
- rename
@@ -50,9 +53,8 @@ class Refactoring(object):
def rename(script, new_name):
""" The `args` / `kwargs` params are the same as in `api.Script`.
:param operation: The refactoring operation to execute.
:type operation: str
:type source: str
:param new_name: The new name of the script.
:param script: The source Script object.
:return: list of changed lines/changed files
"""
return Refactoring(_rename(script.usages(), new_name))
@@ -105,11 +107,12 @@ def extract(script, new_name):
user_stmt = script._parser.user_stmt()
# TODO care for multiline extracts
# TODO care for multi-line extracts
dct = {}
if user_stmt:
pos = script._pos
line_index = pos[0] - 1
# Be careful here. 'array_for_pos' does not exist in 'helpers'.
arr, index = helpers.array_for_pos(user_stmt, pos)
if arr is not None:
start_pos = arr[index].start_pos
@@ -120,7 +123,7 @@ def extract(script, new_name):
start_line = new_lines[start_pos[0] - 1]
text = start_line[start_pos[1]:e]
for l in range(start_pos[0], end_pos[0] - 1):
text += '\n' + l
text += '\n' + str(l)
if e is None:
end_line = new_lines[end_pos[0] - 1]
text += '\n' + end_line[:end_pos[1]]
@@ -140,7 +143,7 @@ def extract(script, new_name):
new_lines[start_pos[0] - 1] = start_line
new_lines[start_pos[0]:end_pos[0] - 1] = []
# add parentheses in multiline case
# add parentheses in multi-line case
open_brackets = ['(', '[', '{']
close_brackets = [')', ']', '}']
if '\n' in text and not (text[0] in open_brackets and text[-1] ==
@@ -172,7 +175,7 @@ def inline(script):
inlines = sorted(inlines, key=lambda x: (x.module_path, x.line, x.column),
reverse=True)
expression_list = stmt.expression_list()
# don't allow multiline refactorings for now.
# don't allow multi-line refactorings for now.
assert stmt.start_pos[0] == stmt.end_pos[0]
index = stmt.start_pos[0] - 1

View File

@@ -144,7 +144,8 @@ Check for `isinstance` and other information to infer a type.
"""
auto_import_modules = [
'hashlib', # setattr
'hashlib', # hashlib is mostly using setattr, which jedi doesn't understand
'gi', # This third-party repository (GTK stuff) doesn't really work with jedi
]
"""
Modules that are not analyzed but imported, although they contain Python code.

View File

@@ -89,12 +89,13 @@ def setup_readline(namespace_module=__main__):
lines = split_lines(text)
position = (len(lines), len(lines[-1]))
name = get_on_completion_name(
interpreter._get_module_node(),
interpreter._module_node,
lines,
position
)
before = text[:len(text) - len(name)]
completions = interpreter.completions()
logging.debug("REPL completions: %s", completions)
except:
logging.error("REPL Completion error:\n" + traceback.format_exc())
raise
@@ -108,6 +109,11 @@ def setup_readline(namespace_module=__main__):
return None
try:
# Need to import this one as well to make sure it's executed before
# this code. This didn't use to be an issue until 3.3. Starting with
# 3.4 this is different, it always overwrites the completer if it's not
# already imported here.
import rlcompleter # noqa: F401
import readline
except ImportError:
print("Jedi: Module readline not available.")
@@ -132,5 +138,5 @@ def version_info():
"""
Version = namedtuple('Version', 'major, minor, micro')
from jedi import __version__
tupl = re.findall('[a-z]+|\d+', __version__)
tupl = re.findall(r'[a-z]+|\d+', __version__)
return Version(*[x if i == 3 else int(x) for i, x in enumerate(tupl)])

View File

@@ -2,7 +2,9 @@
addopts = --doctest-modules
# Ignore broken files in blackbox test directories
norecursedirs = .* docs completion refactor absolute_import namespace_package scripts extensions speed static_analysis not_in_sys_path buildout_project sample_venvs init_extension_module simple_import
norecursedirs = .* docs completion refactor absolute_import namespace_package
scripts extensions speed static_analysis not_in_sys_path
sample_venvs init_extension_module simple_import
# Activate `clean_jedi_cache` fixture for all tests. This should be
# fine as long as we are using `clean_jedi_cache` as a session scoped

View File

@@ -1 +1 @@
parso==0.1.1
parso>=0.3.0

View File

@@ -8,7 +8,7 @@ like a typical big Python modules. A mix between a lot of different Python
things.
You can view a markup version of it here:
http://svn.wxwidgets.org/viewvc/wx/wxPython/trunk/src/gtk/_core.py?view=markup
https://github.com/wxWidgets/wxPython/blob/master/src/gtk/_core.py
"""
import resource
@@ -30,11 +30,12 @@ import jedi
def process_memory():
"""
In kB according to
http://stackoverflow.com/questions/938733/total-memory-used-by-python-process
https://stackoverflow.com/questions/938733/total-memory-used-by-python-process
"""
return resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
uri = 'http://svn.wxwidgets.org/viewvc/wx/wxPython/trunk/src/gtk/_core.py?revision=74740&content-type=text%2Fplain&view=co'
uri = 'https://raw.githubusercontent.com/wxWidgets/wxPython/master/src/gtk/_core.py'
wx_core = urllib2.urlopen(uri).read()

View File

@@ -1,2 +1,8 @@
[bdist_wheel]
universal=1
[flake8]
max-line-length = 100
ignore =
# do not use bare 'except'
E722,

View File

@@ -3,7 +3,6 @@
from setuptools import setup, find_packages
import ast
import sys
__AUTHOR__ = 'David Halter'
__AUTHOR_EMAIL__ = 'davidhalter88@gmail.com'
@@ -11,10 +10,7 @@ __AUTHOR_EMAIL__ = 'davidhalter88@gmail.com'
# Get the version from within jedi. It's defined in exactly one place now.
with open('jedi/__init__.py') as f:
tree = ast.parse(f.read())
if sys.version_info > (3, 7):
version = tree.body[0].value.s
else:
version = tree.body[1].value.s
version = tree.body[1].value.s
readme = open('README.rst').read() + '\n\n' + open('CHANGELOG.rst').read()
with open('requirements.txt') as f:
@@ -32,9 +28,18 @@ setup(name='jedi',
license='MIT',
keywords='python completion refactoring vim',
long_description=readme,
packages=find_packages(exclude=['test']),
packages=find_packages(exclude=['test', 'test.*']),
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*',
install_requires=install_requires,
extras_require={'dev': ['docopt']},
extras_require={
'testing': [
'pytest>=3.1.0',
# docopt for sith doctests
'docopt',
# coloroma for colored debug output
'colorama',
],
},
package_data={'jedi': ['evaluate/compiled/fake/*.pym']},
platforms=['any'],
classifiers=[
@@ -44,10 +49,8 @@ setup(name='jedi',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',

View File

@@ -127,8 +127,6 @@ class TestCase(object):
print("{path}: Line {line} column {column}".format(**self.__dict__))
self.show_location(self.line, self.column)
self.show_operation()
except jedi.NotFoundError:
pass
except Exception:
self.traceback = traceback.format_exc()
if record is not None:

View File

@@ -1,6 +1,4 @@
# todo probably remove test_integration_keyword
def test_keyword_doc():
def test_keyword_doc(Script):
r = list(Script("or", 1, 1).goto_definitions())
assert len(r) == 1
assert len(r[0].doc) > 100
@@ -14,17 +12,18 @@ def test_keyword_doc():
assert k.doc.startswith(imp_start)
def test_blablabla():
def test_blablabla(Script):
defs = Script("import").goto_definitions()
assert len(defs) == 1 and [1 for d in defs if d.doc]
# unrelated to #44
def test_operator_doc(self):
def test_operator_doc(Script):
r = list(Script("a == b", 1, 3).goto_definitions())
assert len(r) == 1
assert len(r[0].doc) > 100
def test_lambda():
def test_lambda(Script):
defs = Script('lambda x: x', column=0).goto_definitions()
assert [d.type for d in defs] == ['keyword']

View File

@@ -1,3 +1,8 @@
""" needed for some modules to test against packages. """
some_variable = 1
from . import imports
#? int()
imports.relative()

View File

@@ -29,6 +29,9 @@ b = [6,7]
#? int()
b[8-7]
# Something unreasonable:
#?
b['']
# -----------------
# Slices
@@ -42,6 +45,8 @@ b[int():]
#? list()
b[:]
#?
b[:, 1]
class _StrangeSlice():
def __getitem__(self, sliced):
@@ -204,6 +209,9 @@ g
dic2 = {'asdf': 3, 'b': 'str'}
#? int()
dic2['asdf']
# TODO for now get doesn't work properly when used with a literal.
#? None
dic2.get('asdf')
# string literal
#? int()
@@ -256,6 +264,17 @@ for x in {1: 3.0, '': 1j}:
#? int() str()
x
#? ['__iter__']
dict().values().__iter__
d = dict(a=3, b='')
#? int() str()
d.values()[0]
#? int()
d['a']
#? int() None
d.get('a')
# -----------------
# with variable as index
# -----------------
@@ -387,6 +406,18 @@ for x in [1] + ['']:
#? int() str()
x
# -----------------
# Potential Recursion Issues
# -----------------
class X():
def y(self):
self.a = [1]
def x(self):
self.a = list(self.a)
#? int()
self.a[0]
# -----------------
# For loops with attribute assignment.
# -----------------
@@ -403,12 +434,10 @@ def test_func():
x
# python >= 2.7
# Set literals are not valid in 2.6.
#? int()
tuple({1})[0]
# python >= 3.3
# python >= 3.4
# -----------------
# PEP 3132 Extended Iterable Unpacking (star unpacking)
# -----------------

View File

@@ -5,19 +5,28 @@ Currently we're not supporting completion of them, but they should at least not
raise errors or return extremely strange results.
"""
async def x():
argh = await x()
#?
argh
return 2
# python >= 3.5
#? int()
x()
async def x():
return 1
#? []
x.cr_awai
#? ['cr_await']
x().cr_awai
a = await x()
#?
#? int()
a
async def y():
argh = await x()
#? int()
argh
#? ['__next__']
x().__await__().__next
return 2
async def x2():
async with open('asdf') as f:
@@ -34,3 +43,42 @@ await A.b()
#! 11 ['param d=2']
await A.b(d=3)
class Awaitable:
def __await__(self):
yield None
return ''
async def awaitable_test():
foo = await Awaitable()
#? str()
foo
# python >= 3.6
async def asgen():
yield 1
await asyncio.sleep(0)
yield 2
async def wrapper():
#? int()
[x async for x in asgen()][0]
async for y in asgen():
#? int()
y
#? ['__anext__']
asgen().__ane
#? []
asgen().mro
# Normal completion (#1092)
normal_var1 = 42
async def foo():
normal_var2 = False
#? ['normal_var1', 'normal_var2']
normal_var

View File

@@ -18,6 +18,13 @@ int(str)
str..
#? []
a(0):.
#? 2 ['and', 'or', 'if', 'is', 'in', 'not']
0x0
#? ['and', 'or', 'if', 'is', 'in', 'not']
1j
x = None()
#?
x
# -----------------
# if/else/elif
@@ -147,6 +154,9 @@ def global_define():
#? int()
global_var_in_func
#? ['global_var_in_func']
global_var_in_f
def funct1():
# From issue #610
@@ -158,6 +168,17 @@ def funct2():
global_dict_var
global_var_predefined = None
def init_global_var_predefined():
global global_var_predefined
if global_var_predefined is None:
global_var_predefined = 3
#? int() None
global_var_predefined
# -----------------
# within docstrs
# -----------------
@@ -286,8 +307,6 @@ with open('') as f:
#? str()
line
# Nested with statements don't exist in Python 2.6.
# python >= 2.7
with open('') as f1, open('') as f2:
#? ['closed']
f1.closed

View File

@@ -215,6 +215,32 @@ class Dude(classgetter()):
#? ['shout']
self.s
# -----------------
# multiple inheritance # 1071
# -----------------
class FactorMixin(object):
FACTOR_1 = 0.1
class Calc(object):
def sum(self, a, b):
self.xxx = 3
return a + b
class BetterCalc(Calc, FactorMixin):
def multiply_factor(self, a):
return a * self.FACTOR_1
calc = BetterCalc()
#? ['sum']
calc.sum
#? ['multiply_factor']
calc.multip
#? ['FACTOR_1']
calc.FACTOR_1
#? ['xxx']
calc.xxx
# -----------------
# __call__
# -----------------
@@ -391,6 +417,9 @@ class PrivateVar():
def __private_func(self):
return 1
#? int()
__private_func()
def wrap_private(self):
return self.__private_func()
#? []
@@ -399,6 +428,8 @@ PrivateVar().__var
PrivateVar().__var
#? []
PrivateVar().__private_func
#? []
PrivateVar.__private_func
#? int()
PrivateVar().wrap_private()
@@ -422,17 +453,18 @@ class Super(object):
a = 3
def return_sup(self):
return 1
SuperCopy = Super
class TestSuper(Super):
#?
super()
def test(self):
#? Super()
#? SuperCopy()
super()
#? ['a']
super().a
if 1:
#? Super()
#? SuperCopy()
super()
def a():
#?
@@ -446,6 +478,17 @@ class TestSuper(Super):
TestSuper().return_sup()
Super = 3
class Foo():
def foo(self):
return 1
# Somehow overwriting the same name caused problems (#1044)
class Foo(Foo):
def foo(self):
#? int()
super().foo()
# -----------------
# if flow at class level
# -----------------

View File

@@ -52,12 +52,12 @@ left
[a for a in {1:'x'}][0]
# list comprehensions should also work in combination with functions
def listen(arg):
def _listen(arg):
for x in arg:
#? str()
x
listen(['' for x in [1]])
_listen(['' for x in [1]])
#?
([str for x in []])[0]
@@ -210,6 +210,16 @@ d[2]
next(iter({a for a in range(10)}))
# with a set literal (also doesn't work in 2.6).
#? int()
[a for a in {1, 2, 3}][0]
# -----------------
# syntax errors
# -----------------
# Issue #1146
#? ['list']
[int(str(x.value) for x in list
def reset_missing_bracket(): pass

View File

@@ -211,6 +211,17 @@ class X():
#?
self.x()
def decorator_var_args(function, *args):
return function(*args)
@decorator_var_args
def function_var_args(param):
return param
#? int()
function_var_args(1)
# -----------------
# method decorators
# -----------------

View File

@@ -30,13 +30,17 @@ def sphinxy(a, b, c, d, x):
sphinxy()
# wrong declarations
def sphinxy2(a, b, x):
def sphinxy2(a, b, x, y, z):
"""
:param a: Forgot type declaration
:type a:
:param b: Just something
:type b: ``
:param x: Just something without type
:param y: A function
:type y: def l(): pass
:param z: A keyword
:type z: return
:rtype:
"""
#?
@@ -45,6 +49,10 @@ def sphinxy2(a, b, x):
b
#?
x
#?
y
#?
z
#?
sphinxy2()

View File

@@ -288,8 +288,6 @@ third()[0]
# -----------------
# set.add
# -----------------
# Set literals are not valid in 2.6.
# python >= 2.7
st = {1.0}
for a in [1,2]:
st.add(a)

View File

@@ -132,3 +132,19 @@ def from_comprehension(foo):
[from_comprehension(1.0) for n in (1,)]
[from_comprehension(n) for n in (1,)]
# -----------------
# lambdas
# -----------------
#? int()
x_lambda = lambda x: x
x_lambda(1)
class X():
#? str()
x_method = lambda self, a: a
X().x_method('')

View File

@@ -29,6 +29,10 @@ finally:
x
x = tuple
if False:
with open("") as defined_in_false:
#? ['flush']
defined_in_false.flu
# -----------------
# Return checks

View File

@@ -0,0 +1,34 @@
# python >= 3.6
class Foo:
bar = 1
#? 10 int()
f'{Foo.bar}'
#? 10 ['bar']
f'{Foo.bar}'
#? 10 int()
Fr'{Foo.bar'
#? 10 ['bar']
Fr'{Foo.bar'
#? int()
Fr'{Foo.bar
#? ['bar']
Fr'{Foo.bar
#? ['Exception']
F"{Excepti
#? 8 Foo
Fr'a{Foo.bar'
#? str()
Fr'sasdf'
#? 7 str()
Fr'''sasdf''' + ''
#? ['upper']
f'xyz'.uppe
#? 3 []
f'f'

Some files were not shown because too many files have changed in this diff Show More