Best way to open/close DB Connection with async/await - python

In tutorials I found, there is always opening and closing the connection for every request, for example :
import asyncio
import asyncpg
async def run():
conn = await asyncpg.connect(user='user', password='password',
database='database', host='127.0.0.1')
values = await conn.fetch('''SELECT * FROM mytable''')
await conn.close()
loop = asyncio.get_event_loop()
loop.run_until_complete(run())
While this works for one single function, How about web application ?
IE: for example in Tornado, every URL is a class, which leads to lot of classes/methods.
I have the habit to open the connection in a blocking way, then use the wrapper to make asynchronous DB calls, and close the connection only to shut down gracefuly the server, what is the best practice in that case with async/await ?

Without having used asyncpg, I assume like in most asyncio compliant packages that there is an async context manager allowing exactly what you are asking for.
Something like:
async with asyncpg.create_pool(**kwargs) as pool:
async with pool.acquire() as connection:
async with connection.transaction():
result = await connection.fetchval(fetch stuff)
connection.execute(insert stuff with result)
(as taken from this question)
Check the docs for mentions of context managers or examples with async with statements or if nothing else then check classes in the source code which have implement the __aenter__, __aexit__ methods.
Edit 1:
The example above is partly taken from the question I've linked to and partly contrived for completeness. But to address your comments about what the with statements are doing:
async with asyncpg.create_pool(**kwargs) as pool:
#in this block pool is created and open
async with pool.acquire() as connection:
# in this block connection is acquired and open
async with connection.transaction():
# in this block each executed statement is in a transaction
execute_stuff_with_connection(connection)
# now we are back up one logical block so the transaction is closed
do_stuff_without_transaction_but_with_connection(connection)
# now we are up another block and the connection is closed and returned to the pool
do_more_stuff_with_pool(pool)
# now we are up another level and the pool is closed/exited/cleaned up
done_doing_async_stuff()
I'm not sure how good of an explanation this is, perhaps you should read up on context managers.

Related

Can FastAPI guarantee a sync handler will never block the main application thread?

I have the following FastAPI application:
from fastapi import FastAPI
import socket
app = FastAPI()
#app.get("/")
async def root():
return {"message": "Hello World"}
#app.get("/healthcheck")
def health_check():
result = some_network_operation()
return result
def some_network_operation():
HOST = "192.168.30.12" # This host does not exist so the connection will time out
PORT = 4567
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(10)
s.connect((HOST, PORT))
s.sendall(b"Are you ok?")
data = s.recv(1024)
print(data)
This is a simple application with two routes:
/ handler that is async
/healthcheck handler that is sync
With this particular example, if you call /healthcheck, it won't complete until after 10 seconds because the socket connection will timeout. However, if you make a call to / in the meantime, it will return the response right away because FastAPI's main thread is not blocked. This makes sense because according to the docs, FastAPI runs sync handlers on an external threadpool.
My question is, if it is at all possible for us to block the application (block FastAPI's main thread) by doing something inside the health_check method.
Perhaps by acquiring the global interpreter lock?
Some other kind of lock?
Yes, if you try to do sync work in a async method it will block FastAPI, something like this:
#router.get("/healthcheck")
async def health_check():
result = some_network_operation()
return result
Where some_network_operation() is blocking the event loop because it is a synchronous method.
I think I may have an answer to my question, which is that there are some weird edge cases where a sync endpoint handler can block FastAPI.
For instance, if we adjust the some_network_operation in my example to the following, it will block the entire application.
def some_network_operation():
""" No, this is not a network operation, but it illustrates the point """
block = pow (363,100000000000000)
I reached this conclusion based on this question: pow function blocking all threads with ThreadPoolExecutor.
So, it looks like the GIL maybe the culprit here.
That SO question suggests using the multiprocessing module (which will get around GIL). However, I tried this, and it still resulted in the same behavior. So my root problem remains unsolved.
Either way, here is the entire example in the question edited to reproduce the problem:
from fastapi import FastAPI
app = FastAPI()
#app.get("/")
async def root():
return {"message": "Hello World"}
#app.get("/healthcheck")
def health_check():
result = some_network_operation()
return result
def some_network_operation():
block = pow(363,100000000000000)

Python - Async callback/receiver for WebSocket

I am trying to implement WebSocket connection to a server (Python app <=> Django app)
Whole system runs in big Asyncio loop with many tasks. Code snippet is just very small testing part.
I am able to send any data to a server at any moment and many of them will be type request something and wait for response. But I would like to have some "always running" handler for all incoming messages. (When something in Django database will change I want to send changes to python app).
How can Include always running receiver/ or add callback to websocket? I am not able to find any solution for this.
My code snippet:
import asyncio, json, websockets, logging
class UpdateConnection:
async def connect(self,botName):
self.sock = await websockets.connect('ws://localhost:8000/updates/bot/'+botName)
async def send(self,data):
try:
await self.sock.send(json.dumps(data))
except:
logging.info("Websocket connection lost!")
# Find a way how to reconenct... or make socket reconnect automatically
if __name__ == '__main__':
async def DebugLoop(socketCon):
await socketCon.connect("dev")
print("Running..")
while True:
data = {"type": "debug"}
await socketCon.send(data)
await asyncio.sleep(1)
uSocket = UpdateConnection()
loop = asyncio.get_event_loop()
loop.create_task(DebugLoop(uSocket))
loop.run_forever()
My debug server after connection will start sending random messages to the client in random intervals and I would like to somehow handle them in async way.
Thanks for any help :)
You don't have to do it so complicated. First of all I suggest you use the context patterns offered by websockets module.
From the documentation:
connect() can be used as an infinite asynchronous iterator to reconnect automatically on errors:
async for websocket in websockets.connect(...):
try:
...
except websockets.ConnectionClosed:
continue
Additionally, you simply keep the connection alive by awaiting incoming messages:
my_websocket = None
async for websocket in websockets.connect('ws://localhost:8000/updates/bot/' + botName):
try:
my_websocket = websocket
async for message in websocket:
pass # here you could also process incoming messages
except websockets.ConnectionClosed:
my_websocket = None
continue
As you can see we have a nested loop here:
The outer loop constantly reconnects to the server
The inner loop processes one incoming message at a time
If you are connected, and no messages are coming in from the server, this will just sleep.
The other thing that happens here is that my_websocket is set to the active connection, and unset again when the connection is lost.
In other parts of your script you can use my_websocket to send data. Note that you will need to check if it is currently set wherever you use it:
async def send(data):
if my_websocket:
await my_websocket.send(json.dumps(data))
This is just an illustration, you can also keep the websocket object as an object member, or pass it to another component through a setter function, etc.

