Source code for pyensae.sql.sql_interface
# -*- coding: utf-8 -*-
"""
Abstract class to connect to a SQL server using various way.
It will be used to implement magic functions
:githublink:`%|py|7`
"""
import re
import sqlite3
[docs]class InterfaceSQLException(BaseException):
"""
a specific exception
:githublink:`%|py|16`
"""
[docs] def __init__(self, message):
"""
:param message: exception message
:githublink:`%|py|21`
"""
self.message = message
[docs] def __str__(self):
"""
usual
:githublink:`%|py|27`
"""
return self.message
[docs]class AutoCompletionSQLObject:
"""
a simple class which allows auto completion
for tables, columns...
:githublink:`%|py|36`
"""
[docs] def __init__(self, name):
"""
creates an instance with a given name
:githublink:`%|py|41`
"""
self._true_name = name
self._filt_name = AutoCompletionSQLObject._filter_name(name)
[docs] @staticmethod
def _filter_name(name):
"""
removes unavailable characters
:githublink:`%|py|49`
"""
return name.replace(".", "_").replace(" ", "_")
@property
def _(self):
"""
returns the true name of the object
:githublink:`%|py|56`
"""
return self._true_name
@property
def _f(self):
"""
returns the filtered name
:githublink:`%|py|63`
"""
return self._filt_name
[docs] def _add(self, name):
"""
add a subname to the class
:param name: string
:return: an AutoCompletionSQLObject
the filtered name (``_f``) of the new object will
be added to ``self.__dict__``, if an object
already exists with the same name, it will raise an exception
:githublink:`%|py|76`
"""
au = AutoCompletionSQLObject(name)
af = au._f
if af in self.__dict__:
raise KeyError(
"the object %s was already added to %s" %
(af, self._f))
self.__dict__[af] = au
return au
[docs]class InterfaceSQL:
"""
Abstract class to connect to a SQL server using various way.
It will be used to implement magic functions
:githublink:`%|py|92`
"""
[docs] @staticmethod
def create(obj):
"""
:param obj: a filename, a connection string, ...
``obj`` can be a:
* file --> the class :class:`Database <pyensae.sql.database_main.Database>` will be used, we assume this file
is sqlite database, the file does not have to exist, in that case, it will created
* sqlite3.Connection --> the object will be wrapped into a :class:`Database <pyensae.sql.database_main.Database>`
* InterfaceSQL --> returns the object itself
.. versionchanged:: 1.1
Parameter *dbfile* can be of type `sqlite3.Connection <https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection>`_.
:githublink:`%|py|109`
"""
if isinstance(obj, str):
from .sql_interface_database import InterfaceSQLDatabase
return InterfaceSQLDatabase(obj)
elif isinstance(obj, sqlite3.Connection):
from .sql_interface_database import InterfaceSQLDatabase
return InterfaceSQLDatabase(obj)
elif isinstance(obj, InterfaceSQL):
return obj
else:
raise NotImplementedError(
"nothing is implemented for type: %s" % str(
type(obj)))
[docs] def populate_completion(self):
"""
the method create an object which contains a class
the user could use to speed the typing SQL queries,
functions in a notebook
This object will added with the name ``CC``,
it is returned by the function.
:return: :class:`AutoCompletionSQLObject <pyensae.sql.sql_interface.AutoCompletionSQLObject>`
The method should be called when the method :meth:`connect`
is called.
:githublink:`%|py|136`
"""
self.CC = AutoCompletionSQLObject("TBL")
tbls = self.get_table_list()
for tb in tbls:
compl = self.CC._add(tb)
cols = self.get_table_columns(tb)
for k, v in cols.items():
compl._add(v[0])
return self.CC
[docs] def __init__(self, obj):
"""
Initializes the object.
:param obj: anything, see below
``obj`` can be a:
* file --> the class :class:`Database <pyensae.sql.database_main.Database>` will be used, we assume this file
is sqlite database, the file does not have to exist, in that case, it will created
:githublink:`%|py|155`
"""
raise NotImplementedError()
[docs] def connect(self):
"""
connection to the database
:githublink:`%|py|161`
"""
raise NotImplementedError()
[docs] def close(self):
"""
close the connection to the database
:githublink:`%|py|167`
"""
raise NotImplementedError()
[docs] def get_table_list(self):
"""
returns the list of tables in the database
:return: list of strings
:githublink:`%|py|175`
"""
raise NotImplementedError()
[docs] def get_table_columns(self, table_name, as_dict=True):
"""
returns the list of columns in a table
:param table_name: table name
:param as_dict: True, as dictionary, as a list otherwise
:return: dictionary { "column": (position, type) } or a list
:githublink:`%|py|185`
"""
raise NotImplementedError()
[docs] def execute(self, sql_query):
"""
execute a SQL query
:param sql_query: query to execute
:return: pandas DataFrame
The function takes care of the unexpected syntax introduction
by the autocompletion object: it just replaces
``DB.CC.<name>`` by the ``true_name``.
:githublink:`%|py|198`
"""
sql_query = self.process_query(sql_query)
return self.execute_clean_query(sql_query)
[docs] def execute_clean_query(self, sql_query):
"""
The function does the same thing as :meth:`execute`
but it does not replace autocompletion object.
It is this function which should be overloaded by
classes inheriting from this one.
:param sql_query: query to execute
:return: pandas DataFrame
:githublink:`%|py|211`
"""
raise NotImplementedError()
_exp = re.compile("(DB[.]CC[.][a-zA-Z0-9_]+([.][a-zA-Z0-9_]+)*)")
[docs] def process_query(self, sql_query):
"""
replaces autocompletion object by their real names
:param sql_query: SQL query
:return: clean sql_query
:githublink:`%|py|222`
"""
# i don't remember the syntax but it should be replaced using regular expression, not
# string replace
fi = InterfaceSQL._exp.findall(sql_query)
if len(fi) > 0:
only = [_[0] for _ in fi]
only.sort(reverse=True)
for o in only:
co = "self." + o[3:]
ev = eval(co)
sql_query = sql_query.replace(o, ev._)
return sql_query
[docs] def import_flat_file(self, filename, table_name):
"""
import a flat file as a table, we assume the columns
separator is ``\\t`` and the file name contains a header
:param filename: filename
:param table: table name
:return: the number of added rows
:githublink:`%|py|243`
"""
raise NotImplementedError()
[docs] def drop_table(self, table_name):
"""
drops a table
:param table: table name
:githublink:`%|py|251`
"""
raise NotImplementedError()
[docs] def refresh_completion(self):
"""
refresh the auto completion
:return: completion object
:githublink:`%|py|259`
"""
return self.populate_completion()
[docs] def add_function(self, code_function):
"""
add a function to the database which can be called in a SELECT statement
:param code_function: pointer to the function
:githublink:`%|py|267`
"""
raise NotImplementedError()
[docs] def import_dataframe(self, tablename, df):
"""
import a dataframe into the database
:param tablename: name of the table
:param df: dataframe
:return: the number of added rows
:githublink:`%|py|277`
"""
raise NotImplementedError()