brython-dev / brython

Brython (Browser Python) is an implementation of Python 3 running in the browser
BSD 3-Clause "New" or "Revised" License
6.36k stars 507 forks source link

No stubs for brython for PyCharm - crucial to make it popular (coding efficiency - heart of Python). #1938

Open ChameleonRed opened 2 years ago

ChameleonRed commented 2 years ago

I need autocomplete during coding in PyCharm. Python heart is fast coding so it is crucial.

I can not import really module from browser import document since no such file - PyCharm will allow autocompletion.

I think you can generate *.pyi stubs to allow completion. It can be generated in console. I do not know how to do it but you can.

Without autocompletion coding is very hard for beginners and slow for experts. That is true obstacle to make it popular.

For example I can use brython (it is not productive) but my younger programmers will have very big problems.

PierreQuentel commented 2 years ago

Even if I agreed that autocompletion in PyCharm is crucial to make Brython popular, I have no idea how to do it.

Someone developed a project brython_autocomplete to avoid error messages in IDEs when importing the browser module.

ChameleonRed commented 2 years ago

There are probably many methods - PyCharm support is crucial will speed up coding 5-10x times - so chance to use brython will increase also 5-10x:

First is to create fake python module like in brython_autocomplete (this works for sure). Second is to create stub *.pyi file using dir() or inspect (no tested but should work). Third is to create semi real/fake python modules which allow unit testing.

I think that second can be easy - not sure what with first.

I will try to do maybe second for private use.

See PyCharm help - this solution works I use it often (not sure what happen if no *.py files). https://www.jetbrains.com/help/pycharm/stubs.html#create-stub-external

ChameleonRed commented 2 years ago

For me only problem is that I can not use ast/inspect to generate stubs - I have problems to access code VFS dump some exception which I do not understand currently.

I will prepare some code snippets showing ast bugs - you can judge if this bugs can be removed or another method for stubs should be chosen - for me VFS is secret by now.

ChameleonRed commented 2 years ago

I wrote such script to stub and it works: If Brython will support ast or at least dta model for reflection more is possible.

Script is half baked so can be improved.

Structure for stub: stub stub\browser__init__.py stub\browser\document.pyi

Autocompletion works (not with typing but works)!

obraz


import browser
from browser import document, html
import inspect
from html import escape

body = document.select('body')[0]

INDENT_STR = ' ' * 4

def stub_function(a, v, indent=0) -> list[str]:
    out = []
    if hasattr(v, '__name__'):
        out.append(f'{INDENT_STR * indent}def {v.__name__}():')
    else:
        out.append(f'{INDENT_STR * indent}def {a}(...) -> ...')
    out.append(f'{INDENT_STR * (indent + 1)}pass')
    return out

def stub_class(class_type, indent=0):
    out = []
    out.append(f'{INDENT_STR * indent}class {class_type.__name__}(...):')
    for a in dir(class_type):
        print(a)
        try:
            v = getattr(class_type, a)
        except Exception as ex:
            print(ex)
            continue
        if inspect.ismethod(v):
            stub_function(a, v, indent + 1)
    return out

def stub_module(module) -> list[str]:
    out = []
    print(type(module))
    for a in dir(module):
        v = getattr(module, a)
        # print(a, dir(v))
        if inspect.isfunction(v):
            out.extend(stub_function(a, v))
        elif inspect.isclass(v):
            print(a)
            out.extend(stub_class(v))
    return out

def main():
    out = stub_module(browser.document)
    body <= html.PRE('\n'.join(escape(str(x)) for x in out))

if __name__ == '__main__':
    main()
Alxe commented 2 years ago

I noticed that the online API Reference is already, essentially, a stub file.
That is, using the referenced page, you could write a stub file in the following fashion:

"""
The package browser groups the built-in Brython-specific names and modules
"""
# import ...
def alert(message: str) -> None:
    """
    a function that prints the message in a pop-up window. Returns None
    """
