3 Simple convenience wrapper for database connections and queries.
5 Provides shorthand functions for operating on a single database connection,
6 and similar interface to multiple databases via returned Database and Transaction objects.
8 ------------------------------------------------------------------------------
9 This file is part of dblite - simple query interface for SQL databases.
10 Released under the MIT License.
15 ------------------------------------------------------------------------------
23 logger = logging.getLogger(__name__)
26 def init(opts=None, engine=None, **kwargs):
28 Returns a Database object.
30 If opts is `None`, returns the default database - the very first created.
31 Module level functions use the default database.
33 @param opts database connection options, engine-specific;
34 SQLite takes a file path or path-like object or `":memory:"`,
35 Postgres takes a Postgres URI scheme like `"postgresql://user@localhost/mydb"`
36 or a Postgres keyword=value format string like
37 `"host=localhost username=user dbname=mydb"`
38 or a dictionary of connection options like `dict(host="localhost", dbname=..)`
39 @param engine database engine if not auto-detecting from connection options,
40 `"sqlite"` for SQLite3 and `"postgres"` for PostgreSQL (case-insensitive)
41 @param kwargs additional arguments given to engine constructor,
42 e.g. `detect_types=sqlite3.PARSE_COLNAMES` for SQLite,
43 or `minconn=1, maxconn=4` for Postgres connection pool
45 return Engines.factory(opts, engine, **kwargs)
49 def fetchall(table, cols="*", where=(), group=(), order=(), limit=(), **kwargs):
51 Convenience wrapper for database SELECT and fetch all.
52 Arguments have the same meaning as in select().
54 return init().
fetchall(table, cols, where, group, order, limit, **kwargs)
57 def fetchone(table, cols="*", where=(), group=(), order=(), limit=(), **kwargs):
59 Convenience wrapper for database SELECT and fetch one.
60 Arguments have the same meaning as in select().
62 return init().
fetchone(table, cols, where, group, order, limit, **kwargs)
65 def insert(table, values=(), **kwargs):
67 Convenience wrapper for database INSERT, returns inserted row ID.
68 Keyword arguments are added to VALUES.
69 `table` argument has the same meaning as in select().
70 Column names can be data-class properies as in select().
72 @param values column values to insert, as dict or a sequence of key-value tuples
80 Convenience wrapper for database multiple INSERTs, returns list of inserted row IDs.
81 Keyword arguments are added to VALUES of every single row, overriding individual row values.
82 `table` argument has the same meaning as in select().
83 Column names can be data-class properies as in select().
85 @param rows list of row values to insert, as list of dicts
86 or sequences of key-value tuples or data objects
91 def select(table, cols="*", where=(), group=(), order=(), limit=(), **kwargs):
93 Convenience wrapper for database SELECT, returns database cursor.
95 Column names in `where/cols/group/order` arguments can be data-class properties,
96 like `order=MyClass.my_attribute`.
98 @param table table/view name or expression to select from,
99 or a data class with `__name__` used for table name
100 @param where columns or expressions to select by, as dict or a sequence
101 of key-value tuples or a data object; value can specify operator
102 e.g. `{"mycolumn": ("IN", [1, 2, 3])}`
103 @param cols columns to select if not all, as string or a sequence of stringables
104 or a dict with stringable keys
105 @param group columns to GROUP BY, as string or a sequence of stringables
106 or a dict with stringable keys
107 @param order columns to ORDER BY, as string, a sequence of stringables,
108 or a combination of column names and direction flags,
109 or a dict with column name keys and direction flag values.
110 Direction flag can be string like `"ASC"`,
111 or truthy/falsy for ascending/descending.
112 @param limit LIMIT .. OFFSET.. values, as integer or a sequence of integers;
113 None or -1 disables LIMIT or OFFSET
114 @param kwargs additional arguments added to WHERE clause
116 return init().
select(table, cols, where, group, order, limit, **kwargs)
119 def update(table, values, where=(), **kwargs):
121 Convenience wrapper for database UPDATE, returns affected row count.
122 Arguments have the same meaning as in select().
123 Column names can be data-class properies as in select().
125 @param values column values to set, as dict or a sequence of key-value tuples
128 return init().
update(table, values, where, **kwargs)
133 Convenience wrapper for database DELETE, returns affected row count.
134 Arguments have the same meaning as in select().
141 Executes the SQL statement and returns database cursor.
143 @param sql SQL statement to execute, with engine-specific parameter bindings, if any
144 @param args query parameters, as tuple or dictionary
151 Executes the SQL statement against all parameter sequences.
153 @param sql SQL statement to execute, with engine-specific parameter bindings
154 @param args iterable of query parameters, as dictionaries for named placeholders
155 or sequences for positional placeholders
162 Executes the SQL as script of any number of statements.
164 Note that in SQLite, the statements are executed outside of transaction,
165 and any pending transaction will be committed first.
167 @param sql script with one or more SQL statements
172 def close(commit=None):
174 Closes the default database connection, if any.
176 @param commit `True` for explicit commit on open transactions,
177 `False` for explicit rollback on open transactions,
178 `None` defaults to `commit` flag from transaction creations
183 def transaction(commit=True, exclusive=None, **kwargs):
185 Returns a transaction context manager.
187 Context is breakable by raising Rollback.
189 Note that parameter `exclusive` defaults to `True` when using SQLite.
191 @param commit whether transaction commits automatically at exiting with-block
192 @param exclusive whether entering a with-block is exclusive over other
193 Transaction instances on this Database;
194 `None` stands for engine default
195 @param kwargs engine-specific arguments, like `schema="other", lazy=True` for Postgres
202 Registers function to auto-adapt given Python types to database types in query parameters.
204 Registration is global per engine.
206 @param transformer function(Python value) returning adapted value
207 @param typeclasses one or more Python classes to adapt
208 @param engine database engine to adapt for, defaults to first initialized
210 if not isinstance(typeclasses, (list, set, tuple)): typeclasses = [typeclasses]
216 Registers function to auto-convert given database types to Python in query results.
218 Registration is global per engine.
220 @param transformer function(raw database value) returning Python value
221 @param typenames one or more database column types to adapt
222 @param engine database engine to convert for, defaults to first initialized
224 if isinstance(typenames, str): typenames = [typenames]
230 Registers function to produce query results as custom type.
232 Registration is global per engine; affects future connections and the current default database.
234 @param row_factory function(cursor, row tuple) returning row as desired type
235 or `None` to reset to default. `cursor.description` is a sequence of
236 7-element tuples, as `(column name, ..engine-specific elements..)`.
237 @param engine database engine to register for, defaults to first initialized
239 Engines.register_row_factory(row_factory, engine)
244 """Abstract base for Database and Transaction."""
250 def fetchall(self, table, cols="*", where=(), group=(), order=(), limit=(), **kwargs):
252 Convenience wrapper for database SELECT and fetch all.
253 Keyword arguments are added to WHERE.
255 return list(self.
select(table, cols, where, group, order, limit, **kwargs))
258 def fetchone(self, table, cols="*", where=(), group=(), order=(), limit=(), **kwargs):
260 Convenience wrapper for database SELECT and fetch one.
261 Keyword arguments are added to WHERE.
263 limit = 1
if not limit
and limit != 0
else limit
264 return next(self.
select(table, cols, where, group, order, limit, **kwargs),
None)
267 def insert(self, table, values=(), **kwargs):
269 Convenience wrapper for database INSERT, returns inserted row ID.
270 Keyword arguments are added to VALUES.
272 raise NotImplementedError()
275 def insertmany(self, table, rows=(), **kwargs):
277 Convenience wrapper for database multiple INSERTs, returns list of inserted row IDs.
278 Keyword arguments are added to VALUES of every single row, overriding individual row values.
283 def select(self, table, cols="*", where=(), group=(), order=(), limit=(), **kwargs):
285 Convenience wrapper for database SELECT, returns database cursor.
286 Keyword arguments are added to WHERE.
288 sql, args = self.
makeSQL(
"SELECT", table, cols, where, group, order, limit, kwargs=kwargs)
289 cursor = self.
execute(sql, args)
290 return TypeCursor(cursor, table)
if inspect.isclass(table)
else cursor
293 def update(self, table, values, where=(), **kwargs):
295 Convenience wrapper for database UPDATE, returns affected row count.
296 Keyword arguments are added to WHERE.
298 sql, args = self.
makeSQL(
"UPDATE", table, values=values, where=where, kwargs=kwargs)
299 return self.
execute(sql, args).rowcount
302 def delete(self, table, where=(), **kwargs):
304 Convenience wrapper for database DELETE, returns affected row count.
305 Keyword arguments are added to WHERE.
307 sql, args = self.
makeSQL(
"DELETE", table, where=where, kwargs=kwargs)
308 return self.
execute(sql, args).rowcount
313 Executes the SQL statement and returns database cursor.
315 @param sql SQL statement to execute, with engine-specific parameter bindings, if any
316 @param args query parameters, as tuple or dictionary
318 raise NotImplementedError()
323 Executes the SQL statement against all parameter sequences
325 @param sql SQL statement to execute, with engine-specific parameter bindings
326 @param args iterable of query parameters, as dictionaries for named placeholders
327 or sequences for positional placeholders
329 raise NotImplementedError()
334 Executes the SQL as script of any number of statements.
336 Note that in SQLite, the statements are executed outside of transaction,
337 and any pending transaction will be committed first.
339 @param sql script with one or more SQL statements
341 raise NotImplementedError()
344 def makeSQL(self, action, table, cols="*", where=(), group=(), order=(), limit=(), values=(),
346 """Returns (SQL statement string, parameter dict)."""
347 raise NotImplementedError()
352 """Whether currently not open."""
353 raise NotImplementedError()
358 """Database engine cursor object, or `None` if closed."""
359 raise NotImplementedError()
363 def quote(cls, value, force=False):
365 Returns identifier in quotes and proper-escaped for queries,
366 if value needs quoting (has non-alphanumerics, starts with number, or is reserved).
368 @param value the value to quote, returned as-is if not string
369 @param force whether to quote value even if not required
371 raise NotImplementedError()
377 Database instance. Usable as an auto-closing context manager.
379 Queries directly on the Database object use autocommit mode.
381 Note that the database connection is not opened immediately on construction.
384 def transaction(self, commit=True, exclusive=None, **kwargs):
386 Returns a transaction context manager.
388 Context is breakable by raising Rollback.
390 Note that parameter `exclusive` defaults to `True` when using SQLite.
392 @param commit whether transaction autocommits at exiting with-block
393 @param exclusive whether entering a with-block is exclusive over other
394 Transaction instances on this Database;
395 `None` stands for engine default
396 @param kwargs engine-specific arguments, like `schema="other", lazy=True` for Postgres
398 raise NotImplementedError()
401 """Context manager entry, opens database if not already open, returns Database object."""
402 raise NotImplementedError()
404 def __exit__(self, exc_type, exc_val, exc_trace):
405 """Context manager exit, closes database and any pending transactions if open."""
406 raise NotImplementedError()
409 """Closes the database, if open."""
413 """Opens database connection if not already open."""
414 raise NotImplementedError()
416 def close(self, commit=None):
418 Closes the database and any pending transactions, if open.
420 @param commit `True` for explicit commit on open transactions,
421 `False` for explicit rollback on open transactions,
422 `None` defaults to `commit` flag from transaction creations
424 raise NotImplementedError()
428 """The custom row factory, if any, as `function(cursor, row tuple)`."""
429 raise NotImplementedError()
434 Sets custom row factory, as `function(cursor, row tuple)`, or `None` to reset to default.
436 `cursor.description` is a sequence of 7-element tuples,
437 first element being column name and the rest engine-specific.
439 Overrides globally registered row factory, if any.
441 raise NotImplementedError()
446 Transaction context manager, breakable by raising Rollback.
448 Note that in SQLite, a single connection has one shared transaction state,
449 so it is highly recommended to use exclusive Transaction instances for any action queries,
450 as concurrent transactions can interfere with one another otherwise.
453 def __init__(self, db, commit=True, exclusive=False, **kwargs):
455 Constructs a new transaction.
457 Note that parameter `exclusive` defaults to `True` when using SQLite.
459 @param db Database instance
460 @param commit whether transaction commits automatically at exiting with-block
461 @param exclusive whether entering a with-block is exclusive
462 over other Transaction instances on this Database
463 @param kwargs engine-specific arguments, like `schema="other", lazy=True` for Postgres
465 raise NotImplementedError()
468 """Context manager entry, returns Transaction object."""
469 raise NotImplementedError()
471 def __exit__(self, exc_type, exc_val, exc_trace):
472 """Context manager exit, propagates raised errors except Rollback."""
473 raise NotImplementedError()
476 """Closes the transaction, if open."""
479 def close(self, commit=None):
481 Closes the transaction, performing commit or rollback as specified.
482 Required if not using transaction as context manager in a with-block.
484 @param commit `True` for explicit commit, `False` for explicit rollback,
485 `None` defaults to `commit` flag from creation
487 raise NotImplementedError()
490 """Commits pending actions, if any."""
491 raise NotImplementedError()
494 """Rolls back pending actions, if any."""
495 raise NotImplementedError()
499 """Returns transaction Database instance."""
500 raise NotImplementedError()
505 Raising in transaction context manager will roll back the transaction
506 and exit the context manager cleanly, without rising further.
514 class Engines(object):
515 """Database engine broker."""
521 DATABASES = collections.OrderedDict()
524 def factory(cls, opts, engine=None, **kwargs):
526 Returns an opened Database, the first created if opts is `None`.
528 @param opts database connection options, engine-specific;
529 SQLite takes a file path or path-like object or `":memory:"`,
530 Postgres takes a Postgres URI scheme
531 like `"postgresql://user@localhost/mydb"`
532 or a Postgres keyword=value format
533 like `"host=localhost username=user dbname=mydb"`
534 or a dictionary of connection options like `dict(host="localhost", ..)`
535 @param engine database engine if not auto-detecting from connection options,
536 "sqlite" for SQLite3 and "postgres" for PostgreSQL (case-insensitive)
537 @param kwargs additional arguments given to engine constructor,
538 e.g. `detect_types=sqlite3.PARSE_COLNAMES` for SQLite,
539 or `minconn=1, maxconn=4` for Postgres connection pool
542 engine = engine.lower()
if engine
else None
543 if opts
is None and engine
is None:
544 engine = next(iter(cls.DATABASES))
545 elif opts
is not None:
546 engine = next(n
for n, m
in cls.MODULES.items()
if m.autodetect(opts))
547 db = cls.DATABASES[engine]
if opts
is None else cls.MODULES[engine].Database(opts, **kwargs)
548 cls.DATABASES.setdefault(engine, db)
553 def get(cls, engine=None):
554 """Returns engine module, by default the first created."""
556 engine = engine.lower()
if engine
else next(iter(cls.DATABASES))
557 return cls.MODULES[engine]
561 """Populates Database engines, if not already populated."""
562 if cls.MODULES
is None: cls.MODULES = util.load_modules()
566 """Registers row factory for engine, and current engine default database if any."""
567 engine = engine.lower()
if engine
else next(iter(cls.DATABASES))
569 if cls.DATABASES.get(engine): cls.DATABASES[engine].row_factory = row_factory
572 class TypeCursor(object):
573 """Wrapper for database cursor, yielding rows constructed with given callable."""
575 def __init__(self, cursor, callable):
577 @param cursor database engine cursor instance
578 @param callable function(rowdict) or function(*row values) or function(**rowdict)
580 self.__cursor = cursor
581 self.__cls = callable
582 self.__logged =
False
583 for name, value
in inspect.getmembers(cursor):
584 if not hasattr(self, name):
585 setattr(self, name, value)
587 def fetchmany(self, size=None):
589 for _
in range(self.__cursor.arraysize
if size
is None else size):
590 row = next(self.__cursor,
None)
591 if row
is None:
break
592 result.append(self.__factory(row))
595 def fetchone(self):
return next(self,
None)
596 def fetchall(self):
return list(self)
597 def __iter__(self):
return iter(self.__factory(x)
for x
in self.__cursor)
598 def __next__(self):
return self.__factory(next(self.__cursor))
599 def next(self):
return self.__factory(next(self.__cursor))
601 def __factory(self, row):
602 """Returns row constructed with callable, or original row if all argument options failed."""
603 result, errors = util.factory(self.__cls, row)
604 if result
is row
and not self.__logged:
605 logger.warning(
"Failed to instantiate %s with keywords, posargs, and dictionary. "
606 "Returning dictionary.\n%s", self.__cls,
"\n".join(map(repr, errors)))
613 "Database",
"Rollback",
"Transaction",
614 "init",
"fetchall",
"fetchone",
"insert",
"insertmany",
"select",
"update",
"delete",
615 "execute",
"executemany",
"executescript",
"close",
"transaction",
616 "register_adapter",
"register_converter",
"register_row_factory",