context-managed resource on module level

I am looking for a pattern where I have multiple functions that need access to a resource that is context-managed.
In particular, I am using fastAPI and want to re-use aiopg (async psycopg2) connections.
This is the basic layout:
#app.get("/hello")
def hello():
async with aiopg.connect(...) as conn:
async with conn.cursor(...):
return cursor.execute(...)
Now I want to avoid a connection per route. I could think of an object outside, and in the route I either access the conn property or await creation (and store back) and then just use the with on the cursor() method.
class Pg():
async def conn(self):
if not self._conn:
self._conn = await aiopg.connect(...)
return self._conn
myPg = Pg()
#app.get("/hello")
def hello():
conn = await myPg.conn()
async with conn.cursor(...):
return cursor.execute(...)
However, I then would lose the ability to automatically close the connection.
I think that I miss something really obvious here and hope that someone could guide me how to implement this properly.
Thanks!
aiopg provides a Pool class that can manage connections.
just create a pool instance at the module level:
pool = await aiopg.create_pool('<your connection string>')
Then you can use the Pool.acquire context-manager to get a connection:
async with pool.acquire() as conn:
...
If connections already exist in the pool, they will be re-used.

Python 3.8 websocket echo client with queue: asyncio.Queue get() does not get queue items added on the fly

I try to create a client which uses a asyncio.Queue to feed the messages I want to send to the server. Receiving data from websocket server works great. Sending data which is just generated by the producer works, too. For explaning what works and what fails, first here's my code:
import sys
import asyncio
import websockets
class WebSocketClient:
def __init__(self):
self.send_queue = asyncio.Queue()
#self.send_queue.put_nowait('test-message-1')
async def startup(self):
await self.connect_websocket()
consumer_task = asyncio.create_task(
self.consumer_handler()
)
producer_task = asyncio.create_task(
self.producer_handler()
)
done, pending = await asyncio.wait(
[consumer_task, producer_task],
return_when=asyncio.ALL_COMPLETED
)
for task in pending:
task.cancel()
async def connect_websocket(self):
try:
self.connection = await websockets.client.connect('ws://my-server')
except ConnectionRefusedError:
sys.exit('error: cannot connect to backend')
async def consumer_handler(self):
async for message in self.connection:
await self.consumer(message)
async def consumer(self, message):
self.send_queue.put_nowait(message)
# await self.send_queue.put(message)
print('mirrored message %s now in queue, queue size is %s' % (message, self.send_queue.qsize()))
async def producer_handler(self):
while True:
message = await self.producer()
await self.connection.send(message)
async def producer(self):
result = await self.send_queue.get()
self.send_queue.task_done()
#await asyncio.sleep(10)
#result = 'test-message-2'
return result
if __name__ == '__main__':
wsc = WebSocketClient()
asyncio.run(wsc.startup())
Connecting works great. If I send something from my server to the client, this works great too and prints the message in consumer(). But producer never gets any message I put in send_queue inside consumer().
The reason why I chose send_queue.put_nowait in consumer() was that I wanted to prevent deadlocks. If I use the line await self.send_queue.put(message) line instead of self.send_queue.put_nowait(message) it makes no difference.
I thought, maybe the queue dos not work at all, so I filled something to the queue just at creation in __init__(): self.send_queue.put_nowait("test-message-1"). This works and is sent to my server. So the basic concept of the queue and await queue.get() works.
I als thought, maybe there is some issue with the producer, so let's just randomly generate messages during runtime: result = "test-message-2" instead of result = await self.send_queue.get(). This works too: every 10 seconds 'test-message-2' is sent to my server.
EDIT: This also happens if I try to add stuff from another source to the queue on the fly. I build a small asyncio socket server which pushes any message to the queue, which works great, and you can see the messages I added from the other source with qsize() in consumer(), but still no successfull queue.get(). So the queue itself seems to work, just not get(). This is btw the reason for the queue, too: I would like to send data from quite different sources.
So, this is the point where I'm stuck. My wild guess is that the queue I use in producer() is not the same as in consumer(), something which happens at threading quite easily if you use non-thread-safe queues like asyncio.Queue, but as I understood it I don't use threading at all, just coroutines. So, what else went wrong here?
Just for the context: it's a Ubuntu 20.04 python 3.8.2 inside a docker container.
Thanks,
Ernesto
Just for the records - the solution for my problem was quite simple: I defined send_queue outside the event loop created by my websocket client. So it called events.get_event_loop() and got its own loop - which was not part of the main loop and therefore never called, therefore await queue.get() really never got anything back.
In normal mode, you don't see any message which is a hint to this issue. But, python documentation to the rescue: for course they mentioned it at https://docs.python.org/3/library/asyncio-dev.html : logging.DEBUG gave the hints I needed to find the problem.
It should look like this:
class WebSocketClient:
async def startup(self):
self.send_queue = asyncio.Queue()
await self.connect_websocket()
Then the queue is defined inside the main loop.

