Safe code refactoring for modern Python.

Overview

Bowler

Safe code refactoring for modern Python projects.

build status code coverage version changelog license code style

Overview

Bowler is a refactoring tool for manipulating Python at the syntax tree level. It enables safe, large scale code modifications while guaranteeing that the resulting code compiles and runs. It provides both a simple command line interface and a fluent API in Python for generating complex code modifications in code.

Bowler uses a "fluent" Query API to build refactoring scripts through a series of selectors, filters, and modifiers. Many simple modifications are already possible using the existing API, but you can also provide custom selectors, filters, and modifiers as needed to build more complex or custom refactorings. See the Query Reference for more details.

Using the query API to rename a single function, and generate an interactive diff from the results, would look something like this:

query = (
    Query(<paths to modify>)
    .select_function("old_name")
    .rename("new_name")
    .diff(interactive=True)
)

For more details or documentation, check out https://pybowler.io

Installing Bowler

Bowler supports modifications to code from any version of Python 2 or 3, but it requires Python 3.6 or higher to run. Bowler can be easily installed using most common Python packaging tools. We recommend installing the latest stable release from PyPI with pip:

pip install bowler

You can also install a development version from source by checking out the Git repo:

git clone https://github.com/facebookincubator/bowler
cd bowler
python setup.py install

License

Bowler is MIT licensed, as found in the LICENSE file.

