upd, add sqlite

This commit is contained in:
ge 2023-07-25 01:40:12 +03:00
parent a511550f71
commit ff69b68ba3
5 changed files with 51 additions and 40 deletions

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
__pychache__/ __pychache__/
*.pyc *.pyc
dist/ dist/
db.sqlite3
*.db

View File

@ -2,17 +2,17 @@
**Quicker** is a pythonic 🐍 tool for querying databases. **Quicker** is a pythonic 🐍 tool for querying databases.
Quicker wraps popular Python libraries: Quicker wraps Python libraries:
- `mysqlclient` for MySQL. - `mysqlclient` for MySQL.
- `psycopg2` for PostgreSQL. - `psycopg2` for PostgreSQL.
- `sqlite` from Python standard library for SQLite (not implemented yet). - `sqlite3` from Python standard library for SQLite.
# Why is it needed? # Why is it needed?
Briefly — just make queries without bothering to learn the abstractions of the library. At work, I periodically have to make queries to different databases and then somehow process the information received. This may be necessary for one-time use, so I don't want to write a lot of code. You may also want to do all the work right in the interactive Python shell.
At work, I periodically have to make queries to different databases and then somehow process the information. I am not an analyst and Quicker is not intended for analysts. The main use of the library is to quickly execute raw SQL queries into the database. You may want to do this entirely in the Python interactive shell. Quicker interface is as simple as possible, thanks to which lazy system administrators can now effortlessly extract data from the database. Quicker interface is as simple as possible, thanks to which lazy system administrators can now effortlessly extract data from the database.
Of course, this library **should not be used in production**. This is just a small assistant in routine tasks. Of course, this library **should not be used in production**. This is just a small assistant in routine tasks.
@ -135,7 +135,7 @@ logging.basicConfig(level=logging.DEBUG)
## Direct access to Cursor object ## Direct access to Cursor object
```python ```python
from quicker import Connection, make_list from quicker import Connection, mklist
# config declaration here... # config declaration here...
@ -144,5 +144,5 @@ with Connection(**config) as db:
users = db.cursor.fetchall() users = db.cursor.fetchall()
# Note: "users" is tuple! Convert it to list of dicts! # Note: "users" is tuple! Convert it to list of dicts!
colnames = [desc[0] for desc in db.cursor.description] colnames = [desc[0] for desc in db.cursor.description]
users_list = make_list(colnames, users) users_list = mklist(colnames, users)
``` ```

View File

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "quicker" name = "quicker"
version = "0.2.0" version = "0.3.0"
description = "Query databases quickly." description = "Query databases quickly."
authors = ["ge <ge@nixhacks.net>"] authors = ["ge <ge@nixhacks.net>"]
license = "Unlicense" license = "Unlicense"

View File

