How to deploy basic python functions (with packages) on Google Cloud Functions - python-3.x

I am attempting to deploy a basic python function which calls an api using an HTTP trigger through Google Functions (browser-editor).
Here's the function I'm trying to deploy:
import requests
import json
def call_api():
API_URL = 'https://some-api.com/v1'
API_TOKEN = 'some-api-token'
result = requests.get(API_URL+"/contacts?access_token="+API_TOKEN).json()
print(result)
call_api()
My requirements.txt contains:
requests==2.21.0
However, every time I try to deploy the function, the following error occurs:
Unknown resource type
What am I doing wrong? The function works just fine on my local machine.

Please refer to Writing HTTP Functions for more information. This is what comes to my mind when looking at your code:
Missing the request parameter (def call_api(request):)
Missing return at the end (you don't need to print it, just return it to the caller)
Calling call_api() at the end of the file will call the function only locally, CFs don't need this
Make sure you are deploying using gcloud functions deploy call_api --runtime python37 --trigger-http

Related

Issue calling Cloud Function (Python 3.7) runtime from Apigee

I have created a very simple hello-world cloud function in Python 3.7. This function works fine when I call it directly from browser, or in testing mode, or via any other client.
Invoking same function using Google Apigee cloud function extension returns an error 400 Bad Request, The browser (or proxy) sent a request that this server could not understand. This error is getting generated on request.get_json line within cloud function.
What is different between invoking Cloud function via Apigee and how to resolve this error?
(Google cloud function is default hello-world python 3.7 sample)

Google Cloud Functions Python3: Wrapping HTTP trigger functions with endpoints

I am exploring Google Cloud Functions in Python to write HTTP triggered functions. I have a main.py with all my triggered functions structured like in this post, but would like to be able to wrap in some endpoints. On nodejs, one could do so like in this post using Express, and on Python, very similarly using Flask.
I have attempted to dabble by wrapping my Cloud Functions using Flask, but Google will bring me to Google's authentication page. My code as follows:
from flask import Flask, jsonify, request
# Initialize Flask application
application = Flask(__name__)
#application.route('/some/endpoint/path', methods=['GET'])
def predict():
inputs = request.args.get('inputs')
//Some logic...
response_object = {}
response_object['statusCode'] = 200
response_object['results'] = results
return jsonify(response_object)
Is there a way to wrap the python cloud functions in such a way to acheive something like this?
https://us-central1-my-project.cloudfunctions.net/some
https://us-central1-my-project.cloudfunctions.net/some/endpoint
https://us-central1-my-project.cloudfunctions.net/some/endpoint/path
I believe you are getting the authentication Google screen because you are trying to access the base url for Cloud Functions on your project.
With HTTP Cloud Functions the trigger url is usually https://[REGION]-[PROJECT_ID].cloudfunctions.net/[FUNCTION_NAME], so any routes would need to follow another slash after the function name.
That being said, I found this post where the solution provided manage to set routes within the same main.py file to access the endpoints from a single Cloud Function. I had to adapt some things, but in the end it worked for me.
The following is the source code I tested at my end:
import flask
import werkzeug.datastructures
app = flask.Flask(__name__)
#app.route('/')
def root():
return 'Hello World!'
#app.route('/hi')
def hi():
return 'Hi there'
#app.route('/hi/<username>')
def hi_user(username):
return 'Hi there, {}'.format(username)
#app.route('/hi/<username>/congrats', methods=['POST'])
def hi_user_congrat(username):
achievement = flask.request.form['achievement']
return 'Hi there {}, congrats on {}!'.format(username, achievement)
def main(request):
with app.app_context():
headers = werkzeug.datastructures.Headers()
for key, value in request.headers.items():
headers.add(key, value)
with app.test_request_context(method=request.method, base_url=request.base_url, path=request.path, query_string=request.query_string, headers=headers, data=request.form):
try:
rv = app.preprocess_request()
if rv is None:
rv = app.dispatch_request()
except Exception as e:
rv = app.handle_user_exception(e)
response = app.make_response(rv)
return app.process_response(response)
This defined the following routes within a single Cloud Function:
https://[REGION]-[PROJECT_ID].cloudfunctions.net/[FUNCTION_NAME]
https://[REGION]-[PROJECT_ID].cloudfunctions.net/[FUNCTION_NAME]/hi
https://[REGION]-[PROJECT_ID].cloudfunctions.net/[FUNCTION_NAME]/hi/<username>
https://[REGION]-[PROJECT_ID].cloudfunctions.net/[FUNCTION_NAME]/hi/<username>/congrats
And the following was the command used to deploy this function:
gcloud functions deploy flask_function --entry-point main --runtime python37 --trigger-http --allow-unauthenticated
Cloud Functions is designed to be used as a single endpoint. You might consider using Cloud Run instead, as it's more suited towards applications with multiple routes, and has many of the same benefits as Cloud Functions.
If you're dead set on using Cloud Functions, something like the answer at Injecting a Flask Request into another Flask App should work, but it's not ideal.

Can zappa be used to run functions directly (non wsgi apps)

zappa can easily be used to run flask apps. But it creates just one lambda function per app. Can I have a separate lambda function for each python function I declare?
Since this is the first SO result that you get when searching for zappa for non-wsgi I'll share my 2 cents.
If you just want use Zappa to deploy to AWS Lambda and be able to invoke your function without actually using WSGI you can do something like this:
myapp.py
def foo(event, context):
print('foo bar')
return 'lambda triggered!'
zappa_settings.json
{
"dev": {
"lambda_handler": "myapp.foo",
...
}
}
Now go to your AWS Lambda console in browser and click Test and see the function being triggered.
You can create 'command' trigger events like below and zappa will invoke your python function:
{
"command": "mymodule.myfunction"
}
Your app doesn't have to be a wsgi app. You can create each lambda function individually & upload the same zappa package as zip on each of them.

How can I split Cloud Functions for Firebase into many files? [duplicate]

Today Firebase released its brand new product Cloud Functions for Firebase and I just created a hello world function and deploy it on my existing firebase project.
It looks like it bundles all dependencies and upload it to firebase just like aws lambda function does. But it takes too much time to be done even on minor changes in code and also need a good connectivity of internet . If you are offline for some reason, you are just in dark what code you are writing until you have a way to execute and test that functions offline on your local machine.
Is there any way to test Cloud Functions for Firebase locally?
firebaser here
Deployment of your Functions indeed takes more time than what I'm normally willing to wait for. We're working hard to improve that and (as Brendan said) are working on a local emulator.
But for the moment, I mostly write my actual business logic into a separate Node script first. That way I can test it from a local command prompt with node speech.js. Once I'm satisfied that the function works, I either copy/paste it into my actual Functions file or (better) import the speech module into my functions file and invoke it from there.
One abbreviated example that I quickly dug up is when I was wiring up text extraction using the Cloud Vision API. I have a file called ocr.js that contains:
var fetch = require('node-fetch');
function extract_text(url, gcloud_authorization) {
console.log('extract_text from image '+url+' with authorization '+gcloud_authorization);
return fetch(url).then(function(res) {
return res.buffer();
}).then(function(buffer) {
return fetch('https://vision.googleapis.com/v1/images:annotate?key='+gcloud_authorization, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
"requests":[
{
"image":{
"content": buffer.toString('base64')
},
"features":[
{
"type":"TEXT_DETECTION",
"maxResults":1
}
]
}
]
})
});
}).then(function(res) {
var json = res.json();
if (res.status >= 200 && res.status < 300) {
return json;
} else {
return json.then(Promise.reject.bind(Promise));
}
}).then(function(json) {
if (json.responses && json.responses.length && json.responses[0].error) {
return Promise.reject(json.responses[0].error);
}
return json.responses[0].textAnnotations[0].description;
});
}
if (process.argv.length > 2) {
// by passing the image URL and gcloud access token, you can test this module
process.argv.forEach(a => console.log(a));
extract_text(
process.argv[2], // image URL
process.argv[3] // gcloud access token or API key
).then(function(description) {
console.log(description);
}).catch(function(error) {
console.error(error);
});
}
exports.extract_text = extract_text;
And then in my Functions index.js, I have:
var functions = require('firebase-functions');
var fetch = require('node-fetch');
var ocr = require('./ocr.js');
exports.ocr = functions.database().path('/messages/{room}/{id}').onWrite(function(event) {
console.log('OCR triggered for /messages/'+event.params.room+'/'+event.params.id);
if (!event.data || !event.data.exists()) return;
if (event.data.ocr) return;
if (event.data.val().text.indexOf("https://firebasestorage.googleapis.com/") !== 0) return; // only OCR images
console.log(JSON.stringify(functions.env));
return ocr.extract_text(event.data.val().text, functions.env.googlecloud.apikey).then(function(text) {
return event.data.adminRef.update({ ocr: text });
});
});
So as you can see this last file is really just about wiring up the "worker method" ocr.extract_text to the database location.
Note this is a project from a while ago, so some of the syntax (mostly the functions.env part) might have changed a bit.
firebaser here
To debug your Cloud Functions for Firebase locally, there is an emulator. See the documentation for more info.
run and debug/inspect functions locally
prerequisites (google-cloud functions and firebase-specific):
npm install -g #google-cloud/functions-emulator
npm install --save firebase-functions
npm install -g firebase-tools
To run and inspect/debug: first run functions locally, then inspect each function, and finally run each specific function to debug+inspect it. Use functions start as an alternative to firebase serve and note the documentation for each tool is available (and useful).
To run and debug the specific function myFn as-expected (eg in Nodejs via chrome://inspect and note this works using Nodejs v10 though not officially supported):
firebase serve --only functions
functions inspect myFn
functions call myFn # or call from browser
additional documentation:
https://firebase.google.com/docs/functions/local-emulator
https://cloud.google.com/functions/docs/emulator#debug-emulator
https://github.com/GoogleCloudPlatform/cloud-functions-emulator/wiki
>> Is there any way to test Cloud Functions for Firebase locally?
You can use the following command to start a firebase shell (execute in your functions directory):
npm run build && firebase functions:shell
You can invoke your functions in the shell like so:
helloWorld()
Refer this link for more information.
Answered here: https://github.com/firebase/firebase-functions/issues/4#issuecomment-286515989
Google Cloud Functions also open-sourced a local emulator, and we are
working to build a tighter integration with Cloud Functions for
Firebase. In the meanwhile, you can check it at here:
https://github.com/GoogleCloudPlatform/cloud-functions-emulator/
The emulator does allow you to run functions locally. Here's the
documentation that explains how to use it:
https://cloud.google.com/functions/docs/emulator
I couldn't get the single stepping working at first. My process was the same as documented in many answers here.
Also, these pages contain nearly all the documentation I required:
https://firebase.google.com/docs/functions/local-emulator
https://cloud.google.com/functions/docs/emulator#debugging_with_the_emulator
I had got the functions running using firebase serve --only functions, but hadn't got the debugger up and running. Then I came across the other way of directly using the emulator and managed to hit a break point like this:
# start the emulator
functions start
# allow inspection
functions inspect helloWorld
# call the function from the cli
functions call helloWorld
This worked, and I could hit a breakpoint.
However, when hitting the endpoint for the function in postman or the browser, I got no response at all.
The step I was missing was:
# deploy the function to the emulator
functions deploy helloWorld --trigger-http
# you need to toggle inspection after the deploy
functions inspect helloWorld
Now I can hit the endpoint for the function from postman or the browser, and the breakpoint is hit.
I recommend the brilliant NiM chrome extension for debugging and hope this answer helps someone, even if this is an old question.
Firstly, I suggest you to install following dependencies,
npm install --save firebase-functions
npm install -g firebase-tools
If already installed then you can update it to latest one. Generally, functions-emulator comes with above dependency but still I would recommend you to update it,
npm install -g #google-cloud/functions-emulator
Once it has been updated, go to functions folder of you application and run following command,
firebase serve --only functions
I hope it helps!
For vscode users debugging HTTP functions (webhooks, etc)...
The google cloud emulator (firebase serve --only functions) launches a separate process to run your functions. You can attach to this process with vscode, but since the emulator only creates this process after the first function is called, it's not straightforward.
create a dummy HTTP endpoint in your functions which will return the processID:
app.get("/processid", function(request, response) {
response.send(`${process.pid}`);
});
start the emulator with firebase serve --only functions
call the http://<localhost_url>/processid endpoint. This will create the process and return the processID
use vscode to attach to the specified process. You can now set breakpoints, step, etc on any of the other functions (they all use the same process).
There's probably a nicer way to glue all this together.
There is now a cloud functions emulator that lets you call functions locally
Once I have completed my PoC I will update this answer to include code and steps I used.

how to test Cloud Functions for Firebase locally on pc

Today Firebase released its brand new product Cloud Functions for Firebase and I just created a hello world function and deploy it on my existing firebase project.
It looks like it bundles all dependencies and upload it to firebase just like aws lambda function does. But it takes too much time to be done even on minor changes in code and also need a good connectivity of internet . If you are offline for some reason, you are just in dark what code you are writing until you have a way to execute and test that functions offline on your local machine.
Is there any way to test Cloud Functions for Firebase locally?
firebaser here
Deployment of your Functions indeed takes more time than what I'm normally willing to wait for. We're working hard to improve that and (as Brendan said) are working on a local emulator.
But for the moment, I mostly write my actual business logic into a separate Node script first. That way I can test it from a local command prompt with node speech.js. Once I'm satisfied that the function works, I either copy/paste it into my actual Functions file or (better) import the speech module into my functions file and invoke it from there.
One abbreviated example that I quickly dug up is when I was wiring up text extraction using the Cloud Vision API. I have a file called ocr.js that contains:
var fetch = require('node-fetch');
function extract_text(url, gcloud_authorization) {
console.log('extract_text from image '+url+' with authorization '+gcloud_authorization);
return fetch(url).then(function(res) {
return res.buffer();
}).then(function(buffer) {
return fetch('https://vision.googleapis.com/v1/images:annotate?key='+gcloud_authorization, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
"requests":[
{
"image":{
"content": buffer.toString('base64')
},
"features":[
{
"type":"TEXT_DETECTION",
"maxResults":1
}
]
}
]
})
});
}).then(function(res) {
var json = res.json();
if (res.status >= 200 && res.status < 300) {
return json;
} else {
return json.then(Promise.reject.bind(Promise));
}
}).then(function(json) {
if (json.responses && json.responses.length && json.responses[0].error) {
return Promise.reject(json.responses[0].error);
}
return json.responses[0].textAnnotations[0].description;
});
}
if (process.argv.length > 2) {
// by passing the image URL and gcloud access token, you can test this module
process.argv.forEach(a => console.log(a));
extract_text(
process.argv[2], // image URL
process.argv[3] // gcloud access token or API key
).then(function(description) {
console.log(description);
}).catch(function(error) {
console.error(error);
});
}
exports.extract_text = extract_text;
And then in my Functions index.js, I have:
var functions = require('firebase-functions');
var fetch = require('node-fetch');
var ocr = require('./ocr.js');
exports.ocr = functions.database().path('/messages/{room}/{id}').onWrite(function(event) {
console.log('OCR triggered for /messages/'+event.params.room+'/'+event.params.id);
if (!event.data || !event.data.exists()) return;
if (event.data.ocr) return;
if (event.data.val().text.indexOf("https://firebasestorage.googleapis.com/") !== 0) return; // only OCR images
console.log(JSON.stringify(functions.env));
return ocr.extract_text(event.data.val().text, functions.env.googlecloud.apikey).then(function(text) {
return event.data.adminRef.update({ ocr: text });
});
});
So as you can see this last file is really just about wiring up the "worker method" ocr.extract_text to the database location.
Note this is a project from a while ago, so some of the syntax (mostly the functions.env part) might have changed a bit.
firebaser here
To debug your Cloud Functions for Firebase locally, there is an emulator. See the documentation for more info.
run and debug/inspect functions locally
prerequisites (google-cloud functions and firebase-specific):
npm install -g #google-cloud/functions-emulator
npm install --save firebase-functions
npm install -g firebase-tools
To run and inspect/debug: first run functions locally, then inspect each function, and finally run each specific function to debug+inspect it. Use functions start as an alternative to firebase serve and note the documentation for each tool is available (and useful).
To run and debug the specific function myFn as-expected (eg in Nodejs via chrome://inspect and note this works using Nodejs v10 though not officially supported):
firebase serve --only functions
functions inspect myFn
functions call myFn # or call from browser
additional documentation:
https://firebase.google.com/docs/functions/local-emulator
https://cloud.google.com/functions/docs/emulator#debug-emulator
https://github.com/GoogleCloudPlatform/cloud-functions-emulator/wiki
>> Is there any way to test Cloud Functions for Firebase locally?
You can use the following command to start a firebase shell (execute in your functions directory):
npm run build && firebase functions:shell
You can invoke your functions in the shell like so:
helloWorld()
Refer this link for more information.
Answered here: https://github.com/firebase/firebase-functions/issues/4#issuecomment-286515989
Google Cloud Functions also open-sourced a local emulator, and we are
working to build a tighter integration with Cloud Functions for
Firebase. In the meanwhile, you can check it at here:
https://github.com/GoogleCloudPlatform/cloud-functions-emulator/
The emulator does allow you to run functions locally. Here's the
documentation that explains how to use it:
https://cloud.google.com/functions/docs/emulator
I couldn't get the single stepping working at first. My process was the same as documented in many answers here.
Also, these pages contain nearly all the documentation I required:
https://firebase.google.com/docs/functions/local-emulator
https://cloud.google.com/functions/docs/emulator#debugging_with_the_emulator
I had got the functions running using firebase serve --only functions, but hadn't got the debugger up and running. Then I came across the other way of directly using the emulator and managed to hit a break point like this:
# start the emulator
functions start
# allow inspection
functions inspect helloWorld
# call the function from the cli
functions call helloWorld
This worked, and I could hit a breakpoint.
However, when hitting the endpoint for the function in postman or the browser, I got no response at all.
The step I was missing was:
# deploy the function to the emulator
functions deploy helloWorld --trigger-http
# you need to toggle inspection after the deploy
functions inspect helloWorld
Now I can hit the endpoint for the function from postman or the browser, and the breakpoint is hit.
I recommend the brilliant NiM chrome extension for debugging and hope this answer helps someone, even if this is an old question.
Firstly, I suggest you to install following dependencies,
npm install --save firebase-functions
npm install -g firebase-tools
If already installed then you can update it to latest one. Generally, functions-emulator comes with above dependency but still I would recommend you to update it,
npm install -g #google-cloud/functions-emulator
Once it has been updated, go to functions folder of you application and run following command,
firebase serve --only functions
I hope it helps!
For vscode users debugging HTTP functions (webhooks, etc)...
The google cloud emulator (firebase serve --only functions) launches a separate process to run your functions. You can attach to this process with vscode, but since the emulator only creates this process after the first function is called, it's not straightforward.
create a dummy HTTP endpoint in your functions which will return the processID:
app.get("/processid", function(request, response) {
response.send(`${process.pid}`);
});
start the emulator with firebase serve --only functions
call the http://<localhost_url>/processid endpoint. This will create the process and return the processID
use vscode to attach to the specified process. You can now set breakpoints, step, etc on any of the other functions (they all use the same process).
There's probably a nicer way to glue all this together.
There is now a cloud functions emulator that lets you call functions locally
Once I have completed my PoC I will update this answer to include code and steps I used.

Resources