def bind(target: DOMNode | str, event: str) -> Callable[..., Any]:
    """
    a function used as a decorator for event binding. Cf. section [events](https://brython.info/static_doc/en/events.html).
    """ 
def confirm(message: str) -> bool:
    """
    a function that prints the message in a window, and two buttons (ok/cancel). Returns True if ok, False if cancel
    """
# rest ...

This file could then be redistributed with the Brython PyPI package, in order to provide type hinting to Brython built-in modules.

image

@PierreQuentel Could you let me know where the documentation can be found? I'm interested in seeing how automatizable this could be.

PierreQuentel commented 2 years ago

@Alxe The documentation of the browser module is in this markdown file

ChameleonRed commented 2 years ago

That is very crucial to give people *.pyi / stub.

I am telling this story very often.

You drive a car and you find obstacle and somebody tell you that you can workaround - you do it - why not - you are hurry.

Next time you will drive car but you will never choose that way since you need do workaround and not drive to target directly.

API is like ways to achieve targets - better API remove as much obstacles as can :) Some API try to force you drive in specific way but it not works if there are alternatives.

Real-Gecko commented 2 years ago

I've created this module https://github.com/Real-Gecko/brython-stubs as a proof of concept.

https://user-images.githubusercontent.com/2231969/167427814-a6bd7517-4352-48dd-9adc-bba081987d92.mp4

Issues I see with this approach:

  1. Separate repo.
  2. pyi files are not generated from brython but are created manually.
  3. it adds extra package browser to lib which actually contains only stubs but no code, so possible name conflict with another modules. Using name browser-stubs did not work with VSCode imports. Putting stubs to brython folder did not work, import needed to be brython.browser.
Real-Gecko commented 2 years ago
  1. Using py.typed within brython did not work either.
Real-Gecko commented 2 years ago

Can someone explain how this line works? https://github.com/brython-dev/brython/blob/366f15c009bf44416f24dc626ebd796ddb89bb9a/www/src/Lib/browser/html.py#L1

PierreQuentel commented 2 years ago

My mistake, this is a very old file that I forgot to remove. The code of browser.html is in builtin_modules.js.

Real-Gecko commented 2 years ago

BTW I noticed that tests fail with latest commit on master branch.

args = [-26.7, 0.25, -0.25, 1.25, 0.75, -0.75]
expected = [-62.208243223652396,
1.2880225246980772,
1.5895753125511862,
-0.0982718364218127,
0.203280951431295,
1.5757045971498584]

for x, r in zip(args, expected):
    assert math.lgamma(x) == r

Not sure what's changed in math but result is like this now: image

PierreQuentel commented 2 years ago

Good catch ! I mostly test on Firefox and this test passed, but it failed on Chrome and Edge, probably subtle differences in arithmetic operations. I have replaced assert a == b by assert math.isclose(a, b).

Real-Gecko commented 2 years ago

I was running on Firefox too, but on Linux.

PierreQuentel commented 2 years ago

Nobody's perfect ;-)

Real-Gecko commented 2 years ago

So by crawling source code I came to a conclusion that browser shall be put into brython namespace:

from brython.browser import ajax, html

I know that it'll break compatibility with existing projects, but we can try and keep it in both places for some time with some kind of deprecation warning. This way we can create browser subdir in brython CLI installation and fill it with .pyi files to provide type hinting and type checking for brython in dev environments.

Real-Gecko commented 2 years ago

My mistake, this is a very old file that I forgot to remove. The code of browser.html is in builtin_modules.js.

I think same is true for ajax.py, svg.py, webcomponent.py and webworker.py

Real-Gecko commented 2 years ago

I've created a PR for you to check, any ideas on improving are welcome.

PierreQuentel commented 2 years ago

@Real-Gecko

So by crawling source code I came to a conclusion that browser shall be put into brython namespace:

Sorry but I will not accept the radical change from import browser to from brython import browser if the reason is that it's the only way that an IDE is able to handle the import (with no guarantee that other IDEs will be happy with PyCharm stubs).

The solution provided by brython_autocomplete looks much better to me.