@ -1,3 +1,13 @@
#
# .88888. oo dP
# d8' `8b 88
# 88 88 dP dP dP .d8888b. 88 .dP .d8888b. 88d888b.
# 88 db 88 88 88 88 88' `"" 88888" 88ooood8 88' `88
# Y8. Y88P 88. .88 88 88. ... 88 `8b. 88. ... 88
# `8888PY8b `88888P' dP `88888P' dP `YP `88888P' dP
#
# Quicker -- pythonic tool for querying databases.
import logging import logging
import importlib.util import importlib.util
from enum import Enum from enum import Enum
@ -21,10 +31,12 @@ def _import(module_name: str, symbol: Optional[str] = None):
return module return module
def make_list( def mklist(column_names: List[str], rows: Union[tuple, Tuple[dict, ...]]) -> List[dict]:
column_names: List[str], rows: Union[tuple, Tuple[dict, ...]] """
) -> List[dict]: Convert output to list of dicts from tuples. `rows` can be
"""Convert output to list of dicts from tuples.""" default tuple or tuple of dicts if MySQL provider is used with
MySQLdb.cursors.DictCursor cursor class.
"""
data = [] data = []
for row in rows: for row in rows:
if isinstance(row, dict): if isinstance(row, dict):
@ -38,7 +50,6 @@ def make_list(
class Provider(str, Enum): class Provider(str, Enum):
MYSQL = 'mysql' MYSQL = 'mysql'
POSTGRES = 'postgres' POSTGRES = 'postgres'
SQLITE = 'sqlite' SQLITE = 'sqlite'
@ -55,26 +66,17 @@ class Connection:
>>> >>>
""" """
def __init__(self, def __init__(self, provider: Provider, commit: bool = True, **kwargs):
provider: Provider = None,
commit: bool = True,
**kwargs
):
if not provider:
raise ValueError('Database provider is not set')
self._provider = Provider(provider) self._provider = Provider(provider)
self._commit = commit self._commit = commit
self._connection_args = kwargs self._connection_args = kwargs
def __enter__(self):
logger.debug(f'Database provider={self._provider}') logger.debug(f'Database provider={self._provider}')
# -- MySQL / MariaDB --
if self._provider == Provider.MYSQL: if self._provider == Provider.MYSQL:
MySQLdb = _import('MySQLdb') MySQLdb = _import('MySQLdb')
DictCursor = _import('MySQLdb.cursors', 'DictCursor') DictCursor = _import('MySQLdb.cursors', 'DictCursor')
try: try:
if self._connection_args['cursorclass']: cursorclass = self._connection_args.pop('cursorclass')
cursorclass = DictCursor
except KeyError: except KeyError:
cursorclass = DictCursor cursorclass = DictCursor
self._connection = MySQLdb.connect( self._connection = MySQLdb.connect(
@ -83,23 +85,35 @@ class Connection:
) )
logger.debug('Session started') logger.debug('Session started')
self._cursor = self._connection.cursor() self._cursor = self._connection.cursor()
return Query(self) self._queryobj = Query(self)
# -- PostgreSQL --
if self._provider == Provider.POSTGRES: if self._provider == Provider.POSTGRES:
psycopg2 = _import('psycopg2') psycopg2 = _import('psycopg2')
dbname = self._connection_args.pop('database') dbname = self._connection_args.pop('database')
self._connection_args['dbname'] = dbname self._connection_args['dbname'] = dbname
self._connection = psycopg2.connect(**self._connection_args) self._connection = psycopg2.connect(**self._connection_args)
self._cursor = self._connection.cursor() self._cursor = self._connection.cursor()
return Query(self) self._queryobj = Query(self)
if self._provider == Provider.SQLITE:
sqlite3 = _import('sqlite3') # Python may built without SQLite
self._connection = sqlite3.connect(**self._connection_args)
self._cursor = self._connection.cursor()
self._queryobj = Query(self)
def __enter__(self):
return self._queryobj
def __exit__(self, exception_type, exception_value, exception_traceback): def __exit__(self, exception_type, exception_value, exception_traceback):
self.close()
def close(self):
if self._commit: if self._commit:
logger.debug('Commiting changes into database')
self._connection.commit() self._connection.commit()
logger.debug('Changes commited into database') logger.debug('Closing cursor and connection')
self._cursor.close() self._cursor.close()
self._connection.close() self._connection.close()
logger.debug('Connection closed')
class Query: class Query:
@ -128,13 +142,17 @@ class Query:
self._fetchall = self._cursor.fetchall() self._fetchall = self._cursor.fetchall()
except pgProgrammingError as e: except pgProgrammingError as e:
self._fetchall = None self._fetchall = None
if self._provider == Provider.SQLITE:
self._cursor.execute(*args, **kwargs)
logger.debug(f'sqlite3 ran: {args}')
self._fetchall = self._cursor.fetchall()
if self._fetchall is not None: if self._fetchall is not None:
self._colnames = [] self._colnames = []
if self._cursor.description is not None: if self._cursor.description is not None:
self._colnames = [ self._colnames = [
desc[0] for desc in self._cursor.description desc[0] for desc in self._cursor.description
] ]
return make_list(self._colnames, self._fetchall) return mklist(self._colnames, self._fetchall)
return None return None
def commit(self) -> None: def commit(self) -> None:

View File

@ -1,9 +0,0 @@
# ____ _ __
# / __ \__ __(_)____/ /_____ _____
# / / / / / / / / ___/ //_/ _ \/ ___/
# / /_/ / /_/ / / /__/ ,< / __/ /
# \___\_\__,_/_/\___/_/|_|\___/_/
#
# Quicker -- pythonic tool for querying databases
from .main import Connection, make_list