Comments
  • python2 print statements broken

    python2 print statements broken

    Parsing python2 code without print functions turned on seems to be broken? The README seems to imply that this should still work (Bowler supports modifications to code from any version of Python 2 or 3)

    Dockerfile:

    FROM python:3.7
    
    RUN pip3 install git+https://github.com/facebookincubator/Bowler.git
    RUN echo "print 'something'" > test.py
    RUN bowler dump test.py
    

    Output:

    $ bowler dump test.py
    ERROR:bowler.tool:Skipping test.py: failed to parse
    
    wontfix 
    opened by ndevenish 7
  • distribute as a wheel on pypi

    distribute as a wheel on pypi

    As a developer in charge of CI and automation I want this project distributed to pypi as a wheel because wheels are faster to deploy, and they don't require the runtime version of python to be available when running pip install. The vast majority of projects are using wheels now, and it's really not much more work for the maintainer. Another thing that should be fixed is that the project should be marked as python3-only on PyPI.

    thanks!

    enhancement 
    opened by chadrik 6
  • Fix encapsulate method in Query class

    Fix encapsulate method in Query class

    Hello,

    It looks like this method never works and there were no tests for it. I have repaired the method to generate the correct indentation and added tests to prevent future regression.

    Best regards,

    CLA Signed 
    opened by mik-laj 5
  • Feature request: helper(s) to avoid syntax errors

    Feature request: helper(s) to avoid syntax errors

    I wrote a thing to convert self.assertFalse(x) to assert not x, etc. Trouble is, it falls over easily when the expression in the parentheses isn't valid without the parentheses:

    self.assertFalse(
        'a'
        'b'
    )
    

    My code naively converts this to

    assert not 'a'
        'b'
    
    • I think a parenthesize_if_necessary helper would be really useful for this kind of situation. I imagine this bug is widespread. I tried to write one, but I'm a bit out of my depth.
    • It'd be useful to have access to a function which asserts a list of nodes has no syntax errors. I realise that would vary depending on targeted python version and __future__ imports, but perhaps something can be done?

    Thanks

    enhancement 
    opened by craigds 5
  • Pybowler cannot work on Windows. Traceback: component

    Pybowler cannot work on Windows. Traceback: component "sh" is currently only supported on linux/osx

    After installation of pybowler using pip install on windows 10, running bowler in powershell returns TB. Steps:

    1. in Python folder (typically something like C:\Python36_32), go to Scripts folder, and run ".\pip.exe install bowler.
    2. run .\bowler.exe in same folder (Python36_32/Scripts)
    3. TB as follows:
    Traceback (most recent call last):
    
    File "C:\Python36\Scripts\bowler-script.py", line 11, in <module>
      load_entry_point('bowler==0.5.1', 'console_scripts', 'bowler')()
    
    File "c:\python36\lib\site-packages\pkg_resources\__init__.py", line 565, in load_entry_point
      return get_distribution(dist).load_entry_point(group, name)
    
    File "c:\python36\lib\site-packages\pkg_resources\__init__.py", line 2631, in load_entry_point
      return ep.load()
    
    File "c:\python36\lib\site-packages\pkg_resources\__init__.py", line 2291, in load
      return self.resolve()
    
    File "c:\python36\lib\site-packages\pkg_resources\__init__.py", line 2297, in resolve
      module = __import__(self.module_name, fromlist=['__name__'], level=0)
    
    File "c:\python36\lib\site-packages\bowler\__init__.py", line 12, in <module>
      from .tool import BowlerTool
    
    File "c:\python36\lib\site-packages\bowler\tool.py", line 12, in <module>
      import sh
    
    File "c:\python36\lib\site-packages\sh.py", line 36, in <module>
      support." % __version__)
    
    ImportError: sh 1.12.14 is currently only supported on linux and osx. please install pbs 0.110 (http://pypi.python.org/pypi/pbs) for windows support.
    
    bug windows 
    opened by WinstonPoh 5
  • Refactoring Python 2.7 files does not work (parse error on print statement)

    Refactoring Python 2.7 files does not work (parse error on print statement)

    https://pybowler.io/docs/basics-intro

    lib2to3 provides a concrete syntax tree (CST) implementation that recognizes and supports the grammar of all Python versions back to 2.6.

    By building on lib2to3, Bowler is capable of reading and modifying source files written for both Python 2 and 3. That said, Bowler requires Python 3.6 or newer to run

    Everything was working fine until I tried running on a (Py 2.7) source file which had a print statement in it

    Then I get:

    ERROR:RefactoringTool:Can't parse tests/fixtures/py27.py: ParseError: bad input: type=3, value='"Python 2 syntax"', context=(' ', (34, 10))
    

    After putting some breakpoints in and digging through the source it seems the problem is here: https://github.com/facebookincubator/Bowler/blob/195fbae4550ea82d72b9c59f3dca372b20d1edf5/bowler/tool.py#L98

    BowlerTool hard-codes it so that the underlying fissix RefactorTool will use the no-print-statements grammar, which is therefore not Python 2 compatible.

    Commenting out that line fixes it for me... I wonder why it was added?

    opened by anentropic 4
  • What are the restrictions on chaining select methods?

    What are the restrictions on chaining select methods?

    The readme appears to say you can make a query that has a select followed by filters and modifiers, then chain a subsequent select onto it: https://github.com/facebookincubator/Bowler/tree/master/bowler#query-reference

    When I try to do this I get an error:

      File "/.virtualenvs/waterloo-sHXra0II/lib/python3.7/site-packages/bowler/tool.py", line 209, in refactor_queue
        hunks = self.refactor_file(filename)
      File "/.virtualenvs/waterloo-sHXra0II/lib/python3.7/site-packages/bowler/tool.py", line 171, in refactor_file
        tree = self.refactor_string(input, filename)
      File "/.virtualenvs/waterloo-sHXra0II/lib/python3.7/site-packages/fissix/refactor.py", line 370, in refactor_string
        self.refactor_tree(tree, name)
      File "/.virtualenvs/waterloo-sHXra0II/lib/python3.7/site-packages/fissix/refactor.py", line 410, in refactor_tree
        self.traverse_by(self.bmi_post_order_heads, tree.post_order())
      File "/.virtualenvs/waterloo-sHXra0II/lib/python3.7/site-packages/fissix/refactor.py", line 488, in traverse_by
        node.replace(new)
      File "/.virtualenvs/waterloo-sHXra0II/lib/python3.7/site-packages/fissix/pytree.py", line 108, in replace
        assert self.parent is not None, str(self)
    AssertionError
    

    I am guessing that this is because the subsequent selector has to select some sub-element of what was already selected by the initial select? You can't chain an unrelated select query on?

    (I wanted to do that because I am naïvely trying to pass metadata about the current file between my modifiers)

    opened by anentropic 4
  • multiprocessing issue on windows

    multiprocessing issue on windows

    Unrelated to the sh problem in windows I've caught another one:

    Traceback (most recent call last):
      File "E:\packages\try_bowler\test.py", line 7, in <module>
        rename.dump()
      File "C:\Users\Levitanus\AppData\Local\Programs\Python\Python37-32\lib\site-packages\bowler\query.py", line 967, in dump
        return self.execute(write=False)
      File "C:\Users\Levitanus\AppData\Local\Programs\Python\Python37-32\lib\site-packages\bowler\query.py", line 957, in execute
        self.retcode = BowlerTool(fixers, **kwargs).run(self.paths)
      File "C:\Users\Levitanus\AppData\Local\Programs\Python\Python37-32\lib\site-packages\bowler\tool.py", line 342, in run
        self.refactor(paths)
      File "C:\Users\Levitanus\AppData\Local\Programs\Python\Python37-32\lib\site-packages\bowler\tool.py", line 223, in refactor
        child.start()
      File "C:\Users\Levitanus\AppData\Local\Programs\Python\Python37-32\lib\multiprocessing\process.py", line 112, in start
        self._popen = self._Popen(self)
      File "C:\Users\Levitanus\AppData\Local\Programs\Python\Python37-32\lib\multiprocessing\context.py", line 223, in _Popen
        return _default_context.get_context().Process._Popen(process_obj)
      File "C:\Users\Levitanus\AppData\Local\Programs\Python\Python37-32\lib\multiprocessing\context.py", line 322, in _Popen
        return Popen(process_obj)
      File "C:\Users\Levitanus\AppData\Local\Programs\Python\Python37-32\lib\multiprocessing\popen_spawn_win32.py", line 65, in __init__
        reduction.dump(process_obj, to_child)
      File "C:\Users\Levitanus\AppData\Local\Programs\Python\Python37-32\lib\multiprocessing\reduction.py", line 60, in dump
        ForkingPickler(file, protocol).dump(obj)
    AttributeError: Can't pickle local object 'Query.create_fixer.<locals>.Fixer'
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "C:\Users\Levitanus\AppData\Local\Programs\Python\Python37-32\lib\multiprocessing\spawn.py", line 99, in spawn_main
        new_handle = reduction.steal_handle(parent_pid, pipe_handle)
      File "C:\Users\Levitanus\AppData\Local\Programs\Python\Python37-32\lib\multiprocessing\reduction.py", line 87, in steal_handle
        _winapi.DUPLICATE_SAME_ACCESS | _winapi.DUPLICATE_CLOSE_SOURCE)
    PermissionError: [WinError 5] Отказано в доступе
    [Finished in 0.3s with exit code 1]
    

    Original import in tool.py was replaced to import pbs as sh, but. as I understant, flow hasn't gone to the patching part of refactor

    original source:

    import bowler as bl
    
    
    rename = bl.Query()
    rename.select_var('value')
    rename.rename('new_val')
    rename.dump()
    
    windows 
    opened by Levitanus 4
  • "Bowler" is nigh impossible to Google

    Unfortunately facebook bowler does not return this project, nor is it easy to do a search for bowler + problem or facebook bowler + problem to see if anyone else is having this issue.

    Cutesy names are cool and all, but there is so much noise looking for bowler that it makes it hard to use the project.

    opened by bertjwregeer 4
  • Question: I have a working select and modifier but want it to remove the matched node, can only make the node a blank line

    Question: I have a working select and modifier but want it to remove the matched node, can only make the node a blank line

    I've built a selector that finds common class attributes and moves them to a superclass definition - but rather than removing the node from the matched class - I can only make the line blank, by setting by calling node.children.clear()

    This also only makes an empty line not a full removal attrib_assignment = capture.get("attr_assignment") attrib_assignment.parent.children.remove(attrib_assignment)

    Thanks in advance

    opened by malsmith 3
  • Callback return type

    Callback return type

    Use a more specific return type at https://github.com/facebookincubator/Bowler/blob/master/bowler/types.py#L47

    Right now it's Any, and seems like it should be Optional[LN]

    1. Prepare a change to do that (see getting started docs)
    2. Verify that mypy is happy with it using make lint
    3. Send a PR!
    good first issue 
    opened by thatch 3
  • Bump wheel from 0.35.1 to 0.38.1

    Bump wheel from 0.35.1 to 0.38.1

    Bumps wheel from 0.35.1 to 0.38.1.

    Changelog

    Sourced from wheel's changelog.

    Release Notes

    UNRELEASED

    • Updated vendored packaging to 22.0

    0.38.4 (2022-11-09)

    • Fixed PKG-INFO conversion in bdist_wheel mangling UTF-8 header values in METADATA (PR by Anderson Bravalheri)

    0.38.3 (2022-11-08)

    • Fixed install failure when used with --no-binary, reported on Ubuntu 20.04, by removing setup_requires from setup.cfg

    0.38.2 (2022-11-05)

    • Fixed regression introduced in v0.38.1 which broke parsing of wheel file names with multiple platform tags

    0.38.1 (2022-11-04)

    • Removed install dependency on setuptools
    • The future-proof fix in 0.36.0 for converting PyPy's SOABI into a abi tag was faulty. Fixed so that future changes in the SOABI will not change the tag.

    0.38.0 (2022-10-21)

    • Dropped support for Python < 3.7
    • Updated vendored packaging to 21.3
    • Replaced all uses of distutils with setuptools
    • The handling of license_files (including glob patterns and default values) is now delegated to setuptools>=57.0.0 (#466). The package dependencies were updated to reflect this change.
    • Fixed potential DoS attack via the WHEEL_INFO_RE regular expression
    • Fixed ValueError: ZIP does not support timestamps before 1980 when using SOURCE_DATE_EPOCH=0 or when on-disk timestamps are earlier than 1980-01-01. Such timestamps are now changed to the minimum value before packaging.

    0.37.1 (2021-12-22)

    • Fixed wheel pack duplicating the WHEEL contents when the build number has changed (#415)
    • Fixed parsing of file names containing commas in RECORD (PR by Hood Chatham)

    0.37.0 (2021-08-09)

    • Added official Python 3.10 support
    • Updated vendored packaging library to v20.9

    ... (truncated)

    Commits
    • 6f1608d Created a new release
    • cf8f5ef Moved news item from PR #484 to its proper place
    • 9ec2016 Removed install dependency on setuptools (#483)
    • 747e1f6 Fixed PyPy SOABI parsing (#484)
    • 7627548 [pre-commit.ci] pre-commit autoupdate (#480)
    • 7b9e8e1 Test on Python 3.11 final
    • a04dfef Updated the pypi-publish action
    • 94bb62c Fixed docs not building due to code style changes
    • d635664 Updated the codecov action to the latest version
    • fcb94cd Updated version to match the release
    • Additional commits viewable in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

    CLA Signed dependencies 
    opened by dependabot[bot] 0
  • Switch from volatile to tempfile.

    Switch from volatile to tempfile.

    bowler/tests/lib.py: switch from volatile back to tempfile

    The volatile project is unmaintained. Use tempfile module, which provides similar functionality.

    See also: 3664c3f145a819ddd1f5d5e39df86a4f34c4f3f2

    CLA Signed 
    opened by qwetwe 2
  • Ingore notebook syntax

    Ingore notebook syntax

    Hello, I am trying to apply changes also on some notebooks from databricks stored as .py files, from time to time, there is %pip install or %magic command, could you please tell me how to ignore it easily, do you support such a case?

    Thank you! Best regards, Andrej

    opened by azachar 1
  • Bowler should include docs on how to test/debug/understand lib2to3 patterns

    Bowler should include docs on how to test/debug/understand lib2to3 patterns

    Not at all sure where something like this belongs, so I'm creating an issue in the hope that the team can give some guidance. When first starting with bowler, I struggled to wrap my head around how the lib2to3 patterns worked. The problem was compounded by the general lack of easily googleable information. To help myself understand (and after some reading though the source code), I came up with a basic way to play around:

    from pprint import pprint
    
    from fissix import pytree, pygram
    from fissix.patcomp import PatternCompiler
    from fissix.pgen2 import driver
    
    d = driver.Driver(pygram.python_grammar.copy(), pytree.convert)
    pc = PatternCompiler()
    
    def test(pattern, code):
        pat = pc.compile_pattern(pattern)
        for node in d.parse_string(code).pre_order():
            for match in pat.generate_matches([node]):
                pprint(match)
    
    # Anything calling .foo() with any args:
    pattern="""
    call=power<
      any*
      trailer<"." "foo">
      trailer<"(" args=any* ")">
    >
    """
    # Prints out details about both the f.foo and bar.foo calls
    test(pattern,'a = f.foo(123) + bar.foo("adf")\n')
    

    This test function made it much easier for me to start writing + understanding patterns because I could quickly iterate tests inside of a REPL.

    Should something like this function be added to the website/documentation/somewhere else? Even without extensive documentation for the lib2to3 patterns (which would be a lot of work), it would be nice for new users to have a good starting point like this. (It also might be that this function already exists somewhere, and I just reinvented the wheel - whoops!).

    opened by msci-jordancrawford 0
  • bowler/README.md documents `rename_function` however this functionality does not appear to exist

    bowler/README.md documents `rename_function` however this functionality does not appear to exist

    Hi,

    bowler/README.md documents rename_function however this functionality does not appear to exist. 'hope to see it added soon!

    Happy New Year, Nicholas

    opened by sten0 0
  • Doc bugs: what is the syntax for `bowler test`?, obsolete or future-functionality documentation, and a doc info request

    Doc bugs: what is the syntax for `bowler test`?, obsolete or future-functionality documentation, and a doc info request

    Hi,

    I'm packaging Bowler for Debian, and while working on the man page I wasn't able to find documentation for bowler test. Is it:

    test [path | module] [-- options]

    or something else? https://pybowler.io/docs/api-commands is missing this info too.

    Other than that, I also found what appears to be either obsolete or yet-to-be-written functionality documented here: https://github.com/facebookincubator/Bowler/blame/master/bowler/README.md#L46

    In Debian we have a policy of providing as complete documentation as possible so that a user can put the software on a disk, disappear to a desert island, install it there, and have the all information at hand that is necessary to learn to use the software proficiently without internet access. At this time, the documentation appears to require Github-specific tooling. Do you know where I can find that tooling? If for some reason that tooling is not permissively licensed enough to meet the Debian Free Software Guidelines, or flexible enough, would you be willing to accept a PR demonstrates full and careful conversion to sphinxdoc? I understand this would require changes to the website's config, which is why I'm offering to help out (probably in January). Why sphinxdoc? It's a great format for conversion to HTML, PDF, man, info, and even EPUB, and it has some nice automatic features. I think you'd like the tooling :-)

    Here's a practical and to-the-point intro article: https://medium.com/@andreas.007/automate-documentation-in-python-925c38eae69f And some of the available themes: https://sphinx-themes.org

    The HTML output is also supports fancy JS features, but by January I'll be limited to the ones that are already in Debian (we're going into a feature freeze then, so no new packages, and all packages and docs must be buildable without internet access).

    Thank you for your work on Bowler! Nicholas

    opened by sten0 1
Owner
Facebook Incubator
We work hard to contribute our work back to the web, mobile, big data, & infrastructure communities. NB: members must have two-factor auth.
Facebook Incubator
Simple, hassle-free, dependency-free, AST based source code refactoring toolkit.

refactor is an end-to-end refactoring framework that is built on top of the 'simple but effective refactorings' assumption. It is much easier to write a simple script with it rather than trying to figure out what sort of a regex you need in order to replace a pattern (if it is even matchable with regexes).

Batuhan Taskaya 385 Jan 6, 2023
Awesome autocompletion, static analysis and refactoring library for python

Jedi - an awesome autocompletion, static analysis and refactoring library for Python Jedi is a static analysis tool for Python that is typically used

Dave Halter 5.3k Dec 29, 2022
a python refactoring library

rope, a python refactoring library ... Overview Rope is a python refactoring library. Notes Nick Smith <[email protected]> takes over maintaining rope

null 1.5k Dec 30, 2022
Bottom-up approach to refactoring in python

Introduction RedBaron is a python library and tool powerful enough to be used into IPython solely that intent to make the process of writing code that

Python Code Quality Authority 653 Dec 30, 2022
AST based refactoring tool for Python.

breakfast AST based refactoring tool. (Very early days, not usable yet.) Why 'breakfast'? I don't know about the most important, but it's a good meal.

eric casteleijn 0 Feb 22, 2022
Refactoring Python Applications for Simplicity

Python Refactoring Refactoring Python Applications for Simplicity. You can open and read project files or use this summary ?? Concatenate String my_st

Mohammad Dori 3 Jul 15, 2022
Code generation and code search for Python and Javascript.

Codeon Code generation and code search for Python and Javascript. Similar to GitHub Copilot with one major difference: Code search is leveraged to mak

null 51 Dec 8, 2022
Find dead Python code

Vulture - Find dead code Vulture finds unused code in Python programs. This is useful for cleaning up and finding errors in large code bases. If you r

Jendrik Seipp 2.4k Dec 27, 2022
Removes commented-out code from Python files

eradicate eradicate removes commented-out code from Python files. Introduction With modern revision control available, there is no reason to save comm

Steven Myint 146 Dec 13, 2022
A library that modifies python source code to conform to pep8.

Pep8ify: Clean your code with ease Pep8ify is a library that modifies python source code to conform to pep8. Installation This library currently works

Steve Pulec 117 Jan 3, 2023
Turn your C++/Java code into a Python-like format for extra style points and to make everyone hates you

Turn your C++/Java code into a Python-like format for extra style points and to make everyone hates you

Tô Đức (Watson) 4 Feb 7, 2022
A simple Python bytecode framework in pure Python

A simple Python bytecode framework in pure Python

null 3 Jan 23, 2022
A system for Python that generates static type annotations by collecting runtime types

MonkeyType MonkeyType collects runtime types of function arguments and return values, and can automatically generate stub files or even add draft type

Instagram 4.1k Dec 28, 2022
Programmatically edit text files with Python. Useful for source to source transformations.

massedit formerly known as Python Mass Editor Implements a python mass editor to process text files using Python code. The modification(s) is (are) sh

null 106 Dec 17, 2022
Tool for translation type comments to type annotations in Python

com2ann Tool for translation of type comments to type annotations in Python. The tool requires Python 3.8 to run. But the supported target code versio

Ivan Levkivskyi 123 Nov 12, 2022
Leap is an experimental package written to enable the utilization of C-like goto statements in Python functions

Leap is an experimental package written to enable the utilization of C-like goto statements in Python functions

null 6 Dec 26, 2022
IDE allow you to refactor code, Baron allows you to write refactoring code.

Introduction Baron is a Full Syntax Tree (FST) library for Python. By opposition to an AST which drops some syntax information in the process of its c

Python Code Quality Authority 278 Dec 29, 2022
Simple, hassle-free, dependency-free, AST based source code refactoring toolkit.

refactor is an end-to-end refactoring framework that is built on top of the 'simple but effective refactorings' assumption. It is much easier to write a simple script with it rather than trying to figure out what sort of a regex you need in order to replace a pattern (if it is even matchable with regexes).

Batuhan Taskaya 385 Jan 6, 2023
This Repository is an up-to-date version of Harvard nlp's Legacy code and a Refactoring of the jupyter notebook version as a shell script version.

This Repository is an up-to-date version of Harvard nlp's Legacy code and a Refactoring of the jupyter notebook version as a shell script version.

신재욱 17 Sep 25, 2022
Awesome autocompletion, static analysis and refactoring library for python

Jedi - an awesome autocompletion, static analysis and refactoring library for Python Jedi is a static analysis tool for Python that is typically used

Dave Halter 5.3k Dec 29, 2022