How to seperate async requests from the underlying socket request - python

I'm calling the following JSON-EndPoint in order to retrieve the following value of reference_number.
Using a normal for loop for 3 requests, I've been able to receive a new token per each request as below:
import httpx
for _ in range(3):
r = httpx.get(
'https://zenpay.zenithpayments.com.au/CyberSource/getsignedobject?programId=2&siteId=2')
print(r.json()['signedObject']['reference_number'])
Output:
P2S2M1R20210307004644
P2S2M1R20210307004645
P2S2M1R20210307004646
Now, I'm trying to send an async requests where each request using it's own session but i keep getting the same value for all requests sent in same time.
import trio
import httpx
async def main():
async with trio.open_nursery() as nurse:
async def call():
async with httpx.AsyncClient(timeout=None) as client:
r = await client.get('https://zenpay.zenithpayments.com.au/CyberSource/getsignedobject?programId=2&siteId=2')
print(r.json()['signedObject']['reference_number'])
for _ in range(3):
nurse.start_soon(call)
if __name__ == "__main__":
trio.run(main)
Output:
P2S2M1R20210307004912
P2S2M1R20210307004912
P2S2M1R20210307004912
Note: I know that the value is incremented by 1 on each request but i can't use this solution as this value needs to be generated by the BackEnd API in order to authenticate using it later.
On the other side, i don't think it's related to be controlled according to the origin IP address as the API already generate the token once you hit individually.
I can set a time delay between the requests but that's will slow down my entire operation.
I've verified that each client is represent it's own which means the underlying socket identifier is different on each request.
print(client, r.json()['signedObject']['reference_number'])
Output:
<httpx.AsyncClient object at 0x000001EF7815BF40> P2S2M1R20210307005512
<httpx.AsyncClient object at 0x000001EF78185250> P2S2M1R20210307005512
<httpx.AsyncClient object at 0x000001EF7817C670> P2S2M1R20210307005512
is there a clue here ? as I tried even to play with the HTTP Transport but i couldn't figure out what to do with it!

Related

Why does this fail with "'async for' requires an object with __aiter__ method, got coroutine"