Pytest: testing a websocket connection

I have this piece of code
class RTMClient:
...
#not important code
...
async def connect(self, queue: asyncio.Queue):
"""
Connect to the websocket stream and iterate over the messages
dumping them in the Queue.
"""
ws_url=''#url aquisition amended to readability
try:
self._ws = await websockets.connect(ws_url)
while not self.is_closed:
msg = await self._ws.recv()
if msg is None:
break
await queue.put(json.loads(msg))
except asyncio.CancelledError:
pass
finally:
self._closed.set()
self._ws = None
I want to write an automated test for it.
What I intend to do:
Monkeypatch websockets.connect to return a mock connection
Make the mock connection return mock messages from a predefined list
Make the mock connection set is_closed to True
Assert that the websocket connection was closed
Assert that all predefined messages are in the Queue
My question: how do I mock the websockets.connection to achieve steps 1-3?
I am thinking of a pytest fixture like this
from websockets import WebSocketClientProtocol()
#pytest.fixture
def patch_websockets_connect(monkeypatch):
async def mock_ws_connect(*args, **kwargs):
mock_connection = WebSocketClientProtocol()
mock_connection.is_closed = False
return mock_connection
monkeypatch.setattr('target_module.websockets.connect', mock_ws_connect)
But I don't see how I will be able to return a predefined list of messages this way, and also there must be a better way of doing this.
It is not a full answer but maybe it will help you.
I hit the simlar problem with testing rabbitmq messages emission. And looks like the most common and robust approach here is to create Connection, create Emmiter and Consumer, connect Consumer to the fake Channel and that manually send messages to that channel in the test.
So you creating all objects and just mocking their responses. Here is very similar example (with sockets and not with websockets, but maybe still usefull for you): mocking a socket connection in Python

Categories

Resources