I think same is true for ajax.py, svg.py, webcomponent.py and webworker.py

webworker.py is an old implementation of Web Workers, replaced long ago by worker.py. I have removed it.

These scripts cannot be removed: they import the Javascript modules _ajax.js, _svg.js etc.

Real-Gecko commented 2 years ago

Sorry but I will not accept the radical change from import browser to from brython import browser if the reason is that it's the only way that an IDE is able to handle the import (with no guarantee that other IDEs will be happy with PyCharm stubs).

Then we can try and create a separate module brython-stubs which will contain browser folder with .pyi files, but instead of being handwritten it will be generated from actual Brython source code. And by the way it's not PyCharm stubs it's a standard way of typehinting for Python which is used by IDEs.

Real-Gecko commented 2 years ago

Ok, I redid the PR and now we have brython-stubs submodule that can be put on PyPi I guess. It installs browser folder which contains .pyi files for Brython browser at least. However they're incomplete as no JS modules are parsed. Preposition for Typescript remains :D

mrkeuz commented 2 years ago

Few words in defense and acceptance of the using of stubs in general.

Stubs is useful for more than just autocompletion in IDE. Also, important, that stubs allows you to use static code analyzers to find errors, like "mypy" or IDE checkers. Which makes it easier to support growing projects and prevent errors in code even before running any code.

And of course working in IDE will much more convenient to work with typed libraries. I.e. this allows check the number and names of parameters, correct assignments of variable/parameter, validate returns types from functions, and so on.

Just for reference

Stubs only packages in PyPi allowed and well specified. And convenient for use in any IDE, just via installing certain stub package subset via pip. Examples: pandas-stubs, numpy-stubs. Also, good example is Micropython Plugin - they generate stubs for subset of native API provided only on microcontrollers. In common, stub files and packages easy for use and distribute.

As an idea, it is possible to start from a small repository with at least with partial stubs for basic browser API tied to the Brython version. It is quite easy to create package for this. Here is my ready for use PoC package for Esp32 Micropython. @Real-Gecko, just for reference.

Links:

I'm excited about the Brython project, thanks to Authors! If Brython project will overgrow with types that would be just great!

@Real-Gecko, hats off for the work done on the PoC 🚀🚀🚀

PierreQuentel commented 2 years ago

I trust you and @Real-Gecko that this would be useful for people who use IDE checkers or code analysers. It's just that I don't know anything about them, and I am afraid that if I accept the PR, in a few months there will be bug reports or feature requests and I will be on my own to reply.

If you create a project to support stubs I will be happy to mention it in Brython documentation.

washad commented 2 years ago

I'm tempted to create a brython-stubs library but might need your help in fully understanding the API signatures. Is that something you would be interested in? I already wrote some stubs yesterday, while playing with the tool for the first time ... something in the order of;


class TABLE:
    def __le__(self, other: TR) -> TABLE: ...

class TH:
    def __init__(self, value: str, **kwargs): ...
    def __add__(self, other: TH or list[TH]) -> list[TH]: ...

class TD:
    def __init__(self, value: str, **kwargs): ...
    def __add__(self, other: TD or list[TD]) -> list[TH]: ...

class TR:
    def __init__(self, vals: list[TH or TR]): ...
    def __le__(self, other: TH or TD) -> TR: ...
tjnaughton commented 1 year ago

Just noticed this issue now. Have you folks seen this? https://github.com/wapringle/mockbrython With PyCharm it provides a level of auto completion and suppresses annoying "unresolved reference" PyCharm messages when importing from browser. I'm not affiliated with this project in any way, but would use it if it was maintained.

JamesHutchison commented 10 months ago

Why was this marked as completed? Is there an up to date version somewhere?

I was thinking AI could probably do this pretty easily.

PierreQuentel commented 10 months ago

I closed it because there has been no activity on this issue for almost one year, no one seems to have time to invest to solve it, and I don't plan to include the solution in the Brython project itself.

But I can leave it open. If you or someone else publish a working solution I will document it.