I am attempting to call an external API (one that is provided by Google) using an async client library (the library is also provided by Google).
The async method that I am attempting to call is async list_featurestores(), the documentation for which is at https://googleapis.dev/python/aiplatform/latest/aiplatform_v1/featurestore_service.html (its about a third of the way down that page, there is no deeplink). It provides the following sample code:
from google.cloud import aiplatform_v1
async def sample_list_featurestores():
# Create a client
client = aiplatform_v1.FeaturestoreServiceAsyncClient()
# Initialize request argument(s)
request = aiplatform_v1.ListFeaturestoresRequest(
parent="parent_value",
)
# Make the request
page_result = client.list_featurestores(request=request)
# Handle the response
async for response in page_result:
print(response)
(I've literally copy/pasted that code from the above linked page).
In order to run that code I've slightly adapted it to:
import asyncio
from google.cloud import aiplatform_v1
async def sample_list_featurestores():
client = aiplatform_v1.FeaturestoreServiceAsyncClient()
request = aiplatform_v1.ListFeaturestoresRequest(parent="projects/MY_GCP_PROJECT/locations/europe-west2",)
page_result = client.list_featurestores(request=request)
async for response in page_result:
print(response)
if __name__ == "__main__":
asyncio.run(sample_list_featurestores())
When I run it it fails on this line:
async for response in page_result: with error:
'async for' requires an object with aiter method, got coroutine
This is my first foray into async python development and, given I (think I) have followed the supplied code to the letter I don't know why I'm getting this error.
Am I missing something obvious here? Can someone explain how to get past this error?

Keep Websockets connection open for incoming requests

I have a Flask server that accepts HTTP requests from a client. This HTTP server needs to delegate work to a third-party server using a websocket connection (for performance reasons).
I find it hard to wrap my head around how to create a permanent websocket connection that can stay open for HTTP requests. Sending requests to the websocket server in a run-once script works fine and looks like this:
async def send(websocket, payload):
await websocket.send(json.dumps(payload).encode("utf-8"))
async def recv(websocket):
data = await websocket.recv()
return json.loads(data)
async def main(payload):
uri = f"wss://the-third-party-server.com/xyz"
async with websockets.connect(uri) as websocket:
future = send(websocket, payload)
future_r = recv(websocket)
_, output = await asyncio.gather(future, future_r)
return output
asyncio.get_event_loop().run_until_complete(main({...}))
Here, main() establishes a WSS connection and closes it when done, but how can I keep that connection open for incoming HTTP requests, such that I can call main() for each of those without re-establising the WSS connection?
The main problem there is that when you code a web app responding http(s), your code have a "life cycle" that is very peculiar to that: usually you have a "view" function that will get the request data, perform all actions needed to gather the response data and return it.
This "view" function in most web frameworks has to be independent from the rest of the system - it should be able to perform its duty relying on no other data or objects than what it gets when called - which are the request data, and system configurations - that gives the application server (the framework parts designed to actually connect your program to the internet) can choose a variety of ways to serve your program: they may run your view function in several parallel threads, or in several parallel processes, or even in different processes in various containers or physical servers: you application would not need to care about that.
If you want a resource that is available across calls to your view functions, you need to break out of this paradigm. For example, typically, frameworks will want to create a pool of database connections, so that views on the same process can re-use those connections. These database connections are usually supplied by the framework itself, which implements a mechanism for allowing then to be reused, and be available in a transparent way, as needed. You have to recreate a mechanism of the same sort if you want to keep a websocket connection alive.
In a certain way, you need a Python object that can mediate your websocket data behaving like a "server" for your web view functions.
That is simpler to do than it sounds - a special Python class designed to have a single instance per process, which keeps the connections, and is able to send and receive data received from parallel calls without mangling it is enough. A callable that will ensure this instance exists in the current process is enough to work under any strategy configured to serve your app to the web.
If you are using Flask, which does not use asyncio, you get a further complication - you will loose the async-ability inside your views, they will have to wait for the websocket requisition to be completed - it will then be the job of your application server to have your view in different threads or processes to ensure availability. And, it is your job to have the asyncio loop for your websocket running in a separate thread, so that it can make the requests it needs.
Here is some example code.
Please note that apart from using a single websocket per process,
this has no provisions in case of failure of any kind, but,
most important: it does nothing in parallel: all
pairs of send-recv are blocking, as you give no clue of
a mechanism that would allow one to pair each outgoing message
with its response.
import asyncio
import threading
from queue import Queue
class AWebSocket:
instance = None
def __new__(cls, *args, **kw):
if cls.instance:
return cls.instance
return super().__new__(cls, *args, **kw)
def __init__(self, *args, **kw):
cls = self.__class__
if cls.instance:
# init will be called even if new finds the existing instance,
# so we have to check again
return
self.outgoing = Queue()
self.responses = Queue()
self.socket_thread = threading.Thread(target=self.start_socket)
self.socket_thread.start()
def start_socket():
# starts an async loop in a separate thread, and keep
# the web socket running, in this separate thread
asyncio.get_event_loop().run_until_complete(self.core())
def core(self):
self.socket = websockets.connect(uri)
async def _send(self, websocket, payload):
await websocket.send(json.dumps(payload).encode("utf-8"))
async def _recv(self, websocket):
data = await websocket.recv()
return json.loads(data)
async def core(self):
uri = f"wss://the-third-party-server.com/xyz"
async with websockets.connect(uri) as websocket:
self.websocket = websocket
while True:
# This code is as you wrote it:
# it essentially blocks until a message is sent
# and the answer is received back.
# You have to have a mechanism in your websocket
# messages allowing you to identify the corresponding
# answer to each request. On doing so, this is trivially
# paralellizable simply by calling asyncio.create_task
# instead of awaiting on asyncio.gather
payload = self.outgoing.get()
future = self._send(websocket, payload)
future_r = self._recv(websocket)
_, response = await asyncio.gather(future, future_r)
self.responses.put(response)
def send(self, payload):
# This is the method you call from your views
# simply do:
# `output = AWebSocket().send(payload)`
self.outgoing.put(payload)
return self.responses.get()

Get image full url on Django Channel Response

I have created a socket with Django channels that return the serialized data of Category Object. But in the response, there is no full URL(the IP address is not there). This problem is similar to this question Django serializer Imagefield to get full URL. The difference is that I am calling the Serializer from a Consumer(Django Channels). Whereas in the link, Serializer is called from a View. In a Consumer, there is no request object as mentioned in the solution. The Django Channels says that scope in Consumers is similar to request in Views. So how can I get the full image url in this case?
The Django Channels says that scope in Consumers is similar to request in Views.
Correct; therefore it depends how to setup your events in the AsyncConsumer.
If you could share more about your code or a better explanation with a dummy example.
In general:
Import the serializers in the consumers and then send the same data to the serializers as shown below.
from <app_name>.serializers import <desired_serializer_name>Serializer
from channels.db import database_sync_to_async
#database_sync_to_async
def serializer_checking_saving_data(self, data):
serializer = <desired_serializer_name>Serializer(data=data)
serializer.is_valid(raise_exception=True)
x = serializer.create(serializer.validated_data)#this will create the value in the DB
return <desired_serializer_name>Serializer(x).data
To fetch data from a websocket request:
Setup a receive event (ie channel-layer will receive the data) wherein it would trigger a particular event[for example I will implement to simply display that data]
#write this inside the AsyncWebsocketConsumer
async def receive_json(self, content, **kwargs):
"""[summary]
• All the events received to the server will be evaluated here.
• If websocket has event-type based on these the receive function will execute
the respective function
"""
message_type = content.get('type')
if message_type == 'start.sepsis':
await self.display_the_data(content)
async def display_the_data(self,data)
message = data.get('payload')
print(f"The data sent to the channel/socket is \n {data}")
You can make the websocket request in the following way:-
create a new python file
import json
import websocket
import asyncio
async def making_websocket_request():
ws_pat = websocket.WebSocket()
ws_pat.connect(
'ws://localhost:8000/<ws-router-url>/')
asyncio.sleep(2)#it might take a couple of seconds to connect to the server
ws.send(json.dumps({
'type':'display.the_data'
#the channels will convert "display.the_data" to "display_the_data"
#since "display_the_data" their is an event as defined above it would be called
'payload':{<can-be-any-json-data>}
#this payload will be sent as a parameter when calling the function.
}))

asyncio: multiplexing messages over single websocket connection

I am using Python 3.6, asyncio and the websockets library. I am trying to build a client for a websocket-based service which works as follows:
The client can send JSON requests with a custom id, a method and some params. The service will reply with a JSON payload with the same id echoed, and data as a result of the method call.
I would like to have an abstraction on top of this device that would work sort of like this:
wsc = get_websocket_connection()
async def call_method(method, **params):
packet = make_json_packet(method, params)
await wsc.send(packet)
resp = await wsc.recv()
return decode_json_packet(resp)
async def working_code():
separate_request = asyncio.ensure_future(call_method("quux"))
first_result = await call_method("foo", x=1)
second_result = await call_method("bar", y=first_result)
print(second_result)
return await separate_request
Now, I expect the separate_request to wait asynchronously while first_result and second_results are processed. But I have no guarantee that the wsc.recv() call will return the matching response; in fact, I have no guarantees that the service returns the responses in order of requests.
I can use the id field to disambiguate the responses. But how can I write the call_method() so that it manages the requests internally and resumes the "right" coroutine when the corresponding reply is received?
when I've done this sort of thing before I've tended to split things out into two parts:
"sending code" (can be multiple threads) this sets up where responses should go to (i.e. a dict of ids to functions or Futures), then sends the request and blocks for the response
"receiving code" (probably one thread per socket) that monitors all inbound traffic and passes responses off to whichever code is interested in the id. this is also a sensible place to handle the socket being closed unexpectedly which should push an exception out as appropriate
this is probably a few hundred lines of code and pretty application specific…

How to handle DNS timeouts with aiohttp?

The aiohttp readme says:
If you want to use timeouts for aiohttp client please use standard asyncio approach:
yield from asyncio.wait_for(client.get(url), 10)
But that doesn't handle DNS timeouts which are, I guess, handled by the OS. Also the with aiohttp.Timeout doesn't handle OS DNS lookups.
There has been a discussion at the asyncio repo without final conclusion and Saghul has made aiodns but I'm not sure how to mix it into aiohttp and whether that will allow asyncio.wait_for functionality.
Testcase (takes 20 sec on my linux box):
async def fetch(url):
url = 'http://alicebluejewelers.com/'
with aiohttp.Timeout(0.001):
resp = await aiohttp.get(url)
Timeout works as expected but unfortunately your example hangs on python shutdown procedure: it waits for termination of background thread which performs DNS lookup.
As a solution I can suggest using aiodns for manual IP resolving:
import asyncio
import aiohttp
import aiodns
async def fetch():
dns = 'alicebluejewelers.com'
# dns = 'google.com'
with aiohttp.Timeout(1):
ips = await resolver.query(dns, 'A')
print(ips)
url = 'http://{}/'.format(ips[0].host)
async with aiohttp.get(url) as resp:
print(resp.status)
loop = asyncio.get_event_loop()
resolver = aiodns.DNSResolver(loop=loop)
loop.run_until_complete(fetch())
Maybe solution worth to be included into TCPConnector as optional feature.
Pull Request is welcome!

Categories

Resources