Compare commits

..
826 Commits
Author SHA1 Message Date
Dave Halter 354dab9503 Debug 2021-01-02 13:48:54 +01:00
Dave Halter ca2c732d66 PNGs are not text and should not be normalized 2021-01-02 12:27:24 +01:00
Dave Halter 2ec3d72151 Use "namespace" as a Name.type 2021-01-02 12:14:28 +01:00
Dave Halter 02d43caa5e Fix a wrong test about references 2021-01-02 01:17:38 +01:00
Dave Halter 55c7e4eb49 Stdlib modules should not be included in the get_references search, fixes davidhalter/jedi-vim#792 2021-01-02 00:58:50 +01:00
Dave Halter 7d160f96f6 Do not show signatures for properties, fixes #1695 2021-01-01 23:51:41 +01:00
Dave Halter 1ccc63e83d Make py__iter__ work as well for Interpreter 2021-01-01 17:58:31 +01:00
Dave Halter 971913be35 Make it possible to use __getitem__ in interpreter 2021-01-01 15:57:55 +01:00
Dave Halter 36ea6b3285 Change an import 2021-01-01 05:19:37 +01:00
Dave Halter 85f45771f1 Fix typing.NewType signature 2021-01-01 04:22:52 +01:00
Dave Halter 30e702de11 Generics don't have signatures 2021-01-01 04:09:49 +01:00
Dave Halter 778442a972 Type aliases should not have a signature 2021-01-01 03:59:28 +01:00
Dave Halter 4f34712858 Fix signatures for TypeVar and cast, fixes #1709 2021-01-01 03:59:12 +01:00
Dave Halter d821451a64 Upgrade typeshed 2021-01-01 03:18:49 +01:00
Dave Halter 92d96ac336 actually use auto_import_modules correctly 2021-01-01 02:59:42 +01:00
Dave Halter c64e33173a Fix an issue about properties, fixes #1705 2020-12-28 00:54:40 +01:00
Dave Halter 5d2aed34f4 Fix signatures if a decorator has no signatures, fixes #1705 2020-12-28 00:47:10 +01:00
Dave Halter 04c1c0f871 Fix an issue with api_name of class attributes, fixes #1688 2020-12-28 00:29:30 +01:00
Dave Halter 0f128c6deb Fix nested comprehension contexts, fixes #1691 2020-12-27 21:09:00 +01:00
Dave Halter 8373ef079f Remove an unnecessary comment 2020-12-26 22:43:47 +01:00
Dave Halter 227cbde169 Merge branch 'master' of github.com:davidhalter/jedi 2020-12-26 18:02:05 +01:00
Dave Halter 1f06e6f0c9 name the ci workflow in the hope that badges will then be displayed 2020-12-26 17:57:38 +01:00
Dave Halter 2d3b8ac8df Merge pull request #1715 from davidhalter/github-actions
Use GitHub Actions
2020-12-26 12:56:20 +01:00
Dave Halter fa6072b4fa Change Python test order in CI 2020-12-26 12:39:37 +01:00
Dave Halter aae2f7c49a Change badges from Travis/Appveyor to GitHub Actions 2020-12-26 12:37:04 +01:00
Dave Halter 52443daf12 Fix another Windows test on 3.8 2020-12-26 12:19:59 +01:00
Dave Halter 86d57edda4 Some Windows compatibility fixes 2020-12-26 11:52:47 +01:00
Dave Halter 7298350e76 Standardize line separator 2020-12-26 04:27:06 +01:00
Dave Halter 3184264b3b Try to fix windows 2020-12-26 04:16:32 +01:00
Dave Halter d4a1657b2e Better error reporting 2020-12-26 04:03:19 +01:00
Dave Halter bea401912f Hopefully fix Actions configuration 2020-12-26 03:42:33 +01:00
Dave Halter 3e4070bbb3 Enable Windows 2019 2020-12-26 03:35:28 +01:00
Dave Halter 3d7ad50f57 Remove travis and appveyor configs in favor of github action 2020-12-26 03:33:22 +01:00
Dave Halter 85ec94cf65 Fix pytest issues, fixes #1699 2020-12-26 03:32:17 +01:00
Dave Halter 0cc5c974f6 Try to improve GH Actions 2020-12-26 01:43:29 +01:00
Dave Halter 6f76bb945a GH actions, checkout recursive submodules 2020-12-26 01:14:17 +01:00
Dave Halter 239a3730a6 Try to add Github Actions 2020-12-26 01:03:03 +01:00
Dave Halter 8740ff2691 Ignore the mypy cache for searching folders 2020-12-25 17:35:28 +01:00
Dave Halter 4b5b2e791b Prepare release of 0.18.0 2020-12-25 11:06:15 +01:00
Dave Halter b89f9445c2 Merge pull request #1684 from davidhalter/relative-import
Relative imports should work even if they are not within the project
2020-12-22 23:18:46 +01:00
Dave Halter ce6ddb91de Merge pull request #1711 from davidhalter/deprecations
Remove Deprecations
2020-12-21 22:49:09 +01:00
Dave Halter fe60b5ca13 Fix flake8 issues in sith.py 2020-12-12 12:45:27 +01:00
Dave Halter fa2d03a4fb Mention removal of deprecations in CHANGELOG 2020-12-12 12:32:29 +01:00
Dave Halter 1b16ca0e2e Add sith.py to the files to be ignored by pytest 2020-12-12 12:25:22 +01:00
Dave Halter f9cec89038 Merge branch 'master' into deprecations 2020-12-12 12:17:25 +01:00
Dave Halter bc4f6ed9dd Merge branch 'master' into relative-import 2020-12-12 12:15:13 +01:00
Leo RyuandLeo Ryu fd435a7bbb Check if string_names is None before returning string_names (#1708)
* Check if string is None before using string_names

* Add test asserting None string_names returns an empty list

* Remove whitespace to pass flake8

* Add name to authors.txt

Co-authored-by: Leo Ryu <leo@episci.com>
2020-12-12 12:13:31 +01:00
Dave Halter ce0ed4b8ae Improve a comment 2020-12-10 16:57:09 +01:00
Dave Halter 42a759a7ae Merge pull request #1706 from ColdGrub1384/master
Catch 'PermissionError' for unreadable directories
2020-12-07 22:34:03 +01:00
Adrian Labbé 6dcae857a7 Remove 'test_get_parent_dir_with_file' 2020-12-07 14:50:04 -03:00
Dave Halter 34792c0077 Merge pull request #1707 from Carreau/fix-1702
Add tests for #1702, for a rare numpydoc syntax.
2020-12-07 15:07:53 +01:00
Dave Halter 6df463b1e3 Merge pull request #1704 from infokiller/expose-comp-prefix-len
add Completion.get_completion_prefix_length
2020-12-07 14:44:01 +01:00
Matthias Bussonnier 4740178bdf Not all nodes have children, protect agaisnt it. 2020-12-06 18:11:49 -08:00
Matthias Bussonnier 06d6776422 Add tests for #1702, for a rare numpydoc syntax.
It looks like numpydoc, and things like masked array docstrings use a
syntax that make jedi crash:

    fill_value : {var}, optional
            Value used internally for the masked values.
            If ``fill_value`` is not None, it supersedes ``endwith``.

Here we add a test that we do not crash jedi.
2020-12-06 18:08:51 -08:00
Yoni Weill 1095820006 add tests for get_completion_prefix_length 2020-12-06 21:09:03 +02:00
Adrian Labbé 47e60107b2 Add tests for 'test_get_parent_dir_with_file' and 'test_is_potential_project' 2020-12-06 15:26:20 -03:00
Adrian Labbé 12a2d10595 Catch 'OSError' instead of just 'PermissionError' 2020-12-06 15:25:46 -03:00
Yoni Weill ccdf7eddf4 add Completion.get_completion_prefix_length
fixes #1687
2020-12-06 17:21:33 +02:00
Adrian Labbé 83d4ec9e84 Catch 'PermissionError' for unreadable directories 2020-12-05 21:00:28 -03:00
Dave Halter 69750b9bf0 Add Python 3.9 to the testsed environments 2020-10-24 13:40:19 +02:00
Dave Halter a03a093e2c change the create_stub_module stuff a bit 2020-10-24 10:41:59 +02:00
Dave Halter 6094e7b39a Fix get_line_code for stubs 2020-10-24 10:12:32 +02:00
Dave Halter 98d0a55a02 Add a few more tests for annotations on self 2020-10-23 23:32:28 +02:00
Dave Halter 6eabde1519 Fix annotations on self attributes, fixes #1681 2020-10-23 23:26:07 +02:00
Dave Halter a4f45993f8 Simplify some things, so something like #1678 does not happen again 2020-10-23 21:38:39 +02:00
Dave Halter 49e35497ae Stop subclassing CompiledName, potentially fixes #1667 2020-10-23 21:28:08 +02:00
Dave Halter bf310c780c Fix a recursion on imports, fixes #1677 2020-10-23 21:04:36 +02:00
Dave Halter e671a0cb6d Fix an error with enums, fixes #1675 2020-10-23 20:25:00 +02:00
Dave Halter a5a36a049c Fix an infer issue on literals after brackets, fixes #1657 2020-10-23 19:09:23 +02:00
Dave Halter 43ff2833f3 Make a test more reliable 2020-10-23 18:04:47 +02:00
Dave HalterandPeter Law 5f2f4af851 Update test/test_inference/test_imports.py
Co-authored-by: Peter Law <PeterJCLaw@gmail.com>
2020-10-21 22:32:33 +02:00
Dave HalterandPeter Law bf56103428 Update jedi/inference/imports.py
Co-authored-by: Peter Law <PeterJCLaw@gmail.com>
2020-10-21 22:32:24 +02:00
Dave Halter 78e87d0ab8 Relative imports should work even if they are not within the project 2020-10-20 01:00:22 +02:00
anki-code 04572422d4 Xonsh shell has jedi extension (#1674)
* xonsh shell has jedy extension

* jedin in xonsh shell usage

* and many more! :)
2020-09-29 00:12:48 +02:00
Dave Halter cb55b45d47 Catch an OSError on Windows 2020-09-19 22:13:45 +02:00
Dave Halter e3fedb52f1 Remove an unused import 2020-09-19 21:40:01 +02:00
Dave Halter c1f4e7d874 One interpreter test is different for 3.9+ 2020-09-19 21:27:55 +02:00
Dave Halter 4082728c32 Revert "Add the Python 3.9 environment"
This reverts commit 39fe9a1979.
2020-09-19 21:22:38 +02:00
Dave Halter 66e2a0fce4 implict_reexport needs to be True for parso 2020-09-19 21:15:03 +02:00
Dave Halter 39fe9a1979 Add the Python 3.9 environment 2020-09-19 20:58:03 +02:00
Dave Halter f18493b627 Fix an interpreter test 2020-09-19 20:57:32 +02:00
Dave Halter fa2abb5ff6 Add mypy cache to gitignore 2020-09-19 20:36:54 +02:00
Dave Halter 5b81abd537 Mention different language servers in README 2020-09-19 20:36:19 +02:00
Dave Halter 01b2e8e6b8 Merge pull request #1669 from mvanderkamp/patch-1
make contextualized_node an optional kwarg in ReversedObject
2020-09-19 20:31:50 +02:00
Mvdk ff439039da make contextualized_node an optional kwarg
In all other py__iter__ definitions that I found, this argument is optional. It also often seems to not be passed around. I'm not sure why it was deemed mandatory here despite not being used.
2020-09-14 10:27:19 -06:00
Dave Halter 216f976fd5 Add a .readthedocs.yml to make sure that it's properly pip installed before the documentation is built 2020-08-06 00:12:50 +02:00
Dave Halter e617c9d344 Formatting 2020-08-05 23:55:46 +02:00
Dave Halter 58ef6cd36b if_stmt test clauses should be resolved at the start of the if_stmt 2020-08-05 23:55:46 +02:00
Dave Halter abf63d73d3 Basic implementation support for namedexpr, fixes #1647 2020-08-05 23:55:46 +02:00
Dave Halter 76c0c373da Merge pull request #1642 from PeterJCLaw/mypy
Add an initial mypy config
2020-08-05 01:09:49 +02:00
Dave Halter 209e2713fd Remove the requirements file and require latest parso 2020-08-05 00:55:57 +02:00
Dave Halter f12ed2088a Use pathlib for file ios, because the new parso is out 2020-08-05 00:52:50 +02:00
Dave Halter 94bf83c826 Revert Django changes in a9e2cd5a74
This was probably an accident in #1646
2020-08-05 00:18:24 +02:00
Peter Law cce3ecb1e4 Use the default handling of optionals
This is strict handling, but allows implicit declarations.
2020-08-04 21:49:42 +01:00
Dave Halter 10aa21f970 Merge branch 'master' of github.com:davidhalter/jedi 2020-08-04 18:29:26 +02:00
Dave Halter 425287055b Merge pull request #1646 from Carreau/warnings
Turn print into warning to simplify silencing them.
2020-08-04 14:47:57 +02:00
Matthias Bussonnier a9e2cd5a74 Reformat and move imports to top level. 2020-08-03 08:24:24 -07:00
Dave Halter 2f7d0ec42c Project attributes are now read accessible 2020-08-01 18:26:26 +02:00
Matthias Bussonnier 20be4f02c8 Turn print into warning to simplify silencing them. 2020-07-27 11:28:05 -07:00
Peter Law 6364dd1511 Add explicit Optional annotation
This isn't a mypy issue -- there's no way it could otherwise know
that this `None` value is in fact an optional callable.
2020-07-26 14:43:41 +01:00
Peter Law 19b8eaea59 Link mypy issue 2020-07-26 13:26:14 +01:00
Peter Law b892c07841 Merge branch 'master' into mypy 2020-07-26 12:25:19 +01:00
Peter Law cefc363f64 Configure mypy and flake8 for our re-export files
This removes the need to use __all__ in these files, while also
allowing us to have strictness elsewhere in the codebase.
2020-07-26 12:20:08 +01:00
Peter Law 45c90efb5c Remove a couple of unused imports 2020-07-26 12:17:54 +01:00
Peter Law 0571e12617 These attributes aren't optional
They just don't yet have a value.
2020-07-26 12:11:34 +01:00
Peter Law 86e0e16625 Drop redundant rtype comment
This is better expressed as an annotation.
2020-07-26 12:10:59 +01:00
Peter Law b3edda30c4 Explain why we 'type: ignore' these properties 2020-07-26 12:09:04 +01:00
Dave Halter 9d1587a41d Don't need to inherit from object anymore 2020-07-26 00:11:57 +02:00
Dave Halter e593396417 Merge pull request #1641 from PeterJCLaw/pydoc-data-python3.6-embedable
Python 3.6 embeddable doesn't have pydoc_data
2020-07-25 01:05:38 +02:00
Peter Law a9cb9fbb1f Give a bit more detail here 2020-07-24 21:06:30 +01:00
Peter Law 3f74981d5e Also typecheck sith 2020-07-24 21:06:30 +01:00
Peter Law 38f853cf86 Add ignores for stdlib imports only recently added 2020-07-24 21:06:30 +01:00
Peter Law 4b7e837f0f Configure the package root as implicit exports 2020-07-24 20:25:55 +01:00
Peter Law a2d9fbcd42 Ignore this runtime-only import
I've queried this in https://github.com/python/typeshed/issues/4360,
though I suspect the answer is going to be to have an ignore comment
like this.
2020-07-24 20:25:55 +01:00
Peter Law 6315709fea Inherit from base class to placate mypy 2020-07-24 20:25:55 +01:00
Peter Law 48e5aa777b Annotate potentially missing import 2020-07-24 20:25:55 +01:00
Peter Law 69be26b16e Change subclass to function wrapper
This avoids mypy complaining that we need to provide a generic
argument to Popen, which we cannot acctually do as the implementation
of Popen does not inherit from typing.Generic.
2020-07-24 20:25:55 +01:00
Peter Law 5e509814f7 Ignore mypy not coping with decorated properties 2020-07-24 20:25:55 +01:00
Peter Law 07fbcd2262 Make this explicitly expect a Path 2020-07-24 20:25:55 +01:00
Peter Law 1c87ae378d This is a Path now 2020-07-24 20:25:55 +01:00
Peter Law b1f95b4bf9 Annotate these attributes 2020-07-24 16:10:34 +01:00
Peter Law 7d9205d4ae This is actually optional 2020-07-24 16:10:34 +01:00
Peter Law 9b3cd15c5f Fix type clash 2020-07-24 16:10:34 +01:00
Peter Law 1418aada91 Annotate top level items mypy needs annotating 2020-07-24 16:10:34 +01:00
Peter Law f98a9f7999 Annotate the completions cache 2020-07-24 16:10:34 +01:00
Peter Law 35c2d660cb Fix most import related mypy errors 2020-07-24 16:10:34 +01:00
Peter Law c09e21ae4b Configure mypy
No fixes yet, this just gets the config in place.

Note: I'm assuming that we'll pick up a change to parso such that
it exposes its type stubs here. Otherwise we'll want to tweak the
imports config to ignore those errors.
2020-07-24 16:10:34 +01:00
Peter Law 480c352d33 Python 3.6 embeddable doesn't have pydoc_data
This reinstates the import check for pydoc_data for now.

Specifically I looked in the following:
- python-3.6.8-embed-amd64.zip: missing pydoc_data
- python-3.7.8-embed-amd64.zip: present
- python-3.8.5-embed-amd64.zip: present
2020-07-24 16:07:48 +01:00
Dave Halter 8f167be980 Merge branch 'master' of github.com:davidhalter/jedi 2020-07-23 01:33:06 +02:00
Dave Halter e86afc1705 _cropped_file_size should be an int, fixes #1639 2020-07-23 01:32:37 +02:00
Dave Halter 7423c65eb5 Merge pull request #1638 from PeterJCLaw/update-flake8
Update flake8
2020-07-22 09:28:08 +02:00
Peter Law b651c6541a Configure travis' flake8 call more explicitly
I'm basing this on '{posargs:jedi}' looking like it was a tox thing,
which we're no longer using.
2020-07-21 23:15:20 +01:00
Peter Law 403564315c Reflow test to ensure trailing space is preserved
Many editors strip trailing space, so avoid using a multiline
string where the space is actually needed.
2020-07-21 22:44:43 +01:00
Peter Law 5e6138d16f Update to flake8 3.8.x
In particular this improves support for detecting usage of various
type annotation usages and adds support for correctly parsing
type: ignore comments which contain a reason tag.
2020-07-21 21:34:58 +01:00
Peter Law 6ef18bea50 Make this noqa more specific 2020-07-21 21:34:37 +01:00
Peter Law 9505dabfef Reflow for linting 2020-07-21 21:32:22 +01:00
Peter Law 4783c065da Configure editors for uniform whitespace handling 2020-07-21 21:26:46 +01:00
Dave Halter bb303a75c0 Fix a test 2020-07-20 23:58:46 +02:00
Dave Halter 1e633ab8ed Remove the requirements file, it should not be necessary 2020-07-20 02:19:55 +02:00
Dave Halter 89f525407a Remove the deprecation tests 2020-07-20 02:06:17 +02:00
Dave Halter d7d42c8e39 Rewrite the deprecation handling 2020-07-20 02:04:31 +02:00
Dave Halter abb2250bf5 Remove all deprecations 2020-07-20 02:02:41 +02:00
Dave Halter ae2becb531 Merge branch 'pytest'
This completely removes tox from Jedi.
2020-07-20 01:46:43 +02:00
Dave Halter 14069e81fd Remove speed tests, they were only flaky and didn't really provide a value anymore 2020-07-20 01:43:29 +02:00
Dave Halter 401e8d3100 Fix issues with property searches 2020-07-20 01:40:25 +02:00
Dave Halter e7c2c85b9f Try to fix issues with the qa and coverage steps 2020-07-20 01:29:38 +02:00
Dave Halter 784e965d3a @property now returns Name.type == 'property', fixes muffinmad/anakin-language-server#15 2020-07-20 01:20:24 +02:00
Dave Halter 10c4dbf785 Try to get rid of tox and test directly with pytest 2020-07-19 14:58:17 +02:00
Dave Halter 7281302281 The defaults for find_system_environments and get_system_environment were wrong
This happened, because of the migration to Python 3 only.
2020-07-19 14:35:40 +02:00
Dave Halter 27603f9780 Reenable a test for nested imports 2020-07-19 13:57:52 +02:00
Dave Halter d9a90d5d5e Remove a test that no longer made sense 2020-07-19 13:55:18 +02:00
Dave Halter 9957565b37 Try to use yield from instead of yield, if possible 2020-07-19 13:34:58 +02:00
Dave Halter 5bc174bf8d Start writing CHANGELOG for the next release 2020-07-18 17:00:45 +02:00
Dave Halter 89f070ea98 Mention the mailing list instead of the github issue for updates 2020-07-17 22:35:14 +02:00
Dave Halter 04d24acb5a Merge branch 'python3' 2020-07-17 21:58:26 +02:00
Dave Halter 3b7106ae71 Fix a typo 2020-07-17 21:56:13 +02:00
Dave Halter 74116fe2ea Prepare for 0.17.2 2020-07-17 21:39:36 +02:00
Dave Halter 1233caebdc Fix a Python 3.9 issue on travis 2020-07-17 16:13:23 +02:00
Dave Halter d78567f853 Fix a Python 3.9 issue on travis 2020-07-17 16:12:55 +02:00
Dave Halter 1ece7698c2 Merge branch 'master' into python3 2020-07-17 16:07:54 +02:00
Dave Halter 7851dff915 Properly negate with Interpreter, fixes #1636 2020-07-17 15:57:32 +02:00
Dave Halter e4987b3e7a Fix issues with generators, fixes #1624 2020-07-17 15:57:32 +02:00
Dave Halter d1851c369c Introduce py__next__ to have more clear way to use __next__ 2020-07-17 15:57:32 +02:00
Dave Halter d63fbd8624 Merge pull request #1633 from mrclary/mrclary-fix-wingkinl-patch-python-environ
Fix for #1630
2020-07-17 11:26:02 +02:00
Ryan Clary b0f664ec94 * reflect default Popen behavior by inheriting os.environ
* without passing env_vars to create_environment, GeneralizedPopen behavior is same as before fix to issue #1540 (803c3cb271)
* env_vars allows explicit environment variables, per PR #1619 (f9183bbf64)
2020-07-16 19:04:33 -07:00
Dave Halter 9957374508 Fix dict completions for inherited dicts, fixes #1631 2020-07-14 17:50:12 +02:00
Dave Halter 7f3a7db7e6 Refactor Interpeter completions a bit 2020-07-12 22:26:57 +02:00
Dave Halter 3ffe8475b8 Make sure the interpreter completions work better in Jupyter Notebook, fixes #1628 2020-07-12 22:20:06 +02:00
Dave Halter 396d7df314 Fix an issue with interpreter completion, see also #1628 2020-07-12 22:02:00 +02:00
Dave Halter 0c618a4456 Making sure to note that Python 2 will not be supported after 0.17.2 2020-07-12 21:22:36 +02:00
Dave Halter c4c36d8e2e Mention in Changelog that 3.9 is now supported 2020-07-12 19:44:48 +02:00
Dave Halter 829dda3ee9 Fix another windows issue 2020-07-12 11:18:35 +02:00
Dave Halter a16f52b9fb Fix some Windows related issues with absolute paths 2020-07-12 11:13:37 +02:00
Dave Halter a49c062b35 Properly support Python3.9 2020-07-12 01:58:13 +02:00
Dave Halter da15e916de Fix a doctest 2020-07-12 01:37:41 +02:00
Dave Halter 480a464179 Implement all remaining Path issues and use it instead of strings 2020-07-12 01:14:00 +02:00
Dave Halter db0e90763b Start using pathlib.Path instead of all the os.path functions 2020-07-10 17:30:36 +02:00
Dave Halter 92af043906 Fix some subprocess issues 2020-07-02 18:39:24 +02:00
Dave Halter 806ad06d6a Use raise from instead of weird magic 2020-07-02 16:14:53 +02:00
Dave Halter dac1fb0a06 Get rid of a few Python 2 things 2020-07-02 16:00:26 +02:00
Dave Halter ec08506704 Remove getstate and setstate, because they are not needed anymore 2020-07-02 15:55:31 +02:00
Dave Halter 7bcb420a0a Delete a weird comment 2020-07-02 12:33:19 +02:00
Dave Halter 546b970240 Rewrite a weird super call 2020-07-02 12:31:16 +02:00
Dave Halter 24a1bbb3ca Even more super deletions 2020-07-02 12:29:10 +02:00
Dave Halter a0de93a638 Remove super arguments 2020-07-02 10:59:59 +02:00
Dave Halter 216ce8726c Move GeneralizedPopen 2020-07-02 10:54:32 +02:00
Dave Halter 0c1ba1b305 Move the importing of modules out of compatibility 2020-07-02 10:51:49 +02:00
Dave Halter 5ab351dc8f Remove unicode literals from code base 2020-07-02 10:43:14 +02:00
Dave Halter f1366b8a74 Remove the u() unicode function 2020-07-02 10:35:39 +02:00
Dave Halter 7f67324210 Remove a lot more Python 2 mentions and todos 2020-07-02 10:30:58 +02:00
Dave Halter a51f667be8 Cleanse the API from Python 2 stuff 2020-07-02 10:24:44 +02:00
Dave Halter f7b445353f Remove Python 2 compatibility functions 2020-07-02 10:14:12 +02:00
Dave Halter 46154a3ee7 Remove an unnecessary print 2020-07-02 03:35:24 +02:00
Dave Halter 0790f376ca Some Python 2 removals 2020-07-02 03:34:44 +02:00
Dave Halter 332631434c Remove some unnecessary utf-8 references 2020-07-02 03:30:41 +02:00
Dave Halter 8ee0c8593e Remove unicode usages 2020-07-02 03:26:22 +02:00
Dave Halter 5a912de937 Remove a few unicode references in tests 2020-07-02 03:18:48 +02:00
Dave Halter ef96c4c66b Remove __future__ usages 2020-07-02 03:15:07 +02:00
Dave Halter 155a1dd3fc A mistaken deletion in appveyor 2020-07-02 03:12:03 +02:00
Dave Halter 65601b6532 Remove compatibility code from getattr_static 2020-07-02 03:09:47 +02:00
Dave Halter 6e4dfda727 Fix a minor issue 2020-07-02 03:08:07 +02:00
Dave Halter 1fbe0d8d2e Remove python_version_match from publish_method 2020-07-02 03:04:14 +02:00
Dave Halter 6e184bca97 Remove most version_info.major usages 2020-07-02 03:00:01 +02:00
Dave Halter 188fdcd34f Remove the skip_python2 fixture 2020-07-02 02:52:24 +02:00
Dave Halter f4e537fd72 Remove a lot of sys.version_info references 2020-07-02 02:49:35 +02:00
Dave Halter cfd8eb23b8 Remove all_suffixes from _compatibility 2020-07-02 02:32:02 +02:00
Dave Halter 57c7d61989 importlib is needed 2020-07-02 02:30:49 +02:00
Dave Halter db28eee760 Remove py__version__ 2020-07-02 02:30:16 +02:00
Dave Halter 0cd6a8f5cc Remove is_py3 and is_py35 2020-07-02 02:23:33 +02:00
Dave Halter 17343bb57c Remove some more Python 3.5 references 2020-07-02 02:18:16 +02:00
Dave Halter 182e1e864c Remove _no_python2_support 2020-07-02 02:05:16 +02:00
Dave Halter 782c561e86 Fix the compatibility docstring 2020-07-02 02:03:34 +02:00
Dave Halter 9838040ca3 Fix a TODO 2020-07-02 01:56:23 +02:00
Dave Halter eea35ffc31 Remove supported Pythons from environments 2020-07-02 01:52:44 +02:00
Dave Halter b639e7fd11 Fixed a minor error with removing of force_unicode 2020-07-02 01:51:06 +02:00
Dave Halter 2c1e591718 Remove python 3.5 from appveyor 2020-07-02 01:47:57 +02:00
Dave Halter 49e4b1a0f8 Remove force_unicode 2020-07-02 01:47:21 +02:00
Dave Halter ebfc330e86 Remove the unused utf8_repr function 2020-07-02 01:32:17 +02:00
Dave Halter e597dcc8fd Remove a Python 2 file 2020-07-02 01:30:34 +02:00
Dave Halter 07fc1ef837 Remove the pickle compatibility stuff 2020-07-02 01:29:54 +02:00
Dave Halter a25e192ff9 Remove shutil.which compatibility 2020-07-02 01:19:12 +02:00
Dave Halter e6a748b1a7 Fix some directory issues 2020-07-02 01:17:35 +02:00
Dave Halter 227cf00638 Remove the __builtin__ compatibility 2020-07-02 01:15:29 +02:00
Dave Halter a9d32fbc99 Remove literal_eval compatibility 2020-07-02 01:10:46 +02:00
Dave Halter b5e0c1e9c6 Remove compatibility for zip_longest 2020-07-02 01:08:57 +02:00
Dave Halter 2aec4678da Remove compatibility for IsADirectoryError PermissionError NotADirectoryError 2020-07-02 01:07:06 +02:00
Dave Halter f9a35ae42a Remove FileNotFoundError compatibility 2020-07-02 01:05:13 +02:00
Dave Halter 0538a3e224 Remove Python 2 import hacks 2020-07-02 01:01:25 +02:00
Dave Halter 64516f1b45 Remove DummyFile 2020-07-02 00:59:36 +02:00
Dave Halter 1dc83115be Remove use_metaclass 2020-07-02 00:58:30 +02:00
Dave Halter c651109b9a Remove _compatibility.reraise 2020-07-02 00:56:30 +02:00
Dave Halter 1df98c5bd6 Remove no_unicode_pprint 2020-07-02 00:54:17 +02:00
Dave Halter aab9fd2fbe Remove queue compatibility 2020-07-02 00:52:26 +02:00
Dave Halter 4e2ca9e5fd Remove some pickle compatibility 2020-07-02 00:50:58 +02:00
Dave Halter 395f7fc59e Remove inspect.Parameter compatibility 2020-07-02 00:44:25 +02:00
Dave Halter 4c557d4050 Remove finalize from compatibility 2020-07-02 00:40:38 +02:00
Dave Halter 86eb48a89b Remove unwrap compatibility 2020-07-02 00:40:08 +02:00
Dave Halter 3262ad4350 Remove the scandir compatibility 2020-07-02 00:38:44 +02:00
Dave Halter fb34df3987 Remove a way for using imp to load Jedi in a subprocess 2020-07-02 00:37:09 +02:00
Dave Halter 23db298e2f Removed various 3.3/3.4/3.5 references 2020-07-02 00:34:27 +02:00
Dave Halter 9d5acf3c53 Remove the has_typing fixture 2020-07-02 00:26:28 +02:00
Dave Halter 7e295d05a1 Remove some more Python 2/3.5 references 2020-07-02 00:25:00 +02:00
Dave Halter 50b85153ce Remove a lot of test references to Python 2/3.5 2020-07-02 00:17:21 +02:00
Dave Halter 0e5869b52f Remove 2.7/3.5 from docs 2020-07-02 00:04:22 +02:00
Dave Halter d67dfba7f5 Remove Python 2.7/3.5 support 2020-07-02 00:00:46 +02:00
Dave Halter a3a9ae1a26 Add download badge 2020-06-27 15:15:34 +02:00
Dave Halter e41b966283 Some test skips 2020-06-27 03:10:24 +02:00
Dave Halter 4188526e2d Revert some of the Decoratee changes 2020-06-27 02:18:31 +02:00
Dave Halter 804b0f0d06 Some more signature adjustments 2020-06-27 02:18:31 +02:00
Dave Halter 7b15f1736c Change Decoratee slightly 2020-06-27 02:18:31 +02:00
Dave Halter 4846848a1e Fix an issue with decoratee names 2020-06-27 02:18:31 +02:00
Dave Halter 344fef1e2f Add Project.path, fixes #1622 2020-06-27 02:18:31 +02:00
Dave Halter bc23458164 Fix the of a signature with a decorator 2020-06-27 02:18:31 +02:00
Dave Halter 9a54e583e7 Fix docstrings for method decorators, fixes #1621 2020-06-27 02:18:31 +02:00
Dave Halter 59ccd2da93 Make partial use the __doc__ of its function, fixes #1621 2020-06-27 02:18:31 +02:00
Dave Halter 737c1e5792 Merge pull request #1614 from PeterJCLaw/fix-decorator-factory-passthrough
Support passing values through decorators from factories
2020-06-26 13:29:58 +02:00
Peter LawandDave Halter f72adf0cbc Switch to much simpler solution for preserving unbound type vars
Co-Authored-By: Dave Halter <davidhalter88@gmail.com>
2020-06-26 11:23:35 +01:00
Peter Law 5184d0cb9c Support passing values through decorators from factories
This builds on the approach taken in https://github.com/davidhalter/jedi/pull/1613
but applies it to type vars themselves so that their type var
nature is preserved when a function returns Callable[[T], T] and
the T has an upper bound.
2020-06-26 11:22:19 +01:00
Peter Law 2d0258db1a Add tests for class-style decorator factories 2020-06-26 11:19:51 +01:00
Dave Halter f5e6a25542 Merge pull request #1623 from mallamanis/master
Add __matmul__ to supported operators.
2020-06-26 12:10:00 +02:00
Miltos bc5a8ddf87 Add __matmul__ to supported operators. 2020-06-25 17:35:07 +01:00
Dave Halter eabddb9698 Remove a print 2020-06-24 01:29:50 +02:00
Dave Halter 6fcdc44f3e Typeshed third party libraries should not be loaded if they don't actually exist in the environment, fixes #1620 2020-06-24 01:08:04 +02:00
Dave Halter 0d1a45ddc1 Add the env_vars change to CHANGELOG 2020-06-22 00:13:57 +02:00
Dave Halter f9183bbf64 Merge pull request #1619 from mrclary/subprocess-env-vars
Provide option to pass explicit environment variables to Environment and CompiledSubprocess
2020-06-22 00:11:18 +02:00
Ryan Clary 7ec8454fc1 * Provide option to pass environment variables to Environment and CompiledSubprocess (subprocess.Popen)
* Extend this option to find_system_enviornments and get_system_environment without breaking API
2020-06-21 08:08:32 -07:00
Dave Halter a3410f124a Make sure that Callables are properly represented
See also comment of https://github.com/davidhalter/jedi/pull/1614#issuecomment-647054740
2020-06-21 01:31:58 +02:00
Peter Law 3488f6b61d Add Python 3.8 to the tox env list (#1618) 2020-06-20 16:18:32 +02:00
Dave Halter 3dad9cac6b Use Python 3 in the deployment script 2020-06-20 01:19:01 +02:00
Dave Halter 7aa13e35e9 Prepare release 0.17.1 2020-06-20 00:39:09 +02:00
Dave Halter cf1b54cfe5 Make sure the current version doesn't install a parso version that is new 2020-06-16 21:39:17 +02:00
Dave Halter 8669405a1c Small changelog improvement 2020-06-16 08:53:02 +02:00
Dave Halter 54775acc7a Mention Django Manager support for managers/querysets in changelog 2020-06-16 08:52:19 +02:00
Dave Halter be184241fd Add SyntaxError.get_message 2020-06-16 08:51:54 +02:00
Dave Halter 61ad05d511 Mention 3.9 support better 2020-06-16 08:42:18 +02:00
Dave Halter 1872ad311b Fix decorator param completion 2020-06-15 00:34:55 +02:00
Dave Halter 364d33119c Merge branch 'django' 2020-06-14 22:24:31 +02:00
Dave Halter 1702a6340e Document a special case in Django a bit better 2020-06-14 22:23:08 +02:00
Dave Halter 4ab35cac7b Merge branch 'master' of github.com:davidhalter/jedi 2020-06-14 18:11:50 +02:00
Dave Halter 21f1df18b6 Fix some issues with sub class matching, fixes #1560 2020-06-14 18:10:00 +02:00
Dave Halter 8ea4c0589c Merge pull request #1613 from PeterJCLaw/fix-1425-1607-typevar-wrap-functions-and-classes
Handle passing functions and classes through a TypeVar
2020-06-14 18:01:48 +02:00
Dave Halter 1d1c0ec3af Better debugging output for is_sub_class_of 2020-06-14 17:55:53 +02:00
Peter Law 7e637c5e5e Python 2 compatible super() 2020-06-14 16:27:39 +01:00
Peter Law 4f11f20e1d Add a signature check for decorated functions
Specifically where the decorator is type annotated.
2020-06-14 16:24:42 +01:00
Dave Halter 674e0114a5 Ignore runtime_checkable, because we don't really need it 2020-06-14 14:14:47 +02:00
Peter Law 1f082b69d2 Handle passing functions and classes through a TypeVar
This fixes #1425 and #1607 by persisting the original underlying
function or class when we process a TypeVar they are passed into.
2020-06-13 23:28:20 +01:00
Dave Halter 9de5ab2037 Make it possible to complete on QuerySet methods, fixes #1587 2020-06-13 20:55:37 +02:00
Dave Halter 3415ccbb73 Add support for Django signatures, fixes parts of #1587 2020-06-13 16:18:47 +02:00
Dave Halter b165596a6e Avoid doing a call twice for now reason 2020-06-13 14:25:52 +02:00
Dave Halter 089a4713e3 Fix a small extract_variable issue, fixes #1611 2020-06-13 01:35:58 +02:00
Dave Halter 365d725bc1 Fix a small issue that was inadvertently changed 2020-06-13 00:26:12 +02:00
Dave Halter 7586900fd9 Merge branch 'master' into django 2020-06-12 20:04:28 +02:00
Dave Halter c4de9ae2d3 Use a customized django-stubs 2020-06-12 19:30:49 +02:00
Dave Halter 3a0a484fcb Try to get get the tests for Python 3.9 passing, fixes #1608 2020-06-10 09:54:32 +02:00
Dave Halter df7dd026d2 Make it possible to use inheritance on generics without always specifying type vars, see also discussion in #1593 2020-06-10 09:54:32 +02:00
Dave Halter a2108de2c0 Use py__get__ for Django Model.objects
This includes the fix in https://github.com/typeddjango/django-stubs/pull/394
2020-06-09 23:26:43 +02:00
Dave Halter 6d0d75c7d9 @publish_method should provide arguments 2020-06-09 22:37:50 +02:00
Dave Halter d4f0424ddc Move py__getitem__ from Class to ClassMixin 2020-06-08 00:58:38 +02:00
Dave Halter cd6113c2c3 Move with_generics and define_generics to ClassMixin 2020-06-08 00:11:45 +02:00
Dave Halter c9a21adc5f Make sure py__get__ is applied properly for Django metaclasses 2020-06-07 15:01:12 +02:00
Dave Halter 9adcf3d233 Make sure meta class filters can distinguish between classes and instances 2020-06-07 14:54:26 +02:00
Dave Halter 34cc8e9ad7 Properly handle __get__ in properties/partials 2020-06-07 14:18:45 +02:00
yuan cf923ec6de Update MANIFEST.in 2020-06-07 12:01:56 +02:00
Dave Halter 105c097fea Merge branch 'django-custom-object-manager' of https://github.com/PeterJCLaw/jedi into django 2020-06-06 01:24:24 +02:00
Dave Halter 574b790296 Make it possible to use inheritance on generics without always specifying type vars, see also discussion in #1593 2020-06-06 01:23:14 +02:00
Dave Halter 3870253b56 Make sure that scopes can only be exact values, see #1590 2020-06-05 23:04:39 +02:00
Dave Halter 21a380f7cb Merge pull request #1590 from muffinmad/references-scope
Get references in the current module only
2020-06-05 19:21:34 +02:00
muffinmad 404661f361 Replace Script by timedelta in the test 2020-06-05 17:44:59 +03:00
muffinmad 1e58f9a15c Test both named params are found 2020-06-05 15:28:22 +03:00
Dave Halter 24236be3ce Fix a small issue with doctest completions, fixes #1585 2020-06-05 13:35:36 +02:00
muffinmad 8705149619 Use pytest.mark.parametrize 2020-06-03 17:20:23 +03:00
muffinmad 782dedd439 Get references in the current module only 2020-06-03 16:35:28 +03:00
muffinmad f9bbccbc13 Pycodestyle configuration section moved to setup.cfg 2020-06-03 15:24:37 +03:00
Michał Górny cecdaa98ae Exclude more Linux constants in test_import
The list of differences have grown again in Python 3.9.  Instead of
increasing the allowed count let's filter out more Linux-specific
constants.  This probably makes it possible to reduce allowed
len(difference) too.
2020-06-02 23:04:50 +02:00
Dave Halter 9980f760b1 Merge pull request #1601 from yuan-xy/patch_3
add test case to fix code example in doc
2020-05-31 11:14:58 +02:00
yuan 5946a5cd8c Refactoring about checking \r\n (#1603) 2020-05-31 11:13:30 +02:00
yuan_xy 32687474db add test case to fix code example in doc 2020-05-31 11:00:15 +08:00
yuan 98a8b6c76c fix typo (#1602) 2020-05-30 12:04:15 +02:00
yuan ca08365a81 fix typo 2020-05-28 21:29:34 +02:00
Dave Halter 8239328e42 Merge pull request #1599 from isidentical/py38-plus-setuppy
Upgrade setup.py's version parsing for 3.8+
2020-05-28 21:18:51 +02:00
Batuhan Taskaya b9131c6070 Upgrade setup.py's version parsing for 3.8+ 2020-05-28 15:26:48 +03:00
muffinmad 1c342d36e5 Don't goto while building found_names for the current file
But goto for all non_matching_reference_maps items later
2020-05-24 22:58:04 +03:00
Dave Halter 2d672d2f28 Merge pull request #1595 from PeterJCLaw/operator-not-in
Explicitly handle `a not in b` operator comparison
2020-05-23 14:48:40 +02:00
Peter Law c62cbd6654 Explicitly handle a not in b operator comparison
This avoids a `KeyError` from operator_to_magic_method lookup for
this case. Jedi probably could check for `__contains__` here, however
as it doesn't do so for `in` checks I'm following that lead for now.

Fixes https://github.com/davidhalter/jedi/issues/1594.
2020-05-23 12:49:53 +01:00
Peter Law c36904d983 Support custom managers in Django models
For the moment this support is limited to just Model.objects
replacements and does not use the custom manager for ForeignKey
related managers.
2020-05-22 12:33:03 +01:00
Peter Law 669b70b2cd Validate instance methods on Django models 2020-05-22 12:32:14 +01:00
muffinmad 7459d67fee Test local references in some other cases 2020-05-22 13:24:39 +03:00
muffinmad 741097827d Get references in the current module only 2020-05-21 19:51:13 +03:00
muffinmad 4ceca54138 Specify max-line-length for pycodestyle
According to CONTRIBUTING.md it can be 100
2020-05-21 17:31:44 +03:00
Christopher Cave-Ayland 860d5e8889 Import FileNotFoundError from jedi._compatibility 2020-05-21 11:45:52 +02:00
Dave Halter 64d131060c Merge pull request #1586 from PeterJCLaw/django-more-fields
Support more Django model fields
2020-05-19 00:39:27 +02:00
Peter Law b7cdec427e Support OneToOneFields 2020-05-18 22:19:20 +01:00
Peter Law df66b35444 Support UUIDFields 2020-05-18 22:11:31 +01:00
Peter Law cd9f2f31ea Support URLFields 2020-05-18 22:10:48 +01:00
Peter Law b54d7433c7 Support GenericIPAddressFields 2020-05-18 22:10:09 +01:00
Dave Halter 855fb5a936 Fix potential AttributeError in get_defintion_start_position/get_defintion_end_position, see #1584 2020-05-18 19:21:04 +02:00
Dave Halter 8fdf16b316 Fix an error of get_definition_end_pos, see #1584 2020-05-18 01:44:51 +02:00
Dave Halter fa6194c0a9 Refactor test_definition_start_end_position to use parametrize 2020-05-18 01:41:07 +02:00
Dave Halter 2d17b81313 definition_end_position -> get_definition_end_position, same for start, see #1584 2020-05-18 01:18:22 +02:00
Dave Halter cb1730f628 Merge pull request #1584 from pappasam/get_definition_position
Add BaseName.definition_[start,end]_position
2020-05-18 01:14:00 +02:00
Sam Roeca d848047012 Add unit tests for definition_[start,end]_position 2020-05-17 11:48:28 -04:00
Sam Roeca 716beae455 Add BaseName.definition_[start,end]_position
Provides two public (property) methods getting the (row, column) of the
start / end of the definition range. Rows start with 1, columns start
with 0.

:rtype: Tuple[int, int]
2020-05-16 15:08:36 -04:00
Dave Halter d16355fcf2 Fix tests in Python 2 2020-05-16 17:47:33 +02:00
Dave Halter cd3d40a3b8 Fix a small issue 2020-05-16 15:42:15 +02:00
Dave Halter b3fc10a6e4 Magic methods fixes for reverse methods 2020-05-16 15:39:48 +02:00
Dave Halter 09dbbc6361 lists and tuples should not be added 2020-05-16 15:10:47 +02:00
Dave Halter f5ad561c51 Use __truediv__ instead of __div__
This ignores Python 2, but that shouldn't be an issue, since we are going to drop it anyway.
2020-05-16 14:57:57 +02:00
Dave Halter 0db50b521d Fix an issue with Tuple generics 2020-05-16 14:55:59 +02:00
Dave Halter 9942a3d44c A few class renames 2020-05-16 14:35:15 +02:00
Dave Halter 47637c147c Better debugging 2020-05-16 14:31:31 +02:00
Dave Halter 2fb072532a Skip another non-important Python 2 test that fails on Windows 2020-05-16 01:25:15 +02:00
Dave Halter 70aa7fc917 Fix a namespace issue when getting references 2020-05-16 01:05:39 +02:00
Dave Halter 384b2ad014 Fix an about dict completions 2020-05-16 00:46:46 +02:00
Dave Halter f2975f9a05 Fix a None issue 2020-05-16 00:27:14 +02:00
Dave Halter 41c146a6f3 Implement magic method return values, fixes #1577 2020-05-15 23:53:44 +02:00
Dave Halter be594f1498 Remove an unused cache method 2020-05-15 23:53:44 +02:00
Dave Halter 99eba4e0eb Undefined api types should not return a random value 2020-05-15 23:53:44 +02:00
Peter Law 43806f8668 Add support for generic optional parameters (#1559)
* Add support for generic optional parameters

* Tests for passing non-optional arguments to optional parameters

* Remove now-redundant is_class_value handling

This parameter has since been removed from infer_type_vars methods,
much simplifying the code.
2020-05-15 19:56:03 +02:00
Dave Halter d4aa583e16 Fix inline case where a name was removed without the code being used, fixes #1582 2020-05-14 23:08:37 +02:00
Dave Halter 381fbeda6a Make the diff nicer if there is no ending newline, fixes #1581 2020-05-14 00:20:20 +02:00
Dave Halter 3104443212 Merge pull request #1579 from muffinmad/pseudotreenameclass
Return 'class' as _PseudoTreeNameClass.type (fix #1578)
2020-05-13 18:59:05 +02:00
muffinmad 16e2b86bcf Fix test 2020-05-13 01:18:47 +03:00
Dave Halter 0caee73975 Merge pull request #1572 from davidhalter/classvar
Remove is_class_value from infer_type_vars
2020-05-12 23:56:03 +02:00
Dave Halter 7f25e28d89 Fix tuple issue in 3.6 2020-05-12 23:33:06 +02:00
muffinmad ce8473ee63 Add author's name to AUTHORS.txt 2020-05-12 23:34:28 +03:00
muffinmad 7ccee7d8fc Add test _PseudoTreeNameClass.type == 'class' 2020-05-12 23:28:46 +03:00
muffinmad 7cd89cff6e Return 'class' as BaseName.type of _PseudoTreeNameClass (fix #1578) 2020-05-12 23:14:32 +03:00
Vlad Serebrennikov e1c0d2c501 Reduce noise in signatures of compiled params (#1564)
* Remove "typing." prefix from compiled signature param

* Don't print default "None" for Optional params

* Don't remove 'typing.' prefix if symbol doesn't come from typing module

* Revert "Don't print default "None" for Optional params"

This reverts commit 8db334d9bb77f7810412bdcc4cb090f69eb17012.

* Make sure "typing." doesn't appear in the middle

* Make sure only "typing." prefix is removed and not it's entries in the middle

* Use inspect.formatannotation() to create an annotation string

* Update AUTHORS.txt

* Add test for compiled param annotation string

* Replace Optional in test with other typing facilities

in order for test to be forward-compatible with 3.9

* Add an empty string fallback for Python 2

* Move _annotation_to_str back to original position
2020-05-10 13:33:36 +02:00
Dave Halter be7a1346ec Fix #1573 again; a tree_node can be None 2020-05-10 13:29:58 +02:00
Dave Halter 6dbc5e783e Fix argument clinic unpacking, remove dynamic bullshit 2020-05-10 13:27:20 +02:00
Max Mäusezahl 1115cbd94d This fixes two issues with the caching on Windows:
* the cache directory should really be %LOCALAPPDATA%
 * ~ is not a meaningful directory on Windows. It should really be
   os.path.expanduser('~'). To be honest it is probably always safe to
   assume that os.getenv('LOCALAPPDATA') executes to something sensible
   on any Windows system that hasn't been tampered with.
2020-05-10 11:46:29 +02:00
Dave Halter bf4ec2282f Fix getattr completions on very weird cases, fixes #1573 2020-05-10 11:37:58 +02:00
Dave Halter e6e43413ff Any -> AnyClass 2020-05-10 03:17:52 +02:00
Dave Halter e9a0c01af8 TypedDictBase -> TypedDictClass 2020-05-10 03:17:07 +02:00
Dave Halter d0270b5e59 DefineGenericBase -> DefineGenericBaseClass 2020-05-10 03:07:40 +02:00
Dave Halter b57654aed3 Rename some classes to make it clearer that they are classes 2020-05-10 03:04:52 +02:00
Dave Halter 78ad06612e Remove an unused import 2020-05-10 03:00:47 +02:00
Dave Halter 434866558a Instances should not need get_generics 2020-05-10 02:59:54 +02:00
Dave Halter 42963a0e03 By having get_annotated_class_object for Tuple/Callable, some details are not necessary anymore 2020-05-10 02:52:42 +02:00
Dave Halter c2d1da09cb Make sure that Tuple/Callable instances have the correct py__class__ 2020-05-10 01:05:55 +02:00
Dave Halter f362932ec5 Return a more correct py__class__ for typing base objects 2020-05-09 16:28:05 +02:00
Dave Halter 3b48c76e4a Make a function private 2020-05-09 00:49:37 +02:00
Dave Halter d56f607f35 Reinstate an if that was deleted by mistake 2020-05-09 00:13:18 +02:00
Dave Halter 39a2cd8aa2 Fix a potential issue with tuples 2020-05-08 18:07:15 +02:00
Dave Halter 14ca8e6499 Add a comment 2020-05-08 18:00:35 +02:00
Dave Halter 2a227dcc7a Remove is_class_value from infer_type_vars 2020-05-08 17:49:02 +02:00
Dave Halter 12090ce74b Fix tests 2020-05-08 15:18:23 +02:00
Dave Halter 25973554e2 Remove the common folder and move it to a common file 2020-05-08 13:23:56 +02:00
Dave Halter 138c22afe9 Remove common.value 2020-05-08 13:18:01 +02:00
Dave Halter d19535340c Move infer_type_vars to base_value 2020-05-08 13:13:26 +02:00
Dave Halter 5fcbed721d Merge pull request #1554 from PeterJCLaw/fix-nested-tuple-argument
Fix handling of nested tuple arguments
2020-05-08 12:49:44 +02:00
Sam Roeca 812776b9ce Add .venv to _IGNORE_FOLDERS
".venv" is a popular virtual environment folder name; project.search
gets really mucked up when it isn't ignored.
2020-05-05 21:15:18 +02:00
Dave Halter d606ea6759 Correct a test 2020-04-27 09:59:38 +02:00
Dave Halter c314e1c36e Speed up signature fetching for MixedName, see discussion in #1422 2020-04-27 01:53:42 +02:00
Dave Halter 8c7a883abd Test that the actual signature of a function is used in Interpreter 2020-04-27 01:47:06 +02:00
Peter Law 55facaaf3d Switch back to using execute_annotation
get_annotated_class_object is (sort-of) the inverse of execute_annotation,
so adding a get_annotated_class_object to implement execute_annotation
specifically for Tuples didn't make much sense.
2020-04-26 14:39:39 +01:00
Peter Law 17ca3a620f Merge branch 'master' into fix-nested-tuple-argument 2020-04-26 13:56:14 +01:00
Dave Halter 9836a1b347 Very small refactoring 2020-04-26 12:47:44 +02:00
Peter Law 8c3fd99009 Tell sith that goto_assignments is now goto 2020-04-26 02:15:53 +02:00
Dave Halter 4d9cb083ac Merge pull request #1561 from PeterJCLaw/newtype-pyclass
Support accessing the py__class__ of a NewType
2020-04-26 02:15:17 +02:00
Peter Law 612fd23777 Support accessing the py__class__ of a NewType
The test here is a bit contrived, the actual place I found this
was in using a NewType as a type within a NamedTuple. However due
to https://github.com/davidhalter/jedi/issues/1560 that currently
also fails for other reasons. This still feels useful to fix on
its own though.
2020-04-26 00:59:07 +01:00
Dave Halter dca505c884 Merge pull request #1553 from PeterJCLaw/generic-tuple-return
Fix construction of nested generic tuple return types
2020-04-26 01:28:51 +02:00
Dave Halter 7fd5c8af8f Allow files for get_default_project, fixes #1552 2020-04-26 00:33:10 +02:00
Dave Halter 97fb95ec0c Don't display unnecessary help, fixes #1557 2020-04-26 00:21:01 +02:00
Dave Halter e6d8a955d2 Pin Django in a different way so tests can work everywhere 2020-04-25 23:25:51 +02:00
Dave Halter a3a147f028 Make sure that Django's values/values_list is tested (though not implemented 2020-04-25 22:55:29 +02:00
Dave Halter c761dded35 Properly implement inheritance for Django models 2020-04-25 22:55:29 +02:00
Dave Halter 92623232c3 Make sure Django User inference works 2020-04-25 22:55:29 +02:00
Dave Halter 9b58bf6199 Pin the Django test dependency 2020-04-25 22:55:29 +02:00
Dave Halter 9d5eb28523 Mention django stubs support in README 2020-04-25 22:55:29 +02:00
Dave Halter 857e0fc00e Include Django stubs license in Jedi package 2020-04-25 22:55:29 +02:00
Dave Halter bf8b58aeeb Some more django query tests 2020-04-25 22:55:29 +02:00
Dave Halter f6803bce2c Infer many to many fields 2020-04-25 22:55:29 +02:00
Dave Halter 6bff30fbbb Include Django stubs as a third party repo 2020-04-25 22:55:29 +02:00
Dave Halter 6d927d502e Make sure that infering the Django User model works 2020-04-25 22:55:29 +02:00
Dave Halter 2e1284f044 Fix a recursion error issue 2020-04-25 22:55:29 +02:00
Dave Halter 11eb4f8fde Remove unused imports 2020-04-25 22:55:29 +02:00
Peter Law c19c13e2c6 Apply tuple-only filtering to apply more broadly 2020-04-24 16:44:25 +01:00
Peter Law 891383f8dc Use get_annotated_class_object over execute_annotation 2020-04-24 16:32:00 +01:00
Peter Law ce1ac38cde Implement get_annotated_class_object for Tuples 2020-04-24 16:25:19 +01:00
Peter Law df951733cd Rename variable to placate mypy 2020-04-24 12:45:05 +01:00
Josh Bax 912fe68069 Fix typos in api.classes docstrings 2020-04-24 10:34:46 +02:00
Josh Bax be82d5ff36 Remove a redundant check from Name.desc_with_module 2020-04-24 10:34:46 +02:00
Dave Halter 784f9ff081 Actually fix #1556, forgot to add this in 94d374c9ce 2020-04-23 10:10:58 +02:00
Dave Halter 0f39135ae5 Start changelog for 0.17.1 2020-04-22 23:14:58 +02:00
Dave Halter 94d374c9ce Fix a small issue with the help method, fixes #1556 2020-04-22 17:32:40 +02:00
Dave Halter f3152a8c2b Django is not supported for Python 2 2020-04-22 09:44:43 +02:00
Dave Halter f3eaa418bb Work with a NameWrapper, so Django goto works better 2020-04-22 09:32:39 +02:00
Dave Halter f9176578ea Fix another django modelfield issue 2020-04-22 00:54:43 +02:00
Dave Halter 17eeb73767 Some nitpicks 2020-04-22 00:41:59 +02:00
Dave Halter 7756792bba Fix another issue with foreign keys 2020-04-22 00:33:51 +02:00
Dave Halter ba4e3393d3 Fix ForeignKey issues with invalid values 2020-04-22 00:27:06 +02:00
Dave Halter 1a89fafce4 Some other small refactorings 2020-04-22 00:15:35 +02:00
Dave Halter df307b8eda Refactor a few things for django 2020-04-22 00:05:35 +02:00
Dave Halter d96887b102 Remove old third party django tests 2020-04-21 23:43:59 +02:00
Dave Halter 89ad9a500b Use debug instead of print for Django and fix indentation, see #1467 2020-04-21 23:41:54 +02:00
Dave Halter 086728365c Make Django test optional 2020-04-21 23:36:00 +02:00
Dave Halter f9e36943d4 Merge branch 'master' of https://github.com/ANtlord/jedi 2020-04-21 23:22:40 +02:00
ANtlord b5c1c6d414 Django plugin test of ManyToManyField is added and marked for future implementation. 2020-04-21 10:56:22 +03:00
ANtlord df76b2462e Review corrections. 2020-04-20 10:31:03 +03:00
Peter Law 343a10d491 Drop redundant blank line 2020-04-19 14:42:57 +01:00
Peter Law 72c52f5f15 Add type match guard 2020-04-19 14:29:44 +01:00
Peter Law cfa01d3ac5 Add handling of nested generic tuples 2020-04-19 14:10:03 +01:00
Peter Law f8e7447d35 Add handling of nested generic callables
Previously tests for these were passing somewhat by accident,
however this commit's parent adds a case which showed that the
handling was missing.

Note that this also relies on the recent fix for nested tuples
which changed the `isinstance` check in `define_generics`.
2020-04-19 13:27:06 +01:00
Peter Law 2ac806e39f Add test which demonstrates incomplete generic Callable handling 2020-04-19 13:25:02 +01:00
Peter Law 7ebbf9da44 Make this test case obey typing rules in Python
Unfortunately I can't recall exactly what it was that this test
case was trying to validate, however on a second look it turns
out that it was working by accident and did not represent a valid
use of generic type vars in Python (which cannot be used completely
unbound as this was).
2020-04-18 22:59:20 +01:00
Peter Law 1c4a2edbdb Fix construction of nested generic tuple return types
Unfortunately this appears to show up a separate bug.
2020-04-18 19:43:47 +01:00
ANtlord 1d3082249f Debug information corrections. 2020-04-18 18:51:12 +03:00
ANtlord 09950233e7 Django is designated in test dependencies. 2020-04-18 18:36:04 +03:00
ANtlord d48575c8c5 Simple tests of Django plugin are added. 2020-04-18 16:13:48 +03:00
ANtlord f8a0cf76c8 Merge branch 'master' of github.com:davidhalter/jedi 2020-04-18 14:25:24 +03:00
Dave Halter 851e0d59f0 Better developer tools 2020-04-18 12:19:17 +02:00
Dave Halter 10b2de2c3f Make the linter completely private 2020-04-18 11:23:25 +02:00
Dave Halter 3718d62e24 Make sure that calling Jedi with a random argument in CLI results in errors 2020-04-18 11:23:12 +02:00
Dave Halter a793dd7c91 Fix a small _get_annotated_class_object, fixes #1550 2020-04-18 00:36:32 +02:00
Dave Halter 0850b86456 Also don't complete keywords if kwargs only are allowed, see #1541 2020-04-17 23:51:40 +02:00
Dave Halter f07dee3564 Completion: Don't suggest variables when only kwargs are legal, fixes #1541 2020-04-17 22:59:26 +02:00
xu0o0 f871f5e726 fix #1548 2020-04-17 19:24:05 +02:00
Ryan Clary 803c3cb271 * Use an explicit environment for subprocess to ensure that existing environment variables are not inherited. This ensures more reliable results, see issue #1540.
* Attempt to send SYSTEMROOT variable to Windows subprocess
2020-04-16 00:52:44 +02:00
Michał Górny 7ff76bb7d0 Sort test_project::test_search results to fix failures
Fixes #1542
2020-04-15 17:21:40 +02:00
Michał Górny e7feeef64e Inc difference limit in TestSetupReadline::test_import for py3.8
Python 3.8 on Linux has 21 differences which exceed the current limit.
Increase it to 22.
2020-04-15 10:09:36 +02:00
Dave Halter 8aaa8e0044 Project._python_path -> Project.environment_path 2020-04-14 23:14:07 +02:00
Dave Halter cbfbe7c08d Set the release date in Changelog 2020-04-14 22:59:17 +02:00
Dave Halter 81926a785c Some README improvements 2020-04-14 00:06:32 +02:00
Dave Halter 9ccb596f93 Extract now properly validates line/column and those two params are required 2020-04-13 23:15:42 +02:00
Dave Halter 25db8de0da Some minor CHANGELOG changes 2020-04-13 22:40:06 +02:00
Dave Halter 24dffe4226 Upgrade parso version 2020-04-13 22:33:51 +02:00
Dave Halter c3fc129695 Fix a small issue 2020-04-12 00:54:31 +02:00
Dave Halter 02c3d651bd Some more code quality fixes 2020-04-11 02:23:23 +02:00
Dave Halter bdd4deedc1 Some code cleanups 2020-04-11 02:11:52 +02:00
Dave Halter 9d55194b92 Don't reuse a variable 2020-04-11 01:40:41 +02:00
Dave Halter 102f83ea85 Remove unreachable code 2020-04-11 01:39:04 +02:00
Dave Halter 22902f6dba _convert_names kwargs are not needed 2020-04-11 01:37:34 +02:00
Dave Halter 5a3565785c Add pyproject.toml to the list of files to search for projects 2020-04-11 00:51:28 +02:00
Dave Halter 0f2a7215bb Use the interpreter environment if the executable is not available, fixes #1531 2020-04-02 20:59:35 +02:00
Dave Halter 61e9371849 Fix a potential AttributeError 2020-04-02 00:32:50 +02:00
Dave Halter dde40b3a71 Add a comment to clarify the Type case 2020-04-02 00:23:38 +02:00
Dave Halter ebb2786748 Avoid AttributeErrors for generics when a module is passed 2020-04-01 01:59:13 +02:00
Dave Halter 28f256d2a6 Merge branch 'improve-type-annotation-inference-refactors' of https://github.com/PeterJCLaw/jedi 2020-04-01 00:54:25 +02:00
Dave Halter 883f5a3824 Merge branch 'improve-type-annotation-inference' of https://github.com/PeterJCLaw/jedi 2020-04-01 00:54:13 +02:00
Dave Halter ac33d5dea3 If branch inference should not trigger for things we don't know, fixes #1530 2020-03-31 22:46:31 +02:00
Dave Halter 604029568c Fix string completion issue, fixes #1528 2020-03-26 15:47:27 +01:00
Peter Law eac5ac8426 Update comment after refactor moved code 2020-03-25 22:35:12 +00:00
Peter Law 7e9ad9e733 Fix typo 2020-03-25 22:32:53 +00:00
Peter Law e2090772f3 Push tuple handling onto Tuple class
This resolves a TODO to avoid using a private method
2020-03-22 16:04:39 +00:00
Peter Law 525b88e9f1 Simplify early-exit code by having it once 2020-03-22 15:49:31 +00:00
Peter Law 3c90a84f68 Extract common get_generics() calls
These no longer need to be guarded by the conditions now that we
know these types are generic anyway.
2020-03-22 15:47:46 +00:00
Peter Law ea33db388b Remove dict merging where it doesn't do anything
These cases are all at the end of a single-path branch that ends
up "merging" against an empty mapping which is then returned
unchanged.
2020-03-22 15:45:18 +00:00
Peter Law f68d65ed59 Push much looping and merging of infering type vars into ValueSet 2020-03-22 15:29:11 +00:00
Peter Law 3c7621049c Extract annotation inference onto annotation classes
This removes the _infer_type_vars util in favour of a polymorphic
implementation, removing the conditional checks on the type of
the annotation instance.

While for the moment this creates some circular imports, further
refactoring to follow should be able to remove those.
2020-03-22 15:29:11 +00:00
Peter Law dd60a8a4c9 Extract nested function which is going to be used elsewhere 2020-03-22 15:20:58 +00:00
Peter Law 5bd6a9c164 Rename function which is going to be used elsewhere 2020-03-22 15:18:41 +00:00
Peter Law c743e5d9f3 Push type check into helper 2020-03-22 15:14:01 +00:00
Peter Law 5ca69458d4 Add testing for mismatch cases
This should help catch any errors in our handling of invalid cases.
While some of these produce outputs which aren't correct, what
we're checking here is that we don't _error_ while producing that
output.

Also fix a case which this showed up.
2020-03-22 15:10:43 +00:00
Dave Halter bb9731b561 Fix wrong types for iterate, fixes #1524 2020-03-21 18:09:03 +01:00
Dave Halter a2f4d1bbe7 Fix stub conversion for Decoratee, so docstrings work, see #117 2020-03-21 17:23:27 +01:00
Dave Halter 88c13639bc Remove unused environment param 2020-03-21 03:19:39 +01:00
Dave Halter 28c1ba6c1c Fix a Python 2 test 2020-03-21 03:13:59 +01:00
Dave Halter a2764283ba Merge branch 'refactor' 2020-03-21 02:54:07 +01:00
Dave Halter 0ffd566957 Merge branch 'project' 2020-03-21 02:52:51 +01:00
Dave Halter 5b54ac835d Fix deprecations in tests 2020-03-21 02:42:00 +01:00
Dave Halter 5f6a25fb58 Add deprecations warnings, to deprecated functions in the main API 2020-03-21 02:30:07 +01:00
Dave Halter d6d9286242 Get rid of deprecations in tests 2020-03-21 02:15:57 +01:00
Dave Halter 4c964ae655 Fix some test results 2020-03-21 01:52:56 +01:00
Dave Halter 8000d425ec Don't use desc_with_module in integration tests 2020-03-21 01:47:00 +01:00
Dave Halter c7cd84b1a4 Rework the introduction of the README/docs 2020-03-21 01:25:58 +01:00
Dave Halter 6a89599fa5 Rework badges 2020-03-19 10:12:52 +01:00
Dave Halter 5f40fa9bc6 Docs: Remove links for sources/created using sphinx/copyright 2020-03-19 09:48:12 +01:00
Dave Halter 24cde8e974 Clean up acknowledgements 2020-03-19 09:43:19 +01:00
Dave Halter dea80b20e9 REPL docs improvements 2020-03-19 02:57:51 +01:00
Dave Halter 197d64d9a8 Remove tox from docs 2020-03-19 02:53:24 +01:00
Dave Halter a2bbbfe2d5 Rework a lot of the README 2020-03-19 02:49:29 +01:00
Dave Halter 2e9fac0b71 Rewrite the history part 2020-03-19 02:33:45 +01:00
Dave Halter 83e0e3bd8d Move history 2020-03-19 02:16:21 +01:00
Dave Halter 2f651966e7 Make jedi testing explanations better 2020-03-19 02:13:01 +01:00
Dave Halter ffbaa4afea Improve settings documentation 2020-03-19 01:53:47 +01:00
Dave Halter e11db6e8e4 Move acknowledgements in docs 2020-03-19 01:42:18 +01:00
Dave Halter eea6c7f41b Move recipes to Jedi Usage 2020-03-19 01:31:49 +01:00
Dave Halter 01f53236a4 Rework the recipe parts 2020-03-19 01:26:45 +01:00
Dave Halter c39326616c A lot of improvements for the features & limitations docs 2020-03-19 01:04:48 +01:00
Dave Halter b1aef26464 Docs: End user usage improvements 2020-03-19 00:25:54 +01:00
Dave Halter 97117bfaf2 Display full version in docs 2020-03-19 00:16:03 +01:00
Dave Halter f12262881d Some minor docstring improvements 2020-03-19 00:11:02 +01:00
Peter Law 95b0cdcb5e Add test for child of specialised generic 2020-03-18 22:15:32 +00:00
Peter Law 0f8e7b453e Formatting 2020-03-18 22:12:21 +00:00
Dave Halter 516b58b287 Fix a lot of sphinx warnings 2020-03-18 10:16:32 +01:00
Dave Halter e53acb4150 Create an autosummary for Jedi's API 2020-03-18 10:03:07 +01:00
Dave Halter 7de475318a Minor refactoring 2020-03-17 10:00:38 +01:00
Dave Halter 6dda514ec6 Make sure encoding doesn't unnecessarily raise warnings 2020-03-17 10:00:30 +01:00
Dave Halter 72a3a33e33 ParamDefinition -> ParamName 2020-03-17 09:34:28 +01:00
Dave Halter d26926a582 Definition -> Name 2020-03-17 09:33:12 +01:00
Dave Halter 0731206b9d BaseDefinition -> BaseName 2020-03-17 09:25:30 +01:00
Dave Halter c2451ddd03 Small docstring changes 2020-03-17 09:21:48 +01:00
Dave Halter 88adf84fc2 Move acknowledgements over to the documentation 2020-03-17 09:18:34 +01:00
Dave Halter 94c97765c8 Include the CHANGELOG in docs 2020-03-17 09:16:57 +01:00
Dave Halter 1c56d15836 Added project support to the changelog 2020-03-17 09:06:37 +01:00
Dave Halter 7985ef37d4 Rewrite Interpreter docs 2020-03-17 09:04:02 +01:00
Dave Halter 8f4f6d6ac3 Document refactoring functions 2020-03-17 08:57:35 +01:00
Dave Halter 4a065642f2 Docs: Reformat API return classes 2020-03-17 08:34:51 +01:00
Dave Halter 3276db0bdc Improve many Script API docstrings 2020-03-16 10:19:39 +01:00
Dave Halter 88757f00e7 Script source argument to code 2020-03-16 09:45:05 +01:00
Dave Halter 6d79ac9fde Add deprecations for Script parameters line/column/encoding 2020-03-16 09:41:47 +01:00
Dave Halter 25af28946e Docs: API overview 2020-03-16 09:35:47 +01:00
Dave Halter 950f5c186c Restructure API documentation 2020-03-16 09:27:01 +01:00
Dave Halter 8f96cbdabf Replace the old flask theme with the sphinx_rtd_theme 2020-03-16 01:28:06 +01:00
Christopher Cave-Ayland 17b3611c53 Included statement as a possible return type for BaseDefinition.type 2020-03-16 00:36:17 +01:00
Dave Halter 9240a20d13 Remove an old note that was not valid anymore 2020-03-16 00:21:15 +01:00
Dave Halter 6220b20659 "Document" stubs for develops 2020-03-16 00:19:08 +01:00
Dave Halter 2feb0acd7d Docs: remove arrogance :) 2020-03-16 00:13:30 +01:00
Dave Halter 8efd111426 Small docs example code changes 2020-03-16 00:07:01 +01:00
Dave Halter 616e9bf275 Docs: security 2020-03-16 00:05:48 +01:00
Dave Halter 78f0f5855f Docs: History 2020-03-16 00:02:17 +01:00
Dave Halter 0f11f65682 Docs: Features 2020-03-16 00:00:43 +01:00
Dave Halter 43363936cd Installation notes for docs moved down in priority a bit 2020-03-15 23:52:52 +01:00
Dave Halter 0f25eb9c9a Way more docs work 2020-03-15 23:41:53 +01:00
Dave Halter 8ceb76b3f6 Move is_side_effect to BaseDefinition 2020-03-15 23:13:41 +01:00
Dave Halter 25e6db5e82 Some more docstring stuff 2020-03-15 23:12:38 +01:00
Dave Halter 7c7864d500 Improve docstrings for a lot of the return API classes 2020-03-15 23:02:30 +01:00
Dave Halter a9761079e6 Remove follow_definition 2020-03-15 19:28:02 +01:00
Dave Halter 20fad922bc Better SyntaxError listings 2020-03-14 17:30:33 +01:00
Dave Halter 3cef022a15 Add a proper CHANGELOG for the current version 2020-03-14 17:22:25 +01:00
Dave Halter 52b0450953 Add a warning about fast_parser, fixes #1240 2020-03-14 16:53:08 +01:00
Dave Halter 7b725553ff Better documentation of Script 2020-03-14 16:48:07 +01:00
Dave Halter e811651b00 Further example tinkering 2020-03-14 15:47:32 +01:00
Dave Halter fbba7714e4 Better examples 2020-03-14 15:42:16 +01:00
Dave Halter bdb36ab626 Document projects better 2020-03-14 15:35:41 +01:00
Dave Halter 1a466d9641 Move the Project.save function within the file 2020-03-14 15:25:40 +01:00
Dave Halter 94f99aaeb3 Docs: Document projects 2020-03-14 15:25:03 +01:00
Dave Halter 851980e2a9 Document errors better 2020-03-14 15:15:09 +01:00
Dave Halter 88c766afb0 Better docstrings for search 2020-03-14 15:00:47 +01:00
Dave Halter 13254a30df Docs: Restructure API overview 2020-03-14 14:28:06 +01:00
Dave Halter 50af2650bb Docs: features reworked 2020-03-14 13:58:30 +01:00
Dave Halter 788562715e Update the README with the latest API changes 2020-03-14 12:21:55 +01:00
Dave Halter 0888dd468f Fix partialmethod issues 2020-03-14 01:22:46 +01:00
Dave Halter fd9a493868 Make sure partialmethod tests are only executed for Python 3 2020-03-14 00:45:43 +01:00
Dave Halter 661fdb2b26 Merge branch 'add-partialmethod' of https://github.com/ffe4/jedi 2020-03-14 00:28:06 +01:00
Dave Halter 23f267bb86 Fix small make html errors for docs 2020-03-14 00:18:29 +01:00
Dave Halter 4af138f4fb Merge branch 'docs' of https://github.com/blueyed/jedi into refactor
Almost all of the docstrings were still there.
2020-03-14 00:12:53 +01:00
Dave Halter 10bc578bfe Merge branch 'master' into refactor 2020-03-13 23:53:09 +01:00
Daniel Lemm 2406e58386 Refactor stdlib PartialObject
Merges PartialObject and PartialMethodObject. Also adds more tests.
Some parts are still WIP, see: #1522.

Fixes #1519
2020-03-13 23:47:48 +01:00
Dave Halter 5cd212c51c Merge branch 'expandtab' of https://github.com/Carreau/jedi
Also modify the test a bit to make sure that it passes properly if there are
folders present.
2020-03-13 23:40:48 +01:00
Daniel Lemm fd6540a9e5 Fix PartialMethodObject (WIP)
Implemented feedback from PR #1522.
Does not pass new tests in test/completion/stdlib.py
2020-03-13 21:40:58 +01:00
Dave Halter 521e240c5f Changed semantics of ClassVar attributes in classes, fixes #1502 2020-03-13 12:54:29 +01:00
Dave Halter b4fa42a282 Avoid duplicate definitions for goto, fixes #1514 2020-03-13 02:22:05 +01:00
Dave Halter fb72e1b448 Merge _remove_statements and infer_expr_stmt, fixes #1504 2020-03-13 00:50:25 +01:00
Peter Law da9d312185 Remove redundant attribute check 2020-03-12 22:06:13 +00:00
Daniel Lemm 96c969687a Add partialmethod, fixes #1519
Returns correct method signature but test/completion/stdlib.py fails
2020-03-12 18:47:17 +01:00
Dave Halter f83844408f Some minor refactorings for string quotes 2020-03-11 19:32:26 +01:00
Dave Halter b247423184 Indentation 2020-03-11 19:26:59 +01:00
Dave Halter 9c77113e21 Fix string completions with quote prefixes, fixes #1503 2020-03-11 19:26:42 +01:00
Dave Halter 91857c2c0a Fix issues with iter_module_names 2020-03-11 00:19:40 +01:00
Dave Halter 886dadaaff Skip more tests for Python 2/3.5 2020-03-10 20:17:39 +01:00
Dave Halter d574162da3 Fix namedtuple docstring/signature issues, fixes #1506 2020-03-10 20:07:10 +01:00
Dave Halter 0aa1ef6639 Move an import to the top 2020-03-10 09:36:45 +01:00
Dave Halter 33c61b8708 Make a method public 2020-03-10 09:35:03 +01:00
Dave Halter bedf3bff0e Add Project.complete_search instead of the complete param 2020-03-10 08:31:15 +01:00
Dave Halter d838eaecd2 Implement Script.complete_search instead of the complete param and return Completion objects 2020-03-09 23:55:17 +01:00
Dave Halter cf3d83ee4f Don't mix up caches for stubs and python files 2020-03-09 17:48:36 +01:00
Dave Halter 7247c32990 Refactor load_module_from_path to be simpler 2020-03-09 17:40:14 +01:00
Dave Halter 75ae73ee97 Load -stubs packages properly in _load_python_module 2020-03-09 17:27:51 +01:00
Dave Halter 753440682e Some further testing of code search with stubs 2020-03-08 15:12:57 +01:00
Dave Halter 53f39c88e4 Try to fix a few more stub issues in search 2020-03-08 15:02:00 +01:00
Dave Halter d3e3021a3d Care better about stubs for code search 2020-03-08 13:16:06 +01:00
Dave Halter e46e1269a2 Finally use the string_names attribute to identify module names instead of some fucked up path calculation. 2020-03-08 12:58:44 +01:00
Dave Halter a5f7412296 Load stub modules if it's a stub 2020-03-08 11:51:39 +01:00
Peter Law b198434694 Remove resolved TODO
The common logic this refers to has now been extracted (see 95cec459)
and the remaining checks are specific to tuple handling.
2020-03-07 20:29:14 +00:00
Dave Halter 58998748e3 Make it clear in search tests if a stub or a normal definition is expected 2020-03-07 20:43:57 +01:00
Dave Halter 6bddca011c Listing modules is no longer done by a subprocess 2020-03-07 20:25:58 +01:00
Dave Halter f147cb1133 Make it possible to get stdlib modules for project search 2020-03-07 19:42:27 +01:00
Peter Law d06efd0dd1 Push fetching of generics into nested function
This slightly simplifies both the calling code and semantics of
the nested function.
2020-03-07 18:09:20 +00:00
Peter Law 96132587b7 Clarify generic tuple inference
This hoist a loop invariant conditional check outside the loop
making it clearer and one branch more obviously similar to the
general type handling.
2020-03-07 17:35:29 +00:00
Peter Law 5d273f4630 Explain these branches 2020-03-07 17:35:03 +00:00
Peter Law 95cec459a8 Extract nested function for common pattern
This slightly simplifies the code, as well as providing a place
to put an explanation of what the moved block of code does.
2020-03-07 17:06:22 +00:00
Peter Law 3b4fa2aa9c Clarify variable name 2020-03-07 16:32:38 +00:00
Peter Law 54e29eede1 Add explanation of the parameters to _infer_type_vars 2020-03-07 16:31:12 +00:00
Dave Halter c159b9debd Get namespace package searches working 2020-03-07 17:14:47 +01:00
Dave Halter eecdf31601 Make it possible to search folders __init__ files 2020-03-07 13:57:14 +01:00
Dave Halter 7f2f025866 Move get_module_names to api.helpers 2020-03-06 14:32:52 +01:00
Dave Halter ed3564831c Some minor test reworks 2020-03-06 14:28:48 +01:00
Dave Halter 8c1e518ab7 Make sure you can search for 'def something' 2020-03-06 14:27:29 +01:00
Dave Halter c7a862ec19 Fix issues where references were identified as definitions 2020-03-06 14:24:57 +01:00
Dave Halter 6e3bd38600 Start merging efforts for project search and file search
First project tests are passing
2020-03-06 13:32:04 +01:00
Dave Halter e6bdaea73e Actually implement symbol search for projects 2020-03-06 11:15:34 +01:00
Dave Halter ebb9df07f3 Progress for recursive symbol searches 2020-03-06 10:31:48 +01:00
Dave Halter 8df917f1df Fix a getattr_static issue, fixes #1517 2020-03-06 10:07:23 +01:00
Dave Halter 30f72c48c4 Test that full_name in funcs work 2020-03-01 20:11:00 +01:00
Dave Halter e03924895b Add tests for search 2020-03-01 19:52:49 +01:00
Dave Halter af055ec69c Some minor refactorings of search 2020-03-01 19:39:26 +01:00
Dave Halter 9d8ad4cc04 Implement a search function, fixes #225 2020-03-01 18:47:01 +01:00
Dave Halter a6ef8efb72 fuzzy_match and start_match are now match with fuzzy param 2020-03-01 18:03:13 +01:00
Dave Halter ccc1262a3e Avoid one more private access 2020-03-01 17:53:39 +01:00
Dave Halter 656324f686 Disable some more tests for Python 2 2020-03-01 13:30:41 +01:00
Dave Halter bd1ef659e8 Make InterpreterEnvironment public 2020-03-01 12:47:26 +01:00
Dave Halter afc61c2576 is_typeddict should be part of ClassMixin 2020-03-01 12:26:40 +01:00
Dave Halter 4d5373d626 Don't continue searching for values if an annotation is found 2020-03-01 12:25:46 +01:00
Dave Halter 609737322d TypedDict checking should be at a later point 2020-03-01 02:34:38 +01:00
Dave Halter fa63c92cf7 Simplify tests a bit 2020-03-01 01:56:49 +01:00
Dave Halter e5fabb4c5f Fix some version issue stuff 2020-03-01 01:42:22 +01:00
Dave Halter bb91b96286 Merge branch 'typeddict' of https://github.com/pappasam/jedi 2020-03-01 01:31:17 +01:00
Dave Halter fd23946de3 Avoid universal newlines even more 2020-03-01 01:12:47 +01:00
Dave Halter a2b8c44e8f Get rid of Python's universal newlines for refactoring 2020-02-29 23:34:49 +01:00
Dave Halter 0a1de619b4 Reverse order of travis tests 2020-02-28 12:48:08 +01:00
Dave Halter 31d5c92dae Reverse order of tests in appveyor 2020-02-28 12:47:18 +01:00
Dave Halter d1873f8e1e Windows uses backslashes for paths 2020-02-28 12:42:39 +01:00
Dave Halter 58ba47841c Use inline_mod instead of some_mod for inline refactor tests 2020-02-28 01:53:35 +01:00
Dave Halter 0f2d6ac27a Undo some .travis.yml changes that were removed because of Python 3.4 drop 2020-02-28 00:22:29 +01:00
Dave Halter 76ce422590 Make refactoring diff path a relative path to the project path 2020-02-28 00:17:14 +01:00
Dave Halter 1f773d8e65 Refactoring is not allowed for environments and the current version lower than 3.6 2020-02-27 23:24:23 +01:00
Dave Halter 4451d2fec7 Refactoring diffs now show relative paths 2020-02-27 23:23:24 +01:00
Dave Halter 0ef8053919 Don't use a random grammar for extract 2020-02-27 22:50:30 +01:00
Dave Halter 140a45081f Python 3.5 is not supported for refactorings 2020-02-27 19:01:08 +01:00
Dave Halter ebdaf0177d Don't continue searching for values if an annotation is found 2020-02-27 18:47:13 +01:00
Dave Halter f2f11bc574 Remove some code for 3.3 compatibility 2020-02-27 18:31:50 +01:00
Dave Halter 5f2a402b19 Removed some more 3.4 usages 2020-02-27 18:30:46 +01:00
Dave Halter 5f226bc82e Make sure to not execute refactoring tests for Python 2 2020-02-27 02:17:05 +01:00
Dave Halter a892887b04 Remove Python 3.4 support 2020-02-27 02:04:03 +01:00
Dave Halter d1ac00f64f Fix run.py issue 2020-02-27 01:44:01 +01:00
Dave Halter 03e1770a24 Fix rename refactoring tests 2020-02-27 01:23:07 +01:00
Dave Halter 42adadd0cb Add an extract test for methods without params 2020-02-27 01:19:01 +01:00
Dave Halter 3708ab3514 Make extract yield error message better 2020-02-27 01:12:34 +01:00
Dave Halter c9334d140b Make it impossible to extract if return is not at the end 2020-02-27 01:08:03 +01:00
Dave Halter 35e992c37c Make sure that return at the end works properly for extract 2020-02-27 00:54:40 +01:00
Dave Halter a92c28840b Fix: Extract can now deal with return statements at the end 2020-02-26 09:31:33 +01:00
Dave Halter c96994dd8d Add a method extract test 2020-02-26 01:11:04 +01:00
Dave Halter bb6f0d5e91 Fix extract: better input filtering 2020-02-26 00:59:04 +01:00
Dave Halter bf9a3a4ca8 Rewrite an extract test to make them more diverse 2020-02-26 00:24:27 +01:00
Dave Halter eef47e951e One more function test 2020-02-26 00:21:46 +01:00
Dave Halter 17892556f8 Fix another comment extraction issue 2020-02-26 00:17:44 +01:00
Dave Halter b65c1c26aa Fix a function extract indentation issue 2020-02-25 23:52:23 +01:00
Dave Halter bc3e1ada03 One more comment test for extract with range 2020-02-25 23:30:44 +01:00
Dave Halter 1f82efa86d Fix a newline issue for refactoring functions 2020-02-25 23:27:21 +01:00
Dave Halter 94c00229f2 Make it possible to include comments for extract function 2020-02-25 23:25:50 +01:00
Dave Halter 5614ef2fed Move all the extract stuff into a different file 2020-02-25 10:33:31 +01:00
Dave Halter 8ff5ca81d2 Make a package out of refactoring 2020-02-25 10:28:27 +01:00
Dave Halter ff60c0af87 Docstrings 2020-02-25 10:27:36 +01:00
Dave Halter 89398e5c87 Deal a lot better with prefixes in range extractions 2020-02-25 10:23:38 +01:00
Dave Halter f8d9f498d0 Get a first extract test mostly working 2020-02-24 10:12:38 +01:00
Peter Law 30738a092b Update sith's module docstring to match the available operations 2020-02-24 01:33:46 +01:00
Dave Halter f527138e6c Extract: Fix param order for methods 2020-02-24 00:19:34 +01:00
Dave Halter 24a4c3ceba Test closure extraction 2020-02-23 23:56:59 +01:00
Dave Halter 48e25c1b9b Extract: Make sure params are not duplicated 2020-02-23 23:22:38 +01:00
Peter Law f1a9e681ad Ensure comprehensions and generator expressions work 2020-02-23 15:25:28 +00:00
Peter Law f4cbf61604 Ensure variadic tuples (Tuple[T, ...]) behave like sequences 2020-02-23 14:00:39 +00:00
Peter Law 5e990d9206 Support passing through values for non-annotated tuples 2020-02-23 14:00:16 +00:00
Peter Law 80db4dcf56 Add test to ensure unions work 2020-02-23 14:00:16 +00:00
Peter Law e557129121 Remove check which doesn't seem to be needed
I'm not sure why I added this, though removing it doesn't seem to
casue any issues. I suspect there might be some oddness if the type
being passed in doesn't match the type expected, though them having
the same number of generic paramters isn't an expecially great way
to validate that.
2020-02-23 14:00:16 +00:00
Peter Law c15e0ef9b8 Ensure specialised types inheriting from generics work 2020-02-23 14:00:15 +00:00
Peter Law e455709a31 Add test case for nested generic callables 2020-02-23 14:00:13 +00:00
Peter Law c03ae0315e Make nested Type[T] annotations work 2020-02-23 13:59:44 +00:00
Peter Law bc53dabce3 Make tuple generic parameters work 2020-02-23 13:59:44 +00:00
Peter Law 969a8f1fd9 First pass at extending infer_type_vars
This mostly works for the new tests, but doesn't work for:
- tuples (though this seems to be because they lack generic information anyway)
- nested Type[T] handling (e.g: List[Type[T]])
2020-02-23 13:59:44 +00:00
Peter Law 0a7820f6de Add many test cases
While these definitely _ought_ to work on Python 2.7, the annotation
support there is very limited and as Python 2 is deprecated it
doesn't seem worth it.
2020-02-23 13:58:10 +00:00
Dave Halter da935baa99 Some more extract improvements 2020-02-23 12:06:37 +01:00
Dave Halter cc8483a07a Fix extract issues when self is involved 2020-02-23 11:50:05 +01:00
Dave Halter 48c4262f66 Start trying to find param names 2020-02-23 01:55:43 +01:00
Dave Halter d069a4e482 Add a test for extraction in a class 2020-02-23 01:41:51 +01:00
Dave Halter 2061919b64 Get staticmethod working 2020-02-23 01:36:45 +01:00
Dave Halter a7110a4e08 Get a first classmethod extraction working 2020-02-23 00:40:31 +01:00
Dave Halter b7be5a4fe2 Extract: Correct newlines for classes and make it possible to be on a return/yield statement 2020-02-23 00:24:34 +01:00
Dave Halter 876109267a Remove is_function_execution, it's not used 2020-02-23 00:16:46 +01:00
Dave Halter 1c0f9e1f30 Extract functions properly out of functions 2020-02-22 21:24:06 +01:00
Peter Law 6efafb348e Extract the annotation name upfront
We almost always need this and this simplifies the code within
each branch. This also means we'll be able to the name to determine
the branching.
2020-02-22 19:42:08 +00:00
Peter Law 36b4b797c1 Add trailing comma 2020-02-22 19:42:08 +00:00
Dave Halter ce1093406a Get some first extract_function stuff working 2020-02-22 00:04:11 +01:00
Dave Halter dcffe8e60b Some refactorings and final tests for extract variable 2020-02-21 03:15:40 +01:00
Dave Halter 0516637e8d Fix an extract case about "not" 2020-02-21 03:03:48 +01:00
Dave Halter 3bc66c2f00 Fix some error cases for extract 2020-02-21 02:22:54 +01:00
Dave Halter 742c4370b5 Fix some last extract issues 2020-02-21 01:57:12 +01:00
Dave Halter 292ad9d9ac Enable extracting of parts of nodes 2020-02-21 01:43:36 +01:00
Dave Halter 3457bd77eb Make sure that extract variable works for some ranges 2020-02-20 23:34:09 +01:00
Lior Goldberg 1874e9be81 Remove the word 'class' from annotation_string
Currently, 'foo(x: int)' results with annotation_string="<class 'int'>".
Change this to 'int'.
2020-02-20 09:35:01 +01:00
Dave Halter 3f86d803d2 Fix another special extract case 2020-02-20 01:29:04 +01:00
Dave Halter 26bf2ceb15 Fix refactoring of leaves just before leaves 2020-02-20 00:43:02 +01:00
Dave Halter bfa15c61f1 Keyword extraction is now working better 2020-02-19 09:25:59 +01:00
Dave Halter 61619c4db1 Test keyword extraction 2020-02-19 09:20:12 +01:00
Dave Halter 50be49544d Move indent_block to common 2020-02-19 09:15:39 +01:00
Dave Halter b1d3c7ef52 Move indent_block to a separate utils 2020-02-18 18:50:40 +01:00
Dave Halter 7dff25f7c9 Test extracing of base classes 2020-02-17 10:06:40 +01:00
Dave Halter ab4fe548f2 Handle params better for extract variable 2020-02-17 09:55:11 +01:00
Peter Law c4cf0d78e1 Add a couple of docstrings
These are based on observation of the outputs of these functions.
2020-02-15 12:25:12 +01:00
Dave Halter d1f7400829 First implementation of extract variable 2020-02-15 12:17:29 +01:00
Dave Halter ee8cdb667d Make it possible to test refactoring outputs a bit different 2020-02-15 00:59:26 +01:00
Dave Halter 24114ba631 Remove reorder imports. For now this is not a priority 2020-02-14 23:56:11 +01:00
Dave Halter 9d171609da Fix some inline tests about different modules and atom_expr/trailer combinations 2020-02-14 18:02:37 +01:00
Dave Halter 518d2449a7 More inline tests 2020-02-14 17:26:58 +01:00
Dave Halter a906a76ccd Don't support refactoring for Python 2 2020-02-14 17:19:21 +01:00
Dave Halter af20905f7d Make sure the brackets are set properly 2020-02-14 17:08:42 +01:00
Dave Halter d536a20019 Fix some whitespace refactoring when inlining 2020-02-14 16:57:25 +01:00
Dave Halter bcefb04d54 add some more test for inline errors 2020-02-14 15:49:18 +01:00
Dave Halter dac2655915 Make sure to test errors for inlining 2020-02-14 15:30:49 +01:00
Dave Halter 14180ad185 Make sure to have a rename test if no name is under the cursor 2020-02-14 14:24:05 +01:00
Dave Halter dbf88f2750 Make it possible to be able to test errors for refactorings 2020-02-14 14:15:57 +01:00
Dave Halter 0a3ff6bd70 Implement inline refactorings 2020-02-14 13:53:41 +01:00
Sam Roeca d6f6c29a63 TypedDict test: fix Bar inheritance checks
Note: foo is defined as a function a the module level so I remove it
from consideration here to avoid complicating this test with other tests
in the module.
2020-02-13 10:43:41 -05:00
Peter Law c7d1b8de9e Tell sith that 'completions' became 'complete' 2020-02-13 09:51:31 +01:00
Dave Halter b4628abc60 Some sother small test improvements 2020-02-13 09:34:33 +01:00
Dave Halter aef675c79b Rewrite old refactoring tests a bit to reuse them 2020-02-13 09:27:57 +01:00
Dave Halter 41602124c7 Prepare remaining refactoring methods that should be implemented at some point 2020-02-13 09:27:36 +01:00
Dave Halter 5c246649e2 Test renames better and change some small things about the refactoring API 2020-02-13 00:19:34 +01:00
Dave Halter 6c9f187884 Refactor the rename tests a bit 2020-02-13 00:19:00 +01:00
Dave Halter 871575b06c Make sure that get_changed_files returns a dict 2020-02-12 09:59:39 +01:00
Dave Halter fd4ba3f47e Make sure to that renames works for keyword params 2020-02-12 01:19:47 +01:00
Dave Halter 204b072388 Add tests for undefined variables 2020-02-12 01:08:47 +01:00
Dave Halter e7ab318107 Make sure rename diffs have the right paths 2020-02-12 01:00:13 +01:00
Dave Halter 52d72157c0 Rename a module to make refactoring tests a bit faster 2020-02-12 00:35:49 +01:00
Sam Roeca ac47866c4c TypedDict: fix non-inheritance tests, add inheritance
Note: tests currently failing
2020-02-11 18:32:15 -05:00
Dave Halter c47021150e Add a rename test for combination of variables and modules 2020-02-11 23:43:09 +01:00
Dave Halter a39b2e95c1 Add another refactoring test 2020-02-11 21:13:55 +01:00
Jma353 d42d3f45f0 Add venv to .gitignore 2020-02-11 19:08:47 +01:00
Dave Halter b4494e588f A prefixed path should not also be suffixed 2020-02-11 18:34:41 +01:00
Dave Halter 0697a39145 Make refactoring tests a bit clearer 2020-02-11 10:08:36 +01:00
Dave Halter e43b0cec4a Get renames working for module imports 2020-02-11 01:35:07 +01:00
Dave Halter ab4f282b03 Move rename function to refactoring 2020-02-11 00:18:49 +01:00
Dave Halter 4bc9075d0b Add another rename test for imports 2020-02-10 21:17:22 +01:00
Dave Halter faddf412f9 Make some refactoring test variables private 2020-02-10 20:06:27 +01:00
Dave Halter e22a44d79e Remove a lot of nonsense from refactoring tests 2020-02-10 20:04:48 +01:00
Dave Halter 4cc03d2239 Add another rename test 2020-02-10 19:51:35 +01:00
Dave Halter 1e929b0aa0 Remove the old refactoring module 2020-02-10 17:48:24 +01:00
Dave Halter 13b393a5e3 Get the first rename test passing 2020-02-10 17:42:23 +01:00
Dave Halter 6166e7961e Make sure that tests for refactoring are redirected 2020-02-09 14:05:16 +01:00
Peter Law 370e539a7e Remove additional prefix which seems incorrect 2020-02-09 11:39:41 +01:00
Peter Law fd1f9f22e9 Update use of _source which no longer exists to _code 2020-02-09 11:39:41 +01:00
Dave Halter bcb7cc864c Make sure to move up VSCode, because it's used a lot 2020-02-08 20:09:46 +01:00
Dave Halter de2f753546 Revert "Make sure to mention that VSCode is using Jedi"
It was already in there.

This reverts commit 2cf06bcf48.
2020-02-08 20:06:17 +01:00
Dave Halter 2cf06bcf48 Make sure to mention that VSCode is using Jedi
It has been used for a long time
2020-02-08 20:04:47 +01:00
Sam Roeca cf954bf006 Expand on TypedDict tests.
Adds a function that takes the TypedDict as an argument.

Note: the last two tests are failing, along with lots of other tests
throughout the system.
2020-02-07 14:40:39 -05:00
Sam Roeca 9d2083fa08 Remove argument to filter.values()
Given 87161df2, values(from_instance=False) doesn't produce completions
anymore. Therefore, we remove from_instance as an argument.
2020-02-07 13:38:52 -05:00
Sam Roeca 6a9745b42b Get basic completions working with TypedDict 2020-02-07 13:24:00 -05:00
Dave Halter 87161df2f0 Make sure that typeddict py__getitem__ works 2020-02-07 16:45:03 +01:00
Dave Halter 7ef07b576f Merge branch 'master' into typeddict 2020-02-07 04:03:27 +01:00
Dave Halter 6e63799a7d Fix a test that picked up the wrong paths 2020-02-06 22:51:40 +01:00
Dave Halter 841fe75326 Fix an issue with environment selection 2020-02-06 22:41:11 +01:00
Dave Halter f6465c5202 Get rid of one more os.getcwd() call 2020-02-06 01:51:10 +01:00
Dave Halter 14ac0512a9 Get rid of cwd modifications in tests 2020-02-06 01:47:39 +01:00
Dave Halter f2722952e7 Fix load_unsafe_extensions issue 2020-02-05 10:01:21 +01:00
Dave Halter b7919bd3e6 Merge branch 'master' into project 2020-02-04 23:56:47 +01:00
Dave Halter 7a55484b79 Fix a test issue 2020-02-04 23:56:01 +01:00
Dave Halter 670d6e8639 Move is_side_effect to Definition and correct bugs 2020-02-04 20:12:24 +01:00
Dave Halter 6313934d94 Add a docstring for is_side_effect 2020-02-04 19:39:13 +01:00
Dave Halter 40fced2450 Actually use follow_builtin_imports and improve the goto docstring, fixes #1492 2020-02-04 19:34:42 +01:00
Dave Halter 692bf5cfb7 Properly identify side effects, fixes #1411 2020-02-04 10:12:13 +01:00
Dave Halter 66e28eb52e Move test_api/test_defined_names.py -> test_api/test_names.py 2020-02-04 10:03:55 +01:00
Dave Halter 3388a9659b Catch an error with illegal class instances, fixes #1491 2020-02-03 22:27:48 +01:00
Dave Halter eb88c483fb Catch an error with illegal class instances, fixes #1491 2020-02-03 22:27:22 +01:00
Dave Halter 2c62166ff6 Get parser errors working, fixes #1488 2020-02-03 22:06:12 +01:00
Dave Halter 3101e43aa6 Merge branch 'master' into project 2020-02-03 09:26:43 +01:00
Dave Halter a49c757b8a Make Ellipsis without list in Callable work, fixes #1475 2020-02-03 09:25:46 +01:00
Dave Halter 3ad3dc08b8 Run get_type_hint tests only for 3.6+ 2020-02-03 01:03:19 +01:00
Dave Halter eee919174d Stubs should not become stubs again in the conversion function, fixes #1475 2020-02-03 00:58:54 +01:00
Dave Halter e802f5aabd Make sure to print errors in __main__ completions 2020-02-02 23:28:55 +01:00
Dave Halter e3c4b5b77e Make sure param hints are working for functions 2020-02-02 18:42:01 +01:00
Dave Halter 4c7179bc87 Generate type hints, fixes #987 2020-02-02 16:55:10 +01:00
Dave Halter f4b1fc479d Bump version to 0.16.1 2020-01-31 13:38:27 +01:00
Dave Halter e1425de8a4 Make sure to be able to deal with all kinds of loaders, fixes #1487 2020-01-31 13:26:56 +01:00
Dave Halter 8ff2ea4b38 Make sure to not load unsafe modules anymore if they are not on the sys path, fixes #760 2020-01-31 13:09:28 +01:00
Dave Halter e7a77e438d Remove python_version again, it might not be needed 2020-01-31 02:15:24 +01:00
Dave Halter a05628443e Make sure serialization works for projects 2020-01-31 02:14:34 +01:00
Dave Halter d09882f970 Remove django from the project API 2020-01-31 01:50:52 +01:00
Dave Halter e5ec2a3adf Introduce two new Project params: python_path, python_version 2020-01-31 01:46:55 +01:00
Dave Halter d02af44331 Make it possible to use get_default_project directly from Jedi 2020-01-31 00:21:46 +01:00
Dave Halter 251ff447bc Add added_sys_path to Project, fixes #1334 2020-01-31 00:08:24 +01:00
Dave Halter 4a1d9a9116 Use project instead of sys_path parameter in tests 2020-01-30 21:02:47 +01:00
Dave Halter ceccbf3678 Make the Project API public, fixes #778 2020-01-30 19:24:16 +01:00
Dave Halter e930f47861 Make generators return more correct values with while loops, fixes #683 2020-01-29 10:13:46 +01:00
Dave Halter d630ed55f3 Avoid aborting search for yields when they are still reachable, see #683 2020-01-28 09:35:58 +01:00
Dave Halter bec87f7ff8 Jedi understand now when you use del, fixes #313 2020-01-26 20:07:56 +01:00
Dave Halter 045b8a35a2 Remove dead code 2020-01-26 19:39:15 +01:00
Dave Halter 8eb980db73 Create the basics to work with TypedDict in the future 2020-01-26 19:25:23 +01:00
ANtlord 8440e1719f Unuseful changes are rolled back. 2020-01-22 20:57:17 +02:00
ANtlord ddcd48edd8 Typeshed submodule checked out to d386452 2020-01-22 20:55:25 +02:00
ANtlord 9bc01da9c4 Fix conflicts. 2020-01-22 11:12:09 +02:00
ANtlord 2a86f7d82f Django-plugin related code is removed from stdlib-plugin. 2020-01-21 21:21:43 +02:00
ANtlord 7287d67e7a Functions infers type of Django model field is refactored. 2020-01-21 21:12:38 +02:00
ANtlord c61ca0d27b Infering of django model fields is moved to a dedicated module. 2020-01-19 18:46:28 +02:00
ANtlord a6dfc130c9 Foreign key is handled. 2020-01-16 15:40:45 +02:00
ANtlord 654475b7d6 Infering multiple fields is fixed. 2019-12-06 23:58:13 +02:00
ANtlord fbeff00761 Decimal, DurationField, DateField, DateTimeField, DecimalField django types are resolved. 2019-12-06 23:47:19 +02:00
ANtlord 4b15c8459a Merge branch 'master' of https://github.com/davidhalter/jedi 2019-10-28 08:52:56 +02:00
ANtlord 893b695a61 Merge branch 'master' of https://github.com/davidhalter/jedi 2019-10-21 22:27:06 +03:00
ANtlord 659aaf6861 Naming corrections. 2019-09-19 08:42:39 +03:00
ANtlord d68545d8de Merge branch 'master' of https://github.com/davidhalter/jedi 2019-09-18 09:28:30 +03:00
ANtlord f5ae7148dd Basic django model fields are infered as builtin types. 2019-09-18 09:27:39 +03:00
Matthias Bussonnier 5329f95096 Attempt at a test of completion of filepath after ~.
I'm not quite sure how this will behave on windows, and we can't really
create a tempdir (as we don't want to mess with path on home.

One possibility would be to mock/monkeypatch scandir, listdir and
os.path.expanduser or set $HOME in env; but I'm quite unsure we want to
go that route.
2019-08-25 19:55:33 +02:00
Matthias Bussonnier 9a3f41e63b Complete path after ~.
Note this is mostly to discuss as if I understood one of your message on
Twitter, this was not possible without fuzzy completion.

I tried with just this patch and that works great.

Note that unlike IPython that right now does :

    ~/<tab> -> /Full/Path/to/user/home

But with this patch this just complete things correctly without
expanding the tab. And I think not expanding the tab is actually better.

Anyway, open that to better understand the why you were waiting for
fuzzy completion.
2019-08-23 17:57:12 +02:00
Daniel Hahler 61bc15b1aa docs: fix some incorrect reference and improve wording 2018-07-01 21:49:18 +02:00
Daniel Hahler 5bad06d4b6 docs: enable searchbox 2018-07-01 21:49:18 +02:00
234 changed files with 10162 additions and 5736 deletions

No files matched your search

-1
View File
@@ -4,7 +4,6 @@ omit =
jedi/inference/compiled/subprocess/__main__.py jedi/inference/compiled/subprocess/__main__.py
jedi/__main__.py jedi/__main__.py
# For now this is not being used. # For now this is not being used.
jedi/refactoring.py
[report] [report]
# Regexes for lines to exclude from consideration # Regexes for lines to exclude from consideration
+14
View File
@@ -0,0 +1,14 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.py]
indent_size = 4
[*.md]
indent_size = 2
+10
View File
@@ -0,0 +1,10 @@
# all end-of-lines are normalized to LF when written to the repository
# https://git-scm.com/docs/gitattributes#_text
* text=auto
# force all text files on the working dir to have LF line endings
# https://git-scm.com/docs/gitattributes#_eol
* text eol=lf
# PNGs are not text and should not be normalized
*.png -text
+76
View File
@@ -0,0 +1,76 @@
name: ci
on: push
jobs:
tests:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-20.04, windows-2019]
python-version: [3.9, 3.8, 3.7, 3.6]
environment: ['3.8', '3.9', '3.7', '3.6', 'interpreter']
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: recursive
- uses: actions/setup-python@v2
if: ${{ matrix.environment != 'interpreter' }}
with:
python-version: ${{ matrix.environment }}
- uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: 'pip install .[testing]'
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
- name: Run tests
run: python -m pytest
env:
JEDI_TEST_ENVIRONMENT: ${{ matrix.environment }}
code-quality:
runs-on: ubuntu-20.04
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: recursive
- name: Install dependencies
run: 'pip install .[qa]'
- name: Run tests
run: |
python -m flake8 jedi setup.py
python -m mypy jedi sith.py
coverage:
runs-on: ubuntu-20.04
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: recursive
- name: Install dependencies
run: 'pip install .[testing] coverage'
- name: Run tests
run: |
python -m coverage run --source jedi -m pytest
python -m coverage report
- name: Upload coverage data
run: |
pip install --quiet codecov coveralls
python -m coverage xml
python -m coverage report -m
bash <(curl -s https://codecov.io/bash) -X gcov -X coveragepy -X search -X fix -X xcode -f coverage.xml
+2 -1
View File
@@ -2,7 +2,6 @@
*.sw? *.sw?
*.pyc *.pyc
.ropeproject .ropeproject
.tox
.coveralls.yml .coveralls.yml
.coverage .coverage
.idea .idea
@@ -13,3 +12,5 @@ jedi.egg-info/
record.json record.json
/.cache/ /.cache/
/.pytest_cache /.pytest_cache
/.mypy_cache
/venv/
+3
View File
@@ -1,3 +1,6 @@
[submodule "jedi/third_party/typeshed"] [submodule "jedi/third_party/typeshed"]
path = jedi/third_party/typeshed path = jedi/third_party/typeshed
url = https://github.com/davidhalter/typeshed.git url = https://github.com/davidhalter/typeshed.git
[submodule "jedi/third_party/django-stubs"]
path = jedi/third_party/django-stubs
url = https://github.com/davidhalter/django-stubs
+2
View File
@@ -0,0 +1,2 @@
python:
pip_install: true
-73
View File
@@ -1,73 +0,0 @@
dist: xenial
language: python
python:
- 2.7
- 3.4
- 3.5
- 3.6
- 3.7
- 3.8
env:
- JEDI_TEST_ENVIRONMENT=27
- JEDI_TEST_ENVIRONMENT=34
- JEDI_TEST_ENVIRONMENT=35
- JEDI_TEST_ENVIRONMENT=36
- JEDI_TEST_ENVIRONMENT=37
- JEDI_TEST_ENVIRONMENT=38
- JEDI_TEST_ENVIRONMENT=interpreter
matrix:
include:
- python: 3.7
env:
- TOXENV=cov-py37
- JEDI_TEST_ENVIRONMENT=37
# For now ignore pypy, there are so many issues that we don't really need
# to run it.
#- python: pypy
# The 3.9 dev build does not seem to be available end 2019.
#- python: 3.9-dev
# env:
# - JEDI_TEST_ENVIRONMENT=39
install:
- pip install --quiet tox-travis
- sudo apt-get -y install python3-venv
script:
- |
# Setup/install Python for $JEDI_TEST_ENVIRONMENT.
set -ex
test_env_version=${JEDI_TEST_ENVIRONMENT:0:1}.${JEDI_TEST_ENVIRONMENT:1:1}
if [ "$TRAVIS_PYTHON_VERSION" != "$test_env_version" ] && [ "$JEDI_TEST_ENVIRONMENT" != "interpreter" ]; then
python_bin=python$test_env_version
python_path="$(which $python_bin || true)"
if [ -z "$python_path" ]; then
# Only required for JEDI_TEST_ENVIRONMENT=34.
download_name=python-$test_env_version
wget https://s3.amazonaws.com/travis-python-archives/binaries/ubuntu/16.04/x86_64/$download_name.tar.bz2
sudo tar xjf $download_name.tar.bz2 --directory / opt/python
ln -s "/opt/python/${test_env_version}/bin/python" /home/travis/bin/$python_bin
elif [ "${python_path#/opt/pyenv/shims}" != "$python_path" ]; then
# Activate pyenv version (required with JEDI_TEST_ENVIRONMENT=36).
pyenv_bin="$(pyenv whence --path "$python_bin" | head -n1)"
ln -s "$pyenv_bin" /home/travis/bin/$python_bin
fi
$python_bin --version
python_ver=$($python_bin -c 'import sys; print("%d%d" % sys.version_info[0:2])')
if [ "$JEDI_TEST_ENVIRONMENT" != "$python_ver" ]; then
echo "Unexpected Python version for $JEDI_TEST_ENVIRONMENT: $python_ver"
set +ex
exit 2
fi
fi
set +ex
- tox
after_script:
- |
if [ $TOXENV == "cov-py37" ]; 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
+60 -52
View File
@@ -1,59 +1,67 @@
Main Authors Main Authors
============ ------------
David Halter (@davidhalter) <davidhalter88@gmail.com> - David Halter (@davidhalter) <davidhalter88@gmail.com>
Takafumi Arakaki (@tkf) <aka.tkf@gmail.com> - Takafumi Arakaki (@tkf) <aka.tkf@gmail.com>
Code Contributors Code Contributors
================= -----------------
Danilo Bargen (@dbrgn) <mail@dbrgn.ch> - Danilo Bargen (@dbrgn) <mail@dbrgn.ch>
Laurens Van Houtven (@lvh) <_@lvh.cc> - Laurens Van Houtven (@lvh) <_@lvh.cc>
Aldo Stracquadanio (@Astrac) <aldo.strac@gmail.com> - Aldo Stracquadanio (@Astrac) <aldo.strac@gmail.com>
Jean-Louis Fuchs (@ganwell) <ganwell@fangorn.ch> - Jean-Louis Fuchs (@ganwell) <ganwell@fangorn.ch>
tek (@tek) - tek (@tek)
Yasha Borevich (@jjay) <j.borevich@gmail.com> - Yasha Borevich (@jjay) <j.borevich@gmail.com>
Aaron Griffin <aaronmgriffin@gmail.com> - Aaron Griffin <aaronmgriffin@gmail.com>
andviro (@andviro) - andviro (@andviro)
Mike Gilbert (@floppym) <floppym@gentoo.org> - Mike Gilbert (@floppym) <floppym@gentoo.org>
Aaron Meurer (@asmeurer) <asmeurer@gmail.com> - Aaron Meurer (@asmeurer) <asmeurer@gmail.com>
Lubos Trilety <ltrilety@redhat.com> - Lubos Trilety <ltrilety@redhat.com>
Akinori Hattori (@hattya) <hattya@gmail.com> - Akinori Hattori (@hattya) <hattya@gmail.com>
srusskih (@srusskih) - srusskih (@srusskih)
Steven Silvester (@blink1073) - Steven Silvester (@blink1073)
Colin Duquesnoy (@ColinDuquesnoy) <colin.duquesnoy@gmail.com> - Colin Duquesnoy (@ColinDuquesnoy) <colin.duquesnoy@gmail.com>
Jorgen Schaefer (@jorgenschaefer) <contact@jorgenschaefer.de> - Jorgen Schaefer (@jorgenschaefer) <contact@jorgenschaefer.de>
Fredrik Bergroth (@fbergroth) - Fredrik Bergroth (@fbergroth)
Mathias Fußenegger (@mfussenegger) - Mathias Fußenegger (@mfussenegger)
Syohei Yoshida (@syohex) <syohex@gmail.com> - Syohei Yoshida (@syohex) <syohex@gmail.com>
ppalucky (@ppalucky) - ppalucky (@ppalucky)
immerrr (@immerrr) immerrr@gmail.com - immerrr (@immerrr) immerrr@gmail.com
Albertas Agejevas (@alga) - Albertas Agejevas (@alga)
Savor d'Isavano (@KenetJervet) <newelevenken@163.com> - Savor d'Isavano (@KenetJervet) <newelevenken@163.com>
Phillip Berndt (@phillipberndt) <phillip.berndt@gmail.com> - Phillip Berndt (@phillipberndt) <phillip.berndt@gmail.com>
Ian Lee (@IanLee1521) <IanLee1521@gmail.com> - Ian Lee (@IanLee1521) <IanLee1521@gmail.com>
Farkhad Khatamov (@hatamov) <comsgn@gmail.com> - Farkhad Khatamov (@hatamov) <comsgn@gmail.com>
Kevin Kelley (@kelleyk) <kelleyk@kelleyk.net> - Kevin Kelley (@kelleyk) <kelleyk@kelleyk.net>
Sid Shanker (@squidarth) <sid.p.shanker@gmail.com> - Sid Shanker (@squidarth) <sid.p.shanker@gmail.com>
Reinoud Elhorst (@reinhrst) - Reinoud Elhorst (@reinhrst)
Guido van Rossum (@gvanrossum) <guido@python.org> - Guido van Rossum (@gvanrossum) <guido@python.org>
Dmytro Sadovnychyi (@sadovnychyi) <jedi@dmit.ro> - Dmytro Sadovnychyi (@sadovnychyi) <jedi@dmit.ro>
Cristi Burcă (@scribu) - Cristi Burcă (@scribu)
bstaint (@bstaint) - bstaint (@bstaint)
Mathias Rav (@Mortal) <rav@cs.au.dk> - Mathias Rav (@Mortal) <rav@cs.au.dk>
Daniel Fiterman (@dfit99) <fitermandaniel2@gmail.com> - Daniel Fiterman (@dfit99) <fitermandaniel2@gmail.com>
Simon Ruggier (@sruggier) - Simon Ruggier (@sruggier)
Élie Gouzien (@ElieGouzien) - Élie Gouzien (@ElieGouzien)
Robin Roth (@robinro) - Robin Roth (@robinro)
Malte Plath (@langsamer) - Malte Plath (@langsamer)
Anton Zub (@zabulazza) - Anton Zub (@zabulazza)
Maksim Novikov (@m-novikov) <mnovikov.work@gmail.com> - Maksim Novikov (@m-novikov) <mnovikov.work@gmail.com>
Tobias Rzepka (@TobiasRzepka) - Tobias Rzepka (@TobiasRzepka)
micbou (@micbou) - micbou (@micbou)
Dima Gerasimov (@karlicoss) <karlicoss@gmail.com> - Dima Gerasimov (@karlicoss) <karlicoss@gmail.com>
Max Woerner Chase (@mwchase) <max.chase@gmail.com> - Max Woerner Chase (@mwchase) <max.chase@gmail.com>
Johannes Maria Frank (@jmfrank63) <jmfrank63@gmail.com> - Johannes Maria Frank (@jmfrank63) <jmfrank63@gmail.com>
Shane Steinert-Threlkeld (@shanest) <ssshanest@gmail.com> - Shane Steinert-Threlkeld (@shanest) <ssshanest@gmail.com>
Tim Gates (@timgates42) <tim.gates@iress.com> - Tim Gates (@timgates42) <tim.gates@iress.com>
- Lior Goldberg (@goldberglior)
- Ryan Clary (@mrclary)
- Max Mäusezahl (@mmaeusezahl) <maxmaeusezahl@googlemail.com>
- Vladislav Serebrennikov (@endilll)
- Andrii Kolomoiets (@muffinmad)
- Leo Ryu (@Leo-Ryu)
And a few more "anonymous" contributors.
Note: (@user) means a github user name. Note: (@user) means a github user name.
+80 -8
View File
@@ -3,6 +3,78 @@
Changelog Changelog
--------- ---------
Unreleased
++++++++++
- Implict namespaces are now a separate types in ``Name().type``
0.18.0 (2020-12-25)
+++++++++++++++++++
- Dropped Python 2 and Python 3.5
- Using ``pathlib.Path()`` as an output instead of ``str`` in most places:
- ``Project.path``
- ``Script.path``
- ``Definition.module_path``
- ``Refactoring.get_renames``
- ``Refactoring.get_changed_files``
- Functions with ``@property`` now return ``property`` instead of ``function``
in ``Name().type``
- Started using annotations
- Better support for the walrus operator
- Project attributes are now read accessible
- Removed all deprecations
This is likely going to be the last minor release before 1.0.
0.17.2 (2020-07-17)
+++++++++++++++++++
- Added an option to pass environment variables to ``Environment``
- ``Project(...).path`` exists now
- Support for Python 3.9
- A few bugfixes
This will be the last release that supports Python 2 and Python 3.5.
``0.18.0`` will be Python 3.6+.
0.17.1 (2020-06-20)
+++++++++++++++++++
- Django ``Model`` meta class support
- Django Manager support (completion on Managers/QuerySets)
- Added Django Stubs to Jedi, thanks to all contributors of the
`Django Stubs <https://github.com/typeddjango/django-stubs>`_ project
- Added ``SyntaxError.get_message``
- Python 3.9 support
- Bugfixes (mostly towards Generics)
0.17.0 (2020-04-14)
+++++++++++++++++++
- Added ``Project`` support. This allows a user to specify which folders Jedi
should work with.
- Added support for Refactoring. The following refactorings have been
implemented: ``Script.rename``, ``Script.inline``,
``Script.extract_variable`` and ``Script.extract_function``.
- Added ``Script.get_syntax_errors`` to display syntax errors in the current
script.
- Added code search capabilities both for individual files and projects. The
new functions are ``Project.search``, ``Project.complete_search``,
``Script.search`` and ``Script.complete_search``.
- Added ``Script.help`` to make it easier to display a help window to people.
Now returns pydoc information as well for Python keywords/operators. This
means that on the class keyword it will now return the docstring of Python's
builtin function ``help('class')``.
- The API documentation is now way more readable and complete. Check it out
under https://jedi.readthedocs.io. A lot of it has been rewritten.
- Removed Python 3.4 support
- Many bugfixes
This is likely going to be the last minor version that supports Python 2 and
Python3.5. Bugfixes will be provided in 0.17.1+. The next minor/major version
will probably be Jedi 1.0.0.
0.16.0 (2020-01-26) 0.16.0 (2020-01-26)
+++++++++++++++++++ +++++++++++++++++++
@@ -25,8 +97,8 @@ Changelog
- ``call_signatures`` deprecated, use ``get_signatures`` instead - ``call_signatures`` deprecated, use ``get_signatures`` instead
- ``usages`` deprecated, use ``get_references`` instead - ``usages`` deprecated, use ``get_references`` instead
- ``jedi.names`` deprecated, use ``jedi.Script(...).get_names()`` - ``jedi.names`` deprecated, use ``jedi.Script(...).get_names()``
- ``BaseDefinition.goto_assignments`` renamed to ``BaseDefinition.goto`` - ``BaseName.goto_assignments`` renamed to ``BaseName.goto``
- Add follow_imports to ``Definition.goto``. Now its signature matches - Add follow_imports to ``Name.goto``. Now its signature matches
``Script.goto``. ``Script.goto``.
- **Python 2 support deprecated**. For this release it is best effort. Python 2 - **Python 2 support deprecated**. For this release it is best effort. Python 2
has reached the end of its life and now it's just about a smooth transition. has reached the end of its life and now it's just about a smooth transition.
@@ -66,13 +138,13 @@ Changelog
New APIs: New APIs:
- ``Definition.get_signatures() -> List[Signature]``. Signatures are similar to - ``Name.get_signatures() -> List[Signature]``. Signatures are similar to
``CallSignature``. ``Definition.params`` is therefore deprecated. ``CallSignature``. ``Name.params`` is therefore deprecated.
- ``Signature.to_string()`` to format signatures. - ``Signature.to_string()`` to format signatures.
- ``Signature.params -> List[ParamDefinition]``, ParamDefinition has the - ``Signature.params -> List[ParamName]``, ParamName has the
following additional attributes ``infer_default()``, ``infer_annotation()``, following additional attributes ``infer_default()``, ``infer_annotation()``,
``to_string()``, and ``kind``. ``to_string()``, and ``kind``.
- ``Definition.execute() -> List[Definition]``, makes it possible to infer - ``Name.execute() -> List[Name]``, makes it possible to infer
return values of functions. return values of functions.
@@ -88,7 +160,7 @@ New APIs:
- Added ``goto_*(prefer_stubs=True)`` as well as ``goto_*(prefer_stubs=True)`` - Added ``goto_*(prefer_stubs=True)`` as well as ``goto_*(prefer_stubs=True)``
- Stubs are used now for type inference - Stubs are used now for type inference
- Typeshed is used for better type inference - Typeshed is used for better type inference
- Reworked Definition.full_name, should have more correct return values - Reworked Name.full_name, should have more correct return values
0.13.3 (2019-02-24) 0.13.3 (2019-02-24)
+++++++++++++++++++ +++++++++++++++++++
@@ -168,7 +240,7 @@ New APIs:
- Actual semantic completions for the complete Python syntax. - Actual semantic completions for the complete Python syntax.
- Basic type inference for ``yield from`` PEP 380. - Basic type inference for ``yield from`` PEP 380.
- PEP 484 support (most of the important features of it). Thanks Claude! (@reinhrst) - PEP 484 support (most of the important features of it). Thanks Claude! (@reinhrst)
- Added ``get_line_code`` to ``Definition`` and ``Completion`` objects. - Added ``get_line_code`` to ``Name`` and ``Completion`` objects.
- Completely rewritten the type inference engine. - Completely rewritten the type inference engine.
- A new and better parser for (fast) parsing diffs of Python code. - A new and better parser for (fast) parsing diffs of Python code.
+1 -3
View File
@@ -6,11 +6,9 @@ include .coveragerc
include sith.py include sith.py
include conftest.py include conftest.py
include pytest.ini include pytest.ini
include tox.ini
include requirements.txt
include jedi/parser/python/grammar*.txt
recursive-include jedi/third_party *.pyi recursive-include jedi/third_party *.pyi
include jedi/third_party/typeshed/LICENSE include jedi/third_party/typeshed/LICENSE
include jedi/third_party/django-stubs/LICENSE.txt
include jedi/third_party/typeshed/README include jedi/third_party/typeshed/README
recursive-include test * recursive-include test *
recursive-include docs * recursive-include docs *
+93 -102
View File
@@ -1,117 +1,106 @@
################################################################### ####################################################################################
Jedi - an awesome autocompletion/static analysis library for Python Jedi - an awesome autocompletion, static analysis and refactoring library for Python
################################################################### ####################################################################################
.. image:: https://img.shields.io/pypi/v/jedi.svg?style=flat .. image:: http://isitmaintained.com/badge/open/davidhalter/jedi.svg
:target: https://pypi.python.org/pypi/jedi :target: https://github.com/davidhalter/jedi/issues
:alt: PyPI version :alt: The percentage of open issues and pull requests
.. image:: https://img.shields.io/pypi/pyversions/jedi.svg .. image:: http://isitmaintained.com/badge/resolution/davidhalter/jedi.svg
:target: https://pypi.python.org/pypi/jedi :target: https://github.com/davidhalter/jedi/issues
:alt: Supported Python versions :alt: The resolution time is the median time an issue or pull request stays open.
.. image:: https://travis-ci.org/davidhalter/jedi.svg?branch=master .. image:: https://github.com/davidhalter/jedi/workflows/ci/badge.svg?branch=master
:target: https://travis-ci.org/davidhalter/jedi :target: https://github.com/davidhalter/jedi/actions
:alt: Linux Tests :alt: Tests
.. image:: https://ci.appveyor.com/api/projects/status/mgva3bbawyma1new/branch/master?svg=true .. image:: https://pepy.tech/badge/jedi
:target: https://ci.appveyor.com/project/davidhalter/jedi/branch/master :target: https://pepy.tech/project/jedi
:alt: Windows Tests :alt: PyPI Downloads
.. image:: https://coveralls.io/repos/davidhalter/jedi/badge.svg?branch=master
:target: https://coveralls.io/r/davidhalter/jedi
:alt: Coverage status
*If you have specific questions, please add an issue or ask on* `Stack Overflow Jedi is a static analysis tool for Python that is typically used in
<https://stackoverflow.com/questions/tagged/python-jedi>`_ *with the label* ``python-jedi``. IDEs/editors plugins. Jedi has a focus on autocompletion and goto
functionality. Other features include refactoring, code search and finding
references.
Jedi has a simple API to work with. There is a reference implementation as a
Jedi is a static analysis tool for Python that can be used in IDEs/editors. `VIM-Plugin <https://github.com/davidhalter/jedi-vim>`_. Autocompletion in your
Jedi has a focus on autocompletion and goto functionality. Jedi is fast and is REPL is also possible, IPython uses it natively and for the CPython REPL you
very well tested. It understands Python and stubs on a deep level. can install it. Jedi is well tested and bugs should be rare.
Jedi has support for different goto functions. It's possible to search for
references and list names in a Python file to get information about them.
Jedi uses a very simple API to connect with IDE's. 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.
Autocompletion in your REPL is also possible, IPython uses it natively and for
the CPython REPL you have to install it.
Jedi can currently be used with the following editors/projects: Jedi can currently be used with the following editors/projects:
- Vim (jedi-vim_, YouCompleteMe_, deoplete-jedi_, completor.vim_) - Vim (jedi-vim_, YouCompleteMe_, deoplete-jedi_, completor.vim_)
- `Visual Studio Code`_ (via `Python Extension <https://marketplace.visualstudio.com/items?itemName=ms-python.python>`_)
- Emacs (Jedi.el_, company-mode_, elpy_, anaconda-mode_, ycmd_) - Emacs (Jedi.el_, company-mode_, elpy_, anaconda-mode_, ycmd_)
- Sublime Text (SublimeJEDI_ [ST2 + ST3], anaconda_ [only ST3]) - Sublime Text (SublimeJEDI_ [ST2 + ST3], anaconda_ [only ST3])
- TextMate_ (Not sure if it's actually working) - TextMate_ (Not sure if it's actually working)
- Kate_ version 4.13+ supports it natively, you have to enable it, though. [`proof - Kate_ version 4.13+ supports it natively, you have to enable it, though. [`see
<https://projects.kde.org/projects/kde/applications/kate/repository/show?rev=KDE%2F4.13>`_] <https://projects.kde.org/projects/kde/applications/kate/repository/show?rev=KDE%2F4.13>`_]
- Atom_ (autocomplete-python-jedi_) - Atom_ (autocomplete-python-jedi_)
- `GNOME Builder`_ (with support for GObject Introspection) - `GNOME Builder`_ (with support for GObject Introspection)
- `Visual Studio Code`_ (via `Python Extension <https://marketplace.visualstudio.com/items?itemName=ms-python.python>`_)
- Gedit (gedi_) - Gedit (gedi_)
- wdb_ - Web Debugger - wdb_ - Web Debugger
- `Eric IDE`_ (Available as a plugin) - `Eric IDE`_ (Available as a plugin)
- `IPython 6.0.0+ <https://ipython.readthedocs.io/en/stable/whatsnew/version6.html>`_ - `IPython 6.0.0+ <https://ipython.readthedocs.io/en/stable/whatsnew/version6.html>`_
- `xonsh shell <https://xon.sh/contents.html>`_ has `jedi extension <https://xon.sh/xontribs.html#jedi>`_
and many more! and many more!
There are a few language servers that use Jedi:
- `jedi-language-server <https://github.com/pappasam/jedi-language-server>`_
- `python-language-server <https://github.com/palantir/python-language-server>`_
- `anakin-language-server <https://github.com/muffinmad/anakin-language-server>`_
Here are some pictures taken from jedi-vim_: Here are some pictures taken from jedi-vim_:
.. image:: https://github.com/davidhalter/jedi/raw/master/docs/_screenshots/screenshot_complete.png .. image:: https://github.com/davidhalter/jedi/raw/master/docs/_screenshots/screenshot_complete.png
Completion for almost anything (Ctrl+Space). Completion for almost anything:
.. image:: https://github.com/davidhalter/jedi/raw/master/docs/_screenshots/screenshot_function.png .. image:: https://github.com/davidhalter/jedi/raw/master/docs/_screenshots/screenshot_function.png
Display of function/class bodies, docstrings. Documentation:
.. image:: https://github.com/davidhalter/jedi/raw/master/docs/_screenshots/screenshot_pydoc.png .. image:: https://github.com/davidhalter/jedi/raw/master/docs/_screenshots/screenshot_pydoc.png
Pydoc support (Shift+k).
There is also support for goto and renaming.
Get the latest version from `github <https://github.com/davidhalter/jedi>`_ Get the latest version from `github <https://github.com/davidhalter/jedi>`_
(master branch should always be kind of stable/working). (master branch should always be kind of stable/working).
Docs are available at `https://jedi.readthedocs.org/en/latest/ Docs are available at `https://jedi.readthedocs.org/en/latest/
<https://jedi.readthedocs.org/en/latest/>`_. Pull requests with documentation <https://jedi.readthedocs.org/en/latest/>`_. Pull requests with enhancements
enhancements and/or fixes are awesome and most welcome. Jedi uses `semantic and/or fixes are awesome and most welcome. Jedi uses `semantic versioning
versioning <https://semver.org/>`_. <https://semver.org/>`_.
If you want to stay up-to-date (News / RFCs), please subscribe to this `github If you want to stay **up-to-date** with releases, please **subscribe** to this
thread <https://github.com/davidhalter/jedi/issues/1063>`_.: mailing list: https://groups.google.com/g/jedi-announce. To subscribe you can
simply send an empty email to ``jedi-announce+subscribe@googlegroups.com``.
Issues & Questions
==================
You can file issues and questions in the `issue tracker
<https://github.com/davidhalter/jedi/>`. Alternatively you can also ask on
`Stack Overflow <https://stackoverflow.com/questions/tagged/python-jedi>`_ with
the label ``python-jedi``.
Installation Installation
============ ============
pip install jedi `Check out the docs <https://jedi.readthedocs.org/en/latest/docs/installation.html>`_.
Note: This just installs the Jedi library, not the editor plugins. For Features and Limitations
information about how to make it work with your editor, refer to the ========================
corresponding documentation.
You don't want to use ``pip``? Please refer to the `manual Jedi's features are listed here:
<https://jedi.readthedocs.org/en/latest/docs/installation.html>`_. `Features <https://jedi.readthedocs.org/en/latest/docs/features.html>`_.
You can run Jedi on Python 3.6+ but it should also
Feature Support and Caveats understand code that is older than those versions. Additionally you should be
=========================== able to use `Virtualenvs <https://jedi.readthedocs.org/en/latest/docs/api.html#environments>`_
Jedi really understands your Python code. For a comprehensive list what Jedi
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.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. very well.
Tips on how to use Jedi efficiently can be found `here Tips on how to use Jedi efficiently can be found `here
@@ -120,47 +109,62 @@ Tips on how to use Jedi efficiently can be found `here
API API
--- ---
You can find the documentation for the `API here <https://jedi.readthedocs.org/en/latest/docs/api.html>`_. You can find a comprehensive documentation for the
`API here <https://jedi.readthedocs.org/en/latest/docs/api.html>`_.
Autocompletion / Goto / Documentation
-------------------------------------
Autocompletion / Goto / Pydoc There are the following commands:
-----------------------------
Please check the API for a good explanation. There are the following commands:
- ``jedi.Script.goto`` - ``jedi.Script.goto``
- ``jedi.Script.infer``
- ``jedi.Script.help``
- ``jedi.Script.complete`` - ``jedi.Script.complete``
- ``jedi.Script.get_references`` - ``jedi.Script.get_references``
- ``jedi.Script.get_signatures``
- ``jedi.Script.get_context``
The returned objects are very powerful and really all you might need. The returned objects are very powerful and are really all you might need.
Autocompletion in your REPL (IPython, etc.) Autocompletion in your REPL (IPython, etc.)
------------------------------------------- -------------------------------------------
Starting with IPython `6.0.0` Jedi is a dependency of IPython. Autocompletion Jedi is a dependency of IPython. Autocompletion in IPython with Jedi is
in IPython is therefore possible without additional configuration. therefore possible without additional configuration.
It's possible to have Jedi autocompletion in REPL modes - `example video <https://vimeo.com/122332037>`_. Here is an `example video <https://vimeo.com/122332037>`_ how REPL completion
This means that in Python you can enable tab completion in a `REPL can look like.
For the ``python`` shell you can enable tab completion in a `REPL
<https://jedi.readthedocs.org/en/latest/docs/usage.html#tab-completion-in-the-python-shell>`_. <https://jedi.readthedocs.org/en/latest/docs/usage.html#tab-completion-in-the-python-shell>`_.
Static Analysis Static Analysis
------------------------ ---------------
To do all forms of static analysis, please try to use For a lot of forms of static analysis, you can try to use
``jedi.Script(...).get_names``. It will return a list of names that you can use ``jedi.Script(...).get_names``. It will return a list of names that you can
to infer types and so on. then filter and work with. There is also a way to list the syntax errors in a
file: ``jedi.Script.get_syntax_errors``.
Refactoring Refactoring
----------- -----------
Jedi's parser would support refactoring, but there's no API to use it right Jedi supports the following refactorings:
now. If you're interested in helping out here, let me know. With the latest
parser changes, it should be very easy to actually make it work.
- ``jedi.Script.inline``
- ``jedi.Script.rename``
- ``jedi.Script.extract_function``
- ``jedi.Script.extract_variable``
Code Search
-----------
There is support for module search with ``jedi.Script.search``, and project
search for ``jedi.Project.search``. The way to search is either by providing a
name like ``foo`` or by using dotted syntax like ``foo.bar``. Additionally you
can provide the API type like ``class foo.bar.Bar``. There are also the
functions ``jedi.Script.complete_search`` and ``jedi.Project.complete_search``.
Development Development
=========== ===========
@@ -168,39 +172,26 @@ Development
There's a pretty good and extensive `development documentation There's a pretty good and extensive `development documentation
<https://jedi.readthedocs.org/en/latest/docs/development.html>`_. <https://jedi.readthedocs.org/en/latest/docs/development.html>`_.
Testing Testing
======= =======
The test suite depends on ``tox`` and ``pytest``:: The test suite uses ``pytest``::
pip install tox pytest pip install pytest
To run the tests for all supported Python versions:: If you want to test only a specific Python version (e.g. Python 3.8), it is as
tox
If you want to test only a specific Python version (e.g. Python 2.7), it's as
easy as:: easy as::
tox -e py27 python3.8 -m pytest
Tests are also run automatically on `Travis CI
<https://travis-ci.org/davidhalter/jedi/>`_.
For more detailed information visit the `testing documentation 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 Acknowledgements
================ ================
- Takafumi Arakaki (@tkf) for creating a solid test environment and a lot of Thanks a lot to all the
other things. `contributors <https://jedi.readthedocs.org/en/latest/docs/acknowledgements.html>`_!
- Danilo Bargen (@dbrgn) for general housekeeping and being a good friend :).
- Guido van Rossum (@gvanrossum) for creating the parser generator pgen2
(originally used in lib2to3).
.. _jedi-vim: https://github.com/davidhalter/jedi-vim .. _jedi-vim: https://github.com/davidhalter/jedi-vim
-71
View File
@@ -1,71 +0,0 @@
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: py27
PYTHON_PATH: C:\Python27
JEDI_TEST_ENVIRONMENT: 37
- 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: py35
PYTHON_PATH: C:\Python35
JEDI_TEST_ENVIRONMENT: 37
- 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
- TOXENV: py36
PYTHON_PATH: C:\Python36
JEDI_TEST_ENVIRONMENT: 37
- TOXENV: py37
PYTHON_PATH: C:\Python37
JEDI_TEST_ENVIRONMENT: 27
- TOXENV: py37
PYTHON_PATH: C:\Python37
JEDI_TEST_ENVIRONMENT: 34
- TOXENV: py37
PYTHON_PATH: C:\Python37
JEDI_TEST_ENVIRONMENT: 35
- TOXENV: py37
PYTHON_PATH: C:\Python37
JEDI_TEST_ENVIRONMENT: 36
- TOXENV: py37
PYTHON_PATH: C:\Python37
JEDI_TEST_ENVIRONMENT: 37
install:
- git submodule update --init --recursive
- set PATH=%PYTHON_PATH%;%PYTHON_PATH%\Scripts;%PATH%
- pip install tox
build_script:
- tox
+26 -39
View File
@@ -8,18 +8,16 @@ import pytest
import jedi import jedi
from jedi.api.environment import get_system_environment, InterpreterEnvironment from jedi.api.environment import get_system_environment, InterpreterEnvironment
from jedi._compatibility import py_version from test.helpers import test_dir
collect_ignore = [ collect_ignore = [
'setup.py', 'setup.py',
'__main__.py', 'jedi/__main__.py',
'jedi/inference/compiled/subprocess/__main__.py', 'jedi/inference/compiled/subprocess/__main__.py',
'build/', 'build/',
'test/examples', 'test/examples',
'sith.py',
] ]
if sys.version_info < (3, 6):
# Python 2 not supported syntax
collect_ignore.append('test/test_inference/test_mixed.py')
# The following hooks (pytest_configure, pytest_unconfigure) are used # The following hooks (pytest_configure, pytest_unconfigure) are used
@@ -44,7 +42,7 @@ def pytest_addoption(parser):
help="Warnings are treated as errors.") help="Warnings are treated as errors.")
parser.addoption("--env", action='store', parser.addoption("--env", action='store',
help="Execute the tests in that environment (e.g. 35 for python3.5).") help="Execute the tests in that environment (e.g. 39 for python3.9).")
parser.addoption("--interpreter-env", "-I", action='store_true', parser.addoption("--interpreter-env", "-I", action='store_true',
help="Don't use subprocesses to guarantee having safe " help="Don't use subprocesses to guarantee having safe "
"code execution. Useful for debugging.") "code execution. Useful for debugging.")
@@ -96,12 +94,15 @@ def clean_jedi_cache(request):
def environment(request): def environment(request):
version = request.config.option.env version = request.config.option.env
if version is None: if version is None:
version = os.environ.get('JEDI_TEST_ENVIRONMENT', str(py_version)) v = str(sys.version_info[0]) + str(sys.version_info[1])
version = os.environ.get('JEDI_TEST_ENVIRONMENT', v)
if request.config.option.interpreter_env or version == 'interpreter': if request.config.option.interpreter_env or version == 'interpreter':
return InterpreterEnvironment() return InterpreterEnvironment()
return get_system_environment(version[0] + '.' + version[1:]) if '.' not in version:
version = version[0] + '.' + version[1:]
return get_system_environment(version)
@pytest.fixture(scope='session') @pytest.fixture(scope='session')
@@ -109,6 +110,12 @@ def Script(environment):
return partial(jedi.Script, environment=environment) return partial(jedi.Script, environment=environment)
@pytest.fixture(scope='session')
def ScriptWithProject(Script):
project = jedi.Project(test_dir)
return partial(jedi.Script, project=project)
@pytest.fixture(scope='session') @pytest.fixture(scope='session')
def get_names(Script): def get_names(Script):
return lambda code, **kwargs: Script(code).get_names(**kwargs) return lambda code, **kwargs: Script(code).get_names(**kwargs)
@@ -126,17 +133,21 @@ def goto_or_help(request, Script):
@pytest.fixture(scope='session', params=['goto', 'help', 'infer']) @pytest.fixture(scope='session', params=['goto', 'help', 'infer'])
def goto_or_help_or_infer(request, Script): def goto_or_help_or_infer(request, Script):
def do(code, *args, **kwargs):
return getattr(Script(code), request.param)(*args, **kwargs)
do.type = request.param
return do
@pytest.fixture(scope='session', params=['goto', 'complete', 'help'])
def goto_or_complete(request, Script):
return lambda code, *args, **kwargs: getattr(Script(code), request.param)(*args, **kwargs) return lambda code, *args, **kwargs: getattr(Script(code), request.param)(*args, **kwargs)
@pytest.fixture(scope='session') @pytest.fixture(scope='session')
def has_typing(environment): def has_django(environment):
if environment.version_info >= (3, 5, 0): script = jedi.Script('import django', environment=environment)
# This if is just needed to avoid that tests ever skip way more than
# they should for all Python versions.
return True
script = jedi.Script('import typing', environment=environment)
return bool(script.infer()) return bool(script.infer())
@@ -145,14 +156,6 @@ def jedi_path():
return os.path.dirname(__file__) return os.path.dirname(__file__)
@pytest.fixture()
def skip_python2(environment):
if environment.version_info.major == 2:
# This if is just needed to avoid that tests ever skip way more than
# they should for all Python versions.
pytest.skip()
@pytest.fixture() @pytest.fixture()
def skip_pre_python38(environment): def skip_pre_python38(environment):
if environment.version_info < (3, 8): if environment.version_info < (3, 8):
@@ -167,19 +170,3 @@ def skip_pre_python37(environment):
# This if is just needed to avoid that tests ever skip way more than # This if is just needed to avoid that tests ever skip way more than
# they should for all Python versions. # they should for all Python versions.
pytest.skip() pytest.skip()
@pytest.fixture()
def skip_pre_python35(environment):
if environment.version_info < (3, 5):
# This if is just needed to avoid that tests ever skip way more than
# they should for all Python versions.
pytest.skip()
@pytest.fixture()
def skip_pre_python36(environment):
if environment.version_info < (3, 6):
# This if is just needed to avoid that tests ever skip way more than
# they should for all Python versions.
pytest.skip()
+3 -3
View File
@@ -24,10 +24,10 @@ git checkout $BRANCH
git submodule update --init git submodule update --init
# Test first. # Test first.
tox pytest
# Create tag # Create tag
tag=v$(python -c "import $PROJECT_NAME; print($PROJECT_NAME.__version__)") tag=v$(python3 -c "import $PROJECT_NAME; print($PROJECT_NAME.__version__)")
master_ref=$(git show-ref -s heads/$BRANCH) master_ref=$(git show-ref -s heads/$BRANCH)
tag_ref=$(git show-ref -s $tag || true) tag_ref=$(git show-ref -s $tag || true)
@@ -44,7 +44,7 @@ fi
# Package and upload to PyPI # Package and upload to PyPI
#rm -rf dist/ - Not needed anymore, because the folder is never reused. #rm -rf dist/ - Not needed anymore, because the folder is never reused.
echo `pwd` echo `pwd`
python setup.py sdist bdist_wheel python3 setup.py sdist bdist_wheel
# Maybe do a pip install twine before. # Maybe do a pip install twine before.
twine upload dist/* twine upload dist/*
+9
View File
@@ -0,0 +1,9 @@
div.version {
color: black !important;
margin-top: -1.2em !important;
margin-bottom: .6em !important;
}
div.wy-side-nav-search {
padding-top: 0 !important;
}
-37
View File
@@ -1,37 +0,0 @@
Copyright (c) 2010 by Armin Ronacher.
Some rights reserved.
Redistribution and use in source and binary forms of the theme, with or
without modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* The names of the contributors may not be used to endorse or
promote products derived from this software without specific
prior written permission.
We kindly ask you to only use these themes in an unmodified manner just
for Flask and Flask-related products, not for unrelated projects. If you
like the visual style and want to use it for your own projects, please
consider making some larger changes to the themes (such as changing
font faces, sizes, colors or margins).
THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
-27
View File
@@ -1,27 +0,0 @@
{%- extends "basic/layout.html" %}
{%- block extrahead %}
{{ super() }}
{% if theme_touch_icon %}
<link rel="apple-touch-icon" href="{{ pathto('_static/' ~ theme_touch_icon, 1) }}" />
{% endif %}
<link media="only screen and (max-device-width: 480px)" href="{{
pathto('_static/small_flask.css', 1) }}" type= "text/css" rel="stylesheet" />
<a href="https://github.com/davidhalter/jedi">
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub">
</a>
{% endblock %}
{%- block relbar2 %}{% endblock %}
{% block header %}
{{ super() }}
{% if pagename == 'index' %}
<div class=indexwrapper>
{% endif %}
{% endblock %}
{%- block footer %}
<div class="footer">
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a>.
</div>
{% if pagename == 'index' %}
</div>
{% endif %}
{%- endblock %}
-19
View File
@@ -1,19 +0,0 @@
<h3>Related Topics</h3>
<ul>
<li><a href="{{ pathto(master_doc) }}">Documentation overview</a><ul>
{%- for parent in parents %}
<li><a href="{{ parent.link|e }}">{{ parent.title }}</a><ul>
{%- endfor %}
{%- if prev %}
<li>Previous: <a href="{{ prev.link|e }}" title="{{ _('previous chapter')
}}">{{ prev.title }}</a></li>
{%- endif %}
{%- if next %}
<li>Next: <a href="{{ next.link|e }}" title="{{ _('next chapter')
}}">{{ next.title }}</a></li>
{%- endif %}
{%- for parent in parents %}
</ul></li>
{%- endfor %}
</ul></li>
</ul>
-394
View File
@@ -1,394 +0,0 @@
/*
* flasky.css_t
* ~~~~~~~~~~~~
*
* :copyright: Copyright 2010 by Armin Ronacher.
* :license: Flask Design License, see LICENSE for details.
*/
{% set page_width = '940px' %}
{% set sidebar_width = '220px' %}
@import url("basic.css");
/* -- page layout ----------------------------------------------------------- */
body {
font-family: 'Georgia', serif;
font-size: 17px;
background-color: white;
color: #000;
margin: 0;
padding: 0;
}
div.document {
width: {{ page_width }};
margin: 30px auto 0 auto;
}
div.documentwrapper {
float: left;
width: 100%;
}
div.bodywrapper {
margin: 0 0 0 {{ sidebar_width }};
}
div.sphinxsidebar {
width: {{ sidebar_width }};
}
hr {
border: 1px solid #B1B4B6;
}
div.body {
background-color: #ffffff;
color: #3E4349;
padding: 0 30px 0 30px;
}
img.floatingflask {
padding: 0 0 10px 10px;
float: right;
}
div.footer {
width: {{ page_width }};
margin: 20px auto 30px auto;
font-size: 14px;
color: #888;
text-align: right;
}
div.footer a {
color: #888;
}
div.related {
display: none;
}
div.sphinxsidebar a {
color: #444;
text-decoration: none;
border-bottom: 1px dotted #999;
}
div.sphinxsidebar a:hover {
border-bottom: 1px solid #999;
}
div.sphinxsidebar {
font-size: 14px;
line-height: 1.5;
}
div.sphinxsidebarwrapper {
padding: 18px 10px;
}
div.sphinxsidebarwrapper p.logo {
padding: 0 0 20px 0;
margin: 0;
text-align: center;
}
div.sphinxsidebar h3,
div.sphinxsidebar h4 {
font-family: 'Garamond', 'Georgia', serif;
color: #444;
font-size: 24px;
font-weight: normal;
margin: 0 0 5px 0;
padding: 0;
}
div.sphinxsidebar h4 {
font-size: 20px;
}
div.sphinxsidebar h3 a {
color: #444;
}
div.sphinxsidebar p.logo a,
div.sphinxsidebar h3 a,
div.sphinxsidebar p.logo a:hover,
div.sphinxsidebar h3 a:hover {
border: none;
}
div.sphinxsidebar p {
color: #555;
margin: 10px 0;
}
div.sphinxsidebar ul {
margin: 10px 0;
padding: 0;
color: #000;
}
div.sphinxsidebar input {
border: 1px solid #ccc;
font-family: 'Georgia', serif;
font-size: 1em;
}
/* -- body styles ----------------------------------------------------------- */
a {
color: #004B6B;
text-decoration: underline;
}
a:hover {
color: #6D4100;
text-decoration: underline;
}
div.body h1,
div.body h2,
div.body h3,
div.body h4,
div.body h5,
div.body h6 {
font-family: 'Garamond', 'Georgia', serif;
font-weight: normal;
margin: 30px 0px 10px 0px;
padding: 0;
}
{% if theme_index_logo %}
div.indexwrapper h1 {
text-indent: -999999px;
background: url({{ theme_index_logo }}) no-repeat center center;
height: {{ theme_index_logo_height }};
}
{% endif %}
div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; }
div.body h2 { font-size: 180%; }
div.body h3 { font-size: 150%; }
div.body h4 { font-size: 130%; }
div.body h5 { font-size: 100%; }
div.body h6 { font-size: 100%; }
a.headerlink {
color: #ddd;
padding: 0 4px;
text-decoration: none;
}
a.headerlink:hover {
color: #444;
}
div.body p, div.body dd, div.body li {
line-height: 1.4em;
}
div.admonition {
background: #fafafa;
margin: 20px -30px;
padding: 10px 30px;
border-top: 1px solid #ccc;
border-bottom: 1px solid #ccc;
}
div.admonition tt.xref, div.admonition a tt {
border-bottom: 1px solid #fafafa;
}
dd div.admonition {
margin-left: -60px;
padding-left: 60px;
}
div.admonition p.admonition-title {
font-family: 'Garamond', 'Georgia', serif;
font-weight: normal;
font-size: 24px;
margin: 0 0 10px 0;
padding: 0;
line-height: 1;
}
div.admonition p.last {
margin-bottom: 0;
}
div.highlight {
background-color: white;
}
dt:target, .highlight {
background: #FAF3E8;
}
div.note {
background-color: #eee;
border: 1px solid #ccc;
}
div.seealso {
background-color: #ffc;
border: 1px solid #ff6;
}
div.topic {
background-color: #eee;
}
p.admonition-title {
display: inline;
}
p.admonition-title:after {
content: ":";
}
pre, tt {
font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
font-size: 0.9em;
}
img.screenshot {
}
tt.descname, tt.descclassname {
font-size: 0.95em;
}
tt.descname {
padding-right: 0.08em;
}
img.screenshot {
-moz-box-shadow: 2px 2px 4px #eee;
-webkit-box-shadow: 2px 2px 4px #eee;
box-shadow: 2px 2px 4px #eee;
}
table.docutils {
border: 1px solid #888;
-moz-box-shadow: 2px 2px 4px #eee;
-webkit-box-shadow: 2px 2px 4px #eee;
box-shadow: 2px 2px 4px #eee;
}
table.docutils td, table.docutils th {
border: 1px solid #888;
padding: 0.25em 0.7em;
}
table.field-list, table.footnote {
border: none;
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
}
table.footnote {
margin: 15px 0;
width: 100%;
border: 1px solid #eee;
background: #fdfdfd;
font-size: 0.9em;
}
table.footnote + table.footnote {
margin-top: -15px;
border-top: none;
}
table.field-list th {
padding: 0 0.8em 0 0;
}
table.field-list td {
padding: 0;
}
table.footnote td.label {
width: 0px;
padding: 0.3em 0 0.3em 0.5em;
}
table.footnote td {
padding: 0.3em 0.5em;
}
dl {
margin: 0;
padding: 0;
}
dl dd {
margin-left: 30px;
}
blockquote {
margin: 0 0 0 30px;
padding: 0;
}
ul, ol {
margin: 10px 0 10px 30px;
padding: 0;
}
pre {
background: #eee;
padding: 7px 30px;
margin: 15px -30px;
line-height: 1.3em;
}
dl pre, blockquote pre, li pre {
margin-left: -60px;
padding-left: 60px;
}
dl dl pre {
margin-left: -90px;
padding-left: 90px;
}
tt {
background-color: #ecf0f3;
color: #222;
/* padding: 1px 2px; */
}
tt.xref, a tt {
background-color: #FBFBFB;
border-bottom: 1px solid white;
}
a.reference {
text-decoration: none;
border-bottom: 1px dotted #004B6B;
}
a.reference:hover {
border-bottom: 1px solid #6D4100;
}
a.footnote-reference {
text-decoration: none;
font-size: 0.7em;
vertical-align: top;
border-bottom: 1px dotted #004B6B;
}
a.footnote-reference:hover {
border-bottom: 1px solid #6D4100;
}
a:hover tt {
background: #EEE;
}
-70
View File
@@ -1,70 +0,0 @@
/*
* small_flask.css_t
* ~~~~~~~~~~~~~~~~~
*
* :copyright: Copyright 2010 by Armin Ronacher.
* :license: Flask Design License, see LICENSE for details.
*/
body {
margin: 0;
padding: 20px 30px;
}
div.documentwrapper {
float: none;
background: white;
}
div.sphinxsidebar {
display: block;
float: none;
width: 102.5%;
margin: 50px -30px -20px -30px;
padding: 10px 20px;
background: #333;
color: white;
}
div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p,
div.sphinxsidebar h3 a {
color: white;
}
div.sphinxsidebar a {
color: #aaa;
}
div.sphinxsidebar p.logo {
display: none;
}
div.document {
width: 100%;
margin: 0;
}
div.related {
display: block;
margin: 0;
padding: 10px 0 20px 0;
}
div.related ul,
div.related ul li {
margin: 0;
padding: 0;
}
div.footer {
display: none;
}
div.bodywrapper {
margin: 0;
}
div.body {
min-height: 0;
padding: 0;
}
-9
View File
@@ -1,9 +0,0 @@
[theme]
inherit = basic
stylesheet = flasky.css
pygments_style = flask_theme_support.FlaskyStyle
[options]
index_logo =
index_logo_height = 120px
touch_icon =
-125
View File
@@ -1,125 +0,0 @@
"""
Copyright (c) 2010 by Armin Ronacher.
Some rights reserved.
Redistribution and use in source and binary forms of the theme, with or
without modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* The names of the contributors may not be used to endorse or
promote products derived from this software without specific
prior written permission.
We kindly ask you to only use these themes in an unmodified manner just
for Flask and Flask-related products, not for unrelated projects. If you
like the visual style and want to use it for your own projects, please
consider making some larger changes to the themes (such as changing
font faces, sizes, colors or margins).
THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
"""
# flasky extensions. flasky pygments style based on tango style
from pygments.style import Style
from pygments.token import Keyword, Name, Comment, String, Error, \
Number, Operator, Generic, Whitespace, Punctuation, Other, Literal
class FlaskyStyle(Style):
background_color = "#f8f8f8"
default_style = ""
styles = {
# No corresponding class for the following:
#Text: "", # class: ''
Whitespace: "underline #f8f8f8", # class: 'w'
Error: "#a40000 border:#ef2929", # class: 'err'
Other: "#000000", # class 'x'
Comment: "italic #8f5902", # class: 'c'
Comment.Preproc: "noitalic", # class: 'cp'
Keyword: "bold #004461", # class: 'k'
Keyword.Constant: "bold #004461", # class: 'kc'
Keyword.Declaration: "bold #004461", # class: 'kd'
Keyword.Namespace: "bold #004461", # class: 'kn'
Keyword.Pseudo: "bold #004461", # class: 'kp'
Keyword.Reserved: "bold #004461", # class: 'kr'
Keyword.Type: "bold #004461", # class: 'kt'
Operator: "#582800", # class: 'o'
Operator.Word: "bold #004461", # class: 'ow' - like keywords
Punctuation: "bold #000000", # class: 'p'
# because special names such as Name.Class, Name.Function, etc.
# are not recognized as such later in the parsing, we choose them
# to look the same as ordinary variables.
Name: "#000000", # class: 'n'
Name.Attribute: "#c4a000", # class: 'na' - to be revised
Name.Builtin: "#004461", # class: 'nb'
Name.Builtin.Pseudo: "#3465a4", # class: 'bp'
Name.Class: "#000000", # class: 'nc' - to be revised
Name.Constant: "#000000", # class: 'no' - to be revised
Name.Decorator: "#888", # class: 'nd' - to be revised
Name.Entity: "#ce5c00", # class: 'ni'
Name.Exception: "bold #cc0000", # class: 'ne'
Name.Function: "#000000", # class: 'nf'
Name.Property: "#000000", # class: 'py'
Name.Label: "#f57900", # class: 'nl'
Name.Namespace: "#000000", # class: 'nn' - to be revised
Name.Other: "#000000", # class: 'nx'
Name.Tag: "bold #004461", # class: 'nt' - like a keyword
Name.Variable: "#000000", # class: 'nv' - to be revised
Name.Variable.Class: "#000000", # class: 'vc' - to be revised
Name.Variable.Global: "#000000", # class: 'vg' - to be revised
Name.Variable.Instance: "#000000", # class: 'vi' - to be revised
Number: "#990000", # class: 'm'
Literal: "#000000", # class: 'l'
Literal.Date: "#000000", # class: 'ld'
String: "#4e9a06", # class: 's'
String.Backtick: "#4e9a06", # class: 'sb'
String.Char: "#4e9a06", # class: 'sc'
String.Doc: "italic #8f5902", # class: 'sd' - like a comment
String.Double: "#4e9a06", # class: 's2'
String.Escape: "#4e9a06", # class: 'se'
String.Heredoc: "#4e9a06", # class: 'sh'
String.Interpol: "#4e9a06", # class: 'si'
String.Other: "#4e9a06", # class: 'sx'
String.Regex: "#4e9a06", # class: 'sr'
String.Single: "#4e9a06", # class: 's1'
String.Symbol: "#4e9a06", # class: 'ss'
Generic: "#000000", # class: 'g'
Generic.Deleted: "#a40000", # class: 'gd'
Generic.Emph: "italic #000000", # class: 'ge'
Generic.Error: "#ef2929", # class: 'gr'
Generic.Heading: "bold #000080", # class: 'gh'
Generic.Inserted: "#00A000", # class: 'gi'
Generic.Output: "#888", # class: 'go'
Generic.Prompt: "#745334", # class: 'gp'
Generic.Strong: "bold #000000", # class: 'gs'
Generic.Subheading: "bold #800080", # class: 'gu'
Generic.Traceback: "bold #a40000", # class: 'gt'
}
+26 -23
View File
@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
#
# Jedi documentation build configuration file, created by # Jedi documentation build configuration file, created by
# sphinx-quickstart on Wed Dec 26 00:11:34 2012. # sphinx-quickstart on Wed Dec 26 00:11:34 2012.
# #
@@ -13,13 +11,11 @@
import sys import sys
import os import os
import datetime
# If extensions (or modules to document with autodoc) are in another directory, # If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the # add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here. # documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath('..')) sys.path.insert(0, os.path.abspath('..'))
sys.path.append(os.path.abspath('_themes'))
# -- General configuration ----------------------------------------------------- # -- General configuration -----------------------------------------------------
@@ -29,7 +25,8 @@ sys.path.append(os.path.abspath('_themes'))
# Add any Sphinx extension module names here, as strings. They can be extensions # Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.todo', extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.todo',
'sphinx.ext.intersphinx', 'sphinx.ext.inheritance_diagram'] 'sphinx.ext.intersphinx', 'sphinx.ext.inheritance_diagram',
'sphinx_rtd_theme', 'sphinx.ext.autosummary']
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates'] templates_path = ['_templates']
@@ -44,8 +41,8 @@ source_encoding = 'utf-8'
master_doc = 'index' master_doc = 'index'
# General information about the project. # General information about the project.
project = u'Jedi' project = 'Jedi'
copyright = u'jedi contributors' copyright = 'jedi contributors'
import jedi import jedi
from jedi.utils import version_info from jedi.utils import version_info
@@ -54,8 +51,8 @@ from jedi.utils import version_info
# |version| and |release|, also used in various other places throughout the # |version| and |release|, also used in various other places throughout the
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y.Z version.
version = '.'.join(str(x) for x in version_info()[:2]) version = '.'.join(str(x) for x in version_info()[:3])
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = jedi.__version__ release = jedi.__version__
@@ -98,12 +95,15 @@ pygments_style = 'sphinx'
# The theme to use for HTML and HTML Help pages. See the documentation for # The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes. # a list of builtin themes.
html_theme = 'flask' html_theme = 'sphinx_rtd_theme'
# Theme options are theme-specific and customize the look and feel of a theme # Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the # further. For a list of options available for each theme, see the
# documentation. # documentation.
#html_theme_options = {} html_theme_options = {
'logo_only': True,
'style_nav_header_background': 'white',
}
# Add any paths that contain custom themes here, relative to this directory. # Add any paths that contain custom themes here, relative to this directory.
html_theme_path = ['_themes'] html_theme_path = ['_themes']
@@ -117,7 +117,7 @@ html_theme_path = ['_themes']
# The name of an image file (relative to this directory) to place at the top # The name of an image file (relative to this directory) to place at the top
# of the sidebar. # of the sidebar.
#html_logo = None html_logo = '_static/logo.png'
# The name of an image file (within the static path) to use as favicon of the # The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
@@ -129,6 +129,8 @@ html_theme_path = ['_themes']
# so a file named "default.css" will overwrite the builtin "default.css". # so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static'] html_static_path = ['_static']
html_css_files = ['custom_style.css']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format. # using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y' #html_last_updated_fmt = '%b %d, %Y'
@@ -145,7 +147,7 @@ html_sidebars = {
#'relations.html', #'relations.html',
'ghbuttons.html', 'ghbuttons.html',
#'sourcelink.html', #'sourcelink.html',
#'searchbox.html' 'searchbox.html'
] ]
} }
@@ -163,13 +165,13 @@ html_sidebars = {
#html_split_index = False #html_split_index = False
# If true, links to the reST sources are added to the pages. # If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True html_show_sourcelink = False
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True html_show_sphinx = False
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True html_show_copyright = False
# If true, an OpenSearch description file will be output, and all pages will # If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the # contain a <link> tag referring to it. The value of this option must be the
@@ -201,8 +203,8 @@ latex_elements = {
# Grouping the document tree into LaTeX files. List of tuples # Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]). # (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [ latex_documents = [
('index', 'Jedi.tex', u'Jedi Documentation', ('index', 'Jedi.tex', 'Jedi Documentation',
u'Jedi contributors', 'manual'), 'Jedi contributors', 'manual'),
] ]
# The name of an image file (relative to this directory) to place at the top of # The name of an image file (relative to this directory) to place at the top of
@@ -231,8 +233,8 @@ latex_documents = [
# One entry per manual page. List of tuples # One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section). # (source start file, name, description, authors, manual section).
man_pages = [ man_pages = [
('index', 'jedi', u'Jedi Documentation', ('index', 'jedi', 'Jedi Documentation',
[u'Jedi contributors'], 1) ['Jedi contributors'], 1)
] ]
# If true, show URL addresses after external links. # If true, show URL addresses after external links.
@@ -245,8 +247,8 @@ man_pages = [
# (source start file, target name, title, author, # (source start file, target name, title, author,
# dir menu entry, description, category) # dir menu entry, description, category)
texinfo_documents = [ texinfo_documents = [
('index', 'Jedi', u'Jedi Documentation', ('index', 'Jedi', 'Jedi Documentation',
u'Jedi contributors', 'Jedi', 'Awesome Python autocompletion library.', 'Jedi contributors', 'Jedi', 'Awesome Python autocompletion library.',
'Miscellaneous'), 'Miscellaneous'),
] ]
@@ -274,7 +276,8 @@ autodoc_default_flags = []
# -- Options for intersphinx module -------------------------------------------- # -- Options for intersphinx module --------------------------------------------
intersphinx_mapping = { intersphinx_mapping = {
'https://docs.python.org/': None, 'python': ('https://docs.python.org/', None),
'parso': ('https://parso.readthedocs.io/en/latest/', None),
} }
+66
View File
@@ -0,0 +1,66 @@
.. include global.rst
History & Acknowledgements
==========================
Acknowledgements
----------------
- Dave Halter for creating and maintaining Jedi & Parso.
- Takafumi Arakaki (@tkf) for creating a solid test environment and a lot of
other things.
- Danilo Bargen (@dbrgn) for general housekeeping and being a good friend :).
- Guido van Rossum (@gvanrossum) for creating the parser generator pgen2
(originally used in lib2to3).
- Thanks to all the :ref:`contributors <contributors>`.
A Little Bit of History
-----------------------
Written by Dave.
The Star Wars Jedi are awesome. My Jedi software tries to imitate a little bit
of the precognition the Jedi have. There's even an awesome `scene
<https://youtu.be/yHRJLIf7wMU>`_ of Monty Python Jedis :-).
But actually the name has not much to do with Star Wars. It's part of my
second name Jedidjah.
I actually started Jedi back in 2012, because there were no good solutions
available for VIM. Most auto-completion solutions just did not work well. The
only good solution was PyCharm. But I liked my good old VIM very much. There
was also a solution called Rope that did not work at all for me. So I decided
to write my own version of a completion engine.
The first idea was to execute non-dangerous code. But I soon realized, that
this would not work. So I started to build a static analysis tool.
The biggest problem that I had at the time was that I did not know a thing
about parsers.I did not did not even know the word static analysis. It turns
out they are the foundation of a good static analysis tool. I of course did not
know that and tried to write my own poor version of a parser that I ended up
throwing away two years later.
Because of my lack of knowledge, everything after 2012 and before 2020 was
basically refactoring. I rewrote the core parts of Jedi probably like 5-10
times. The last big rewrite (that I did twice) was the inclusion of
gradual typing and stubs.
I learned during that time that it is crucial to have a good understanding of
your problem. Otherwise you just end up doing it again. I only wrote features
in the beginning and in the end. Everything else was bugfixing and refactoring.
However now I am really happy with the result. It works well, bugfixes can be
quick and is pretty much feature complete.
--------
I will leave you with a small annectote that happend in 2012, if I remember
correctly. After I explained Guido van Rossum, how some parts of my
auto-completion work, he said:
*"Oh, that worries me..."*
Now that it is finished, I hope he likes it :-).
.. _contributors:
.. include:: ../../AUTHORS.txt
+45 -2
View File
@@ -5,6 +5,49 @@
API Return Classes API Return Classes
------------------ ------------------
.. automodule:: jedi.api.classes Abstract Base Class
~~~~~~~~~~~~~~~~~~~
.. autoclass:: jedi.api.classes.BaseName
:members: :members:
:undoc-members: :show-inheritance:
Name
~~~~
.. autoclass:: jedi.api.classes.Name
:members:
:show-inheritance:
Completion
~~~~~~~~~~
.. autoclass:: jedi.api.classes.Completion
:members:
:show-inheritance:
BaseSignature
~~~~~~~~~~~~~
.. autoclass:: jedi.api.classes.BaseSignature
:members:
:show-inheritance:
Signature
~~~~~~~~~
.. autoclass:: jedi.api.classes.Signature
:members:
:show-inheritance:
ParamName
~~~~~~~~~
.. autoclass:: jedi.api.classes.ParamName
:members:
:show-inheritance:
Refactoring
~~~~~~~~~~~
.. autoclass:: jedi.api.refactoring.Refactoring
:members:
:show-inheritance:
.. autoclass:: jedi.api.errors.SyntaxError
:members:
:show-inheritance:
+98 -54
View File
@@ -3,56 +3,74 @@
API Overview API Overview
============ ============
.. currentmodule:: jedi .. note:: This documentation is mostly for Plugin developers, who want to
improve their editors/IDE with Jedi.
Note: This documentation is for Plugin developers, who want to improve their
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
<api-classes>`.
Deprecations
------------
The deprecation process is as follows:
1. A deprecation is announced in the next major/minor release.
2. We wait either at least a year & at least two minor releases until we remove
the deprecated functionality.
API Documentation
-----------------
The API consists of a few different parts:
- The main starting points for complete/goto: :class:`.Script` and :class:`.Interpreter`
- Helpful functions: :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: .. _api:
Static Analysis Interface The API consists of a few different parts:
~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: jedi - The main starting points for complete/goto: :class:`.Script` and
:class:`.Interpreter`. If you work with Jedi you want to understand these
classes first.
- :ref:`API Result Classes <api-classes>`
- :ref:`Python Versions/Virtualenv Support <environments>` with functions like
:func:`.find_system_environments` and :func:`.find_virtualenvs`
- A way to work with different :ref:`Folders / Projects <projects>`
- Helpful functions: :func:`.preload_module` and :func:`.set_debug_function`
The methods that you are most likely going to use to work with Jedi are the
following ones:
.. currentmodule:: jedi
.. autosummary::
:nosignatures:
Script.complete
Script.goto
Script.infer
Script.help
Script.get_signatures
Script.get_references
Script.get_context
Script.get_names
Script.get_syntax_errors
Script.rename
Script.inline
Script.extract_variable
Script.extract_function
Script.search
Script.complete_search
Project.search
Project.complete_search
Script
------
.. autoclass:: jedi.Script .. autoclass:: jedi.Script
:members: :members:
Interpreter
-----------
.. autoclass:: jedi.Interpreter .. autoclass:: jedi.Interpreter
:members: :members:
.. autofunction:: jedi.preload_module
.. autofunction:: jedi.set_debug_function .. _projects:
Projects
--------
.. automodule:: jedi.api.project
.. autofunction:: jedi.get_default_project
.. autoclass:: jedi.Project
:members:
.. _environments: .. _environments:
Environments Environments
~~~~~~~~~~~~ ------------
.. automodule:: jedi.api.environment .. automodule:: jedi.api.environment
@@ -65,18 +83,31 @@ Environments
.. autoclass:: jedi.api.environment.Environment .. autoclass:: jedi.api.environment.Environment
:members: :members:
Helper Functions
----------------
.. autofunction:: jedi.preload_module
.. autofunction:: jedi.set_debug_function
Errors
------
.. autoexception:: jedi.InternalError
.. autoexception:: jedi.RefactoringError
Examples Examples
-------- --------
Completions: Completions
~~~~~~~~~~~
.. sourcecode:: python .. sourcecode:: python
>>> import jedi >>> import jedi
>>> source = '''import json; json.l''' >>> code = '''import json; json.l'''
>>> script = jedi.Script(source, path='') >>> script = jedi.Script(code, path='example.py')
>>> script >>> script
<jedi.api.Script object at 0x2121b10> <Script: 'example.py' <SameEnvironment: 3.9.0 in /usr>>
>>> completions = script.complete(1, 19) >>> completions = script.complete(1, 19)
>>> completions >>> completions
[<Completion: load>, <Completion: loads>] [<Completion: load>, <Completion: loads>]
@@ -87,12 +118,14 @@ Completions:
>>> completions[1].name >>> completions[1].name
'loads' 'loads'
Definitions / Goto: Type Inference / Goto
~~~~~~~~~~~~~~~~~~~~~
.. sourcecode:: python .. sourcecode:: python
>>> import jedi >>> import jedi
>>> source = '''def my_func(): >>> code = '''\
... def my_func():
... print 'called' ... print 'called'
... ...
... alias = my_func ... alias = my_func
@@ -100,30 +133,41 @@ Definitions / Goto:
... inception = my_list[2] ... inception = my_list[2]
... ...
... inception()''' ... inception()'''
>>> script = jedi.Script(source, path='') >>> script = jedi.Script(code)
>>> >>>
>>> script.goto(8, 1) >>> script.goto(8, 1)
[<Definition inception=my_list[2]>] [<Name full_name='__main__.inception', description='inception = my_list[2]'>]
>>> >>>
>>> script.infer(8, 1) >>> script.infer(8, 1)
[<Definition def my_func>] [<Name full_name='__main__.my_func', description='def my_func'>]
References: References
~~~~~~~~~~
.. sourcecode:: python .. sourcecode:: python
>>> import jedi >>> import jedi
>>> source = '''x = 3 >>> code = '''\
... x = 3
... if 1 == 2: ... if 1 == 2:
... x = 4 ... x = 4
... else: ... else:
... del x''' ... del x'''
>>> script = jedi.Script(source, '') >>> script = jedi.Script(code)
>>> rns = script.get_references(5, 8) >>> rns = script.get_references(5, 8)
>>> rns >>> rns
[<Definition full_name='__main__.x', description='x = 3'>, [<Name full_name='__main__.x', description='x = 3'>,
<Definition full_name='__main__.x', description='x'>] <Name full_name='__main__.x', description='x = 4'>,
<Name full_name='__main__.x', description='del x'>]
>>> rns[1].line >>> rns[1].line
5 3
>>> rns[0].column >>> rns[1].column
8 4
Deprecations
------------
The deprecation process is as follows:
1. A deprecation is announced in any release.
2. The next major release removes the deprecated functionality.
+1
View File
@@ -0,0 +1 @@
.. include:: ../../CHANGELOG.rst
+27 -30
View File
@@ -22,16 +22,12 @@ couldn't get rid of complexity. I know that **simple is better than complex**,
but unfortunately it sometimes requires complex solutions to understand complex but unfortunately it sometimes requires complex solutions to understand complex
systems. systems.
Since most of the Jedi internals have been written by me (David Halter), this In six chapters I'm trying to describe the internals of |jedi|:
introduction will be written mostly by me, because no one else understands to
the same level how Jedi works. Actually this is also the reason for exactly this
part of the documentation. To make multiple people able to edit the Jedi core.
In five chapters I'm trying to describe the internals of |jedi|:
- :ref:`The Jedi Core <core>` - :ref:`The Jedi Core <core>`
- :ref:`Core Extensions <core-extensions>` - :ref:`Core Extensions <core-extensions>`
- :ref:`Imports & Modules <imports-modules>` - :ref:`Imports & Modules <imports-modules>`
- :ref:`Stubs & Annotations <stubs>`
- :ref:`Caching & Recursions <caching-recursions>` - :ref:`Caching & Recursions <caching-recursions>`
- :ref:`Helper modules <dev-helpers>` - :ref:`Helper modules <dev-helpers>`
@@ -59,17 +55,17 @@ because that's where all the magic happens. I need to introduce the :ref:`parser
Parser Parser
~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
Jedi used to have it's internal parser, however this is now a separate project Jedi used to have its internal parser, however this is now a separate project
and is called `parso <http://parso.readthedocs.io>`_. and is called `parso <http://parso.readthedocs.io>`_.
The parser creates a syntax tree that |jedi| analyses and tries to understand. 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 The grammar that this parser uses is very similar to the official Python
`grammar files <https://docs.python.org/3/reference/grammar.html>`_. `grammar files <https://docs.python.org/3/reference/grammar.html>`_.
.. _inference: .. _inference:
Type inference of python code (inference/__init__.py) Type inference of python code (inference/__init__.py)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: jedi.inference .. automodule:: jedi.inference
@@ -80,7 +76,7 @@ Inference Values (inference/base_value.py)
.. inheritance-diagram:: .. inheritance-diagram::
jedi.inference.value.instance.TreeInstance jedi.inference.value.instance.TreeInstance
jedi.inference.value.klass.Classvalue jedi.inference.value.klass.ClassValue
jedi.inference.value.function.FunctionValue jedi.inference.value.function.FunctionValue
jedi.inference.value.function.FunctionExecutionContext jedi.inference.value.function.FunctionExecutionContext
:parts: 1 :parts: 1
@@ -89,7 +85,7 @@ Inference Values (inference/base_value.py)
.. _name_resolution: .. _name_resolution:
Name resolution (inference/finder.py) Name resolution (inference/finder.py)
++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++++++++++
.. automodule:: jedi.inference.finder .. automodule:: jedi.inference.finder
@@ -114,7 +110,7 @@ Core Extensions
Core Extensions is a summary of the following topics: Core Extensions is a summary of the following topics:
- :ref:`Iterables & Dynamic Arrays <iterables>` - :ref:`Iterables & Dynamic Arrays <iterables>`
- :ref:`Dynamic Parameters <dynamic>` - :ref:`Dynamic Parameters <dynamic_params>`
- :ref:`Docstrings <docstrings>` - :ref:`Docstrings <docstrings>`
- :ref:`Refactoring <refactoring>` - :ref:`Refactoring <refactoring>`
@@ -125,7 +121,7 @@ without some features.
.. _iterables: .. _iterables:
Iterables & Dynamic Arrays (inference/value/iterable.py) Iterables & Dynamic Arrays (inference/value/iterable.py)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To understand Python on a deeper level, |jedi| needs to understand some of the 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: dynamic features of Python like lists that are filled after creation:
@@ -133,33 +129,33 @@ dynamic features of Python like lists that are filled after creation:
.. automodule:: jedi.inference.value.iterable .. automodule:: jedi.inference.value.iterable
.. _dynamic: .. _dynamic_params:
Parameter completion (inference/dynamic.py) Parameter completion (inference/dynamic_params.py)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: jedi.inference.dynamic .. automodule:: jedi.inference.dynamic_params
.. _docstrings: .. _docstrings:
Docstrings (inference/docstrings.py) Docstrings (inference/docstrings.py)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: jedi.inference.docstrings .. automodule:: jedi.inference.docstrings
.. _refactoring: .. _refactoring:
Refactoring (inference/refactoring.py) Refactoring (api/refactoring.py)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: jedi.refactoring .. automodule:: jedi.api.refactoring
.. _imports-modules: .. _imports-modules:
Imports & Modules Imports & Modules
------------------- -----------------
- :ref:`Modules <modules>` - :ref:`Modules <modules>`
@@ -170,7 +166,7 @@ Imports & Modules
.. _builtin: .. _builtin:
Compiled Modules (inference/compiled.py) Compiled Modules (inference/compiled.py)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: jedi.inference.compiled .. automodule:: jedi.inference.compiled
@@ -178,10 +174,16 @@ Compiled Modules (inference/compiled.py)
.. _imports: .. _imports:
Imports (inference/imports.py) Imports (inference/imports.py)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: jedi.inference.imports .. automodule:: jedi.inference.imports
.. _stubs:
Stubs & Annotations (inference/gradual)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: jedi.inference.gradual
.. _caching-recursions: .. _caching-recursions:
@@ -210,13 +212,8 @@ Recursions (recursion.py)
.. _dev-helpers: .. _dev-helpers:
Helper Modules Helper Modules
--------------- --------------
Most other modules are not really central to how Jedi works. They all contain Most other modules are not really central to how Jedi works. They all contain
relevant code, but you if you understand the modules above, you pretty much relevant code, but you if you understand the modules above, you pretty much
understand Jedi. understand Jedi.
Python 2/3 compatibility (_compatibility.py)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: jedi._compatibility
+63 -189
View File
@@ -1,29 +1,30 @@
.. include:: ../global.rst .. include:: ../global.rst
Features and Caveats Features and Limitations
==================== ========================
Jedi obviously supports autocompletion. It's also possible to get it working in Jedi's main API calls and features are:
(:ref:`your REPL (IPython, etc.) <repl-completion>`).
Static analysis is also possible by using ``jedi.Script(...).get_names``. - Autocompletion: :meth:`.Script.complete`; It's also possible to get it
working in :ref:`your REPL (IPython, etc.) <repl-completion>`
- Goto/Type Inference: :meth:`.Script.goto` and :meth:`.Script.infer`
- Static Analysis: :meth:`.Script.get_names` and :meth:`.Script.get_syntax_errors`
- Refactorings: :meth:`.Script.rename`, :meth:`.Script.inline`,
:meth:`.Script.extract_variable` and :meth:`.Script.extract_function`
- Code Search: :meth:`.Script.search` and :meth:`.Project.search`
Jedi would in theory support refactoring, but we have never publicized it, Basic Features
because it's not production ready. If you're interested in helping out here, --------------
let me know. With the latest parser changes, it should be very easy to actually
make it work.
- Python 3.6+ support
General Features
----------------
- Python 2.7 and 3.4+ support
- Ignores syntax errors and wrong indentation - Ignores syntax errors and wrong indentation
- Can deal with complex module / function / class structures - Can deal with complex module / function / class structures
- Great Virtualenv support - Great ``virtualenv``/``venv`` support
- Can infer function arguments from sphinx, epydoc and basic numpydoc docstrings, - Works great with Python's :ref:`type hinting <type-hinting>`,
and PEP0484-style type hints (:ref:`type hinting <type-hinting>`) - Understands stub files
- Stub files - Can infer function arguments for sphinx, epydoc and basic numpydoc docstrings
- Is overall a very solid piece of software that has been refined for a long
time. Bug reports are very welcome and are usually fixed within a few weeks.
Supported Python Features Supported Python Features
@@ -38,7 +39,7 @@ Supported Python Features
- ``*args`` / ``**kwargs`` - ``*args`` / ``**kwargs``
- decorators / lambdas / closures - decorators / lambdas / closures
- generators / iterators - generators / iterators
- some descriptors: property / staticmethod / classmethod - descriptors: property / staticmethod / classmethod / custom descriptors
- some magic methods: ``__call__``, ``__iter__``, ``__next__``, ``__get__``, - some magic methods: ``__call__``, ``__iter__``, ``__next__``, ``__get__``,
``__getitem__``, ``__init__`` ``__getitem__``, ``__init__``
- ``list.append()``, ``set.add()``, ``list.extend()``, etc. - ``list.append()``, ``set.add()``, ``list.extend()``, etc.
@@ -46,191 +47,64 @@ Supported Python Features
- relative imports - relative imports
- ``getattr()`` / ``__getattr__`` / ``__getattribute__`` - ``getattr()`` / ``__getattr__`` / ``__getattribute__``
- function annotations - function annotations
- class decorators (py3k feature, are being ignored too, until I find a use - simple/typical ``sys.path`` modifications
case, that doesn't work with |jedi|)
- simple/usual ``sys.path`` modifications
- ``isinstance`` checks for if/while/assert - ``isinstance`` checks for if/while/assert
- namespace packages (includes ``pkgutil``, ``pkg_resources`` and PEP420 namespaces) - namespace packages (includes ``pkgutil``, ``pkg_resources`` and PEP420 namespaces)
- Django / Flask / Buildout support - Django / Flask / Buildout support
- Understands Pytest fixtures - Understands Pytest fixtures
Not Supported Limitations
------------- -----------
Not yet implemented: In general Jedi's limit are quite high, but for very big projects or very
complex code, sometimes Jedi intentionally stops type inference, to avoid
hanging for a long time.
- manipulations of instances outside the instance variables without using Additionally there are some Python patterns Jedi does not support. This is
intentional and below should be a complete list:
- Arbitrary metaclasses: Some metaclasses like enums and dataclasses are
reimplemented in Jedi to make them work. Most of the time stubs are good
enough to get type inference working, even when metaclasses are involved.
- ``setattr()``, ``__import__()``
- Writing to some dicts: ``globals()``, ``locals()``, ``object.__dict__``
- Manipulations of instances outside the instance variables without using
methods methods
Will probably never be implemented: Performance Issues
~~~~~~~~~~~~~~~~~~
- metaclasses (how could an auto-completion ever support this)
- ``setattr()``, ``__import__()``
- writing to some dicts: ``globals()``, ``locals()``, ``object.__dict__``
Caveats
-------
**Slow Performance**
Importing ``numpy`` can be quite slow sometimes, as well as loading the Importing ``numpy`` can be quite slow sometimes, as well as loading the
builtins the first time. If you want to speed things up, you could write import builtins the first time. If you want to speed things up, you could preload
hooks in |jedi|, which preload stuff. However, once loaded, this is not a libriaries in |jedi|, with :func:`.preload_module`. However, once loaded, this
problem anymore. The same is true for huge modules like ``PySide``, ``wx``, should not be a problem anymore. The same is true for huge modules like
etc. ``PySide``, ``wx``, ``tensorflow``, ``pandas``, etc.
**Security** Jedi does not have a very good cache layer. This is probably the biggest and
only architectural `issue <https://github.com/davidhalter/jedi/issues/1059>`_ in
Jedi. Unfortunately it is not easy to change that. Dave Halter is thinking
about rewriting Jedi in Rust, but it has taken Jedi more than 8 years to reach
version 1.0, a rewrite will probably also take years.
Security is an important issue for |jedi|. Therefore no Python code is Security
executed. As long as you write pure Python, everything is inferred --------
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
means one import and no more. So basically the only dangerous thing is using
the import itself. If your ``c_builtin`` uses some strange initializations, it
might be dangerous. But if it does you're screwed anyways, because eventually
you're going to execute your code, which executes the import.
For :class:`.Script`
~~~~~~~~~~~~~~~~~~~~
Recipes Security is an important topic for |jedi|. By default, no code is executed
------- within Jedi. As long as you write pure Python, everything is inferred
statically. If you enable ``load_unsafe_extensions=True`` for your
:class:`.Project` and you use builtin modules (``c_builtin``) Jedi will execute
those modules. If you don't trust a code base, please do not enable that
option. It might lead to arbitrary code execution.
Here are some tips on how to use |jedi| efficiently. For :class:`.Interpreter`
~~~~~~~~~~~~~~~~~~~~~~~~~
If you want security for :class:`.Interpreter`, ``do not`` use it. Jedi does
.. _type-hinting: execute properties and in general is not very careful to avoid code execution.
This is intentional: Most people trust the code bases they have imported,
Type Hinting because at that point a malicious code base would have had code execution
~~~~~~~~~~~~ already.
If |jedi| cannot detect the type of a function argument correctly (due to the
dynamic nature of Python), you can help it by hinting the type using
one of the following docstring/annotation syntax styles:
**PEP-0484 style**
https://www.python.org/dev/peps/pep-0484/
function annotations
::
def myfunction(node: ProgramNode, foo: str) -> None:
"""Do something with a ``node``.
"""
node.| # complete here
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
::
x = foo() # type: int
x, y = 2, 3 # type: typing.Optional[int], typing.Union[int, str] # typing module is mostly supported
for key, value in foo.items(): # type: str, Employee # note that Employee must be in scope
pass
with foo() as f: # type: int
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),
and forward references.
You can also use stub files.
**Sphinx style**
http://www.sphinx-doc.org/en/stable/domains.html#info-field-lists
::
def myfunction(node, foo):
"""Do something with a ``node``.
:type node: ProgramNode
:param str foo: foo parameter description
"""
node.| # complete here
**Epydoc**
http://epydoc.sourceforge.net/manual-fields.html
::
def myfunction(node):
"""Do something with a ``node``.
@type node: ProgramNode
"""
node.| # complete here
**Numpydoc**
https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt
In order to support the numpydoc format, you need to install the `numpydoc
<https://pypi.python.org/pypi/numpydoc>`__ package.
::
def foo(var1, var2, long_var_name='hi'):
r"""A one-line summary that does not use variable names or the
function name.
...
Parameters
----------
var1 : array_like
Array_like means all those objects -- lists, nested lists,
etc. -- that can be converted to an array. We can also
refer to variables like `var1`.
var2 : int
The type above can either refer to an actual Python type
(e.g. ``int``), or describe the type of the variable in more
detail, e.g. ``(N,) ndarray`` or ``array_like``.
long_variable_name : {'hi', 'ho'}, optional
Choices in brackets, default first when optional.
...
"""
var2.| # complete here
A little history
----------------
The Star Wars Jedi are awesome. My Jedi software tries to imitate a little bit
of the precognition the Jedi have. There's even an awesome `scene
<https://youtu.be/yHRJLIf7wMU>`_ of Monty Python Jedis :-).
But actually the name hasn't so much to do with Star Wars. It's part of my
second name.
After I explained Guido van Rossum, how some parts of my auto-completion work,
he said (we drank a beer or two):
*"Oh, that worries me..."*
When it's finished, I hope he'll like it :-)
I actually started Jedi, because there were no good solutions available for VIM.
Most auto-completions just didn't work well. The only good solution was PyCharm.
But I like my good old VIM. Rope was never really intended to be an
auto-completion (and also I really hate project folders for my Python scripts).
It's more of a refactoring suite. So I decided to do my own version of a
completion, which would execute non-dangerous code. But I soon realized, that
this wouldn't work. So I built an extremely recursive thing which understands
many of Python's key features.
By the way, I really tried to program it as understandable as possible. But I
think understanding it might need quite some time, because of its recursive
nature.
+9 -8
View File
@@ -3,6 +3,15 @@
Installation and Configuration Installation and Configuration
============================== ==============================
.. warning:: Most people will want to install Jedi as a submodule/vendored and
not through pip/system wide. The reason for this is that it makes sense that
the plugin that uses Jedi has always access to it. Otherwise Jedi will not
work properly when virtualenvs are activated. So please read the
documentation of your editor/IDE plugin to install Jedi.
For plugin developers, Jedi works best if it is always available. Vendoring
is a pretty good option for that.
You can either include |jedi| as a submodule in your text editor plugin (like You can either include |jedi| as a submodule in your text editor plugin (like
jedi-vim_ does by default), or you can install it systemwide. jedi-vim_ does by default), or you can install it systemwide.
@@ -41,14 +50,6 @@ Arch Linux
You can install |jedi| directly from official Arch Linux packages: You can install |jedi| directly from official Arch Linux packages:
- `python-jedi <https://www.archlinux.org/packages/community/any/python-jedi/>`__ - `python-jedi <https://www.archlinux.org/packages/community/any/python-jedi/>`__
(Python 3)
- `python2-jedi <https://www.archlinux.org/packages/community/any/python2-jedi/>`__
(Python 2)
The specified Python version just refers to the *runtime environment* for
|jedi|. Use the Python 2 version if you're running vim (or whatever editor you
use) under Python 2. Otherwise, use the Python 3 version. But whatever version
you choose, both are able to complete both Python 2 and 3 *code*.
(There is also a packaged version of the vim plugin available: (There is also a packaged version of the vim plugin available:
`vim-jedi at Arch Linux <https://www.archlinux.org/packages/community/any/vim-jedi/>`__.) `vim-jedi at Arch Linux <https://www.archlinux.org/packages/community/any/vim-jedi/>`__.)
+8 -12
View File
@@ -3,21 +3,17 @@
Jedi Testing Jedi Testing
============ ============
The test suite depends on ``tox`` and ``pytest``:: The test suite depends on ``pytest``::
pip install tox pytest pip install pytest
To run the tests for all supported Python versions:: If you want to test only a specific Python version (e.g. Python 3.8), it is as
tox
If you want to test only a specific Python version (e.g. Python 2.7), it's as
easy as:: easy as::
tox -e py27 python3.8 -m pytest
Tests are also run automatically on `Travis CI Tests are also run automatically on `GitHub Actions
<https://travis-ci.org/davidhalter/jedi/>`_. <https://github.com/davidhalter/jedi/actions>`_.
You want to add a test for |jedi|? Great! We love that. Normally you should You want to add a test for |jedi|? Great! We love that. Normally you should
write your tests as :ref:`Blackbox Tests <blackbox>`. Most tests would write your tests as :ref:`Blackbox Tests <blackbox>`. Most tests would
@@ -28,8 +24,8 @@ simple and readable testing structure.
.. _blackbox: .. _blackbox:
Blackbox Tests (run.py) Integration Tests (run.py)
~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: test.run .. automodule:: test.run
+186 -28
View File
@@ -1,79 +1,108 @@
.. include:: ../global.rst .. include:: ../global.rst
End User Usage Using Jedi
============== ==========
If you are a not an IDE Developer, the odds are that you just want to use |jedi| is can be used with a variety of :ref:`plugins <editor-plugins>`,
|jedi| as a browser plugin or in the shell. Yes that's :ref:`also possible `language servers <language-servers>` and other software.
<repl-completion>`! It is also possible to use |jedi| in the :ref:`Python shell or with IPython
<repl-completion>`.
|jedi| is relatively young and can be used in a variety of Plugins and Below you can also find a list of :ref:`recipes for type hinting <recipes>`.
Software. If your Editor/IDE is not among them, recommend |jedi| to your IDE
developers.
.. _language-servers:
Language Servers
--------------
- `jedi-language-server <https://github.com/pappasam/jedi-language-server>`_
- `python-language-server <https://github.com/palantir/python-language-server>`_
- `anakin-language-server <https://github.com/muffinmad/anakin-language-server>`_
.. _editor-plugins: .. _editor-plugins:
Editor Plugins Editor Plugins
-------------- --------------
Vim: Vim
~~~
- jedi-vim_ - jedi-vim_
- YouCompleteMe_ - YouCompleteMe_
- deoplete-jedi_ - deoplete-jedi_
Emacs: Visual Studio Code
~~~~~~~~~~~~~~~~~~
- `Python Extension`_
Emacs
~~~~~
- Jedi.el_ - Jedi.el_
- elpy_ - elpy_
- anaconda-mode_ - anaconda-mode_
Sublime Text 2/3: Sublime Text 2/3
~~~~~~~~~~~~~~~~
- SublimeJEDI_ (ST2 & ST3) - SublimeJEDI_ (ST2 & ST3)
- anaconda_ (only ST3) - anaconda_ (only ST3)
SynWrite: SynWrite
~~~~~~~~
- SynJedi_ - SynJedi_
TextMate: TextMate
~~~~~~~~
- Textmate_ (Not sure if it's actually working) - Textmate_ (Not sure if it's actually working)
Kate: Kate
~~~~
- Kate_ version 4.13+ `supports it natively - Kate_ version 4.13+ `supports it natively
<https://projects.kde.org/projects/kde/applications/kate/repository/entry/addons/kate/pate/src/plugins/python_autocomplete_jedi.py?rev=KDE%2F4.13>`__, <https://projects.kde.org/projects/kde/applications/kate/repository/entry/addons/kate/pate/src/plugins/python_autocomplete_jedi.py?rev=KDE%2F4.13>`__,
you have to enable it, though. you have to enable it, though.
Visual Studio Code: Atom
~~~~
- `Python Extension`_
Atom:
- autocomplete-python-jedi_ - autocomplete-python-jedi_
GNOME Builder: GNOME Builder
~~~~~~~~~~~~~
- `GNOME Builder`_ `supports it natively - `GNOME Builder`_ `supports it natively
<https://git.gnome.org/browse/gnome-builder/tree/plugins/jedi>`__, <https://git.gnome.org/browse/gnome-builder/tree/plugins/jedi>`__,
and is enabled by default. and is enabled by default.
Gedit: Gedit
~~~~~
- gedi_ - gedi_
Eric IDE: Eric IDE
~~~~~~~~
- `Eric IDE`_ (Available as a plugin) - `Eric IDE`_ (Available as a plugin)
Web Debugger: Web Debugger
~~~~~~~~~~~~
- wdb_ - wdb_
xonsh shell
~~~~~~~~~~~
Jedi is a preinstalled extension in `xonsh shell <https://xon.sh/contents.html>`_.
Run the following command to enable:
::
xontrib load jedi
and many more! and many more!
.. _repl-completion: .. _repl-completion:
@@ -81,11 +110,14 @@ and many more!
Tab Completion in the Python Shell Tab Completion in the Python Shell
---------------------------------- ----------------------------------
Starting with Ipython `6.0.0` Jedi is a dependency of IPython. Autocompletion Jedi is a dependency of IPython. Autocompletion in IPython is therefore
in IPython is therefore possible without additional configuration. possible without additional configuration.
Here is an `example video <https://vimeo.com/122332037>`_ how REPL completion
can look like in a different shell.
There are two different options how you can use Jedi autocompletion in There are two different options how you can use Jedi autocompletion in
your Python interpreter. One with your custom ``$HOME/.pythonrc.py`` file your ``python`` interpreter. One with your custom ``$HOME/.pythonrc.py`` file
and one that uses ``PYTHONSTARTUP``. and one that uses ``PYTHONSTARTUP``.
Using ``PYTHONSTARTUP`` Using ``PYTHONSTARTUP``
@@ -93,11 +125,137 @@ Using ``PYTHONSTARTUP``
.. automodule:: jedi.api.replstartup .. automodule:: jedi.api.replstartup
Using a custom ``$HOME/.pythonrc.py`` Using a Custom ``$HOME/.pythonrc.py``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. autofunction:: jedi.utils.setup_readline .. autofunction:: jedi.utils.setup_readline
.. _recipes:
Recipes
-------
Here are some tips on how to use |jedi| efficiently.
.. _type-hinting:
Type Hinting
~~~~~~~~~~~~
If |jedi| cannot detect the type of a function argument correctly (due to the
dynamic nature of Python), you can help it by hinting the type using
one of the docstring/annotation styles below. **Only gradual typing will
always work**, all the docstring solutions are glorified hacks and more
complicated cases will probably not work.
Official Gradual Typing (Recommended)
+++++++++++++++++++++++++++++++++++++
You can read a lot about Python's gradual typing system in the corresponding
PEPs like:
- `PEP 484 <https://www.python.org/dev/peps/pep-0484/>`_ as an introduction
- `PEP 526 <https://www.python.org/dev/peps/pep-0526/>`_ for variable annotations
- `PEP 589 <https://www.python.org/dev/peps/pep-0589/>`_ for ``TypeDict``
- There are probably more :)
Below you can find a few examples how you can use this feature.
Function annotations::
def myfunction(node: ProgramNode, foo: str) -> None:
"""Do something with a ``node``.
"""
node.| # complete here
Assignment, for-loop and with-statement type hints::
import typing
x: int = foo()
y: typing.Optional[int] = 3
key: str
value: Employee
for key, value in foo.items():
pass
f: Union[int, float]
with foo() as f:
print(f + 3)
PEP-0484 should be supported in its entirety. Feel free to open issues if that
is not the case. You can also use stub files.
Sphinx style
++++++++++++
http://www.sphinx-doc.org/en/stable/domains.html#info-field-lists
::
def myfunction(node, foo):
"""
Do something with a ``node``.
:type node: ProgramNode
:param str foo: foo parameter description
"""
node.| # complete here
Epydoc
++++++
http://epydoc.sourceforge.net/manual-fields.html
::
def myfunction(node):
"""
Do something with a ``node``.
@type node: ProgramNode
"""
node.| # complete here
Numpydoc
++++++++
https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt
In order to support the numpydoc format, you need to install the `numpydoc
<https://pypi.python.org/pypi/numpydoc>`__ package.
::
def foo(var1, var2, long_var_name='hi'):
r"""
A one-line summary that does not use variable names or the
function name.
...
Parameters
----------
var1 : array_like
Array_like means all those objects -- lists, nested lists,
etc. -- that can be converted to an array. We can also
refer to variables like `var1`.
var2 : int
The type above can either refer to an actual Python type
(e.g. ``int``), or describe the type of the variable in more
detail, e.g. ``(N,) ndarray`` or ``array_like``.
long_variable_name : {'hi', 'ho'}, optional
Choices in brackets, default first when optional.
...
"""
var2.| # complete here
.. _jedi-vim: https://github.com/davidhalter/jedi-vim .. _jedi-vim: https://github.com/davidhalter/jedi-vim
.. _youcompleteme: https://valloric.github.io/YouCompleteMe/ .. _youcompleteme: https://valloric.github.io/YouCompleteMe/
.. _deoplete-jedi: https://github.com/zchee/deoplete-jedi .. _deoplete-jedi: https://github.com/zchee/deoplete-jedi
@@ -114,4 +272,4 @@ Using a custom ``$HOME/.pythonrc.py``
.. _GNOME Builder: https://wiki.gnome.org/Apps/Builder/ .. _GNOME Builder: https://wiki.gnome.org/Apps/Builder/
.. _gedi: https://github.com/isamert/gedi .. _gedi: https://github.com/isamert/gedi
.. _Eric IDE: https://eric-ide.python-projects.org .. _Eric IDE: https://eric-ide.python-projects.org
.. _Python Extension: https://marketplace.visualstudio.com/items?itemName=donjayamanne.python .. _Python Extension: https://marketplace.visualstudio.com/items?itemName=ms-python.python
+1 -1
View File
@@ -1,3 +1,3 @@
:orphan: :orphan:
.. |jedi| replace:: *Jedi* .. |jedi| replace:: Jedi
+39 -7
View File
@@ -1,13 +1,40 @@
.. include global.rst .. include global.rst
Jedi - an awesome autocompletion/static analysis library for Python .. meta::
=================================================================== :github_url: https://github.com/davidhalter/jedi
Release v\ |release|. (:doc:`Installation <docs/installation>`) Jedi - an awesome autocompletion, static analysis and refactoring library for Python
====================================================================================
.. image:: https://img.shields.io/github/stars/davidhalter/jedi.svg?style=social&label=Star&maxAge=2592000
:target: https://github.com/davidhalter/jedi
:alt: GitHub stars
.. image:: http://isitmaintained.com/badge/open/davidhalter/jedi.svg
:target: https://github.com/davidhalter/jedi/issues
:alt: The percentage of open issues and pull requests
.. image:: http://isitmaintained.com/badge/resolution/davidhalter/jedi.svg
:target: https://github.com/davidhalter/jedi/issues
:alt: The resolution time is the median time an issue or pull request stays open.
.. image:: https://github.com/davidhalter/jedi/workflows/ci/badge.svg?branch=master
:target: https://github.com/davidhalter/jedi/actions
:alt: Tests
.. image:: https://coveralls.io/repos/davidhalter/jedi/badge.svg?branch=master
:target: https://coveralls.io/r/davidhalter/jedi
:alt: Coverage status
.. image:: https://pepy.tech/badge/jedi
:target: https://pepy.tech/project/jedi
:alt: PyPI Downloads
`Github Repository <https://github.com/davidhalter/jedi>`_
.. automodule:: jedi .. automodule:: jedi
Autocompletion can look like this (e.g. VIM plugin): Autocompletion can for example look like this in jedi-vim:
.. figure:: _screenshots/screenshot_complete.png .. figure:: _screenshots/screenshot_complete.png
@@ -18,16 +45,18 @@ Docs
---- ----
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 1
docs/usage docs/usage
docs/installation
docs/features docs/features
docs/api docs/api
docs/api-classes docs/api-classes
docs/installation
docs/settings docs/settings
docs/development docs/development
docs/testing docs/testing
docs/acknowledgements
docs/changelog
.. _resources: .. _resources:
@@ -35,6 +64,9 @@ Docs
Resources Resources
--------- ---------
If you want to stay **up-to-date** with releases, please **subscribe** to this
mailing list: https://groups.google.com/g/jedi-announce. To subscribe you can
simply send an empty email to ``jedi-announce+subscribe@googlegroups.com``.
- `Source Code on Github <https://github.com/davidhalter/jedi>`_ - `Source Code on Github <https://github.com/davidhalter/jedi>`_
- `Travis Testing <https://travis-ci.org/davidhalter/jedi>`_
- `Python Package Index <https://pypi.python.org/pypi/jedi/>`_ - `Python Package Index <https://pypi.python.org/pypi/jedi/>`_
+14 -19
View File
@@ -1,16 +1,13 @@
""" """
Jedi is a static analysis tool for Python that can be used in IDEs/editors. Jedi is a static analysis tool for Python that is typically used in
Jedi has a focus on autocompletion and goto functionality. Jedi is fast and is IDEs/editors plugins. Jedi has a focus on autocompletion and goto
very well tested. It understands Python and stubs on a deep level. functionality. Other features include refactoring, code search and finding
references.
Jedi has support for different goto functions. It's possible to search for Jedi has a simple API to work with. There is a reference implementation as a
references and list names in a Python file to get information about them. `VIM-Plugin <https://github.com/davidhalter/jedi-vim>`_. Autocompletion in your
REPL is also possible, IPython uses it natively and for the CPython REPL you
Jedi uses a very simple API to connect with IDE's. There's a reference can install it. Jedi is well tested and bugs should be rare.
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.
Autocompletion in your REPL is also possible, IPython uses it natively and for
the CPython REPL you have to install it.
Here's a simple example of the autocompletion feature: Here's a simple example of the autocompletion feature:
@@ -28,20 +25,18 @@ Here's a simple example of the autocompletion feature:
ad ad
>>> print(completions[0].name) >>> print(completions[0].name)
load load
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.16.0' __version__ = '0.18.0'
from jedi.api import Script, Interpreter, set_debug_function, \ from jedi.api import Script, Interpreter, set_debug_function, preload_module
preload_module, names
from jedi import settings from jedi import settings
from jedi.api.environment import find_virtualenvs, find_system_environments, \ from jedi.api.environment import find_virtualenvs, find_system_environments, \
get_default_environment, InvalidPythonEnvironment, create_environment, \ get_default_environment, InvalidPythonEnvironment, create_environment, \
get_system_environment get_system_environment, InterpreterEnvironment
from jedi.api.exceptions import InternalError from jedi.api.project import Project, get_default_project
from jedi.api.exceptions import InternalError, RefactoringError
# Finally load the internal plugins. This is only internal. # Finally load the internal plugins. This is only internal.
from jedi.plugins import registry from jedi.plugins import registry
del registry del registry
+15 -5
View File
@@ -27,8 +27,8 @@ def _start_linter():
paths = [path] paths = [path]
try: try:
for path in paths: for p in paths:
for error in jedi.Script(path=path)._analysis(): for error in jedi.Script(path=p)._analysis():
print(error) print(error)
except Exception: except Exception:
if '--pdb' in sys.argv: if '--pdb' in sys.argv:
@@ -44,19 +44,29 @@ def _complete():
import jedi import jedi
import pdb import pdb
if '-d' in sys.argv:
sys.argv.remove('-d')
jedi.set_debug_function()
try: try:
for c in jedi.Script(sys.argv[2]).complete(): completions = jedi.Script(sys.argv[2]).complete()
for c in completions:
c.docstring() c.docstring()
c.type c.type
except Exception: except Exception as e:
print(repr(e))
pdb.post_mortem() pdb.post_mortem()
else:
print(completions)
if len(sys.argv) == 2 and sys.argv[1] == 'repl': if len(sys.argv) == 2 and sys.argv[1] == 'repl':
# don't want to use __main__ only for repl yet, maybe we want to use it for # don't want to use __main__ only for repl yet, maybe we want to use it for
# something else. So just use the keyword ``repl`` for now. # something else. So just use the keyword ``repl`` for now.
print(join(dirname(abspath(__file__)), 'api', 'replstartup.py')) print(join(dirname(abspath(__file__)), 'api', 'replstartup.py'))
elif len(sys.argv) > 1 and sys.argv[1] == 'linter': elif len(sys.argv) > 1 and sys.argv[1] == '_linter':
_start_linter() _start_linter()
elif len(sys.argv) > 1 and sys.argv[1] == '_complete': elif len(sys.argv) > 1 and sys.argv[1] == '_complete':
_complete() _complete()
else:
print('Command not implemented: %s' % sys.argv[1])
+7 -693
View File
@@ -1,344 +1,13 @@
""" """
To ensure compatibility from Python ``2.7`` - ``3.x``, a module has been This module is here to ensure compatibility of Windows/Linux/MacOS and
created. Clearly there is huge need to use conforming syntax. different Python versions.
""" """
from __future__ import print_function
import atexit
import errno import errno
import functools
import sys import sys
import os import pickle
import re
import pkgutil
import warnings
import inspect
import subprocess
import weakref
try:
import importlib
except ImportError:
pass
from zipimport import zipimporter
from jedi.file_io import KnownContentFileIO, ZipFileIO
is_py3 = sys.version_info[0] >= 3 def cast_path(string):
is_py35 = is_py3 and sys.version_info[1] >= 5
py_version = int(str(sys.version_info[0]) + str(sys.version_info[1]))
if sys.version_info[:2] < (3, 5):
"""
A super-minimal shim around listdir that behave like
scandir for the information we need.
"""
class _DirEntry:
def __init__(self, name, basepath):
self.name = name
self.basepath = basepath
def is_dir(self):
path_for_name = os.path.join(self.basepath, self.name)
return os.path.isdir(path_for_name)
def scandir(dir):
return [_DirEntry(name, dir) for name in os.listdir(dir)]
else:
from os import scandir
class DummyFile(object):
def __init__(self, loader, string):
self.loader = loader
self.string = string
def read(self):
return self.loader.get_source(self.string)
def close(self):
del self.loader
def find_module_py34(string, path=None, full_name=None, is_global_search=True):
spec = None
loader = None
for finder in sys.meta_path:
if is_global_search and finder != importlib.machinery.PathFinder:
p = None
else:
p = path
try:
find_spec = finder.find_spec
except AttributeError:
# These are old-school clases that still have a different API, just
# ignore those.
continue
spec = find_spec(string, p)
if spec is not None:
loader = spec.loader
if loader is None and not spec.has_location:
# This is a namespace package.
full_name = string if not path else full_name
implicit_ns_info = ImplicitNSInfo(full_name, spec.submodule_search_locations._path)
return implicit_ns_info, True
break
return find_module_py33(string, path, loader)
def find_module_py33(string, path=None, loader=None, full_name=None, is_global_search=True):
loader = loader or importlib.machinery.PathFinder.find_module(string, path)
if loader is None and path is None: # Fallback to find builtins
try:
with warnings.catch_warnings(record=True):
# Mute "DeprecationWarning: Use importlib.util.find_spec()
# instead." While we should replace that in the future, it's
# probably good to wait until we deprecate Python 3.3, since
# it was added in Python 3.4 and find_loader hasn't been
# removed in 3.6.
loader = importlib.find_loader(string)
except ValueError as e:
# See #491. Importlib might raise a ValueError, to avoid this, we
# just raise an ImportError to fix the issue.
raise ImportError("Originally " + repr(e))
if loader is None:
raise ImportError("Couldn't find a loader for {}".format(string))
return _from_loader(loader, string)
def _from_loader(loader, string):
is_package = loader.is_package(string)
try:
get_filename = loader.get_filename
except AttributeError:
return None, is_package
else:
module_path = cast_path(get_filename(string))
# To avoid unicode and read bytes, "overwrite" loader.get_source if
# possible.
f = type(loader).get_source
if is_py3 and f is not importlib.machinery.SourceFileLoader.get_source:
# Unfortunately we are reading unicode here, not bytes.
# It seems hard to get bytes, because the zip importer
# logic just unpacks the zip file and returns a file descriptor
# that we cannot as easily access. Therefore we just read it as
# a string in the cases where get_source was overwritten.
code = loader.get_source(string)
else:
code = _get_source(loader, string)
if code is None:
return None, is_package
if isinstance(loader, zipimporter):
return ZipFileIO(module_path, code, cast_path(loader.archive)), is_package
return KnownContentFileIO(module_path, code), is_package
def _get_source(loader, fullname):
"""
This method is here as a replacement for SourceLoader.get_source. That
method returns unicode, but we prefer bytes.
"""
path = loader.get_filename(fullname)
try:
return loader.get_data(path)
except OSError:
raise ImportError('source not available through get_data()',
name=fullname)
def find_module_pre_py3(string, path=None, full_name=None, is_global_search=True):
# This import is here, because in other places it will raise a
# DeprecationWarning.
import imp
try:
module_file, module_path, description = imp.find_module(string, path)
module_type = description[2]
is_package = module_type is imp.PKG_DIRECTORY
if is_package:
# In Python 2 directory package imports are returned as folder
# paths, not __init__.py paths.
p = os.path.join(module_path, '__init__.py')
try:
module_file = open(p)
module_path = p
except FileNotFoundError:
pass
elif module_type != imp.PY_SOURCE:
if module_file is not None:
module_file.close()
module_file = None
if module_file is None:
code = None
return None, is_package
with module_file:
code = module_file.read()
return KnownContentFileIO(cast_path(module_path), code), is_package
except ImportError:
pass
if path is None:
path = sys.path
for item in path:
loader = pkgutil.get_importer(item)
if loader:
loader = loader.find_module(string)
if loader is not None:
return _from_loader(loader, string)
raise ImportError("No module named {}".format(string))
find_module = find_module_py34 if is_py3 else find_module_pre_py3
find_module.__doc__ = """
Provides information about a module.
This function isolates the differences in importing libraries introduced with
python 3.3 on; it gets a module name and optionally a path. It will return a
tuple containin an open file for the module (if not builtin), the filename
or the name of the module if it is a builtin one and a boolean indicating
if the module is contained in a package.
"""
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
except NameError:
unicode = str
# re-raise function
if is_py3:
def reraise(exception, traceback):
raise exception.with_traceback(traceback)
else:
eval(compile("""
def reraise(exception, traceback):
raise exception, None, traceback
""", 'blub', 'exec'))
reraise.__doc__ = """
Re-raise `exception` with a `traceback` object.
Usage::
reraise(Exception, sys.exc_info()[2])
"""
def use_metaclass(meta, *bases):
""" Create a class with a metaclass. """
if not bases:
bases = (object,)
return meta("Py2CompatibilityMetaClass", bases, {})
try:
encoding = sys.stdout.encoding
if encoding is None:
encoding = 'utf-8'
except AttributeError:
encoding = 'ascii'
def u(string, errors='strict'):
"""Cast to unicode DAMMIT!
Written because Python2 repr always implicitly casts to a string, so we
have to cast back to a unicode (and we now that we always deal with valid
unicode, because we check that in the beginning).
"""
if isinstance(string, bytes):
return unicode(string, encoding='UTF-8', errors=errors)
return string
def cast_path(obj):
""" """
Take a bytes or str path and cast it to unicode. Take a bytes or str path and cast it to unicode.
@@ -349,154 +18,13 @@ def cast_path(obj):
Since this just really complicates everything and Python 2.7 will be EOL Since this just really complicates everything and Python 2.7 will be EOL
soon anyway, just go with always strings. soon anyway, just go with always strings.
""" """
return u(obj, errors='replace') if isinstance(string, bytes):
return str(string, encoding='UTF-8', errors='replace')
return str(string)
def force_unicode(obj):
# Intentionally don't mix those two up, because those two code paths might
# be different in the future (maybe windows?).
return cast_path(obj)
try:
import builtins # module name in python 3
except ImportError:
import __builtin__ as builtins # noqa: F401
import ast # noqa: F401
def literal_eval(string):
return ast.literal_eval(string)
try:
from itertools import zip_longest
except ImportError:
from itertools import izip_longest as zip_longest # Python 2 # noqa: F401
try:
FileNotFoundError = FileNotFoundError
except NameError:
FileNotFoundError = IOError
try:
IsADirectoryError = IsADirectoryError
except NameError:
IsADirectoryError = IOError
try:
PermissionError = PermissionError
except NameError:
PermissionError = IOError
def no_unicode_pprint(dct):
"""
Python 2/3 dict __repr__ may be different, because of unicode differens
(with or without a `u` prefix). Normally in doctests we could use `pprint`
to sort dicts and check for equality, but here we have to write a separate
function to do that.
"""
import pprint
s = pprint.pformat(dct)
print(re.sub("u'", "'", s))
def utf8_repr(func):
"""
``__repr__`` methods in Python 2 don't allow unicode objects to be
returned. Therefore cast them to utf-8 bytes in this decorator.
"""
def wrapper(self):
result = func(self)
if isinstance(result, unicode):
return result.encode('utf-8')
else:
return result
if is_py3:
return func
else:
return wrapper
if is_py3:
import queue
else:
import Queue as queue # noqa: F401
try:
# Attempt to load the C implementation of pickle on Python 2 as it is way
# faster.
import cPickle as pickle
except ImportError:
import pickle
if 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): def pickle_load(file):
try: try:
if is_py3:
return pickle.load(file, encoding='bytes')
return pickle.load(file) return pickle.load(file)
# Python on Windows don't throw EOF errors for pipes. So reraise them with # Python on Windows don't throw EOF errors for pipes. So reraise them with
# the correct type, which is caught upwards. # the correct type, which is caught upwards.
@@ -506,24 +34,8 @@ def pickle_load(file):
raise raise
def _python2_dct_keys_to_unicode(data):
"""
Python 2 stores object __dict__ entries as bytes, not unicode, correct it
here. Python 2 can deal with both, Python 3 expects unicode.
"""
if isinstance(data, tuple):
return tuple(_python2_dct_keys_to_unicode(x) for x in data)
elif isinstance(data, list):
return list(_python2_dct_keys_to_unicode(x) for x in data)
elif hasattr(data, '__dict__') and type(data.__dict__) == dict:
data.__dict__ = {unicode(k): v for k, v in data.__dict__.items()}
return data
def pickle_dump(data, file, protocol): def pickle_dump(data, file, protocol):
try: try:
if not is_py3:
data = _python2_dct_keys_to_unicode(data)
pickle.dump(data, file, protocol) pickle.dump(data, file, protocol)
# On Python 3.3 flush throws sometimes an error even though the writing # On Python 3.3 flush throws sometimes an error even though the writing
# operation should be completed. # operation should be completed.
@@ -534,201 +46,3 @@ def pickle_dump(data, file, protocol):
if sys.platform == 'win32': if sys.platform == 'win32':
raise IOError(errno.EPIPE, "Broken pipe") raise IOError(errno.EPIPE, "Broken pipe")
raise raise
# Determine the highest protocol version compatible for a given list of Python
# versions.
def highest_pickle_protocol(python_versions):
protocol = 4
for version in python_versions:
if version[0] == 2:
# The minimum protocol version for the versions of Python that we
# support (2.7 and 3.3+) is 2.
return 2
if version[1] < 4:
protocol = 3
return protocol
try:
from inspect import Parameter
except ImportError:
class Parameter(object):
POSITIONAL_ONLY = object()
POSITIONAL_OR_KEYWORD = object()
VAR_POSITIONAL = object()
KEYWORD_ONLY = object()
VAR_KEYWORD = object()
class GeneralizedPopen(subprocess.Popen):
def __init__(self, *args, **kwargs):
if os.name == 'nt':
try:
# Was introduced in Python 3.7.
CREATE_NO_WINDOW = subprocess.CREATE_NO_WINDOW
except AttributeError:
CREATE_NO_WINDOW = 0x08000000
kwargs['creationflags'] = CREATE_NO_WINDOW
# The child process doesn't need file descriptors except 0, 1, 2.
# This is unix only.
kwargs['close_fds'] = 'posix' in sys.builtin_module_names
super(GeneralizedPopen, self).__init__(*args, **kwargs)
# shutil.which is not available on Python 2.7.
def which(cmd, mode=os.F_OK | os.X_OK, path=None):
"""Given a command, mode, and a PATH string, return the path which
conforms to the given mode on the PATH, or None if there is no such
file.
`mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result
of os.environ.get("PATH"), or can be overridden with a custom search
path.
"""
# Check that a given file can be accessed with the correct mode.
# Additionally check that `file` is not a directory, as on Windows
# directories pass the os.access check.
def _access_check(fn, mode):
return (os.path.exists(fn) and os.access(fn, mode)
and not os.path.isdir(fn))
# If we're given a path with a directory part, look it up directly rather
# than referring to PATH directories. This includes checking relative to the
# current directory, e.g. ./script
if os.path.dirname(cmd):
if _access_check(cmd, mode):
return cmd
return None
if path is None:
path = os.environ.get("PATH", os.defpath)
if not path:
return None
path = path.split(os.pathsep)
if sys.platform == "win32":
# The current directory takes precedence on Windows.
if os.curdir not in path:
path.insert(0, os.curdir)
# PATHEXT is necessary to check on Windows.
pathext = os.environ.get("PATHEXT", "").split(os.pathsep)
# See if the given file matches any of the expected path extensions.
# This will allow us to short circuit when given "python.exe".
# If it does match, only test that one, otherwise we have to try
# others.
if any(cmd.lower().endswith(ext.lower()) for ext in pathext):
files = [cmd]
else:
files = [cmd + ext for ext in pathext]
else:
# On other platforms you don't have things like PATHEXT to tell you
# what file suffixes are executable, so just pass on cmd as-is.
files = [cmd]
seen = set()
for dir in path:
normdir = os.path.normcase(dir)
if normdir not in seen:
seen.add(normdir)
for thefile in files:
name = os.path.join(dir, thefile)
if _access_check(name, mode):
return name
return None
if not is_py3:
# Simplified backport of Python 3 weakref.finalize:
# https://github.com/python/cpython/blob/ded4737989316653469763230036b04513cb62b3/Lib/weakref.py#L502-L662
class finalize(object):
"""Class for finalization of weakrefable objects.
finalize(obj, func, *args, **kwargs) returns a callable finalizer
object which will be called when obj is garbage collected. The
first time the finalizer is called it evaluates func(*arg, **kwargs)
and returns the result. After this the finalizer is dead, and
calling it just returns None.
When the program exits any remaining finalizers will be run.
"""
# Finalizer objects don't have any state of their own.
# This ensures that they cannot be part of a ref-cycle.
__slots__ = ()
_registry = {}
def __init__(self, obj, func, *args, **kwargs):
info = functools.partial(func, *args, **kwargs)
info.weakref = weakref.ref(obj, self)
self._registry[self] = info
# To me it's an absolute mystery why in Python 2 we need _=None. It
# makes really no sense since it's never really called. Then again it
# might be called by Python 2.7 itself, but weakref.finalize is not
# documented in Python 2 and therefore shouldn't be randomly called.
# We never call this stuff with a parameter and therefore this
# parameter should not be needed. But it is. ~dave
def __call__(self, _=None):
"""Return func(*args, **kwargs) if alive."""
info = self._registry.pop(self, None)
if info:
return info()
@classmethod
def _exitfunc(cls):
if not cls._registry:
return
for finalizer in list(cls._registry):
try:
finalizer()
except Exception:
sys.excepthook(*sys.exc_info())
assert finalizer not in cls._registry
atexit.register(finalize._exitfunc)
weakref.finalize = finalize
if is_py3 and sys.version_info[1] > 5:
from inspect import unwrap
else:
# Only Python >=3.6 does properly limit the amount of unwraps. This is very
# relevant in the case of unittest.mock.patch.
# Below is the implementation of Python 3.7.
def unwrap(func, stop=None):
"""Get the object wrapped by *func*.
Follows the chain of :attr:`__wrapped__` attributes returning the last
object in the chain.
*stop* is an optional callback accepting an object in the wrapper chain
as its sole argument that allows the unwrapping to be terminated early if
the callback returns a true value. If the callback never returns a true
value, the last object in the chain is returned as usual. For example,
:func:`signature` uses this to stop unwrapping if any object in the
chain has a ``__signature__`` attribute defined.
:exc:`ValueError` is raised if a cycle is encountered.
"""
if stop is None:
def _is_wrapper(f):
return hasattr(f, '__wrapped__')
else:
def _is_wrapper(f):
return hasattr(f, '__wrapped__') and not stop(f)
f = func # remember the original func for error reporting
# Memoise by id to tolerate non-hashable objects, but store objects to
# ensure they aren't destroyed, which would allow their IDs to be reused.
memo = {id(f): f}
recursion_limit = sys.getrecursionlimit()
while _is_wrapper(func):
func = func.__wrapped__
id_func = id(func)
if (id_func in memo) or (len(memo) >= recursion_limit):
raise ValueError('wrapper loop when unwrapping {!r}'.format(f))
memo[id_func] = func
return func
+352 -202
View File
@@ -6,17 +6,14 @@ Additionally you can add a debug function with :func:`set_debug_function`.
Alternatively, if you don't need a custom function and are happy with printing Alternatively, if you don't need a custom function and are happy with printing
debug messages to stdout, simply call :func:`set_debug_function` without debug messages to stdout, simply call :func:`set_debug_function` without
arguments. arguments.
.. warning:: Please, note that Jedi is **not thread safe**.
""" """
import os
import sys import sys
import warnings from pathlib import Path
import parso import parso
from parso.python import tree from parso.python import tree
from jedi._compatibility import force_unicode, cast_path, is_py3 from jedi._compatibility import cast_path
from jedi.parser_utils import get_executable_nodes from jedi.parser_utils import get_executable_nodes
from jedi import debug from jedi import debug
from jedi import settings from jedi import settings
@@ -26,15 +23,18 @@ from jedi.api import classes
from jedi.api import interpreter from jedi.api import interpreter
from jedi.api import helpers from jedi.api import helpers
from jedi.api.helpers import validate_line_column from jedi.api.helpers import validate_line_column
from jedi.api.completion import Completion from jedi.api.completion import Completion, search_in_module
from jedi.api.keywords import KeywordName from jedi.api.keywords import KeywordName
from jedi.api.environment import InterpreterEnvironment from jedi.api.environment import InterpreterEnvironment
from jedi.api.project import get_default_project, Project from jedi.api.project import get_default_project, Project
from jedi.api.errors import parso_to_jedi_errors
from jedi.api import refactoring
from jedi.api.refactoring.extract import extract_function, extract_variable
from jedi.inference import InferenceState from jedi.inference import InferenceState
from jedi.inference import imports from jedi.inference import imports
from jedi.inference.references import find_references from jedi.inference.references import find_references
from jedi.inference.arguments import try_iter_content from jedi.inference.arguments import try_iter_content
from jedi.inference.helpers import get_module_names, infer_call_of_leaf from jedi.inference.helpers import infer_call_of_leaf
from jedi.inference.sys_path import transform_path_to_dotted from jedi.inference.sys_path import transform_path_to_dotted
from jedi.inference.syntax_tree import tree_name_to_values from jedi.inference.syntax_tree import tree_name_to_values
from jedi.inference.value import ModuleValue from jedi.inference.value import ModuleValue
@@ -42,93 +42,94 @@ from jedi.inference.base_value import ValueSet
from jedi.inference.value.iterable import unpack_tuple_to_dict from jedi.inference.value.iterable import unpack_tuple_to_dict
from jedi.inference.gradual.conversion import convert_names, convert_values from jedi.inference.gradual.conversion import convert_names, convert_values
from jedi.inference.gradual.utils import load_proper_stub_module from jedi.inference.gradual.utils import load_proper_stub_module
from jedi.inference.utils import to_list
# Jedi uses lots and lots of recursion. By setting this a little bit higher, we # Jedi uses lots and lots of recursion. By setting this a little bit higher, we
# can remove some "maximum recursion depth" errors. # can remove some "maximum recursion depth" errors.
sys.setrecursionlimit(3000) sys.setrecursionlimit(3000)
class Script(object): class Script:
""" """
A Script is the base for completions, goto or whatever you want to do with A Script is the base for completions, goto or whatever you want to do with
|jedi|. Jedi. The counter part of this class is :class:`Interpreter`, which works
with actual dictionaries and can work with a REPL. This class
should be used when a user edits code in an editor.
You can either use the ``source`` parameter or ``path`` to read a file. You can either use the ``code`` parameter or ``path`` to read a file.
Usually you're going to want to use both of them (in an editor). Usually you're going to want to use both of them (in an editor).
The script might be analyzed in a different ``sys.path`` than |jedi|: The Script's ``sys.path`` is very customizable:
- if `sys_path` parameter is not ``None``, it will be used as ``sys.path`` - If `project` is provided with a ``sys_path``, that is going to be used.
for the script; - If `environment` is provided, its ``sys.path`` will be used
(see :func:`Environment.get_sys_path <jedi.api.environment.Environment.get_sys_path>`);
- Otherwise ``sys.path`` will match that of the default environment of
Jedi, which typically matches the sys path that was used at the time
when Jedi was imported.
- if `sys_path` parameter is ``None`` and ``VIRTUAL_ENV`` environment Most methods have a ``line`` and a ``column`` parameter. Lines in Jedi are
variable is defined, ``sys.path`` for the specified environment will be always 1-based and columns are always zero based. To avoid repetition they
guessed (see :func:`jedi.inference.sys_path.get_venv_path`) and used for are not always documented. You can omit both line and column. Jedi will
the script; then just do whatever action you are calling at the end of the file. If you
provide only the line, just will complete at the end of that line.
- otherwise ``sys.path`` will match that of |jedi|. .. warning:: By default :attr:`jedi.settings.fast_parser` is enabled, which means
that parso reuses modules (i.e. they are not immutable). With this setting
Jedi is **not thread safe** and it is also not safe to use multiple
:class:`.Script` instances and its definitions at the same time.
:param source: The source code of the current file, separated by newlines. If you are a normal plugin developer this should not be an issue. It is
:type source: str an issue for people that do more complex stuff with Jedi.
:param line: Deprecated, please use it directly on e.g. `.complete`
:type line: int This is purely a performance optimization and works pretty well for all
:param column: Deprecated, please use it directly on e.g. `.complete` typical usages, however consider to turn the setting off if it causes
:type column: int you problems. See also
`this discussion <https://github.com/davidhalter/jedi/issues/1240>`_.
:param code: The source code of the current file, separated by newlines.
:type code: str
:param path: The path of the file in the file system, or ``''`` if :param path: The path of the file in the file system, or ``''`` if
it hasn't been saved yet. it hasn't been saved yet.
:type path: str or None :type path: str or pathlib.Path or None
:param encoding: The encoding of ``source``, if it is not a :param Environment environment: Provide a predefined :ref:`Environment <environments>`
``unicode`` object (default ``'utf-8'``). to work with a specific Python version or virtualenv.
:type encoding: str :param Project project: Provide a :class:`.Project` to make sure finding
:param sys_path: ``sys.path`` to use during analysis of the script references works well, because the right folder is searched. There are
:type sys_path: list also ways to modify the sys path and other things.
:param environment: TODO
:type environment: Environment
""" """
def __init__(self, source=None, line=None, column=None, path=None, def __init__(self, code=None, *, path=None, environment=None, project=None):
encoding='utf-8', sys_path=None, environment=None,
_project=None):
self._orig_path = path self._orig_path = path
# An empty path (also empty string) should always result in no path. # An empty path (also empty string) should always result in no path.
self.path = os.path.abspath(path) if path else None if isinstance(path, str):
path = Path(path)
if source is None: self.path = path.absolute() if path else None
if code is None:
# TODO add a better warning than the traceback! # TODO add a better warning than the traceback!
with open(path, 'rb') as f: with open(path, 'rb') as f:
source = f.read() code = f.read()
# 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))
project = _project
if project is None: if project is None:
# Load the Python grammar of the current interpreter. # Load the Python grammar of the current interpreter.
project = get_default_project( project = get_default_project(None if self.path is None else self.path.parent)
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._inference_state = InferenceState( self._inference_state = InferenceState(
project, environment=environment, script_path=self.path project, environment=environment, script_path=self.path
) )
debug.speed('init') debug.speed('init')
self._module_node, source = self._inference_state.parse_and_get_code( self._module_node, code = self._inference_state.parse_and_get_code(
code=source, code=code,
path=self.path, path=self.path,
encoding=encoding, use_latest_grammar=path and path.suffix == '.pyi',
use_latest_grammar=path and path.endswith('.pyi'),
cache=False, # No disk cache, because the current script often changes. cache=False, # No disk cache, because the current script often changes.
diff_cache=settings.fast_parser, diff_cache=settings.fast_parser,
cache_path=settings.cache_directory, cache_path=settings.cache_directory,
) )
debug.speed('parsed') debug.speed('parsed')
self._code_lines = parso.split_lines(source, keepends=True) self._code_lines = parso.split_lines(code, keepends=True)
self._code = source self._code = code
self._pos = line, column
cache.clear_time_caches() cache.clear_time_caches()
debug.reset_time() debug.reset_time()
@@ -152,10 +153,11 @@ class Script(object):
file_io = None file_io = None
else: else:
file_io = KnownContentFileIO(cast_path(self.path), self._code) file_io = KnownContentFileIO(cast_path(self.path), self._code)
if self.path is not None and self.path.endswith('.pyi'): if self.path is not None and self.path.suffix == '.pyi':
# We are in a stub file. Try to load the stub properly. # We are in a stub file. Try to load the stub properly.
stub_module = load_proper_stub_module( stub_module = load_proper_stub_module(
self._inference_state, self._inference_state,
self._inference_state.latest_grammar,
file_io, file_io,
names, names,
self._module_node self._module_node
@@ -173,7 +175,7 @@ class Script(object):
code_lines=self._code_lines, code_lines=self._code_lines,
is_package=is_package, is_package=is_package,
) )
if names[0] not in ('builtins', '__builtin__', 'typing'): if names[0] not in ('builtins', 'typing'):
# These modules are essential for Jedi, so don't overwrite them. # These modules are essential for Jedi, so don't overwrite them.
self._inference_state.module_cache.add(names, ValueSet([module])) self._inference_state.module_cache.add(names, ValueSet([module]))
return module return module
@@ -189,19 +191,20 @@ class Script(object):
) )
@validate_line_column @validate_line_column
def complete(self, line=None, column=None, **kwargs): def complete(self, line=None, column=None, *, fuzzy=False):
""" """
Return :class:`classes.Completion` objects. Those objects contain Completes objects under the cursor.
information about the completions, more than just names.
Those objects contain information about the completions, more than just
names.
:param fuzzy: Default False. Will return fuzzy completions, which means :param fuzzy: Default False. Will return fuzzy completions, which means
that e.g. ``ooa`` will match ``foobar``. that e.g. ``ooa`` will match ``foobar``.
:return: Completion objects, sorted by name and ``__`` comes last. :return: Completion objects, sorted by name. Normal names appear
:rtype: list of :class:`classes.Completion` before "private" names that start with ``_`` and those appear
before magic methods and name mangled names that start with ``__``.
:rtype: list of :class:`.Completion`
""" """
return self._complete(line, column, **kwargs)
def _complete(self, line, column, fuzzy=False): # Python 2...
with debug.increase_indent_cm('complete'): with debug.increase_indent_cm('complete'):
completion = Completion( completion = Completion(
self._inference_state, self._get_module_context(), self._code_lines, self._inference_state, self._get_module_context(), self._code_lines,
@@ -209,40 +212,34 @@ class Script(object):
) )
return completion.complete() return completion.complete()
def completions(self, fuzzy=False):
# Deprecated, will be removed.
return self.complete(*self._pos, fuzzy=fuzzy)
@validate_line_column @validate_line_column
def infer(self, line=None, column=None, **kwargs): def infer(self, line=None, column=None, *, only_stubs=False, prefer_stubs=False):
""" """
Return the definitions of a the path under the cursor. goto function! Return the definitions of under the cursor. It is basically a wrapper
This follows complicated paths and returns the end, not the first around Jedi's type inference.
definition. The big difference between :meth:`goto` and
This method follows complicated paths and returns the end, not the
first definition. The big difference between :meth:`goto` and
:meth:`infer` is that :meth:`goto` doesn't :meth:`infer` is that :meth:`goto` doesn't
follow imports and statements. Multiple objects may be returned, follow imports and statements. Multiple objects may be returned,
because Python itself is a dynamic language, which means depending on because depending on an option you can have two different versions of a
an option you can have two different versions of a function. function.
:param only_stubs: Only return stubs for this goto call. :param only_stubs: Only return stubs for this method.
:param prefer_stubs: Prefer stubs to Python objects for this type :param prefer_stubs: Prefer stubs to Python objects for this method.
inference call. :rtype: list of :class:`.Name`
:rtype: list of :class:`classes.Definition`
""" """
with debug.increase_indent_cm('infer'):
return self._infer(line, column, **kwargs)
def goto_definitions(self, **kwargs):
# Deprecated, will be removed.
return self.infer(*self._pos, **kwargs)
def _infer(self, line, column, only_stubs=False, prefer_stubs=False):
pos = line, column pos = line, column
leaf = self._module_node.get_name_of_position(pos) leaf = self._module_node.get_name_of_position(pos)
if leaf is None: if leaf is None:
leaf = self._module_node.get_leaf_for_position(pos) leaf = self._module_node.get_leaf_for_position(pos)
if leaf is None or leaf.type == 'string': if leaf is None or leaf.type == 'string':
return [] return []
if leaf.end_pos == (line, column) and leaf.type == 'operator':
next_ = leaf.get_next_leaf()
if next_.start_pos == leaf.end_pos \
and next_.type in ('number', 'string', 'keyword'):
leaf = next_
context = self._get_module_context().create_context(leaf) context = self._get_module_context().create_context(leaf)
@@ -253,39 +250,28 @@ class Script(object):
prefer_stubs=prefer_stubs, prefer_stubs=prefer_stubs,
) )
defs = [classes.Definition(self._inference_state, c.name) for c in values] defs = [classes.Name(self._inference_state, c.name) for c in values]
# The additional set here allows the definitions to become unique in an # The additional set here allows the definitions to become unique in an
# API sense. In the internals we want to separate more things than in # API sense. In the internals we want to separate more things than in
# the API. # the API.
return helpers.sorted_definitions(set(defs)) return helpers.sorted_definitions(set(defs))
def goto_assignments(self, follow_imports=False, follow_builtin_imports=False, **kwargs):
# Deprecated, will be removed.
return self.goto(*self._pos,
follow_imports=follow_imports,
follow_builtin_imports=follow_builtin_imports,
**kwargs)
@validate_line_column @validate_line_column
def goto(self, line=None, column=None, **kwargs): def goto(self, line=None, column=None, *, follow_imports=False, follow_builtin_imports=False,
only_stubs=False, prefer_stubs=False):
""" """
Return the first definition found, while optionally following imports. Goes to the name that defined the object under the cursor. Optionally
Multiple objects may be returned, because Python itself is a you can follow imports.
dynamic language, which means depending on an option you can have two Multiple objects may be returned, depending on an if you can have two
different versions of a function. different versions of a function.
:param follow_imports: The goto call will follow imports. :param follow_imports: The method will follow imports.
:param follow_builtin_imports: If follow_imports is True will decide if :param follow_builtin_imports: If ``follow_imports`` is True will try
it follow builtin imports. to look up names in builtins (i.e. compiled or extension modules).
:param only_stubs: Only return stubs for this goto call. :param only_stubs: Only return stubs for this method.
:param prefer_stubs: Prefer stubs to Python objects for this goto call. :param prefer_stubs: Prefer stubs to Python objects for this method.
:rtype: list of :class:`classes.Definition` :rtype: list of :class:`.Name`
""" """
with debug.increase_indent_cm('goto'):
return self._goto(line, column, **kwargs)
def _goto(self, line, column, follow_imports=False, follow_builtin_imports=False,
only_stubs=False, prefer_stubs=False):
tree_name = self._module_node.get_name_of_position((line, column)) tree_name = self._module_node.get_name_of_position((line, column))
if tree_name is None: if tree_name is None:
# Without a name we really just want to jump to the result e.g. # Without a name we really just want to jump to the result e.g.
@@ -310,78 +296,130 @@ class Script(object):
names = list(name.goto()) names = list(name.goto())
if follow_imports: if follow_imports:
names = helpers.filter_follow_imports(names) names = helpers.filter_follow_imports(names, follow_builtin_imports)
names = convert_names( names = convert_names(
names, names,
only_stubs=only_stubs, only_stubs=only_stubs,
prefer_stubs=prefer_stubs, prefer_stubs=prefer_stubs,
) )
defs = [classes.Definition(self._inference_state, d) for d in set(names)] defs = [classes.Name(self._inference_state, d) for d in set(names)]
return helpers.sorted_definitions(defs) # Avoid duplicates
return list(set(helpers.sorted_definitions(defs)))
def search(self, string, *, all_scopes=False):
"""
Searches a name in the current file. For a description of how the
search string should look like, please have a look at
:meth:`.Project.search`.
:param bool all_scopes: Default False; searches not only for
definitions on the top level of a module level, but also in
functions and classes.
:yields: :class:`.Name`
"""
return self._search_func(string, all_scopes=all_scopes)
@to_list
def _search_func(self, string, all_scopes=False, complete=False, fuzzy=False):
names = self._names(all_scopes=all_scopes)
wanted_type, wanted_names = helpers.split_search_string(string)
return search_in_module(
self._inference_state,
self._get_module_context(),
names=names,
wanted_type=wanted_type,
wanted_names=wanted_names,
complete=complete,
fuzzy=fuzzy,
)
def complete_search(self, string, **kwargs):
"""
Like :meth:`.Script.search`, but completes that string. If you want to
have all possible definitions in a file you can also provide an empty
string.
:param bool all_scopes: Default False; searches not only for
definitions on the top level of a module level, but also in
functions and classes.
:param fuzzy: Default False. Will return fuzzy completions, which means
that e.g. ``ooa`` will match ``foobar``.
:yields: :class:`.Completion`
"""
return self._search_func(string, complete=True, **kwargs)
@validate_line_column @validate_line_column
def help(self, line=None, column=None): def help(self, line=None, column=None):
""" """
Works like goto and returns a list of Definition objects. Returns Used to display a help window to users. Uses :meth:`.Script.goto` and
additional definitions for keywords and operators. returns additional definitions for keywords and operators.
The additional definitions are of ``Definition(...).type == 'keyword'``. Typically you will want to display :meth:`.BaseName.docstring` to the
user for all the returned definitions.
The additional definitions are ``Name(...).type == 'keyword'``.
These definitions do not have a lot of value apart from their docstring These definitions do not have a lot of value apart from their docstring
attribute, which contains the output of Python's ``help()`` function. attribute, which contains the output of Python's :func:`help` function.
:rtype: list of :class:`classes.Definition` :rtype: list of :class:`.Name`
""" """
definitions = self.goto(line, column, follow_imports=True) definitions = self.goto(line, column, follow_imports=True)
if definitions: if definitions:
return definitions return definitions
leaf = self._module_node.get_leaf_for_position((line, column)) leaf = self._module_node.get_leaf_for_position((line, column))
if leaf.type in ('keyword', 'operator', 'error_leaf'): if leaf is not None and leaf.type in ('keyword', 'operator', 'error_leaf'):
reserved = self._grammar._pgen_grammar.reserved_syntax_strings.keys() def need_pydoc():
if leaf.value in reserved: if leaf.value in ('(', ')', '[', ']'):
name = KeywordName(self._inference_state, leaf.value) if leaf.parent.type == 'trailer':
return [classes.Definition(self._inference_state, name)] return False
return [] if leaf.parent.type == 'atom':
return False
grammar = self._inference_state.grammar
# This parso stuff is not public, but since I control it, this
# is fine :-) ~dave
reserved = grammar._pgen_grammar.reserved_syntax_strings.keys()
return leaf.value in reserved
def usages(self, **kwargs): if need_pydoc():
# Deprecated, will be removed. name = KeywordName(self._inference_state, leaf.value)
return self.get_references(*self._pos, **kwargs) return [classes.Name(self._inference_state, name)]
return []
@validate_line_column @validate_line_column
def get_references(self, line=None, column=None, **kwargs): def get_references(self, line=None, column=None, **kwargs):
""" """
Return :class:`classes.Definition` objects, which contain all Lists all references of a variable in a project. Since this can be
names that point to the definition of the name under the cursor. This quite hard to do for Jedi, if it is too complicated, Jedi will stop
is very useful for refactoring (renaming), or to show all references of searching.
a variable.
:param include_builtins: Default True, checks if a reference is a :param include_builtins: Default ``True``. If ``False``, checks if a definition
builtin (e.g. ``sys``) and in that case does not return it. is a builtin (e.g. ``sys``) and in that case does not return it.
:rtype: list of :class:`classes.Definition` :param scope: Default ``'project'``. If ``'file'``, include references in
the current module only.
:rtype: list of :class:`.Name`
""" """
def _references(include_builtins=True): def _references(include_builtins=True, scope='project'):
if scope not in ('project', 'file'):
raise ValueError('Only the scopes "file" and "project" are allowed')
tree_name = self._module_node.get_name_of_position((line, column)) tree_name = self._module_node.get_name_of_position((line, column))
if tree_name is None: if tree_name is None:
# Must be syntax # Must be syntax
return [] return []
names = find_references(self._get_module_context(), tree_name) names = find_references(self._get_module_context(), tree_name, scope == 'file')
definitions = [classes.Definition(self._inference_state, n) for n in names] definitions = [classes.Name(self._inference_state, n) for n in names]
if not include_builtins: if not include_builtins or scope == 'file':
definitions = [d for d in definitions if not d.in_builtin_module()] definitions = [d for d in definitions if not d.in_builtin_module()]
return helpers.sorted_definitions(definitions) return helpers.sorted_definitions(definitions)
return _references(**kwargs) return _references(**kwargs)
def call_signatures(self):
# Deprecated, will be removed.
return self.get_signatures(*self._pos)
@validate_line_column @validate_line_column
def get_signatures(self, line=None, column=None): def get_signatures(self, line=None, column=None):
""" """
Return the function object of the call you're currently in. Return the function object of the call under the cursor.
E.g. if the cursor is here:: E.g. if the cursor is here::
@@ -393,7 +431,7 @@ class Script(object):
This would return an empty list.. This would return an empty list..
:rtype: list of :class:`classes.Signature` :rtype: list of :class:`.Signature`
""" """
pos = line, column pos = line, column
call_details = helpers.get_signature_details(self._module_node, pos) call_details = helpers.get_signature_details(self._module_node, pos)
@@ -417,6 +455,12 @@ class Script(object):
@validate_line_column @validate_line_column
def get_context(self, line=None, column=None): def get_context(self, line=None, column=None):
"""
Returns the scope context under the cursor. This basically means the
function, class or module where the cursor is at.
:rtype: :class:`.Name`
"""
pos = (line, column) pos = (line, column)
leaf = self._module_node.get_leaf_for_position(pos, include_prefixes=True) leaf = self._module_node.get_leaf_for_position(pos, include_prefixes=True)
if leaf.start_pos > pos or leaf.type == 'endmarker': if leaf.start_pos > pos or leaf.type == 'endmarker':
@@ -439,7 +483,7 @@ class Script(object):
while context.name is None: while context.name is None:
context = context.parent_context # comprehensions context = context.parent_context # comprehensions
definition = classes.Definition(self._inference_state, context.name) definition = classes.Name(self._inference_state, context.name)
while definition.type != 'module': while definition.type != 'module':
name = definition._name # TODO private access name = definition._name # TODO private access
tree_name = name.tree_name tree_name = name.tree_name
@@ -486,66 +530,186 @@ class Script(object):
def get_names(self, **kwargs): def get_names(self, **kwargs):
""" """
Returns a list of `Definition` objects, containing name parts. Returns names defined in the current file.
This means you can call ``Definition.goto()`` and get the
reference of a name.
:param all_scopes: If True lists the names of all scopes instead of only :param all_scopes: If True lists the names of all scopes instead of
the module namespace. only the module namespace.
:param definitions: If True lists the names that have been defined by a :param definitions: If True lists the names that have been defined by a
class, function or a statement (``a = b`` returns ``a``). class, function or a statement (``a = b`` returns ``a``).
:param references: If True lists all the names that are not listed by :param references: If True lists all the names that are not listed by
``definitions=True``. E.g. ``a = b`` returns ``b``. ``definitions=True``. E.g. ``a = b`` returns ``b``.
:rtype: list of :class:`.Name`
""" """
return self._names(**kwargs) # Python 2... names = self._names(**kwargs)
return [classes.Name(self._inference_state, n) for n in names]
def get_syntax_errors(self):
"""
Lists all syntax errors in the current file.
:rtype: list of :class:`.SyntaxError`
"""
return parso_to_jedi_errors(self._inference_state.grammar, self._module_node)
def _names(self, all_scopes=False, definitions=True, references=False): def _names(self, all_scopes=False, definitions=True, references=False):
def def_ref_filter(_def):
is_def = _def._name.tree_name.is_definition()
return definitions and is_def or references and not is_def
# Set line/column to a random position, because they don't matter. # Set line/column to a random position, because they don't matter.
module_context = self._get_module_context() module_context = self._get_module_context()
defs = [ defs = [
classes.Definition(
self._inference_state,
module_context.create_name(name) module_context.create_name(name)
) for name in get_module_names(self._module_node, all_scopes) for name in helpers.get_module_names(
self._module_node,
all_scopes=all_scopes,
definitions=definitions,
references=references,
)
] ]
return sorted(filter(def_ref_filter, defs), key=lambda x: (x.line, x.column)) return sorted(defs, key=lambda x: x.start_pos)
def rename(self, line=None, column=None, *, new_name):
"""
Renames all references of the variable under the cursor.
:param new_name: The variable under the cursor will be renamed to this
string.
:raises: :exc:`.RefactoringError`
:rtype: :class:`.Refactoring`
"""
definitions = self.get_references(line, column, include_builtins=False)
return refactoring.rename(self._inference_state, definitions, new_name)
@validate_line_column
def extract_variable(self, line, column, *, new_name, until_line=None, until_column=None):
"""
Moves an expression to a new statemenet.
For example if you have the cursor on ``foo`` and provide a
``new_name`` called ``bar``::
foo = 3.1
x = int(foo + 1)
the code above will become::
foo = 3.1
bar = foo + 1
x = int(bar)
:param new_name: The expression under the cursor will be renamed to
this string.
:param int until_line: The the selection range ends at this line, when
omitted, Jedi will be clever and try to define the range itself.
:param int until_column: The the selection range ends at this column, when
omitted, Jedi will be clever and try to define the range itself.
:raises: :exc:`.RefactoringError`
:rtype: :class:`.Refactoring`
"""
if until_line is None and until_column is None:
until_pos = None
else:
if until_line is None:
until_line = line
if until_column is None:
until_column = len(self._code_lines[until_line - 1])
until_pos = until_line, until_column
return extract_variable(
self._inference_state, self.path, self._module_node,
new_name, (line, column), until_pos
)
@validate_line_column
def extract_function(self, line, column, *, new_name, until_line=None, until_column=None):
"""
Moves an expression to a new function.
For example if you have the cursor on ``foo`` and provide a
``new_name`` called ``bar``::
global_var = 3
def x():
foo = 3.1
x = int(foo + 1 + global_var)
the code above will become::
global_var = 3
def bar(foo):
return int(foo + 1 + global_var)
def x():
foo = 3.1
x = bar(foo)
:param new_name: The expression under the cursor will be replaced with
a function with this name.
:param int until_line: The the selection range ends at this line, when
omitted, Jedi will be clever and try to define the range itself.
:param int until_column: The the selection range ends at this column, when
omitted, Jedi will be clever and try to define the range itself.
:raises: :exc:`.RefactoringError`
:rtype: :class:`.Refactoring`
"""
if until_line is None and until_column is None:
until_pos = None
else:
if until_line is None:
until_line = line
if until_column is None:
until_column = len(self._code_lines[until_line - 1])
until_pos = until_line, until_column
return extract_function(
self._inference_state, self.path, self._get_module_context(),
new_name, (line, column), until_pos
)
def inline(self, line=None, column=None):
"""
Inlines a variable under the cursor. This is basically the opposite of
extracting a variable. For example with the cursor on bar::
foo = 3.1
bar = foo + 1
x = int(bar)
the code above will become::
foo = 3.1
x = int(foo + 1)
:raises: :exc:`.RefactoringError`
:rtype: :class:`.Refactoring`
"""
names = [d._name for d in self.get_references(line, column, include_builtins=True)]
return refactoring.inline(self._inference_state, names)
class Interpreter(Script): class Interpreter(Script):
""" """
Jedi API for Python REPLs. Jedi's API for Python REPLs.
In addition to completion of simple attribute access, Jedi Implements all of the methods that are present in :class:`.Script` as well.
supports code completion based on static code analysis.
Jedi can complete attributes of object which is not initialized In addition to completions that normal REPL completion does like
yet. ``str.upper``, Jedi also supports code completion based on static code
analysis. For example Jedi will complete ``str().upper``.
>>> from os.path import join >>> from os.path import join
>>> namespace = locals() >>> namespace = locals()
>>> script = Interpreter('join("").up', [namespace]) >>> script = Interpreter('join("").up', [namespace])
>>> print(script.complete()[0].name) >>> print(script.complete()[0].name)
upper upper
All keyword arguments are same as the arguments for :class:`.Script`.
:param str code: Code to parse.
:type namespaces: typing.List[dict]
:param namespaces: A list of namespace dictionaries such as the one
returned by :func:`globals` and :func:`locals`.
""" """
_allow_descriptor_getattr_default = True _allow_descriptor_getattr_default = True
def __init__(self, source, namespaces, **kwds): def __init__(self, code, namespaces, **kwds):
"""
Parse `source` and mixin interpreted Python objects from `namespaces`.
:type source: str
:arg source: Code to parse.
:type namespaces: list of dict
:arg namespaces: a list of namespace dictionaries such as the one
returned by :func:`locals`.
Other optional arguments are same as the ones for :class:`Script`.
If `line` and `column` are None, they are assumed be at the end of
`source`.
"""
try: try:
namespaces = [dict(n) for n in namespaces] namespaces = [dict(n) for n in namespaces]
except Exception: except Exception:
@@ -558,8 +722,8 @@ class Interpreter(Script):
if not isinstance(environment, InterpreterEnvironment): if not isinstance(environment, InterpreterEnvironment):
raise TypeError("The environment needs to be an InterpreterEnvironment subclass.") raise TypeError("The environment needs to be an InterpreterEnvironment subclass.")
super(Interpreter, self).__init__(source, environment=environment, super().__init__(code, environment=environment,
_project=Project(os.getcwd()), **kwds) project=Project(Path.cwd()), **kwds)
self.namespaces = namespaces self.namespaces = namespaces
self._inference_state.allow_descriptor_getattr = self._allow_descriptor_getattr_default self._inference_state.allow_descriptor_getattr = self._allow_descriptor_getattr_default
@@ -567,7 +731,7 @@ class Interpreter(Script):
def _get_module_context(self): def _get_module_context(self):
tree_module_value = ModuleValue( tree_module_value = ModuleValue(
self._inference_state, self._module_node, self._inference_state, self._module_node,
file_io=KnownContentFileIO(self.path, self._code), file_io=KnownContentFileIO(str(self.path), self._code),
string_names=('__main__',), string_names=('__main__',),
code_lines=self._code_lines, code_lines=self._code_lines,
) )
@@ -577,31 +741,17 @@ class Interpreter(Script):
) )
def names(source=None, path=None, encoding='utf-8', all_scopes=False,
definitions=True, references=False, environment=None):
warnings.warn(
"Deprecated since version 0.16.0. Use Script(...).get_names instead.",
DeprecationWarning,
stacklevel=2
)
return Script(source, path=path, encoding=encoding).get_names(
all_scopes=all_scopes,
definitions=definitions,
references=references,
)
def preload_module(*modules): def preload_module(*modules):
""" """
Preloading modules tells Jedi to load a module now, instead of lazy parsing Preloading modules tells Jedi to load a module now, instead of lazy parsing
of modules. Usful for IDEs, to control which modules to load on startup. of modules. This can be useful for IDEs, to control which modules to load
on startup.
:param modules: different module names, list of string. :param modules: different module names, list of string.
""" """
for m in modules: for m in modules:
s = "import %s as x; x." % m s = "import %s as x; x." % m
Script(s, path=None).complete(1, len(s)) Script(s).complete(1, len(s))
def set_debug_function(func_cb=debug.print_to_stdout, warnings=True, def set_debug_function(func_cb=debug.print_to_stdout, warnings=True,
@@ -611,7 +761,7 @@ def set_debug_function(func_cb=debug.print_to_stdout, warnings=True,
If you don't specify any arguments, debug messages will be printed to stdout. If you don't specify any arguments, debug messages will be printed to stdout.
:param func_cb: The callback function for debug messages, with n params. :param func_cb: The callback function for debug messages.
""" """
debug.debug_function = func_cb debug.debug_function = func_cb
debug.enable_warning = warnings debug.enable_warning = warnings
+255 -167
View File
@@ -1,21 +1,31 @@
""" """
The :mod:`jedi.api.classes` module contains the return classes of the API. There are a couple of classes documented in here:
These classes are the much bigger part of the whole API, because they contain
the interesting information about completion and goto operations. - :class:`.BaseName` as an abstact base class for almost everything.
- :class:`.Name` used in a lot of places
- :class:`.Completion` for completions
- :class:`.BaseSignature` as a base class for signatures
- :class:`.Signature` for :meth:`.Script.get_signatures` only
- :class:`.ParamName` used for parameters of signatures
- :class:`.Refactoring` for refactorings
- :class:`.SyntaxError` for :meth:`.Script.get_syntax_errors` only
These classes are the much biggest part of the API, because they contain
the interesting information about all operations.
""" """
import re import re
import sys from pathlib import Path
import warnings from typing import Optional
from parso.python.tree import search_ancestor from parso.tree import search_ancestor
from jedi import settings from jedi import settings
from jedi import debug from jedi import debug
from jedi.inference.utils import unite from jedi.inference.utils import unite
from jedi.cache import memoize_method from jedi.cache import memoize_method
from jedi.inference import imports from jedi.inference.compiled.mixed import MixedName
from jedi.inference.imports import ImportName from jedi.inference.names import ImportName, SubModuleName
from jedi.inference.gradual.typeshed import StubModuleValue from jedi.inference.gradual.stub_value import StubModuleValue
from jedi.inference.gradual.conversion import convert_names, convert_values from jedi.inference.gradual.conversion import convert_names, convert_values
from jedi.inference.base_value import ValueSet from jedi.inference.base_value import ValueSet
from jedi.api.keywords import KeywordName from jedi.api.keywords import KeywordName
@@ -32,18 +42,21 @@ def defined_names(inference_state, context):
List sub-definitions (e.g., methods in class). List sub-definitions (e.g., methods in class).
:type scope: Scope :type scope: Scope
:rtype: list of Definition :rtype: list of Name
""" """
filter = next(context.get_filters()) filter = next(context.get_filters())
names = [name for name in filter.values()] names = [name for name in filter.values()]
return [Definition(inference_state, n) for n in _sort_names_by_start_pos(names)] return [Name(inference_state, n) for n in _sort_names_by_start_pos(names)]
def _values_to_definitions(values): def _values_to_definitions(values):
return [Definition(c.inference_state, c.name) for c in values] return [Name(c.inference_state, c.name) for c in values]
class BaseDefinition(object): class BaseName:
"""
The base class for all definitions, completions and signatures.
"""
_mapping = { _mapping = {
'posixpath': 'os.path', 'posixpath': 'os.path',
'riscospath': 'os.path', 'riscospath': 'os.path',
@@ -57,7 +70,6 @@ class BaseDefinition(object):
'_collections': 'collections', '_collections': 'collections',
'_socket': 'socket', '_socket': 'socket',
'_sqlite3': 'sqlite3', '_sqlite3': 'sqlite3',
'__builtin__': 'builtins',
} }
_tuple_mapping = dict((tuple(k.split('.')), v) for (k, v) in { _tuple_mapping = dict((tuple(k.split('.')), v) for (k, v) in {
@@ -80,13 +92,17 @@ class BaseDefinition(object):
return self._name.get_root_context() return self._name.get_root_context()
@property @property
def module_path(self): def module_path(self) -> Optional[Path]:
"""Shows the file path of a module. e.g. ``/usr/lib/python2.7/os.py``""" """
Shows the file path of a module. e.g. ``/usr/lib/python3.9/os.py``
"""
module = self._get_module_context() module = self._get_module_context()
if module.is_stub() or not module.is_compiled(): if module.is_stub() or not module.is_compiled():
# Compiled modules should not return a module path even if they # Compiled modules should not return a module path even if they
# have one. # have one.
return self._get_module_context().py__file__() path: Optional[Path] = self._get_module_context().py__file__()
if path is not None:
return path
return None return None
@@ -111,7 +127,6 @@ class BaseDefinition(object):
to Jedi, :meth:`jedi.Script.infer` should return a list of to Jedi, :meth:`jedi.Script.infer` should return a list of
definition for ``sys``, ``f``, ``C`` and ``x``. definition for ``sys``, ``f``, ``C`` and ``x``.
>>> from jedi._compatibility import no_unicode_pprint
>>> from jedi import Script >>> from jedi import Script
>>> source = ''' >>> source = '''
... import keyword ... import keyword
@@ -137,15 +152,15 @@ class BaseDefinition(object):
so that it is easy to relate the result to the source code. so that it is easy to relate the result to the source code.
>>> defs = sorted(defs, key=lambda d: d.line) >>> defs = sorted(defs, key=lambda d: d.line)
>>> no_unicode_pprint(defs) # doctest: +NORMALIZE_WHITESPACE >>> print(defs) # doctest: +NORMALIZE_WHITESPACE
[<Definition full_name='keyword', description='module keyword'>, [<Name full_name='keyword', description='module keyword'>,
<Definition full_name='__main__.C', description='class C'>, <Name full_name='__main__.C', description='class C'>,
<Definition full_name='__main__.D', description='instance D'>, <Name full_name='__main__.D', description='instance D'>,
<Definition full_name='__main__.f', description='def f'>] <Name full_name='__main__.f', description='def f'>]
Finally, here is what you can get from :attr:`type`: Finally, here is what you can get from :attr:`type`:
>>> defs = [str(d.type) for d in defs] # It's unicode and in Py2 has u before it. >>> defs = [d.type for d in defs]
>>> defs[0] >>> defs[0]
'module' 'module'
>>> defs[1] >>> defs[1]
@@ -155,8 +170,8 @@ class BaseDefinition(object):
>>> defs[3] >>> defs[3]
'function' 'function'
Valid values for are ``module``, ``class``, ``instance``, ``function``, Valid values for type are ``module``, ``class``, ``instance``, ``function``,
``param``, ``path`` and ``keyword``. ``param``, ``path``, ``keyword``, ``property`` and ``statement``.
""" """
tree_name = self._name.tree_name tree_name = self._name.tree_name
@@ -168,7 +183,7 @@ class BaseDefinition(object):
tree_name.is_definition(): tree_name.is_definition():
resolve = True resolve = True
if isinstance(self._name, imports.SubModuleName) or resolve: if isinstance(self._name, SubModuleName) or resolve:
for value in self._name.infer(): for value in self._name.infer():
return value.api_type return value.api_type
return self._name.api_type return self._name.api_type
@@ -176,7 +191,8 @@ class BaseDefinition(object):
@property @property
def module_name(self): def module_name(self):
""" """
The module name. The module name, a bit similar to what ``__name__`` is in a random
Python module.
>>> from jedi import Script >>> from jedi import Script
>>> source = 'import json' >>> source = 'import json'
@@ -188,7 +204,9 @@ class BaseDefinition(object):
return self._get_module_context().py__name__() return self._get_module_context().py__name__()
def in_builtin_module(self): def in_builtin_module(self):
"""Whether this is a builtin module.""" """
Returns True, if this is a builtin module.
"""
value = self._get_module_context().get_value() value = self._get_module_context().get_value()
if isinstance(value, StubModuleValue): if isinstance(value, StubModuleValue):
return any(v.is_compiled() for v in value.non_stub_value_set) return any(v.is_compiled() for v in value.non_stub_value_set)
@@ -210,6 +228,39 @@ class BaseDefinition(object):
return None return None
return start_pos[1] return start_pos[1]
def get_definition_start_position(self):
"""
The (row, column) of the start of the definition range. Rows start with
1, columns start with 0.
:rtype: Optional[Tuple[int, int]]
"""
if self._name.tree_name is None:
return None
definition = self._name.tree_name.get_definition()
if definition is None:
return self._name.start_pos
return definition.start_pos
def get_definition_end_position(self):
"""
The (row, column) of the end of the definition range. Rows start with
1, columns start with 0.
:rtype: Optional[Tuple[int, int]]
"""
if self._name.tree_name is None:
return None
definition = self._name.tree_name.get_definition()
if definition is None:
return self._name.tree_name.end_pos
if self.type in ("function", "class"):
last_leaf = definition.get_last_leaf()
if last_leaf.type == "newline":
return last_leaf.get_previous_leaf().end_pos
return last_leaf.end_pos
return definition.end_pos
def docstring(self, raw=False, fast=True): def docstring(self, raw=False, fast=True):
r""" r"""
Return a document string for this completion object. Return a document string for this completion object.
@@ -229,8 +280,8 @@ class BaseDefinition(object):
Document for function f. Document for function f.
Notice that useful extra information is added to the actual Notice that useful extra information is added to the actual
docstring. For function, it is signature. If you need docstring, e.g. function signatures are prepended to their docstrings.
actual docstring, use ``raw=True`` instead. If you need the actual docstring, use ``raw=True`` instead.
>>> print(script.infer(1, len('def f'))[0].docstring(raw=True)) >>> print(script.infer(1, len('def f'))[0].docstring(raw=True))
Document for function f. Document for function f.
@@ -265,12 +316,11 @@ class BaseDefinition(object):
@property @property
def description(self): def description(self):
""" """
A description of the :class:`.Definition` object, which is heavily used A description of the :class:`.Name` object, which is heavily used
in testing. e.g. for ``isinstance`` it returns ``def isinstance``. in testing. e.g. for ``isinstance`` it returns ``def isinstance``.
Example: Example:
>>> from jedi._compatibility import no_unicode_pprint
>>> from jedi import Script >>> from jedi import Script
>>> source = ''' >>> source = '''
... def f(): ... def f():
@@ -283,10 +333,10 @@ class BaseDefinition(object):
>>> script = Script(source) # line is maximum by default >>> script = Script(source) # line is maximum by default
>>> defs = script.infer(column=3) >>> defs = script.infer(column=3)
>>> defs = sorted(defs, key=lambda d: d.line) >>> defs = sorted(defs, key=lambda d: d.line)
>>> no_unicode_pprint(defs) # doctest: +NORMALIZE_WHITESPACE >>> print(defs) # doctest: +NORMALIZE_WHITESPACE
[<Definition full_name='__main__.f', description='def f'>, [<Name full_name='__main__.f', description='def f'>,
<Definition full_name='__main__.C', description='class C'>] <Name full_name='__main__.C', description='class C'>]
>>> str(defs[0].description) # strip literals in python2 >>> str(defs[0].description)
'def f' 'def f'
>>> str(defs[1].description) >>> str(defs[1].description)
'class C' 'class C'
@@ -341,7 +391,7 @@ class BaseDefinition(object):
names = self._name.get_qualified_names(include_module_names=True) names = self._name.get_qualified_names(include_module_names=True)
if names is None: if names is None:
return names return None
names = list(names) names = list(names)
try: try:
@@ -352,26 +402,40 @@ class BaseDefinition(object):
return '.'.join(names) return '.'.join(names)
def is_stub(self): def is_stub(self):
"""
Returns True if the current name is defined in a stub file.
"""
if not self._name.is_value_name: if not self._name.is_value_name:
return False return False
return self._name.get_root_context().is_stub() return self._name.get_root_context().is_stub()
def goto(self, **kwargs): def is_side_effect(self):
with debug.increase_indent_cm('goto for %s' % self._name): """
return self._goto(**kwargs) Checks if a name is defined as ``self.foo = 3``. In case of self, this
function would return False, for foo it would return True.
"""
tree_name = self._name.tree_name
if tree_name is None:
return False
return tree_name.is_definition() and tree_name.parent.type == 'trailer'
def goto_assignments(self, **kwargs): # Python 2... @debug.increase_indent_cm('goto on name')
warnings.warn( def goto(self, *, follow_imports=False, follow_builtin_imports=False,
"Deprecated since version 0.16.0. Use .goto.",
DeprecationWarning,
stacklevel=2
)
return self.goto(**kwargs)
def _goto(self, follow_imports=False, follow_builtin_imports=False,
only_stubs=False, prefer_stubs=False): only_stubs=False, prefer_stubs=False):
"""
Like :meth:`.Script.goto` (also supports the same params), but does it
for the current name. This is typically useful if you are using
something like :meth:`.Script.get_names()`.
:param follow_imports: The goto call will follow imports.
:param follow_builtin_imports: If follow_imports is True will try to
look up names in builtins (i.e. compiled or extension modules).
:param only_stubs: Only return stubs for this goto call.
:param prefer_stubs: Prefer stubs to Python objects for this goto call.
:rtype: list of :class:`Name`
"""
if not self._name.is_value_name: if not self._name.is_value_name:
return [] return []
@@ -383,14 +447,27 @@ class BaseDefinition(object):
only_stubs=only_stubs, only_stubs=only_stubs,
prefer_stubs=prefer_stubs, prefer_stubs=prefer_stubs,
) )
return [self if n == self._name else Definition(self._inference_state, n) return [self if n == self._name else Name(self._inference_state, n)
for n in names] for n in names]
def infer(self, **kwargs): # Python 2... @debug.increase_indent_cm('infer on name')
with debug.increase_indent_cm('infer for %s' % self._name): def infer(self, *, only_stubs=False, prefer_stubs=False):
return self._infer(**kwargs) """
Like :meth:`.Script.infer`, it can be useful to understand which type
the current name has.
def _infer(self, only_stubs=False, prefer_stubs=False): Return the actual definitions. I strongly recommend not using it for
your completions, because it might slow down |jedi|. If you want to
read only a few objects (<=20), it might be useful, especially to get
the original docstrings. The basic problem of this function is that it
follows all results. This means with 1000 completions (e.g. numpy),
it's just very, very slow.
:param only_stubs: Only return stubs for this goto call.
:param prefer_stubs: Prefer stubs to Python objects for this type
inference call.
:rtype: list of :class:`Name`
"""
assert not (only_stubs and prefer_stubs) assert not (only_stubs and prefer_stubs)
if not self._name.is_value_name: if not self._name.is_value_name:
@@ -406,38 +483,15 @@ class BaseDefinition(object):
prefer_stubs=prefer_stubs, prefer_stubs=prefer_stubs,
) )
resulting_names = [c.name for c in values] resulting_names = [c.name for c in values]
return [self if n == self._name else Definition(self._inference_state, n) return [self if n == self._name else Name(self._inference_state, n)
for n in resulting_names] for n in resulting_names]
@property
@memoize_method
def params(self):
"""
Deprecated! Will raise a warning soon. Use get_signatures()[...].params.
Raises an ``AttributeError`` if the definition is not callable.
Otherwise returns a list of `Definition` that represents the params.
"""
warnings.warn(
"Deprecated since version 0.16.0. Use get_signatures()[...].params",
DeprecationWarning,
stacklevel=2
)
# Only return the first one. There might be multiple one, especially
# with overloading.
for signature in self._get_signatures():
return [
Definition(self._inference_state, n)
for n in signature.get_param_names(resolve_stars=True)
]
if self.type == 'function' or self.type == 'class':
# Fallback, if no signatures were defined (which is probably by
# itself a bug).
return []
raise AttributeError('There are no params defined on this.')
def parent(self): def parent(self):
"""
Returns the parent scope of this identifier.
:rtype: Name
"""
if not self._name.is_value_name: if not self._name.is_value_name:
return None return None
@@ -463,7 +517,7 @@ class BaseDefinition(object):
# Happens for comprehension contexts # Happens for comprehension contexts
context = context.parent_context context = context.parent_context
return Definition(self._inference_state, context.name) return Name(self._inference_state, context.name)
def __repr__(self): def __repr__(self):
return "<%s %sname=%r, description=%r>" % ( return "<%s %sname=%r, description=%r>" % (
@@ -496,32 +550,64 @@ class BaseDefinition(object):
return ''.join(lines[start_index:index + after + 1]) return ''.join(lines[start_index:index + after + 1])
def _get_signatures(self, for_docstring=False): def _get_signatures(self, for_docstring=False):
if self._name.api_type == 'property':
return []
if for_docstring and self._name.api_type == 'statement' and not self.is_stub(): if for_docstring and self._name.api_type == 'statement' and not self.is_stub():
# For docstrings we don't resolve signatures if they are simple # For docstrings we don't resolve signatures if they are simple
# statements and not stubs. This is a speed optimization. # statements and not stubs. This is a speed optimization.
return [] return []
if isinstance(self._name, MixedName):
# While this would eventually happen anyway, it's basically just a
# shortcut to not infer anything tree related, because it's really
# not necessary.
return self._name.infer_compiled_value().get_signatures()
names = convert_names([self._name], prefer_stubs=True) names = convert_names([self._name], prefer_stubs=True)
return [sig for name in names for sig in name.infer().get_signatures()] return [sig for name in names for sig in name.infer().get_signatures()]
def get_signatures(self): def get_signatures(self):
"""
Returns all potential signatures for a function or a class. Multiple
signatures are typical if you use Python stubs with ``@overload``.
:rtype: list of :class:`BaseSignature`
"""
return [ return [
BaseSignature(self._inference_state, s) BaseSignature(self._inference_state, s)
for s in self._get_signatures() for s in self._get_signatures()
] ]
def execute(self): def execute(self):
"""
Uses type inference to "execute" this identifier and returns the
executed objects.
:rtype: list of :class:`Name`
"""
return _values_to_definitions(self._name.infer().execute_with_values()) return _values_to_definitions(self._name.infer().execute_with_values())
def get_type_hint(self):
class Completion(BaseDefinition):
""" """
`Completion` objects are returned from :meth:`api.Script.complete`. They Returns type hints like ``Iterable[int]`` or ``Union[int, str]``.
This method might be quite slow, especially for functions. The problem
is finding executions for those functions to return something like
``Callable[[int, str], str]``.
:rtype: str
"""
return self._name.infer().get_type_hint()
class Completion(BaseName):
"""
``Completion`` objects are returned from :meth:`.Script.complete`. They
provide additional information about a completion. provide additional information about a completion.
""" """
def __init__(self, inference_state, name, stack, like_name_length, def __init__(self, inference_state, name, stack, like_name_length,
is_fuzzy, cached_name=None): is_fuzzy, cached_name=None):
super(Completion, self).__init__(inference_state, name) super().__init__(inference_state, name)
self._like_name_length = like_name_length self._like_name_length = like_name_length
self._stack = stack self._stack = stack
@@ -554,15 +640,15 @@ class Completion(BaseDefinition):
isinstan# <-- Cursor is here isinstan# <-- Cursor is here
would return the string 'ce'. It also adds additional stuff, depending would return the string 'ce'. It also adds additional stuff, depending
on your `settings.py`. on your ``settings.py``.
Assuming the following function definition:: Assuming the following function definition::
def foo(param=0): def foo(param=0):
pass pass
completing ``foo(par`` would give a ``Completion`` which `complete` completing ``foo(par`` would give a ``Completion`` which ``complete``
would be `am=` would be ``am=``.
""" """
if self._is_fuzzy: if self._is_fuzzy:
return None return None
@@ -571,7 +657,7 @@ class Completion(BaseDefinition):
@property @property
def name_with_symbols(self): def name_with_symbols(self):
""" """
Similar to :attr:`name`, but like :attr:`name` returns also the Similar to :attr:`.name`, but like :attr:`.name` returns also the
symbols, for example assuming the following function definition:: symbols, for example assuming the following function definition::
def foo(param=0): def foo(param=0):
@@ -584,12 +670,15 @@ class Completion(BaseDefinition):
return self._complete(False) return self._complete(False)
def docstring(self, raw=False, fast=True): def docstring(self, raw=False, fast=True):
"""
Documented under :meth:`BaseName.docstring`.
"""
if self._like_name_length >= 3: if self._like_name_length >= 3:
# In this case we can just resolve the like name, because we # In this case we can just resolve the like name, because we
# wouldn't load like > 100 Python modules anymore. # wouldn't load like > 100 Python modules anymore.
fast = False fast = False
return super(Completion, self).docstring(raw=raw, fast=fast) return super().docstring(raw=raw, fast=fast)
def _get_docstring(self): def _get_docstring(self):
if self._cached_name is not None: if self._cached_name is not None:
@@ -598,7 +687,7 @@ class Completion(BaseDefinition):
self._name.get_public_name(), self._name.get_public_name(),
lambda: self._get_cache() lambda: self._get_cache()
) )
return super(Completion, self)._get_docstring() return super()._get_docstring()
def _get_docstring_signature(self): def _get_docstring_signature(self):
if self._cached_name is not None: if self._cached_name is not None:
@@ -607,18 +696,20 @@ class Completion(BaseDefinition):
self._name.get_public_name(), self._name.get_public_name(),
lambda: self._get_cache() lambda: self._get_cache()
) )
return super(Completion, self)._get_docstring_signature() return super()._get_docstring_signature()
def _get_cache(self): def _get_cache(self):
typ = super(Completion, self).type
return ( return (
typ, super().type,
super(Completion, self)._get_docstring_signature(), super()._get_docstring_signature(),
super(Completion, self)._get_docstring(), super()._get_docstring(),
) )
@property @property
def type(self): def type(self):
"""
Documented under :meth:`BaseName.type`.
"""
# Purely a speed optimization. # Purely a speed optimization.
if self._cached_name is not None: if self._cached_name is not None:
return completion_cache.get_type( return completion_cache.get_type(
@@ -627,59 +718,44 @@ class Completion(BaseDefinition):
lambda: self._get_cache() lambda: self._get_cache()
) )
return super(Completion, self).type return super().type
def get_completion_prefix_length(self):
"""
Returns the length of the prefix being completed.
For example, completing ``isinstance``::
isinstan# <-- Cursor is here
would return 8, because len('isinstan') == 8.
Assuming the following function definition::
def foo(param=0):
pass
completing ``foo(par`` would return 3.
"""
return self._like_name_length
def __repr__(self): def __repr__(self):
return '<%s: %s>' % (type(self).__name__, self._name.get_public_name()) return '<%s: %s>' % (type(self).__name__, self._name.get_public_name())
@memoize_method
def follow_definition(self):
"""
Deprecated!
Return the original definitions. I strongly recommend not using it for class Name(BaseName):
your completions, because it might slow down |jedi|. If you want to
read only a few objects (<=20), it might be useful, especially to get
the original docstrings. The basic problem of this function is that it
follows all results. This means with 1000 completions (e.g. numpy),
it's just PITA-slow.
""" """
warnings.warn( *Name* objects are returned from many different APIs including
"Deprecated since version 0.14.0. Use .infer.", :meth:`.Script.goto` or :meth:`.Script.infer`.
DeprecationWarning,
stacklevel=2
)
return self.infer()
class Definition(BaseDefinition):
"""
*Definition* objects are returned from :meth:`api.Script.goto`
or :meth:`api.Script.infer`.
""" """
def __init__(self, inference_state, definition): def __init__(self, inference_state, definition):
super(Definition, self).__init__(inference_state, definition) super().__init__(inference_state, definition)
@property
def desc_with_module(self):
"""
In addition to the definition, also return the module.
.. warning:: Don't use this function yet, its behaviour may change. If
you really need it, talk to me.
.. todo:: Add full path. This function is should return a
`module.class.function` path.
"""
position = '' if self.in_builtin_module else '@%s' % self.line
return "%s:%s%s" % (self.module_name, self.description, position)
@memoize_method @memoize_method
def defined_names(self): def defined_names(self):
""" """
List sub-definitions (e.g., methods in class). List sub-definitions (e.g., methods in class).
:rtype: list of Definition :rtype: list of :class:`Name`
""" """
defs = self._name.infer() defs = self._name.infer()
return sorted( return sorted(
@@ -710,45 +786,53 @@ class Definition(BaseDefinition):
return hash((self._name.start_pos, self.module_path, self.name, self._inference_state)) return hash((self._name.start_pos, self.module_path, self.name, self._inference_state))
class BaseSignature(Definition): class BaseSignature(Name):
""" """
`BaseSignature` objects is the return value of `Script.function_definition`. These signatures are returned by :meth:`BaseName.get_signatures`
It knows what functions you are currently in. e.g. `isinstance(` would calls.
return the `isinstance` function. without `(` it would return nothing.
""" """
def __init__(self, inference_state, signature): def __init__(self, inference_state, signature):
super(BaseSignature, self).__init__(inference_state, signature.name) super().__init__(inference_state, signature.name)
self._signature = signature self._signature = signature
@property @property
def params(self): def params(self):
""" """
:return list of ParamDefinition: Returns definitions for all parameters that a signature defines.
This includes stuff like ``*args`` and ``**kwargs``.
:rtype: list of :class:`.ParamName`
""" """
return [ParamDefinition(self._inference_state, n) return [ParamName(self._inference_state, n)
for n in self._signature.get_param_names(resolve_stars=True)] for n in self._signature.get_param_names(resolve_stars=True)]
def to_string(self): def to_string(self):
"""
Returns a text representation of the signature. This could for example
look like ``foo(bar, baz: int, **kwargs)``.
:rtype: str
"""
return self._signature.to_string() return self._signature.to_string()
class Signature(BaseSignature): class Signature(BaseSignature):
""" """
`Signature` objects is the return value of `Script.get_signatures`. A full signature object is the return value of
It knows what functions you are currently in. e.g. `isinstance(` would :meth:`.Script.get_signatures`.
return the `isinstance` function with its params. Without `(` it would
return nothing.
""" """
def __init__(self, inference_state, signature, call_details): def __init__(self, inference_state, signature, call_details):
super(Signature, self).__init__(inference_state, signature) super().__init__(inference_state, signature)
self._call_details = call_details self._call_details = call_details
self._signature = signature self._signature = signature
@property @property
def index(self): def index(self):
""" """
The Param index of the current call. Returns the param index of the current cursor position.
Returns None if the index cannot be found in the curent call. Returns None if the index cannot be found in the curent call.
:rtype: int
""" """
return self._call_details.calculate_index( return self._call_details.calculate_index(
self._signature.get_param_names(resolve_stars=True) self._signature.get_param_names(resolve_stars=True)
@@ -757,8 +841,10 @@ class Signature(BaseSignature):
@property @property
def bracket_start(self): def bracket_start(self):
""" """
The line/column of the bracket that is responsible for the last Returns a line/column tuple of the bracket that is responsible for the
function call. last function call. The first line is 1 and the first column 0.
:rtype: int, int
""" """
return self._call_details.bracket_leaf.start_pos return self._call_details.bracket_leaf.start_pos
@@ -770,35 +856,37 @@ class Signature(BaseSignature):
) )
class ParamDefinition(Definition): class ParamName(Name):
def infer_default(self): def infer_default(self):
""" """
:return list of Definition: Returns default values like the ``1`` of ``def foo(x=1):``.
:rtype: list of :class:`.Name`
""" """
return _values_to_definitions(self._name.infer_default()) return _values_to_definitions(self._name.infer_default())
def infer_annotation(self, **kwargs): def infer_annotation(self, **kwargs):
""" """
:return list of Definition: :param execute_annotation: Default True; If False, values are not
executed and classes are returned instead of instances.
:param execute_annotation: If False, the values are not executed and :rtype: list of :class:`.Name`
you get classes instead of instances.
""" """
return _values_to_definitions(self._name.infer_annotation(ignore_stars=True, **kwargs)) return _values_to_definitions(self._name.infer_annotation(ignore_stars=True, **kwargs))
def to_string(self): def to_string(self):
"""
Returns a simple representation of a param, like
``f: Callable[..., Any]``.
:rtype: str
"""
return self._name.to_string() return self._name.to_string()
@property @property
def kind(self): def kind(self):
""" """
Returns an enum instance. Returns the same values as the builtin Returns an enum instance of :mod:`inspect`'s ``Parameter`` enum.
:py:attr:`inspect.Parameter.kind`.
No support for Python < 3.4 anymore. :rtype: :py:attr:`inspect.Parameter.kind`
""" """
if sys.version_info < (3, 5):
raise NotImplementedError(
'Python 2 is end-of-life, the new feature is not available for it'
)
return self._name.get_kind() return self._name.get_kind()
+190 -102
View File
@@ -1,12 +1,12 @@
import re import re
from textwrap import dedent from textwrap import dedent
from inspect import Parameter
from parso.python.token import PythonTokenTypes from parso.python.token import PythonTokenTypes
from parso.python import tree from parso.python import tree
from parso.tree import search_ancestor, Leaf from parso.tree import search_ancestor, Leaf
from parso import split_lines from parso import split_lines
from jedi._compatibility import Parameter
from jedi import debug from jedi import debug
from jedi import settings from jedi import settings
from jedi.api import classes from jedi.api import classes
@@ -19,8 +19,8 @@ from jedi.inference.base_value import ValueSet
from jedi.inference.helpers import infer_call_of_leaf, parse_dotted_names from jedi.inference.helpers import infer_call_of_leaf, parse_dotted_names
from jedi.inference.context import get_global_filters from jedi.inference.context import get_global_filters
from jedi.inference.value import TreeInstance, ModuleValue from jedi.inference.value import TreeInstance, ModuleValue
from jedi.inference.names import ParamNameWrapper from jedi.inference.names import ParamNameWrapper, SubModuleName
from jedi.inference.gradual.conversion import convert_values from jedi.inference.gradual.conversion import convert_values, convert_names
from jedi.parser_utils import cut_value_at_position from jedi.parser_utils import cut_value_at_position
from jedi.plugins import plugin_manager from jedi.plugins import plugin_manager
@@ -30,16 +30,40 @@ class ParamNameWithEquals(ParamNameWrapper):
return self.string_name + '=' return self.string_name + '='
def get_signature_param_names(signatures): def _get_signature_param_names(signatures, positional_count, used_kwargs):
# add named params # Add named params
for call_sig in signatures: for call_sig in signatures:
for p in call_sig.params: for i, p in enumerate(call_sig.params):
# Allow protected access, because it's a public API. kind = p.kind
if p._name.get_kind() in (Parameter.POSITIONAL_OR_KEYWORD, if i < positional_count and kind == Parameter.POSITIONAL_OR_KEYWORD:
Parameter.KEYWORD_ONLY): continue
if kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY) \
and p.name not in used_kwargs:
yield ParamNameWithEquals(p._name) yield ParamNameWithEquals(p._name)
def _must_be_kwarg(signatures, positional_count, used_kwargs):
if used_kwargs:
return True
must_be_kwarg = True
for signature in signatures:
for i, p in enumerate(signature.params):
kind = p.kind
if kind is Parameter.VAR_POSITIONAL:
# In case there were not already kwargs, the next param can
# always be a normal argument.
return False
if i >= positional_count and kind in (Parameter.POSITIONAL_OR_KEYWORD,
Parameter.POSITIONAL_ONLY):
must_be_kwarg = False
break
if not must_be_kwarg:
break
return must_be_kwarg
def filter_names(inference_state, completion_names, stack, like_name, fuzzy, cached_name): def filter_names(inference_state, completion_names, stack, like_name, fuzzy, cached_name):
comp_dct = set() comp_dct = set()
if settings.case_insensitive_completion: if settings.case_insensitive_completion:
@@ -48,11 +72,7 @@ def filter_names(inference_state, completion_names, stack, like_name, fuzzy, cac
string = name.string_name string = name.string_name
if settings.case_insensitive_completion: if settings.case_insensitive_completion:
string = string.lower() string = string.lower()
if fuzzy: if helpers.match(string, like_name, fuzzy=fuzzy):
match = helpers.fuzzy_match(string, like_name)
else:
match = helpers.start_match(string, like_name)
if match:
new = classes.Completion( new = classes.Completion(
inference_state, inference_state,
name, name,
@@ -135,7 +155,7 @@ class Completion:
if string is not None and not prefixed_completions: if string is not None and not prefixed_completions:
prefixed_completions = list(complete_file_name( prefixed_completions = list(complete_file_name(
self._inference_state, self._module_context, start_leaf, string, self._inference_state, self._module_context, start_leaf, quote, string,
self._like_name, self._signatures_callback, self._like_name, self._signatures_callback,
self._code_lines, self._original_position, self._code_lines, self._original_position,
self._fuzzy self._fuzzy
@@ -233,14 +253,8 @@ class Completion:
allowed_transitions.append('else') allowed_transitions.append('else')
completion_names = [] completion_names = []
current_line = self._code_lines[self._position[0] - 1][:self._position[1]]
completion_names += self._complete_keywords(
allowed_transitions,
only_values=not (not current_line or current_line[-1] in ' \t.;'
and current_line[-3:] != '...')
)
kwargs_only = False
if any(t in allowed_transitions for t in (PythonTokenTypes.NAME, if any(t in allowed_transitions for t in (PythonTokenTypes.NAME,
PythonTokenTypes.INDENT)): PythonTokenTypes.INDENT)):
# This means that we actually have to do type inference. # This means that we actually have to do type inference.
@@ -268,9 +282,6 @@ class Completion:
elif self._is_parameter_completion(): elif self._is_parameter_completion():
completion_names += self._complete_params(leaf) completion_names += self._complete_params(leaf)
else: else:
completion_names += self._complete_global_scope()
completion_names += self._complete_inherited(is_function=False)
# Apparently this looks like it's good enough to filter most cases # Apparently this looks like it's good enough to filter most cases
# so that signature completions don't randomly appear. # so that signature completions don't randomly appear.
# To understand why this works, three things are important: # To understand why this works, three things are important:
@@ -279,9 +290,33 @@ class Completion:
# with `(`. Other trailers could start with `.` or `[`. # with `(`. Other trailers could start with `.` or `[`.
# 3. Decorators are very primitive and have an optional `(` with # 3. Decorators are very primitive and have an optional `(` with
# optional arglist in them. # optional arglist in them.
if nodes[-1] in ['(', ','] and nonterminals[-1] in ('trailer', 'arglist', 'decorator'): if nodes[-1] in ['(', ','] \
and nonterminals[-1] in ('trailer', 'arglist', 'decorator'):
signatures = self._signatures_callback(*self._position) signatures = self._signatures_callback(*self._position)
completion_names += get_signature_param_names(signatures) if signatures:
call_details = signatures[0]._call_details
used_kwargs = list(call_details.iter_used_keyword_arguments())
positional_count = call_details.count_positional_arguments()
completion_names += _get_signature_param_names(
signatures,
positional_count,
used_kwargs,
)
kwargs_only = _must_be_kwarg(signatures, positional_count, used_kwargs)
if not kwargs_only:
completion_names += self._complete_global_scope()
completion_names += self._complete_inherited(is_function=False)
if not kwargs_only:
current_line = self._code_lines[self._position[0] - 1][:self._position[1]]
completion_names += self._complete_keywords(
allowed_transitions,
only_values=not (not current_line or current_line[-1] in ' \t.;'
and current_line[-3:] != '...')
)
return cached_name, completion_names return cached_name, completion_names
@@ -305,6 +340,7 @@ class Completion:
if stack_node.nonterminal == 'funcdef': if stack_node.nonterminal == 'funcdef':
context = get_user_context(self._module_context, self._position) context = get_user_context(self._module_context, self._position)
node = search_ancestor(leaf, 'error_node', 'funcdef') node = search_ancestor(leaf, 'error_node', 'funcdef')
if node is not None:
if node.type == 'error_node': if node.type == 'error_node':
n = node.children[0] n = node.children[0]
if n.type == 'decorators': if n.type == 'decorators':
@@ -361,80 +397,7 @@ class Completion:
def _complete_trailer_for_values(self, values): def _complete_trailer_for_values(self, values):
user_context = get_user_context(self._module_context, self._position) user_context = get_user_context(self._module_context, self._position)
completion_names = [] return complete_trailer(user_context, values)
for value in values:
for filter in value.get_filters(origin_scope=user_context.tree_node):
completion_names += filter.values()
if not value.is_stub() and isinstance(value, TreeInstance):
completion_names += self._complete_getattr(value)
python_values = convert_values(values)
for c in python_values:
if c not in values:
for filter in c.get_filters(origin_scope=user_context.tree_node):
completion_names += filter.values()
return completion_names
def _complete_getattr(self, instance):
"""
A heuristic to make completion for proxy objects work. This is not
intended to work in all cases. It works exactly in this case:
def __getattr__(self, name):
...
return getattr(any_object, name)
It is important that the return contains getattr directly, otherwise it
won't work anymore. It's really just a stupid heuristic. It will not
work if you write e.g. `return (getatr(o, name))`, because of the
additional parentheses. It will also not work if you move the getattr
to some other place that is not the return statement itself.
It is intentional that it doesn't work in all cases. Generally it's
really hard to do even this case (as you can see below). Most people
will write it like this anyway and the other ones, well they are just
out of luck I guess :) ~dave.
"""
names = (instance.get_function_slot_names(u'__getattr__')
or instance.get_function_slot_names(u'__getattribute__'))
functions = ValueSet.from_sets(
name.infer()
for name in names
)
for func in functions:
tree_node = func.tree_node
for return_stmt in tree_node.iter_return_stmts():
# Basically until the next comment we just try to find out if a
# return statement looks exactly like `return getattr(x, name)`.
if return_stmt.type != 'return_stmt':
continue
atom_expr = return_stmt.children[1]
if atom_expr.type != 'atom_expr':
continue
atom = atom_expr.children[0]
trailer = atom_expr.children[1]
if len(atom_expr.children) != 2 or atom.type != 'name' \
or atom.value != 'getattr':
continue
arglist = trailer.children[1]
if arglist.type != 'arglist' or len(arglist.children) < 3:
continue
context = func.as_context()
object_node = arglist.children[0]
# Make sure it's a param: foo in __getattr__(self, foo)
name_node = arglist.children[2]
name_list = context.goto(name_node, name_node.start_pos)
if not any(n.api_type == 'param' for n in name_list):
continue
# Now that we know that these are most probably completion
# objects, we just infer the object and return them as
# completions.
objects = context.infer_node(object_node)
return self._complete_trailer_for_values(objects)
return []
def _get_importer_names(self, names, level=0, only_modules=True): def _get_importer_names(self, names, level=0, only_modules=True):
names = [n.value for n in names] names = [n.value for n in names]
@@ -490,7 +453,7 @@ class Completion:
relevant_code_lines = list(iter_relevant_lines(code_lines)) relevant_code_lines = list(iter_relevant_lines(code_lines))
if relevant_code_lines[-1] is not None: if relevant_code_lines[-1] is not None:
# Some code lines might be None, therefore get rid of that. # Some code lines might be None, therefore get rid of that.
relevant_code_lines = [c or '\n' for c in relevant_code_lines] relevant_code_lines = ['\n' if c is None else c for c in relevant_code_lines]
return self._complete_code_lines(relevant_code_lines) return self._complete_code_lines(relevant_code_lines)
match = re.search(r'`([^`\s]+)', code_lines[-1]) match = re.search(r'`([^`\s]+)', code_lines[-1])
if match: if match:
@@ -534,6 +497,8 @@ def _extract_string_while_in_string(leaf, position):
if leaf.line == position[0]: if leaf.line == position[0]:
kwargs['endpos'] = position[1] - leaf.column kwargs['endpos'] = position[1] - leaf.column
match = _string_start.match(leaf.value, **kwargs) match = _string_start.match(leaf.value, **kwargs)
if not match:
return None, None, None
start = match.group(0) start = match.group(0)
if leaf.line == position[0] and position[1] < leaf.column + match.end(): if leaf.line == position[0] and position[1] < leaf.column + match.end():
return None, None, None return None, None, None
@@ -572,3 +537,126 @@ def _extract_string_while_in_string(leaf, position):
leaves.insert(0, leaf) leaves.insert(0, leaf)
leaf = leaf.get_previous_leaf() leaf = leaf.get_previous_leaf()
return None, None, None return None, None, None
def complete_trailer(user_context, values):
completion_names = []
for value in values:
for filter in value.get_filters(origin_scope=user_context.tree_node):
completion_names += filter.values()
if not value.is_stub() and isinstance(value, TreeInstance):
completion_names += _complete_getattr(user_context, value)
python_values = convert_values(values)
for c in python_values:
if c not in values:
for filter in c.get_filters(origin_scope=user_context.tree_node):
completion_names += filter.values()
return completion_names
def _complete_getattr(user_context, instance):
"""
A heuristic to make completion for proxy objects work. This is not
intended to work in all cases. It works exactly in this case:
def __getattr__(self, name):
...
return getattr(any_object, name)
It is important that the return contains getattr directly, otherwise it
won't work anymore. It's really just a stupid heuristic. It will not
work if you write e.g. `return (getatr(o, name))`, because of the
additional parentheses. It will also not work if you move the getattr
to some other place that is not the return statement itself.
It is intentional that it doesn't work in all cases. Generally it's
really hard to do even this case (as you can see below). Most people
will write it like this anyway and the other ones, well they are just
out of luck I guess :) ~dave.
"""
names = (instance.get_function_slot_names('__getattr__')
or instance.get_function_slot_names('__getattribute__'))
functions = ValueSet.from_sets(
name.infer()
for name in names
)
for func in functions:
tree_node = func.tree_node
if tree_node is None or tree_node.type != 'funcdef':
continue
for return_stmt in tree_node.iter_return_stmts():
# Basically until the next comment we just try to find out if a
# return statement looks exactly like `return getattr(x, name)`.
if return_stmt.type != 'return_stmt':
continue
atom_expr = return_stmt.children[1]
if atom_expr.type != 'atom_expr':
continue
atom = atom_expr.children[0]
trailer = atom_expr.children[1]
if len(atom_expr.children) != 2 or atom.type != 'name' \
or atom.value != 'getattr':
continue
arglist = trailer.children[1]
if arglist.type != 'arglist' or len(arglist.children) < 3:
continue
context = func.as_context()
object_node = arglist.children[0]
# Make sure it's a param: foo in __getattr__(self, foo)
name_node = arglist.children[2]
name_list = context.goto(name_node, name_node.start_pos)
if not any(n.api_type == 'param' for n in name_list):
continue
# Now that we know that these are most probably completion
# objects, we just infer the object and return them as
# completions.
objects = context.infer_node(object_node)
return complete_trailer(user_context, objects)
return []
def search_in_module(inference_state, module_context, names, wanted_names,
wanted_type, complete=False, fuzzy=False,
ignore_imports=False, convert=False):
for s in wanted_names[:-1]:
new_names = []
for n in names:
if s == n.string_name:
if n.tree_name is not None and n.api_type in ('module', 'namespace') \
and ignore_imports:
continue
new_names += complete_trailer(
module_context,
n.infer()
)
debug.dbg('dot lookup on search %s from %s', new_names, names[:10])
names = new_names
last_name = wanted_names[-1].lower()
for n in names:
string = n.string_name.lower()
if complete and helpers.match(string, last_name, fuzzy=fuzzy) \
or not complete and string == last_name:
if isinstance(n, SubModuleName):
names = [v.name for v in n.infer()]
else:
names = [n]
if convert:
names = convert_names(names)
for n2 in names:
if complete:
def_ = classes.Completion(
inference_state, n2,
stack=None,
like_name_length=len(last_name),
is_fuzzy=fuzzy,
)
else:
def_ = classes.Name(inference_state, n2)
if not wanted_type or wanted_type == def_.type:
yield def_
+10 -4
View File
@@ -1,7 +1,13 @@
_cache = {} from typing import Dict, Tuple, Callable
CacheValues = Tuple[str, str, str]
CacheValuesCallback = Callable[[], CacheValues]
def save_entry(module_name, name, cache): _cache: Dict[str, Dict[str, CacheValues]] = {}
def save_entry(module_name: str, name: str, cache: CacheValues) -> None:
try: try:
module_cache = _cache[module_name] module_cache = _cache[module_name]
except KeyError: except KeyError:
@@ -9,8 +15,8 @@ def save_entry(module_name, name, cache):
module_cache[name] = cache module_cache[name] = cache
def _create_get_from_cache(number): def _create_get_from_cache(number: int) -> Callable[[str, str, CacheValuesCallback], str]:
def _get_from_cache(module_name, name, get_cache_values): def _get_from_cache(module_name: str, name: str, get_cache_values: CacheValuesCallback) -> str:
try: try:
return _cache[module_name][name][number] return _cache[module_name][name][number]
except KeyError: except KeyError:
+35 -41
View File
@@ -7,8 +7,8 @@ import sys
import hashlib import hashlib
import filecmp import filecmp
from collections import namedtuple from collections import namedtuple
from shutil import which
from jedi._compatibility import highest_pickle_protocol, which
from jedi.cache import memoize_method, time_cache from jedi.cache import memoize_method, time_cache
from jedi.inference.compiled.subprocess import CompiledSubprocess, \ from jedi.inference.compiled.subprocess import CompiledSubprocess, \
InferenceStateSameProcess, InferenceStateSubprocess InferenceStateSameProcess, InferenceStateSubprocess
@@ -17,7 +17,7 @@ import parso
_VersionInfo = namedtuple('VersionInfo', 'major minor micro') _VersionInfo = namedtuple('VersionInfo', 'major minor micro')
_SUPPORTED_PYTHONS = ['3.8', '3.7', '3.6', '3.5', '3.4', '2.7'] _SUPPORTED_PYTHONS = ['3.9', '3.8', '3.7', '3.6']
_SAFE_PATHS = ['/usr/bin', '/usr/local/bin'] _SAFE_PATHS = ['/usr/bin', '/usr/local/bin']
_CONDA_VAR = 'CONDA_PREFIX' _CONDA_VAR = 'CONDA_PREFIX'
_CURRENT_VERSION = '%s.%s' % (sys.version_info.major, sys.version_info.minor) _CURRENT_VERSION = '%s.%s' % (sys.version_info.major, sys.version_info.minor)
@@ -30,7 +30,7 @@ class InvalidPythonEnvironment(Exception):
""" """
class _BaseEnvironment(object): class _BaseEnvironment:
@memoize_method @memoize_method
def get_grammar(self): def get_grammar(self):
version_string = '%s.%s' % (self.version_info.major, self.version_info.minor) version_string = '%s.%s' % (self.version_info.major, self.version_info.minor)
@@ -61,8 +61,9 @@ class Environment(_BaseEnvironment):
""" """
_subprocess = None _subprocess = None
def __init__(self, executable): def __init__(self, executable, env_vars=None):
self._start_executable = executable self._start_executable = executable
self._env_vars = env_vars
# Initialize the environment # Initialize the environment
self._get_subprocess() self._get_subprocess()
@@ -71,7 +72,8 @@ class Environment(_BaseEnvironment):
return self._subprocess return self._subprocess
try: try:
self._subprocess = CompiledSubprocess(self._start_executable) self._subprocess = CompiledSubprocess(self._start_executable,
env_vars=self._env_vars)
info = self._subprocess._send(None, _get_info) info = self._subprocess._send(None, _get_info)
except Exception as exc: except Exception as exc:
raise InvalidPythonEnvironment( raise InvalidPythonEnvironment(
@@ -91,19 +93,9 @@ class Environment(_BaseEnvironment):
""" """
self.version_info = _VersionInfo(*info[2]) self.version_info = _VersionInfo(*info[2])
""" """
Like ``sys.version_info``. A tuple to show the current Environment's Like :data:`sys.version_info`: a tuple to show the current
Python version. 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 return self._subprocess
def __repr__(self): def __repr__(self):
@@ -117,7 +109,7 @@ class Environment(_BaseEnvironment):
def get_sys_path(self): def get_sys_path(self):
""" """
The sys path for this environment. Does not include potential The sys path for this environment. Does not include potential
modifications like ``sys.path.append``. modifications from e.g. appending to :data:`sys.path`.
:returns: list of str :returns: list of str
""" """
@@ -129,11 +121,12 @@ class Environment(_BaseEnvironment):
return self._get_subprocess().get_sys_path() return self._get_subprocess().get_sys_path()
class _SameEnvironmentMixin(object): class _SameEnvironmentMixin:
def __init__(self): def __init__(self):
self._start_executable = self.executable = sys.executable self._start_executable = self.executable = sys.executable
self.path = sys.prefix self.path = sys.prefix
self.version_info = _VersionInfo(*sys.version_info[:3]) self.version_info = _VersionInfo(*sys.version_info[:3])
self._env_vars = None
class SameEnvironment(_SameEnvironmentMixin, Environment): class SameEnvironment(_SameEnvironmentMixin, Environment):
@@ -185,7 +178,7 @@ def get_default_environment():
makes it possible to use as many new Python features as possible when using makes it possible to use as many new Python features as possible when using
autocompletion and other functionality. autocompletion and other functionality.
:returns: :class:`Environment` :returns: :class:`.Environment`
""" """
virtual_env = _get_virtual_env_from_var() virtual_env = _get_virtual_env_from_var()
if virtual_env is not None: if virtual_env is not None:
@@ -254,10 +247,17 @@ def get_cached_default_environment():
@time_cache(seconds=10 * 60) # 10 Minutes @time_cache(seconds=10 * 60) # 10 Minutes
def _get_cached_default_environment(): def _get_cached_default_environment():
try:
return get_default_environment() return get_default_environment()
except InvalidPythonEnvironment:
# It's possible that `sys.executable` is wrong. Typically happens
# when Jedi is used in an executable that embeds Python. For further
# information, have a look at:
# https://github.com/davidhalter/jedi/issues/1531
return InterpreterEnvironment()
def find_virtualenvs(paths=None, **kwargs): def find_virtualenvs(paths=None, *, safe=True, use_environment_vars=True):
""" """
:param paths: A list of paths in your file system to be scanned for :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 Virtualenvs. It will search in these paths and potentially execute the
@@ -272,9 +272,8 @@ def find_virtualenvs(paths=None, **kwargs):
CONDA_PREFIX will be checked to see if it contains a valid conda CONDA_PREFIX will be checked to see if it contains a valid conda
environment. environment.
:yields: :class:`Environment` :yields: :class:`.Environment`
""" """
def py27_comp(paths=None, safe=True, use_environment_vars=True):
if paths is None: if paths is None:
paths = [] paths = []
@@ -311,10 +310,8 @@ def find_virtualenvs(paths=None, **kwargs):
except InvalidPythonEnvironment: except InvalidPythonEnvironment:
pass pass
return py27_comp(paths, **kwargs)
def find_system_environments(*, env_vars=None):
def find_system_environments():
""" """
Ignores virtualenvs and returns the Python versions that were installed on 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 your system. This might return nothing, if you're running Python e.g. from
@@ -322,24 +319,24 @@ def find_system_environments():
The environments are sorted from latest to oldest Python version. The environments are sorted from latest to oldest Python version.
:yields: :class:`Environment` :yields: :class:`.Environment`
""" """
for version_string in _SUPPORTED_PYTHONS: for version_string in _SUPPORTED_PYTHONS:
try: try:
yield get_system_environment(version_string) yield get_system_environment(version_string, env_vars=env_vars)
except InvalidPythonEnvironment: except InvalidPythonEnvironment:
pass pass
# TODO: this function should probably return a list of environments since # TODO: this function should probably return a list of environments since
# multiple Python installations can be found on a system for the same version. # multiple Python installations can be found on a system for the same version.
def get_system_environment(version): def get_system_environment(version, *, env_vars=None):
""" """
Return the first Python environment found for a string of the form 'X.Y' 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. where X and Y are the major and minor versions of Python.
:raises: :exc:`.InvalidPythonEnvironment` :raises: :exc:`.InvalidPythonEnvironment`
:returns: :class:`Environment` :returns: :class:`.Environment`
""" """
exe = which('python' + version) exe = which('python' + version)
if exe: if exe:
@@ -350,24 +347,24 @@ def get_system_environment(version):
if os.name == 'nt': if os.name == 'nt':
for exe in _get_executables_from_windows_registry(version): for exe in _get_executables_from_windows_registry(version):
try: try:
return Environment(exe) return Environment(exe, env_vars=env_vars)
except InvalidPythonEnvironment: except InvalidPythonEnvironment:
pass pass
raise InvalidPythonEnvironment("Cannot find executable python%s." % version) raise InvalidPythonEnvironment("Cannot find executable python%s." % version)
def create_environment(path, safe=True): def create_environment(path, *, safe=True, env_vars=None):
""" """
Make it possible to manually create an Environment object by specifying a Make it possible to manually create an Environment object by specifying a
Virtualenv path or an executable path. Virtualenv path or an executable path and optional environment variables.
:raises: :exc:`.InvalidPythonEnvironment` :raises: :exc:`.InvalidPythonEnvironment`
:returns: :class:`Environment` :returns: :class:`.Environment`
""" """
if os.path.isfile(path): if os.path.isfile(path):
_assert_safe(path, safe) _assert_safe(path, safe)
return Environment(path) return Environment(path, env_vars=env_vars)
return Environment(_get_executable_path(path, safe=safe)) return Environment(_get_executable_path(path, safe=safe), env_vars=env_vars)
def _get_executable_path(path, safe=True): def _get_executable_path(path, safe=True):
@@ -387,11 +384,8 @@ def _get_executable_path(path, safe=True):
def _get_executables_from_windows_registry(version): def _get_executables_from_windows_registry(version):
# The winreg module is named _winreg on Python 2. # https://github.com/python/typeshed/pull/3794 adds winreg
try: import winreg # type: ignore[import]
import winreg
except ImportError:
import _winreg as winreg
# TODO: support Python Anaconda. # TODO: support Python Anaconda.
sub_keys = [ sub_keys = [
+46
View File
@@ -0,0 +1,46 @@
"""
This file is about errors in Python files and not about exception handling in
Jedi.
"""
def parso_to_jedi_errors(grammar, module_node):
return [SyntaxError(e) for e in grammar.iter_errors(module_node)]
class SyntaxError:
"""
Syntax errors are generated by :meth:`.Script.get_syntax_errors`.
"""
def __init__(self, parso_error):
self._parso_error = parso_error
@property
def line(self):
"""The line where the error starts (starting with 1)."""
return self._parso_error.start_pos[0]
@property
def column(self):
"""The column where the error starts (starting with 0)."""
return self._parso_error.start_pos[1]
@property
def until_line(self):
"""The line where the error ends (starting with 1)."""
return self._parso_error.end_pos[0]
@property
def until_column(self):
"""The column where the error ends (starting with 0)."""
return self._parso_error.end_pos[1]
def get_message(self):
return self._parso_error.message
def __repr__(self):
return '<%s from=%s to=%s>' % (
self.__class__.__name__,
self._parso_error.start_pos,
self._parso_error.end_pos,
)
+23 -2
View File
@@ -3,8 +3,29 @@ class _JediError(Exception):
class InternalError(_JediError): class InternalError(_JediError):
pass """
This error might happen a subprocess is crashing. The reason for this is
usually broken C code in third party libraries. This is not a very common
thing and it is safe to use Jedi again. However using the same calls might
result in the same error again.
"""
class WrongVersion(_JediError): class WrongVersion(_JediError):
pass """
This error is reserved for the future, shouldn't really be happening at the
moment.
"""
class RefactoringError(_JediError):
"""
Refactorings can fail for various reasons. So if you work with refactorings
like :meth:`.Script.rename`, :meth:`.Script.inline`,
:meth:`.Script.extract_variable` and :meth:`.Script.extract_function`, make
sure to catch these. The descriptions in the errors are ususally valuable
for end users.
A typical ``RefactoringError`` would tell the user that inlining is not
possible if no name is under the cursor.
"""
+11 -13
View File
@@ -1,22 +1,23 @@
import os import os
from jedi._compatibility import FileNotFoundError, force_unicode, scandir
from jedi.api import classes from jedi.api import classes
from jedi.api.strings import StringName, get_quote_ending from jedi.api.strings import StringName, get_quote_ending
from jedi.api.helpers import fuzzy_match, start_match from jedi.api.helpers import match
from jedi.inference.helpers import get_str_or_none from jedi.inference.helpers import get_str_or_none
class PathName(StringName): class PathName(StringName):
api_type = u'path' api_type = 'path'
def complete_file_name(inference_state, module_context, start_leaf, string, def complete_file_name(inference_state, module_context, start_leaf, quote, string,
like_name, signatures_callback, code_lines, position, fuzzy): like_name, signatures_callback, code_lines, position, fuzzy):
# First we want to find out what can actually be changed as a name. # First we want to find out what can actually be changed as a name.
like_name_length = len(os.path.basename(string)) like_name_length = len(os.path.basename(string))
addition = _get_string_additions(module_context, start_leaf) addition = _get_string_additions(module_context, start_leaf)
if string.startswith('~'):
string = os.path.expanduser(string)
if addition is None: if addition is None:
return return
string = addition + string string = addition + string
@@ -34,21 +35,18 @@ def complete_file_name(inference_state, module_context, start_leaf, string,
is_in_os_path_join = False is_in_os_path_join = False
else: else:
string = to_be_added + string string = to_be_added + string
base_path = os.path.join(inference_state.project._path, string) base_path = os.path.join(inference_state.project.path, string)
try: try:
listed = sorted(scandir(base_path), key=lambda e: e.name) listed = sorted(os.scandir(base_path), key=lambda e: e.name)
# OSError: [Errno 36] File name too long: '...' # OSError: [Errno 36] File name too long: '...'
except (FileNotFoundError, OSError): except (FileNotFoundError, OSError):
return return
quote_ending = get_quote_ending(quote, code_lines, position)
for entry in listed: for entry in listed:
name = entry.name name = entry.name
if fuzzy: if match(name, must_start_with, fuzzy=fuzzy):
match = fuzzy_match(name, must_start_with)
else:
match = start_match(name, must_start_with)
if match:
if is_in_os_path_join or not entry.is_dir(): if is_in_os_path_join or not entry.is_dir():
name += get_quote_ending(start_leaf.value, code_lines, position) name += quote_ending
else: else:
name += os.path.sep name += os.path.sep
@@ -95,7 +93,7 @@ def _add_strings(context, nodes, add_slash=False):
return None return None
if not first and add_slash: if not first and add_slash:
string += os.path.sep string += os.path.sep
string += force_unicode(s) string += s
first = False first = False
return string return string
+88 -22
View File
@@ -4,38 +4,50 @@ Helpers for the API
import re import re
from collections import namedtuple from collections import namedtuple
from textwrap import dedent from textwrap import dedent
from itertools import chain
from functools import wraps from functools import wraps
from inspect import Parameter
from parso.python.parser import Parser from parso.python.parser import Parser
from parso.python import tree from parso.python import tree
from jedi._compatibility import u, Parameter
from jedi.inference.base_value import NO_VALUES from jedi.inference.base_value import NO_VALUES
from jedi.inference.syntax_tree import infer_atom from jedi.inference.syntax_tree import infer_atom
from jedi.inference.helpers import infer_call_of_leaf from jedi.inference.helpers import infer_call_of_leaf
from jedi.inference.compiled import get_string_value_set from jedi.inference.compiled import get_string_value_set
from jedi.cache import signature_time_cache from jedi.cache import signature_time_cache, memoize_method
from jedi.parser_utils import get_parent_scope
CompletionParts = namedtuple('CompletionParts', ['path', 'has_dot', 'name']) CompletionParts = namedtuple('CompletionParts', ['path', 'has_dot', 'name'])
def start_match(string, like_name): def _start_match(string, like_name):
return string.startswith(like_name) return string.startswith(like_name)
def fuzzy_match(string, like_name): def _fuzzy_match(string, like_name):
if len(like_name) <= 1: if len(like_name) <= 1:
return like_name in string return like_name in string
pos = string.find(like_name[0]) pos = string.find(like_name[0])
if pos >= 0: if pos >= 0:
return fuzzy_match(string[pos + 1:], like_name[1:]) return _fuzzy_match(string[pos + 1:], like_name[1:])
return False return False
def match(string, like_name, fuzzy=False):
if fuzzy:
return _fuzzy_match(string, like_name)
else:
return _start_match(string, like_name)
def sorted_definitions(defs): def sorted_definitions(defs):
# Note: `or ''` below is required because `module_path` could be # Note: `or ''` below is required because `module_path` could be
return sorted(defs, key=lambda x: (x.module_path or '', x.line or 0, x.column or 0, x.name)) return sorted(defs, key=lambda x: (str(x.module_path or ''),
x.line or 0,
x.column or 0,
x.name))
def get_on_completion_name(module_node, lines, position): def get_on_completion_name(module_node, lines, position):
@@ -75,18 +87,18 @@ def _get_code_for_stack(code_lines, leaf, position):
# If we're not on a comment simply get the previous leaf and proceed. # If we're not on a comment simply get the previous leaf and proceed.
leaf = leaf.get_previous_leaf() leaf = leaf.get_previous_leaf()
if leaf is None: if leaf is None:
return u('') # At the beginning of the file. return '' # At the beginning of the file.
is_after_newline = leaf.type == 'newline' is_after_newline = leaf.type == 'newline'
while leaf.type == 'newline': while leaf.type == 'newline':
leaf = leaf.get_previous_leaf() leaf = leaf.get_previous_leaf()
if leaf is None: if leaf is None:
return u('') return ''
if leaf.type == 'error_leaf' or leaf.type == 'string': if leaf.type == 'error_leaf' or leaf.type == 'string':
if leaf.start_pos[0] < position[0]: if leaf.start_pos[0] < position[0]:
# On a different line, we just begin anew. # On a different line, we just begin anew.
return u('') return ''
# Error leafs cannot be parsed, completion in strings is also # Error leafs cannot be parsed, completion in strings is also
# impossible. # impossible.
@@ -102,7 +114,7 @@ def _get_code_for_stack(code_lines, leaf, position):
if user_stmt.start_pos[1] > position[1]: if user_stmt.start_pos[1] > position[1]:
# This means that it's actually a dedent and that means that we # This means that it's actually a dedent and that means that we
# start without value (part of a suite). # start without value (part of a suite).
return u('') return ''
# This is basically getting the relevant lines. # This is basically getting the relevant lines.
return _get_code(code_lines, user_stmt.get_start_pos_of_prefix(), position) return _get_code(code_lines, user_stmt.get_start_pos_of_prefix(), position)
@@ -186,13 +198,12 @@ def filter_follow_imports(names, follow_builtin_imports=False):
if found_builtin: if found_builtin:
yield name yield name
else: else:
for new_name in new_names: yield from new_names
yield new_name
else: else:
yield name yield name
class CallDetails(object): class CallDetails:
def __init__(self, bracket_leaf, children, position): def __init__(self, bracket_leaf, children, position):
['bracket_leaf', 'call_index', 'keyword_name_str'] ['bracket_leaf', 'call_index', 'keyword_name_str']
self.bracket_leaf = bracket_leaf self.bracket_leaf = bracket_leaf
@@ -207,11 +218,15 @@ class CallDetails(object):
def keyword_name_str(self): def keyword_name_str(self):
return _get_index_and_key(self._children, self._position)[1] return _get_index_and_key(self._children, self._position)[1]
@memoize_method
def _list_arguments(self):
return list(_iter_arguments(self._children, self._position))
def calculate_index(self, param_names): def calculate_index(self, param_names):
positional_count = 0 positional_count = 0
used_names = set() used_names = set()
star_count = -1 star_count = -1
args = list(_iter_arguments(self._children, self._position)) args = self._list_arguments()
if not args: if not args:
if param_names: if param_names:
return 0 return 0
@@ -258,6 +273,19 @@ class CallDetails(object):
return i return i
return None return None
def iter_used_keyword_arguments(self):
for star_count, key_start, had_equal in list(self._list_arguments()):
if had_equal and key_start:
yield key_start
def count_positional_arguments(self):
count = 0
for star_count, key_start, had_equal in self._list_arguments()[:-1]:
if star_count:
break
count += 1
return count
def _iter_arguments(nodes, position): def _iter_arguments(nodes, position):
def remove_after_pos(name): def remove_after_pos(name):
@@ -268,8 +296,7 @@ def _iter_arguments(nodes, position):
# Returns Generator[Tuple[star_count, Optional[key_start: str], had_equal]] # Returns Generator[Tuple[star_count, Optional[key_start: str], had_equal]]
nodes_before = [c for c in nodes if c.start_pos < position] nodes_before = [c for c in nodes if c.start_pos < position]
if nodes_before[-1].type == 'arglist': if nodes_before[-1].type == 'arglist':
for x in _iter_arguments(nodes_before[-1].children, position): yield from _iter_arguments(nodes_before[-1].children, position)
yield x # Python 2 :(
return return
previous_node_yielded = False previous_node_yielded = False
@@ -294,7 +321,7 @@ def _iter_arguments(nodes, position):
else: else:
yield 0, None, False yield 0, None, False
stars_seen = 0 stars_seen = 0
elif node.type in ('testlist', 'testlist_star_expr'): # testlist is Python 2 elif node.type == 'testlist_star_expr':
for n in node.children[::2]: for n in node.children[::2]:
if n.type == 'star_expr': if n.type == 'star_expr':
stars_seen = 1 stars_seen = 1
@@ -376,7 +403,7 @@ def get_signature_details(module, position):
# parents for possible function definitions. # parents for possible function definitions.
node = leaf.parent node = leaf.parent
while node is not None: while node is not None:
if node.type in ('funcdef', 'classdef'): if node.type in ('funcdef', 'classdef', 'decorated', 'async_stmt'):
# Don't show signatures if there's stuff before it that just # Don't show signatures if there's stuff before it that just
# makes it feel strange to have a signature. # makes it feel strange to have a signature.
return None return None
@@ -396,7 +423,8 @@ def get_signature_details(module, position):
additional_children.insert(0, n) additional_children.insert(0, n)
# Find a valid trailer # Find a valid trailer
if node.type == 'trailer' and node.children[0] == '(': if node.type == 'trailer' and node.children[0] == '(' \
or node.type == 'decorator' and node.children[2] == '(':
# Additionally we have to check that an ending parenthesis isn't # Additionally we have to check that an ending parenthesis isn't
# interpreted wrong. There are two cases: # interpreted wrong. There are two cases:
# 1. Cursor before paren -> The current signature is good # 1. Cursor before paren -> The current signature is good
@@ -405,7 +433,11 @@ def get_signature_details(module, position):
leaf = node.get_previous_leaf() leaf = node.get_previous_leaf()
if leaf is None: if leaf is None:
return None return None
return CallDetails(node.children[0], node.children, position) return CallDetails(
node.children[0] if node.type == 'trailer' else node.children[2],
node.children,
position
)
node = node.parent node = node.parent
@@ -444,8 +476,8 @@ def validate_line_column(func):
line_string = self._code_lines[line - 1] line_string = self._code_lines[line - 1]
line_len = len(line_string) line_len = len(line_string)
if line_string.endswith('\r\n'): if line_string.endswith('\r\n'):
line_len -= 1 line_len -= 2
if line_string.endswith('\n'): elif line_string.endswith('\n'):
line_len -= 1 line_len -= 1
column = line_len if column is None else column column = line_len if column is None else column
@@ -455,3 +487,37 @@ def validate_line_column(func):
column, line_len, line, line_string)) column, line_len, line, line_string))
return func(self, line, column, *args, **kwargs) return func(self, line, column, *args, **kwargs)
return wrapper return wrapper
def get_module_names(module, all_scopes, definitions=True, references=False):
"""
Returns a dictionary with name parts as keys and their call paths as
values.
"""
def def_ref_filter(name):
is_def = name.is_definition()
return definitions and is_def or references and not is_def
names = list(chain.from_iterable(module.get_used_names().values()))
if not all_scopes:
# We have to filter all the names that don't have the module as a
# parent_scope. There's None as a parent, because nodes in the module
# node have the parent module and not suite as all the others.
# Therefore it's important to catch that case.
def is_module_scope_name(name):
parent_scope = get_parent_scope(name)
# async functions have an extra wrapper. Strip it.
if parent_scope and parent_scope.type == 'async_stmt':
parent_scope = parent_scope.parent
return parent_scope in (module, None)
names = [n for n in names if is_module_scope_name(n)]
return filter(def_ref_filter, names)
def split_search_string(name):
type, _, dotted_names = name.rpartition(' ')
if type == 'def':
type = 'function'
return type, dotted_names.split('.')
+44 -11
View File
@@ -3,6 +3,9 @@ TODO Some parts of this module are still not well documented.
""" """
from jedi.inference import compiled from jedi.inference import compiled
from jedi.inference.base_value import ValueSet
from jedi.inference.filters import ParserTreeFilter, MergedFilter
from jedi.inference.names import TreeNameDefinition
from jedi.inference.compiled import mixed from jedi.inference.compiled import mixed
from jedi.inference.compiled.access import create_access_path from jedi.inference.compiled.access import create_access_path
from jedi.inference.context import ModuleContext from jedi.inference.context import ModuleContext
@@ -14,15 +17,42 @@ def _create(inference_state, obj):
) )
class NamespaceObject(object): class NamespaceObject:
def __init__(self, dct): def __init__(self, dct):
self.__dict__ = dct self.__dict__ = dct
class MixedTreeName(TreeNameDefinition):
def infer(self):
"""
In IPython notebook it is typical that some parts of the code that is
provided was already executed. In that case if something is not properly
inferred, it should still infer from the variables it already knows.
"""
inferred = super().infer()
if not inferred:
for compiled_value in self.parent_context.mixed_values:
for f in compiled_value.get_filters():
values = ValueSet.from_sets(
n.infer() for n in f.get(self.string_name)
)
if values:
return values
return inferred
class MixedParserTreeFilter(ParserTreeFilter):
name_class = MixedTreeName
class MixedModuleContext(ModuleContext): class MixedModuleContext(ModuleContext):
def __init__(self, tree_module_value, namespaces): def __init__(self, tree_module_value, namespaces):
super(MixedModuleContext, self).__init__(tree_module_value) super().__init__(tree_module_value)
self._namespace_objects = [NamespaceObject(n) for n in namespaces] self.mixed_values = [
self._get_mixed_object(
_create(self.inference_state, NamespaceObject(n))
) for n in namespaces
]
def _get_mixed_object(self, compiled_value): def _get_mixed_object(self, compiled_value):
return mixed.MixedObject( return mixed.MixedObject(
@@ -30,12 +60,15 @@ class MixedModuleContext(ModuleContext):
tree_value=self._value tree_value=self._value
) )
def get_filters(self, *args, **kwargs): def get_filters(self, until_position=None, origin_scope=None):
for filter in self._value.as_context().get_filters(*args, **kwargs): yield MergedFilter(
yield filter MixedParserTreeFilter(
parent_context=self,
until_position=until_position,
origin_scope=origin_scope
),
self.get_global_filter(),
)
for namespace_obj in self._namespace_objects: for mixed_object in self.mixed_values:
compiled_value = _create(self.inference_state, namespace_obj) yield from mixed_object.get_filters(until_position, origin_scope)
mixed_object = self._get_mixed_object(compiled_value)
for filter in mixed_object.get_filters(*args, **kwargs):
yield filter
+9 -14
View File
@@ -1,22 +1,20 @@
import pydoc import pydoc
from contextlib import suppress
from typing import Dict, Optional
from jedi.inference.utils import ignored
from jedi.inference.names import AbstractArbitraryName from jedi.inference.names import AbstractArbitraryName
try: try:
from pydoc_data import topics as pydoc_topics # https://github.com/python/typeshed/pull/4351 adds pydoc_data
from pydoc_data import topics # type: ignore[import]
pydoc_topics: Optional[Dict[str, str]] = topics.topics
except ImportError: except ImportError:
# Python 2 # Python 3.6.8 embeddable does not have pydoc_data.
try:
import pydoc_topics
except ImportError:
# This is for Python 3 embeddable version, which dont have
# pydoc_data module in its file python3x.zip.
pydoc_topics = None pydoc_topics = None
class KeywordName(AbstractArbitraryName): class KeywordName(AbstractArbitraryName):
api_type = u'keyword' api_type = 'keyword'
def py__doc__(self): def py__doc__(self):
return imitate_pydoc(self.string_name) return imitate_pydoc(self.string_name)
@@ -30,11 +28,8 @@ def imitate_pydoc(string):
if pydoc_topics is None: if pydoc_topics is None:
return '' return ''
# str needed because of possible unicode stuff in py2k (pydoc doesn't work
# with unicode strings)
string = str(string)
h = pydoc.help h = pydoc.help
with ignored(KeyError): with suppress(KeyError):
# try to access symbols # try to access symbols
string = h.symbols[string] string = h.symbols[string]
string, _, related = string.partition(' ') string, _, related = string.partition(' ')
@@ -52,6 +47,6 @@ def imitate_pydoc(string):
return '' return ''
try: try:
return pydoc_topics.topics[label].strip() if pydoc_topics else '' return pydoc_topics[label].strip() if pydoc_topics else ''
except KeyError: except KeyError:
return '' return ''
+302 -67
View File
@@ -1,21 +1,52 @@
import os """
import json Projects are a way to handle Python projects within Jedi. For simpler plugins
you might not want to deal with projects, but if you want to give the user more
flexibility to define sys paths and Python interpreters for a project,
:class:`.Project` is the perfect way to allow for that.
from jedi._compatibility import FileNotFoundError, PermissionError, IsADirectoryError Projects can be saved to disk and loaded again, to allow project definitions to
from jedi.api.environment import SameEnvironment, \ be used across repositories.
get_cached_default_environment """
import json
from pathlib import Path
from itertools import chain
from jedi import debug
from jedi.api.environment import get_cached_default_environment, create_environment
from jedi.api.exceptions import WrongVersion from jedi.api.exceptions import WrongVersion
from jedi._compatibility import force_unicode from jedi.api.completion import search_in_module
from jedi.api.helpers import split_search_string, get_module_names
from jedi.inference.imports import load_module_from_path, \
load_namespace_from_path, iter_module_names
from jedi.inference.sys_path import discover_buildout_paths from jedi.inference.sys_path import discover_buildout_paths
from jedi.inference.cache import inference_state_as_method_param_cache from jedi.inference.cache import inference_state_as_method_param_cache
from jedi.common.utils import traverse_parents from jedi.inference.references import recurse_find_python_folders_and_files, search_in_file_ios
from jedi.file_io import FolderIO
_CONFIG_FOLDER = '.jedi' _CONFIG_FOLDER = '.jedi'
_CONTAINS_POTENTIAL_PROJECT = 'setup.py', '.git', '.hg', 'requirements.txt', 'MANIFEST.in' _CONTAINS_POTENTIAL_PROJECT = \
'setup.py', '.git', '.hg', 'requirements.txt', 'MANIFEST.in', 'pyproject.toml'
_SERIALIZER_VERSION = 1 _SERIALIZER_VERSION = 1
def _try_to_skip_duplicates(func):
def wrapper(*args, **kwargs):
found_tree_nodes = []
found_modules = []
for definition in func(*args, **kwargs):
tree_node = definition._name.tree_name
if tree_node is not None and tree_node in found_tree_nodes:
continue
if definition.type == 'module' and definition.module_path is not None:
if definition.module_path in found_modules:
continue
found_modules.append(definition.module_path)
yield definition
found_tree_nodes.append(tree_node)
return wrapper
def _remove_duplicates_from_path(path): def _remove_duplicates_from_path(path):
used = set() used = set()
for p in path: for p in path:
@@ -25,68 +56,129 @@ def _remove_duplicates_from_path(path):
yield p yield p
def _force_unicode_list(lst): class Project:
return list(map(force_unicode, lst)) """
Projects are a simple way to manage Python folders and define how Jedi does
import resolution. It is mostly used as a parameter to :class:`.Script`.
class Project(object): Additionally there are functions to search a whole project.
# TODO serialize environment """
_serializer_ignore_attributes = ('_environment',)
_environment = None _environment = None
@staticmethod
def _get_config_folder_path(base_path):
return base_path.joinpath(_CONFIG_FOLDER)
@staticmethod @staticmethod
def _get_json_path(base_path): def _get_json_path(base_path):
return os.path.join(base_path, _CONFIG_FOLDER, 'project.json') return Project._get_config_folder_path(base_path).joinpath('project.json')
@classmethod @classmethod
def load(cls, path): def load(cls, path):
""" """
Loads a project from a specific path. You should not provide the path
to ``.jedi/project.json``, but rather the path to the project folder.
:param path: The path of the directory you want to use as a project. :param path: The path of the directory you want to use as a project.
""" """
if isinstance(path, str):
path = Path(path)
with open(cls._get_json_path(path)) as f: with open(cls._get_json_path(path)) as f:
version, data = json.load(f) version, data = json.load(f)
if version == 1: if version == 1:
self = cls.__new__() return cls(**data)
self.__dict__.update(data)
return self
else: else:
raise WrongVersion( raise WrongVersion(
"The Jedi version of this project seems newer than what we can handle." "The Jedi version of this project seems newer than what we can handle."
) )
def save(self):
"""
Saves the project configuration in the project in ``.jedi/project.json``.
"""
data = dict(self.__dict__)
data.pop('_environment', None)
data.pop('_django', None) # TODO make django setting public?
data = {k.lstrip('_'): v for k, v in data.items()}
data['path'] = str(data['path'])
self._get_config_folder_path(self._path).mkdir(parents=True, exist_ok=True)
with open(self._get_json_path(self._path), 'w') as f:
return json.dump((_SERIALIZER_VERSION, data), f)
def __init__(self, path, **kwargs): def __init__(self, path, **kwargs):
""" """
:param path: The base path for this project. :param path: The base path for this project.
:param environment_path: The Python executable path, typically the path
of a virtual environment.
:param load_unsafe_extensions: Default False, Loads extensions that are not in the
sys path and in the local directories. With this option enabled,
this is potentially unsafe if you clone a git repository and
analyze it's code, because those compiled extensions will be
important and therefore have execution privileges.
:param sys_path: list of str. You can override the sys path if you :param sys_path: list of str. You can override the sys path if you
want. By default the ``sys.path.`` is generated from the want. By default the ``sys.path.`` is generated by the
environment (virtualenvs, etc). environment (virtualenvs, etc).
:param added_sys_path: list of str. Adds these paths at the end of the
sys path.
:param smart_sys_path: If this is enabled (default), adds paths from :param smart_sys_path: If this is enabled (default), adds paths from
local directories. Otherwise you will have to rely on your packages local directories. Otherwise you will have to rely on your packages
being properly configured on the ``sys.path``. being properly configured on the ``sys.path``.
""" """
def py2_comp(path, environment=None, sys_path=None, def py2_comp(path, environment_path=None, load_unsafe_extensions=False,
smart_sys_path=True, _django=False): sys_path=None, added_sys_path=(), smart_sys_path=True):
self._path = os.path.abspath(path) if isinstance(path, str):
if isinstance(environment, SameEnvironment): path = Path(path).absolute()
self._environment = environment self._path = path
self._environment_path = environment_path
if sys_path is not None:
# Remap potential pathlib.Path entries
sys_path = list(map(str, sys_path))
self._sys_path = sys_path self._sys_path = sys_path
self._smart_sys_path = smart_sys_path self._smart_sys_path = smart_sys_path
self._django = _django self._load_unsafe_extensions = load_unsafe_extensions
self._django = False
# Remap potential pathlib.Path entries
self.added_sys_path = list(map(str, added_sys_path))
"""The sys path that is going to be added at the end of the """
py2_comp(path, **kwargs) py2_comp(path, **kwargs)
@inference_state_as_method_param_cache() @property
def _get_base_sys_path(self, inference_state, environment=None): def path(self):
if self._sys_path is not None: """
The base path for this project.
"""
return self._path
@property
def sys_path(self):
"""
The sys path provided to this project. This can be None and in that
case will be auto generated.
"""
return self._sys_path return self._sys_path
# The sys path has not been set explicitly. @property
if environment is None: def smart_sys_path(self):
environment = self.get_environment() """
If the sys path is going to be calculated in a smart way, where
additional paths are added.
"""
return self._smart_sys_path
sys_path = list(environment.get_sys_path()) @property
def load_unsafe_extensions(self):
"""
Wheter the project loads unsafe extensions.
"""
return self._load_unsafe_extensions
@inference_state_as_method_param_cache()
def _get_base_sys_path(self, inference_state):
# The sys path has not been set explicitly.
sys_path = list(inference_state.environment.get_sys_path())
try: try:
sys_path.remove('') sys_path.remove('')
except ValueError: except ValueError:
@@ -94,34 +186,41 @@ class Project(object):
return sys_path return sys_path
@inference_state_as_method_param_cache() @inference_state_as_method_param_cache()
def _get_sys_path(self, inference_state, environment=None, def _get_sys_path(self, inference_state, add_parent_paths=True, add_init_paths=False):
add_parent_paths=True, add_init_paths=False):
""" """
Keep this method private for all users of jedi. However internally this Keep this method private for all users of jedi. However internally this
one is used like a public method. one is used like a public method.
""" """
suffixed = [] suffixed = list(self.added_sys_path)
prefixed = [] prefixed = []
sys_path = list(self._get_base_sys_path(inference_state, environment)) if self._sys_path is None:
sys_path = list(self._get_base_sys_path(inference_state))
else:
sys_path = list(self._sys_path)
if self._smart_sys_path: if self._smart_sys_path:
prefixed.append(self._path) prefixed.append(str(self._path))
if inference_state.script_path is not None: if inference_state.script_path is not None:
suffixed += discover_buildout_paths(inference_state, inference_state.script_path) suffixed += map(str, discover_buildout_paths(
inference_state,
inference_state.script_path
))
if add_parent_paths: if add_parent_paths:
# Collect directories in upward search by: # Collect directories in upward search by:
# 1. Skipping directories with __init__.py # 1. Skipping directories with __init__.py
# 2. Stopping immediately when above self._path # 2. Stopping immediately when above self._path
traversed = [] traversed = []
for parent_path in traverse_parents(inference_state.script_path): for parent_path in inference_state.script_path.parents:
if not parent_path.startswith(self._path): if parent_path == self._path \
or self._path not in parent_path.parents:
break break
if not add_init_paths \ if not add_init_paths \
and os.path.isfile(os.path.join(parent_path, "__init__.py")): and parent_path.joinpath("__init__.py").is_file():
continue continue
traversed.append(parent_path) traversed.append(str(parent_path))
# AFAIK some libraries have imports like `foo.foo.bar`, which # AFAIK some libraries have imports like `foo.foo.bar`, which
# leads to the conclusion to by default prefer longer paths # leads to the conclusion to by default prefer longer paths
@@ -129,70 +228,199 @@ class Project(object):
suffixed += reversed(traversed) suffixed += reversed(traversed)
if self._django: if self._django:
prefixed.append(self._path) prefixed.append(str(self._path))
path = prefixed + sys_path + suffixed path = prefixed + sys_path + suffixed
return list(_force_unicode_list(_remove_duplicates_from_path(path))) return 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): def get_environment(self):
if self._environment is None: if self._environment is None:
return get_cached_default_environment() if self._environment_path is not None:
self._environment = create_environment(self._environment_path, safe=False)
else:
self._environment = get_cached_default_environment()
return self._environment return self._environment
def search(self, string, *, all_scopes=False):
"""
Searches a name in the whole project. If the project is very big,
at some point Jedi will stop searching. However it's also very much
recommended to not exhaust the generator. Just display the first ten
results to the user.
There are currently three different search patterns:
- ``foo`` to search for a definition foo in any file or a file called
``foo.py`` or ``foo.pyi``.
- ``foo.bar`` to search for the ``foo`` and then an attribute ``bar``
in it.
- ``class foo.bar.Bar`` or ``def foo.bar.baz`` to search for a specific
API type.
:param bool all_scopes: Default False; searches not only for
definitions on the top level of a module level, but also in
functions and classes.
:yields: :class:`.Name`
"""
return self._search_func(string, all_scopes=all_scopes)
def complete_search(self, string, **kwargs):
"""
Like :meth:`.Script.search`, but completes that string. An empty string
lists all definitions in a project, so be careful with that.
:param bool all_scopes: Default False; searches not only for
definitions on the top level of a module level, but also in
functions and classes.
:yields: :class:`.Completion`
"""
return self._search_func(string, complete=True, **kwargs)
@_try_to_skip_duplicates
def _search_func(self, string, complete=False, all_scopes=False):
# Using a Script is they easiest way to get an empty module context.
from jedi import Script
s = Script('', project=self)
inference_state = s._inference_state
empty_module_context = s._get_module_context()
debug.dbg('Search for string %s, complete=%s', string, complete)
wanted_type, wanted_names = split_search_string(string)
name = wanted_names[0]
stub_folder_name = name + '-stubs'
ios = recurse_find_python_folders_and_files(FolderIO(str(self._path)))
file_ios = []
# 1. Search for modules in the current project
for folder_io, file_io in ios:
if file_io is None:
file_name = folder_io.get_base_name()
if file_name == name or file_name == stub_folder_name:
f = folder_io.get_file_io('__init__.py')
try:
m = load_module_from_path(inference_state, f).as_context()
except FileNotFoundError:
f = folder_io.get_file_io('__init__.pyi')
try:
m = load_module_from_path(inference_state, f).as_context()
except FileNotFoundError:
m = load_namespace_from_path(inference_state, folder_io).as_context()
else:
continue
else:
file_ios.append(file_io)
if Path(file_io.path).name in (name + '.py', name + '.pyi'):
m = load_module_from_path(inference_state, file_io).as_context()
else:
continue
debug.dbg('Search of a specific module %s', m)
yield from search_in_module(
inference_state,
m,
names=[m.name],
wanted_type=wanted_type,
wanted_names=wanted_names,
complete=complete,
convert=True,
ignore_imports=True,
)
# 2. Search for identifiers in the project.
for module_context in search_in_file_ios(inference_state, file_ios, name):
names = get_module_names(module_context.tree_node, all_scopes=all_scopes)
names = [module_context.create_name(n) for n in names]
names = _remove_imports(names)
yield from search_in_module(
inference_state,
module_context,
names=names,
wanted_type=wanted_type,
wanted_names=wanted_names,
complete=complete,
ignore_imports=True,
)
# 3. Search for modules on sys.path
sys_path = [
p for p in self._get_sys_path(inference_state)
# Exclude folders that are handled by recursing of the Python
# folders.
if not p.startswith(str(self._path))
]
names = list(iter_module_names(inference_state, empty_module_context, sys_path))
yield from search_in_module(
inference_state,
empty_module_context,
names=names,
wanted_type=wanted_type,
wanted_names=wanted_names,
complete=complete,
convert=True,
)
def __repr__(self): def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self._path) return '<%s: %s>' % (self.__class__.__name__, self._path)
def _is_potential_project(path): def _is_potential_project(path):
for name in _CONTAINS_POTENTIAL_PROJECT: for name in _CONTAINS_POTENTIAL_PROJECT:
if os.path.exists(os.path.join(path, name)): try:
if path.joinpath(name).exists():
return True return True
except OSError:
continue
return False return False
def _is_django_path(directory): def _is_django_path(directory):
""" Detects the path of the very well known Django library (if used) """ """ Detects the path of the very well known Django library (if used) """
try: try:
with open(os.path.join(directory, 'manage.py'), 'rb') as f: with open(directory.joinpath('manage.py'), 'rb') as f:
return b"DJANGO_SETTINGS_MODULE" in f.read() return b"DJANGO_SETTINGS_MODULE" in f.read()
except (FileNotFoundError, IsADirectoryError, PermissionError): except (FileNotFoundError, IsADirectoryError, PermissionError):
return False return False
return False
def get_default_project(path=None): def get_default_project(path=None):
if path is None: """
path = os.getcwd() If a project is not defined by the user, Jedi tries to define a project by
itself as well as possible. Jedi traverses folders until it finds one of
the following:
check = os.path.realpath(path) 1. A ``.jedi/config.json``
2. One of the following files: ``setup.py``, ``.git``, ``.hg``,
``requirements.txt`` and ``MANIFEST.in``.
"""
if path is None:
path = Path.cwd()
elif isinstance(path, str):
path = Path(path)
check = path.absolute()
probable_path = None probable_path = None
first_no_init_file = None first_no_init_file = None
for dir in traverse_parents(check, include_current=True): for dir in chain([check], check.parents):
try: try:
return Project.load(dir) return Project.load(dir)
except (FileNotFoundError, IsADirectoryError, PermissionError): except (FileNotFoundError, IsADirectoryError, PermissionError):
pass pass
except NotADirectoryError:
continue
if first_no_init_file is None: if first_no_init_file is None:
if os.path.exists(os.path.join(dir, '__init__.py')): if dir.joinpath('__init__.py').exists():
# In the case that a __init__.py exists, it's in 99% just a # In the case that a __init__.py exists, it's in 99% just a
# Python package and the project sits at least one level above. # Python package and the project sits at least one level above.
continue continue
else: elif not dir.is_file():
first_no_init_file = dir first_no_init_file = dir
if _is_django_path(dir): if _is_django_path(dir):
return Project(dir, _django=True) project = Project(dir)
project._django = True
return project
if probable_path is None and _is_potential_project(dir): if probable_path is None and _is_potential_project(dir):
probable_path = dir probable_path = dir
@@ -204,5 +432,12 @@ def get_default_project(path=None):
if first_no_init_file is not None: if first_no_init_file is not None:
return Project(first_no_init_file) return Project(first_no_init_file)
curdir = path if os.path.isdir(path) else os.path.dirname(path) curdir = path if path.is_dir() else path.parent
return Project(curdir) return Project(curdir)
def _remove_imports(names):
return [
n for n in names
if n.tree_name is None or n.api_type not in ('module', 'namespace')
]
+242
View File
@@ -0,0 +1,242 @@
import difflib
from pathlib import Path
from typing import Dict, Iterable, Tuple
from parso import split_lines
from jedi.api.exceptions import RefactoringError
EXPRESSION_PARTS = (
'or_test and_test not_test comparison '
'expr xor_expr and_expr shift_expr arith_expr term factor power atom_expr'
).split()
class ChangedFile:
def __init__(self, inference_state, from_path, to_path,
module_node, node_to_str_map):
self._inference_state = inference_state
self._from_path = from_path
self._to_path = to_path
self._module_node = module_node
self._node_to_str_map = node_to_str_map
def get_diff(self):
old_lines = split_lines(self._module_node.get_code(), keepends=True)
new_lines = split_lines(self.get_new_code(), keepends=True)
# Add a newline at the end if it's missing. Otherwise the diff will be
# very weird. A `diff -u file1 file2` would show the string:
#
# \ No newline at end of file
#
# This is not necessary IMO, because Jedi does not really play with
# newlines and the ending newline does not really matter in Python
# files. ~dave
if old_lines[-1] != '':
old_lines[-1] += '\n'
if new_lines[-1] != '':
new_lines[-1] += '\n'
project_path = self._inference_state.project.path
if self._from_path is None:
from_p = ''
else:
from_p = self._from_path.relative_to(project_path)
if self._to_path is None:
to_p = ''
else:
to_p = self._to_path.relative_to(project_path)
diff = difflib.unified_diff(
old_lines, new_lines,
fromfile=str(from_p),
tofile=str(to_p),
)
# Apparently there's a space at the end of the diff - for whatever
# reason.
return ''.join(diff).rstrip(' ')
def get_new_code(self):
return self._inference_state.grammar.refactor(self._module_node, self._node_to_str_map)
def apply(self):
if self._from_path is None:
raise RefactoringError(
'Cannot apply a refactoring on a Script with path=None'
)
with open(self._from_path, 'w', newline='') as f:
f.write(self.get_new_code())
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self._from_path)
class Refactoring:
def __init__(self, inference_state, file_to_node_changes, renames=()):
self._inference_state = inference_state
self._renames = renames
self._file_to_node_changes = file_to_node_changes
def get_changed_files(self) -> Dict[Path, ChangedFile]:
def calculate_to_path(p):
if p is None:
return p
p = str(p)
for from_, to in renames:
if p.startswith(str(from_)):
p = str(to) + p[len(str(from_)):]
return Path(p)
renames = self.get_renames()
return {
path: ChangedFile(
self._inference_state,
from_path=path,
to_path=calculate_to_path(path),
module_node=next(iter(map_)).get_root_node(),
node_to_str_map=map_
) for path, map_ in sorted(self._file_to_node_changes.items())
}
def get_renames(self) -> Iterable[Tuple[Path, Path]]:
"""
Files can be renamed in a refactoring.
"""
return sorted(self._renames)
def get_diff(self):
text = ''
project_path = self._inference_state.project.path
for from_, to in self.get_renames():
text += 'rename from %s\nrename to %s\n' \
% (from_.relative_to(project_path), to.relative_to(project_path))
return text + ''.join(f.get_diff() for f in self.get_changed_files().values())
def apply(self):
"""
Applies the whole refactoring to the files, which includes renames.
"""
for f in self.get_changed_files().values():
f.apply()
for old, new in self.get_renames():
old.rename(new)
def _calculate_rename(path, new_name):
dir_ = path.parent
if path.name in ('__init__.py', '__init__.pyi'):
return dir_, dir_.parent.joinpath(new_name)
return path, dir_.joinpath(new_name + path.suffix)
def rename(inference_state, definitions, new_name):
file_renames = set()
file_tree_name_map = {}
if not definitions:
raise RefactoringError("There is no name under the cursor")
for d in definitions:
tree_name = d._name.tree_name
if d.type == 'module' and tree_name is None:
p = None if d.module_path is None else Path(d.module_path)
file_renames.add(_calculate_rename(p, new_name))
else:
# This private access is ok in a way. It's not public to
# protect Jedi users from seeing it.
if tree_name is not None:
fmap = file_tree_name_map.setdefault(d.module_path, {})
fmap[tree_name] = tree_name.prefix + new_name
return Refactoring(inference_state, file_tree_name_map, file_renames)
def inline(inference_state, names):
if not names:
raise RefactoringError("There is no name under the cursor")
if any(n.api_type in ('module', 'namespace') for n in names):
raise RefactoringError("Cannot inline imports, modules or namespaces")
if any(n.tree_name is None for n in names):
raise RefactoringError("Cannot inline builtins/extensions")
definitions = [n for n in names if n.tree_name.is_definition()]
if len(definitions) == 0:
raise RefactoringError("No definition found to inline")
if len(definitions) > 1:
raise RefactoringError("Cannot inline a name with multiple definitions")
if len(names) == 1:
raise RefactoringError("There are no references to this name")
tree_name = definitions[0].tree_name
expr_stmt = tree_name.get_definition()
if expr_stmt.type != 'expr_stmt':
type_ = dict(
funcdef='function',
classdef='class',
).get(expr_stmt.type, expr_stmt.type)
raise RefactoringError("Cannot inline a %s" % type_)
if len(expr_stmt.get_defined_names(include_setitem=True)) > 1:
raise RefactoringError("Cannot inline a statement with multiple definitions")
first_child = expr_stmt.children[1]
if first_child.type == 'annassign' and len(first_child.children) == 4:
first_child = first_child.children[2]
if first_child != '=':
if first_child.type == 'annassign':
raise RefactoringError(
'Cannot inline a statement that is defined by an annotation'
)
else:
raise RefactoringError(
'Cannot inline a statement with "%s"'
% first_child.get_code(include_prefix=False)
)
rhs = expr_stmt.get_rhs()
replace_code = rhs.get_code(include_prefix=False)
references = [n for n in names if not n.tree_name.is_definition()]
file_to_node_changes = {}
for name in references:
tree_name = name.tree_name
path = name.get_root_context().py__file__()
s = replace_code
if rhs.type == 'testlist_star_expr' \
or tree_name.parent.type in EXPRESSION_PARTS \
or tree_name.parent.type == 'trailer' \
and tree_name.parent.get_next_sibling() is not None:
s = '(' + replace_code + ')'
of_path = file_to_node_changes.setdefault(path, {})
n = tree_name
prefix = n.prefix
par = n.parent
if par.type == 'trailer' and par.children[0] == '.':
prefix = par.parent.children[0].prefix
n = par
for some_node in par.parent.children[:par.parent.children.index(par)]:
of_path[some_node] = ''
of_path[n] = prefix + s
path = definitions[0].get_root_context().py__file__()
changes = file_to_node_changes.setdefault(path, {})
changes[expr_stmt] = _remove_indent_of_prefix(expr_stmt.get_first_leaf().prefix)
next_leaf = expr_stmt.get_next_leaf()
# Most of the time we have to remove the newline at the end of the
# statement, but if there's a comment we might not need to.
if next_leaf.prefix.strip(' \t') == '' \
and (next_leaf.type == 'newline' or next_leaf == ';'):
changes[next_leaf] = ''
return Refactoring(inference_state, file_to_node_changes)
def _remove_indent_of_prefix(prefix):
r"""
Removes the last indentation of a prefix, e.g. " \n \n " becomes " \n \n".
"""
return ''.join(split_lines(prefix, keepends=True)[:-1])
+386
View File
@@ -0,0 +1,386 @@
from textwrap import dedent
from parso import split_lines
from jedi import debug
from jedi.api.exceptions import RefactoringError
from jedi.api.refactoring import Refactoring, EXPRESSION_PARTS
from jedi.common import indent_block
from jedi.parser_utils import function_is_classmethod, function_is_staticmethod
_DEFINITION_SCOPES = ('suite', 'file_input')
_VARIABLE_EXCTRACTABLE = EXPRESSION_PARTS + \
('atom testlist_star_expr testlist test lambdef lambdef_nocond '
'keyword name number string fstring').split()
def extract_variable(inference_state, path, module_node, name, pos, until_pos):
nodes = _find_nodes(module_node, pos, until_pos)
debug.dbg('Extracting nodes: %s', nodes)
is_expression, message = _is_expression_with_error(nodes)
if not is_expression:
raise RefactoringError(message)
generated_code = name + ' = ' + _expression_nodes_to_string(nodes)
file_to_node_changes = {path: _replace(nodes, name, generated_code, pos)}
return Refactoring(inference_state, file_to_node_changes)
def _is_expression_with_error(nodes):
"""
Returns a tuple (is_expression, error_string).
"""
if any(node.type == 'name' and node.is_definition() for node in nodes):
return False, 'Cannot extract a name that defines something'
if nodes[0].type not in _VARIABLE_EXCTRACTABLE:
return False, 'Cannot extract a "%s"' % nodes[0].type
return True, ''
def _find_nodes(module_node, pos, until_pos):
"""
Looks up a module and tries to find the appropriate amount of nodes that
are in there.
"""
start_node = module_node.get_leaf_for_position(pos, include_prefixes=True)
if until_pos is None:
if start_node.type == 'operator':
next_leaf = start_node.get_next_leaf()
if next_leaf is not None and next_leaf.start_pos == pos:
start_node = next_leaf
if _is_not_extractable_syntax(start_node):
start_node = start_node.parent
if start_node.parent.type == 'trailer':
start_node = start_node.parent.parent
while start_node.parent.type in EXPRESSION_PARTS:
start_node = start_node.parent
nodes = [start_node]
else:
# Get the next leaf if we are at the end of a leaf
if start_node.end_pos == pos:
next_leaf = start_node.get_next_leaf()
if next_leaf is not None:
start_node = next_leaf
# Some syntax is not exactable, just use its parent
if _is_not_extractable_syntax(start_node):
start_node = start_node.parent
# Find the end
end_leaf = module_node.get_leaf_for_position(until_pos, include_prefixes=True)
if end_leaf.start_pos > until_pos:
end_leaf = end_leaf.get_previous_leaf()
if end_leaf is None:
raise RefactoringError('Cannot extract anything from that')
parent_node = start_node
while parent_node.end_pos < end_leaf.end_pos:
parent_node = parent_node.parent
nodes = _remove_unwanted_expression_nodes(parent_node, pos, until_pos)
# If the user marks just a return statement, we return the expression
# instead of the whole statement, because the user obviously wants to
# extract that part.
if len(nodes) == 1 and start_node.type in ('return_stmt', 'yield_expr'):
return [nodes[0].children[1]]
return nodes
def _replace(nodes, expression_replacement, extracted, pos,
insert_before_leaf=None, remaining_prefix=None):
# Now try to replace the nodes found with a variable and move the code
# before the current statement.
definition = _get_parent_definition(nodes[0])
if insert_before_leaf is None:
insert_before_leaf = definition.get_first_leaf()
first_node_leaf = nodes[0].get_first_leaf()
lines = split_lines(insert_before_leaf.prefix, keepends=True)
if first_node_leaf is insert_before_leaf:
if remaining_prefix is not None:
# The remaining prefix has already been calculated.
lines[:-1] = remaining_prefix
lines[-1:-1] = [indent_block(extracted, lines[-1]) + '\n']
extracted_prefix = ''.join(lines)
replacement_dct = {}
if first_node_leaf is insert_before_leaf:
replacement_dct[nodes[0]] = extracted_prefix + expression_replacement
else:
if remaining_prefix is None:
p = first_node_leaf.prefix
else:
p = remaining_prefix + _get_indentation(nodes[0])
replacement_dct[nodes[0]] = p + expression_replacement
replacement_dct[insert_before_leaf] = extracted_prefix + insert_before_leaf.value
for node in nodes[1:]:
replacement_dct[node] = ''
return replacement_dct
def _expression_nodes_to_string(nodes):
return ''.join(n.get_code(include_prefix=i != 0) for i, n in enumerate(nodes))
def _suite_nodes_to_string(nodes, pos):
n = nodes[0]
prefix, part_of_code = _split_prefix_at(n.get_first_leaf(), pos[0] - 1)
code = part_of_code + n.get_code(include_prefix=False) \
+ ''.join(n.get_code() for n in nodes[1:])
return prefix, code
def _split_prefix_at(leaf, until_line):
"""
Returns a tuple of the leaf's prefix, split at the until_line
position.
"""
# second means the second returned part
second_line_count = leaf.start_pos[0] - until_line
lines = split_lines(leaf.prefix, keepends=True)
return ''.join(lines[:-second_line_count]), ''.join(lines[-second_line_count:])
def _get_indentation(node):
return split_lines(node.get_first_leaf().prefix)[-1]
def _get_parent_definition(node):
"""
Returns the statement where a node is defined.
"""
while node is not None:
if node.parent.type in _DEFINITION_SCOPES:
return node
node = node.parent
raise NotImplementedError('We should never even get here')
def _remove_unwanted_expression_nodes(parent_node, pos, until_pos):
"""
This function makes it so for `1 * 2 + 3` you can extract `2 + 3`, even
though it is not part of the expression.
"""
typ = parent_node.type
is_suite_part = typ in ('suite', 'file_input')
if typ in EXPRESSION_PARTS or is_suite_part:
nodes = parent_node.children
for i, n in enumerate(nodes):
if n.end_pos > pos:
start_index = i
if n.type == 'operator':
start_index -= 1
break
for i, n in reversed(list(enumerate(nodes))):
if n.start_pos < until_pos:
end_index = i
if n.type == 'operator':
end_index += 1
# Something like `not foo or bar` should not be cut after not
for n2 in nodes[i:]:
if _is_not_extractable_syntax(n2):
end_index += 1
else:
break
break
nodes = nodes[start_index:end_index + 1]
if not is_suite_part:
nodes[0:1] = _remove_unwanted_expression_nodes(nodes[0], pos, until_pos)
nodes[-1:] = _remove_unwanted_expression_nodes(nodes[-1], pos, until_pos)
return nodes
return [parent_node]
def _is_not_extractable_syntax(node):
return node.type == 'operator' \
or node.type == 'keyword' and node.value not in ('None', 'True', 'False')
def extract_function(inference_state, path, module_context, name, pos, until_pos):
nodes = _find_nodes(module_context.tree_node, pos, until_pos)
assert len(nodes)
is_expression, _ = _is_expression_with_error(nodes)
context = module_context.create_context(nodes[0])
is_bound_method = context.is_bound_method()
params, return_variables = list(_find_inputs_and_outputs(module_context, context, nodes))
# Find variables
# Is a class method / method
if context.is_module():
insert_before_leaf = None # Leaf will be determined later
else:
node = _get_code_insertion_node(context.tree_node, is_bound_method)
insert_before_leaf = node.get_first_leaf()
if is_expression:
code_block = 'return ' + _expression_nodes_to_string(nodes) + '\n'
remaining_prefix = None
has_ending_return_stmt = False
else:
has_ending_return_stmt = _is_node_ending_return_stmt(nodes[-1])
if not has_ending_return_stmt:
# Find the actually used variables (of the defined ones). If none are
# used (e.g. if the range covers the whole function), return the last
# defined variable.
return_variables = list(_find_needed_output_variables(
context,
nodes[0].parent,
nodes[-1].end_pos,
return_variables
)) or [return_variables[-1]] if return_variables else []
remaining_prefix, code_block = _suite_nodes_to_string(nodes, pos)
after_leaf = nodes[-1].get_next_leaf()
first, second = _split_prefix_at(after_leaf, until_pos[0])
code_block += first
code_block = dedent(code_block)
if not has_ending_return_stmt:
output_var_str = ', '.join(return_variables)
code_block += 'return ' + output_var_str + '\n'
# Check if we have to raise RefactoringError
_check_for_non_extractables(nodes[:-1] if has_ending_return_stmt else nodes)
decorator = ''
self_param = None
if is_bound_method:
if not function_is_staticmethod(context.tree_node):
function_param_names = context.get_value().get_param_names()
if len(function_param_names):
self_param = function_param_names[0].string_name
params = [p for p in params if p != self_param]
if function_is_classmethod(context.tree_node):
decorator = '@classmethod\n'
else:
code_block += '\n'
function_code = '%sdef %s(%s):\n%s' % (
decorator,
name,
', '.join(params if self_param is None else [self_param] + params),
indent_block(code_block)
)
function_call = '%s(%s)' % (
('' if self_param is None else self_param + '.') + name,
', '.join(params)
)
if is_expression:
replacement = function_call
else:
if has_ending_return_stmt:
replacement = 'return ' + function_call + '\n'
else:
replacement = output_var_str + ' = ' + function_call + '\n'
replacement_dct = _replace(nodes, replacement, function_code, pos,
insert_before_leaf, remaining_prefix)
if not is_expression:
replacement_dct[after_leaf] = second + after_leaf.value
file_to_node_changes = {path: replacement_dct}
return Refactoring(inference_state, file_to_node_changes)
def _check_for_non_extractables(nodes):
for n in nodes:
try:
children = n.children
except AttributeError:
if n.value == 'return':
raise RefactoringError(
'Can only extract return statements if they are at the end.')
if n.value == 'yield':
raise RefactoringError('Cannot extract yield statements.')
else:
_check_for_non_extractables(children)
def _is_name_input(module_context, names, first, last):
for name in names:
if name.api_type == 'param' or not name.parent_context.is_module():
if name.get_root_context() is not module_context:
return True
if name.start_pos is None or not (first <= name.start_pos < last):
return True
return False
def _find_inputs_and_outputs(module_context, context, nodes):
first = nodes[0].start_pos
last = nodes[-1].end_pos
inputs = []
outputs = []
for name in _find_non_global_names(nodes):
if name.is_definition():
if name not in outputs:
outputs.append(name.value)
else:
if name.value not in inputs:
name_definitions = context.goto(name, name.start_pos)
if not name_definitions \
or _is_name_input(module_context, name_definitions, first, last):
inputs.append(name.value)
# Check if outputs are really needed:
return inputs, outputs
def _find_non_global_names(nodes):
for node in nodes:
try:
children = node.children
except AttributeError:
if node.type == 'name':
yield node
else:
# We only want to check foo in foo.bar
if node.type == 'trailer' and node.children[0] == '.':
continue
yield from _find_non_global_names(children)
def _get_code_insertion_node(node, is_bound_method):
if not is_bound_method or function_is_staticmethod(node):
while node.parent.type != 'file_input':
node = node.parent
while node.parent.type in ('async_funcdef', 'decorated', 'async_stmt'):
node = node.parent
return node
def _find_needed_output_variables(context, search_node, at_least_pos, return_variables):
"""
Searches everything after at_least_pos in a node and checks if any of the
return_variables are used in there and returns those.
"""
for node in search_node.children:
if node.start_pos < at_least_pos:
continue
return_variables = set(return_variables)
for name in _find_non_global_names([node]):
if not name.is_definition() and name.value in return_variables:
return_variables.remove(name.value)
yield name.value
def _is_node_ending_return_stmt(node):
t = node.type
if t == 'simple_stmt':
return _is_node_ending_return_stmt(node.children[0])
return t == 'return_stmt'
+1 -1
View File
@@ -9,7 +9,7 @@ just use IPython instead::
Then you will be able to use Jedi completer in your Python interpreter:: Then you will be able to use Jedi completer in your Python interpreter::
$ python $ python
Python 2.7.2+ (default, Jul 20 2012, 22:15:08) Python 3.9.2+ (default, Jul 20 2020, 22:15:08)
[GCC 4.6.1] on linux2 [GCC 4.6.1] on linux2
Type "help", "copyright", "credits" or "license" for more information. Type "help", "copyright", "credits" or "license" for more information.
>>> import os >>> import os
+6 -8
View File
@@ -9,7 +9,6 @@ names in a module, but pretty much an arbitrary string.
""" """
import re import re
from jedi._compatibility import unicode
from jedi.inference.names import AbstractArbitraryName from jedi.inference.names import AbstractArbitraryName
from jedi.inference.helpers import infer_call_of_leaf from jedi.inference.helpers import infer_call_of_leaf
from jedi.api.classes import Completion from jedi.api.classes import Completion
@@ -19,7 +18,7 @@ _sentinel = object()
class StringName(AbstractArbitraryName): class StringName(AbstractArbitraryName):
api_type = u'string' api_type = 'string'
is_value_name = False is_value_name = False
@@ -65,7 +64,7 @@ def _completions_for_dicts(inference_state, dicts, literal_string, cut_end_quote
def _create_repr_string(literal_string, dict_key): def _create_repr_string(literal_string, dict_key):
if not isinstance(dict_key, (unicode, bytes)) or not literal_string: if not isinstance(dict_key, (str, bytes)) or not literal_string:
return repr(dict_key) return repr(dict_key)
r = repr(dict_key) r = repr(dict_key)
@@ -93,17 +92,16 @@ def _get_string_prefix_and_quote(string):
return match.group(1), match.group(2) return match.group(1), match.group(2)
def _get_string_quote(string):
return _get_string_prefix_and_quote(string)[1]
def _matches_quote_at_position(code_lines, quote, position): def _matches_quote_at_position(code_lines, quote, position):
string = code_lines[position[0] - 1][position[1]:position[1] + len(quote)] string = code_lines[position[0] - 1][position[1]:position[1] + len(quote)]
return string == quote return string == quote
def get_quote_ending(string, code_lines, position, invert_result=False): def get_quote_ending(string, code_lines, position, invert_result=False):
quote = _get_string_quote(string) _, quote = _get_string_prefix_and_quote(string)
if quote is None:
return ''
# Add a quote only if it's not already there. # Add a quote only if it's not already there.
if _matches_quote_at_position(code_lines, quote, position) != invert_result: if _matches_quote_at_position(code_lines, quote, position) != invert_result:
return '' return ''
+3 -2
View File
@@ -13,14 +13,15 @@ these variables are being cleaned after every API usage.
""" """
import time import time
from functools import wraps from functools import wraps
from typing import Any, Dict, Tuple
from jedi import settings from jedi import settings
from parso.cache import parser_cache from parso.cache import parser_cache
_time_caches = {} _time_caches: Dict[str, Dict[Any, Tuple[float, Any]]] = {}
def clear_time_caches(delete_all=False): def clear_time_caches(delete_all: bool = False) -> None:
""" Jedi caches many things, that should be completed after each completion """ Jedi caches many things, that should be completed after each completion
finishes. finishes.
+10 -12
View File
@@ -1,18 +1,6 @@
import os
from contextlib import contextmanager from contextlib import contextmanager
def traverse_parents(path, include_current=False):
if not include_current:
path = os.path.dirname(path)
previous = None
while previous != path:
yield path
previous = path
path = os.path.dirname(path)
@contextmanager @contextmanager
def monkeypatch(obj, attribute_name, new_value): def monkeypatch(obj, attribute_name, new_value):
""" """
@@ -24,3 +12,13 @@ def monkeypatch(obj, attribute_name, new_value):
yield yield
finally: finally:
setattr(obj, attribute_name, old_value) setattr(obj, attribute_name, old_value)
def indent_block(text, indention=' '):
"""This function indents a text block with a default of four spaces."""
temp = ''
while text and text[-1] == '\n':
temp += text[-1]
text = text[:-1]
lines = text.split('\n')
return '\n'.join(map(lambda s: indention + s, lines)) + temp
-1
View File
@@ -1 +0,0 @@
from jedi.common.value import BaseValueSet, BaseValue
-76
View File
@@ -1,76 +0,0 @@
class BaseValue(object):
def __init__(self, inference_state, parent_context=None):
self.inference_state = inference_state
self.parent_context = parent_context
def get_root_context(self):
value = self
while True:
if value.parent_context is None:
return value
value = value.parent_context
class BaseValueSet(object):
def __init__(self, iterable):
self._set = frozenset(iterable)
for value in iterable:
assert not isinstance(value, BaseValueSet)
@classmethod
def _from_frozen_set(cls, frozenset_):
self = cls.__new__(cls)
self._set = frozenset_
return self
@classmethod
def from_sets(cls, sets):
"""
Used to work with an iterable of set.
"""
aggregated = set()
for set_ in sets:
if isinstance(set_, BaseValueSet):
aggregated |= set_._set
else:
aggregated |= frozenset(set_)
return cls._from_frozen_set(frozenset(aggregated))
def __or__(self, other):
return self._from_frozen_set(self._set | other._set)
def __and__(self, other):
return self._from_frozen_set(self._set & other._set)
def __iter__(self):
for element in self._set:
yield element
def __bool__(self):
return bool(self._set)
def __len__(self):
return len(self._set)
def __repr__(self):
return 'S{%s}' % (', '.join(str(s) for s in self._set))
def filter(self, filter_func):
return self.__class__(filter(filter_func, self._set))
def __getattr__(self, name):
def mapper(*args, **kwargs):
return self.from_sets(
getattr(value, name)(*args, **kwargs)
for value in self._set
)
return mapper
def __eq__(self, other):
return self._set == other._set
def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
return hash(self._set)
+10 -18
View File
@@ -1,8 +1,7 @@
import os import os
import time import time
from contextlib import contextmanager from contextlib import contextmanager
from typing import Callable, Optional
from jedi._compatibility import encoding, is_py3, u
_inited = False _inited = False
@@ -22,7 +21,7 @@ try:
raise ImportError raise ImportError
else: else:
# Use colorama for nicer console output. # Use colorama for nicer console output.
from colorama import Fore, init from colorama import Fore, init # type: ignore[import]
from colorama import initialise from colorama import initialise
def _lazy_colorama_init(): # noqa: F811 def _lazy_colorama_init(): # noqa: F811
@@ -47,7 +46,7 @@ try:
_inited = True _inited = True
except ImportError: except ImportError:
class Fore(object): class Fore: # type: ignore[no-redef]
RED = '' RED = ''
GREEN = '' GREEN = ''
YELLOW = '' YELLOW = ''
@@ -64,7 +63,7 @@ enable_warning = False
enable_notice = False enable_notice = False
# callback, interface: level, str # callback, interface: level, str
debug_function = None debug_function: Optional[Callable[[str, str], None]] = None
_debug_indent = 0 _debug_indent = 0
_start_time = time.time() _start_time = time.time()
@@ -84,29 +83,27 @@ def increase_indent(func):
@contextmanager @contextmanager
def increase_indent_cm(title=None): def increase_indent_cm(title=None, color='MAGENTA'):
global _debug_indent global _debug_indent
if title: if title:
dbg('Start: ' + title, color='MAGENTA') dbg('Start: ' + title, color=color)
_debug_indent += 1 _debug_indent += 1
try: try:
yield yield
finally: finally:
_debug_indent -= 1 _debug_indent -= 1
if title: if title:
dbg('End: ' + title, color='MAGENTA') dbg('End: ' + title, color=color)
def dbg(message, *args, **kwargs): def dbg(message, *args, color='GREEN'):
""" Looks at the stack, to see if a debug message should be printed. """ """ Looks at the stack, to see if a debug message should be printed. """
# Python 2 compatibility, because it doesn't understand default args
color = kwargs.pop('color', 'GREEN')
assert color assert color
if debug_function and enable_notice: if debug_function and enable_notice:
i = ' ' * _debug_indent i = ' ' * _debug_indent
_lazy_colorama_init() _lazy_colorama_init()
debug_function(color, i + 'dbg: ' + message % tuple(u(repr(a)) for a in args)) debug_function(color, i + 'dbg: ' + message % tuple(repr(a) for a in args))
def warning(message, *args, **kwargs): def warning(message, *args, **kwargs):
@@ -116,7 +113,7 @@ def warning(message, *args, **kwargs):
if debug_function and enable_warning: if debug_function and enable_warning:
i = ' ' * _debug_indent i = ' ' * _debug_indent
if format: if format:
message = message % tuple(u(repr(a)) for a in args) message = message % tuple(repr(a) for a in args)
debug_function('RED', i + 'warning: ' + message) debug_function('RED', i + 'warning: ' + message)
@@ -135,9 +132,4 @@ def print_to_stdout(color, str_out):
""" """
col = getattr(Fore, color) col = getattr(Fore, color)
_lazy_colorama_init() _lazy_colorama_init()
if not is_py3:
str_out = str_out.encode(encoding, 'replace')
print(col + str_out + Fore.RESET) print(col + str_out + Fore.RESET)
# debug_function = print_to_stdout
+7 -4
View File
@@ -3,10 +3,13 @@ import os
from parso import file_io from parso import file_io
class AbstractFolderIO(object): class AbstractFolderIO:
def __init__(self, path): def __init__(self, path):
self.path = path self.path = path
def get_base_name(self):
raise NotImplementedError
def list(self): def list(self):
raise NotImplementedError raise NotImplementedError
@@ -54,7 +57,7 @@ class FolderIO(AbstractFolderIO):
del dirs[i] del dirs[i]
class FileIOFolderMixin(object): class FileIOFolderMixin:
def get_parent_folder(self): def get_parent_folder(self):
return FolderIO(os.path.dirname(self.path)) return FolderIO(os.path.dirname(self.path))
@@ -62,13 +65,13 @@ class FileIOFolderMixin(object):
class ZipFileIO(file_io.KnownContentFileIO, FileIOFolderMixin): class ZipFileIO(file_io.KnownContentFileIO, FileIOFolderMixin):
"""For .zip and .egg archives""" """For .zip and .egg archives"""
def __init__(self, path, code, zip_path): def __init__(self, path, code, zip_path):
super(ZipFileIO, self).__init__(path, code) super().__init__(path, code)
self._zip_path = zip_path self._zip_path = zip_path
def get_last_modified(self): def get_last_modified(self):
try: try:
return os.path.getmtime(self._zip_path) return os.path.getmtime(self._zip_path)
except OSError: # Python 3 would probably only need FileNotFoundError except (FileNotFoundError, PermissionError, NotADirectoryError):
return None return None
+13 -11
View File
@@ -63,7 +63,6 @@ only *inferes* what needs to be *inferred*. All the statements and modules
that are not used are just being ignored. that are not used are just being ignored.
""" """
import parso import parso
from parso import python_bytes_to_unicode
from jedi.file_io import FileIO from jedi.file_io import FileIO
from jedi import debug from jedi import debug
@@ -82,7 +81,7 @@ from jedi.inference.imports import follow_error_node_imports_if_possible
from jedi.plugins import plugin_manager from jedi.plugins import plugin_manager
class InferenceState(object): class InferenceState:
def __init__(self, project, environment=None, script_path=None): def __init__(self, project, environment=None, script_path=None):
if environment is None: if environment is None:
environment = project.get_environment() environment = project.get_environment()
@@ -121,19 +120,18 @@ class InferenceState(object):
debug.dbg('execute result: %s in %s', value_set, value) debug.dbg('execute result: %s in %s', value_set, value)
return value_set return value_set
@property # mypy doesn't suppport decorated propeties (https://github.com/python/mypy/issues/1362)
@property # type: ignore[misc]
@inference_state_function_cache() @inference_state_function_cache()
def builtins_module(self): def builtins_module(self):
module_name = u'builtins' module_name = 'builtins'
if self.environment.version_info.major == 2:
module_name = u'__builtin__'
builtins_module, = self.import_module((module_name,), sys_path=()) builtins_module, = self.import_module((module_name,), sys_path=())
return builtins_module return builtins_module
@property @property # type: ignore[misc]
@inference_state_function_cache() @inference_state_function_cache()
def typing_module(self): def typing_module(self):
typing_module, = self.import_module((u'typing',)) typing_module, = self.import_module(('typing',))
return typing_module return typing_module
def reset_recursion_limitations(self): def reset_recursion_limitations(self):
@@ -142,7 +140,7 @@ class InferenceState(object):
def get_sys_path(self, **kwargs): def get_sys_path(self, **kwargs):
"""Convenience function""" """Convenience function"""
return self.project._get_sys_path(self, environment=self.environment, **kwargs) return self.project._get_sys_path(self, **kwargs)
def infer(self, context, name): def infer(self, context, name):
def_ = name.get_definition(import_name_always=True) def_ = name.get_definition(import_name_always=True)
@@ -172,6 +170,8 @@ class InferenceState(object):
return tree_name_to_values(self, context, name) return tree_name_to_values(self, context, name)
elif type_ == 'param': elif type_ == 'param':
return context.py__getattribute__(name.value, position=name.end_pos) return context.py__getattribute__(name.value, position=name.end_pos)
elif type_ == 'namedexpr_test':
return context.infer_node(def_)
else: else:
result = follow_error_node_imports_if_possible(context, name) result = follow_error_node_imports_if_possible(context, name)
if result is not None: if result is not None:
@@ -179,14 +179,16 @@ class InferenceState(object):
return helpers.infer_call_of_leaf(context, name) return helpers.infer_call_of_leaf(context, name)
def parse_and_get_code(self, code=None, path=None, encoding='utf-8', def parse_and_get_code(self, code=None, path=None,
use_latest_grammar=False, file_io=None, **kwargs): use_latest_grammar=False, file_io=None, **kwargs):
if path is not None:
path = str(path)
if code is None: if code is None:
if file_io is None: if file_io is None:
file_io = FileIO(path) file_io = FileIO(path)
code = file_io.read() code = file_io.read()
# We cannot just use parso, because it doesn't use errors='replace'. # We cannot just use parso, because it doesn't use errors='replace'.
code = python_bytes_to_unicode(code, encoding=encoding, errors='replace') code = parso.python_bytes_to_unicode(code, encoding='utf-8', errors='replace')
if len(code) > settings._cropped_file_size: if len(code) > settings._cropped_file_size:
code = code[:settings._cropped_file_size] code = code[:settings._cropped_file_size]
+3 -7
View File
@@ -3,7 +3,6 @@ Module for statical analysis.
""" """
from parso.python import tree from parso.python import tree
from jedi._compatibility import force_unicode
from jedi import debug from jedi import debug
from jedi.inference.helpers import is_string from jedi.inference.helpers import is_string
@@ -27,7 +26,7 @@ CODES = {
} }
class Error(object): class Error:
def __init__(self, name, module_path, start_pos, message=None): def __init__(self, name, module_path, start_pos, message=None):
self.path = module_path self.path = module_path
self._start_pos = start_pos self._start_pos = start_pos
@@ -50,13 +49,10 @@ class Error(object):
first = self.__class__.__name__[0] first = self.__class__.__name__[0]
return first + str(CODES[self.name][0]) return first + str(CODES[self.name][0])
def __unicode__(self): def __str__(self):
return '%s:%s:%s: %s %s' % (self.path, self.line, self.column, return '%s:%s:%s: %s %s' % (self.path, self.line, self.column,
self.code, self.message) self.code, self.message)
def __str__(self):
return self.__unicode__()
def __eq__(self, other): def __eq__(self, other):
return (self.path == other.path and self.name == other.name return (self.path == other.path and self.name == other.name
and self._start_pos == other._start_pos) and self._start_pos == other._start_pos)
@@ -193,7 +189,7 @@ def _check_for_exception_catch(node_context, jedi_name, exception, payload=None)
key, lazy_value = unpacked_args[1] key, lazy_value = unpacked_args[1]
names = list(lazy_value.infer()) names = list(lazy_value.infer())
assert len(names) == 1 and is_string(names[0]) assert len(names) == 1 and is_string(names[0])
assert force_unicode(names[0].get_safe_value()) == payload[1].value assert names[0].get_safe_value() == payload[1].value
# Check objects # Check objects
key, lazy_value = unpacked_args[0] key, lazy_value = unpacked_args[0]
+21 -42
View File
@@ -1,8 +1,8 @@
import re import re
from itertools import zip_longest
from parso.python import tree from parso.python import tree
from jedi._compatibility import zip_longest
from jedi import debug from jedi import debug
from jedi.inference.utils import PushBackIterator from jedi.inference.utils import PushBackIterator
from jedi.inference import analysis from jedi.inference import analysis
@@ -35,7 +35,7 @@ class ParamIssue(Exception):
pass pass
def repack_with_argument_clinic(string, keep_arguments_param=False, keep_callback_param=False): def repack_with_argument_clinic(clinic_string):
""" """
Transforms a function or method with arguments to the signature that is Transforms a function or method with arguments to the signature that is
given as an argument clinic notation. given as an argument clinic notation.
@@ -46,35 +46,29 @@ def repack_with_argument_clinic(string, keep_arguments_param=False, keep_callbac
str.split.__text_signature__ str.split.__text_signature__
# Results in: '($self, /, sep=None, maxsplit=-1)' # Results in: '($self, /, sep=None, maxsplit=-1)'
""" """
clinic_args = list(_parse_argument_clinic(string))
def decorator(func): def decorator(func):
def wrapper(context, *args, **kwargs): def wrapper(value, arguments):
if keep_arguments_param:
arguments = kwargs['arguments']
else:
arguments = kwargs.pop('arguments')
if not keep_arguments_param:
kwargs.pop('callback', None)
try: try:
args += tuple(_iterate_argument_clinic( args = tuple(iterate_argument_clinic(
context.inference_state, value.inference_state,
arguments, arguments,
clinic_args clinic_string,
)) ))
except ParamIssue: except ParamIssue:
return NO_VALUES return NO_VALUES
else: else:
return func(context, *args, **kwargs) return func(value, *args)
return wrapper return wrapper
return decorator return decorator
def _iterate_argument_clinic(inference_state, arguments, parameters): def iterate_argument_clinic(inference_state, arguments, clinic_string):
"""Uses a list with argument clinic information (see PEP 436).""" """Uses a list with argument clinic information (see PEP 436)."""
clinic_args = list(_parse_argument_clinic(clinic_string))
iterator = PushBackIterator(arguments.unpack()) iterator = PushBackIterator(arguments.unpack())
for i, (name, optional, allow_kwargs, stars) in enumerate(parameters): for i, (name, optional, allow_kwargs, stars) in enumerate(clinic_args):
if stars == 1: if stars == 1:
lazy_values = [] lazy_values = []
for key, argument in iterator: for key, argument in iterator:
@@ -94,7 +88,7 @@ def _iterate_argument_clinic(inference_state, arguments, parameters):
raise ParamIssue raise ParamIssue
if argument is None and not optional: if argument is None and not optional:
debug.warning('TypeError: %s expected at least %s arguments, got %s', debug.warning('TypeError: %s expected at least %s arguments, got %s',
name, len(parameters), i) name, len(clinic_args), i)
raise ParamIssue raise ParamIssue
value_set = NO_VALUES if argument is None else argument.infer() value_set = NO_VALUES if argument is None else argument.infer()
@@ -130,16 +124,7 @@ def _parse_argument_clinic(string):
allow_kwargs = True allow_kwargs = True
class _AbstractArgumentsMixin(object): class _AbstractArgumentsMixin:
def infer_all(self, funcdef=None):
"""
Inferes all arguments as a support for static analysis
(normally Jedi).
"""
for key, lazy_value in self.unpack():
types = lazy_value.infer()
try_iter_content(types)
def unpack(self, funcdef=None): def unpack(self, funcdef=None):
raise NotImplementedError raise NotImplementedError
@@ -157,11 +142,8 @@ def unpack_arglist(arglist):
if arglist is None: if arglist is None:
return return
# Allow testlist here as well for Python2's class inheritance if arglist.type != 'arglist' and not (
# definitions. arglist.type == 'argument' and arglist.children[0] in ('*', '**')):
if not (arglist.type in ('arglist', 'testlist') or (
# in python 3.5 **arg is an argument, not arglist
arglist.type == 'argument' and arglist.children[0] in ('*', '**'))):
yield 0, arglist yield 0, arglist
return return
@@ -170,7 +152,9 @@ def unpack_arglist(arglist):
if child == ',': if child == ',':
continue continue
elif child in ('*', '**'): elif child in ('*', '**'):
yield len(child.value), next(iterator) c = next(iterator, None)
assert c is not None
yield len(child.value), c
elif child.type == 'argument' and \ elif child.type == 'argument' and \
child.children[0] in ('*', '**'): child.children[0] in ('*', '**'):
assert len(child.children) == 2 assert len(child.children) == 2
@@ -202,16 +186,13 @@ class TreeArguments(AbstractArguments):
iterators = [_iterate_star_args(self.context, a, el, funcdef) iterators = [_iterate_star_args(self.context, a, el, funcdef)
for a in arrays] for a in arrays]
for values in list(zip_longest(*iterators)): for values in list(zip_longest(*iterators)):
# TODO zip_longest yields None, that means this would raise
# an exception?
yield None, get_merged_lazy_value( yield None, get_merged_lazy_value(
[v for v in values if v is not None] [v for v in values if v is not None]
) )
elif star_count == 2: elif star_count == 2:
arrays = self.context.infer_node(el) arrays = self.context.infer_node(el)
for dct in arrays: for dct in arrays:
for key, values in _star_star_dict(self.context, dct, el, funcdef): yield from _star_star_dict(self.context, dct, el, funcdef)
yield key, values
else: else:
if el.type == 'argument': if el.type == 'argument':
c = el.children c = el.children
@@ -234,8 +215,7 @@ class TreeArguments(AbstractArguments):
# Reordering arguments is necessary, because star args sometimes appear # Reordering arguments is necessary, because star args sometimes appear
# after named argument, but in the actual order it's prepended. # after named argument, but in the actual order it's prepended.
for named_arg in named_args: yield from named_args
yield named_arg
def _as_tree_tuple_objects(self): def _as_tree_tuple_objects(self):
for star_count, argument in unpack_arglist(self.argument_node): for star_count, argument in unpack_arglist(self.argument_node):
@@ -336,8 +316,7 @@ def _iterate_star_args(context, array, input_node, funcdef=None):
except AttributeError: except AttributeError:
pass pass
else: else:
for lazy_value in iter_(): yield from iter_()
yield lazy_value
def _star_star_dict(context, array, input_node, funcdef): def _star_star_dict(context, array, input_node, funcdef):
+158 -29
View File
@@ -8,12 +8,12 @@ just one.
""" """
from functools import reduce from functools import reduce
from operator import add from operator import add
from itertools import zip_longest
from parso.python.tree import Name from parso.python.tree import Name
from jedi import debug from jedi import debug
from jedi._compatibility import zip_longest, unicode
from jedi.parser_utils import clean_scope_docstring from jedi.parser_utils import clean_scope_docstring
from jedi.common import BaseValueSet, BaseValue
from jedi.inference.helpers import SimpleGetItemNotFound from jedi.inference.helpers import SimpleGetItemNotFound
from jedi.inference.utils import safe_property from jedi.inference.utils import safe_property
from jedi.inference.cache import inference_state_as_method_param_cache from jedi.inference.cache import inference_state_as_method_param_cache
@@ -22,7 +22,7 @@ from jedi.cache import memoize_method
sentinel = object() sentinel = object()
class HelperValueMixin(object): class HelperValueMixin:
def get_root_context(self): def get_root_context(self):
value = self value = self
if value.parent_context is None: if value.parent_context is None:
@@ -33,11 +33,6 @@ class HelperValueMixin(object):
return value return value
value = value.parent_context value = value.parent_context
@classmethod
@inference_state_as_method_param_cache()
def create_cached(cls, *args, **kwargs):
return cls(*args, **kwargs)
def execute(self, arguments): def execute(self, arguments):
return self.inference_state.execute(self, arguments=arguments) return self.inference_state.execute(self, arguments=arguments)
@@ -60,18 +55,14 @@ class HelperValueMixin(object):
def _get_value_filters(self, name_or_str): def _get_value_filters(self, name_or_str):
origin_scope = name_or_str if isinstance(name_or_str, Name) else None origin_scope = name_or_str if isinstance(name_or_str, Name) else None
for f in self.get_filters(origin_scope=origin_scope): yield from self.get_filters(origin_scope=origin_scope)
yield f
# This covers the case where a stub files are incomplete. # This covers the case where a stub files are incomplete.
if self.is_stub(): if self.is_stub():
from jedi.inference.gradual.conversion import convert_values from jedi.inference.gradual.conversion import convert_values
for c in convert_values(ValueSet({self})): for c in convert_values(ValueSet({self})):
for f in c.get_filters(): yield from c.get_filters()
yield f
def goto(self, name_or_str, name_context=None, analysis_errors=True): def goto(self, name_or_str, name_context=None, analysis_errors=True):
if name_context is None:
name_context = self
from jedi.inference import finder from jedi.inference import finder
filters = self._get_value_filters(name_or_str) filters = self._get_value_filters(name_or_str)
names = finder.filter_name(filters, name_or_str) names = finder.filter_name(filters, name_or_str)
@@ -100,11 +91,14 @@ class HelperValueMixin(object):
return values return values
def py__await__(self): def py__await__(self):
await_value_set = self.py__getattribute__(u"__await__") await_value_set = self.py__getattribute__("__await__")
if not await_value_set: if not await_value_set:
debug.warning('Tried to run __await__ on value %s', self) debug.warning('Tried to run __await__ on value %s', self)
return await_value_set.execute_with_values() return await_value_set.execute_with_values()
def py__name__(self):
return self.name.string_name
def iterate(self, contextualized_node=None, is_async=False): def iterate(self, contextualized_node=None, is_async=False):
debug.dbg('iterate %s', self) debug.dbg('iterate %s', self)
if is_async: if is_async:
@@ -117,14 +111,18 @@ class HelperValueMixin(object):
.py__getattribute__('__anext__').execute_with_values() .py__getattribute__('__anext__').execute_with_values()
.py__getattribute__('__await__').execute_with_values() .py__getattribute__('__await__').execute_with_values()
.py__stop_iteration_returns() .py__stop_iteration_returns()
) # noqa ) # noqa: E124
]) ])
return self.py__iter__(contextualized_node) return self.py__iter__(contextualized_node)
def is_sub_class_of(self, class_value): def is_sub_class_of(self, class_value):
with debug.increase_indent_cm('subclass matching of %s <=> %s' % (self, class_value),
color='BLUE'):
for cls in self.py__mro__(): for cls in self.py__mro__():
if cls.is_same_class(class_value): if cls.is_same_class(class_value):
debug.dbg('matched subclass True', color='BLUE')
return True return True
debug.dbg('matched subclass False', color='BLUE')
return False return False
def is_same_class(self, class2): def is_same_class(self, class2):
@@ -138,7 +136,7 @@ class HelperValueMixin(object):
return self._as_context(*args, **kwargs) return self._as_context(*args, **kwargs)
class Value(HelperValueMixin, BaseValue): class Value(HelperValueMixin):
""" """
To be implemented by subclasses. To be implemented by subclasses.
""" """
@@ -146,12 +144,11 @@ class Value(HelperValueMixin, BaseValue):
# Possible values: None, tuple, list, dict and set. Here to deal with these # Possible values: None, tuple, list, dict and set. Here to deal with these
# very important containers. # very important containers.
array_type = None array_type = None
api_type = 'not_defined_please_report_bug'
@property def __init__(self, inference_state, parent_context=None):
def api_type(self): self.inference_state = inference_state
# By default just lower name of the class. Can and should be self.parent_context = parent_context
# overwritten.
return self.__class__.__name__.lower()
def py__getitem__(self, index_value_set, contextualized_node): def py__getitem__(self, index_value_set, contextualized_node):
from jedi.inference import analysis from jedi.inference import analysis
@@ -177,12 +174,18 @@ class Value(HelperValueMixin, BaseValue):
message="TypeError: '%s' object is not iterable" % self) message="TypeError: '%s' object is not iterable" % self)
return iter([]) return iter([])
def py__next__(self, contextualized_node=None):
return self.py__iter__(contextualized_node)
def get_signatures(self): def get_signatures(self):
return [] return []
def is_class(self): def is_class(self):
return False return False
def is_class_mixin(self):
return False
def is_instance(self): def is_instance(self):
return False return False
@@ -218,7 +221,6 @@ class Value(HelperValueMixin, BaseValue):
return '' return ''
else: else:
return clean_scope_docstring(self.tree_node) return clean_scope_docstring(self.tree_node)
return None
def get_safe_value(self, default=sentinel): def get_safe_value(self, default=sentinel):
if default is sentinel: if default is sentinel:
@@ -247,6 +249,9 @@ class Value(HelperValueMixin, BaseValue):
debug.warning("No __get__ defined on %s", self) debug.warning("No __get__ defined on %s", self)
return ValueSet([self]) return ValueSet([self])
def py__get__on_class(self, calling_instance, instance, class_value):
return NotImplemented
def get_qualified_names(self): def get_qualified_names(self):
# Returns Optional[Tuple[str, ...]] # Returns Optional[Tuple[str, ...]]
return None return None
@@ -258,11 +263,41 @@ class Value(HelperValueMixin, BaseValue):
def _as_context(self): def _as_context(self):
raise NotImplementedError('Not all values need to be converted to contexts: %s', self) raise NotImplementedError('Not all values need to be converted to contexts: %s', self)
@property
def name(self): def name(self):
raise NotImplementedError raise NotImplementedError
def py__name__(self): def get_type_hint(self, add_class_info=True):
return self.name.string_name return None
def infer_type_vars(self, value_set):
"""
When the current instance represents a type annotation, this method
tries to find information about undefined type vars and returns a dict
from type var name to value set.
This is for example important to understand what `iter([1])` returns.
According to typeshed, `iter` returns an `Iterator[_T]`:
def iter(iterable: Iterable[_T]) -> Iterator[_T]: ...
This functions would generate `int` for `_T` in this case, because it
unpacks the `Iterable`.
Parameters
----------
`self`: represents the annotation of the current parameter to infer the
value for. In the above example, this would initially be the
`Iterable[_T]` of the `iterable` parameter and then, when recursing,
just the `_T` generic parameter.
`value_set`: represents the actual argument passed to the parameter
we're inferrined for, or (for recursive calls) their types. In the
above example this would first be the representation of the list
`[1]` and then, when recursing, just of `1`.
"""
return {}
def iterate_values(values, contextualized_node=None, is_async=False): def iterate_values(values, contextualized_node=None, is_async=False):
@@ -321,14 +356,14 @@ class ValueWrapper(_ValueWrapperBase):
class TreeValue(Value): class TreeValue(Value):
def __init__(self, inference_state, parent_context, tree_node): def __init__(self, inference_state, parent_context, tree_node):
super(TreeValue, self).__init__(inference_state, parent_context) super().__init__(inference_state, parent_context)
self.tree_node = tree_node self.tree_node = tree_node
def __repr__(self): def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self.tree_node) return '<%s: %s>' % (self.__class__.__name__, self.tree_node)
class ContextualizedNode(object): class ContextualizedNode:
def __init__(self, context, node): def __init__(self, context, node):
self.context = context self.context = context
self.node = node self.node = node
@@ -349,7 +384,7 @@ def _getitem(value, index_values, contextualized_node):
unused_values = set() unused_values = set()
for index_value in index_values: for index_value in index_values:
index = index_value.get_safe_value(default=None) index = index_value.get_safe_value(default=None)
if type(index) in (float, int, str, unicode, slice, bytes): if type(index) in (float, int, str, slice, bytes):
try: try:
result |= value.py__simple_getitem__(index) result |= value.py__simple_getitem__(index)
continue continue
@@ -370,7 +405,69 @@ def _getitem(value, index_values, contextualized_node):
return result return result
class ValueSet(BaseValueSet): class ValueSet:
def __init__(self, iterable):
self._set = frozenset(iterable)
for value in iterable:
assert not isinstance(value, ValueSet)
@classmethod
def _from_frozen_set(cls, frozenset_):
self = cls.__new__(cls)
self._set = frozenset_
return self
@classmethod
def from_sets(cls, sets):
"""
Used to work with an iterable of set.
"""
aggregated = set()
for set_ in sets:
if isinstance(set_, ValueSet):
aggregated |= set_._set
else:
aggregated |= frozenset(set_)
return cls._from_frozen_set(frozenset(aggregated))
def __or__(self, other):
return self._from_frozen_set(self._set | other._set)
def __and__(self, other):
return self._from_frozen_set(self._set & other._set)
def __iter__(self):
return iter(self._set)
def __bool__(self):
return bool(self._set)
def __len__(self):
return len(self._set)
def __repr__(self):
return 'S{%s}' % (', '.join(str(s) for s in self._set))
def filter(self, filter_func):
return self.__class__(filter(filter_func, self._set))
def __getattr__(self, name):
def mapper(*args, **kwargs):
return self.from_sets(
getattr(value, name)(*args, **kwargs)
for value in self._set
)
return mapper
def __eq__(self, other):
return self._set == other._set
def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
return hash(self._set)
def py__class__(self): def py__class__(self):
return ValueSet(c.py__class__() for c in self._set) return ValueSet(c.py__class__() for c in self._set)
@@ -414,6 +511,38 @@ class ValueSet(BaseValueSet):
def get_signatures(self): def get_signatures(self):
return [sig for c in self._set for sig in c.get_signatures()] return [sig for c in self._set for sig in c.get_signatures()]
def get_type_hint(self, add_class_info=True):
t = [v.get_type_hint(add_class_info=add_class_info) for v in self._set]
type_hints = sorted(filter(None, t))
if len(type_hints) == 1:
return type_hints[0]
optional = 'None' in type_hints
if optional:
type_hints.remove('None')
if len(type_hints) == 0:
return None
elif len(type_hints) == 1:
s = type_hints[0]
else:
s = 'Union[%s]' % ', '.join(type_hints)
if optional:
s = 'Optional[%s]' % s
return s
def infer_type_vars(self, value_set):
# Circular
from jedi.inference.gradual.annotation import merge_type_var_dicts
type_var_dict = {}
for value in self._set:
merge_type_var_dicts(
type_var_dict,
value.infer_type_vars(value_set),
)
return type_var_dict
NO_VALUES = ValueSet([]) NO_VALUES = ValueSet([])
+3 -1
View File
@@ -3,6 +3,7 @@
default otherwise. default otherwise.
- ``CachedMetaClass`` uses ``_memoize_default`` to do the same with classes. - ``CachedMetaClass`` uses ``_memoize_default`` to do the same with classes.
""" """
from functools import wraps
from jedi import debug from jedi import debug
@@ -77,7 +78,7 @@ class CachedMetaClass(type):
""" """
@inference_state_as_method_param_cache() @inference_state_as_method_param_cache()
def __call__(self, *args, **kwargs): def __call__(self, *args, **kwargs):
return super(CachedMetaClass, self).__call__(*args, **kwargs) return super().__call__(*args, **kwargs)
def inference_state_method_generator_cache(): def inference_state_method_generator_cache():
@@ -86,6 +87,7 @@ def inference_state_method_generator_cache():
recursion errors and returns no further iterator elemends in that case. recursion errors and returns no further iterator elemends in that case.
""" """
def func(function): def func(function):
@wraps(function)
def wrapper(obj, *args, **kwargs): def wrapper(obj, *args, **kwargs):
cache = obj.inference_state.memoize_cache cache = obj.inference_state.memoize_cache
try: try:
+6 -4
View File
@@ -1,4 +1,6 @@
from jedi._compatibility import unicode # This file also re-exports symbols for wider use. We configure mypy and flake8
# to be aware that this file does this.
from jedi.inference.compiled.value import CompiledValue, CompiledName, \ from jedi.inference.compiled.value import CompiledValue, CompiledName, \
CompiledValueFilter, CompiledValueName, create_from_access_path CompiledValueFilter, CompiledValueName, create_from_access_path
from jedi.inference.base_value import LazyValueWrapper from jedi.inference.base_value import LazyValueWrapper
@@ -29,7 +31,7 @@ class ExactValue(LazyValueWrapper):
if name in ('get_safe_value', 'execute_operation', 'access_handle', if name in ('get_safe_value', 'execute_operation', 'access_handle',
'negate', 'py__bool__', 'is_compiled'): 'negate', 'py__bool__', 'is_compiled'):
return getattr(self._compiled_value, name) return getattr(self._compiled_value, name)
return super(ExactValue, self).__getattribute__(name) return super().__getattribute__(name)
def _get_wrapped_value(self): def _get_wrapped_value(self):
instance, = builtin_from_name( instance, = builtin_from_name(
@@ -45,7 +47,7 @@ def create_simple_object(inference_state, obj):
Only allows creations of objects that are easily picklable across Python Only allows creations of objects that are easily picklable across Python
versions. versions.
""" """
assert type(obj) in (int, float, str, bytes, unicode, slice, complex, bool), obj assert type(obj) in (int, float, str, bytes, slice, complex, bool), repr(obj)
compiled_value = create_from_access_path( compiled_value = create_from_access_path(
inference_state, inference_state,
inference_state.compiled_subprocess.create_simple_object(obj) inference_state.compiled_subprocess.create_simple_object(obj)
@@ -54,7 +56,7 @@ def create_simple_object(inference_state, obj):
def get_string_value_set(inference_state): def get_string_value_set(inference_state):
return builtin_from_name(inference_state, u'str').execute_with_values() return builtin_from_name(inference_state, 'str').execute_with_values()
def load_module(inference_state, dotted_name, **kwargs): def load_module(inference_state, dotted_name, **kwargs):
+35 -73
View File
@@ -1,17 +1,17 @@
from __future__ import print_function
import inspect import inspect
import types import types
import traceback
import sys import sys
import operator as op import operator as op
from collections import namedtuple from collections import namedtuple
import warnings import warnings
import re import re
import builtins
import typing
from jedi._compatibility import unicode, is_py3, builtins, \
py_version, force_unicode
from jedi.inference.compiled.getattr_static import getattr_static from jedi.inference.compiled.getattr_static import getattr_static
ALLOWED_GETITEM_TYPES = (str, list, tuple, unicode, bytes, bytearray, dict) ALLOWED_GETITEM_TYPES = (str, list, tuple, bytes, bytearray, dict)
MethodDescriptorType = type(str.replace) MethodDescriptorType = type(str.replace)
# These are not considered classes and access is granted even though they have # These are not considered classes and access is granted even though they have
@@ -28,17 +28,12 @@ NOT_CLASS_TYPES = (
types.MethodType, types.MethodType,
types.ModuleType, types.ModuleType,
types.TracebackType, types.TracebackType,
MethodDescriptorType MethodDescriptorType,
)
if is_py3:
NOT_CLASS_TYPES += (
types.MappingProxyType, types.MappingProxyType,
types.SimpleNamespace, types.SimpleNamespace,
types.DynamicClassAttribute, types.DynamicClassAttribute,
) )
# Those types don't exist in typing. # Those types don't exist in typing.
MethodDescriptorType = type(str.replace) MethodDescriptorType = type(str.replace)
WrapperDescriptorType = type(set.__iter__) WrapperDescriptorType = type(set.__iter__)
@@ -123,13 +118,18 @@ def load_module(inference_state, dotted_name, sys_path):
__import__(dotted_name) __import__(dotted_name)
except ImportError: except ImportError:
# If a module is "corrupt" or not really a Python module or whatever. # If a module is "corrupt" or not really a Python module or whatever.
print('Module %s not importable in path %s.' % (dotted_name, sys_path), file=sys.stderr) warnings.warn(
"Module %s not importable in path %s." % (dotted_name, sys_path),
UserWarning,
stacklevel=2,
)
return None return None
except Exception: except Exception:
# Since __import__ pretty much makes code execution possible, just # Since __import__ pretty much makes code execution possible, just
# catch any error here and print it. # catch any error here and print it.
import traceback warnings.warn(
print("Cannot import:\n%s" % traceback.format_exc(), file=sys.stderr) "Cannot import:\n%s" % traceback.format_exc(), UserWarning, stacklevel=2
)
return None return None
finally: finally:
sys.path = temp sys.path = temp
@@ -140,42 +140,29 @@ def load_module(inference_state, dotted_name, sys_path):
return create_access_path(inference_state, module) return create_access_path(inference_state, module)
class AccessPath(object): class AccessPath:
def __init__(self, accesses): def __init__(self, accesses):
self.accesses = accesses self.accesses = accesses
# Writing both of these methods here looks a bit ridiculous. However with
# the differences of Python 2/3 it's actually necessary, because we will
# otherwise have a accesses attribute that is bytes instead of unicode.
def __getstate__(self):
return self.accesses
def __setstate__(self, value):
self.accesses = value
def create_access_path(inference_state, obj): def create_access_path(inference_state, obj):
access = create_access(inference_state, obj) access = create_access(inference_state, obj)
return AccessPath(access.get_access_path_tuples()) return AccessPath(access.get_access_path_tuples())
def _force_unicode_decorator(func):
return lambda *args, **kwargs: force_unicode(func(*args, **kwargs))
def get_api_type(obj): def get_api_type(obj):
if inspect.isclass(obj): if inspect.isclass(obj):
return u'class' return 'class'
elif inspect.ismodule(obj): elif inspect.ismodule(obj):
return u'module' return 'module'
elif inspect.isbuiltin(obj) or inspect.ismethod(obj) \ elif inspect.isbuiltin(obj) or inspect.ismethod(obj) \
or inspect.ismethoddescriptor(obj) or inspect.isfunction(obj): or inspect.ismethoddescriptor(obj) or inspect.isfunction(obj):
return u'function' return 'function'
# Everything else... # Everything else...
return u'instance' return 'instance'
class DirectObjectAccess(object): class DirectObjectAccess:
def __init__(self, inference_state, obj): def __init__(self, inference_state, obj):
self._inference_state = inference_state self._inference_state = inference_state
self._obj = obj self._obj = obj
@@ -199,7 +186,7 @@ class DirectObjectAccess(object):
return None return None
def py__doc__(self): def py__doc__(self):
return force_unicode(inspect.getdoc(self._obj)) or u'' return inspect.getdoc(self._obj) or ''
def py__name__(self): def py__name__(self):
if not _is_class_instance(self._obj) or \ if not _is_class_instance(self._obj) or \
@@ -214,7 +201,7 @@ class DirectObjectAccess(object):
return None return None
try: try:
return force_unicode(cls.__name__) return cls.__name__
except AttributeError: except AttributeError:
return None return None
@@ -260,26 +247,23 @@ class DirectObjectAccess(object):
# Avoid some weird hacks that would just fail, because they cannot be # Avoid some weird hacks that would just fail, because they cannot be
# used by pickle. # used by pickle.
if not isinstance(paths, list) \ if not isinstance(paths, list) \
or not all(isinstance(p, (bytes, unicode)) for p in paths): or not all(isinstance(p, str) for p in paths):
return None return None
return paths return paths
@_force_unicode_decorator
@shorten_repr @shorten_repr
def get_repr(self): def get_repr(self):
builtins = 'builtins', '__builtin__'
if inspect.ismodule(self._obj): if inspect.ismodule(self._obj):
return repr(self._obj) return repr(self._obj)
# Try to avoid execution of the property. # Try to avoid execution of the property.
if safe_getattr(self._obj, '__module__', default='') in builtins: if safe_getattr(self._obj, '__module__', default='') == 'builtins':
return repr(self._obj) return repr(self._obj)
type_ = type(self._obj) type_ = type(self._obj)
if type_ == type: if type_ == type:
return type.__repr__(self._obj) return type.__repr__(self._obj)
if safe_getattr(type_, '__module__', default='') in builtins: if safe_getattr(type_, '__module__', default='') == 'builtins':
# Allow direct execution of repr for builtins. # Allow direct execution of repr for builtins.
return repr(self._obj) return repr(self._obj)
return object.__repr__(self._obj) return object.__repr__(self._obj)
@@ -310,10 +294,10 @@ class DirectObjectAccess(object):
name = try_to_get_name(type(self._obj)) name = try_to_get_name(type(self._obj))
if name is None: if name is None:
return () return ()
return tuple(force_unicode(n) for n in name.split('.')) return tuple(name.split('.'))
def dir(self): def dir(self):
return list(map(force_unicode, dir(self._obj))) return dir(self._obj)
def has_iter(self): def has_iter(self):
try: try:
@@ -396,7 +380,7 @@ class DirectObjectAccess(object):
return [self._create_access(module), access] return [self._create_access(module), access]
def get_safe_value(self): def get_safe_value(self):
if type(self._obj) in (bool, bytes, float, int, str, unicode, slice) or self._obj is None: if type(self._obj) in (bool, bytes, float, int, str, slice) or self._obj is None:
return self._obj return self._obj
raise ValueError("Object is type %s and not simple" % type(self._obj)) raise ValueError("Object is type %s and not simple" % type(self._obj))
@@ -464,9 +448,6 @@ class DirectObjectAccess(object):
""" """
Returns Tuple[Optional[str], Tuple[AccessPath, ...]] Returns Tuple[Optional[str], Tuple[AccessPath, ...]]
""" """
if sys.version_info < (3, 5):
return None, ()
name = None name = None
args = () args = ()
if safe_getattr(self._obj, '__module__', default='') == 'typing': if safe_getattr(self._obj, '__module__', default='') == 'typing':
@@ -484,6 +465,9 @@ class DirectObjectAccess(object):
def needs_type_completions(self): def needs_type_completions(self):
return inspect.isclass(self._obj) and self._obj != type return inspect.isclass(self._obj) and self._obj != type
def _annotation_to_str(self, annotation):
return inspect.formatannotation(annotation)
def get_signature_params(self): def get_signature_params(self):
return [ return [
SignatureParam( SignatureParam(
@@ -493,31 +477,13 @@ class DirectObjectAccess(object):
default_string=repr(p.default), default_string=repr(p.default),
has_annotation=p.annotation is not p.empty, has_annotation=p.annotation is not p.empty,
annotation=self._create_access_path(p.annotation), annotation=self._create_access_path(p.annotation),
annotation_string=str(p.annotation), annotation_string=self._annotation_to_str(p.annotation),
kind_name=str(p.kind) kind_name=str(p.kind)
) for p in self._get_signature().parameters.values() ) for p in self._get_signature().parameters.values()
] ]
def _get_signature(self): def _get_signature(self):
obj = self._obj 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: try:
return inspect.signature(obj) return inspect.signature(obj)
except (RuntimeError, TypeError): except (RuntimeError, TypeError):
@@ -535,12 +501,6 @@ class DirectObjectAccess(object):
if o is None: if o is None:
return None return None
try:
# Python 2 doesn't have typing.
import typing
except ImportError:
pass
else:
try: try:
o = typing.get_type_hints(self._obj).get('return') o = typing.get_type_hints(self._obj).get('return')
except Exception: except Exception:
@@ -557,7 +517,7 @@ class DirectObjectAccess(object):
objects of an objects objects of an objects
""" """
tuples = dict( tuples = dict(
(force_unicode(name), self.is_allowed_getattr(name)) (name, self.is_allowed_getattr(name))
for name in self.dir() for name in self.dir()
) )
return self.needs_type_completions(), tuples return self.needs_type_completions(), tuples
@@ -570,4 +530,6 @@ def _is_class_instance(obj):
except AttributeError: except AttributeError:
return False return False
else: else:
return cls != type and not issubclass(cls, NOT_CLASS_TYPES) # The isinstance check for cls is just there so issubclass doesn't
# raise an exception.
return cls != type and isinstance(cls, type) and not issubclass(cls, NOT_CLASS_TYPES)
+12 -67
View File
@@ -6,7 +6,7 @@ information returned to enable Jedi to make decisions.
import types import types
from jedi._compatibility import py_version from jedi import debug
_sentinel = object() _sentinel = object()
@@ -38,7 +38,7 @@ def _is_type(obj):
return True return True
def _shadowed_dict_newstyle(klass): def _shadowed_dict(klass):
dict_attr = type.__dict__["__dict__"] dict_attr = type.__dict__["__dict__"]
for entry in _static_getmro(klass): for entry in _static_getmro(klass):
try: try:
@@ -53,74 +53,19 @@ def _shadowed_dict_newstyle(klass):
return _sentinel return _sentinel
def _static_getmro_newstyle(klass):
return type.__dict__['__mro__'].__get__(klass)
if py_version >= 30:
_shadowed_dict = _shadowed_dict_newstyle
_get_type = type
_static_getmro = _static_getmro_newstyle
else:
def _shadowed_dict(klass):
"""
In Python 2 __dict__ is not overwritable:
class Foo(object): pass
setattr(Foo, '__dict__', 4)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __dict__ must be a dictionary object
It applies to both newstyle and oldstyle classes:
class Foo(object): pass
setattr(Foo, '__dict__', 4)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: attribute '__dict__' of 'type' objects is not writable
It also applies to instances of those objects. However to keep things
straight forward, newstyle classes always use the complicated way of
accessing it while oldstyle classes just use getattr.
"""
if type(klass) is _oldstyle_class_type:
return getattr(klass, '__dict__', _sentinel)
return _shadowed_dict_newstyle(klass)
class _OldStyleClass:
pass
_oldstyle_instance_type = type(_OldStyleClass())
_oldstyle_class_type = type(_OldStyleClass)
def _get_type(obj):
type_ = object.__getattribute__(obj, '__class__')
if type_ is _oldstyle_instance_type:
# Somehow for old style classes we need to access it directly.
return obj.__class__
return type_
def _static_getmro(klass): def _static_getmro(klass):
if type(klass) is _oldstyle_class_type: mro = type.__dict__['__mro__'].__get__(klass)
def oldstyle_mro(klass): if not isinstance(mro, (tuple, list)):
""" # There are unfortunately no tests for this, I was not able to
Oldstyle mro is a really simplistic way of look up mro: # reproduce this in pure Python. However should still solve the issue
https://stackoverflow.com/questions/54867/what-is-the-difference-between-old-style-and-new-style-classes-in-python # raised in GH #1517.
""" debug.warning('mro of %s returned %s, should be a tuple' % (klass, mro))
yield klass return ()
for base in klass.__bases__: return mro
for yield_from in oldstyle_mro(base):
yield yield_from
return oldstyle_mro(klass)
return _static_getmro_newstyle(klass)
def _safe_hasattr(obj, name): def _safe_hasattr(obj, name):
return _check_class(_get_type(obj), name) is not _sentinel return _check_class(type(obj), name) is not _sentinel
def _safe_is_data_descriptor(obj): def _safe_is_data_descriptor(obj):
@@ -143,7 +88,7 @@ def getattr_static(obj, attr, default=_sentinel):
""" """
instance_result = _sentinel instance_result = _sentinel
if not _is_type(obj): if not _is_type(obj):
klass = _get_type(obj) klass = type(obj)
dict_attr = _shadowed_dict(klass) 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) instance_result = _check_instance(obj, attr)
+25 -10
View File
@@ -3,12 +3,10 @@ Used only for REPL Completion.
""" """
import inspect import inspect
import os from pathlib import Path
import sys
from jedi.parser_utils import get_cached_code_lines from jedi.parser_utils import get_cached_code_lines
from jedi._compatibility import unwrap
from jedi import settings from jedi import settings
from jedi.cache import memoize_method from jedi.cache import memoize_method
from jedi.inference import compiled from jedi.inference import compiled
@@ -44,7 +42,7 @@ class MixedObject(ValueWrapper):
to modify the runtime. to modify the runtime.
""" """
def __init__(self, compiled_value, tree_value): def __init__(self, compiled_value, tree_value):
super(MixedObject, self).__init__(tree_value) super().__init__(tree_value)
self.compiled_value = compiled_value self.compiled_value = compiled_value
self.access_handle = compiled_value.access_handle self.access_handle = compiled_value.access_handle
@@ -71,12 +69,22 @@ class MixedObject(ValueWrapper):
else: else:
return self.compiled_value.get_safe_value(default) return self.compiled_value.get_safe_value(default)
@property
def array_type(self):
return self.compiled_value.array_type
def get_key_values(self):
return self.compiled_value.get_key_values()
def py__simple_getitem__(self, index): def py__simple_getitem__(self, index):
python_object = self.compiled_value.access_handle.access._obj python_object = self.compiled_value.access_handle.access._obj
if type(python_object) in ALLOWED_GETITEM_TYPES: if type(python_object) in ALLOWED_GETITEM_TYPES:
return self.compiled_value.py__simple_getitem__(index) return self.compiled_value.py__simple_getitem__(index)
return self._wrapped_value.py__simple_getitem__(index) return self._wrapped_value.py__simple_getitem__(index)
def negate(self):
return self.compiled_value.negate()
def _as_context(self): def _as_context(self):
if self.parent_context is None: if self.parent_context is None:
return MixedModuleContext(self) return MixedModuleContext(self)
@@ -105,7 +113,7 @@ class MixedName(NameWrapper):
The ``CompiledName._compiled_value`` is our MixedObject. The ``CompiledName._compiled_value`` is our MixedObject.
""" """
def __init__(self, wrapped_name, parent_tree_value): def __init__(self, wrapped_name, parent_tree_value):
super(MixedName, self).__init__(wrapped_name) super().__init__(wrapped_name)
self._parent_tree_value = parent_tree_value self._parent_tree_value = parent_tree_value
@property @property
@@ -131,12 +139,12 @@ class MixedName(NameWrapper):
class MixedObjectFilter(compiled.CompiledValueFilter): class MixedObjectFilter(compiled.CompiledValueFilter):
def __init__(self, inference_state, compiled_value, tree_value): def __init__(self, inference_state, compiled_value, tree_value):
super(MixedObjectFilter, self).__init__(inference_state, compiled_value) super().__init__(inference_state, compiled_value)
self._tree_value = tree_value self._tree_value = tree_value
def _create_name(self, name): def _create_name(self, name):
return MixedName( return MixedName(
super(MixedObjectFilter, self)._create_name(name), super()._create_name(name),
self._tree_value, self._tree_value,
) )
@@ -153,9 +161,8 @@ def _load_module(inference_state, path):
def _get_object_to_check(python_object): def _get_object_to_check(python_object):
"""Check if inspect.getfile has a chance to find the source.""" """Check if inspect.getfile has a chance to find the source."""
if sys.version_info[0] > 2:
try: try:
python_object = unwrap(python_object) python_object = inspect.unwrap(python_object)
except ValueError: except ValueError:
# Can return a ValueError when it wraps around # Can return a ValueError when it wraps around
pass pass
@@ -183,9 +190,17 @@ def _find_syntax_node_name(inference_state, python_object):
except TypeError: except TypeError:
# The type might not be known (e.g. class_with_dict.__weakref__) # The type might not be known (e.g. class_with_dict.__weakref__)
return None return None
if path is None or not os.path.exists(path): path = None if path is None else Path(path)
try:
if path is None or not path.exists():
# The path might not exist or be e.g. <stdin>. # The path might not exist or be e.g. <stdin>.
return None return None
except OSError:
# Might raise an OSError on Windows:
#
# [WinError 123] The filename, directory name, or volume label
# syntax is incorrect: '<string>'
return None
file_io = FileIO(path) file_io = FileIO(path)
module_node = _load_module(inference_state, path) module_node = _load_module(inference_state, path)
+39 -51
View File
@@ -9,19 +9,14 @@ goals:
import os import os
import sys import sys
import queue
import subprocess import subprocess
import socket
import errno
import traceback import traceback
import weakref
from functools import partial from functools import partial
from threading import Thread 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, \ from jedi._compatibility import pickle_dump, pickle_load
pickle_dump, pickle_load, GeneralizedPopen, weakref
from jedi import debug from jedi import debug
from jedi.cache import memoize_method from jedi.cache import memoize_method
from jedi.inference.compiled.subprocess import functions from jedi.inference.compiled.subprocess import functions
@@ -31,11 +26,27 @@ from jedi.api.exceptions import InternalError
_MAIN_PATH = os.path.join(os.path.dirname(__file__), '__main__.py') _MAIN_PATH = os.path.join(os.path.dirname(__file__), '__main__.py')
PICKLE_PROTOCOL = 4
def _enqueue_output(out, queue): def _GeneralizedPopen(*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
return subprocess.Popen(*args, **kwargs)
def _enqueue_output(out, queue_):
for line in iter(out.readline, b''): for line in iter(out.readline, b''):
queue.put(line) queue_.put(line)
def _add_stderr_to_debug(stderr_queue): def _add_stderr_to_debug(stderr_queue):
@@ -46,7 +57,7 @@ def _add_stderr_to_debug(stderr_queue):
line = stderr_queue.get_nowait() line = stderr_queue.get_nowait()
line = line.decode('utf-8', 'replace') line = line.decode('utf-8', 'replace')
debug.warning('stderr output: %s' % line.rstrip('\n')) debug.warning('stderr output: %s' % line.rstrip('\n'))
except Empty: except queue.Empty:
break break
@@ -70,7 +81,7 @@ def _cleanup_process(process, thread):
pass pass
class _InferenceStateProcess(object): class _InferenceStateProcess:
def __init__(self, inference_state): def __init__(self, inference_state):
self._inference_state_weakref = weakref.ref(inference_state) self._inference_state_weakref = weakref.ref(inference_state)
self._inference_state_id = id(inference_state) self._inference_state_id = id(inference_state)
@@ -105,7 +116,7 @@ class InferenceStateSameProcess(_InferenceStateProcess):
class InferenceStateSubprocess(_InferenceStateProcess): class InferenceStateSubprocess(_InferenceStateProcess):
def __init__(self, inference_state, compiled_subprocess): def __init__(self, inference_state, compiled_subprocess):
super(InferenceStateSubprocess, self).__init__(inference_state) super().__init__(inference_state)
self._used = False self._used = False
self._compiled_subprocess = compiled_subprocess self._compiled_subprocess = compiled_subprocess
@@ -151,22 +162,20 @@ class InferenceStateSubprocess(_InferenceStateProcess):
self._compiled_subprocess.delete_inference_state(self._inference_state_id) self._compiled_subprocess.delete_inference_state(self._inference_state_id)
class CompiledSubprocess(object): class CompiledSubprocess:
is_crashed = False is_crashed = False
# Start with 2, gets set after _get_info.
_pickle_protocol = 2
def __init__(self, executable): def __init__(self, executable, env_vars=None):
self._executable = executable self._executable = executable
self._env_vars = env_vars
self._inference_state_deletion_queue = queue.deque() self._inference_state_deletion_queue = queue.deque()
self._cleanup_callable = lambda: None self._cleanup_callable = lambda: None
def __repr__(self): def __repr__(self):
pid = os.getpid() pid = os.getpid()
return '<%s _executable=%r, _pickle_protocol=%r, is_crashed=%r, pid=%r>' % ( return '<%s _executable=%r, is_crashed=%r, pid=%r>' % (
self.__class__.__name__, self.__class__.__name__,
self._executable, self._executable,
self._pickle_protocol,
self.is_crashed, self.is_crashed,
pid, pid,
) )
@@ -181,16 +190,14 @@ class CompiledSubprocess(object):
os.path.dirname(os.path.dirname(parso_path)), os.path.dirname(os.path.dirname(parso_path)),
'.'.join(str(x) for x in sys.version_info[:3]), '.'.join(str(x) for x in sys.version_info[:3]),
) )
process = GeneralizedPopen( process = _GeneralizedPopen(
args, args,
stdin=subprocess.PIPE, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, stderr=subprocess.PIPE,
# Use system default buffering on Python 2 to improve performance env=self._env_vars
# (this is already the case on Python 3).
bufsize=-1
) )
self._stderr_queue = Queue() self._stderr_queue = queue.Queue()
self._stderr_thread = t = Thread( self._stderr_thread = t = Thread(
target=_enqueue_output, target=_enqueue_output,
args=(process.stderr, self._stderr_queue) args=(process.stderr, self._stderr_queue)
@@ -229,20 +236,10 @@ class CompiledSubprocess(object):
if self.is_crashed: if self.is_crashed:
raise InternalError("The subprocess %s has crashed." % self._executable) raise InternalError("The subprocess %s has crashed." % self._executable)
if not is_py3:
# Python 2 compatibility
kwargs = {force_unicode(key): value for key, value in kwargs.items()}
data = inference_state_id, function, args, kwargs data = inference_state_id, function, args, kwargs
try: try:
pickle_dump(data, self._get_process().stdin, self._pickle_protocol) pickle_dump(data, self._get_process().stdin, PICKLE_PROTOCOL)
except (socket.error, IOError) as e: except BrokenPipeError:
# 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() self._kill()
raise InternalError("The subprocess %s was killed. Maybe out of memory?" raise InternalError("The subprocess %s was killed. Maybe out of memory?"
% self._executable) % self._executable)
@@ -283,13 +280,12 @@ class CompiledSubprocess(object):
self._inference_state_deletion_queue.append(inference_state_id) self._inference_state_deletion_queue.append(inference_state_id)
class Listener(object): class Listener:
def __init__(self, pickle_protocol): def __init__(self):
self._inference_states = {} self._inference_states = {}
# TODO refactor so we don't need to process anymore just handle # TODO refactor so we don't need to process anymore just handle
# controlling. # controlling.
self._process = _InferenceStateProcess(Listener) self._process = _InferenceStateProcess(Listener)
self._pickle_protocol = pickle_protocol
def _get_inference_state(self, function, inference_state_id): def _get_inference_state(self, function, inference_state_id):
from jedi.inference import InferenceState from jedi.inference import InferenceState
@@ -297,7 +293,7 @@ class Listener(object):
try: try:
inference_state = self._inference_states[inference_state_id] inference_state = self._inference_states[inference_state_id]
except KeyError: except KeyError:
from jedi.api.environment import InterpreterEnvironment from jedi import InterpreterEnvironment
inference_state = InferenceState( inference_state = InferenceState(
# The project is not actually needed. Nothing should need to # The project is not actually needed. Nothing should need to
# access it. # access it.
@@ -332,15 +328,8 @@ class Listener(object):
# because stdout is used for IPC. # because stdout is used for IPC.
sys.stdout = open(os.devnull, 'w') sys.stdout = open(os.devnull, 'w')
stdin = sys.stdin stdin = sys.stdin
if sys.version_info[0] > 2:
stdout = stdout.buffer stdout = stdout.buffer
stdin = stdin.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: while True:
try: try:
@@ -354,10 +343,10 @@ class Listener(object):
except Exception as e: except Exception as e:
result = True, traceback.format_exc(), e result = True, traceback.format_exc(), e
pickle_dump(result, stdout, self._pickle_protocol) pickle_dump(result, stdout, PICKLE_PROTOCOL)
class AccessHandle(object): class AccessHandle:
def __init__(self, subprocess, access, id_): def __init__(self, subprocess, access, id_):
self.access = access self.access = access
self._subprocess = subprocess self._subprocess = subprocess
@@ -383,9 +372,8 @@ class AccessHandle(object):
if name in ('id', 'access') or name.startswith('_'): if name in ('id', 'access') or name.startswith('_'):
raise AttributeError("Something went wrong with unpickling") raise AttributeError("Something went wrong with unpickling")
# if not is_py3: print >> sys.stderr, name
# print('getattr', name, file=sys.stderr) # print('getattr', name, file=sys.stderr)
return partial(self._workaround, force_unicode(name)) return partial(self._workaround, name)
def _workaround(self, name, *args, **kwargs): def _workaround(self, name, *args, **kwargs):
""" """
+10 -25
View File
@@ -1,5 +1,11 @@
import os import os
import sys import sys
from importlib.abc import MetaPathFinder
from importlib.machinery import PathFinder
# Remove the first entry, because it's simply a directory entry that equals
# this directory.
del sys.path[0]
def _get_paths(): def _get_paths():
@@ -11,14 +17,7 @@ def _get_paths():
return {'jedi': _jedi_path, 'parso': _parso_path} return {'jedi': _jedi_path, 'parso': _parso_path}
# Remove the first entry, because it's simply a directory entry that equals class _ExactImporter(MetaPathFinder):
# 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): def __init__(self, path_dct):
self._path_dct = path_dct self._path_dct = path_dct
@@ -29,27 +28,13 @@ if sys.version_info > (3, 4):
return loader return loader
return None return None
# Try to import jedi/parso. # Try to import jedi/parso.
sys.meta_path.insert(0, _ExactImporter(_get_paths())) sys.meta_path.insert(0, _ExactImporter(_get_paths()))
from jedi.inference.compiled import subprocess # NOQA from jedi.inference.compiled import subprocess # noqa: E402
sys.meta_path.pop(0) sys.meta_path.pop(0)
else:
import imp
def load(name):
paths = list(_get_paths().values())
fp, pathname, description = imp.find_module(name, paths)
return imp.load_module(name, fp, pathname, description)
load('parso')
load('jedi')
from jedi.inference.compiled import subprocess # NOQA
from jedi._compatibility import highest_pickle_protocol # noqa: E402
# Retrieve the pickle protocol. # Retrieve the pickle protocol.
host_sys_version = [int(x) for x in sys.argv[2].split('.')] 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. # And finally start the client.
subprocess.Listener(pickle_protocol=pickle_protocol).listen() subprocess.Listener().listen()
+163 -12
View File
@@ -1,11 +1,17 @@
from __future__ import print_function
import sys import sys
import os import os
import inspect
import importlib
import warnings
from pathlib import Path
from zipimport import zipimporter
from importlib.machinery import all_suffixes
from jedi._compatibility import find_module, cast_path, force_unicode, \ from jedi._compatibility import cast_path
iter_modules, all_suffixes
from jedi.inference.compiled import access from jedi.inference.compiled import access
from jedi import debug
from jedi import parser_utils from jedi import parser_utils
from jedi.file_io import KnownContentFileIO, ZipFileIO
def get_sys_path(): def get_sys_path():
@@ -32,7 +38,7 @@ def get_module_info(inference_state, sys_path=None, full_name=None, **kwargs):
if sys_path is not None: if sys_path is not None:
sys.path, temp = sys_path, sys.path sys.path, temp = sys_path, sys.path
try: try:
return find_module(full_name=full_name, **kwargs) return _find_module(full_name=full_name, **kwargs)
except ImportError: except ImportError:
return None, None return None, None
finally: finally:
@@ -40,15 +46,8 @@ def get_module_info(inference_state, sys_path=None, full_name=None, **kwargs):
sys.path = temp sys.path = temp
def list_module_names(inference_state, search_path):
return [
force_unicode(name)
for module_loader, name, is_pkg in iter_modules(search_path)
]
def get_builtin_module_names(inference_state): def get_builtin_module_names(inference_state):
return list(map(force_unicode, sys.builtin_module_names)) return sys.builtin_module_names
def _test_raise_error(inference_state, exception_type): def _test_raise_error(inference_state, exception_type):
@@ -84,3 +83,155 @@ def _get_init_path(directory_path):
def safe_literal_eval(inference_state, value): def safe_literal_eval(inference_state, value):
return parser_utils.safe_literal_eval(value) return parser_utils.safe_literal_eval(value)
def iter_module_names(*args, **kwargs):
return list(_iter_module_names(*args, **kwargs))
def _iter_module_names(inference_state, paths):
# Python modules/packages
for path in paths:
try:
dirs = os.scandir(path)
except OSError:
# The file might not exist or reading it might lead to an error.
debug.warning("Not possible to list directory: %s", path)
continue
for dir_entry in dirs:
name = dir_entry.name
# First Namespaces then modules/stubs
if dir_entry.is_dir():
# pycache is obviously not an interesting namespace. Also the
# name must be a valid identifier.
if name != '__pycache__' and name.isidentifier():
yield name
else:
if name.endswith('.pyi'): # Stub files
modname = name[:-4]
else:
modname = inspect.getmodulename(name)
if modname and '.' not in modname:
if modname != '__init__':
yield modname
def _find_module(string, path=None, full_name=None, is_global_search=True):
"""
Provides information about a module.
This function isolates the differences in importing libraries introduced with
python 3.3 on; it gets a module name and optionally a path. It will return a
tuple containin an open file for the module (if not builtin), the filename
or the name of the module if it is a builtin one and a boolean indicating
if the module is contained in a package.
"""
spec = None
loader = None
for finder in sys.meta_path:
if is_global_search and finder != importlib.machinery.PathFinder:
p = None
else:
p = path
try:
find_spec = finder.find_spec
except AttributeError:
# These are old-school clases that still have a different API, just
# ignore those.
continue
spec = find_spec(string, p)
if spec is not None:
loader = spec.loader
if loader is None and not spec.has_location:
# This is a namespace package.
full_name = string if not path else full_name
implicit_ns_info = ImplicitNSInfo(full_name, spec.submodule_search_locations._path)
return implicit_ns_info, True
break
return _find_module_py33(string, path, loader)
def _find_module_py33(string, path=None, loader=None, full_name=None, is_global_search=True):
loader = loader or importlib.machinery.PathFinder.find_module(string, path)
if loader is None and path is None: # Fallback to find builtins
try:
with warnings.catch_warnings(record=True):
# Mute "DeprecationWarning: Use importlib.util.find_spec()
# instead." While we should replace that in the future, it's
# probably good to wait until we deprecate Python 3.3, since
# it was added in Python 3.4 and find_loader hasn't been
# removed in 3.6.
loader = importlib.find_loader(string)
except ValueError as e:
# See #491. Importlib might raise a ValueError, to avoid this, we
# just raise an ImportError to fix the issue.
raise ImportError("Originally " + repr(e))
if loader is None:
raise ImportError("Couldn't find a loader for {}".format(string))
return _from_loader(loader, string)
def _from_loader(loader, string):
try:
is_package_method = loader.is_package
except AttributeError:
is_package = False
else:
is_package = is_package_method(string)
try:
get_filename = loader.get_filename
except AttributeError:
return None, is_package
else:
module_path = cast_path(get_filename(string))
# To avoid unicode and read bytes, "overwrite" loader.get_source if
# possible.
try:
f = type(loader).get_source
except AttributeError:
raise ImportError("get_source was not defined on loader")
if f is not importlib.machinery.SourceFileLoader.get_source:
# Unfortunately we are reading unicode here, not bytes.
# It seems hard to get bytes, because the zip importer
# logic just unpacks the zip file and returns a file descriptor
# that we cannot as easily access. Therefore we just read it as
# a string in the cases where get_source was overwritten.
code = loader.get_source(string)
else:
code = _get_source(loader, string)
if code is None:
return None, is_package
if isinstance(loader, zipimporter):
return ZipFileIO(module_path, code, Path(cast_path(loader.archive))), is_package
return KnownContentFileIO(module_path, code), is_package
def _get_source(loader, fullname):
"""
This method is here as a replacement for SourceLoader.get_source. That
method returns unicode, but we prefer bytes.
"""
path = loader.get_filename(fullname)
try:
return loader.get_data(path)
except OSError:
raise ImportError('source not available through get_data()',
name=fullname)
class ImplicitNSInfo:
"""Stores information returned from an implicit namespace spec"""
def __init__(self, name, paths):
self.name = name
self.paths = paths
+36 -45
View File
@@ -3,10 +3,12 @@ Imitate the parser representation.
""" """
import re import re
from functools import partial from functools import partial
from inspect import Parameter
from pathlib import Path
from jedi import debug from jedi import debug
from jedi.inference.utils import to_list from jedi.inference.utils import to_list
from jedi._compatibility import force_unicode, Parameter, cast_path from jedi._compatibility import cast_path
from jedi.cache import memoize_method from jedi.cache import memoize_method
from jedi.inference.filters import AbstractFilter from jedi.inference.filters import AbstractFilter
from jedi.inference.names import AbstractNameDefinition, ValueNameMixin, \ from jedi.inference.names import AbstractNameDefinition, ValueNameMixin, \
@@ -20,8 +22,8 @@ from jedi.inference.signature import BuiltinSignature
from jedi.inference.context import CompiledContext, CompiledModuleContext from jedi.inference.context import CompiledContext, CompiledModuleContext
class CheckAttribute(object): class CheckAttribute:
"""Raises an AttributeError if the attribute X isn't available.""" """Raises :exc:`AttributeError` if the attribute X is not available."""
def __init__(self, check_name=None): def __init__(self, check_name=None):
# Remove the py in front of e.g. py__call__. # Remove the py in front of e.g. py__call__.
self.check_name = check_name self.check_name = check_name
@@ -29,7 +31,7 @@ class CheckAttribute(object):
def __call__(self, func): def __call__(self, func):
self.func = func self.func = func
if self.check_name is None: if self.check_name is None:
self.check_name = force_unicode(func.__name__[2:]) self.check_name = func.__name__[2:]
return self return self
def __get__(self, instance, owner): def __get__(self, instance, owner):
@@ -43,7 +45,7 @@ class CheckAttribute(object):
class CompiledValue(Value): class CompiledValue(Value):
def __init__(self, inference_state, access_handle, parent_context=None): def __init__(self, inference_state, access_handle, parent_context=None):
super(CompiledValue, self).__init__(inference_state, parent_context) super().__init__(inference_state, parent_context)
self.access_handle = access_handle self.access_handle = access_handle
def py__call__(self, arguments): def py__call__(self, arguments):
@@ -56,9 +58,9 @@ class CompiledValue(Value):
).execute_annotation() ).execute_annotation()
try: try:
self.access_handle.getattr_paths(u'__call__') self.access_handle.getattr_paths('__call__')
except AttributeError: except AttributeError:
return super(CompiledValue, self).py__call__(arguments) return super().py__call__(arguments)
else: else:
if self.access_handle.is_class(): if self.access_handle.is_class():
from jedi.inference.value import CompiledInstance from jedi.inference.value import CompiledInstance
@@ -163,7 +165,7 @@ class CompiledValue(Value):
try: try:
access = self.access_handle.py__simple_getitem__(index) access = self.access_handle.py__simple_getitem__(index)
except AttributeError: except AttributeError:
return super(CompiledValue, self).py__simple_getitem__(index) return super().py__simple_getitem__(index)
if access is None: if access is None:
return NO_VALUES return NO_VALUES
@@ -174,20 +176,15 @@ class CompiledValue(Value):
if all_access_paths is None: if all_access_paths is None:
# This means basically that no __getitem__ has been defined on this # This means basically that no __getitem__ has been defined on this
# object. # object.
return super(CompiledValue, self).py__getitem__(index_value_set, contextualized_node) return super().py__getitem__(index_value_set, contextualized_node)
return ValueSet( return ValueSet(
create_from_access_path(self.inference_state, access) create_from_access_path(self.inference_state, access)
for access in all_access_paths for access in all_access_paths
) )
def py__iter__(self, contextualized_node=None): def py__iter__(self, contextualized_node=None):
# Python iterators are a bit strange, because there's no need for
# the __iter__ function as long as __getitem__ is defined (it will
# just start with __getitem__(0). This is especially true for
# Python 2 strings, where `str.__iter__` is not even defined.
if not self.access_handle.has_iter(): if not self.access_handle.has_iter():
for x in super(CompiledValue, self).py__iter__(contextualized_node): yield from super().py__iter__(contextualized_node)
yield x
access_path_list = self.access_handle.py__iter__list() access_path_list = self.access_handle.py__iter__list()
if access_path_list is None: if access_path_list is None:
@@ -222,10 +219,8 @@ class CompiledValue(Value):
continue continue
else: else:
bltn_obj = builtin_from_name(self.inference_state, name) bltn_obj = builtin_from_name(self.inference_state, name)
for result in self.inference_state.execute(bltn_obj, params): yield from self.inference_state.execute(bltn_obj, params)
yield result yield from docstrings.infer_return_types(self)
for type_ in docstrings.infer_return_types(self):
yield type_
def get_safe_value(self, default=_sentinel): def get_safe_value(self, default=_sentinel):
try: try:
@@ -264,7 +259,7 @@ class CompiledValue(Value):
v.with_generics(arguments) v.with_generics(arguments)
for v in self.inference_state.typing_module.py__getattribute__(name) for v in self.inference_state.typing_module.py__getattribute__(name)
]).execute_annotation() ]).execute_annotation()
return super(CompiledValue, self).execute_annotation() return super().execute_annotation()
def negate(self): def negate(self):
return create_from_access_path(self.inference_state, self.access_handle.negate()) return create_from_access_path(self.inference_state, self.access_handle.negate())
@@ -285,6 +280,11 @@ class CompiledValue(Value):
for k in self.access_handle.get_key_paths() for k in self.access_handle.get_key_paths()
] ]
def get_type_hint(self, add_class_info=True):
if self.access_handle.get_repr() in ('None', "<class 'NoneType'>"):
return 'None'
return None
class CompiledModule(CompiledValue): class CompiledModule(CompiledValue):
file_io = None # For modules file_io = None # For modules
@@ -310,7 +310,10 @@ class CompiledModule(CompiledValue):
return tuple(name.split('.')) return tuple(name.split('.'))
def py__file__(self): def py__file__(self):
return cast_path(self.access_handle.py__file__()) path = cast_path(self.access_handle.py__file__())
if path is None:
return None
return Path(path)
class CompiledName(AbstractNameDefinition): class CompiledName(AbstractNameDefinition):
@@ -321,8 +324,7 @@ class CompiledName(AbstractNameDefinition):
self.string_name = name self.string_name = name
def py__doc__(self): def py__doc__(self):
value, = self.infer() return self.infer_compiled_value().py__doc__()
return value.py__doc__()
def _get_qualified_names(self): def _get_qualified_names(self):
parent_qualified_names = self.parent_context.get_qualified_names() parent_qualified_names = self.parent_context.get_qualified_names()
@@ -346,16 +348,12 @@ class CompiledName(AbstractNameDefinition):
@property @property
def api_type(self): def api_type(self):
api = self.infer() return self.infer_compiled_value().api_type
# If we can't find the type, assume it is an instance variable
if not api:
return "instance"
return next(iter(api)).api_type
@memoize_method
def infer(self): def infer(self):
return ValueSet([self.infer_compiled_value()]) return ValueSet([self.infer_compiled_value()])
@memoize_method
def infer_compiled_value(self): def infer_compiled_value(self):
return create_from_name(self._inference_state, self._parent_value, self.string_name) return create_from_name(self._inference_state, self._parent_value, self.string_name)
@@ -451,9 +449,6 @@ class CompiledValueFilter(AbstractFilter):
""" """
To remove quite a few access calls we introduced the callback here. To remove quite a few access calls we introduced the callback here.
""" """
# Always use unicode objects in Python 2 from here.
name = force_unicode(name)
if self._inference_state.allow_descriptor_getattr: if self._inference_state.allow_descriptor_getattr:
pass pass
@@ -497,7 +492,7 @@ class CompiledValueFilter(AbstractFilter):
# ``dir`` doesn't include the type names. # ``dir`` doesn't include the type names.
if not self.is_instance and needs_type_completions: if not self.is_instance and needs_type_completions:
for filter in builtin_from_name(self._inference_state, u'type').get_filters(): for filter in builtin_from_name(self._inference_state, 'type').get_filters():
names += filter.values() names += filter.values()
return names return names
@@ -513,11 +508,11 @@ class CompiledValueFilter(AbstractFilter):
docstr_defaults = { docstr_defaults = {
'floating point number': u'float', 'floating point number': 'float',
'character': u'str', 'character': 'str',
'integer': u'int', 'integer': 'int',
'dictionary': u'dict', 'dictionary': 'dict',
'string': u'str', 'string': 'str',
} }
@@ -529,7 +524,6 @@ def _parse_function_doc(doc):
TODO docstrings like utime(path, (atime, mtime)) and a(b [, b]) -> None TODO docstrings like utime(path, (atime, mtime)) and a(b [, b]) -> None
TODO docstrings like 'tuple of integers' TODO docstrings like 'tuple of integers'
""" """
doc = force_unicode(doc)
# parse round parentheses: def func(a, (b,c)) # parse round parentheses: def func(a, (b,c))
try: try:
count = 0 count = 0
@@ -548,7 +542,7 @@ def _parse_function_doc(doc):
# UnboundLocalError for undefined end in last line # UnboundLocalError for undefined end in last line
debug.dbg('no brackets found - no param') debug.dbg('no brackets found - no param')
end = 0 end = 0
param_str = u'' param_str = ''
else: else:
# remove square brackets, that show an optional param ( = None) # remove square brackets, that show an optional param ( = None)
def change_options(m): def change_options(m):
@@ -566,9 +560,9 @@ def _parse_function_doc(doc):
param_str = param_str.replace('-', '_') # see: isinstance.__doc__ param_str = param_str.replace('-', '_') # see: isinstance.__doc__
# parse return value # parse return value
r = re.search(u'-[>-]* ', doc[end:end + 7]) r = re.search('-[>-]* ', doc[end:end + 7])
if r is None: if r is None:
ret = u'' ret = ''
else: else:
index = end + r.end() index = end + r.end()
# get result type, which can contain newlines # get result type, which can contain newlines
@@ -584,9 +578,6 @@ def _parse_function_doc(doc):
def create_from_name(inference_state, compiled_value, name): def create_from_name(inference_state, compiled_value, name):
access_paths = compiled_value.access_handle.getattr_paths(name, default=None) access_paths = compiled_value.access_handle.getattr_paths(name, default=None)
parent_context = compiled_value
if parent_context.is_class():
parent_context = parent_context.parent_context
value = None value = None
for access_path in access_paths: for access_path in access_paths:
+26 -17
View File
@@ -13,7 +13,7 @@ from jedi import debug
from jedi import parser_utils from jedi import parser_utils
class AbstractContext(object): class AbstractContext:
# Must be defined: inference_state and tree_node and parent_context as an attribute/property # Must be defined: inference_state and tree_node and parent_context as an attribute/property
def __init__(self, inference_state): def __init__(self, inference_state):
@@ -129,6 +129,9 @@ class AbstractContext(object):
def is_compiled(self): def is_compiled(self):
return False return False
def is_bound_method(self):
return False
@abstractmethod @abstractmethod
def py__name__(self): def py__name__(self):
raise NotImplementedError raise NotImplementedError
@@ -161,7 +164,7 @@ class ValueContext(AbstractContext):
Should be defined, otherwise the API returns empty types. Should be defined, otherwise the API returns empty types.
""" """
def __init__(self, value): def __init__(self, value):
super(ValueContext, self).__init__(value.inference_state) super().__init__(value.inference_state)
self._value = value self._value = value
@property @property
@@ -190,6 +193,9 @@ class ValueContext(AbstractContext):
def is_compiled(self): def is_compiled(self):
return self._value.is_compiled() return self._value.is_compiled()
def is_bound_method(self):
return self._value.is_bound_method()
def py__name__(self): def py__name__(self):
return self._value.py__name__() return self._value.py__name__()
@@ -210,7 +216,7 @@ class ValueContext(AbstractContext):
return '%s(%s)' % (self.__class__.__name__, self._value) return '%s(%s)' % (self.__class__.__name__, self._value)
class TreeContextMixin(object): class TreeContextMixin:
def infer_node(self, node): def infer_node(self, node):
from jedi.inference.syntax_tree import infer_node from jedi.inference.syntax_tree import infer_node
return infer_node(self, node) return infer_node(self, node)
@@ -249,8 +255,7 @@ class TreeContextMixin(object):
if scope_node.type in ('funcdef', 'lambdef', 'classdef'): if scope_node.type in ('funcdef', 'lambdef', 'classdef'):
return self.create_value(scope_node).as_context() return self.create_value(scope_node).as_context()
elif scope_node.type in ('comp_for', 'sync_comp_for'): elif scope_node.type in ('comp_for', 'sync_comp_for'):
parent_scope = parser_utils.get_parent_scope(scope_node) parent_context = from_scope_node(parent_scope(scope_node.parent))
parent_context = from_scope_node(parent_scope)
if node.start_pos >= scope_node.children[-1].start_pos: if node.start_pos >= scope_node.children[-1].start_pos:
return parent_context return parent_context
return CompForContext(parent_context, scope_node) return CompForContext(parent_context, scope_node)
@@ -308,7 +313,7 @@ class ModuleContext(TreeContextMixin, ValueContext):
def get_filters(self, until_position=None, origin_scope=None): def get_filters(self, until_position=None, origin_scope=None):
filters = self._value.get_filters(origin_scope) filters = self._value.get_filters(origin_scope)
# Skip the first filter and replace it. # Skip the first filter and replace it.
next(filters) next(filters, None)
yield MergedFilter( yield MergedFilter(
ParserTreeFilter( ParserTreeFilter(
parent_context=self, parent_context=self,
@@ -317,8 +322,7 @@ class ModuleContext(TreeContextMixin, ValueContext):
), ),
self.get_global_filter(), self.get_global_filter(),
) )
for f in filters: # Python 2... yield from filters
yield f
def get_global_filter(self): def get_global_filter(self):
return GlobalNameFilter(self, self.tree_node) return GlobalNameFilter(self, self.tree_node)
@@ -347,6 +351,10 @@ class NamespaceContext(TreeContextMixin, ValueContext):
def get_value(self): def get_value(self):
return self._value return self._value
@property
def string_names(self):
return self._value.string_names
def py__file__(self): def py__file__(self):
return self._value.py__file__() return self._value.py__file__()
@@ -365,7 +373,7 @@ class ClassContext(TreeContextMixin, ValueContext):
class CompForContext(TreeContextMixin, AbstractContext): class CompForContext(TreeContextMixin, AbstractContext):
def __init__(self, parent_context, comp_for): def __init__(self, parent_context, comp_for):
super(CompForContext, self).__init__(parent_context.inference_state) super().__init__(parent_context.inference_state)
self.tree_node = comp_for self.tree_node = comp_for
self.parent_context = parent_context self.parent_context = parent_context
@@ -429,13 +437,12 @@ def get_global_filters(context, until_position, origin_scope):
For global name lookups. The filters will handle name resolution For global name lookups. The filters will handle name resolution
themselves, but here we gather possible filters downwards. themselves, but here we gather possible filters downwards.
>>> from jedi._compatibility import u, no_unicode_pprint
>>> from jedi import Script >>> from jedi import Script
>>> script = Script(u(''' >>> script = Script('''
... x = ['a', 'b', 'c'] ... x = ['a', 'b', 'c']
... def func(): ... def func():
... y = None ... y = None
... ''')) ... ''')
>>> module_node = script._module_node >>> module_node = script._module_node
>>> scope = next(module_node.iter_funcdefs()) >>> scope = next(module_node.iter_funcdefs())
>>> scope >>> scope
@@ -445,7 +452,7 @@ def get_global_filters(context, until_position, origin_scope):
First we get the names from the function scope. First we get the names from the function scope.
>>> no_unicode_pprint(filters[0]) # doctest: +ELLIPSIS >>> print(filters[0]) # doctest: +ELLIPSIS
MergedFilter(<ParserTreeFilter: ...>, <GlobalNameFilter: ...>) MergedFilter(<ParserTreeFilter: ...>, <GlobalNameFilter: ...>)
>>> sorted(str(n) for n in filters[0].values()) # doctest: +NORMALIZE_WHITESPACE >>> sorted(str(n) for n in filters[0].values()) # doctest: +NORMALIZE_WHITESPACE
['<TreeNameDefinition: string_name=func start_pos=(3, 4)>', ['<TreeNameDefinition: string_name=func start_pos=(3, 4)>',
@@ -474,15 +481,17 @@ def get_global_filters(context, until_position, origin_scope):
from jedi.inference.value.function import BaseFunctionExecutionContext from jedi.inference.value.function import BaseFunctionExecutionContext
while context is not None: while context is not None:
# Names in methods cannot be resolved within the class. # Names in methods cannot be resolved within the class.
for filter in context.get_filters( yield from context.get_filters(
until_position=until_position, until_position=until_position,
origin_scope=origin_scope): origin_scope=origin_scope
yield filter )
if isinstance(context, (BaseFunctionExecutionContext, ModuleContext)): if isinstance(context, (BaseFunctionExecutionContext, ModuleContext)):
# The position should be reset if the current scope is a function. # The position should be reset if the current scope is a function.
until_position = None until_position = None
context = context.parent_context context = context.parent_context
b = next(base_context.inference_state.builtins_module.get_filters(), None)
assert b is not None
# Add builtins to the global scope. # Add builtins to the global scope.
yield next(base_context.inference_state.builtins_module.get_filters()) yield b
+11 -18
View File
@@ -1,7 +1,7 @@
""" """
Docstrings are another source of information for functions and classes. Docstrings are another source of information for functions and classes.
:mod:`jedi.inference.dynamic` tries to find all executions of functions, while :mod:`jedi.inference.dynamic_params` tries to find all executions of functions,
the docstring parsing is much easier. There are three different types of while the docstring parsing is much easier. There are three different types of
docstrings that |jedi| understands: docstrings that |jedi| understands:
- `Sphinx <http://sphinx-doc.org/markup/desc.html#info-field-lists>`_ - `Sphinx <http://sphinx-doc.org/markup/desc.html#info-field-lists>`_
@@ -21,9 +21,8 @@ from textwrap import dedent
from parso import parse, ParserSyntaxError from parso import parse, ParserSyntaxError
from jedi._compatibility import u
from jedi import debug from jedi import debug
from jedi.inference.utils import indent_block from jedi.common import indent_block
from jedi.inference.cache import inference_state_method_cache from jedi.inference.cache import inference_state_method_cache
from jedi.inference.base_value import iterator_to_value_set, ValueSet, \ from jedi.inference.base_value import iterator_to_value_set, ValueSet, \
NO_VALUES NO_VALUES
@@ -51,7 +50,7 @@ def _get_numpy_doc_string_cls():
global _numpy_doc_string_cache global _numpy_doc_string_cache
if isinstance(_numpy_doc_string_cache, (ImportError, SyntaxError)): if isinstance(_numpy_doc_string_cache, (ImportError, SyntaxError)):
raise _numpy_doc_string_cache raise _numpy_doc_string_cache
from numpydoc.docscrape import NumpyDocString from numpydoc.docscrape import NumpyDocString # type: ignore[import]
_numpy_doc_string_cache = NumpyDocString _numpy_doc_string_cache = NumpyDocString
return _numpy_doc_string_cache return _numpy_doc_string_cache
@@ -96,8 +95,7 @@ def _search_return_in_numpydocstr(docstr):
# Return names are optional and if so the type is in the name # Return names are optional and if so the type is in the name
if not r_type: if not r_type:
r_type = r_name r_type = r_name
for type_ in _expand_typestr(r_type): yield from _expand_typestr(r_type)
yield type_
def _expand_typestr(type_str): def _expand_typestr(type_str):
@@ -115,7 +113,7 @@ def _expand_typestr(type_str):
elif type_str.startswith('{'): elif type_str.startswith('{'):
node = parse(type_str, version='3.7').children[0] node = parse(type_str, version='3.7').children[0]
if node.type == 'atom': if node.type == 'atom':
for leaf in node.children[1].children: for leaf in getattr(node.children[1], "children", []):
if leaf.type == 'number': if leaf.type == 'number':
if '.' in leaf.value: if '.' in leaf.value:
yield 'float' yield 'float'
@@ -184,7 +182,7 @@ def _strip_rst_role(type_str):
def _infer_for_statement_string(module_context, string): def _infer_for_statement_string(module_context, string):
code = dedent(u(""" code = dedent("""
def pseudo_docstring_stuff(): def pseudo_docstring_stuff():
''' '''
Create a pseudo function for docstring statements. Create a pseudo function for docstring statements.
@@ -192,7 +190,7 @@ def _infer_for_statement_string(module_context, string):
is still a function. is still a function.
''' '''
{} {}
""")) """)
if string is None: if string is None:
return [] return []
@@ -201,11 +199,8 @@ def _infer_for_statement_string(module_context, string):
# (e.g., 'threading' in 'threading.Thread'). # (e.g., 'threading' in 'threading.Thread').
string = 'import %s\n' % element + string string = 'import %s\n' % element + string
# Take the default grammar here, if we load the Python 2.7 grammar here, it
# will be impossible to use `...` (Ellipsis) as a token. Docstring types
# don't need to conform with the current grammar.
debug.dbg('Parse docstring code %s', string, color='BLUE') debug.dbg('Parse docstring code %s', string, color='BLUE')
grammar = module_context.inference_state.latest_grammar grammar = module_context.inference_state.grammar
try: try:
module = grammar.parse(code.format(indent_block(string)), error_recovery=False) module = grammar.parse(code.format(indent_block(string)), error_recovery=False)
except ParserSyntaxError: except ParserSyntaxError:
@@ -299,9 +294,7 @@ def infer_return_types(function_value):
if match: if match:
yield _strip_rst_role(match.group(1)) yield _strip_rst_role(match.group(1))
# Check for numpy style return hint # Check for numpy style return hint
for type_ in _search_return_in_numpydocstr(code): yield from _search_return_in_numpydocstr(code)
yield type_
for type_str in search_return_in_docstr(function_value.py__doc__()): for type_str in search_return_in_docstr(function_value.py__doc__()):
for value in _infer_for_statement_string(function_value.get_root_context(), type_str): yield from _infer_for_statement_string(function_value.get_root_context(), type_str)
yield value
+1 -4
View File
@@ -48,7 +48,6 @@ def _avoid_recursions(func):
finally: finally:
inf.dynamic_params_depth -= 1 inf.dynamic_params_depth -= 1
return NO_VALUES return NO_VALUES
return
return wrapper return wrapper
@@ -216,12 +215,10 @@ def _check_name_for_execution(inference_state, context, compare_node, name, trai
for name, trailer in potential_nodes: for name, trailer in potential_nodes:
if value_node.start_pos < name.start_pos < value_node.end_pos: if value_node.start_pos < name.start_pos < value_node.end_pos:
random_context = execution_context.create_context(name) random_context = execution_context.create_context(name)
iterator = _check_name_for_execution( yield from _check_name_for_execution(
inference_state, inference_state,
random_context, random_context,
compare_node, compare_node,
name, name,
trailer trailer
) )
for arguments in iterator:
yield arguments
+31 -38
View File
@@ -3,23 +3,25 @@ Filters are objects that you can use to filter names in different scopes. They
are needed for name resolution. are needed for name resolution.
""" """
from abc import abstractmethod from abc import abstractmethod
from typing import List, MutableMapping, Type
import weakref import weakref
from parso.tree import search_ancestor from parso.tree import search_ancestor
from parso.python.tree import Name, UsedNamesMapping
from jedi._compatibility import use_metaclass
from jedi.inference import flow_analysis from jedi.inference import flow_analysis
from jedi.inference.base_value import ValueSet, ValueWrapper, \ from jedi.inference.base_value import ValueSet, ValueWrapper, \
LazyValueWrapper LazyValueWrapper
from jedi.parser_utils import get_cached_parent_scope from jedi.parser_utils import get_cached_parent_scope
from jedi.inference.utils import to_list from jedi.inference.utils import to_list
from jedi.inference.names import TreeNameDefinition, ParamName, \ from jedi.inference.names import TreeNameDefinition, ParamName, \
AnonymousParamName, AbstractNameDefinition AnonymousParamName, AbstractNameDefinition, NameWrapper
_definition_name_cache: MutableMapping[UsedNamesMapping, List[Name]]
_definition_name_cache = weakref.WeakKeyDictionary() _definition_name_cache = weakref.WeakKeyDictionary()
class AbstractFilter(object): class AbstractFilter:
_until_position = None _until_position = None
def _filter(self, names): def _filter(self, names):
@@ -36,8 +38,8 @@ class AbstractFilter(object):
raise NotImplementedError raise NotImplementedError
class FilterWrapper(object): class FilterWrapper:
name_wrapper_class = None name_wrapper_class: Type[NameWrapper]
def __init__(self, wrapped_filter): def __init__(self, wrapped_filter):
self._wrapped_filter = wrapped_filter self._wrapped_filter = wrapped_filter
@@ -77,22 +79,20 @@ class AbstractUsedNamesFilter(AbstractFilter):
self._used_names = self._module_node.get_used_names() self._used_names = self._module_node.get_used_names()
self.parent_context = parent_context self.parent_context = parent_context
def get(self, name, **filter_kwargs): def get(self, name):
return self._convert_names(self._filter( return self._convert_names(self._filter(
_get_definition_names(self._used_names, name), _get_definition_names(self._used_names, name),
**filter_kwargs
)) ))
def _convert_names(self, names): def _convert_names(self, names):
return [self.name_class(self.parent_context, name) for name in names] return [self.name_class(self.parent_context, name) for name in names]
def values(self, **filter_kwargs): def values(self):
return self._convert_names( return self._convert_names(
name name
for name_key in self._used_names for name_key in self._used_names
for name in self._filter( for name in self._filter(
_get_definition_names(self._used_names, name_key), _get_definition_names(self._used_names, name_key),
**filter_kwargs
) )
) )
@@ -111,13 +111,13 @@ class ParserTreeFilter(AbstractUsedNamesFilter):
""" """
if node_context is None: if node_context is None:
node_context = parent_context node_context = parent_context
super(ParserTreeFilter, self).__init__(parent_context, node_context.tree_node) super().__init__(parent_context, node_context.tree_node)
self._node_context = node_context self._node_context = node_context
self._origin_scope = origin_scope self._origin_scope = origin_scope
self._until_position = until_position self._until_position = until_position
def _filter(self, names): def _filter(self, names):
names = super(ParserTreeFilter, self)._filter(names) names = super()._filter(names)
names = [n for n in names if self._is_name_reachable(n)] names = [n for n in names if self._is_name_reachable(n)]
return list(self._check_flows(names)) return list(self._check_flows(names))
@@ -145,7 +145,7 @@ class ParserTreeFilter(AbstractUsedNamesFilter):
class _FunctionExecutionFilter(ParserTreeFilter): class _FunctionExecutionFilter(ParserTreeFilter):
def __init__(self, parent_context, function_value, until_position, origin_scope): def __init__(self, parent_context, function_value, until_position, origin_scope):
super(_FunctionExecutionFilter, self).__init__( super().__init__(
parent_context, parent_context,
until_position=until_position, until_position=until_position,
origin_scope=origin_scope, origin_scope=origin_scope,
@@ -169,9 +169,9 @@ class _FunctionExecutionFilter(ParserTreeFilter):
class FunctionExecutionFilter(_FunctionExecutionFilter): class FunctionExecutionFilter(_FunctionExecutionFilter):
def __init__(self, *args, **kwargs): def __init__(self, *args, arguments, **kwargs):
self._arguments = kwargs.pop('arguments') # Python 2 super().__init__(*args, **kwargs)
super(FunctionExecutionFilter, self).__init__(*args, **kwargs) self._arguments = arguments
def _convert_param(self, param, name): def _convert_param(self, param, name):
return ParamName(self._function_value, name, self._arguments) return ParamName(self._function_value, name, self._arguments)
@@ -232,7 +232,7 @@ class DictFilter(AbstractFilter):
return '<%s: for {%s}>' % (self.__class__.__name__, keys) return '<%s: for {%s}>' % (self.__class__.__name__, keys)
class MergedFilter(object): class MergedFilter:
def __init__(self, *filters): def __init__(self, *filters):
self._filters = filters self._filters = filters
@@ -248,16 +248,16 @@ class MergedFilter(object):
class _BuiltinMappedMethod(ValueWrapper): class _BuiltinMappedMethod(ValueWrapper):
"""``Generator.__next__`` ``dict.values`` methods and so on.""" """``Generator.__next__`` ``dict.values`` methods and so on."""
api_type = u'function' api_type = 'function'
def __init__(self, value, method, builtin_func): def __init__(self, value, method, builtin_func):
super(_BuiltinMappedMethod, self).__init__(builtin_func) super().__init__(builtin_func)
self._value = value self._value = value
self._method = method self._method = method
def py__call__(self, arguments): def py__call__(self, arguments):
# TODO add TypeError if params are given/or not correct. # TODO add TypeError if params are given/or not correct.
return self._method(self._value) return self._method(self._value, arguments)
class SpecialMethodFilter(DictFilter): class SpecialMethodFilter(DictFilter):
@@ -266,14 +266,9 @@ class SpecialMethodFilter(DictFilter):
classes like Generator (for __next__, etc). classes like Generator (for __next__, etc).
""" """
class SpecialMethodName(AbstractNameDefinition): class SpecialMethodName(AbstractNameDefinition):
api_type = u'function' api_type = 'function'
def __init__(self, parent_context, string_name, value, builtin_value):
callable_, python_version = value
if python_version is not None and \
python_version != parent_context.inference_state.environment.version_info.major:
raise KeyError
def __init__(self, parent_context, string_name, callable_, builtin_value):
self.parent_context = parent_context self.parent_context = parent_context
self.string_name = string_name self.string_name = string_name
self._callable = callable_ self._callable = callable_
@@ -295,7 +290,7 @@ class SpecialMethodFilter(DictFilter):
]) ])
def __init__(self, value, dct, builtin_value): def __init__(self, value, dct, builtin_value):
super(SpecialMethodFilter, self).__init__(dct) super().__init__(dct)
self.value = value self.value = value
self._builtin_value = builtin_value self._builtin_value = builtin_value
""" """
@@ -311,7 +306,7 @@ class SpecialMethodFilter(DictFilter):
class _OverwriteMeta(type): class _OverwriteMeta(type):
def __init__(cls, name, bases, dct): def __init__(cls, name, bases, dct):
super(_OverwriteMeta, cls).__init__(name, bases, dct) super().__init__(name, bases, dct)
base_dct = {} base_dct = {}
for base_cls in reversed(cls.__bases__): for base_cls in reversed(cls.__bases__):
@@ -328,28 +323,26 @@ class _OverwriteMeta(type):
cls.overwritten_methods = base_dct cls.overwritten_methods = base_dct
class _AttributeOverwriteMixin(object): class _AttributeOverwriteMixin:
def get_filters(self, *args, **kwargs): def get_filters(self, *args, **kwargs):
yield SpecialMethodFilter(self, self.overwritten_methods, self._wrapped_value) yield SpecialMethodFilter(self, self.overwritten_methods, self._wrapped_value)
yield from self._wrapped_value.get_filters(*args, **kwargs)
for filter in self._wrapped_value.get_filters():
yield filter
class LazyAttributeOverwrite(use_metaclass(_OverwriteMeta, _AttributeOverwriteMixin, class LazyAttributeOverwrite(_AttributeOverwriteMixin, LazyValueWrapper,
LazyValueWrapper)): metaclass=_OverwriteMeta):
def __init__(self, inference_state): def __init__(self, inference_state):
self.inference_state = inference_state self.inference_state = inference_state
class AttributeOverwrite(use_metaclass(_OverwriteMeta, _AttributeOverwriteMixin, class AttributeOverwrite(_AttributeOverwriteMixin, ValueWrapper,
ValueWrapper)): metaclass=_OverwriteMeta):
pass pass
def publish_method(method_name, python_version_match=None): def publish_method(method_name):
def decorator(func): def decorator(func):
dct = func.__dict__.setdefault('registered_overwritten_methods', {}) dct = func.__dict__.setdefault('registered_overwritten_methods', {})
dct[method_name] = func, python_version_match dct[method_name] = func
return func return func
return decorator return decorator
+5 -3
View File
@@ -1,12 +1,14 @@
from typing import Dict, Optional
from jedi.parser_utils import get_flow_branch_keyword, is_scope, get_parent_scope from jedi.parser_utils import get_flow_branch_keyword, is_scope, get_parent_scope
from jedi.inference.recursion import execution_allowed from jedi.inference.recursion import execution_allowed
from jedi.inference.helpers import is_big_annoying_library from jedi.inference.helpers import is_big_annoying_library
class Status(object): class Status:
lookup_table = {} lookup_table: Dict[Optional[bool], 'Status'] = {}
def __init__(self, value, name): def __init__(self, value: Optional[bool], name: str) -> None:
self._value = value self._value = value
self._name = name self._name = name
Status.lookup_table[value] = self Status.lookup_table[value] = self
+4
View File
@@ -0,0 +1,4 @@
"""
It is unfortunately not well documented how stubs and annotations work in Jedi.
If somebody needs an introduction, please let me know.
"""
+66 -102
View File
@@ -6,15 +6,14 @@ as annotations in future python versions.
""" """
import re import re
from inspect import Parameter
from parso import ParserSyntaxError, parse from parso import ParserSyntaxError, parse
from jedi._compatibility import force_unicode, Parameter
from jedi.inference.cache import inference_state_method_cache from jedi.inference.cache import inference_state_method_cache
from jedi.inference.base_value import ValueSet, NO_VALUES from jedi.inference.base_value import ValueSet, NO_VALUES
from jedi.inference.gradual.base import DefineGenericBase, GenericClass from jedi.inference.gradual.base import DefineGenericBaseClass, GenericClass
from jedi.inference.gradual.generics import TupleGenericManager from jedi.inference.gradual.generics import TupleGenericManager
from jedi.inference.gradual.typing import TypingClassValueWithIndex
from jedi.inference.gradual.type_var import TypeVar from jedi.inference.gradual.type_var import TypeVar
from jedi.inference.helpers import is_string from jedi.inference.helpers import is_string
from jedi.inference.compiled import builtin_from_name from jedi.inference.compiled import builtin_from_name
@@ -54,8 +53,10 @@ def _infer_annotation_string(context, string, index=None):
value_set = context.infer_node(node) value_set = context.infer_node(node)
if index is not None: if index is not None:
value_set = value_set.filter( value_set = value_set.filter(
lambda value: value.array_type == u'tuple' # noqa lambda value: (
value.array_type == 'tuple'
and len(list(value.py__iter__())) >= index and len(list(value.py__iter__())) >= index
)
).py__simple_getitem__(index) ).py__simple_getitem__(index)
return value_set return value_set
@@ -63,7 +64,7 @@ def _infer_annotation_string(context, string, index=None):
def _get_forward_reference_node(context, string): def _get_forward_reference_node(context, string):
try: try:
new_node = context.inference_state.grammar.parse( new_node = context.inference_state.grammar.parse(
force_unicode(string), string,
start_symbol='eval_input', start_symbol='eval_input',
error_recovery=False error_recovery=False
) )
@@ -111,7 +112,7 @@ def _split_comment_param_declaration(decl_text):
@inference_state_method_cache() @inference_state_method_cache()
def infer_param(function_value, param, ignore_stars=False): def infer_param(function_value, param, ignore_stars=False):
values = _infer_param(function_value, param) values = _infer_param(function_value, param)
if ignore_stars: if ignore_stars or not values:
return values return values
inference_state = function_value.inference_state inference_state = function_value.inference_state
if param.star_count == 1: if param.star_count == 1:
@@ -119,7 +120,7 @@ def infer_param(function_value, param, ignore_stars=False):
return ValueSet([GenericClass( return ValueSet([GenericClass(
tuple_, tuple_,
TupleGenericManager((values,)), TupleGenericManager((values,)),
) for c in values]) )])
elif param.star_count == 2: elif param.star_count == 2:
dct = builtin_from_name(inference_state, 'dict') dct = builtin_from_name(inference_state, 'dict')
generics = ( generics = (
@@ -129,8 +130,7 @@ def infer_param(function_value, param, ignore_stars=False):
return ValueSet([GenericClass( return ValueSet([GenericClass(
dct, dct,
TupleGenericManager(generics), TupleGenericManager(generics),
) for c in values]) )])
pass
return values return values
@@ -140,8 +140,7 @@ def _infer_param(function_value, param):
""" """
annotation = param.annotation annotation = param.annotation
if annotation is None: if annotation is None:
# If no Python 3-style annotation, look for a Python 2-style comment # If no Python 3-style annotation, look for a comment annotation.
# annotation.
# Identify parameters to function in the same sequence as they would # Identify parameters to function in the same sequence as they would
# appear in a type comment. # appear in a type comment.
all_params = [child for child in param.parent.children all_params = [child for child in param.parent.children
@@ -206,7 +205,8 @@ def infer_return_types(function, arguments):
all_annotations = py__annotations__(function.tree_node) all_annotations = py__annotations__(function.tree_node)
annotation = all_annotations.get("return", None) annotation = all_annotations.get("return", None)
if annotation is None: if annotation is None:
# If there is no Python 3-type annotation, look for a Python 2-type annotation # If there is no Python 3-type annotation, look for an annotation
# comment.
node = function.tree_node node = function.tree_node
comment = parser_utils.get_following_comment_same_line(node) comment = parser_utils.get_following_comment_same_line(node)
if comment is None: if comment is None:
@@ -220,8 +220,6 @@ def infer_return_types(function, arguments):
function.get_default_param_context(), function.get_default_param_context(),
match.group(1).strip() match.group(1).strip()
).execute_annotation() ).execute_annotation()
if annotation is None:
return NO_VALUES
context = function.get_default_param_context() context = function.get_default_param_context()
unknown_type_vars = find_unknown_type_vars(context, annotation) unknown_type_vars = find_unknown_type_vars(context, annotation)
@@ -233,7 +231,7 @@ def infer_return_types(function, arguments):
return ValueSet.from_sets( return ValueSet.from_sets(
ann.define_generics(type_var_dict) ann.define_generics(type_var_dict)
if isinstance(ann, (DefineGenericBase, TypeVar)) else ValueSet({ann}) if isinstance(ann, (DefineGenericBaseClass, TypeVar)) else ValueSet({ann})
for ann in annotation_values for ann in annotation_values
).execute_annotation() ).execute_annotation()
@@ -269,29 +267,29 @@ def infer_type_vars_for_execution(function, arguments, annotation_dict):
elif kind is Parameter.VAR_KEYWORD: elif kind is Parameter.VAR_KEYWORD:
# TODO _dict_values is not public. # TODO _dict_values is not public.
actual_value_set = actual_value_set.try_merge('_dict_values') actual_value_set = actual_value_set.try_merge('_dict_values')
for ann in annotation_value_set: merge_type_var_dicts(
_merge_type_var_dicts(
annotation_variable_results, annotation_variable_results,
_infer_type_vars(ann, actual_value_set), annotation_value_set.infer_type_vars(actual_value_set),
) )
return annotation_variable_results return annotation_variable_results
def infer_return_for_callable(arguments, param_values, result_values): def infer_return_for_callable(arguments, param_values, result_values):
result = NO_VALUES all_type_vars = {}
for pv in param_values: for pv in param_values:
if pv.array_type == 'list': if pv.array_type == 'list':
type_var_dict = infer_type_vars_for_callable(arguments, pv.py__iter__()) type_var_dict = _infer_type_vars_for_callable(arguments, pv.py__iter__())
all_type_vars.update(type_var_dict)
result |= ValueSet.from_sets( return ValueSet.from_sets(
v.define_generics(type_var_dict) v.define_generics(all_type_vars)
if isinstance(v, (DefineGenericBase, TypeVar)) else ValueSet({v}) if isinstance(v, (DefineGenericBaseClass, TypeVar))
else ValueSet({v})
for v in result_values for v in result_values
).execute_annotation() ).execute_annotation()
return result
def infer_type_vars_for_callable(arguments, lazy_params): def _infer_type_vars_for_callable(arguments, lazy_params):
""" """
Infers type vars for the Calllable class: Infers type vars for the Calllable class:
@@ -302,15 +300,14 @@ def infer_type_vars_for_callable(arguments, lazy_params):
callable_param_values = lazy_callable_param.infer() callable_param_values = lazy_callable_param.infer()
# Infer unknown type var # Infer unknown type var
actual_value_set = lazy_value.infer() actual_value_set = lazy_value.infer()
for v in callable_param_values: merge_type_var_dicts(
_merge_type_var_dicts(
annotation_variable_results, annotation_variable_results,
_infer_type_vars(v, actual_value_set), callable_param_values.infer_type_vars(actual_value_set),
) )
return annotation_variable_results return annotation_variable_results
def _merge_type_var_dicts(base_dict, new_dict): def merge_type_var_dicts(base_dict, new_dict):
for type_var_name, values in new_dict.items(): for type_var_name, values in new_dict.items():
if values: if values:
try: try:
@@ -319,88 +316,55 @@ def _merge_type_var_dicts(base_dict, new_dict):
base_dict[type_var_name] = values base_dict[type_var_name] = values
def _infer_type_vars(annotation_value, value_set, is_class_value=False): def merge_pairwise_generics(annotation_value, annotated_argument_class):
""" """
This function tries to find information about undefined type vars and Match up the generic parameters from the given argument class to the
returns a dict from type var name to value set. target annotation.
This is for example important to understand what `iter([1])` returns. This walks the generic parameters immediately within the annotation and
According to typeshed, `iter` returns an `Iterator[_T]`: argument's type, in order to determine the concrete values of the
annotation's parameters for the current case.
def iter(iterable: Iterable[_T]) -> Iterator[_T]: ... For example, given the following code:
This functions would generate `int` for `_T` in this case, because it def values(mapping: Mapping[K, V]) -> List[V]: ...
unpacks the `Iterable`.
for val in values({1: 'a'}):
val
Then this function should be given representations of `Mapping[K, V]`
and `Mapping[int, str]`, so that it can determine that `K` is `int and
`V` is `str`.
Note that it is responsibility of the caller to traverse the MRO of the
argument type as needed in order to find the type matching the
annotation (in this case finding `Mapping[int, str]` as a parent of
`Dict[int, str]`).
Parameters
----------
`annotation_value`: represents the annotation to infer the concrete
parameter types of.
`annotated_argument_class`: represents the annotated class of the
argument being passed to the object annotated by `annotation_value`.
""" """
type_var_dict = {} type_var_dict = {}
if isinstance(annotation_value, TypeVar):
if not is_class_value:
return {annotation_value.py__name__(): value_set.py__class__()}
return {annotation_value.py__name__(): value_set}
elif isinstance(annotation_value, TypingClassValueWithIndex):
name = annotation_value.py__name__()
if name == 'Type':
given = annotation_value.get_generics()
if given:
for nested_annotation_value in given[0]:
_merge_type_var_dicts(
type_var_dict,
_infer_type_vars(
nested_annotation_value,
value_set,
is_class_value=True,
)
)
elif name == 'Callable':
given = annotation_value.get_generics()
if len(given) == 2:
for nested_annotation_value in given[1]:
_merge_type_var_dicts(
type_var_dict,
_infer_type_vars(
nested_annotation_value,
value_set.execute_annotation(),
)
)
elif isinstance(annotation_value, GenericClass):
name = annotation_value.py__name__()
if name == 'Iterable':
given = annotation_value.get_generics()
if given:
for nested_annotation_value in given[0]:
_merge_type_var_dicts(
type_var_dict,
_infer_type_vars(
nested_annotation_value,
value_set.merge_types_of_iterate()
)
)
elif name == 'Mapping':
given = annotation_value.get_generics()
if len(given) == 2:
for value in value_set:
try:
method = value.get_mapping_item_values
except AttributeError:
continue
key_values, value_values = method()
for nested_annotation_value in given[0]: if not isinstance(annotated_argument_class, DefineGenericBaseClass):
_merge_type_var_dicts( return type_var_dict
annotation_generics = annotation_value.get_generics()
actual_generics = annotated_argument_class.get_generics()
for annotation_generics_set, actual_generic_set in zip(annotation_generics, actual_generics):
merge_type_var_dicts(
type_var_dict, type_var_dict,
_infer_type_vars( annotation_generics_set.infer_type_vars(actual_generic_set.execute_annotation()),
nested_annotation_value,
key_values,
)
)
for nested_annotation_value in given[1]:
_merge_type_var_dicts(
type_var_dict,
_infer_type_vars(
nested_annotation_value,
value_values,
)
) )
return type_var_dict return type_var_dict
+141 -28
View File
@@ -23,10 +23,9 @@ class _BoundTypeVarName(AbstractNameDefinition):
def iter_(): def iter_():
for value in self._value_set: for value in self._value_set:
# Replace any with the constraints if they are there. # Replace any with the constraints if they are there.
from jedi.inference.gradual.typing import Any from jedi.inference.gradual.typing import AnyClass
if isinstance(value, Any): if isinstance(value, AnyClass):
for constraint in self._type_var.constraints: yield from self._type_var.constraints
yield constraint
else: else:
yield value yield value
return ValueSet(iter_()) return ValueSet(iter_())
@@ -38,7 +37,7 @@ class _BoundTypeVarName(AbstractNameDefinition):
return '<%s %s -> %s>' % (self.__class__.__name__, self.py__name__(), self._value_set) return '<%s %s -> %s>' % (self.__class__.__name__, self.py__name__(), self._value_set)
class _TypeVarFilter(object): class _TypeVarFilter:
""" """
A filter for all given variables in a class. A filter for all given variables in a class.
@@ -70,18 +69,17 @@ class _TypeVarFilter(object):
class _AnnotatedClassContext(ClassContext): class _AnnotatedClassContext(ClassContext):
def get_filters(self, *args, **kwargs): def get_filters(self, *args, **kwargs):
filters = super(_AnnotatedClassContext, self).get_filters( filters = super().get_filters(
*args, **kwargs *args, **kwargs
) )
for f in filters: yield from filters
yield f
# The type vars can only be looked up if it's a global search and # The type vars can only be looked up if it's a global search and
# not a direct lookup on the class. # not a direct lookup on the class.
yield self._value.get_type_var_filter() yield self._value.get_type_var_filter()
class DefineGenericBase(LazyValueWrapper): class DefineGenericBaseClass(LazyValueWrapper):
def __init__(self, generics_manager): def __init__(self, generics_manager):
self._generics_manager = generics_manager self._generics_manager = generics_manager
@@ -99,7 +97,7 @@ class DefineGenericBase(LazyValueWrapper):
for generic_set in self.get_generics(): for generic_set in self.get_generics():
values = NO_VALUES values = NO_VALUES
for generic in generic_set: for generic in generic_set:
if isinstance(generic, (GenericClass, TypeVar)): if isinstance(generic, (DefineGenericBaseClass, TypeVar)):
result = generic.define_generics(type_var_dict) result = generic.define_generics(type_var_dict)
values |= result values |= result
if result != ValueSet({generic}): if result != ValueSet({generic}):
@@ -119,7 +117,7 @@ class DefineGenericBase(LazyValueWrapper):
)]) )])
def is_same_class(self, other): def is_same_class(self, other):
if not isinstance(other, DefineGenericBase): if not isinstance(other, DefineGenericBaseClass):
return False return False
if self.tree_node != other.tree_node: if self.tree_node != other.tree_node:
@@ -138,11 +136,19 @@ class DefineGenericBase(LazyValueWrapper):
any( any(
# TODO why is this ordering the correct one? # TODO why is this ordering the correct one?
cls2.is_same_class(cls1) cls2.is_same_class(cls1)
for cls1 in class_set1 # TODO I'm still not sure gather_annotation_classes is a good
for cls2 in class_set2 # idea. They are essentially here to avoid comparing Tuple <=>
# tuple and instead compare tuple <=> tuple, but at the moment
# the whole `is_same_class` and `is_sub_class` matching is just
# not in the best shape.
for cls1 in class_set1.gather_annotation_classes()
for cls2 in class_set2.gather_annotation_classes()
) for class_set1, class_set2 in zip(given_params1, given_params2) ) for class_set1, class_set2 in zip(given_params1, given_params2)
) )
def get_signatures(self):
return []
def __repr__(self): def __repr__(self):
return '<%s: %s%s>' % ( return '<%s: %s%s>' % (
self.__class__.__name__, self.__class__.__name__,
@@ -151,7 +157,7 @@ class DefineGenericBase(LazyValueWrapper):
) )
class GenericClass(ClassMixin, DefineGenericBase): class GenericClass(DefineGenericBaseClass, ClassMixin):
""" """
A class that is defined with generics, might be something simple like: A class that is defined with generics, might be something simple like:
@@ -159,17 +165,29 @@ class GenericClass(ClassMixin, DefineGenericBase):
my_foo_int_cls = Foo[int] my_foo_int_cls = Foo[int]
""" """
def __init__(self, class_value, generics_manager): def __init__(self, class_value, generics_manager):
super(GenericClass, self).__init__(generics_manager) super().__init__(generics_manager)
self._class_value = class_value self._class_value = class_value
def _get_wrapped_value(self): def _get_wrapped_value(self):
return self._class_value return self._class_value
def get_type_hint(self, add_class_info=True):
n = self.py__name__()
# Not sure if this is the best way to do this, but all of these types
# are a bit special in that they have type aliases and other ways to
# become lower case. It's probably better to make them upper case,
# because that's what you can use in annotations.
n = dict(list="List", dict="Dict", set="Set", tuple="Tuple").get(n, n)
s = n + self._generics_manager.get_type_hint()
if add_class_info:
return 'Type[%s]' % s
return s
def get_type_var_filter(self): def get_type_var_filter(self):
return _TypeVarFilter(self.get_generics(), self.list_type_vars()) return _TypeVarFilter(self.get_generics(), self.list_type_vars())
def py__call__(self, arguments): def py__call__(self, arguments):
instance, = super(GenericClass, self).py__call__(arguments) instance, = super().py__call__(arguments)
return ValueSet([_GenericInstanceWrapper(instance)]) return ValueSet([_GenericInstanceWrapper(instance)])
def _as_context(self): def _as_context(self):
@@ -178,21 +196,64 @@ class GenericClass(ClassMixin, DefineGenericBase):
@to_list @to_list
def py__bases__(self): def py__bases__(self):
for base in self._wrapped_value.py__bases__(): for base in self._wrapped_value.py__bases__():
yield _LazyGenericBaseClass(self, base) yield _LazyGenericBaseClass(self, base, self._generics_manager)
def _create_instance_with_generics(self, generics_manager): def _create_instance_with_generics(self, generics_manager):
return GenericClass(self._class_value, generics_manager) return GenericClass(self._class_value, generics_manager)
def is_sub_class_of(self, class_value): def is_sub_class_of(self, class_value):
if super(GenericClass, self).is_sub_class_of(class_value): if super().is_sub_class_of(class_value):
return True return True
return self._class_value.is_sub_class_of(class_value) return self._class_value.is_sub_class_of(class_value)
def with_generics(self, generics_tuple):
return self._class_value.with_generics(generics_tuple)
class _LazyGenericBaseClass(object): def infer_type_vars(self, value_set):
def __init__(self, class_value, lazy_base_class): # Circular
from jedi.inference.gradual.annotation import merge_pairwise_generics, merge_type_var_dicts
annotation_name = self.py__name__()
type_var_dict = {}
if annotation_name == 'Iterable':
annotation_generics = self.get_generics()
if annotation_generics:
return annotation_generics[0].infer_type_vars(
value_set.merge_types_of_iterate(),
)
else:
# Note: we need to handle the MRO _in order_, so we need to extract
# the elements from the set first, then handle them, even if we put
# them back in a set afterwards.
for py_class in value_set:
if py_class.is_instance() and not py_class.is_compiled():
py_class = py_class.get_annotated_class_object()
else:
continue
if py_class.api_type != 'class':
# Functions & modules don't have an MRO and we're not
# expecting a Callable (those are handled separately within
# TypingClassValueWithIndex).
continue
for parent_class in py_class.py__mro__():
class_name = parent_class.py__name__()
if annotation_name == class_name:
merge_type_var_dicts(
type_var_dict,
merge_pairwise_generics(self, parent_class),
)
break
return type_var_dict
class _LazyGenericBaseClass:
def __init__(self, class_value, lazy_base_class, generics_manager):
self._class_value = class_value self._class_value = class_value
self._lazy_base_class = lazy_base_class self._lazy_base_class = lazy_base_class
self._generics_manager = generics_manager
@iterator_to_value_set @iterator_to_value_set
def infer(self): def infer(self):
@@ -204,6 +265,16 @@ class _LazyGenericBaseClass(object):
base._wrapped_value, base._wrapped_value,
TupleGenericManager(tuple(self._remap_type_vars(base))), TupleGenericManager(tuple(self._remap_type_vars(base))),
) )
else:
if base.is_class_mixin():
# This case basically allows classes like `class Foo(List)`
# to be used like `Foo[int]`. The generics are not
# necessary and can be used later.
yield GenericClass.create_cached(
base.inference_state,
base,
self._generics_manager,
)
else: else:
yield base yield base
@@ -225,6 +296,9 @@ class _LazyGenericBaseClass(object):
new |= ValueSet([type_var]) new |= ValueSet([type_var])
yield new yield new
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self._lazy_base_class)
class _GenericInstanceWrapper(ValueWrapper): class _GenericInstanceWrapper(ValueWrapper):
def py__stop_iteration_returns(self): def py__stop_iteration_returns(self):
@@ -236,9 +310,12 @@ class _GenericInstanceWrapper(ValueWrapper):
except IndexError: except IndexError:
pass pass
elif cls.py__name__() == 'Iterator': elif cls.py__name__() == 'Iterator':
return ValueSet([builtin_from_name(self.inference_state, u'None')]) return ValueSet([builtin_from_name(self.inference_state, 'None')])
return self._wrapped_value.py__stop_iteration_returns() return self._wrapped_value.py__stop_iteration_returns()
def get_type_hint(self, add_class_info=True):
return self._wrapped_value.class_value.get_type_hint(add_class_info=False)
class _PseudoTreeNameClass(Value): class _PseudoTreeNameClass(Value):
""" """
@@ -250,8 +327,10 @@ class _PseudoTreeNameClass(Value):
this class. Essentially this class makes it possible to goto that `Tuple` this class. Essentially this class makes it possible to goto that `Tuple`
name, without affecting anything else negatively. name, without affecting anything else negatively.
""" """
api_type = 'class'
def __init__(self, parent_context, tree_name): def __init__(self, parent_context, tree_name):
super(_PseudoTreeNameClass, self).__init__( super().__init__(
parent_context.inference_state, parent_context.inference_state,
parent_context parent_context
) )
@@ -276,15 +355,17 @@ class _PseudoTreeNameClass(Value):
yield EmptyFilter() yield EmptyFilter()
def py__class__(self): def py__class__(self):
# TODO this is obviously not correct, but at least gives us a class if # This might not be 100% correct, but it is good enough. The details of
# we have none. Some of these objects don't really have a base class in # the typing library are not really an issue for Jedi.
# typeshed. return builtin_from_name(self.inference_state, 'type')
return builtin_from_name(self.inference_state, u'object')
@property @property
def name(self): def name(self):
return ValueName(self, self._tree_name) return ValueName(self, self._tree_name)
def get_qualified_names(self):
return (self._tree_name.value,)
def __repr__(self): def __repr__(self):
return '%s(%s)' % (self.__class__.__name__, self._tree_name.value) return '%s(%s)' % (self.__class__.__name__, self._tree_name.value)
@@ -302,13 +383,16 @@ class BaseTypingValue(LazyValueWrapper):
def _get_wrapped_value(self): def _get_wrapped_value(self):
return _PseudoTreeNameClass(self.parent_context, self._tree_name) return _PseudoTreeNameClass(self.parent_context, self._tree_name)
def get_signatures(self):
return self._wrapped_value.get_signatures()
def __repr__(self): def __repr__(self):
return '%s(%s)' % (self.__class__.__name__, self._tree_name.value) return '%s(%s)' % (self.__class__.__name__, self._tree_name.value)
class BaseTypingValueWithGenerics(DefineGenericBase): class BaseTypingClassWithGenerics(DefineGenericBaseClass):
def __init__(self, parent_context, tree_name, generics_manager): def __init__(self, parent_context, tree_name, generics_manager):
super(BaseTypingValueWithGenerics, self).__init__(generics_manager) super().__init__(generics_manager)
self.inference_state = parent_context.inference_state self.inference_state = parent_context.inference_state
self.parent_context = parent_context self.parent_context = parent_context
self._tree_name = tree_name self._tree_name = tree_name
@@ -319,3 +403,32 @@ class BaseTypingValueWithGenerics(DefineGenericBase):
def __repr__(self): def __repr__(self):
return '%s(%s%s)' % (self.__class__.__name__, self._tree_name.value, return '%s(%s%s)' % (self.__class__.__name__, self._tree_name.value,
self._generics_manager) self._generics_manager)
class BaseTypingInstance(LazyValueWrapper):
def __init__(self, parent_context, class_value, tree_name, generics_manager):
self.inference_state = class_value.inference_state
self.parent_context = parent_context
self._class_value = class_value
self._tree_name = tree_name
self._generics_manager = generics_manager
def py__class__(self):
return self._class_value
def get_annotated_class_object(self):
return self._class_value
def get_qualified_names(self):
return (self.py__name__(),)
@property
def name(self):
return ValueName(self, self._tree_name)
def _get_wrapped_value(self):
object_, = builtin_from_name(self.inference_state, 'object').execute_annotation()
return object_
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self._generics_manager)
+19 -5
View File
@@ -3,6 +3,8 @@ from jedi.inference.base_value import ValueSet, \
NO_VALUES NO_VALUES
from jedi.inference.utils import to_list from jedi.inference.utils import to_list
from jedi.inference.gradual.stub_value import StubModuleValue from jedi.inference.gradual.stub_value import StubModuleValue
from jedi.inference.gradual.typeshed import try_to_load_stub_cached
from jedi.inference.value.decorator import Decoratee
def _stub_to_python_value_set(stub_value, ignore_compiled=False): def _stub_to_python_value_set(stub_value, ignore_compiled=False):
@@ -10,8 +12,13 @@ def _stub_to_python_value_set(stub_value, ignore_compiled=False):
if not stub_module_context.is_stub(): if not stub_module_context.is_stub():
return ValueSet([stub_value]) return ValueSet([stub_value])
decorates = None
if isinstance(stub_value, Decoratee):
decorates = stub_value._original_value
was_instance = stub_value.is_instance() was_instance = stub_value.is_instance()
if was_instance: if was_instance:
arguments = getattr(stub_value, '_arguments', None)
stub_value = stub_value.py__class__() stub_value = stub_value.py__class__()
qualified_names = stub_value.get_qualified_names() qualified_names = stub_value.get_qualified_names()
@@ -24,11 +31,12 @@ def _stub_to_python_value_set(stub_value, ignore_compiled=False):
method_name = qualified_names[-1] method_name = qualified_names[-1]
qualified_names = qualified_names[:-1] qualified_names = qualified_names[:-1]
was_instance = True was_instance = True
arguments = None
values = _infer_from_stub(stub_module_context, qualified_names, ignore_compiled) values = _infer_from_stub(stub_module_context, qualified_names, ignore_compiled)
if was_instance: if was_instance:
values = ValueSet.from_sets( values = ValueSet.from_sets(
c.execute_with_values() c.execute_with_values() if arguments is None else c.execute(arguments)
for c in values for c in values
if c.is_class() if c.is_class()
) )
@@ -36,6 +44,8 @@ def _stub_to_python_value_set(stub_value, ignore_compiled=False):
# Now that the instance has been properly created, we can simply get # Now that the instance has been properly created, we can simply get
# the method. # the method.
values = values.py__getattribute__(method_name) values = values.py__getattribute__(method_name)
if decorates is not None:
values = ValueSet(Decoratee(v, decorates) for v in values)
return values return values
@@ -73,6 +83,12 @@ def _try_stub_to_python_names(names, prefer_stub_to_compiled=False):
converted_names = converted.goto(name.get_public_name()) converted_names = converted.goto(name.get_public_name())
if converted_names: if converted_names:
for n in converted_names: for n in converted_names:
if n.get_root_context().is_stub():
# If it's a stub again, it means we're going in
# a circle. Probably some imports make it a
# stub again.
yield name
else:
yield n yield n
continue continue
yield name yield name
@@ -81,8 +97,7 @@ def _try_stub_to_python_names(names, prefer_stub_to_compiled=False):
def _load_stub_module(module): def _load_stub_module(module):
if module.is_stub(): if module.is_stub():
return module return module
from jedi.inference.gradual.typeshed import _try_to_load_stub_cached return try_to_load_stub_cached(
return _try_to_load_stub_cached(
module.inference_state, module.inference_state,
import_names=module.string_names, import_names=module.string_names,
python_value_set=ValueSet([module]), python_value_set=ValueSet([module]),
@@ -120,8 +135,7 @@ def _python_to_stub_names(names, fallback_to_python=False):
if converted: if converted:
converted_names = converted.goto(name.get_public_name()) converted_names = converted.goto(name.get_public_name())
if converted_names: if converted_names:
for n in converted_names: yield from converted_names
yield n
continue continue
if fallback_to_python: if fallback_to_python:
# This is the part where if we haven't found anything, just return # This is the part where if we haven't found anything, just return
+4 -1
View File
@@ -23,7 +23,7 @@ def _resolve_forward_references(context, value_set):
yield value yield value
class _AbstractGenericManager(object): class _AbstractGenericManager:
def get_index_and_execute(self, index): def get_index_and_execute(self, index):
try: try:
return self[index].execute_annotation() return self[index].execute_annotation()
@@ -31,6 +31,9 @@ class _AbstractGenericManager(object):
debug.warning('No param #%s found for annotation %s', index, self) debug.warning('No param #%s found for annotation %s', index, self)
return NO_VALUES return NO_VALUES
def get_type_hint(self):
return '[%s]' % ', '.join(t.get_type_hint(add_class_info=False) for t in self.to_tuple())
class LazyGenericManager(_AbstractGenericManager): class LazyGenericManager(_AbstractGenericManager):
def __init__(self, context_of_index, index_value): def __init__(self, context_of_index, index_value):
+16 -19
View File
@@ -10,7 +10,7 @@ class StubModuleValue(ModuleValue):
_module_name_class = StubModuleName _module_name_class = StubModuleName
def __init__(self, non_stub_value_set, *args, **kwargs): def __init__(self, non_stub_value_set, *args, **kwargs):
super(StubModuleValue, self).__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.non_stub_value_set = non_stub_value_set self.non_stub_value_set = non_stub_value_set
def is_stub(self): def is_stub(self):
@@ -30,7 +30,7 @@ class StubModuleValue(ModuleValue):
pass pass
else: else:
names.update(method()) names.update(method())
names.update(super(StubModuleValue, self).sub_modules_dict()) names.update(super().sub_modules_dict())
return names return names
def _get_stub_filters(self, origin_scope): def _get_stub_filters(self, origin_scope):
@@ -40,14 +40,11 @@ class StubModuleValue(ModuleValue):
)] + list(self.iter_star_filters()) )] + list(self.iter_star_filters())
def get_filters(self, origin_scope=None): def get_filters(self, origin_scope=None):
filters = super(StubModuleValue, self).get_filters(origin_scope) filters = super().get_filters(origin_scope)
next(filters) # Ignore the first filter and replace it with our own next(filters, None) # Ignore the first filter and replace it with our own
stub_filters = self._get_stub_filters(origin_scope=origin_scope) stub_filters = self._get_stub_filters(origin_scope=origin_scope)
for f in stub_filters: yield from stub_filters
yield f yield from filters
for f in filters:
yield f
def _as_context(self): def _as_context(self):
return StubModuleContext(self) return StubModuleContext(self)
@@ -57,15 +54,16 @@ class StubModuleContext(ModuleContext):
def get_filters(self, until_position=None, origin_scope=None): def get_filters(self, until_position=None, origin_scope=None):
# Make sure to ignore the position, because positions are not relevant # Make sure to ignore the position, because positions are not relevant
# for stubs. # for stubs.
return super(StubModuleContext, self).get_filters(origin_scope=origin_scope) return super().get_filters(origin_scope=origin_scope)
class TypingModuleWrapper(StubModuleValue): class TypingModuleWrapper(StubModuleValue):
def get_filters(self, *args, **kwargs): def get_filters(self, *args, **kwargs):
filters = super(TypingModuleWrapper, self).get_filters(*args, **kwargs) filters = super().get_filters(*args, **kwargs)
yield TypingModuleFilterWrapper(next(filters)) f = next(filters, None)
for f in filters: assert f is not None
yield f yield TypingModuleFilterWrapper(f)
yield from filters
def _as_context(self): def _as_context(self):
return TypingModuleContext(self) return TypingModuleContext(self)
@@ -73,17 +71,16 @@ class TypingModuleWrapper(StubModuleValue):
class TypingModuleContext(ModuleContext): class TypingModuleContext(ModuleContext):
def get_filters(self, *args, **kwargs): def get_filters(self, *args, **kwargs):
filters = super(TypingModuleContext, self).get_filters(*args, **kwargs) filters = super().get_filters(*args, **kwargs)
yield TypingModuleFilterWrapper(next(filters)) yield TypingModuleFilterWrapper(next(filters, None))
for f in filters: yield from filters
yield f
class StubFilter(ParserTreeFilter): class StubFilter(ParserTreeFilter):
name_class = StubName name_class = StubName
def _is_name_reachable(self, name): def _is_name_reachable(self, name):
if not super(StubFilter, self)._is_name_reachable(name): if not super()._is_name_reachable(name):
return False return False
# Imports in stub files are only public if they have an "as" # Imports in stub files are only public if they have an "as"
+28 -12
View File
@@ -1,10 +1,9 @@
from jedi._compatibility import unicode, force_unicode
from jedi import debug from jedi import debug
from jedi.inference.base_value import ValueSet, NO_VALUES from jedi.inference.base_value import ValueSet, NO_VALUES, ValueWrapper
from jedi.inference.gradual.base import BaseTypingValue from jedi.inference.gradual.base import BaseTypingValue
class TypeVarClass(BaseTypingValue): class TypeVarClass(ValueWrapper):
def py__call__(self, arguments): def py__call__(self, arguments):
unpacked = arguments.unpack() unpacked = arguments.unpack()
@@ -18,9 +17,9 @@ class TypeVarClass(BaseTypingValue):
return ValueSet([TypeVar.create_cached( return ValueSet([TypeVar.create_cached(
self.inference_state, self.inference_state,
self.parent_context, self.parent_context,
self._tree_name, tree_name=self.tree_node.name,
var_name, var_name=var_name,
unpacked unpacked_args=unpacked,
)]) )])
def _find_string_name(self, lazy_value): def _find_string_name(self, lazy_value):
@@ -40,17 +39,14 @@ class TypeVarClass(BaseTypingValue):
return None return None
else: else:
safe_value = method(default=None) safe_value = method(default=None)
if self.inference_state.environment.version_info.major == 2: if isinstance(safe_value, str):
if isinstance(safe_value, bytes):
return force_unicode(safe_value)
if isinstance(safe_value, (str, unicode)):
return safe_value return safe_value
return None return None
class TypeVar(BaseTypingValue): class TypeVar(BaseTypingValue):
def __init__(self, parent_context, tree_name, var_name, unpacked_args): def __init__(self, parent_context, tree_name, var_name, unpacked_args):
super(TypeVar, self).__init__(parent_context, tree_name) super().__init__(parent_context, tree_name)
self._var_name = var_name self._var_name = var_name
self._constraints_lazy_values = [] self._constraints_lazy_values = []
@@ -102,10 +98,30 @@ class TypeVar(BaseTypingValue):
else: else:
if found: if found:
return found return found
return self._get_classes() or ValueSet({self}) return ValueSet({self})
def execute_annotation(self): def execute_annotation(self):
return self._get_classes().execute_annotation() return self._get_classes().execute_annotation()
def infer_type_vars(self, value_set):
def iterate():
for v in value_set:
cls = v.py__class__()
if v.is_function() or v.is_class():
cls = TypeWrapper(cls, v)
yield cls
annotation_name = self.py__name__()
return {annotation_name: ValueSet(iterate())}
def __repr__(self): def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self.py__name__()) return '<%s: %s>' % (self.__class__.__name__, self.py__name__())
class TypeWrapper(ValueWrapper):
def __init__(self, wrapped_value, original_value):
super().__init__(wrapped_value)
self._original_value = original_value
def execute_annotation(self):
return ValueSet({self._original_value})
+70 -45
View File
@@ -1,74 +1,81 @@
import os import os
import re import re
from functools import wraps from functools import wraps
from collections import namedtuple
from typing import Dict, Mapping, Tuple
from pathlib import Path
from jedi import settings
from jedi.file_io import FileIO from jedi.file_io import FileIO
from jedi._compatibility import FileNotFoundError, cast_path from jedi._compatibility import cast_path
from jedi.parser_utils import get_cached_code_lines from jedi.parser_utils import get_cached_code_lines
from jedi.inference.base_value import ValueSet, NO_VALUES from jedi.inference.base_value import ValueSet, NO_VALUES
from jedi.inference.gradual.stub_value import TypingModuleWrapper, StubModuleValue from jedi.inference.gradual.stub_value import TypingModuleWrapper, StubModuleValue
from jedi.inference.value import ModuleValue from jedi.inference.value import ModuleValue
_jedi_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) _jedi_path = Path(__file__).parent.parent.parent
TYPESHED_PATH = os.path.join(_jedi_path, 'third_party', 'typeshed') TYPESHED_PATH = _jedi_path.joinpath('third_party', 'typeshed')
DJANGO_INIT_PATH = _jedi_path.joinpath('third_party', 'django-stubs',
'django-stubs', '__init__.pyi')
_IMPORT_MAP = dict( _IMPORT_MAP = dict(
_collections='collections', _collections='collections',
_socket='socket', _socket='socket',
) )
PathInfo = namedtuple('PathInfo', 'path is_third_party')
def _merge_create_stub_map(directories):
def _merge_create_stub_map(path_infos):
map_ = {} map_ = {}
for directory in directories: for directory_path_info in path_infos:
map_.update(_create_stub_map(directory)) map_.update(_create_stub_map(directory_path_info))
return map_ return map_
def _create_stub_map(directory): def _create_stub_map(directory_path_info):
""" """
Create a mapping of an importable name in Python to a stub file. Create a mapping of an importable name in Python to a stub file.
""" """
def generate(): def generate():
try: try:
listed = os.listdir(directory) listed = os.listdir(directory_path_info.path)
except (FileNotFoundError, OSError): except (FileNotFoundError, NotADirectoryError):
# OSError is Python 2
return return
for entry in listed: for entry in listed:
entry = cast_path(entry) entry = cast_path(entry)
path = os.path.join(directory, entry) path = os.path.join(directory_path_info.path, entry)
if os.path.isdir(path): if os.path.isdir(path):
init = os.path.join(path, '__init__.pyi') init = os.path.join(path, '__init__.pyi')
if os.path.isfile(init): if os.path.isfile(init):
yield entry, init yield entry, PathInfo(init, directory_path_info.is_third_party)
elif entry.endswith('.pyi') and os.path.isfile(path): elif entry.endswith('.pyi') and os.path.isfile(path):
name = entry[:-4] name = entry[:-4]
if name != '__init__': if name != '__init__':
yield name, path yield name, PathInfo(path, directory_path_info.is_third_party)
# Create a dictionary from the tuple generator. # Create a dictionary from the tuple generator.
return dict(generate()) return dict(generate())
def _get_typeshed_directories(version_info): def _get_typeshed_directories(version_info):
check_version_list = ['2and3', str(version_info.major)] check_version_list = ['2and3', '3']
for base in ['stdlib', 'third_party']: for base in ['stdlib', 'third_party']:
base = os.path.join(TYPESHED_PATH, base) base_path = TYPESHED_PATH.joinpath(base)
base_list = os.listdir(base) base_list = os.listdir(base_path)
for base_list_entry in base_list: for base_list_entry in base_list:
match = re.match(r'(\d+)\.(\d+)$', base_list_entry) match = re.match(r'(\d+)\.(\d+)$', base_list_entry)
if match is not None: if match is not None:
if int(match.group(1)) == version_info.major \ if match.group(1) == '3' and int(match.group(2)) <= version_info.minor:
and int(match.group(2)) <= version_info.minor:
check_version_list.append(base_list_entry) check_version_list.append(base_list_entry)
for check_version in check_version_list: for check_version in check_version_list:
yield os.path.join(base, check_version) is_third_party = base != 'stdlib'
yield PathInfo(str(base_path.joinpath(check_version)), is_third_party)
_version_cache = {} _version_cache: Dict[Tuple[int, int], Mapping[str, PathInfo]] = {}
def _cache_stub_file_map(version_info): def _cache_stub_file_map(version_info):
@@ -102,12 +109,9 @@ def import_module_decorator(func):
# ``os.path``, because it's a very important one in Python # ``os.path``, because it's a very important one in Python
# that is being achieved by messing with ``sys.modules`` in # that is being achieved by messing with ``sys.modules`` in
# ``os``. # ``os``.
python_parent = next(iter(parent_module_values))
if python_parent is None:
python_parent, = inference_state.import_module(('os',), prefer_stubs=False)
python_value_set = ValueSet.from_sets( python_value_set = ValueSet.from_sets(
func(inference_state, (n,), None, sys_path,) func(inference_state, (n,), None, sys_path,)
for n in [u'posixpath', u'ntpath', u'macpath', u'os2emxpath'] for n in ['posixpath', 'ntpath', 'macpath', 'os2emxpath']
) )
else: else:
python_value_set = ValueSet.from_sets( python_value_set = ValueSet.from_sets(
@@ -116,10 +120,10 @@ def import_module_decorator(func):
) )
inference_state.module_cache.add(import_names, python_value_set) inference_state.module_cache.add(import_names, python_value_set)
if not prefer_stubs: if not prefer_stubs or import_names[0] in settings.auto_import_modules:
return python_value_set return python_value_set
stub = _try_to_load_stub_cached(inference_state, import_names, python_value_set, stub = try_to_load_stub_cached(inference_state, import_names, python_value_set,
parent_module_value, sys_path) parent_module_value, sys_path)
if stub is not None: if stub is not None:
return ValueSet([stub]) return ValueSet([stub])
@@ -128,7 +132,10 @@ def import_module_decorator(func):
return wrapper return wrapper
def _try_to_load_stub_cached(inference_state, import_names, *args, **kwargs): def try_to_load_stub_cached(inference_state, import_names, *args, **kwargs):
if import_names is None:
return None
try: try:
return inference_state.stub_module_cache[import_names] return inference_state.stub_module_cache[import_names]
except KeyError: except KeyError:
@@ -152,7 +159,7 @@ def _try_to_load_stub(inference_state, import_names, python_value_set,
""" """
if parent_module_value is None and len(import_names) > 1: if parent_module_value is None and len(import_names) > 1:
try: try:
parent_module_value = _try_to_load_stub_cached( parent_module_value = try_to_load_stub_cached(
inference_state, import_names[:-1], NO_VALUES, inference_state, import_names[:-1], NO_VALUES,
parent_module_value=None, sys_path=sys_path) parent_module_value=None, sys_path=sys_path)
except KeyError: except KeyError:
@@ -172,6 +179,13 @@ def _try_to_load_stub(inference_state, import_names, python_value_set,
) )
if m is not None: if m is not None:
return m return m
if import_names[0] == 'django' and python_value_set:
return _try_to_load_stub_from_file(
inference_state,
python_value_set,
file_io=FileIO(str(DJANGO_INIT_PATH)),
import_names=import_names,
)
# 2. Try to load pyi files next to py files. # 2. Try to load pyi files next to py files.
for c in python_value_set: for c in python_value_set:
@@ -184,8 +198,8 @@ def _try_to_load_stub(inference_state, import_names, python_value_set,
file_paths = [] file_paths = []
if c.is_namespace(): if c.is_namespace():
file_paths = [os.path.join(p, '__init__.pyi') for p in c.py__path__()] file_paths = [os.path.join(p, '__init__.pyi') for p in c.py__path__()]
elif file_path is not None and file_path.endswith('.py'): elif file_path is not None and file_path.suffix == '.py':
file_paths = [file_path + 'i'] file_paths = [str(file_path) + 'i']
for file_path in file_paths: for file_path in file_paths:
m = _try_to_load_stub_from_file( m = _try_to_load_stub_from_file(
@@ -239,38 +253,49 @@ def _load_from_typeshed(inference_state, python_value_set, parent_module_value,
# Only if it's a package (= a folder) something can be # Only if it's a package (= a folder) something can be
# imported. # imported.
return None return None
path = parent_module_value.py__path__() paths = parent_module_value.py__path__()
map_ = _merge_create_stub_map(path) # Once the initial package has been loaded, the sub packages will
# always be loaded, regardless if they are there or not. This makes
# sense, IMO, because stubs take preference, even if the original
# library doesn't provide a module (it could be dynamic). ~dave
map_ = _merge_create_stub_map([PathInfo(p, is_third_party=False) for p in paths])
if map_ is not None: if map_ is not None:
path = map_.get(import_name) path_info = map_.get(import_name)
if path is not None: if path_info is not None and (not path_info.is_third_party or python_value_set):
return _try_to_load_stub_from_file( return _try_to_load_stub_from_file(
inference_state, inference_state,
python_value_set, python_value_set,
file_io=FileIO(path), file_io=FileIO(path_info.path),
import_names=import_names, import_names=import_names,
) )
def _try_to_load_stub_from_file(inference_state, python_value_set, file_io, import_names): def _try_to_load_stub_from_file(inference_state, python_value_set, file_io, import_names):
try: try:
stub_module_node = inference_state.parse( stub_module_node = parse_stub_module(inference_state, file_io)
file_io=file_io, except OSError:
cache=True,
use_latest_grammar=True
)
except (OSError, IOError): # IOError is Python 2 only
# The file that you're looking for doesn't exist (anymore). # The file that you're looking for doesn't exist (anymore).
return None return None
else: else:
return create_stub_module( return create_stub_module(
inference_state, python_value_set, stub_module_node, file_io, inference_state, inference_state.latest_grammar, python_value_set,
import_names stub_module_node, file_io, import_names
) )
def create_stub_module(inference_state, python_value_set, stub_module_node, file_io, import_names): def parse_stub_module(inference_state, file_io):
return inference_state.parse(
file_io=file_io,
cache=True,
diff_cache=settings.fast_parser,
cache_path=settings.cache_directory,
use_latest_grammar=True
)
def create_stub_module(inference_state, grammar, python_value_set,
stub_module_node, file_io, import_names):
if import_names == ('typing',): if import_names == ('typing',):
module_cls = TypingModuleWrapper module_cls = TypingModuleWrapper
else: else:
@@ -282,7 +307,7 @@ def create_stub_module(inference_state, python_value_set, stub_module_node, file
string_names=import_names, string_names=import_names,
# The code was loaded with latest_grammar, so use # The code was loaded with latest_grammar, so use
# that. # that.
code_lines=get_cached_code_lines(inference_state.latest_grammar, file_io.path), code_lines=get_cached_code_lines(grammar, file_io.path),
is_package=file_name == '__init__.pyi', is_package=file_name == '__init__.pyi',
) )
return stub_module_value return stub_module_value
+183 -42
View File
@@ -5,16 +5,19 @@ values.
This file deals with all the typing.py cases. This file deals with all the typing.py cases.
""" """
import itertools
from jedi import debug from jedi import debug
from jedi.inference.compiled import builtin_from_name from jedi.inference.compiled import builtin_from_name, create_simple_object
from jedi.inference.base_value import ValueSet, NO_VALUES, Value, \ from jedi.inference.base_value import ValueSet, NO_VALUES, Value, \
LazyValueWrapper LazyValueWrapper, ValueWrapper
from jedi.inference.lazy_value import LazyKnownValues from jedi.inference.lazy_value import LazyKnownValues
from jedi.inference.arguments import repack_with_argument_clinic from jedi.inference.arguments import repack_with_argument_clinic
from jedi.inference.filters import FilterWrapper from jedi.inference.filters import FilterWrapper
from jedi.inference.names import NameWrapper, ValueName from jedi.inference.names import NameWrapper, ValueName
from jedi.inference.value.klass import ClassMixin from jedi.inference.value.klass import ClassMixin
from jedi.inference.gradual.base import BaseTypingValue, BaseTypingValueWithGenerics from jedi.inference.gradual.base import BaseTypingValue, \
BaseTypingClassWithGenerics, BaseTypingInstance
from jedi.inference.gradual.type_var import TypeVarClass from jedi.inference.gradual.type_var import TypeVarClass
from jedi.inference.gradual.generics import LazyGenericManager, TupleGenericManager from jedi.inference.gradual.generics import LazyGenericManager, TupleGenericManager
@@ -60,43 +63,41 @@ class TypingModuleName(NameWrapper):
# have any effects there (because it's never executed). # have any effects there (because it's never executed).
return return
elif name == 'TypeVar': elif name == 'TypeVar':
yield TypeVarClass.create_cached( cls, = self._wrapped_name.infer()
inference_state, self.parent_context, self.tree_name) yield TypeVarClass.create_cached(inference_state, cls)
elif name == 'Any': elif name == 'Any':
yield Any.create_cached( yield AnyClass.create_cached(
inference_state, self.parent_context, self.tree_name) inference_state, self.parent_context, self.tree_name)
elif name == 'TYPE_CHECKING': elif name == 'TYPE_CHECKING':
# This is needed for e.g. imports that are only available for type # This is needed for e.g. imports that are only available for type
# checking or are in cycles. The user can then check this variable. # checking or are in cycles. The user can then check this variable.
yield builtin_from_name(inference_state, u'True') yield builtin_from_name(inference_state, 'True')
elif name == 'overload': elif name == 'overload':
yield OverloadFunction.create_cached( yield OverloadFunction.create_cached(
inference_state, self.parent_context, self.tree_name) inference_state, self.parent_context, self.tree_name)
elif name == 'NewType': elif name == 'NewType':
yield NewTypeFunction.create_cached( v, = self._wrapped_name.infer()
inference_state, self.parent_context, self.tree_name) yield NewTypeFunction.create_cached(inference_state, v)
elif name == 'cast': elif name == 'cast':
yield CastFunction.create_cached( cast_fn, = self._wrapped_name.infer()
inference_state, self.parent_context, self.tree_name) yield CastFunction.create_cached(inference_state, cast_fn)
elif name == 'TypedDict': elif name == 'TypedDict':
# TODO doesn't even exist in typeshed/typing.py, yet. But will be # TODO doesn't even exist in typeshed/typing.py, yet. But will be
# added soon. # added soon.
pass yield TypedDictClass.create_cached(
elif name in ('no_type_check', 'no_type_check_decorator'): inference_state, self.parent_context, self.tree_name)
# This is not necessary, as long as we are not doing type checking.
for c in self._wrapped_name.infer(): # Fuck my life Python 2
yield c
else: else:
# Everything else shouldn't be relevant for type checking. # Not necessary, as long as we are not doing type checking:
for c in self._wrapped_name.infer(): # Fuck my life Python 2 # no_type_check & no_type_check_decorator
yield c # Everything else shouldn't be relevant...
yield from self._wrapped_name.infer()
class TypingModuleFilterWrapper(FilterWrapper): class TypingModuleFilterWrapper(FilterWrapper):
name_wrapper_class = TypingModuleName name_wrapper_class = TypingModuleName
class TypingValueWithIndex(BaseTypingValueWithGenerics): class ProxyWithGenerics(BaseTypingClassWithGenerics):
def execute_annotation(self): def execute_annotation(self):
string_name = self._tree_name.value string_name = self._tree_name.value
@@ -108,7 +109,7 @@ class TypingValueWithIndex(BaseTypingValueWithGenerics):
# Optional is basically just saying it's either None or the actual # Optional is basically just saying it's either None or the actual
# type. # type.
return self.gather_annotation_classes().execute_annotation() \ return self.gather_annotation_classes().execute_annotation() \
| ValueSet([builtin_from_name(self.inference_state, u'None')]) | ValueSet([builtin_from_name(self.inference_state, 'None')])
elif string_name == 'Type': elif string_name == 'Type':
# The type is actually already given in the index_value # The type is actually already given in the index_value
return self._generics_manager[0] return self._generics_manager[0]
@@ -125,6 +126,7 @@ class TypingValueWithIndex(BaseTypingValueWithGenerics):
cls = mapped[string_name] cls = mapped[string_name]
return ValueSet([cls( return ValueSet([cls(
self.parent_context, self.parent_context,
self,
self._tree_name, self._tree_name,
generics_manager=self._generics_manager, generics_manager=self._generics_manager,
)]) )])
@@ -133,15 +135,33 @@ class TypingValueWithIndex(BaseTypingValueWithGenerics):
return ValueSet.from_sets(self._generics_manager.to_tuple()) return ValueSet.from_sets(self._generics_manager.to_tuple())
def _create_instance_with_generics(self, generics_manager): def _create_instance_with_generics(self, generics_manager):
return TypingValueWithIndex( return ProxyWithGenerics(
self.parent_context, self.parent_context,
self._tree_name, self._tree_name,
generics_manager generics_manager
) )
def infer_type_vars(self, value_set):
annotation_generics = self.get_generics()
if not annotation_generics:
return {}
annotation_name = self.py__name__()
if annotation_name == 'Optional':
# Optional[T] is equivalent to Union[T, None]. In Jedi unions
# are represented by members within a ValueSet, so we extract
# the T from the Optional[T] by removing the None value.
none = builtin_from_name(self.inference_state, 'None')
return annotation_generics[0].infer_type_vars(
value_set.filter(lambda x: x != none),
)
return {}
class ProxyTypingValue(BaseTypingValue): class ProxyTypingValue(BaseTypingValue):
index_class = TypingValueWithIndex index_class = ProxyWithGenerics
def with_generics(self, generics_tuple): def with_generics(self, generics_tuple):
return self.index_class.create_cached( return self.index_class.create_cached(
@@ -179,12 +199,45 @@ class _TypingClassMixin(ClassMixin):
return ValueName(self, self._tree_name) return ValueName(self, self._tree_name)
class TypingClassValueWithIndex(_TypingClassMixin, TypingValueWithIndex): class TypingClassWithGenerics(ProxyWithGenerics, _TypingClassMixin):
pass def infer_type_vars(self, value_set):
type_var_dict = {}
annotation_generics = self.get_generics()
if not annotation_generics:
return type_var_dict
annotation_name = self.py__name__()
if annotation_name == 'Type':
return annotation_generics[0].infer_type_vars(
# This is basically a trick to avoid extra code: We execute the
# incoming classes to be able to use the normal code for type
# var inference.
value_set.execute_annotation(),
)
elif annotation_name == 'Callable':
if len(annotation_generics) == 2:
return annotation_generics[1].infer_type_vars(
value_set.execute_annotation(),
)
elif annotation_name == 'Tuple':
tuple_annotation, = self.execute_annotation()
return tuple_annotation.infer_type_vars(value_set)
return type_var_dict
def _create_instance_with_generics(self, generics_manager):
return TypingClassWithGenerics(
self.parent_context,
self._tree_name,
generics_manager
)
class ProxyTypingClassValue(_TypingClassMixin, ProxyTypingValue): class ProxyTypingClassValue(ProxyTypingValue, _TypingClassMixin):
index_class = TypingClassValueWithIndex index_class = TypingClassWithGenerics
class TypeAlias(LazyValueWrapper): class TypeAlias(LazyValueWrapper):
@@ -206,8 +259,6 @@ class TypeAlias(LazyValueWrapper):
def _get_wrapped_value(self): def _get_wrapped_value(self):
module_name, class_name = self._actual.split('.') module_name, class_name = self._actual.split('.')
if self.inference_state.environment.version_info.major == 2 and module_name == 'builtins':
module_name = '__builtin__'
# TODO use inference_state.import_module? # TODO use inference_state.import_module?
from jedi.inference.imports import Importer from jedi.inference.imports import Importer
@@ -223,8 +274,11 @@ class TypeAlias(LazyValueWrapper):
def gather_annotation_classes(self): def gather_annotation_classes(self):
return ValueSet([self._get_wrapped_value()]) return ValueSet([self._get_wrapped_value()])
def get_signatures(self):
return []
class Callable(BaseTypingValueWithGenerics):
class Callable(BaseTypingInstance):
def py__call__(self, arguments): def py__call__(self, arguments):
""" """
def x() -> Callable[[Callable[..., _T]], _T]: ... def x() -> Callable[[Callable[..., _T]], _T]: ...
@@ -241,12 +295,7 @@ class Callable(BaseTypingValueWithGenerics):
return infer_return_for_callable(arguments, param_values, result_values) return infer_return_for_callable(arguments, param_values, result_values)
class Tuple(LazyValueWrapper): class Tuple(BaseTypingInstance):
def __init__(self, parent_context, name, generics_manager):
self.inference_state = parent_context.inference_state
self.parent_context = parent_context
self._generics_manager = generics_manager
def _is_homogenous(self): def _is_homogenous(self):
# To specify a variable-length tuple of homogeneous type, Tuple[T, ...] # To specify a variable-length tuple of homogeneous type, Tuple[T, ...]
# is used. # is used.
@@ -282,16 +331,60 @@ class Tuple(LazyValueWrapper):
.py__getattribute__('tuple').execute_annotation() .py__getattribute__('tuple').execute_annotation()
return tuple_ return tuple_
@property
def name(self):
return self._wrapped_value.name
class Generic(BaseTypingValueWithGenerics): def infer_type_vars(self, value_set):
# Circular
from jedi.inference.gradual.annotation import merge_pairwise_generics, merge_type_var_dicts
value_set = value_set.filter(
lambda x: x.py__name__().lower() == 'tuple',
)
if self._is_homogenous():
# The parameter annotation is of the form `Tuple[T, ...]`,
# so we treat the incoming tuple like a iterable sequence
# rather than a positional container of elements.
return self._class_value.get_generics()[0].infer_type_vars(
value_set.merge_types_of_iterate(),
)
else:
# The parameter annotation has only explicit type parameters
# (e.g: `Tuple[T]`, `Tuple[T, U]`, `Tuple[T, U, V]`, etc.) so we
# treat the incoming values as needing to match the annotation
# exactly, just as we would for non-tuple annotations.
type_var_dict = {}
for element in value_set:
try:
method = element.get_annotated_class_object
except AttributeError:
# This might still happen, because the tuple name matching
# above is not 100% correct, so just catch the remaining
# cases here.
continue
py_class = method()
merge_type_var_dicts(
type_var_dict,
merge_pairwise_generics(self._class_value, py_class),
)
return type_var_dict
class Generic(BaseTypingInstance):
pass pass
class Protocol(BaseTypingValueWithGenerics): class Protocol(BaseTypingInstance):
pass pass
class Any(BaseTypingValue): class AnyClass(BaseTypingValue):
def execute_annotation(self): def execute_annotation(self):
debug.warning('Used Any - returned no results') debug.warning('Used Any - returned no results')
return NO_VALUES return NO_VALUES
@@ -304,7 +397,7 @@ class OverloadFunction(BaseTypingValue):
return func_value_set return func_value_set
class NewTypeFunction(BaseTypingValue): class NewTypeFunction(ValueWrapper):
def py__call__(self, arguments): def py__call__(self, arguments):
ordered_args = arguments.unpack() ordered_args = arguments.unpack()
next(ordered_args, (None, None)) next(ordered_args, (None, None))
@@ -322,10 +415,14 @@ class NewTypeFunction(BaseTypingValue):
class NewType(Value): class NewType(Value):
def __init__(self, inference_state, parent_context, tree_node, type_value_set): def __init__(self, inference_state, parent_context, tree_node, type_value_set):
super(NewType, self).__init__(inference_state, parent_context) super().__init__(inference_state, parent_context)
self._type_value_set = type_value_set self._type_value_set = type_value_set
self.tree_node = tree_node self.tree_node = tree_node
def py__class__(self):
c, = self._type_value_set.py__class__()
return c
def py__call__(self, arguments): def py__call__(self, arguments):
return self._type_value_set.execute_annotation() return self._type_value_set.execute_annotation()
@@ -335,7 +432,51 @@ class NewType(Value):
return CompiledValueName(self, 'NewType') return CompiledValueName(self, 'NewType')
class CastFunction(BaseTypingValue): class CastFunction(ValueWrapper):
@repack_with_argument_clinic('type, object, /') @repack_with_argument_clinic('type, object, /')
def py__call__(self, type_value_set, object_value_set): def py__call__(self, type_value_set, object_value_set):
return type_value_set.execute_annotation() return type_value_set.execute_annotation()
class TypedDictClass(BaseTypingValue):
"""
This class has no responsibilities and is just here to make sure that typed
dicts can be identified.
"""
class TypedDict(LazyValueWrapper):
"""Represents the instance version of ``TypedDictClass``."""
def __init__(self, definition_class):
self.inference_state = definition_class.inference_state
self.parent_context = definition_class.parent_context
self.tree_node = definition_class.tree_node
self._definition_class = definition_class
@property
def name(self):
return ValueName(self, self.tree_node.name)
def py__simple_getitem__(self, index):
if isinstance(index, str):
return ValueSet.from_sets(
name.infer()
for filter in self._definition_class.get_filters(is_instance=True)
for name in filter.get(index)
)
return NO_VALUES
def get_key_values(self):
filtered_values = itertools.chain.from_iterable((
f.values()
for f in self._definition_class.get_filters(is_instance=True)
))
return ValueSet({
create_simple_object(self.inference_state, v.string_name)
for v in filtered_values
})
def _get_wrapped_value(self):
d, = self.inference_state.builtins_module.py__getattribute__('dict')
result, = d.execute_with_values()
return result
+16 -11
View File
@@ -1,29 +1,34 @@
import os from pathlib import Path
from jedi.inference.gradual.typeshed import TYPESHED_PATH, create_stub_module from jedi.inference.gradual.typeshed import TYPESHED_PATH, create_stub_module
def load_proper_stub_module(inference_state, file_io, import_names, module_node): def load_proper_stub_module(inference_state, grammar, file_io, import_names, module_node):
""" """
This function is given a random .pyi file and should return the proper This function is given a random .pyi file and should return the proper
module. module.
""" """
path = file_io.path path = file_io.path
assert path.endswith('.pyi') path = Path(path)
if path.startswith(TYPESHED_PATH): assert path.suffix == '.pyi'
# /foo/stdlib/3/os/__init__.pyi -> stdlib/3/os/__init__ try:
rest = path[len(TYPESHED_PATH) + 1: -4] relative_path = path.relative_to(TYPESHED_PATH)
split_paths = tuple(rest.split(os.path.sep)) except ValueError:
# Remove the stdlib/3 or third_party/3.5 part pass
import_names = split_paths[2:] else:
if import_names[-1] == '__init__': # /[...]/stdlib/3/os/__init__.pyi -> stdlib/3/os/__init__
rest = relative_path.with_suffix('')
# Remove the stdlib/3 or third_party/3.6 part
import_names = rest.parts[2:]
if rest.name == '__init__':
import_names = import_names[:-1] import_names = import_names[:-1]
if import_names is not None: if import_names is not None:
actual_value_set = inference_state.import_module(import_names, prefer_stubs=False) actual_value_set = inference_state.import_module(import_names, prefer_stubs=False)
stub = create_stub_module( stub = create_stub_module(
inference_state, actual_value_set, module_node, file_io, import_names inference_state, grammar, actual_value_set,
module_node, file_io, import_names
) )
inference_state.stub_module_cache[import_names] = stub inference_state.stub_module_cache[import_names] = stub
return stub return stub
+6 -35
View File
@@ -7,19 +7,17 @@ from contextlib import contextmanager
from parso.python import tree from parso.python import tree
from jedi._compatibility import unicode
from jedi.parser_utils import get_parent_scope
def is_stdlib_path(path): def is_stdlib_path(path):
# Python standard library paths look like this: # Python standard library paths look like this:
# /usr/lib/python3.5/... # /usr/lib/python3.9/...
# TODO The implementation below is probably incorrect and not complete. # TODO The implementation below is probably incorrect and not complete.
if 'dist-packages' in path or 'site-packages' in path: parts = path.parts
if 'dist-packages' in parts or 'site-packages' in parts:
return False return False
base_path = os.path.join(sys.prefix, 'lib', 'python') base_path = os.path.join(sys.prefix, 'lib', 'python')
return bool(re.match(re.escape(base_path) + r'\d.\d', path)) return bool(re.match(re.escape(base_path) + r'\d.\d', str(path)))
def deep_ast_copy(obj): def deep_ast_copy(obj):
@@ -122,35 +120,8 @@ def get_names_of_node(node):
return list(chain.from_iterable(get_names_of_node(c) for c in children)) return list(chain.from_iterable(get_names_of_node(c) for c in children))
def get_module_names(module, all_scopes):
"""
Returns a dictionary with name parts as keys and their call paths as
values.
"""
names = list(chain.from_iterable(module.get_used_names().values()))
if not all_scopes:
# We have to filter all the names that don't have the module as a
# parent_scope. There's None as a parent, because nodes in the module
# node have the parent module and not suite as all the others.
# Therefore it's important to catch that case.
def is_module_scope_name(name):
parent_scope = get_parent_scope(name)
# async functions have an extra wrapper. Strip it.
if parent_scope and parent_scope.type == 'async_stmt':
parent_scope = parent_scope.parent
return parent_scope in (module, None)
names = [n for n in names if is_module_scope_name(n)]
return names
def is_string(value): def is_string(value):
if value.inference_state.environment.version_info.major == 2: return value.is_compiled() and isinstance(value.get_safe_value(default=None), str)
str_classes = (unicode, bytes)
else:
str_classes = (unicode,)
return value.is_compiled() and isinstance(value.get_safe_value(default=None), str_classes)
def is_literal(value): def is_literal(value):
@@ -168,7 +139,7 @@ def get_int_or_none(value):
def get_str_or_none(value): def get_str_or_none(value):
return _get_safe_value_or_none(value, (bytes, unicode)) return _get_safe_value_or_none(value, str)
def is_number(value): def is_number(value):
+118 -43
View File
@@ -5,20 +5,18 @@ not any actual importing done. This module is about finding modules in the
filesystem. This can be quite tricky sometimes, because Python imports are not filesystem. This can be quite tricky sometimes, because Python imports are not
always that simple. always that simple.
This module uses imp for python up to 3.2 and importlib for python 3.3 on; the
correct implementation is delegated to _compatibility.
This module also supports import autocompletion, which means to complete This module also supports import autocompletion, which means to complete
statements like ``from datetim`` (cursor at the end would return ``datetime``). statements like ``from datetim`` (cursor at the end would return ``datetime``).
""" """
import os import os
from pathlib import Path
from parso.python import tree from parso.python import tree
from parso.tree import search_ancestor from parso.tree import search_ancestor
from jedi._compatibility import ImplicitNSInfo, force_unicode
from jedi import debug from jedi import debug
from jedi import settings from jedi import settings
from jedi.file_io import FolderIO
from jedi.parser_utils import get_cached_code_lines from jedi.parser_utils import get_cached_code_lines
from jedi.inference import sys_path from jedi.inference import sys_path
from jedi.inference import helpers from jedi.inference import helpers
@@ -28,12 +26,13 @@ from jedi.inference.utils import unite
from jedi.inference.cache import inference_state_method_cache from jedi.inference.cache import inference_state_method_cache
from jedi.inference.names import ImportName, SubModuleName from jedi.inference.names import ImportName, SubModuleName
from jedi.inference.base_value import ValueSet, NO_VALUES from jedi.inference.base_value import ValueSet, NO_VALUES
from jedi.inference.gradual.typeshed import import_module_decorator from jedi.inference.gradual.typeshed import import_module_decorator, \
from jedi.inference.value.module import iter_module_names create_stub_module, parse_stub_module
from jedi.inference.compiled.subprocess.functions import ImplicitNSInfo
from jedi.plugins import plugin_manager from jedi.plugins import plugin_manager
class ModuleCache(object): class ModuleCache:
def __init__(self): def __init__(self):
self._name_cache = {} self._name_cache = {}
@@ -151,7 +150,7 @@ def _level_to_base_import_path(project_path, directory, level):
return None, directory return None, directory
class Importer(object): class Importer:
def __init__(self, inference_state, import_path, module_context, level=0): def __init__(self, inference_state, import_path, module_context, level=0):
""" """
An implementation similar to ``__import__``. Use `follow` An implementation similar to ``__import__``. Use `follow`
@@ -191,25 +190,26 @@ class Importer(object):
import_path = base + tuple(import_path) import_path = base + tuple(import_path)
else: else:
path = module_context.py__file__() path = module_context.py__file__()
project_path = self._inference_state.project.path
import_path = list(import_path) import_path = list(import_path)
if path is None: if path is None:
# If no path is defined, our best guess is that the current # If no path is defined, our best guess is that the current
# file is edited by a user on the current working # file is edited by a user on the current working
# directory. We need to add an initial path, because it # directory. We need to add an initial path, because it
# will get removed as the name of the current file. # will get removed as the name of the current file.
directory = os.getcwd() directory = project_path
else: else:
directory = os.path.dirname(path) directory = os.path.dirname(path)
base_import_path, base_directory = _level_to_base_import_path( base_import_path, base_directory = _level_to_base_import_path(
self._inference_state.project._path, directory, level, project_path, directory, level,
) )
if base_directory is None: if base_directory is None:
# Everything is lost, the relative import does point # Everything is lost, the relative import does point
# somewhere out of the filesystem. # somewhere out of the filesystem.
self._infer_possible = False self._infer_possible = False
else: else:
self._fixed_sys_path = [force_unicode(base_directory)] self._fixed_sys_path = [base_directory]
if base_import_path is None: if base_import_path is None:
if import_path: if import_path:
@@ -238,11 +238,49 @@ class Importer(object):
# inference we want to show the user as much as possible. # inference we want to show the user as much as possible.
# See GH #1446. # See GH #1446.
self._inference_state.get_sys_path(add_init_paths=not is_completion) self._inference_state.get_sys_path(add_init_paths=not is_completion)
+ sys_path.check_sys_path_modifications(self._module_context) + [
str(p) for p
in sys_path.check_sys_path_modifications(self._module_context)
]
) )
def follow(self): def follow(self):
if not self.import_path or not self._infer_possible: if not self.import_path:
if self._fixed_sys_path:
# This is a bit of a special case, that maybe should be
# revisited. If the project path is wrong or the user uses
# relative imports the wrong way, we might end up here, where
# the `fixed_sys_path == project.path` in that case we kind of
# use the project.path.parent directory as our path. This is
# usually not a problem, except if imports in other places are
# using the same names. Example:
#
# foo/ < #1
# - setup.py
# - foo/ < #2
# - __init__.py
# - foo.py < #3
#
# If the top foo is our project folder and somebody uses
# `from . import foo` in `setup.py`, it will resolve to foo #2,
# which means that the import for foo.foo is cached as
# `__init__.py` (#2) and not as `foo.py` (#3). This is usually
# not an issue, because this case is probably pretty rare, but
# might be an issue for some people.
#
# However for most normal cases where we work with different
# file names, this code path hits where we basically change the
# project path to an ancestor of project path.
from jedi.inference.value.namespace import ImplicitNamespaceValue
import_path = (os.path.basename(self._fixed_sys_path[0]),)
ns = ImplicitNamespaceValue(
self._inference_state,
string_names=import_path,
paths=self._fixed_sys_path,
)
return ValueSet({ns})
return NO_VALUES
if not self._infer_possible:
return NO_VALUES return NO_VALUES
# Check caches first # Check caches first
@@ -264,24 +302,15 @@ class Importer(object):
Get the names of all modules in the search_path. This means file names Get the names of all modules in the search_path. This means file names
and not names defined in the files. and not names defined in the files.
""" """
names = []
# add builtin module names
if search_path is None and in_module is None:
names += [
ImportName(self._module_context, name)
for name in self._inference_state.compiled_subprocess.get_builtin_module_names()
]
if search_path is None: if search_path is None:
search_path = self._sys_path_with_modifications(is_completion=True) sys_path = self._sys_path_with_modifications(is_completion=True)
for name in iter_module_names(self._inference_state, search_path):
if in_module is None:
n = ImportName(self._module_context, name)
else: else:
n = SubModuleName(in_module.as_context(), name) sys_path = search_path
names.append(n) return list(iter_module_names(
return names self._inference_state, self._module_context, sys_path,
module_cls=ImportName if in_module is None else SubModuleName,
add_builtin_modules=search_path is None and in_module is None,
))
def completion_names(self, inference_state, only_modules=False): def completion_names(self, inference_state, only_modules=False):
""" """
@@ -310,7 +339,7 @@ class Importer(object):
values = self.follow() values = self.follow()
for value in values: for value in values:
# Non-modules are not completable. # Non-modules are not completable.
if value.api_type != 'module': # not a module if value.api_type not in ('module', 'namespace'): # not a module
continue continue
if not value.is_compiled(): if not value.is_compiled():
# sub_modules_dict is not implemented for compiled modules. # sub_modules_dict is not implemented for compiled modules.
@@ -339,7 +368,7 @@ def import_module_by_names(inference_state, import_names, sys_path=None,
sys_path = inference_state.get_sys_path() sys_path = inference_state.get_sys_path()
str_import_names = tuple( str_import_names = tuple(
force_unicode(i.value if isinstance(i, tree.Name) else i) i.value if isinstance(i, tree.Name) else i
for i in import_names for i in import_names
) )
value_set = [None] value_set = [None]
@@ -440,7 +469,7 @@ def _load_python_module(inference_state, file_io,
file_io=file_io, file_io=file_io,
cache=True, cache=True,
diff_cache=settings.fast_parser, diff_cache=settings.fast_parser,
cache_path=settings.cache_directory cache_path=settings.cache_directory,
) )
from jedi.inference.value import ModuleValue from jedi.inference.value import ModuleValue
@@ -454,8 +483,12 @@ def _load_python_module(inference_state, file_io,
def _load_builtin_module(inference_state, import_names=None, sys_path=None): def _load_builtin_module(inference_state, import_names=None, sys_path=None):
project = inference_state.project
if sys_path is None: if sys_path is None:
sys_path = inference_state.get_sys_path() sys_path = inference_state.get_sys_path()
if not project._load_unsafe_extensions:
safe_paths = project._get_base_sys_path(inference_state)
sys_path = [p for p in sys_path if p in safe_paths]
dotted_name = '.'.join(import_names) dotted_name = '.'.join(import_names)
assert dotted_name is not None assert dotted_name is not None
@@ -467,25 +500,43 @@ def _load_builtin_module(inference_state, import_names=None, sys_path=None):
return module return module
def load_module_from_path(inference_state, file_io, base_names=None): def load_module_from_path(inference_state, file_io, import_names=None, is_package=None):
""" """
This should pretty much only be used for get_modules_containing_name. It's This should pretty much only be used for get_modules_containing_name. It's
here to ensure that a random path is still properly loaded into the Jedi here to ensure that a random path is still properly loaded into the Jedi
module structure. module structure.
""" """
path = file_io.path path = Path(file_io.path)
if base_names: if import_names is None:
module_name = os.path.basename(path)
module_name = sys_path.remove_python_path_suffix(module_name)
is_package = module_name == '__init__'
if is_package:
import_names = base_names
else:
import_names = base_names + (module_name,)
else:
e_sys_path = inference_state.get_sys_path() e_sys_path = inference_state.get_sys_path()
import_names, is_package = sys_path.transform_path_to_dotted(e_sys_path, path) import_names, is_package = sys_path.transform_path_to_dotted(e_sys_path, path)
else:
assert isinstance(is_package, bool)
is_stub = path.suffix == '.pyi'
if is_stub:
folder_io = file_io.get_parent_folder()
if folder_io.path.endswith('-stubs'):
folder_io = FolderIO(folder_io.path[:-6])
if path.name == '__init__.pyi':
python_file_io = folder_io.get_file_io('__init__.py')
else:
python_file_io = folder_io.get_file_io(import_names[-1] + '.py')
try:
v = load_module_from_path(
inference_state, python_file_io,
import_names, is_package=is_package
)
values = ValueSet([v])
except FileNotFoundError:
values = NO_VALUES
return create_stub_module(
inference_state, inference_state.latest_grammar, values,
parse_stub_module(inference_state, file_io), file_io, import_names
)
else:
module = _load_python_module( module = _load_python_module(
inference_state, file_io, inference_state, file_io,
import_names=import_names, import_names=import_names,
@@ -495,6 +546,15 @@ def load_module_from_path(inference_state, file_io, base_names=None):
return module return module
def load_namespace_from_path(inference_state, folder_io):
import_names, is_package = sys_path.transform_path_to_dotted(
inference_state.get_sys_path(),
Path(folder_io.path)
)
from jedi.inference.value.namespace import ImplicitNamespaceValue
return ImplicitNamespaceValue(inference_state, import_names, [folder_io.path])
def follow_error_node_imports_if_possible(context, name): def follow_error_node_imports_if_possible(context, name):
error_node = tree.search_ancestor(name, 'error_node') error_node = tree.search_ancestor(name, 'error_node')
if error_node is not None: if error_node is not None:
@@ -522,3 +582,18 @@ def follow_error_node_imports_if_possible(context, name):
return Importer( return Importer(
context.inference_state, names, context.get_root_context(), level).follow() context.inference_state, names, context.get_root_context(), level).follow()
return None return None
def iter_module_names(inference_state, module_context, search_path,
module_cls=ImportName, add_builtin_modules=True):
"""
Get the names of all modules in the search_path. This means file names
and not names defined in the files.
"""
# add builtin module names
if add_builtin_modules:
for name in inference_state.compiled_subprocess.get_builtin_module_names():
yield module_cls(module_context, name)
for name in inference_state.compiled_subprocess.iter_module_names(search_path):
yield module_cls(module_context, name)
+9 -7
View File
@@ -1,10 +1,12 @@
from jedi.inference.base_value import ValueSet, NO_VALUES from jedi.inference.base_value import ValueSet, NO_VALUES
from jedi.common.utils import monkeypatch from jedi.common import monkeypatch
class AbstractLazyValue(object): class AbstractLazyValue:
def __init__(self, data): def __init__(self, data, min=1, max=1):
self.data = data self.data = data
self.min = min
self.max = max
def __repr__(self): def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self.data) return '<%s: %s>' % (self.__class__.__name__, self.data)
@@ -26,16 +28,16 @@ class LazyKnownValues(AbstractLazyValue):
class LazyUnknownValue(AbstractLazyValue): class LazyUnknownValue(AbstractLazyValue):
def __init__(self): def __init__(self, min=1, max=1):
super(LazyUnknownValue, self).__init__(None) super().__init__(None, min, max)
def infer(self): def infer(self):
return NO_VALUES return NO_VALUES
class LazyTreeValue(AbstractLazyValue): class LazyTreeValue(AbstractLazyValue):
def __init__(self, context, node): def __init__(self, context, node, min=1, max=1):
super(LazyTreeValue, self).__init__(node) super().__init__(node, min, max)
self.context = context self.context = context
# We need to save the predefined names. It's an unfortunate side effect # We need to save the predefined names. It's an unfortunate side effect
# that needs to be tracked otherwise results will be wrong. # that needs to be tracked otherwise results will be wrong.
+28 -20
View File
@@ -1,11 +1,13 @@
from abc import abstractmethod from abc import abstractmethod
from inspect import Parameter
from typing import Optional, Tuple
from parso.tree import search_ancestor from parso.tree import search_ancestor
from jedi._compatibility import Parameter
from jedi.parser_utils import find_statement_documentation, clean_scope_docstring from jedi.parser_utils import find_statement_documentation, clean_scope_docstring
from jedi.inference.utils import unite from jedi.inference.utils import unite
from jedi.inference.base_value import ValueSet, NO_VALUES from jedi.inference.base_value import ValueSet, NO_VALUES
from jedi.inference.cache import inference_state_method_cache
from jedi.inference import docstrings from jedi.inference import docstrings
from jedi.cache import memoize_method from jedi.cache import memoize_method
from jedi.inference.helpers import deep_ast_copy, infer_call_of_leaf from jedi.inference.helpers import deep_ast_copy, infer_call_of_leaf
@@ -23,9 +25,9 @@ def _merge_name_docs(names):
return doc return doc
class AbstractNameDefinition(object): class AbstractNameDefinition:
start_pos = None start_pos: Optional[Tuple[int, int]] = None
string_name = None string_name: str
parent_context = None parent_context = None
tree_name = None tree_name = None
is_value_name = True is_value_name = True
@@ -123,7 +125,7 @@ class AbstractTreeName(AbstractNameDefinition):
else: else:
return None return None
return super(AbstractTreeName, self).get_qualified_names(include_module_names) return super().get_qualified_names(include_module_names)
def _get_qualified_names(self): def _get_qualified_names(self):
parent_names = self.parent_context.get_qualified_names() parent_names = self.parent_context.get_qualified_names()
@@ -223,7 +225,7 @@ class AbstractTreeName(AbstractNameDefinition):
return self.tree_name.start_pos return self.tree_name.start_pos
class ValueNameMixin(object): class ValueNameMixin:
def infer(self): def infer(self):
return ValueSet([self._value]) return ValueSet([self._value])
@@ -242,7 +244,7 @@ class ValueNameMixin(object):
def get_root_context(self): def get_root_context(self):
if self.parent_context is None: # A module if self.parent_context is None: # A module
return self._value.as_context() return self._value.as_context()
return super(ValueNameMixin, self).get_root_context() return super().get_root_context()
def get_defining_qualified_value(self): def get_defining_qualified_value(self):
context = self.parent_context context = self.parent_context
@@ -257,7 +259,7 @@ class ValueNameMixin(object):
class ValueName(ValueNameMixin, AbstractTreeName): class ValueName(ValueNameMixin, AbstractTreeName):
def __init__(self, value, tree_name): def __init__(self, value, tree_name):
super(ValueName, self).__init__(value.parent_context, tree_name) super().__init__(value.parent_context, tree_name)
self._value = value self._value = value
def goto(self): def goto(self):
@@ -330,9 +332,15 @@ class TreeNameDefinition(AbstractTreeName):
node = node.parent node = node.parent
return indexes return indexes
@property
def inference_state(self):
# Used by the cache function below
return self.parent_context.inference_state
@inference_state_method_cache(default='')
def py__doc__(self): def py__doc__(self):
api_type = self.api_type api_type = self.api_type
if api_type in ('function', 'class'): if api_type in ('function', 'class', 'property'):
# Make sure the names are not TreeNameDefinitions anymore. # Make sure the names are not TreeNameDefinitions anymore.
return clean_scope_docstring(self.tree_name.get_definition()) return clean_scope_docstring(self.tree_name.get_definition())
@@ -346,7 +354,7 @@ class TreeNameDefinition(AbstractTreeName):
return '' return ''
class _ParamMixin(object): class _ParamMixin:
def maybe_positional_argument(self, include_star=True): def maybe_positional_argument(self, include_star=True):
options = [Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD] options = [Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD]
if include_star: if include_star:
@@ -372,7 +380,7 @@ class _ParamMixin(object):
class ParamNameInterface(_ParamMixin): class ParamNameInterface(_ParamMixin):
api_type = u'param' api_type = 'param'
def get_kind(self): def get_kind(self):
raise NotImplementedError raise NotImplementedError
@@ -429,7 +437,7 @@ class BaseTreeParamName(ParamNameInterface, AbstractTreeName):
class _ActualTreeParamName(BaseTreeParamName): class _ActualTreeParamName(BaseTreeParamName):
def __init__(self, function_value, tree_name): def __init__(self, function_value, tree_name):
super(_ActualTreeParamName, self).__init__( super().__init__(
function_value.get_default_param_context(), tree_name) function_value.get_default_param_context(), tree_name)
self.function_value = function_value self.function_value = function_value
@@ -499,11 +507,11 @@ class _ActualTreeParamName(BaseTreeParamName):
class AnonymousParamName(_ActualTreeParamName): class AnonymousParamName(_ActualTreeParamName):
@plugin_manager.decorate(name='goto_anonymous_param') @plugin_manager.decorate(name='goto_anonymous_param')
def goto(self): def goto(self):
return super(AnonymousParamName, self).goto() return super().goto()
@plugin_manager.decorate(name='infer_anonymous_param') @plugin_manager.decorate(name='infer_anonymous_param')
def infer(self): def infer(self):
values = super(AnonymousParamName, self).infer() values = super().infer()
if values: if values:
return values return values
from jedi.inference.dynamic_params import dynamic_param_lookup from jedi.inference.dynamic_params import dynamic_param_lookup
@@ -527,11 +535,11 @@ class AnonymousParamName(_ActualTreeParamName):
class ParamName(_ActualTreeParamName): class ParamName(_ActualTreeParamName):
def __init__(self, function_value, tree_name, arguments): def __init__(self, function_value, tree_name, arguments):
super(ParamName, self).__init__(function_value, tree_name) super().__init__(function_value, tree_name)
self.arguments = arguments self.arguments = arguments
def infer(self): def infer(self):
values = super(ParamName, self).infer() values = super().infer()
if values: if values:
return values return values
@@ -604,7 +612,7 @@ class SubModuleName(ImportName):
_level = 1 _level = 1
class NameWrapper(object): class NameWrapper:
def __init__(self, wrapped_name): def __init__(self, wrapped_name):
self._wrapped_name = wrapped_name self._wrapped_name = wrapped_name
@@ -615,7 +623,7 @@ class NameWrapper(object):
return '%s(%s)' % (self.__class__.__name__, self._wrapped_name) return '%s(%s)' % (self.__class__.__name__, self._wrapped_name)
class StubNameMixin(object): class StubNameMixin:
def py__doc__(self): def py__doc__(self):
from jedi.inference.gradual.conversion import convert_names from jedi.inference.gradual.conversion import convert_names
# Stubs are not complicated and we can just follow simple statements # Stubs are not complicated and we can just follow simple statements
@@ -627,7 +635,7 @@ class StubNameMixin(object):
names = convert_names(names, prefer_stub_to_compiled=False) names = convert_names(names, prefer_stub_to_compiled=False)
if self in names: if self in names:
return super(StubNameMixin, self).py__doc__() return super().py__doc__()
else: else:
# We have signatures ourselves in stubs, so don't use signatures # We have signatures ourselves in stubs, so don't use signatures
# from the implementation. # from the implementation.
@@ -637,7 +645,7 @@ class StubNameMixin(object):
# From here on down we make looking up the sys.version_info fast. # From here on down we make looking up the sys.version_info fast.
class StubName(StubNameMixin, TreeNameDefinition): class StubName(StubNameMixin, TreeNameDefinition):
def infer(self): def infer(self):
inferred = super(StubName, self).infer() inferred = super().infer()
if self.string_name == 'version_info' and self.get_root_context().py__name__() == 'sys': if self.string_name == 'version_info' and self.get_root_context().py__name__() == 'sys':
from jedi.inference.gradual.stub_value import VersionInfo from jedi.inference.gradual.stub_value import VersionInfo
return ValueSet(VersionInfo(c) for c in inferred) return ValueSet(VersionInfo(c) for c in inferred)
+38 -3
View File
@@ -1,4 +1,5 @@
from collections import defaultdict from collections import defaultdict
from inspect import Parameter
from jedi import debug from jedi import debug
from jedi.inference.utils import PushBackIterator from jedi.inference.utils import PushBackIterator
@@ -6,7 +7,6 @@ from jedi.inference import analysis
from jedi.inference.lazy_value import LazyKnownValue, \ from jedi.inference.lazy_value import LazyKnownValue, \
LazyTreeValue, LazyUnknownValue LazyTreeValue, LazyUnknownValue
from jedi.inference.value import iterable from jedi.inference.value import iterable
from jedi._compatibility import Parameter
from jedi.inference.names import ParamName from jedi.inference.names import ParamName
@@ -20,8 +20,7 @@ def _add_argument_issue(error_name, lazy_value, message):
class ExecutedParamName(ParamName): class ExecutedParamName(ParamName):
def __init__(self, function_value, arguments, param_node, lazy_value, is_default=False): def __init__(self, function_value, arguments, param_node, lazy_value, is_default=False):
super(ExecutedParamName, self).__init__( super().__init__(function_value, param_node.name, arguments=arguments)
function_value, param_node.name, arguments=arguments)
self._lazy_value = lazy_value self._lazy_value = lazy_value
self._is_default = is_default self._is_default = is_default
@@ -51,6 +50,25 @@ class ExecutedParamName(ParamName):
def get_executed_param_names_and_issues(function_value, arguments): def get_executed_param_names_and_issues(function_value, arguments):
"""
Return a tuple of:
- a list of `ExecutedParamName`s corresponding to the arguments of the
function execution `function_value`, containing the inferred value of
those arguments (whether explicit or default)
- a list of the issues encountered while building that list
For example, given:
```
def foo(a, b, c=None, d='d'): ...
foo(42, c='c')
```
Then for the execution of `foo`, this will return a tuple containing:
- a list with entries for each parameter a, b, c & d; the entries for a,
c, & d will have their values (42, 'c' and 'd' respectively) included.
- a list with a single entry about the lack of a value for `b`
"""
def too_many_args(argument): def too_many_args(argument):
m = _error_argument_count(funcdef, len(unpacked_va)) m = _error_argument_count(funcdef, len(unpacked_va))
# Just report an error for the first param that is not needed (like # Just report an error for the first param that is not needed (like
@@ -207,6 +225,23 @@ def get_executed_param_names_and_issues(function_value, arguments):
def get_executed_param_names(function_value, arguments): def get_executed_param_names(function_value, arguments):
"""
Return a list of `ExecutedParamName`s corresponding to the arguments of the
function execution `function_value`, containing the inferred value of those
arguments (whether explicit or default). Any issues building this list (for
example required arguments which are missing in the invocation) are ignored.
For example, given:
```
def foo(a, b, c=None, d='d'): ...
foo(42, c='c')
```
Then for the execution of `foo`, this will return a list containing entries
for each parameter a, b, c & d; the entries for a, c, & d will have their
values (42, 'c' and 'd' respectively) included.
"""
return get_executed_param_names_and_issues(function_value, arguments)[0] return get_executed_param_names_and_issues(function_value, arguments)[0]
+5 -5
View File
@@ -3,8 +3,8 @@ Recursions are the recipe of |jedi| to conquer Python code. However, someone
must stop recursions going mad. Some settings are here to make |jedi| stop at must stop recursions going mad. Some settings are here to make |jedi| stop at
the right time. You can read more about them :ref:`here <settings-recursion>`. the right time. You can read more about them :ref:`here <settings-recursion>`.
Next to :mod:`jedi.inference.cache` this module also makes |jedi| not Next to the internal ``jedi.inference.cache`` this module also makes |jedi| not
thread-safe. Why? ``execution_recursion_decorator`` uses class variables to thread-safe, because ``execution_recursion_decorator`` uses class variables to
count the function calls. count the function calls.
.. _settings-recursion: .. _settings-recursion:
@@ -34,7 +34,7 @@ from jedi.inference.base_value import NO_VALUES
recursion_limit = 15 recursion_limit = 15
""" """
Like ``sys.getrecursionlimit()``, just for |jedi|. Like :func:`sys.getrecursionlimit()`, just for |jedi|.
""" """
total_function_execution_limit = 200 total_function_execution_limit = 200
""" """
@@ -50,7 +50,7 @@ A function may not be executed more than this number of times recursively.
""" """
class RecursionDetector(object): class RecursionDetector:
def __init__(self): def __init__(self):
self.pushed_nodes = [] self.pushed_nodes = []
@@ -92,7 +92,7 @@ def execution_recursion_decorator(default=NO_VALUES):
return decorator return decorator
class ExecutionRecursionDetector(object): class ExecutionRecursionDetector:
""" """
Catches recursions of executions. Catches recursions of executions.
""" """
+51 -20
View File
@@ -3,12 +3,14 @@ import re
from parso import python_bytes_to_unicode from parso import python_bytes_to_unicode
from jedi.file_io import KnownContentFileIO from jedi.debug import dbg
from jedi.inference.imports import SubModuleName, load_module_from_path from jedi.file_io import KnownContentFileIO, FolderIO
from jedi.inference.names import SubModuleName
from jedi.inference.imports import load_module_from_path
from jedi.inference.filters import ParserTreeFilter from jedi.inference.filters import ParserTreeFilter
from jedi.inference.gradual.conversion import convert_names from jedi.inference.gradual.conversion import convert_names
_IGNORE_FOLDERS = ('.tox', 'venv', '__pycache__') _IGNORE_FOLDERS = ('.tox', '.venv', '.mypy_cache', 'venv', '__pycache__')
_OPENED_FILE_LIMIT = 2000 _OPENED_FILE_LIMIT = 2000
""" """
@@ -37,8 +39,7 @@ def _resolve_names(definition_names, avoid_names=()):
yield name yield name
if name.api_type == 'module': if name.api_type == 'module':
for name in _resolve_names(name.goto(), definition_names): yield from _resolve_names(name.goto(), definition_names)
yield name
def _dictionarize(names): def _dictionarize(names):
@@ -89,8 +90,7 @@ def _add_names_in_same_context(context, string_name):
names = set(filter_.get(string_name)) names = set(filter_.get(string_name))
if not names: if not names:
break break
for name in names: yield from names
yield name
ordered = sorted(names, key=lambda x: x.start_pos) ordered = sorted(names, key=lambda x: x.start_pos)
until_position = ordered[0].start_pos until_position = ordered[0].start_pos
@@ -108,11 +108,10 @@ def _find_global_variables(names, search_name):
for global_name in method().get(search_name): for global_name in method().get(search_name):
yield global_name yield global_name
c = module_context.create_context(global_name.tree_name) c = module_context.create_context(global_name.tree_name)
for name in _add_names_in_same_context(c, global_name.string_name): yield from _add_names_in_same_context(c, global_name.string_name)
yield name
def find_references(module_context, tree_name): def find_references(module_context, tree_name, only_in_module=False):
inf = module_context.inference_state inf = module_context.inference_state
search_name = tree_name.value search_name = tree_name.value
@@ -126,10 +125,14 @@ def find_references(module_context, tree_name):
found_names_dct = _dictionarize(found_names) found_names_dct = _dictionarize(found_names)
module_contexts = set(d.get_root_context() for d in found_names) module_contexts = [module_context]
module_contexts = [module_context] + [m for m in module_contexts if m != module_context] if not only_in_module:
for m in set(d.get_root_context() for d in found_names):
if m != module_context and m.tree_node is not None \
and inf.project.path in m.py__file__().parents:
module_contexts.append(m)
# For param no search for other modules is necessary. # For param no search for other modules is necessary.
if any(n.api_type == 'param' for n in found_names): if only_in_module or any(n.api_type == 'param' for n in found_names):
potential_modules = module_contexts potential_modules = module_contexts
else: else:
potential_modules = get_module_contexts_containing_name( potential_modules = get_module_contexts_containing_name(
@@ -156,7 +159,10 @@ def find_references(module_context, tree_name):
else: else:
for name in new: for name in new:
non_matching_reference_maps.setdefault(name, []).append(new) non_matching_reference_maps.setdefault(name, []).append(new)
return found_names_dct.values() result = found_names_dct.values()
if only_in_module:
return [n for n in result if n.get_root_context() == module_context]
return result
def _check_fs(inference_state, file_io, regex): def _check_fs(inference_state, file_io, regex):
@@ -192,16 +198,17 @@ def gitignored_lines(folder_io, file_io):
return ignored_paths, ignored_names return ignored_paths, ignored_names
def _recurse_find_python_files(folder_io, except_paths): def recurse_find_python_folders_and_files(folder_io, except_paths=()):
except_paths = set(except_paths)
for root_folder_io, folder_ios, file_ios in folder_io.walk(): for root_folder_io, folder_ios, file_ios in folder_io.walk():
# Delete folders that we don't want to iterate over. # Delete folders that we don't want to iterate over.
for file_io in file_ios: for file_io in file_ios:
path = file_io.path path = file_io.path
if path.endswith('.py') or path.endswith('.pyi'): if path.suffix in ('.py', '.pyi'):
if path not in except_paths: if path not in except_paths:
yield file_io yield None, file_io
if path.endswith('.gitignore'): if path.name == '.gitignore':
ignored_paths, ignored_names = \ ignored_paths, ignored_names = \
gitignored_lines(root_folder_io, file_io) gitignored_lines(root_folder_io, file_io)
except_paths |= ignored_paths except_paths |= ignored_paths
@@ -212,6 +219,14 @@ def _recurse_find_python_files(folder_io, except_paths):
if folder_io.path not in except_paths if folder_io.path not in except_paths
and folder_io.get_base_name() not in _IGNORE_FOLDERS and folder_io.get_base_name() not in _IGNORE_FOLDERS
] ]
for folder_io in folder_ios:
yield folder_io, None
def recurse_find_python_files(folder_io, except_paths=()):
for folder_io, file_io in recurse_find_python_folders_and_files(folder_io, except_paths):
if file_io is not None:
yield file_io
def _find_python_files_in_sys_path(inference_state, module_contexts): def _find_python_files_in_sys_path(inference_state, module_contexts):
@@ -228,13 +243,18 @@ def _find_python_files_in_sys_path(inference_state, module_contexts):
path = folder_io.path path = folder_io.path
if not any(path.startswith(p) for p in sys_path) or path in except_paths: if not any(path.startswith(p) for p in sys_path) or path in except_paths:
break break
for file_io in _recurse_find_python_files(folder_io, except_paths): for file_io in recurse_find_python_files(folder_io, except_paths):
if file_io.path not in yielded_paths: if file_io.path not in yielded_paths:
yield file_io yield file_io
except_paths.add(path) except_paths.add(path)
folder_io = folder_io.get_parent_folder() folder_io = folder_io.get_parent_folder()
def _find_project_modules(inference_state, module_contexts):
except_ = [m.py__file__() for m in module_contexts]
yield from recurse_find_python_files(FolderIO(inference_state.project.path), except_)
def get_module_contexts_containing_name(inference_state, module_contexts, name, def get_module_contexts_containing_name(inference_state, module_contexts, name,
limit_reduction=1): limit_reduction=1):
""" """
@@ -254,19 +274,30 @@ def get_module_contexts_containing_name(inference_state, module_contexts, name,
if len(name) <= 2: if len(name) <= 2:
return return
# Currently not used, because there's only `scope=project` and `scope=file`
# At the moment there is no such thing as `scope=sys.path`.
# file_io_iterator = _find_python_files_in_sys_path(inference_state, module_contexts)
file_io_iterator = _find_project_modules(inference_state, module_contexts)
yield from search_in_file_ios(inference_state, file_io_iterator, name,
limit_reduction=limit_reduction)
def search_in_file_ios(inference_state, file_io_iterator, name, limit_reduction=1):
parse_limit = _PARSED_FILE_LIMIT / limit_reduction parse_limit = _PARSED_FILE_LIMIT / limit_reduction
open_limit = _OPENED_FILE_LIMIT / limit_reduction open_limit = _OPENED_FILE_LIMIT / limit_reduction
file_io_count = 0 file_io_count = 0
parsed_file_count = 0 parsed_file_count = 0
regex = re.compile(r'\b' + re.escape(name) + r'\b') regex = re.compile(r'\b' + re.escape(name) + r'\b')
for file_io in _find_python_files_in_sys_path(inference_state, module_contexts): for file_io in file_io_iterator:
file_io_count += 1 file_io_count += 1
m = _check_fs(inference_state, file_io, regex) m = _check_fs(inference_state, file_io, regex)
if m is not None: if m is not None:
parsed_file_count += 1 parsed_file_count += 1
yield m yield m
if parsed_file_count >= parse_limit: if parsed_file_count >= parse_limit:
dbg('Hit limit of parsed files: %s', parse_limit)
break break
if file_io_count >= open_limit: if file_io_count >= open_limit:
dbg('Hit limit of opened files: %s', open_limit)
break break
+8 -5
View File
@@ -1,10 +1,11 @@
from jedi._compatibility import Parameter from inspect import Parameter
from jedi.cache import memoize_method from jedi.cache import memoize_method
from jedi import debug from jedi import debug
from jedi import parser_utils from jedi import parser_utils
class _SignatureMixin(object): class _SignatureMixin:
def to_string(self): def to_string(self):
def param_strings(): def param_strings():
is_positional = False is_positional = False
@@ -67,7 +68,7 @@ class AbstractSignature(_SignatureMixin):
class TreeSignature(AbstractSignature): class TreeSignature(AbstractSignature):
def __init__(self, value, function_value=None, is_bound=False): def __init__(self, value, function_value=None, is_bound=False):
super(TreeSignature, self).__init__(value, is_bound) super().__init__(value, is_bound)
self._function_value = function_value or value self._function_value = function_value or value
def bind(self, value): def bind(self, value):
@@ -90,10 +91,12 @@ class TreeSignature(AbstractSignature):
@memoize_method @memoize_method
def get_param_names(self, resolve_stars=False): def get_param_names(self, resolve_stars=False):
params = super(TreeSignature, self).get_param_names(resolve_stars=False) params = self._function_value.get_param_names()
if resolve_stars: if resolve_stars:
from jedi.inference.star_args import process_params from jedi.inference.star_args import process_params
params = process_params(params) params = process_params(params)
if self.is_bound:
return params[1:]
return params return params
def matches_signature(self, arguments): def matches_signature(self, arguments):
@@ -119,7 +122,7 @@ class TreeSignature(AbstractSignature):
class BuiltinSignature(AbstractSignature): class BuiltinSignature(AbstractSignature):
def __init__(self, value, return_string, function_value=None, is_bound=False): def __init__(self, value, return_string, function_value=None, is_bound=False):
super(BuiltinSignature, self).__init__(value, is_bound) super().__init__(value, is_bound)
self._return_string = return_string self._return_string = return_string
self.__function_value = function_value self.__function_value = function_value
+3 -6
View File
@@ -10,8 +10,8 @@ This means for example in this case::
The signature here for bar should be `bar(b, c)` instead of bar(*args). The signature here for bar should be `bar(b, c)` instead of bar(*args).
""" """
from inspect import Parameter
from jedi._compatibility import Parameter
from jedi.inference.utils import to_list from jedi.inference.utils import to_list
from jedi.inference.names import ParamNameWrapper from jedi.inference.names import ParamNameWrapper
from jedi.inference.helpers import is_big_annoying_library from jedi.inference.helpers import is_big_annoying_library
@@ -32,8 +32,6 @@ def _iter_nodes_for_param(param_name):
argument = name.parent argument = name.parent
if argument.type == 'argument' \ if argument.type == 'argument' \
and argument.children[0] == '*' * param_name.star_count: and argument.children[0] == '*' * param_name.star_count:
# No support for Python <= 3.4 here, but they are end-of-life
# anyway
trailer = search_ancestor(argument, 'trailer') trailer = search_ancestor(argument, 'trailer')
if trailer is not None: # Make sure we're in a function if trailer is not None: # Make sure we're in a function
context = execution_context.create_context(trailer) context = execution_context.create_context(trailer)
@@ -98,8 +96,7 @@ def process_params(param_names, star_count=3): # default means both * and **
if is_big_annoying_library(param_names[0].parent_context): if is_big_annoying_library(param_names[0].parent_context):
# At first this feature can look innocent, but it does a lot of # At first this feature can look innocent, but it does a lot of
# type inference in some cases, so we just ditch it. # type inference in some cases, so we just ditch it.
for p in param_names: yield from param_names
yield p
return return
used_names = set() used_names = set()
@@ -210,7 +207,7 @@ def process_params(param_names, star_count=3): # default means both * and **
class ParamNameFixedKind(ParamNameWrapper): class ParamNameFixedKind(ParamNameWrapper):
def __init__(self, param_name, new_kind): def __init__(self, param_name, new_kind):
super(ParamNameFixedKind, self).__init__(param_name) super().__init__(param_name)
self._new_kind = new_kind self._new_kind = new_kind
def get_kind(self): def get_kind(self):
+79 -50
View File
@@ -5,7 +5,6 @@ import copy
from parso.python import tree from parso.python import tree
from jedi._compatibility import force_unicode, unicode
from jedi import debug from jedi import debug
from jedi import parser_utils from jedi import parser_utils
from jedi.inference.base_value import ValueSet, NO_VALUES, ContextualizedNode, \ from jedi.inference.base_value import ValueSet, NO_VALUES, ContextualizedNode, \
@@ -31,6 +30,26 @@ from jedi.inference.context import CompForContext
from jedi.inference.value.decorator import Decoratee from jedi.inference.value.decorator import Decoratee
from jedi.plugins import plugin_manager from jedi.plugins import plugin_manager
operator_to_magic_method = {
'+': '__add__',
'-': '__sub__',
'*': '__mul__',
'@': '__matmul__',
'/': '__truediv__',
'//': '__floordiv__',
'%': '__mod__',
'**': '__pow__',
'<<': '__lshift__',
'>>': '__rshift__',
'&': '__and__',
'|': '__or__',
'^': '__xor__',
}
reverse_operator_to_magic_method = {
k: '__r' + v[2:] for k, v in operator_to_magic_method.items()
}
def _limit_value_infers(func): def _limit_value_infers(func):
""" """
@@ -205,12 +224,10 @@ def _infer_node(context, element):
| context.infer_node(element.children[-1])) | context.infer_node(element.children[-1]))
elif typ == 'operator': elif typ == 'operator':
# Must be an ellipsis, other operators are not inferred. # Must be an ellipsis, other operators are not inferred.
# In Python 2 ellipsis is coded as three single dot tokens, not if element.value != '...':
# as one token 3 dot token.
if element.value not in ('.', '...'):
origin = element.parent origin = element.parent
raise AssertionError("unhandled operator %s in %s " % (repr(element.value), origin)) raise AssertionError("unhandled operator %s in %s " % (repr(element.value), origin))
return ValueSet([compiled.builtin_from_name(inference_state, u'Ellipsis')]) return ValueSet([compiled.builtin_from_name(inference_state, 'Ellipsis')])
elif typ == 'dotted_name': elif typ == 'dotted_name':
value_set = infer_atom(context, element.children[0]) value_set = infer_atom(context, element.children[0])
for next_name in element.children[2::2]: for next_name in element.children[2::2]:
@@ -269,15 +286,12 @@ def infer_atom(context, atom):
""" """
state = context.inference_state state = context.inference_state
if atom.type == 'name': if atom.type == 'name':
if atom.value in ('True', 'False', 'None'):
# Python 2...
return ValueSet([compiled.builtin_from_name(state, atom.value)])
# This is the first global lookup. # This is the first global lookup.
stmt = tree.search_ancestor( stmt = tree.search_ancestor(atom, 'expr_stmt', 'lambdef', 'if_stmt') or atom
atom, 'expr_stmt', 'lambdef' if stmt.type == 'if_stmt':
) or atom if not any(n.start_pos <= atom.start_pos < n.end_pos for n in stmt.get_test_nodes()):
if stmt.type == 'lambdef': stmt = atom
elif stmt.type == 'lambdef':
stmt = atom stmt = atom
position = stmt.start_pos position = stmt.start_pos
if _is_annotation_name(atom): if _is_annotation_name(atom):
@@ -292,9 +306,6 @@ def infer_atom(context, atom):
# For False/True/None # For False/True/None
if atom.value in ('False', 'True', 'None'): if atom.value in ('False', 'True', 'None'):
return ValueSet([compiled.builtin_from_name(state, atom.value)]) return ValueSet([compiled.builtin_from_name(state, atom.value)])
elif atom.value == 'print':
# print e.g. could be inferred like this in Python 2.7
return NO_VALUES
elif atom.value == 'yield': elif atom.value == 'yield':
# Contrary to yield from, yield can just appear alone to return a # Contrary to yield from, yield can just appear alone to return a
# value when used with `.send()`. # value when used with `.send()`.
@@ -309,7 +320,7 @@ def infer_atom(context, atom):
value_set = infer_atom(context, atom.children[0]) value_set = infer_atom(context, atom.children[0])
for string in atom.children[1:]: for string in atom.children[1:]:
right = infer_atom(context, string) right = infer_atom(context, string)
value_set = _infer_comparison(context, value_set, u'+', right) value_set = _infer_comparison(context, value_set, '+', right)
return value_set return value_set
elif atom.type == 'fstring': elif atom.type == 'fstring':
return compiled.get_string_value_set(state) return compiled.get_string_value_set(state)
@@ -356,6 +367,12 @@ def infer_atom(context, atom):
def infer_expr_stmt(context, stmt, seek_name=None): def infer_expr_stmt(context, stmt, seek_name=None):
with recursion.execution_allowed(context.inference_state, stmt) as allowed: with recursion.execution_allowed(context.inference_state, stmt) as allowed:
if allowed: if allowed:
if seek_name is not None:
pep0484_values = \
annotation.find_type_from_comment_hint_assign(context, stmt, seek_name)
if pep0484_values:
return pep0484_values
return _infer_expr_stmt(context, stmt, seek_name) return _infer_expr_stmt(context, stmt, seek_name)
return NO_VALUES return NO_VALUES
@@ -388,6 +405,7 @@ def _infer_expr_stmt(context, stmt, seek_name=None):
debug.dbg('infer_expr_stmt %s (%s)', stmt, seek_name) debug.dbg('infer_expr_stmt %s (%s)', stmt, seek_name)
rhs = stmt.get_rhs() rhs = stmt.get_rhs()
value_set = context.infer_node(rhs) value_set = context.infer_node(rhs)
if seek_name: if seek_name:
@@ -531,16 +549,16 @@ def _is_annotation_name(name):
return False return False
def _is_tuple(value):
return isinstance(value, iterable.Sequence) and value.array_type == 'tuple'
def _is_list(value): def _is_list(value):
return isinstance(value, iterable.Sequence) and value.array_type == 'list' return value.array_type == 'list'
def _is_tuple(value):
return value.array_type == 'tuple'
def _bool_to_value(inference_state, bool_): def _bool_to_value(inference_state, bool_):
return compiled.builtin_from_name(inference_state, force_unicode(str(bool_))) return compiled.builtin_from_name(inference_state, str(bool_))
def _get_tuple_ints(value): def _get_tuple_ints(value):
@@ -563,10 +581,10 @@ def _get_tuple_ints(value):
def _infer_comparison_part(inference_state, context, left, operator, right): def _infer_comparison_part(inference_state, context, left, operator, right):
l_is_num = is_number(left) l_is_num = is_number(left)
r_is_num = is_number(right) r_is_num = is_number(right)
if isinstance(operator, unicode): if isinstance(operator, str):
str_operator = operator str_operator = operator
else: else:
str_operator = force_unicode(str(operator.value)) str_operator = str(operator.value)
if str_operator == '*': if str_operator == '*':
# for iterables, ignore * operations # for iterables, ignore * operations
@@ -577,7 +595,7 @@ def _infer_comparison_part(inference_state, context, left, operator, right):
elif str_operator == '+': elif str_operator == '+':
if l_is_num and r_is_num or is_string(left) and is_string(right): if l_is_num and r_is_num or is_string(left) and is_string(right):
return left.execute_operation(right, str_operator) return left.execute_operation(right, str_operator)
elif _is_tuple(left) and _is_tuple(right) or _is_list(left) and _is_list(right): elif _is_list(left) and _is_list(right) or _is_tuple(left) and _is_tuple(right):
return ValueSet([iterable.MergedArray(inference_state, (left, right))]) return ValueSet([iterable.MergedArray(inference_state, (left, right))])
elif str_operator == '-': elif str_operator == '-':
if l_is_num and r_is_num: if l_is_num and r_is_num:
@@ -596,6 +614,10 @@ def _infer_comparison_part(inference_state, context, left, operator, right):
if str_operator in ('is', '!=', '==', 'is not'): if str_operator in ('is', '!=', '==', 'is not'):
operation = COMPARISON_OPERATORS[str_operator] operation = COMPARISON_OPERATORS[str_operator]
bool_ = operation(left, right) bool_ = operation(left, right)
# Only if == returns True or != returns False, we can continue.
# There's no guarantee that they are not equal. This can help
# in some cases, but does not cover everything.
if (str_operator in ('is', '==')) == bool_:
return ValueSet([_bool_to_value(inference_state, bool_)]) return ValueSet([_bool_to_value(inference_state, bool_)])
if isinstance(left, VersionInfo): if isinstance(left, VersionInfo):
@@ -611,7 +633,7 @@ def _infer_comparison_part(inference_state, context, left, operator, right):
_bool_to_value(inference_state, True), _bool_to_value(inference_state, True),
_bool_to_value(inference_state, False) _bool_to_value(inference_state, False)
]) ])
elif str_operator == 'in': elif str_operator in ('in', 'not in'):
return NO_VALUES return NO_VALUES
def check(obj): def check(obj):
@@ -626,28 +648,29 @@ def _infer_comparison_part(inference_state, context, left, operator, right):
analysis.add(context, 'type-error-operation', operator, analysis.add(context, 'type-error-operation', operator,
message % (left, right)) message % (left, right))
if left.is_class() or right.is_class():
return NO_VALUES
method_name = operator_to_magic_method[str_operator]
magic_methods = left.py__getattribute__(method_name)
if magic_methods:
result = magic_methods.execute_with_values(right)
if result:
return result
if not magic_methods:
reverse_method_name = reverse_operator_to_magic_method[str_operator]
magic_methods = right.py__getattribute__(reverse_method_name)
result = magic_methods.execute_with_values(left)
if result:
return result
result = ValueSet([left, right]) result = ValueSet([left, right])
debug.dbg('Used operator %s resulting in %s', operator, result) debug.dbg('Used operator %s resulting in %s', operator, result)
return result return result
def _remove_statements(context, stmt, name):
"""
This is the part where statements are being stripped.
Due to lazy type inference, statements like a = func; b = a; b() have to be
inferred.
TODO merge with infer_expr_stmt?
"""
pep0484_values = \
annotation.find_type_from_comment_hint_assign(context, stmt, name)
if pep0484_values:
return pep0484_values
return infer_expr_stmt(context, stmt, seek_name=name)
@plugin_manager.decorate() @plugin_manager.decorate()
def tree_name_to_values(inference_state, context, tree_name): def tree_name_to_values(inference_state, context, tree_name):
value_set = NO_VALUES value_set = NO_VALUES
@@ -655,16 +678,18 @@ def tree_name_to_values(inference_state, context, tree_name):
# First check for annotations, like: `foo: int = 3` # First check for annotations, like: `foo: int = 3`
if module_node is not None: if module_node is not None:
names = module_node.get_used_names().get(tree_name.value, []) names = module_node.get_used_names().get(tree_name.value, [])
found_annotation = False
for name in names: for name in names:
expr_stmt = name.parent expr_stmt = name.parent
if expr_stmt.type == "expr_stmt" and expr_stmt.children[1].type == "annassign": if expr_stmt.type == "expr_stmt" and expr_stmt.children[1].type == "annassign":
correct_scope = parser_utils.get_parent_scope(name) == context.tree_node correct_scope = parser_utils.get_parent_scope(name) == context.tree_node
if correct_scope: if correct_scope:
found_annotation = True
value_set |= annotation.infer_annotation( value_set |= annotation.infer_annotation(
context, expr_stmt.children[1].children[1] context, expr_stmt.children[1].children[1]
).execute_annotation() ).execute_annotation()
if value_set: if found_annotation:
return value_set return value_set
types = [] types = []
@@ -710,10 +735,10 @@ def tree_name_to_values(inference_state, context, tree_name):
n = TreeNameDefinition(context, tree_name) n = TreeNameDefinition(context, tree_name)
types = check_tuple_assignments(n, for_types) types = check_tuple_assignments(n, for_types)
elif typ == 'expr_stmt': elif typ == 'expr_stmt':
types = _remove_statements(context, node, tree_name) types = infer_expr_stmt(context, node, tree_name)
elif typ == 'with_stmt': elif typ == 'with_stmt':
value_managers = context.infer_node(node.get_test_node_from_name(tree_name)) value_managers = context.infer_node(node.get_test_node_from_name(tree_name))
enter_methods = value_managers.py__getattribute__(u'__enter__') enter_methods = value_managers.py__getattribute__('__enter__')
return enter_methods.execute_with_values() return enter_methods.execute_with_values()
elif typ in ('import_from', 'import_name'): elif typ in ('import_from', 'import_name'):
types = imports.infer_import(context, tree_name) types = imports.infer_import(context, tree_name)
@@ -729,6 +754,8 @@ def tree_name_to_values(inference_state, context, tree_name):
types = NO_VALUES types = NO_VALUES
elif typ == 'del_stmt': elif typ == 'del_stmt':
types = NO_VALUES types = NO_VALUES
elif typ == 'namedexpr_test':
types = infer_node(context, node)
else: else:
raise ValueError("Should not happen. type: %s" % typ) raise ValueError("Should not happen. type: %s" % typ)
return types return types
@@ -797,7 +824,8 @@ def check_tuple_assignments(name, value_set):
if isinstance(index, slice): if isinstance(index, slice):
# For no star unpacking is not possible. # For no star unpacking is not possible.
return NO_VALUES return NO_VALUES
for _ in range(index + 1): i = 0
while i <= index:
try: try:
lazy_value = next(iterated) lazy_value = next(iterated)
except StopIteration: except StopIteration:
@@ -806,6 +834,8 @@ def check_tuple_assignments(name, value_set):
# index number is high. Therefore break if the loop is # index number is high. Therefore break if the loop is
# finished. # finished.
return NO_VALUES return NO_VALUES
else:
i += lazy_value.max
value_set = lazy_value.infer() value_set = lazy_value.infer()
return value_set return value_set
@@ -824,8 +854,7 @@ def _infer_subscript_list(context, index):
return ValueSet([iterable.Slice(context, None, None, None)]) return ValueSet([iterable.Slice(context, None, None, None)])
elif index.type == 'subscript' and not index.children[0] == '.': elif index.type == 'subscript' and not index.children[0] == '.':
# subscript basically implies a slice operation, except for Python 2's # subscript basically implies a slice operation
# Ellipsis.
# e.g. array[:3] # e.g. array[:3]
result = [] result = []
for el in index.children: for el in index.children:
+34 -29
View File
@@ -1,10 +1,11 @@
import os import os
import re
from pathlib import Path
from importlib.machinery import all_suffixes
from jedi._compatibility import unicode, force_unicode, all_suffixes
from jedi.inference.cache import inference_state_method_cache from jedi.inference.cache import inference_state_method_cache
from jedi.inference.base_value import ContextualizedNode from jedi.inference.base_value import ContextualizedNode
from jedi.inference.helpers import is_string, get_str_or_none from jedi.inference.helpers import is_string, get_str_or_none
from jedi.common.utils import traverse_parents
from jedi.parser_utils import get_cached_code_lines from jedi.parser_utils import get_cached_code_lines
from jedi.file_io import FileIO from jedi.file_io import FileIO
from jedi import settings from jedi import settings
@@ -13,8 +14,9 @@ from jedi import debug
_BUILDOUT_PATH_INSERTION_LIMIT = 10 _BUILDOUT_PATH_INSERTION_LIMIT = 10
def _abs_path(module_context, path): def _abs_path(module_context, str_path: str):
if os.path.isabs(path): path = Path(str_path)
if path.is_absolute():
return path return path
module_path = module_context.py__file__() module_path = module_context.py__file__()
@@ -23,9 +25,8 @@ def _abs_path(module_context, path):
# system. # system.
return None return None
base_dir = os.path.dirname(module_path) base_dir = module_path.parent
path = force_unicode(path) return base_dir.joinpath(path).absolute()
return os.path.abspath(os.path.join(base_dir, path))
def _paths_from_assignment(module_context, expr_stmt): def _paths_from_assignment(module_context, expr_stmt):
@@ -147,7 +148,7 @@ def discover_buildout_paths(inference_state, script_path):
def _get_paths_from_buildout_script(inference_state, buildout_script_path): def _get_paths_from_buildout_script(inference_state, buildout_script_path):
file_io = FileIO(buildout_script_path) file_io = FileIO(str(buildout_script_path))
try: try:
module_node = inference_state.parse( module_node = inference_state.parse(
file_io=file_io, file_io=file_io,
@@ -165,18 +166,20 @@ def _get_paths_from_buildout_script(inference_state, buildout_script_path):
string_names=None, string_names=None,
code_lines=get_cached_code_lines(inference_state.grammar, buildout_script_path), code_lines=get_cached_code_lines(inference_state.grammar, buildout_script_path),
).as_context() ).as_context()
for path in check_sys_path_modifications(module_context): yield from check_sys_path_modifications(module_context)
yield path
def _get_parent_dir_with_file(path, filename): def _get_parent_dir_with_file(path: Path, filename):
for parent in traverse_parents(path): for parent in path.parents:
if os.path.isfile(os.path.join(parent, filename)): try:
if parent.joinpath(filename).is_file():
return parent return parent
except OSError:
continue
return None return None
def _get_buildout_script_paths(search_path): def _get_buildout_script_paths(search_path: Path):
""" """
if there is a 'buildout.cfg' file in one of the parent directories of the if there is a 'buildout.cfg' file in one of the parent directories of the
given module it will return a list of all files in the buildout bin given module it will return a list of all files in the buildout bin
@@ -188,13 +191,13 @@ def _get_buildout_script_paths(search_path):
project_root = _get_parent_dir_with_file(search_path, 'buildout.cfg') project_root = _get_parent_dir_with_file(search_path, 'buildout.cfg')
if not project_root: if not project_root:
return return
bin_path = os.path.join(project_root, 'bin') bin_path = project_root.joinpath('bin')
if not os.path.exists(bin_path): if not bin_path.exists():
return return
for filename in os.listdir(bin_path): for filename in os.listdir(bin_path):
try: try:
filepath = os.path.join(bin_path, filename) filepath = bin_path.joinpath(filename)
with open(filepath, 'r') as f: with open(filepath, 'r') as f:
firstline = f.readline() firstline = f.readline()
if firstline.startswith('#!') and 'python' in firstline: if firstline.startswith('#!') and 'python' in firstline:
@@ -202,14 +205,14 @@ def _get_buildout_script_paths(search_path):
except (UnicodeDecodeError, IOError) as e: except (UnicodeDecodeError, IOError) as e:
# Probably a binary file; permission error or race cond. because # Probably a binary file; permission error or race cond. because
# file got deleted. Ignore it. # file got deleted. Ignore it.
debug.warning(unicode(e)) debug.warning(e)
continue continue
def remove_python_path_suffix(path): def remove_python_path_suffix(path):
for suffix in all_suffixes(): for suffix in all_suffixes() + ['.pyi']:
if path.endswith(suffix): if path.suffix == suffix:
path = path[:-len(suffix)] path = path.with_name(path.stem)
break break
return path return path
@@ -218,8 +221,7 @@ def transform_path_to_dotted(sys_path, module_path):
""" """
Returns the dotted path inside a sys.path as a list of names. e.g. Returns the dotted path inside a sys.path as a list of names. e.g.
>>> from os.path import abspath >>> transform_path_to_dotted([str(Path("/foo").absolute())], Path('/foo/bar/baz.py').absolute())
>>> transform_path_to_dotted([abspath("/foo")], abspath('/foo/bar/baz.py'))
(('bar', 'baz'), False) (('bar', 'baz'), False)
Returns (None, False) if the path doesn't really resolve to anything. Returns (None, False) if the path doesn't really resolve to anything.
@@ -227,21 +229,22 @@ def transform_path_to_dotted(sys_path, module_path):
""" """
# First remove the suffix. # First remove the suffix.
module_path = remove_python_path_suffix(module_path) module_path = remove_python_path_suffix(module_path)
if module_path.name.startswith('.'):
return None, False
# Once the suffix was removed we are using the files as we know them. This # Once the suffix was removed we are using the files as we know them. This
# means that if someone uses an ending like .vim for a Python file, .vim # means that if someone uses an ending like .vim for a Python file, .vim
# will be part of the returned dotted part. # will be part of the returned dotted part.
is_package = module_path.endswith(os.path.sep + '__init__') is_package = module_path.name == '__init__'
if is_package: if is_package:
# -1 to remove the separator module_path = module_path.parent
module_path = module_path[:-len('__init__') - 1]
def iter_potential_solutions(): def iter_potential_solutions():
for p in sys_path: for p in sys_path:
if module_path.startswith(p): if str(module_path).startswith(p):
# Strip the trailing slash/backslash # Strip the trailing slash/backslash
rest = module_path[len(p):] rest = str(module_path)[len(p):]
# On Windows a path can also use a slash. # On Windows a path can also use a slash.
if rest.startswith(os.path.sep) or rest.startswith('/'): if rest.startswith(os.path.sep) or rest.startswith('/'):
# Remove a slash in cases it's still there. # Remove a slash in cases it's still there.
@@ -254,7 +257,9 @@ def transform_path_to_dotted(sys_path, module_path):
# is very strange and is probably a file that is called # is very strange and is probably a file that is called
# `.py`. # `.py`.
return return
yield tuple(split) # Stub folders for foo can end with foo-stubs. Just remove
# it.
yield tuple(re.sub(r'-stubs$', '', s) for s in split)
potential_solutions = tuple(iter_potential_solutions()) potential_solutions = tuple(iter_potential_solutions())
if not potential_solutions: if not potential_solutions:
+3 -38
View File
@@ -1,12 +1,8 @@
""" A universal module with functions / classes without dependencies. """ """ A universal module with functions / classes without dependencies. """
import sys
import contextlib
import functools import functools
import re import re
import os import os
from jedi._compatibility import reraise
_sep = os.path.sep _sep = os.path.sep
if os.path.altsep is not None: if os.path.altsep is not None:
@@ -36,7 +32,6 @@ class UncaughtAttributeError(Exception):
""" """
Important, because `__getattr__` and `hasattr` catch AttributeErrors Important, because `__getattr__` and `hasattr` catch AttributeErrors
implicitly. This is really evil (mainly because of `__getattr__`). implicitly. This is really evil (mainly because of `__getattr__`).
`hasattr` in Python 2 is even more evil, because it catches ALL exceptions.
Therefore this class originally had to be derived from `BaseException` Therefore this class originally had to be derived from `BaseException`
instead of `Exception`. But because I removed relevant `hasattr` from instead of `Exception`. But because I removed relevant `hasattr` from
the code base, we can now switch back to `Exception`. the code base, we can now switch back to `Exception`.
@@ -65,21 +60,17 @@ def reraise_uncaught(func):
difficult. This decorator is to help us getting there by changing difficult. This decorator is to help us getting there by changing
`AttributeError` to `UncaughtAttributeError` to avoid unexpected catch. `AttributeError` to `UncaughtAttributeError` to avoid unexpected catch.
This helps us noticing bugs earlier and facilitates debugging. This helps us noticing bugs earlier and facilitates debugging.
.. note:: Treating StopIteration here is easy.
Add that feature when needed.
""" """
@functools.wraps(func) @functools.wraps(func)
def wrapper(*args, **kwds): def wrapper(*args, **kwds):
try: try:
return func(*args, **kwds) return func(*args, **kwds)
except AttributeError: except AttributeError as e:
exc_info = sys.exc_info() raise UncaughtAttributeError(e) from e
reraise(UncaughtAttributeError(exc_info[1]), exc_info[2])
return wrapper return wrapper
class PushBackIterator(object): class PushBackIterator:
def __init__(self, iterator): def __init__(self, iterator):
self.pushes = [] self.pushes = []
self.iterator = iterator self.iterator = iterator
@@ -91,35 +82,9 @@ class PushBackIterator(object):
def __iter__(self): def __iter__(self):
return self return self
def next(self):
""" Python 2 Compatibility """
return self.__next__()
def __next__(self): def __next__(self):
if self.pushes: if self.pushes:
self.current = self.pushes.pop() self.current = self.pushes.pop()
else: else:
self.current = next(self.iterator) self.current = next(self.iterator)
return self.current return self.current
@contextlib.contextmanager
def ignored(*exceptions):
"""
Value manager that ignores all of the specified exceptions. This will
be in the standard library starting with Python 3.4.
"""
try:
yield
except exceptions:
pass
def indent_block(text, indention=' '):
"""This function indents a text block with a default of four spaces."""
temp = ''
while text and text[-1] == '\n':
temp += text[-1]
text = text[:-1]
lines = text.split('\n')
return '\n'.join(map(lambda s: indention + s, lines)) + temp
Loaded 100 of 234 files, more files were not shown because too many files have changed in this diff. Show more