Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request: a decorator to declare a function/class as "native" #71

Closed
dlashua opened this issue Nov 2, 2020 · 4 comments
Closed

Comments

@dlashua
Copy link
Contributor

dlashua commented Nov 2, 2020

Would it be possible to use a decorator to indicate that a function/class should remain native (i.e. not "pyscript") so that it can be called from other native (pure python) code?

As an example of what I mean, in the below code, I'd much prefer to define PyscriptWatchdogEventHandler directly in watchdog.py with some kind of decorator on it to indicate that it shouldn't be "pyscriptified".

https://gist.github.com/dlashua/f7d88f9a5afdcf7af17ce24266925a0b

Related Question: The purpose of the above code is to automatically reload pyscript files when they change (because I'm lazy). I use an input_boolean to indicate this functionality should be enabled that I can turn off when I'm not developing to save resources. Have I missed a situation that could occur and leave me with memory leaks or other unintended effects? Is there a better way to do this?

@craigbarratt
Copy link
Member

I've never used it before, but perhaps python's compile() function can be used to compile a function's source code (as native Python), which could then be callable in pyscript. A decorator could indicate that treatment. I'll look into it (but probably not for a while).

While it seems funny adding a compiler feature to an interpreter, if it can be made to work it would also be useful for callbacks from python packages to functions (eg: voluptuous type checking functions). All pyscript functions are async, so they can only be used as a callback if the code is expecting an async function and uses await when it calls it, which is not common outside of packages specifically designed to be async.

@craigbarratt
Copy link
Member

craigbarratt commented Dec 18, 2020

I just pushed a first attempt at providing native (compiled) functions in pyscript using the @pyscript_compile decorator.

Example:

@pyscript_compile
def incr_test(x):
    return x + 1

incr_test(10)

This defines incr_test as a regular, compiled python function, useful for callbacks when you need a regular function instead of a coroutine.

The python function can't use any pyscript features. It won't work if you use this as an inner function inside a pyscript function since the closure of local variables will not work (since they are objects in pyscript).

I've been experimenting with making things like state.get and state.set available inside the compiled function, but I haven't found a good way to make that work.

I guess you could use hass inside the compiled function if you need access to state variables etc.

Also, I'm not sure I like the decorator name, so that most likely will change.

@dlashua
Copy link
Contributor Author

dlashua commented Dec 29, 2020

This has been working great for me so far. I've not had any need for state.get or other pyscript functions because I just pass in and out of native functions as needed. I.E. the native functions only do non-hass stuff and then pass whatever back to the pyscript function that works with hass. It's a nice separation.

Also, for a decorator name, I think @python_native, @python, @python_raw, @python_plain, or @disable_pyscript all seem reasonable and describe it well to a user.

@dlashua
Copy link
Contributor Author

dlashua commented Dec 31, 2020

Using this, I was able to make a "file_helpers" module to allow me to read and write to the file system (which I needed because I have a pyscript app that automatically generates lovelace dashboard configuration files).

@pyscript_compile
def blocking_read_file(filename):
    f = open(filename, "r")
    data = f.read()
    f.close()
    return data

def read_file(filename):
    x = task.executor(blocking_read_file, filename)
    return x


@pyscript_compile
def blocking_write_file(filename, data):
    f = open(filename, "w")
    f.write(data)
    f.close()

def write_file(filename, data):
    task.executor(blocking_write_file, filename, data)

Since this is maybe a common use case for @pyscript_compile, there could be a separate decorator to mean "this is native python code that needs to be run in the executor" and pyscript would do all the needed things, changing my code to this:

@pyscript_compile_executor
def read_file(filename):
    f = open(filename, "r")
    data = f.read()
    f.close()
    return data

@pyscript_compile_executor
def write_file(filename, data):
    f = open(filename, "w")
    f.write(data)
    f.close()

Not that there's anything wrong with the two method approach I'm using now. I don't think enough people will be doing this sort of thing to make it worth while to put too much effort into making it any easier to use. As it is, I imagine @pyscript_compile will get very little use, though, for those who need it, it's a LOT easier than the method of extending the import path and having these methods defined in a separate file.

craigbarratt added a commit that referenced this issue Feb 16, 2021
…t_compile and

additionally wraps the resulting function with a call to ``task.executor``; see #71.
Also added error checking for @pyscript_compile and @pyscript_executor to enforce
there are no args or kwargs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants