Making asynchronous requests to a Vertex AI endpoint (Google cloud platform) - python

I deployed a model to the model registry on Vertex AI. I added an endpoint too, and I am able to make inferences. Below is the code that I wrote (using Python 3.9.12):
from google.cloud import aiplatform
from google.oauth2 import service_account
# settings is a Pydantic BaseSettings subclass object
credentials_json = json.loads(settings.GCP_VERTEX_SERVICE_ACC)
credentials = service_account.Credentials.from_service_account_info(
info=credentials_json
)
aiplatform.init(project=settings.GCLOUD_PROJECT_NUMBER,
location=settings.GCLOUD_LOCATION,
credentials=credentials)
endpoint = aiplatform.Endpoint(settings.GCLOUD_SBERT_ENDPOINT_ID)
...
async def do_inference(list_strs: List[str]):
result = endpoint.predict(instances=list_strs)
return result.predictions
Right now I'm not able to make asynchronous requests. Is there a way around this? For instance, would using the aiplatform_v1beta1.PredictionServiceAsyncClient library be a solution? Thanks in advance!
---- EDIT -----
Below is the piece of code that did it for me in case someone else is struggling with the same thing.
import asyncio
from google.cloud import aiplatform_v1beta1
from google.oauth2 import service_account
from google.protobuf import json_format
from google.protobuf.struct_pb2 import Value
# settings is a Pydantic BaseSettings subclass object
credentials_json = json.loads(settings.GCP_VERTEX_SERVICE_ACC)
credentials = service_account.Credentials.from_service_account_info(
info=credentials_json
)
client_options = {"api_endpoint": f"{settings.GCLOUD_LOCATION}-aiplatform.googleapis.com"}
client = aiplatform_v1beta1.PredictionServiceAsyncClient(credentials=credentials, client_options=client_options)
...
async def do_inference(list_strs: List[str]):
request = aiplatform_v1beta1.PredictRequest(endpoint=endpoint)
request.instances.extend(list_strs)
response = await client.predict(request)
predictions = response.predictions
return predictions
asyncio.get_event_loop().run_until_complete(do_inference())
This code owes a lot to #milad_raesi's answer!

Related

Locust IO failures 'BadStatusCode' meaning

I'm using locust to load test APIs, but every time I'm testing API which need parameter other than authorization to be inputted like this:
It always fail at 100% which says 'BadStatusCode('endpoint',)', already google what does it mean and search locust error documentation but I still haven't found any clue. Every other API (mainly API with method other than get) I test with locust which only need parameter authorization like this:
run perfectly fine. I already google various locust code on how to input parameters to endpoint and I think my code already correct.
Here's my code (with 100% failures):
import os
from dotenv import load_dotenv
from locust import TaskSet, task, between, HttpLocust
from locust.contrib.fasthttp import FastHttpLocust
import resource
from dotenv import dotenv_values
load_dotenv()
resource.setrlimit(resource.RLIMIT_NOFILE, (65536, 999999))
host_p = os.getenv("HOST_P")
header = {
'authorization': 'Bearer ' + os.getenv('TOKEN')
}
values = {
"amount": 100
}
def payment(self):
self.client.post("/pay", headers=header, json=values)
class ProcessPost(TaskSet):
tasks={payment:2}
class ApiPost(FastHttpLocust):
host = host_payment
task_set = ProcessPost
wait_time = between(5.0, 9.0)
and here's my other code (run perfectly fine):
import os
from dotenv import load_dotenv
from locust import TaskSet, task, between, HttpLocust
from locust.contrib.fasthttp import FastHttpLocust
import resource
import datetime as d
from dotenv import dotenv_values
import json
load_dotenv()
resource.setrlimit(resource.RLIMIT_NOFILE, (65536, 999999))
host_p = os.getenv("HOST_P")
header = {
'authorization': 'Bearer ' + os.getenv('TOKEN')
}
def payment(self):
self.client.get("/pay", headers=header)
class ProcessPost(TaskSet):
tasks={payment:2}
class ApiPost(FastHttpLocust):
host = host_payment
task_set = ProcessPost
wait_time = between(5.0, 9.0)
I guess it is probably you are sending data as body and not adding content type header , if you use json instead it adds content-type header itself, but you need to add it yourself if you use data to pass values.
headers['Content-Type'] = "application/json"

How to patch an existing application using Python Azure SDK and Graph?

I am trying to add a reply_url programmatically to an Azure app registration, but I receive an azure.graphrbac.models.graph_error_py3.GraphErrorException: Specified HTTP method is not allowed for the request target.
It fails when I try to update an existing application with new reply_urls.
SDK I am using is: azure-graphrbac==0.61.1
My code:
from azure.common.credentials import ServicePrincipalCredentials
from azure.graphrbac import GraphRbacManagementClient
from azure.graphrbac.models import ApplicationUpdateParameters
class GraphClient:
def __init__(self, client_id, client_secret, tenant_id, object_id):
self._credentials = ServicePrincipalCredentials(
client_id=client_id,
secret=client_secret,
tenant=tenant_id,
resource="https://graph.windows.net"
)
self._graph_client = GraphRbacManagementClient(
credentials=self._credentials,
tenant_id=tenant_id
)
self._object_id = object_id
self._application = self._graph_client.applications.get(self._object_id)
def get_reply_urls(self) -> List[str]:
return self._application.reply_urls
def add_reply_url(self, reply_url) -> None:
reply_urls: list = self.get_reply_urls()
self._graph_client.applications.patch(
self._object_id,
ApplicationUpdateParameters(
reply_urls=[
*reply_urls,
reply_url]
)
)
Could not reproduce your issue, use the same version of azure-graphrbac, I test your code on my side, it works fine.
testclient = GraphClient(client_id = "xxxxx",client_secret = "xxxxx", tenant_id = "xxxxx", object_id = "xxxxx")
testclient.add_reply_url(reply_url = "http://localhost:8085")
Check in the portal:
Also, I test the sdk directly, both work.
from azure.common.credentials import ServicePrincipalCredentials
from azure.graphrbac import GraphRbacManagementClient
from azure.graphrbac.models import ApplicationUpdateParameters
_credentials = ServicePrincipalCredentials(
client_id="xxxxx",
secret="xxxxx",
tenant="xxxxx",
resource="https://graph.windows.net"
)
_graph_client = GraphRbacManagementClient(
credentials=_credentials,
tenant_id="xxxxx"
)
app = _graph_client.applications.patch(
application_object_id = "xxxxx",
parameters = ApplicationUpdateParameters(reply_urls = ["http://localhost:8080","http://localhost:8081"])
)
Update call looks good however it depends on a legacy API (AAD Graph) and tools. It's strongly recommended to move to MS Graph which supports almost all Azure AD Graph operations and will fully support them all in a future. Applications being one of them.
You can use Requests-OAuthlib or Microsoft Graph Python Client Library for this.

can't fix error: Object of type ListVector is not JSON serializable on flask?

I am trying to make a request for session info, and I could print out session info on python console, but can't return session info on server endpoint. The error said the object is not JSON serializable. Here is the error message:
TypeError: Object of type ListVector is not JSON serializable
I found this post relevant to my post, I tried its solution, but it didn't work for me. how can I convert rpy2.ListVector to the dictionary that can be JSON serializable? any solution?
my attempt:
from flask import Flask, jsonify, request
from flask_restplus import Api, Resource
import json
##
import os
os.environ['PYTHONHOME'] = r"C:\Users\me\AppData\Local\Programs\Python\Python37"
os.environ['PYTHONPATH'] = r"C:\Users\me\AppData\Local\Programs\Python\Python37\Lib\site-packages"
os.environ['R_HOME'] = r"C:\Program Files\R\R-3.3.2"
os.environ['R_USER'] = r"C:\Users\me\AppData\Local\Programs\Python\Python37\Lib\site-packages\rpy2"
##
app = Flask(__name__)
api = Api(app)
ns = api.namespace('ns')
## load R function
import rpy2
import rpy2.robjects as robjects
#api.route('/')
class AResource(Resource):
def post(self):
sess = robjects.r("sessionInfo()")
return jsonify({'sessioninfo': sess})
if __name__ == '__main__':
api.add_namespace(ns)
app.run(debug=True)
here I just simply make a request to the server endpoint, I can print out session info on console but not able to return it on server endpoint. how can I make this work? any quick thought? thanks
Is there any possible solution to fix this error? any idea?
follow up your above attempt, I fixed your problem as follow:
class AResource(Resource):
def post(self):
sess = robjects.r("sessionInfo()")
new_sess= { key : sess.rx2(key)[0] for key in sess.names }
return jsonify({'sessioninfo': str(new_sess)})
now you can see json output on your server endpoint.

Python ml engine predict: How can I make a googleapiclient.discovery.build persistent?

I need to make online predictions from a model that is deployed in cloud ml engine. My code in python is similar to the one found in the docs (https://cloud.google.com/ml-engine/docs/tensorflow/online-predict):
service = googleapiclient.discovery.build('ml', 'v1')
name = 'projects/{}/models/{}'.format(project, model)
if version is not None:
name += '/versions/{}'.format(version)
response = service.projects().predict(
name=name,
body={'instances': instances}
).execute()
However, I receive the "instances" data from outside the script, I wonder if there is a way I could run this script without making the "service = googleapiclient.discovery.build('ml', 'v1')" each time before a request, since it takes time.
pd: this is my very first project on gcp. Thank you.
Something like this will work. You'll want to initialize your service globally then use that service instance to make your call.
import googleapiclient.discovery
AI_SERVICE = None
def ai_platform_init():
global AI_SERVICE
# Set GCP Authentication
credentials = os.environ.get('GOOGLE_APPLICATION_CREDENTIALS')
# Path to your credentials
credentials_path = os.path.join(os.path.dirname(__file__), 'ai-platform-credentials.json')
if credentials is None and os.path.exists(credentials_path):
os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = credentials_path
# Create AI Platform Service
if os.path.exists(credentials_path):
AI_SERVICE = googleapiclient.discovery.build('ml', 'v1', cache=MemoryCache())
# Initialize AI Platform on load.
ai_platform_init()
then later on, you can do something like this:
def call_ai_platform():
response = AI_SERVICE.projects().predict(name=name,
body={'instances': instances}).execute()
Bonus! in case you were curious about the MemoryCache class in the googleapiclient.discovery call, that was borrowed from another SO:
class MemoryCache():
"""A workaround for cache warnings from Google.
Check out: https://github.com/googleapis/google-api-python-client/issues/325#issuecomment-274349841
"""
_CACHE = {}
def get(self, url):
return MemoryCache._CACHE.get(url)
def set(self, url, content):
MemoryCache._CACHE[url] = content

Creating a reusable datastore client for large flask application

I want to create a database connection from my python flask application to my datastore instance on GCP. I have a file services/db.py:
from google.cloud import datastore
from google.auth import compute_engine
from config import env
import os
import logging
import warnings
namespace = env.str('ENV')
class Datastore_Client():
def context_local(self):
datastore_client = datastore.Client(namespace=namespace)
return datastore_client
def context_test(self):
project = env.str("GOOGLE_CLOUD_PROJECT")
credentials = compute_engine.Credentials()
datastore_client = datastore.Client(credentials=credentials, project=project, namespace='test')
return datastore_client
def context_live(self):
datastore_client = datastore.Client(namespace=namespace)
return datastore_client
def get_datastore_client(self):
contexts = {
'local': self.context_local,
'test': self.context_test,
'appengine': self.context_live
}
context_func = contexts.get(env.str("CONTEXT"), self.context_local)
return context_func()
builtin_list = list
def from_datastore(self, entity):
if not entity:
return None
if isinstance(entity, list):
entity = entity.pop()
return entity
In my model files I would reference the datastore client like so:
from ..services.db import Datastore_Client
client = Datastore_Client().get_datastore_client()
but in providing this reference in all files which need it when running my application it seems to spin up a database connection for each instance whereas I would want an application wide connection.
I have looked at application context but with example using sqlite and talking about tearing down the connection after I am not sure if the same approach can be used for datastore.
You are creating an object everytime you call Datastore_Client(). It sounds like you want to create a Singleton Datastore_Client and use that in your model files.

Categories

Resources