PK!6aiosql/__init__.pyfrom .aiosql import from_path, from_str, register_driver_adapter, SQLOperationType __all__ = ["from_path", "from_str", "register_driver_adapter", "SQLOperationType"] PK!aiosql/adapters/__init__.pyPK!)U``aiosql/adapters/aiosqlite.pyfrom ..aioctxlib import aiocontextmanager class AioSQLiteAdapter: is_aio_driver = True @staticmethod def process_sql(_query_name, _op_type, sql): """Pass through function because the ``aiosqlite`` driver can already handle the :var_name format used by aiosql and doesn't need any additional processing. Args: _query_name (str): The name of the sql query. _op_type (SQLOperationType): The type of SQL operation performed by the query. sql (str): The sql as written before processing. Returns: str: Original SQL text unchanged. """ return sql @staticmethod async def select(conn, _query_name, sql, parameters): async with conn.execute(sql, parameters) as cur: return await cur.fetchall() @staticmethod @aiocontextmanager async def select_cursor(conn, _query_name, sql, parameters): async with conn.execute(sql, parameters) as cur: yield cur @staticmethod async def insert_returning(conn, _query_name, sql, parameters): async with conn.execute(sql, parameters) as cur: return cur.lastrowid @staticmethod async def insert_update_delete(conn, _query_name, sql, parameters): cur = await conn.execute(sql, parameters) await cur.close() @staticmethod async def insert_update_delete_many(conn, _query_name, sql, parameters): cur = await conn.executemany(sql, parameters) await cur.close() @staticmethod async def execute_script(conn, sql): await conn.executescript(sql) PK!ڽBTTaiosql/adapters/asyncpg.pyfrom collections import defaultdict from ..aioctxlib import aiocontextmanager from ..patterns import var_pattern class MaybeAcquire: def __init__(self, client): self.client = client async def __aenter__(self): if "acquire" in dir(self.client): self._managed_conn = await self.client.acquire() return self._managed_conn else: self._managed_conn = None return self.client async def __aexit__(self, exc_type, exc, tb): if self._managed_conn is not None: await self.client.release(self._managed_conn) class AsyncPGAdapter: is_aio_driver = True def __init__(self): self.var_replacements = defaultdict(dict) def process_sql(self, query_name, _op_type, sql): count = 0 adj = 0 for match in var_pattern.finditer(sql): gd = match.groupdict() # Do nothing if the match is found within quotes. if gd["dblquote"] is not None or gd["quote"] is not None: continue var_name = gd["var_name"] if var_name in self.var_replacements[query_name]: replacement = f"${self.var_replacements[query_name][var_name]}" else: count += 1 replacement = f"${count}" self.var_replacements[query_name][var_name] = count start = match.start() + len(gd["lead"]) + adj end = match.end() - len(gd["trail"]) + adj sql = sql[:start] + replacement + sql[end:] replacement_len = len(replacement) # the lead ":" char is the reason for the +1 var_len = len(var_name) + 1 if replacement_len < var_len: adj = adj + replacement_len - var_len else: adj = adj + var_len - replacement_len return sql def maybe_order_params(self, query_name, parameters): if isinstance(parameters, dict): xs = [(self.var_replacements[query_name][k], v) for k, v in parameters.items()] xs = sorted(xs, key=lambda x: x[0]) return [x[1] for x in xs] elif isinstance(parameters, tuple): return parameters else: raise ValueError(f"Parameters expected to be dict or tuple, received {parameters}") async def select(self, conn, query_name, sql, parameters): parameters = self.maybe_order_params(query_name, parameters) async with MaybeAcquire(conn) as connection: return await connection.fetch(sql, *parameters) @aiocontextmanager async def select_cursor(self, conn, query_name, sql, parameters): parameters = self.maybe_order_params(query_name, parameters) async with MaybeAcquire(conn) as connection: stmt = await connection.prepare(sql) async with connection.transaction(): yield stmt.cursor(*parameters) async def insert_returning(self, conn, query_name, sql, parameters): parameters = self.maybe_order_params(query_name, parameters) async with MaybeAcquire(conn) as connection: return await connection.fetchrow(sql, *parameters) async def insert_update_delete(self, conn, query_name, sql, parameters): parameters = self.maybe_order_params(query_name, parameters) async with MaybeAcquire(conn) as connection: await connection.execute(sql, *parameters) async def insert_update_delete_many(self, conn, query_name, sql, parameters): parameters = [self.maybe_order_params(query_name, params) for params in parameters] async with MaybeAcquire(conn) as connection: await connection.executemany(sql, parameters) @staticmethod async def execute_script(conn, sql): async with MaybeAcquire(conn) as connection: await connection.execute(sql) PK! 1+aiosql/adapters/psycopg2.pyfrom contextlib import contextmanager from ..patterns import var_pattern def replacer(match): gd = match.groupdict() if gd["dblquote"] is not None: return gd["dblquote"] elif gd["quote"] is not None: return gd["quote"] else: return f'{gd["lead"]}%({gd["var_name"]})s{gd["trail"]}' class PsycoPG2Adapter: @staticmethod def process_sql(_query_name, _op_type, sql): return var_pattern.sub(replacer, sql) @staticmethod def select(conn, _query_name, sql, parameters): with conn.cursor() as cur: cur.execute(sql, parameters) return cur.fetchall() @staticmethod @contextmanager def select_cursor(conn, _query_name, sql, parameters): with conn.cursor() as cur: cur.execute(sql, parameters) yield cur @staticmethod def insert_update_delete(conn, _query_name, sql, parameters): with conn.cursor() as cur: cur.execute(sql, parameters) @staticmethod def insert_update_delete_many(conn, _query_name, sql, parmeters): with conn.cursor() as cur: cur.executemany(sql, parmeters) @staticmethod def insert_returning(conn, _query_name, sql, parameters): with conn.cursor() as cur: cur.execute(sql, parameters) res = cur.fetchone() return res if res else None @staticmethod def execute_script(conn, sql): with conn.cursor() as cur: cur.execute(sql) PK!Riaiosql/adapters/sqlite3.pyfrom contextlib import contextmanager class SQLite3DriverAdapter: @staticmethod def process_sql(_query_name, _op_type, sql): """Pass through function because the ``sqlite3`` driver already handles the :var_name "named style" syntax used by aiosql variables. Note, it will also accept "qmark style" variables. Args: _query_name (str): The name of the sql query. Unused. _op_type (aiosql.SQLOperationType): The type of SQL operation performed by the sql. sql (str): The sql as written before processing. Returns: str: Original SQL text unchanged. """ return sql @staticmethod def select(conn, _query_name, sql, parameters): cur = conn.cursor() cur.execute(sql, parameters) results = cur.fetchall() cur.close() return results @staticmethod @contextmanager def select_cursor(conn, _query_name, sql, parameters): cur = conn.cursor() cur.execute(sql, parameters) try: yield cur finally: cur.close() @staticmethod def insert_update_delete(conn, _query_name, sql, parameters): conn.execute(sql, parameters) @staticmethod def insert_update_delete_many(conn, _query_name, sql, parameters): conn.executemany(sql, parameters) @staticmethod def insert_returning(conn, _query_name, sql, parameters): cur = conn.cursor() cur.execute(sql, parameters) results = cur.lastrowid cur.close() return results @staticmethod def execute_script(conn, sql): conn.executescript(sql) PK!X׃aiosql/aioctxlib.pyfrom functools import wraps class _AioCtxMgr: def __init__(self, func, args, kwds): self.gen = func(*args, **kwds) self.func = func self.args = args self.kwds = kwds doc = getattr(func, "__doc__", None) if doc is None: doc = type(self).__doc__ self.__doc__ = doc async def __aenter__(self): try: return await self.gen.__anext__() except StopAsyncIteration: raise RuntimeError("generator didn't yield") from None async def __aexit__(self, typ, value, traceback): if typ is None: try: await self.gen.__anext__() except StopAsyncIteration: return else: raise RuntimeError("generator didn't stop") else: if value is None: value = typ() # See _GeneratorContextManager.__exit__ for comments on subtleties # in this implementation try: await self.gen.athrow(typ, value, traceback) raise RuntimeError("generator didn't stop after throw()") except StopAsyncIteration as exc: return exc is not value except RuntimeError as exc: if exc is value: return False # Avoid suppressing if a StopIteration exception # was passed to throw() and later wrapped into a RuntimeError # (see PEP 479 for sync generators; async generators also # have this behavior). But do this only if the exception wrapped # by the RuntimeError is actully Stop(Async)Iteration (see # issue29692). if isinstance(value, (StopIteration, StopAsyncIteration)): if exc.__cause__ is value: return False raise except BaseException as exc: if exc is not value: raise def aiocontextmanager(func): @wraps(func) def helper(*args, **kwds): return _AioCtxMgr(func, args, kwds) return helper PK!00aiosql/aiosql.pyfrom pathlib import Path from enum import Enum from .adapters.aiosqlite import AioSQLiteAdapter from .adapters.asyncpg import AsyncPGAdapter from .adapters.psycopg2 import PsycoPG2Adapter from .adapters.sqlite3 import SQLite3DriverAdapter from .exceptions import SQLLoadException, SQLParseException from .patterns import ( query_name_definition_pattern, empty_pattern, doc_comment_pattern, valid_query_name_pattern, ) _ADAPTERS = { "aiosqlite": AioSQLiteAdapter, "asyncpg": AsyncPGAdapter, "psycopg2": PsycoPG2Adapter, "sqlite3": SQLite3DriverAdapter, } def register_driver_adapter(driver_name, driver_adapter): """Registers custom driver adapter classes to extend ``aiosql`` to to handle additional drivers. For details on how to create a new driver adapter see the documentation `link `_. TODO: Make a link to the documentation when it exists. Args: driver_name (str): The driver type name. driver_adapter (callable): Either n class or function which creates an instance of a driver adapter. Returns: None Examples: To register a new loader:: class MyDbAdapter(): def process_sql(self, name, op_type, sql): pass def select(self, conn, sql, parameters): pass @contextmanager def select_cursor(self, conn, sql, parameters): pass def insert_update_delete(self, conn, sql, parameters): pass def insert_update_delete_many(self, conn, sql, parameters): pass def insert_returning(self, conn, sql, parameters): pass def execute_script(self, conn, sql): pass aiosql.register_driver_adapter("mydb", MyDbAdapter) If your adapter constructor takes arguments you can register a function which can build your adapter instance:: def adapter_factory(): return MyDbAdapter("foo", 42) aiosql.register_driver_adapter("mydb", adapter_factory) """ _ADAPTERS[driver_name] = driver_adapter def get_driver_adapter(driver_name): """Get the driver adapter instance registered by the ``driver_name``. Args: driver_name (str): The database driver name. Returns: object: A driver adapter class. """ try: driver_adapter = _ADAPTERS[driver_name] except KeyError: raise ValueError(f"Encountered unregistered driver_name: {driver_name}") return driver_adapter() class SQLOperationType(Enum): """Enumeration of aiosql operation types. """ INSERT_RETURNING = 0 INSERT_UPDATE_DELETE = 1 INSERT_UPDATE_DELETE_MANY = 2 SCRIPT = 3 SELECT = 4 class Queries: """Container object with dynamic methods built from SQL queries. The ``-- name`` definition comments in the SQL content determine what the dynamic methods of this class will be named. @DynamicAttrs """ def __init__(self, queries=None): """Queries constructor. Args: queries (list(tuple)): """ if queries is None: queries = [] self._available_queries = set() for query_name, fn in queries: self.add_query(query_name, fn) @property def available_queries(self): """Returns listing of all the available query methods loaded in this class. Returns: list(str): List of dot-separated method accessor names. """ return sorted(self._available_queries) def __repr__(self): return "Queries(" + self.available_queries.__repr__() + ")" def add_query(self, query_name, fn): """Adds a new dynamic method to this class. Args: query_name (str): The method name as found in the SQL content. fn (function): The loaded query function. Returns: """ setattr(self, query_name, fn) self._available_queries.add(query_name) def add_child_queries(self, child_name, child_queries): """Adds a Queries object as a property. Args: child_name (str): The property name to group the child queries under. child_queries (Queries): Queries instance to add as sub-queries. Returns: None """ setattr(self, child_name, child_queries) for child_query_name in child_queries.available_queries: self._available_queries.add(f"{child_name}.{child_query_name}") def _create_fns(query_name, docs, op_type, sql, driver_adapter): def fn(conn, *args, **kwargs): parameters = kwargs if len(kwargs) > 0 else args if op_type == SQLOperationType.INSERT_RETURNING: return driver_adapter.insert_returning(conn, query_name, sql, parameters) elif op_type == SQLOperationType.INSERT_UPDATE_DELETE: return driver_adapter.insert_update_delete(conn, query_name, sql, parameters) elif op_type == SQLOperationType.INSERT_UPDATE_DELETE_MANY: return driver_adapter.insert_update_delete_many(conn, query_name, sql, *parameters) elif op_type == SQLOperationType.SCRIPT: return driver_adapter.execute_script(conn, sql) elif op_type == SQLOperationType.SELECT: return driver_adapter.select(conn, query_name, sql, parameters) else: raise ValueError(f"Unknown op_type: {op_type}") fn.__name__ = query_name fn.__doc__ = docs fn.sql = sql async def aio_fn(conn, *args, **kwargs): parameters = kwargs if len(kwargs) > 0 else args if op_type == SQLOperationType.INSERT_RETURNING: return await driver_adapter.insert_returning(conn, query_name, sql, parameters) elif op_type == SQLOperationType.INSERT_UPDATE_DELETE: return await driver_adapter.insert_update_delete(conn, query_name, sql, parameters) elif op_type == SQLOperationType.INSERT_UPDATE_DELETE_MANY: return await driver_adapter.insert_update_delete_many( conn, query_name, sql, *parameters ) elif op_type == SQLOperationType.SCRIPT: return await driver_adapter.execute_script(conn, sql) elif op_type == SQLOperationType.SELECT: return await driver_adapter.select(conn, query_name, sql, parameters) else: raise ValueError(f"Unknown op_type: {op_type}") aio_fn.__name__ = query_name aio_fn.__doc__ = docs aio_fn.sql = sql ctx_mgr_method_name = f"{query_name}_cursor" def ctx_mgr(conn, *args, **kwargs): parameters = kwargs if len(kwargs) > 0 else args return driver_adapter.select_cursor(conn, query_name, sql, parameters) ctx_mgr.__name__ = ctx_mgr_method_name ctx_mgr.__doc__ = docs ctx_mgr.sql = sql if getattr(driver_adapter, "is_aio_driver", False): if op_type == SQLOperationType.SELECT: return [(query_name, aio_fn), (ctx_mgr_method_name, ctx_mgr)] else: return [(query_name, aio_fn)] else: if op_type == SQLOperationType.SELECT: return [(query_name, fn), (ctx_mgr_method_name, ctx_mgr)] else: return [(query_name, fn)] def load_methods(sql_text, driver_adapter): lines = sql_text.strip().splitlines() query_name = lines[0].replace("-", "_") if query_name.endswith(""[^"]+")|' r"(?P\'[^\']+\')|" r"(?P[^:]):(?P[\w-]+)(?P[^:])" ) """ Pattern: Identifies variable definitions in SQL code. """ PK! #>aiosql-2.0.2.dist-info/LICENSECopyright (c) 2014-2017, Honza Pokorny Copyright (c) 2018, William Vaughn All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of the aiosql Project. PK!H=jTTaiosql-2.0.2.dist-info/WHEEL 1 0 нRN&":WhAqo;VSoBtM4[`Z} Mz7M*{/ܒ?֎XPK!Hv۟%,aiosql-2.0.2.dist-info/METADATATQoH ~_Sv\{)CURxo2t2f&,ןd[x8"e9ͣ }q,ܼj=Rٴ6wöx>ںco [O%{M*5 k˫+N&V!5<<:gw8_kѺ>Oƽ4jRF> 6RZϳߋGÔΒF{[@R"R#Hu^('9hCIgy};i/y#5Xi3 F u<̰ؼϑd!Tn1io#Џ {3a^x|CiAE/B15j_%wM=7=tB!ӖbJsrQʘI MZWUYGiqE ?\oUh-'14I蜴Ƈy !p(See@@vv9>ڸ&Ty$T'$!Qn8ynl., {-ZZ$0FvJR?mpŋriS\tiXr}uSB r ]3)NBkE& ^4FdR4m}tA"},ܠ_xWZSc0;Ž G|b4 %ec7u8@Nm5+?8D9['J;PI=+1!1NFNbGkṍ4WYێe2x+lpn^):,HyVVXîSOOlCh}"嚬Sy/'2,Jy(6|P0?6/~ÃI OI\HiWP8M4zx*2?;(ow EPK!6aiosql/__init__.pyPK!aiosql/adapters/__init__.pyPK!)U``aiosql/adapters/aiosqlite.pyPK!ڽBTTaiosql/adapters/asyncpg.pyPK! 1+6aiosql/adapters/psycopg2.pyPK!Ri[aiosql/adapters/sqlite3.pyPK!X׃,$aiosql/aioctxlib.pyPK!00,aiosql/aiosql.pyPK!A{[[]aiosql/exceptions.pyPK!%oo]aiosql/patterns.pyPK! #>=`aiosql-2.0.2.dist-info/LICENSEPK!H=jTTfaiosql-2.0.2.dist-info/WHEELPK!Hv۟%,gaiosql-2.0.2.dist-info/METADATAPK!H)^7yjaiosql-2.0.2.dist-info/RECORDPKHm