Cloud Functions run in a fully-managed, serverless environment where Google handles infrastructure, operating systems, and runtime environments completely on your behalf. Each Cloud Function runs in its own isolated secure execution context, scales automatically, and has a lifecycle independent from other functions.
There are two types of Cloud Functions,
Functions can be triggered by HTTP, publishing a message to Pub/Sub topic, based on events in Firebase/Firestore, Cloud Scheduler, Cloud Storage. Functions can be categorized as two types, i) HTTP triggers and ii) Background functions.
Functions must be stateless -- one function invocation should not rely on in-memory state set by a previous invocation.
Each instance of a function handles only one concurrent request at a time.
If there is extremely rapid increase in inbound traffic can intermittently cause some requests to fail with an HTTP code of 500. This is because the requests are timing out in the pending queue while waiting for new instances to be created.
Cold starts may create latency problems this happens during new deployment and creating new instances during scaling.
Global Scope in a function is executed only once, when its first invoked. In a situation where the same funtion is reused for another execution, the variables you had set in global scope would not be set/triggered. So, write programs in such a way, it should not depend on global variables (or) reinitialize the variable at the end of execution.
At certain times, functions might be triggered more than once, your application should be able to handle that. You functions should be idempotent, an identical request can be made once or several times in a row with the same effect while leaving the server in the same state.
By default, a function times out after 1 minute, but you can extend this period up to 9 minutes(Add flag --timeout=540 during gcloud functions deploy
)
/tmp/
path can be used for local storage during execution but be aware it takes up the space which you had allocated in memory default is 256MB. When CF completes its execution the files in /tmp/ are not deleted automatically. So, in a scenario where, if the same instance of the CF is used by the next request, memory can be used up by the files you had created in the previous innovation. Have a practice of cleaning up /tmp/ before program completion. To increase memory add flag --memory=512MB
during gcloud functions deploy.
To share data between functions, use Firebase, Cloud Storage and Datastore do not use /tmp/.
When there is a version migration of any languages, you will be notified about it in a mail and there will be a mention of whether its going to be minor or major and a deadline will be given past that deadline, your function may be redeployed. If alls well, it will work, otherwise it will fail. So recommendation is, when you get a mail prepare for it. See the features of newer versions and google with keywords like "Migrating Cloud Functions Runtimes" and know the differences as some things might not be backward compatible.
Timestamps and Timezone used in Cloud Function is UTC. So if your program uses datetime, you need to make your program timezone aware.
Python and Go Admin SDKs, all write methods are blocking. That is, the write methods do not return until the writes are committed to the database.
This reduces a lot of my code. Seriously, i was doing a lot to make things synchronous in my NodeJS Firebase CF's earlier.
Below is the recommended folder path structure to create cloud function
1fn-function_name2├── main.py <-- Your cloud function's entrypoint should be defined in this file3├── requirements.txt <-- Specifies all the program dependencies4└── localPkg/5 ├── __init__.py <-- adding this file turns localPkg folder into a module6 └── myFunctions.py <-- Contains your helper functions which is called from main.py in root directory.
1# Environment Setup2python3 -m venv env3
4# Run everyday5.\env\Scripts\activate6set GOOGLE_APPLICATION_CREDENTIALS=<<path of the service account json file>>7
8# Generate requirement.txt using below command9pip3 freeze --all > requirements.txt
1# Imports from localPkg2from localPkg.myFunctions import *3
4import os5import json6import csv7import sys, traceback8from datetime import datetime9from flask import escape10
11# Imports Google Cloud Libraries12# ....13
14# [START hello-cf]15def hello-cf(request):16 """17 This is a template for python cloud function with logging and global variables18 Args:19 request (flask.Request): The request object.20 Returns:21 The response text22 """23
24 # ---- Initializations ----25 global global_arr26
27 try:28 # Extract value from queryString if any is passed29 request_json = request.get_json(silent=True)30 request_args = request.args31
32 #date is queryString in http request33 if request_json and 'date' in request_json:34 paramDate = request_json['date']35 elif request_args and 'date' in request_args:36 paramDate = request_args['date']37 else:38 #Logic if no queryString is provided39 # ....40 print("")41 42 except ValueError as ve: 43 # Structured Log Entry using print44 entry = dict(45 severity="INFO",46 message='All done',47 # Log viewer accesses 'component' as jsonPayload.component'48 log_entries={ index : item for index,item in enumerate(global_arr) },49 )50 print(json.dumps(entry))51 global_arr = []52
53 return str(ve)54
55 except Exception as e:56 print("Unexpected ERROR: ", sys.exc_info()) 57 er_str = ''58 for el in traceback.format_exc().splitlines():59 print(el)60 er_str=er_str+'::'+el61 global_arr.append(er_str)62 global_arr.append("return=no data")63
64 # Structured Log Entry using print65 entry = dict(severity="ERROR", message=er_str 66 , log_entries={ index : item for index,item in enumerate(global_arr) }, )67 print(json.dumps(entry))68 69 global_arr = []70 return 'no data'71 else:72 # Structured Log Entry using print73 entry = dict( severity="INFO", message='All done'74 , log_entries={ index : item for index,item in enumerate(global_arr) }, )75 print(json.dumps(entry))76 global_arr = []77
78 return 'success'79# [END hello-cf]
Below is the code in sub-folder localPkg
1from .myFunctions import *
This is the myFunctions module
1# Imports2#...3
4# Initialize Global Variables5global_arr = []6
7#...
This cloud function does the following,
Folder structure
1fn-function_name2├── main.py3├── requirements.txt4└── localPkg/5 ├── __init__.py6 └── myFunctions.py
1# Imports from localPkg2from localPkg.myFunctions import *3
4import os5import json6import csv7import sys, traceback8from datetime import datetime9from flask import escape10
11# [START download_bhavcopy]12# def download_bhavcopy():13def download_bhavcopy(request):14 """15 This is a template for python cloud function with logging and global variables16 Args:17 request (flask.Request): The request object.18 Returns:19 The response text20 """21
22 # ---- Initializations ----23 global global_arr24 25 try:26 # ---- Getting Dates from QueryString ----27 request_json = request.get_json(silent=True)28 request_args = request.args29
30 if request_json and 'date' in request_json:31 fromDate = request_json['date']32 elif request_args and 'date' in request_args:33 fromDate = request_args['date']34 else:35 # If there is no queryString. Current date will be considered36 fromDate = datetime.date(datetime.now(timezone('UTC')).astimezone(timezone('Asia/Calcutta'))).strftime("%Y%m%d")37
38 global_arr.append('fromDate='+fromDate)39
40 # fromDate = '20210901'41 bucket = '<<bucket-name>>'42 csvPath = 'bse/bhavcopy_csv/'43 44 # ---- Cloud Functions temporary folder ---- 45 temp_folder = '/tmp/'46
47 bDate = convert_date(fromDate, '%d%m%y')48 url_bhavcopy = 'https://www.bseindia.com/download/BhavCopy/Equity/EQ_ISINCODE_<<date>>.ZIP'.replace('<<date>>',bDate)49 csvFile = 'EQ_ISINCODE_<<date>>.CSV'.replace('<<date>>',bDate)50
51 # ---- Download the bhavcopy ----52 bhav_file = download_to_tmp(temp_folder, url_bhavcopy)53 if(bhav_file == ''):54 raise ValueError(bhav_file)55
56 # ---- Unzipping file in Cloud Functions /tmp/ folder ----57 bhav_file = temp_folder+bhav_file58 bhav_file = temp_folder+unzip_file(temp_folder, bhav_file, csvFile)59
60 # ---- PreProcess CSV save as new file ----61 ppCSV = temp_folder+'pp-'+csvFile62 with open(bhav_file, 'r') as inf, open(ppCSV, 'w', newline='') as of:63 r = csv.reader(inf, delimiter=',')64 w = csv.writer(of, delimiter=',')65 total_rows = 066 for line in r:67 total_rows+=168 trim = (field.strip() for field in line)69 w.writerow(trim) 70
71 # ---- Upload to GCS CSV & Preprocessed file ----72 temp = csvPath+bhav_file.split('/')[-1]73 upload_blob(bucket, bhav_file, temp)74
75 temp = csvPath+ppCSV.split('/')[-1]76 upload_blob(bucket, ppCSV, temp)77
78 except Exception as e:79 print("Unexpected ERROR: ", sys.exc_info()) 80 er_str = ''81 for el in traceback.format_exc().splitlines():82 print(el)83 er_str=er_str+'::'+el84 global_arr.append(er_str)85 global_arr.append("return=no data")86
87 # Structured Log Entry using print88 entry = dict(severity="ERROR", message=er_str89 , log_entries={ index : item for index,item in enumerate(global_arr) }, )90 print(json.dumps(entry))91 92 global_arr = []93 return 'no data'94
95 else:96
97 # Structured Log Entry using print98 entry = dict( severity="INFO", message='All done'99 , log_entries={ index : item for index,item in enumerate(global_arr) }, )100 print(json.dumps(entry))101 global_arr = []102 return 'success'103# [END download_bhavcopy]104
105
106# if __name__ == "__main__":107# download_bhavcopy()
1# Added . as python not able to find the myFunctions file2from .myFunctions import *
1import os2import sys3import urllib.request 4import socket5import zipfile6import time7from datetime import datetime8from pytz import timezone9
10# Importing Google libraries11from google.cloud import storage 12
13# Global Variables14global_arr = []15
16### Functions17# Download the file in the URL to the specified folder path 18def download_to_tmp(temp_folder, url):19 global global_arr20 # print('Entry', global_arr)21
22 try: 23 socket.setdefaulttimeout(20)24 opener = urllib.request.build_opener()25 opener.addheaders = [('User-agent', 'Mozilla/5.0')]26 urllib.request.install_opener(opener)27
28 os.makedirs(os.path.dirname(temp_folder), exist_ok=True)29 osfp_folder = os.path.join(temp_folder)30 temp = 'Downloading '+ url + ' --> ' + osfp_folder+url.split('/')[-1]31 global_arr.append(temp)32 print(temp)33
34 filename = url.split('/')[-1]35 print(osfp_folder + filename)36 resultFilePath, responseHeaders = urllib.request.urlretrieve(url, osfp_folder + filename)37
38 except Exception as e:39 filename = ''40
41 return filename42
43# Unzips the file44def unzip_file(temp_folder, fp, fn):45 with zipfile.ZipFile(fp) as z:46 with open(temp_folder + fn, 'wb') as f:47 f.write(z.read(fn))48 return fn49
50# Standard function to upload file to GCS51def upload_blob(bucket_name, source_file_name, destination_blob_name): 52 global global_arr53
54 # If you don't specify credentials when constructing the client, the55 # client library will look for credentials in the environment.56 storage_client = storage.Client() 57 bucket = storage_client.bucket(bucket_name)58 blob = bucket.blob(destination_blob_name)59 60 blob.upload_from_filename(source_file_name)61 62 temp = "Uploaded {} to {} successfully.".format(source_file_name, destination_blob_name)63 # print(temp)64 global_arr.append(temp)65 return None66
67# Converts date to the format specified in the parameter68def convert_date(parmDate, dtFormat):69 return datetime.strptime(parmDate, '%Y%m%d').strftime(dtFormat)
Deploying the Cloud Function
1gcloud config set project <<project-name>>2
3# Using ^ as i am submitting from Windows4gcloud functions deploy download_bhavcopy ^5 --runtime python39 ^6 --trigger-http ^7 --allow-unauthenticated ^8 --timeout=540s9
10# Following is the output11Deploying function (may take a while - up to 2 minutes).../12For Cloud Build Logs, visit: https://console.cloud.google.com/cloud-build/builds;region=us-central1/9cd4f07f-3b73-4f2e-9c2e?project=25491243513Deploying function (may take a while - up to 2 minutes)...done.14availableMemoryMb: 25615buildId: 9cd4f07f-3b73-4f2e-9c2e16buildName: projects/254912435/locations/us-central1/builds/<<some-hash>>17entryPoint: download_bhavcopy18httpsTrigger:19 securityLevel: SECURE_OPTIONAL20 url: https://us-central1-<<project-name>>.cloudfunctions.net/download_bhavcopy21ingressSettings: ALLOW_ALL22labels:23 deployment-tool: cli-gcloud24name: projects/<<project-name>>/locations/us-central1/functions/download_bhavcopy25runtime: python3926serviceAccountEmail: <<project-name>>@appspot.gserviceaccount.com27sourceUploadUrl: https://storage.googleapis.com/gcf-upload-us-central1-<<some-hash>>/57467b94-d75a-4f74-bb26-b22be872ae72.zip28status: ACTIVE29timeout: 540s30updateTime: '2021-10-12T15:06:55.539Z'31versionId: '1'
Above CF can be trigged like from the browser
1https://us-central1-<<project-name>>.cloudfunctions.net/download_bhavcopy?date=202109022(or3https://us-central1-<<project-name>>.cloudfunctions.net/download_bhavcopy