Unfortunately I’m getting this error:
2024-06-08 08:51:14.622 ERROR (MainThread) [custom_components.pyscript.file.wiki.search_wikipedia] Exception in <file.wiki.search_wikipedia> line 6:
summary = task.executor(wikipedia.summary, **funcArgs)
Unfortunately I’m getting this error:
2024-06-08 08:51:14.622 ERROR (MainThread) [custom_components.pyscript.file.wiki.search_wikipedia] Exception in <file.wiki.search_wikipedia> line 6:
summary = task.executor(wikipedia.summary, **funcArgs)
Welp. I’ve certainly seen more useful error messages than that
What if you try this?
summary = task.executor(wikipedia.summary, kwargs=funcArgs)
I’ll see if I can play around with some code myself later today, I’ll get back to you if I find something.
Exception in <file.wiki.search_wikipedia> line 6: summary = task.executor(wikipedia.summary, kwargs=funcArgs) ^ TypeError: summary() got an unexpected keyword argument 'kwargs'
Thanks for your persistence. Still not working I feel very confident that this will work if somehow we can get the variables passed.
Sorry it took me a bit longer, but I got it working!
@service(supports_response="optional")
def search_wikipedia(searchterm=None, return_response=True):
"""yaml
name: Search Wikipedia
description: hello_world service example using pyscript.
fields:
searchterm:
description: What to search for?
example: Madonna
required: true
selector:
text:
"""
funcArgs = {"sentences": 2, "auto_suggest": False}
summary = task.executor(wikipedia.summary, searchterm, **funcArgs)
response_variable = { "summary": summary }
return response_variable
I am not fully sure why query cannot be in the **kwargs
argument of task.executor
honestly. But calling this service from Home Assistant returned a summary of Madonna for me.
You are the champion of champions my friend! I cannot thank you enough. This opens so many possibilities for me and the users of my project. We will be able to extend this to other APIs for grabbing data.
If interested, check out the project wiki and/or Discord:
It’s still in beta but really close to release.
Anyone still seeing an error with state.persist
?
Cross-posting from another thread: State.persist in pyscript not working - #8 by danhiking
Basically: state.persist('pyscript.foo', default_value=None)
still results in NameError: name 'pyscript.foo' is not defined
when I try to access pyscript.foo
.
Any idea what might have changed, or what precondition is required for the state.persist
mechanism to work?
@Slalamander I’m back to bother you one more time. I’m wanting to pull data via API and am not sure how to proceed.
import http.client
@service(supports_response="optional")
def get_dadjoke(return_response=True):
"""yaml
name: Get Dad joke
description: Gets a random dad joke from api
"""
conn = http.client.HTTPSConnection("dad-jokes-by-api-ninjas.p.rapidapi.com")
headers = {
'x-rapidapi-key': "get your api key at rapidapi",
'x-rapidapi-host': "dad-jokes-by-api-ninjas.p.rapidapi.com"
}
conn.request("GET", "/v1/dadjokes", headers=headers)
res = conn.getresponse()
response_variable = res.read()
return response_variable
I’m basing this on a sample script from rapidapi which works in regular python (I have a valid api key) and trying to mix in what you gave me for the wikipedia part.
I’m a bit confused on the task.executor portion. I know just a bit about python so excuse me. Do I need to use the task.executor for all lines that use that ‘conn’ variable or certain ones, or none? Seems like a pretty simple script but not so much for me. I think once I get one of these working I’ll be able to follow suit for others and this will open up a lot of things. Thanks for your time and help!
The task.executor
is there for functions that block the event loop. I’m not fully sure how the methods for conn
function, but I think only conn.request
should be run in the executor, perhaps though also getresponse
.
However, I think it may be easier to use the requests
module. I believe that is included by default in Home Assistant installations. The code would become something like the following I think.
import requests
@service(supports_response="optional")
def get_dadjoke(return_response=True):
"""yaml
name: Get Dad joke
description: Gets a random dad joke from api
"""
headers = {
'x-rapidapi-key': "get your api key at rapidapi",
'x-rapidapi-host': "dad-jokes-by-api-ninjas.p.rapidapi.com"
}
r = task.executor(requests.get, url, headers=headers)
response_variable = r.json()
return response_variable
If you need to pass an authentication argument, you can add auth=auth
.
I defined url like this:
url = "https://dad-jokes-by-api-ninjas.p.rapidapi.com/v1/dadjokes"
ran the script and got this:
websocket_api script: Error executing script. Error for call_service at pos 1: Failed to process the returned service response data, expected a dictionary, but got <class 'NoneType'>
websocket_api script: Error executing script. Error for call_service at pos 1: Failed to process the returned service response data, expected a dictionary, but got <class 'list'>
I wish I was better at troubleshooting. Is this failing to connect and therefore is returning the dictionary error because the response is empty?
EDIT By the way, I tried changing from https to http and got this:
message: Please use HTTPS protocol
as my response so it appears to at least be contacting the server. Wondering if it is getting those headers or not. Again, I’m a novice here. Thanks again for the help.
EDIT2 This is an example of what the python script (not pyscript) outputs for reference:
[{"joke": "What do you put on a lonely grilled cheese sandwich? Provolone, but only if you have it\u2019s parmesan."}]
Ah that’s an easy fix actually, it’s just a dict put into a list, and Home Assistant only accepts dicts as response variables. I didn’t notice the json()
function from requests always returns a list with json data (See the docs here). Assuming resp
is the output of the python script, you should be able to return it to Home Assistant as such:
resp = r.json()
return resp[0]
This assumes you’re only getting one joke at a time. If it’s possible to get more jokes, you could also do the following:
resp = r.json()
jokes = {"jokes": resp}
return jokes
This way, the data under "jokes"
will always be a list, so you’d have to extract each joke individually in Home Assistant, even if it is just the one. I don’t know of the top of my head how to do that in Jinja2, so let me know if you run into trouble with that.
EDIT:
Come to think of it, your first error shows a Nonetype
returning. I’m not fully sure what the case is with that. It may be a good idea to check for statuscodes, in case you get errors. See here.
if r.status_code == requests.codes.ok:
##Code here with how you want to return the response
else:
resp = {"error": r.status_code, "data": r.json()}
return resp
(You don’t need to return errors, but it may be useful while debugging).
Once again, thank you for this. I will be able to use this method to pull lots of different data from api. You were right, the fix was very easy and I guess the error I provided may have included something from a previous run.
This is all that was needed:
resp = r.json()
return resp[0]
So I thought I’d give myself a little more practice and modify the wiki search to use a different method:
import requests
@service(supports_response="optional")
def search_wikipedia_new(searchterm=None, return_response=True):
"""yaml
name: Search Wikipedia
description: hello_world service example using pyscript.
fields:
searchterm:
description: What to search for?
example: Madonna
required: true
selector:
text:
"""
url = "https://en.wikipedia.org/api/rest_v1/page/summary/Madonna?redirect=true"
r = task.executor(requests.get, url)
if r.status_code == requests.codes.ok:
response_variable = r.json()
return response_variable[0]
else:
response_variable = {"error": r.status_code, "data": r.json()}
return response_variable
I feel like I’m not changing much short of the url and not using the headers but I am once again getting the error:
Failed to process the returned service response data, expected a dictionary, but got <class 'NoneType'>.
Is there an easy way to view what is being received? It’s super hard to troubleshoot this without being able to somehow get data out short of the return variable route.
As a test I wrote this in regular python and it works as expected:
import requests
url = "https://en.wikipedia.org/api/rest_v1/page/summary/Madonna?redirect=true"
r = requests.get(url)
if r.status_code == requests.codes.ok:
response_variable = r.json()
print (response_variable)
else:
response_variable = {"error": r.status_code, "data": r.json()}
print (response_variable)
Again, thank you in teaching me this stuff. It will pay off for sure just frustrating with the errors at the moment.
Debugging pyscripts is a real pain yeah I tend to use print statements myself and look them up in the log. However they accumulate and different log messages tend to be filed under the same header in the Home Assistant logs
You could try setting up the Jupyter Notebook environment, as that allows real time code execution I believe, so it’d be easier to check the print statements in the notebook. However I haven’t used it much, so I can’t really help much with setting it up. I believe the pyscript docs explain it quite a bit tho!
Those print statements sounds like my kind of troubleshooting as I’m definitely familiar with that approach. I didn’t realize that was an option. I’m guessing I need to set logging to a certain level to see that?
I tried the Jupyter Notebook approach but it’s not easy with my environment. I’m running on Docker and it is a struggle for me to figure out how to get that in place. I did set up a VM and was able to install JN but now having issues getting that going. Struggles.
Jep, see Reference — hacs-pyscript 1.5.0 documentation
You can also use log.warning("something happened")
which should probably log it in HA without needing additional configuration (I believe warning is the default level for integrations). For the print statement, the loglevel should be debug.
import requests
@service(supports_response="optional")
def search_wikipedia_new(searchterm=None, return_response=True):
"""yaml
name: Search Wikipedia New
description: hello_world service example using pyscript.
fields:
searchterm:
description: What to search for?
example: Madonna
required: true
selector:
text:
"""
url = "https://en.wikipedia.org/api/rest_v1/page/summary/" + searchterm.replace(" ","_") + "?redirect=true"
r = task.executor(requests.get, url)
if r.status_code == requests.codes.ok:
wiki_data = r.json()
title = wiki_data['title']
thumbnail = wiki_data['thumbnail']['source']
extract= wiki_data['extract']
response_variable = {"title": title,"thumbnail": thumbnail, "extract": extract}
return response_variable
else:
response_variable = {"error": r.status_code, "data": r.json()}
return response_variable
This is the push that I needed! Managed to figure it out once I could ‘see’ what was going on. Again, thank you @Slalamander ! Hopefully this is all that I’ll need to really hit the ground running.
I use log.info('Here is some string to help with debugging')
in the .py
file and then use the Log Viewer add-on to view them. It’s instant and works well for me.
Out of curiosity, where do you see the print
statements in HA?
P.S. PyScript has been a game changer for me. I struggle coding in YAML. So THANK YOU to the developers and contributes to this project!
I run Home Assistant container, so I can’t use any add-ons.
The print statements show up in the full logs, which you can access via your-url/config/logs
and then scroll down to where it says “load full logs”. Since it’s all the logs it’s not as clear though as the base log view.
Also it requires reloading every time you print something new, so I think the add-on is a much better option for anyone that has it available
Ah that makes sense. I’m currently using HA in a VM on an Unraid server.
You’re absolutely right. I was reading the PyScript documentation and I learned that print(str)
is the same as log.debug(str)
.
@Slalamander and everyone. I am wanting to use pyscript to create temporary timers instead of creating a bunch of helpers. I am writing to see if this is even a good idea.
Here’s what I would like to do:
That would give basic functionality. Extended functionality would include these:
Of these, the most important would be time remaining. I understand that these extended functionality could be difficult to accomplish. Basic functionality would at least get things going though.
Any thoughts?