Skip to content

Instantly share code, notes, and snippets.

@mikeshardmind
Last active January 22, 2023 14:26
Show Gist options
  • Save mikeshardmind/e0d6e01705e590983a8aee4aefcc4c8a to your computer and use it in GitHub Desktop.
Save mikeshardmind/e0d6e01705e590983a8aee4aefcc4c8a to your computer and use it in GitHub Desktop.
# MIT License
#
# Copyright (c) 2023 Michael Hall
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from __future__ import annotations
import asyncio
import apsw
from collections import deque
from typing import Any
from contextlib import asynccontextmanager
__all__ = ["ConnectionPool"]
class ConnectionPool:
"""
It's a misuse of this class to continue using a connection
outside of the provided async context manager.
I may actually do the required wrapping to check for this at some point,
but I'm just wrapping some really simple
behavior here and don't want to.
with the exception of poolsize, the arguments this accepts are
just passed through to the underlying aspw.connection(s)
use of wal mode (see apsw.connection_hooks) is highly reccomended, but not done for you
Lazy async use requires functions which accept a cconnection + things to pass through
def set_user_phone_number(conn, user_id, phone):
with conn:
cursor = conn.cursor()
cursor.execute(
'''
INSERT INTO USERS (user_id, phone) VALUES (?, ?)
ON CONFLICT (user_id) DO UPDATE SET phone=excluded.phone
''',
(phone,)
)
elsewhere....
async with pool.get_connection() as conn:
await asyncio.to_thread(set_user_phone_number, conn, phone_number)
"""
def __init__(
self,
poolsize: int,
filename: str,
# I am going to assume someone knows enough to know you can't pool sqlite connections and exclusive mode
flags: int = apsw.SQLITE_OPEN_READWRITE | apsw.SQLITE_OPEN_CREATE,
vfs: Any | None = None,
statementcachesize: int = 100
):
if poolsize < 1:
raise ValueError("Must have a poolsize of at least 2")
self._sem = asyncio.Semaphore(value=poolsize)
self._connections: deque[apsw.Connection] = deque(maxlen=poolsize)
for _ in range(poolsize):
self._connections.append(apsw.Connection(filename, flags, vfs, statementcachesize))
# not implementing close, apsw connections are self-closing at collection
@asynccontextmanager
async def get_connection(self):
# There's not much point in a sync version of this IMO
# Additionally, the connections are threadsafe, you can toss them into asyncio.to_thread if you need
# I don't see much a point in wrapping beyond that either.
async with self._sem:
try:
conn = self._connections.pop()
yield conn
finally:
self._connections.appendleft(conn) # could add misuse wrapping here by wrapping the returned connection, but... meh
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment