summaryrefslogtreecommitdiff
path: root/sqlplain
diff options
context:
space:
mode:
authormichele.simionato <devnull@localhost>2008-12-29 06:00:23 +0000
committermichele.simionato <devnull@localhost>2008-12-29 06:00:23 +0000
commitcc2fea83562bc9b73d19b53584e07eee676d91fa (patch)
tree47f53a05160a89180defdc598551c7a76dbeeede /sqlplain
parent6c5dc2dd4d7e0da06db0044c8a854df6483f13cd (diff)
downloadmicheles-cc2fea83562bc9b73d19b53584e07eee676d91fa.tar.gz
Refactoring of sqlplain (removed connmethod and made tables more OO)
Diffstat (limited to 'sqlplain')
-rw-r--r--sqlplain/automatize.py16
-rw-r--r--sqlplain/configurator.py83
-rw-r--r--sqlplain/connection.py33
-rw-r--r--sqlplain/doc/book_importer.py46
-rw-r--r--sqlplain/doc/book_importer.py~37
-rw-r--r--sqlplain/doc/books1.sql41
-rw-r--r--sqlplain/doc/books1.sql~32
-rw-r--r--sqlplain/doc/cache_ex.py~28
-rw-r--r--sqlplain/doc/compare_tables.py32
-rw-r--r--sqlplain/doc/compare_tables.py~32
-rw-r--r--sqlplain/doc/doc.py124
-rw-r--r--sqlplain/doc/first-page.txt125
-rw-r--r--sqlplain/doc/first-page.txt~138
-rw-r--r--sqlplain/doc/logtable.py14
-rw-r--r--sqlplain/doc/logtable.py~12
-rw-r--r--sqlplain/doc/queries.py~5
-rw-r--r--sqlplain/doc/tables.py90
-rw-r--r--sqlplain/doc/tables.py~92
-rw-r--r--sqlplain/mssql_util.py3
-rw-r--r--sqlplain/postgres_util.py4
-rw-r--r--sqlplain/sqlite_util.py3
-rw-r--r--sqlplain/table.py159
-rw-r--r--sqlplain/util.py11
23 files changed, 888 insertions, 272 deletions
diff --git a/sqlplain/automatize.py b/sqlplain/automatize.py
index 8b1d267..0aded29 100644
--- a/sqlplain/automatize.py
+++ b/sqlplain/automatize.py
@@ -8,7 +8,7 @@ Chunk = namedtuple('Chunk', 'version fname code')
try:
CalledProcessError = subprocess.CalledProcessError
-except AttributeError:
+except AttributeError: # Python < 2.5
class CalledProcessError(Exception): pass
def getoutput(commandlist):
@@ -44,6 +44,20 @@ def make_db(alias=None, uri=None, dir=None):
db.execute(chunk.code)
elif chunk.fname.endswith('.py'):
exec chunk.code in {}
+
+def populate_db(db, dir=None):
+ uri = db.uri
+ if dir is None:
+ alias = configurator.alias[uri]
+ dir = configurator.dir[alias]
+ else:
+ raise TypeError('Please provide a scriptdir')
+ chunks = collect(dir, ('.sql', '.py'))
+ for chunk in chunks:
+ if chunk.fname.endswith('.sql'):
+ db.execute(chunk.code)
+ elif chunk.fname.endswith('.py'):
+ exec chunk.code in {}
def make_schema(alias=None, schema=None, uri=None, dir=None):
if alias is not None and uri is None:
diff --git a/sqlplain/configurator.py b/sqlplain/configurator.py
index 2c7f31b..1e6be43 100644
--- a/sqlplain/configurator.py
+++ b/sqlplain/configurator.py
@@ -20,72 +20,47 @@ import UserDict
from ConfigParser import RawConfigParser # no magic interpolation
from cStringIO import StringIO
-class ConfigObj(UserDict.DictMixin):
- # spaces are ignored and options are case-insensitive
- """Convert a configuration file or string into a dictionary-like object
- sections -> items. For instance
-
- cfg = ConfigObj('''
- [fields]
- title: char(32)
- score: char(6)
- author: char(32)
- date: char(8)
- genre: char(4)
- nation: char(4)
- ''')
- print cfg.fields
- """
- def __init__(self, config_str_or_file):
- if isinstance(config_str_or_file, basestring):
- # dedent and convert into a file-like object
- cfg = StringIO(textwrap.dedent(config_str_or_file))
+class ReadOnlyObject(object):
+ def __init__(self, items):
+ keys = []
+ for name, value in items:
+ if name.startswith('_'):
+ raise TypeError('Inner attributes cannot begin with "_"')
+ object.__setattr__(self, name, value)
+ keys.append(name)
+ object.__setattr__(self, '_dic', dic)
+ object.__setattr__(self, '_keys', keys)
+ def __iter__(self):
+ for name in self._keys:
+ yield name, getattr(self, name)
+ def __contains__(self, name):
+ return name in self._keys
+ def __len__(self):
+ return self(self._keys)
+ def __setattr__(self, name, value):
+ if name in self._dic:
+ raise TypeError('Read-only object!')
else:
- cfg = config_str_or_file # assume cfg is a file-like object\
- try:
- cfp = RawConfigParser()
- cfp.readfp(cfg)
- self._keys = []
- for sect in cfp.sections():
- setattr(self, sect, dict(cfp.items(sect)))
- self._keys.append(sect)
- finally:
- cfg.close()
+ object.__setattr__(self, name, value)
+ def __str__(self):
+ return '\n'.join('%s=%s' % (k, v) for k, v in self)
- def __getitem__(self, name):
- return getattr(self, name)
-
- def iteritems(self):
- for k in self._keys:
- yield k, getattr(self, k)
-
- def keys(self):
- return self._keys
-
- def textrepr(self):
- for sect, settings in self.iteritems():
- yield '\n[%s]\n' % sect
- for k, v in settings.iteritems():
- yield '%s=%s' % (k, v)
-
- def save(self, fname):
- f = file(fname, 'w')
- for line in self.textrepr():
- print >> f, line
- f.close()
-
class _Configurator(object): # singleton
_initialized = False
def _initialize(self):
+ cfp = RawConfigParser()
self._conf_file = os.environ.get(
'SQLPLAIN', os.path.expanduser('~/.sqlplain'))
- self._conf_obj = ConfigObj(file(self._conf_file))
+ cfp.readfp(file(self._conf_file))
+ self._conf_obj = ReadOnlyObject(
+ (sect, ReadOnlyObject(cfp.items(sect)))
+ for sect in cfp.sections())
self._initialized = True
def __getattr__(self, name):
if not self._initialized:
self._initialize()
- return self._conf_obj[name]
+ return getattr(self._conf_obj, name)
configurator = _Configurator()
diff --git a/sqlplain/connection.py b/sqlplain/connection.py
index 2eac05c..63e1087 100644
--- a/sqlplain/connection.py
+++ b/sqlplain/connection.py
@@ -48,7 +48,7 @@ class Transaction(object):
conn.rollback()
class _Storage(object):
- "A place where to store low level connection and cursor"
+ "A place where to store the low level connection and cursor"
@classmethod
def new(cls, connect, args):
@@ -119,7 +119,11 @@ class LazyConnection(object):
self.driver.ProgrammingError,
self.driver.InterfaceError,
self.driver.DatabaseError)
-
+
+ def open(self):
+ "Return the low level underlying connection"
+ return self._storage.conn
+
def _raw_execute(self, cursor, templ, args):
"""
Call a dbapi2 cursor; return the rowcount or a list of tuples,
@@ -152,7 +156,7 @@ class LazyConnection(object):
except self.errors, e: # maybe bad connection
self.close() # reset connection and retry
print '%s, retrying' % e
- return raw_execute(self._curs, templ, args)
+ return raw_execute(self._storage.curs, templ, args)
def execute(self, templ, args=(), ntuple=None, scalar=False):
if self.dbtype == 'mssql':
@@ -171,7 +175,7 @@ class LazyConnection(object):
raise TypeError("Expected %d arguments, got %d: %s" % (
qmarks, len(args), args))
- descr, res = self._execute(self._curs, templ, args)
+ descr, res = self._execute(self._storage.curs, templ, args)
if scalar: # you expect a scalar result
if not res:
raise KeyError(
@@ -181,7 +185,7 @@ class LazyConnection(object):
"Expected to get a scalar result, got %s\nQUERY WAS:%s%s\n"
% (res, templ, args))
return res[0][0]
- cursor = self._curs # needed to make the reset work
+ cursor = self._storage.curs # needed to make the reset work
if self.chatty:
print(cursor.rowcount, templ, args)
if descr:
@@ -206,7 +210,7 @@ class LazyConnection(object):
if d:
sql = string.Template(sql).substitute(d)
if self.dbtype == 'sqlite':
- self._curs.executescript(sql)
+ self._storage.curs.executescript(sql)
else: # psycopg and pymssql are already able to execute chunks
self.execute(sql)
@@ -217,16 +221,6 @@ class LazyConnection(object):
def __repr__(self):
return "<%s %s>" % (self.__class__.__name__, self.uri)
-
- @property
- def _conn(self):
- "Return the low level underlying connection"
- return self._storage.conn
-
- @property
- def _curs(self):
- "Return the low level underlying cursor"
- return self._storage.curs
class TransactionalConnection(LazyConnection):
"""
@@ -235,10 +229,10 @@ class TransactionalConnection(LazyConnection):
"""
def rollback(self):
- return self._conn.rollback()
+ return self._storage.conn.rollback()
def commit(self):
- return self._conn.commit()
+ return self._storage.conn.commit()
def __enter__(self):
return self
@@ -274,8 +268,7 @@ class NullObject(object):
class FakeConnection(object):
def __init__(self, iodict):
self.iodict = iodict
- self._conn = NullObject()
- self._curs = NullObject()
+ self._storage = NullObject()
def execute(self, templ, args=()):
return self.iodict[(templ,) + args]
def executescript(self, templ, *dicts, **kw):
diff --git a/sqlplain/doc/book_importer.py b/sqlplain/doc/book_importer.py
new file mode 100644
index 0000000..8935ec6
--- /dev/null
+++ b/sqlplain/doc/book_importer.py
@@ -0,0 +1,46 @@
+import re, csv, itertools
+from sqlplain.namedtuple import namedtuple
+
+class BookImporter(object):
+
+ # format of the CSV files to be imported
+ BOOKFILE = re.compile('books(\d\d).csv')
+ Book = namedtuple('title author genre nation date')
+
+ def __init__(self, db):
+ self.db = db
+ self.bookid = KTable(db, 'bookid')
+ self.book = KTable(db, 'book')
+ self.score = dict(KTable(db, 'score'))
+ self.genre = KTable(db, 'genre')
+ self.nation = KTable(db, 'nation')
+ self.counter = itertools.count(1)
+
+ def import_books(self, books):
+ for b in books:
+ self.bookid.insert_row(
+ id = self.counter.next(),
+ title = b.title,
+ author = b.author,
+ rdate = b.date)
+ self.book.insert_row(
+ id = id,
+ genre = b.genre,
+ nation = b.nation,
+ score = self.scoredic[b.score])
+
+ def import_all(self, dirname=None):
+ dirname = dirname or self.db.uri.scriptdir
+ for fname in os.listdir(dirname):
+ mo = self.BOOKFILE.match(fname)
+ f = file(os.path.join(dirname, fname))
+ if mo:
+ books = map(self.Book, csv.reader(f))
+ f.close()
+ self.import_books(books)
+
+
+if __name__ == '__main__':
+ db = lazyconnect('bookdb')
+ imp = BookImporter(db)
+ imp.import_all()
diff --git a/sqlplain/doc/book_importer.py~ b/sqlplain/doc/book_importer.py~
new file mode 100644
index 0000000..1398cbe
--- /dev/null
+++ b/sqlplain/doc/book_importer.py~
@@ -0,0 +1,37 @@
+import re, csv, itertools
+from sqlplain.namedtuple import namedtuple
+
+Book = namedtuple('title author genre nation date')
+
+BOOKFILE = re.compile('books(\d\d)')
+
+class BookImporter(object):
+ def __init__(self, db):
+ self.db = db
+ self.bookid = KTable.reflect(db, 'bookid')
+ self.book = KTable.reflect(db, 'book')
+ self.counter = itertools.count(1)
+
+ def import_year(self, books, year):
+ for b in books:
+ self.bookid.insert_row(
+ id = self.counter.next(),
+ title = b.title,
+ author = b.author,
+ rdate = b.date)
+
+ def import_all(self, dirname):
+ n = 0
+ for fname in os.listdir(dirname):
+ mo = BOOKFILE.match(fname)
+ f = file(os.path.join(dirname, fname))
+ if mo:
+ yy = int(mo.group(1))
+ if yy > 80:
+ year = 1900 + yy
+ else:
+ year = 2000 + y
+ rows = map(Book, csv.reader(f))
+ f.close()
+ self.import_year(rows, year)
+
diff --git a/sqlplain/doc/books1.sql b/sqlplain/doc/books1.sql
new file mode 100644
index 0000000..e29f2aa
--- /dev/null
+++ b/sqlplain/doc/books1.sql
@@ -0,0 +1,41 @@
+CREATE TABLE bookid (
+ id INTEGER AUTO_INCREMENT PRIMARY KEY,
+ title VARCHAR(128),
+ author VARCHAR(64),
+ rdate DATETIME,
+ UNIQUE (title, author rdate));
+
+CREATE TABLE score (
+ score VARCHAR(4) PRIMARY KEY,
+ value INTEGER);
+
+INSERT INTO score VALUES ('+', 1);
+INSERT INTO score VALUES ('O', 2);
+INSERT INTO score VALUES ('O+', 3);
+INSERT INTO score VALUES ('OO', 4);
+INSERT INTO score VALUES ('OO+', 5);
+INSERT INTO score VALUES ('OOO', 6);
+INSERT INTO score VALUES ('OOO+', 7);
+INSERT INTO score VALUES ('OOOO', 8);
+
+CREATE TABLE genre (
+ id CHAR(2) PRIMARY KEY,
+ descr VARCHAR(32))
+
+CREATE TABLE nation (
+ id CHAR(2) PRIMARY KEY,
+ descr VARCHAR(32));
+
+CREATE TABLE book (
+ id INTEGER PRIMARY KEY REFERENCES bookid (id),
+ genre CHAR(2) REFERENCES genre (id),
+ nation CHAR(2) REFERENCES nation (id),
+ score INTEGER REFERENCES score (value));
+
+CREATE VIEW book_view AS
+ SELECT a.id, a.title, a.author, a.rdate, b.genre, b.nation, c.score
+ FROM bookid AS a
+ INNER JOIN book AS b
+ ON a.id = b.id
+ INNER JOIN score AS c
+ ON c.value=b.score;
diff --git a/sqlplain/doc/books1.sql~ b/sqlplain/doc/books1.sql~
new file mode 100644
index 0000000..1320689
--- /dev/null
+++ b/sqlplain/doc/books1.sql~
@@ -0,0 +1,32 @@
+CREATE TABLE bookid (
+ id INTEGER AUTO_INCREMENT PRIMARY KEY,
+ title VARCHAR(128),
+ author VARCHAR(64),
+ rdate DATETIME,
+ UNIQUE (title, author rdate));
+
+CREATE TABLE score (
+ score VARCHAR(4) PRIMARY KEY,
+ value INTEGER);
+
+CREATE TABLE genre (
+ id CHAR(2) PRIMARY KEY,
+ descr VARCHAR(32))
+
+CREATE TABLE nation (
+ id CHAR(2) PRIMARY KEY,
+ descr VARCHAR(32));
+
+CREATE TABLE book (
+ id INTEGER PRIMARY KEY REFERENCES bookid (id),
+ genre CHAR(2) REFERENCES genre (id),
+ nation CHAR(2) REFERENCES nation (id),
+ score INTEGER REFERENCES score (value));
+
+CREATE VIEW book_view AS
+ SELECT a.id, a.title, a.author, a.rdate, b.genre, b.nation, c.score
+ FROM bookid AS a
+ INNER JOIN book AS b
+ ON a.id = b.id
+ INNER JOIN score AS c
+ ON c.value=b.score;
diff --git a/sqlplain/doc/cache_ex.py~ b/sqlplain/doc/cache_ex.py~
new file mode 100644
index 0000000..f9b802b
--- /dev/null
+++ b/sqlplain/doc/cache_ex.py~
@@ -0,0 +1,28 @@
+from sqlplain.memoize import Memoize
+
+class Forever(object):
+ "Cache type for caches which are is never cleared"
+
+class LongTime(object):
+ "Cache type for caches which are rarely cleared"
+
+class ShortTime(object):
+ "Cache type for caches which are is often cleared"
+
+@Memoize(ShortTime)
+def f1():
+ return 1
+
+@Memoize(LongTime)
+def f2():
+ return 2
+
+@Memoize(Forever)
+def f3():
+ return 3
+
+def test1():
+ assert not f1.cache
+ f1()
+ assert f1.cache == {(): 1}
+
diff --git a/sqlplain/doc/compare_tables.py b/sqlplain/doc/compare_tables.py
new file mode 100644
index 0000000..89911a0
--- /dev/null
+++ b/sqlplain/doc/compare_tables.py
@@ -0,0 +1,32 @@
+from sqlplain import lazyconnect, do
+from cStringIO import StringIO
+#from sqlplain.postgres_util import get_schema_postgres
+#print get_schema_postgres(rcare.uri, 'customer')
+
+rcare = lazyconnect('rcare')
+rcare_prod = lazyconnect('rcare_prod')
+
+CREATE_CUSTOMER = '''
+CREATE TABLE $customer (
+ client_id character varying(32) PRIMARY KEY,
+ description character varying,
+ email character varying,
+ short_description character varying(16),
+ mail_report_info character varying(128),
+ attach_prefix character varying(32),
+ filter_from character varying(32),
+ zope_id character varying(32),
+ lookup boolean,
+ client_srs character varying(32),
+ CONSTRAINT nnull_id_short_desc CHECK ((short_description IS NOT NULL))
+);
+'''
+
+def copy_from(src, dest, table):
+ out = StringIO()
+ src._curs.copy_to(out, 'customer')
+ drop_table(dest, 'customer_prod', force=True):
+ dest.executescript(CREATE_CUSTOMER, customer='customer_prod')
+ dest._curs.copy_from(out, 'customer_prod')
+ out.close()
+
diff --git a/sqlplain/doc/compare_tables.py~ b/sqlplain/doc/compare_tables.py~
new file mode 100644
index 0000000..f714473
--- /dev/null
+++ b/sqlplain/doc/compare_tables.py~
@@ -0,0 +1,32 @@
+from sqlplain import lazyconnect
+from cStringIO import StringIO
+#from sqlplain.postgres_util import get_schema_postgres
+#print get_schema_postgres(rcare.uri, 'customer')
+
+rcare = lazyconnect('rcare')
+rcare_prod = lazyconnect('rcare_prod')
+
+CREATE_CUSTOMER = '''
+CREATE TABLE customer_prod (
+ client_id character varying(32) PRIMARY KEY,
+ description character varying,
+ email character varying,
+ short_description character varying(16),
+ mail_report_info character varying(128),
+ attach_prefix character varying(32),
+ filter_from character varying(32),
+ zope_id character varying(32),
+ lookup boolean,
+ client_srs character varying(32),
+ CONSTRAINT nnull_id_short_desc CHECK ((short_description IS NOT NULL))
+);
+'''
+
+def copy_from(src, dest, table):
+ out = StringIO()
+ src._curs.copy_to(out, 'customer')
+ if not exists_table(
+ dest.create_table('customer_prod', force=True)
+ dest._curs.copy_from(out, 'customer_prod')
+ out.close()
+
diff --git a/sqlplain/doc/doc.py b/sqlplain/doc/doc.py
index 207abc4..0c9af99 100644
--- a/sqlplain/doc/doc.py
+++ b/sqlplain/doc/doc.py
@@ -68,6 +68,19 @@ plain old SQL queries, without any support from Python.
.. _Django philosophy: http://docs.djangoproject.com/en/dev/misc/design-philosophies/#misc-design-philosophies
+Differences with the DB API
+--------------------------------------------------
+
+sqlplain has a functional feeling.
+i have always hated the DB API 2, particularly the fact that the execute
+method does not return anyything but works by side effects, changing
+the state of the cursor so that the next fetch operation returns the
+retrieved rows. In sqlplain instead the eexecute method returns
+directly the retried rows and there is no explicit concept of cursor.
+This is not a particularly original idea, and actually the sqlite
+driver offers the same functionality. The rows are returned as named
+tuples, not as plain tuples.
+
sqlplain for the impatient
---------------------------------------------------------------
@@ -451,18 +464,9 @@ operation.
If you want to insert a line at the time, you can do so by using the
low level mechanism (
``conn.execute("INSERT INTO mytable VALUES (?, ?, ?)", (r1, r2, r3))``)
-or by using the high level table framework discussed in the next section.
-
-sqlplain: extensions
-=================================================================
+or by using the high level `table framework`_.
-``sqlplain`` is designed as a small core - just a lightweight wrapper
-over the standard DB API 2 interface - and a set of extensions.
-Future versions of ``sqlplain`` may offer additional extensions, but
-for the moment very little is provided. I am committed to keep the
-whole of ``sqlplain`` small - as I said, well under the 5% of the
-codebase of sqlalchemy - so even the extension part is guaranteed to
-stay small in the foreseeable future.
+.. _table framework: tables.html
SQL template functions
--------------------------------------------------------------
@@ -609,97 +613,17 @@ something more sophisticated on top of it, but for the moment it
works well enough for my needs. Future versions of ``sqlplain``
could offer additional functionality for generating SQL templates,
or could not.
-
-The table framework
-------------------------------------------------------------
-
-As I said in the introduction, ``sqlplain`` is not intended to be
-a fully fledged ORM, therefore it does not provide a builtin mechanism
-to map user defined classes to database objects, such as the mapping
-mechanism in SQLAlchemy, or the popular Active Record pattern; nevertheless,
-it provides a table framework which us a lightweight object oriented layer
-over database tables.
-``sqlplain`` table object comes in two flavors: D-tables, which should be
-used for tables without a primary key, and K-tables, which must
-used for tables with a primary key, possibly composite. Actually K-tables
-are D-tables too, since they inherit from D-tables, therefore they
-share all D-tables methods and they add a few methods which are
-meaninful only if the underlying table has a primary key.
-If you try to use a K-table over a database table which has no primary
-key, a ``TypeError`` will be raised. Here is an example:
-
-$$logtable
-
->>> from sqlplain import table
->>> import logtable
->>> db = logtable.init('sqlite_test')
->>> log = table.KTable.reflect(db, 'log')
-Traceback (most recent call last):
- ...
-TypeError: table log has no primary key!
-
-Using a DTable instead works:
-
->>> log = table.DTable.reflect(db, 'log')
-
-The ``.reflect`` classmethod creates a suitable subclass of ``DTable``
-(called ``Log``) and instantiates it. ``DTable`` is a kind of abstract
-base class and you cannot instantiate it directly (the same is true
-for the KTable class):
-
->>> table.DTable(db)
-Traceback (most recent call last):
- ...
-TypeError: You cannot instantiate the ABC DTable
-
-In order to create a concrete subclass of DTable (or KTable) one needs
-to set the tabletuple class attribute ``tt``, which contains information
-about the table structure. The ``.reflect`` method extracts the information
-from the database schema; for instance ``log.tt`` is a namedtuple with
-fields ``date`` and ``message``:
-
->>> print log.tt._fields
-('date', 'message')
-
-
->>> from datetime import datetime
->>> now = datetime.now
->>> log.insert_row(date=now(), message='message1')
-1
->>> log.insert_row(date=now(), message='message2')
-1
->>> print len(log)
-2
-
-Here is the full API for DTable:
-
-- type
-- create
-- reflect
-- select
-- count
-- delete
-- truncate
-- insert_row
-- insert_rows
-- insert_file
-
-Here are the additional methods of K-tables:
-
-- select_row
-- update_row
-- update_or_insert_row
-- delete_row
-
-Instead of an explanation, I will give examples::
- select_book = select_row('book', 'title author')
- select_book(bookdb, title='T', author='A')
+sqlplain: extensions
+=================================================================
-``select_row`` raises an error if the corresponding queries
-returns no result (you are looking for a missing record) or
-if it returns multiple results (it means that your primary key specification
-was incomplete).
+``sqlplain`` is designed as a small core - just a lightweight wrapper
+over the standard DB API 2 interface - and a set of extensions.
+Future versions of ``sqlplain`` may offer additional extensions, but
+for the moment very little is provided. I am committed to keep the
+whole of ``sqlplain`` small - as I said, well under the 5% of the
+codebase of sqlalchemy - so even the extension part is guaranteed to
+stay small in the foreseeable future.
Memoization
-------------------------------------------------------------
diff --git a/sqlplain/doc/first-page.txt b/sqlplain/doc/first-page.txt
new file mode 100644
index 0000000..19f9521
--- /dev/null
+++ b/sqlplain/doc/first-page.txt
@@ -0,0 +1,125 @@
+sqlplain: a simple and opinionated Databases Toolkit
+====================================================================
+
+sqlplain is a small Python library with the aim of simplifying your
+interaction with relation databases.
+
+sqlplain is very much opinionated and it
+does not try to be everything to everybody. It is intended to be a
+middle level toolkit: higher level than the DB API 2, but lower
+level than SQLAlchemy.
+
+It was written in part as a reaction to SQLAlchemy (only in part since I
+wrote the core of sqlplain years ago, before the publication of SQLAlchemy).
+SQLAlchemy is actually
+an impressive piece of software but I think it is overkill for many users.
+sqlplain is for users looking for a simple library. It is also fine for
+database administrators with a lot of experience in SQL and little
+experience in Python, since its basic mode of operation is just to run
+plain old SQL scripts (hence the name).
+
+Its design guidelines are the following:
+
+- conciseness
+
+sqlplain is committed to keep the codebase small (say under 2000 lines)
+and the number of features limited;
+
+- simplicity
+
+sqlplain is based on a simple
+implementation, to make it easy for its users to understand it and to
+build the features they need;
+
+- easyness of use
+
+sqlplain has a small learning curve
+and provides a small set of convenient utilities for scripting your
+database interation;
+
+- familiarity
+
+sqlplain connections are a simplified version of standard
+DB API 2 connections, and you can readily get the underlying low
+level connection to use of all its power, if need there is;
+
+- transparency
+
+sqlplain tries hard not to add too many layers of indirection to your
+SQL queries: the idea is that it should be easy for the user to extract
+the plain old SQL query from the code, cut and paste it on the database
+console, and try it out directly.
+
+sqlplain can be used to do anything on a database, but there is special
+support for the following areas:
+
+- creating test databases
+- running database performance tests
+- introspecting pre-existing databases
+- scripting database operations
+- memoizing queries
+
+You can use sqlplain with your Web framework of choice (I have
+been running it with Pylons for years without problems) but there
+is no special integration with any framework - this is an advantage
+or a disadvantage depending on your point of view. Certainly sqlplain
+is *not* intended to be used as the Django ORM. Django (as most
+Python ORMs) puts the emphasis on Python, in the sense that the
+model is written down as a set of Python classes which are then
+persistent to the database. On the contrary, sqlplain assumes
+that you already have your tables on the database and that they
+are managed via DDL queries: you can introspect your tables
+from Python, but if you want to look at their definitions you
+must look into SQL code, not into Python code. There is no
+facility to create tables from Python code (actually there is,
+but it works by just passing plain SQL code to the creational procedure)
+and no facility to modify the schema of an existing table from
+Python (apart for passing raw "ALTER TABLE" queries to the connection
+object).
+
+sqlplain does provide limited support for the following areas:
+
+- database independence
+
+True database-independence is a dream, since the features and the
+performance characteristics provided by different databases are
+totally different. Therefore, sqlplain does not even attempt to
+be a fully database-independent toolkit. However, in the cases where
+I could keep database-independence with little effort, I kept it.
+In particular the introspection functionalities are database-independent.
+But in general I judge spending a lot of time to provide a false sensation
+of database independence not worth the effort.
+
+- programmatic generation of SQL queries
+
+Some database toolkits (notably SQLAlchemy) have an incredibly sophisticated
+mechanism to generate SQL queries from Python objects in a
+database-independent way. That is not the goal of
+sqlplain. Out of the box sqlplain provides no support at all for generating
+SQL queries programmatically, however the documentation provides a few
+do-it-yourself recipes.
+
+- Object Relation Mapper
+
+The core of sqlplain does not provide any ORM. A small table framework
+is provided as an experimental extension: its goal is to provide a small
+object oriented wrapper over the tables of database, but it is not an
+ORM such as the ones you can find in SQLAlchemy, SQLObject or Storm.
+It is pretty easy to write custom classes making use of the table
+framework, but you are on your own, there is nothing built-in.
+This is done on purpose.
+
+- Connection pooling
+
+sqlplain does not provide any connection pooling facility and it will never
+provide one. There are already many connection pool implementations available
+out there and just use the one you like, if you need one.
+Notice that you can use sqlplain connections in a multithreaded application
+(such as a Web server) even without an explicit connection pool since the
+connections can be made thread local.
+
+- no support for nested transactions, no unit-of-work pattern.
+
+
+
+
diff --git a/sqlplain/doc/first-page.txt~ b/sqlplain/doc/first-page.txt~
new file mode 100644
index 0000000..3e0fe47
--- /dev/null
+++ b/sqlplain/doc/first-page.txt~
@@ -0,0 +1,138 @@
+sqlplain: a simple and opinionated Databases Toolkit
+====================================================================
+
+sqlplain is a small Python library with the aim of simplifying your
+interaction with relation databases.
+
+sqlplain is very much opinionated and it
+does not try to be everything to everybody. It is intended to be a
+middle level toolkit: higher level than the DB API 2, but lower
+level than SQLAlchemy.
+
+Differences with the DB API
+--------------------------------------------------
+
+sqlplain has a functional feeling.
+i have always hated the DB API 2, particularly the fact that the execute
+method does not return anyything but works by side effects, changing
+the state of the cursor so that the next fetch operation returns the
+retrieved rows. In sqlplain instead the eexecute method returns
+directly the retried rows and there is no explicit concept of cursor.
+This is not a particularly original idea, and actually the sqlite
+driver offers the same functionality. The rows are returned as named
+tuples, not as raw tuples.
+
+It was written in part as a reaction to SQLAlchemy (only in part since I
+wrote the core of sqlplain years ago, before the publication of SQLAlchemy).
+SQLAlchemy is actually
+an impressive piece of software but I think it is overkill for many users.
+sqlplain is for users looking for a simple library. It is also fine for
+database administrators with a lot of experience in SQL and little
+experience in Python, since its basic mode of operation is just to run
+plain old SQL scripts (hence the name).
+
+Its design guidelines are the following:
+
+- conciseness
+
+sqlplain is committed to keep the codebase small (say under 2000 lines)
+and the number of features limited;
+
+- simplicity
+
+sqlplain is based on a simple
+implementation, to make it easy for its users to understand it and to
+build the features they need;
+
+- easyness of use
+
+sqlplain has a small learning curve
+and provides a small set of convenient utilities for scripting your
+database interation;
+
+- familiarity
+
+sqlplain connections are a simplified version of standard
+DB API 2 connections, and you can readily get the underlying low
+level connection to use of all its power, if need there is;
+
+- transparency
+
+sqlplain tries hard not to add too many layers of indirection to your
+SQL queries: the idea is that it should be easy for the user to extract
+the plain old SQL query from the code, cut and paste it on the database
+console, and try it out directly.
+
+sqlplain can be used to do anything on a database, but there is special
+support for the following areas:
+
+- creating test databases
+- running database performance tests
+- introspecting pre-existing databases
+- scripting database operations
+- memoizing queries
+
+You can use sqlplain with your Web framework of choice (I have
+been running it with Pylons for years without problems) but there
+is no special integration with any framework - this is an advantage
+or a disadvantage depending on your point of view. Certainly sqlplain
+is *not* intended to be used as the Django ORM. Django (as most
+Python ORMs) puts the emphasis on Python, in the sense that the
+model is written down as a set of Python classes which are then
+persistent to the database. On the contrary, sqlplain assumes
+that you already have your tables on the database and that they
+are managed via DDL queries: you can introspect your tables
+from Python, but if you want to look at their definitions you
+must look into SQL code, not into Python code. There is no
+facility to create tables from Python code (actually there is,
+but it works by just passing plain SQL code to the creational procedure)
+and no facility to modify the schema of an existing table from
+Python (apart for passing raw "ALTER TABLE" queries to the connection
+object).
+
+sqlplain does provide limited support for the following areas:
+
+- database independence
+
+True database-independence is a dream, since the features and the
+performance characteristics provided by different databases are
+totally different. Therefore, sqlplain does not even attempt to
+be a fully database-independent toolkit. However, in the cases where
+I could keep database-independence with little effort, I kept it.
+In particular the introspection functionalities are database-independent.
+But in general I judge spending a lot of time to provide a false sensation
+of database independence not worth the effort.
+
+- programmatic generation of SQL queries
+
+Some database toolkits (notably SQLAlchemy) have an incredibly sophisticated
+mechanism to generate SQL queries from Python objects in a
+database-independent way. That is not the goal of
+sqlplain. Out of the box sqlplain provides no support at all for generating
+SQL queries programmatically, however the documentation provides a few
+do-it-yourself recipes.
+
+- Object Relation Mapper
+
+The core of sqlplain does not provide any ORM. A small table framework
+is provided as an experimental extension: its goal is to provide a small
+object oriented wrapper over the tables of database, but it is not an
+ORM such as the ones you can find in SQLAlchemy, SQLObject or Storm.
+It is pretty easy to write custom classes making use of the table
+framework, but you are on your own, there is nothing built-in.
+This is done on purpose.
+
+- Connection pooling
+
+sqlplain does not provide any connection pooling facility and it will never
+provide one. There are already many connection pool implementations available
+out there and just use the one you like, if you need one.
+Notice that you can use sqlplain connections in a multithreaded application
+(such as a Web server) even without an explicit connection pool since the
+connections can be made thread local.
+
+- no support for nested transactions, no unit-of-work pattern.
+
+
+
+
diff --git a/sqlplain/doc/logtable.py b/sqlplain/doc/logtable.py
new file mode 100644
index 0000000..9928ccd
--- /dev/null
+++ b/sqlplain/doc/logtable.py
@@ -0,0 +1,14 @@
+from datetime import datetime
+from sqlplain import do, lazyconnect, util, table
+
+create_log_table = do("""
+CREATE TABLE log(
+ date DATETIME,
+ message VARCHAR(255))
+""")
+
+def init(db_uri):
+ db = lazyconnect(db_uri)
+ util.drop_table(db, 'log', force=True)
+ create_log_table(db)
+ return db
diff --git a/sqlplain/doc/logtable.py~ b/sqlplain/doc/logtable.py~
new file mode 100644
index 0000000..50713a8
--- /dev/null
+++ b/sqlplain/doc/logtable.py~
@@ -0,0 +1,12 @@
+from sqlplain import do, lazyconnect, util
+
+create_log_table = do("""
+CREATE TABLE log(
+ date DATETIME,
+ message VARCHAR(255))
+""")
+
+if __name__ == '__main__':
+ db = lazyconnect('sqlite_test')
+ util.drop_table(db, 'log', force=True)
+ create_log_table(db)
diff --git a/sqlplain/doc/queries.py~ b/sqlplain/doc/queries.py~
new file mode 100644
index 0000000..e3f0dd9
--- /dev/null
+++ b/sqlplain/doc/queries.py~
@@ -0,0 +1,5 @@
+from sqlplain import do
+
+get_authors = do('select author from book')
+get_titles = do('select title from book where author=?')
+set_uppercase_titles = do('UPDATE book SET title=upper(title)')
diff --git a/sqlplain/doc/tables.py b/sqlplain/doc/tables.py
new file mode 100644
index 0000000..470ffa7
--- /dev/null
+++ b/sqlplain/doc/tables.py
@@ -0,0 +1,90 @@
+"""
+The table framework
+------------------------------------------------------------
+
+As I said in the introduction, ``sqlplain`` is not intended to be
+a fully fledged ORM, therefore it does not provide a builtin mechanism
+to map user defined classes to database objects, such as the mapping
+mechanism in SQLAlchemy, or the popular Active Record pattern; nevertheless,
+it provides a table framework which us a lightweight object oriented layer
+over database tables.
+``sqlplain`` table object comes in two flavors: D-tables, which should be
+used for tables without a primary key, and K-tables, which must
+used for tables with a primary key, possibly composite. Actually K-tables
+are D-tables too, since they inherit from D-tables, therefore they
+share all D-tables methods and they add a few methods which are
+meaninful only if the underlying table has a primary key.
+If you try to use a K-table over a database table which has no primary
+key, a ``TypeError`` will be raised. Here is an example:
+
+$$logtable
+
+>>> from sqlplain import table
+>>> import logtable
+>>> db = logtable.init('sqlite_test')
+>>> log = table.KTable.reflect(db, 'log')
+Traceback (most recent call last):
+ ...
+TypeError: table log has no primary key!
+
+Using a DTable instead works:
+
+>>> log = table.DTable.reflect(db, 'log')
+
+The ``.reflect`` classmethod creates a suitable subclass of ``DTable``
+(called ``Log``) and instantiates it. ``DTable`` is a kind of abstract
+base class and you cannot instantiate it directly (the same is true
+for the KTable class):
+
+>>> table.DTable(db)
+Traceback (most recent call last):
+ ...
+TypeError: You cannot instantiate the ABC DTable
+
+In order to create a concrete subclass of DTable (or KTable) one needs
+to set the tabletuple class attribute ``tt``, which contains information
+about the table structure. The ``.reflect`` method extracts the information
+from the database schema; for instance ``log.tt`` is a namedtuple with
+fields ``date`` and ``message``:
+
+>>> print log.tt._fields
+('date', 'message')
+
+
+>>> from datetime import datetime
+>>> now = datetime.now
+>>> log.insert_row(date=now(), message='message1')
+1
+>>> log.insert_row(date=now(), message='message2')
+1
+>>> print len(log)
+2
+
+Here is the full API for DTable:
+
+- create
+- select
+- count
+- delete
+- truncate
+- insert_row
+- insert_rows
+- insert_file
+
+Here are the additional functions of K-tables:
+
+- select_row
+- update_row
+- update_or_insert_row
+- delete_row
+
+Instead of an explanation, I will give examples::
+
+ select_book = select_row('book', 'title author')
+ select_book(bookdb, title='T', author='A')
+
+``select_row`` raises an error if the corresponding queries
+returns no result (you are looking for a missing record) or
+if it returns multiple results (it means that your primary key specification
+was incomplete).
+"""
diff --git a/sqlplain/doc/tables.py~ b/sqlplain/doc/tables.py~
new file mode 100644
index 0000000..6f3f401
--- /dev/null
+++ b/sqlplain/doc/tables.py~
@@ -0,0 +1,92 @@
+"""
+The table framework
+------------------------------------------------------------
+
+As I said in the introduction, ``sqlplain`` is not intended to be
+a fully fledged ORM, therefore it does not provide a builtin mechanism
+to map user defined classes to database objects, such as the mapping
+mechanism in SQLAlchemy, or the popular Active Record pattern; nevertheless,
+it provides a table framework which us a lightweight object oriented layer
+over database tables.
+``sqlplain`` table object comes in two flavors: D-tables, which should be
+used for tables without a primary key, and K-tables, which must
+used for tables with a primary key, possibly composite. Actually K-tables
+are D-tables too, since they inherit from D-tables, therefore they
+share all D-tables methods and they add a few methods which are
+meaninful only if the underlying table has a primary key.
+If you try to use a K-table over a database table which has no primary
+key, a ``TypeError`` will be raised. Here is an example:
+
+$$logtable
+
+>>> from sqlplain import table
+>>> import logtable
+>>> db = logtable.init('sqlite_test')
+>>> log = table.KTable.reflect(db, 'log')
+Traceback (most recent call last):
+ ...
+TypeError: table log has no primary key!
+
+Using a DTable instead works:
+
+>>> log = table.DTable.reflect(db, 'log')
+
+The ``.reflect`` classmethod creates a suitable subclass of ``DTable``
+(called ``Log``) and instantiates it. ``DTable`` is a kind of abstract
+base class and you cannot instantiate it directly (the same is true
+for the KTable class):
+
+>>> table.DTable(db)
+Traceback (most recent call last):
+ ...
+TypeError: You cannot instantiate the ABC DTable
+
+In order to create a concrete subclass of DTable (or KTable) one needs
+to set the tabletuple class attribute ``tt``, which contains information
+about the table structure. The ``.reflect`` method extracts the information
+from the database schema; for instance ``log.tt`` is a namedtuple with
+fields ``date`` and ``message``:
+
+>>> print log.tt._fields
+('date', 'message')
+
+
+>>> from datetime import datetime
+>>> now = datetime.now
+>>> log.insert_row(date=now(), message='message1')
+1
+>>> log.insert_row(date=now(), message='message2')
+1
+>>> print len(log)
+2
+
+Here is the full API for DTable:
+
+- type
+- create
+- reflect
+- select
+- count
+- delete
+- truncate
+- insert_row
+- insert_rows
+- insert_file
+
+Here are the additional methods of K-tables:
+
+- select_row
+- update_row
+- update_or_insert_row
+- delete_row
+
+Instead of an explanation, I will give examples::
+
+ select_book = select_row('book', 'title author')
+ select_book(bookdb, title='T', author='A')
+
+``select_row`` raises an error if the corresponding queries
+returns no result (you are looking for a missing record) or
+if it returns multiple results (it means that your primary key specification
+was incomplete).
+"""
diff --git a/sqlplain/mssql_util.py b/sqlplain/mssql_util.py
index baad580..da6b865 100644
--- a/sqlplain/mssql_util.py
+++ b/sqlplain/mssql_util.py
@@ -7,6 +7,9 @@ def insert_file_mssql(uri, fname, table, sep='\t'):
return conn.execute(
'BULK INSERT %s FROM ? SEPARATOR ?' % table, (fname, sep))
+def get_tables(conn):
+ return [r.name for r in conn.execute('SELECT name FROM sysobjects')]
+
def exists_table_mssql(conn, tname):
return conn.execute('SELECT count(*) FROM sysobjects WHERE name=?',
(tname,), scalar=True)
diff --git a/sqlplain/postgres_util.py b/sqlplain/postgres_util.py
index 9045791..a1fe5a4 100644
--- a/sqlplain/postgres_util.py
+++ b/sqlplain/postgres_util.py
@@ -21,6 +21,10 @@ def drop_db_postgres(uri):
openclose(uri.copy(database='template1'),
'DROP DATABASE %(database)s' % uri)
+
+def get_tables_postgres(conn):
+ return [r.name for r in conn.execute('SELECT name FROM pg_tables')]
+
def exists_table_postgres(conn, tname):
return conn.execute('SELECT count(*) FROM pg_tables WHERE name=?',
tname, scalar=True)
diff --git a/sqlplain/sqlite_util.py b/sqlplain/sqlite_util.py
index b304814..0c1d33f 100644
--- a/sqlplain/sqlite_util.py
+++ b/sqlplain/sqlite_util.py
@@ -21,6 +21,9 @@ def insert_file_sqlite(conn, fname, tname, sep):
def get_kfields_sqlite(conn, tname):
return [x.name for x in get_info(conn, tname) if x.pk]
+def get_tables_sqlite(conn):
+ return [r.name for r in conn.execute('PRAGMA table_info')]
+
def exists_table_sqlite(conn, tname):
res = conn.execute('PRAGMA table_info(%s)' % tname)
return res != -1
diff --git a/sqlplain/table.py b/sqlplain/table.py
index e6d13c0..7e50d29 100644
--- a/sqlplain/table.py
+++ b/sqlplain/table.py
@@ -1,8 +1,6 @@
import sys, string
from sqlplain import util, do
from sqlplain.namedtuple import namedtuple
-from sqlplain.connection import connmethod
-
# kfields and dfields must be tuples, not strings
def tabletuple(name, kfields, dfields):
@@ -21,8 +19,7 @@ def tabletuple(name, kfields, dfields):
lambda self: dtuple(*[getattr(self, n) for n in self._dfields]))
return ttuple
-# note: the methods of the Table classes
-# are completely external to the class except for the .conn attribute
+# closures to be instantiated in DTable.__init__
def insert(ttuple):
"Return a procedure inserting a row or a dictionary into a table"
@@ -114,76 +111,77 @@ def update_or_insert(ttuple):
return n
update_or_insert_row.__doc__ = update_or_insert_row.templ = None
return update_or_insert_row
-
-class DTable(object):
+
+class DView(object):
"""
- A simple table class for database tables without a primary key.
- The only methods are insert_row, insert_file, delete, truncate, select.
"""
- _registry = {}
-
- @classmethod
- def type(cls, name, fields):
- "Ex. Insert = DTable.type('book', 'serial', 'title author')"
- fields = tuple(fields)
- if (name, fields) in cls._registry:
- return cls._registry[name, fields]
- tt = namedtuple(name, fields)
- def insert_file(conn, file, sep='\t'):
- 'Populate a table by reading a file-like object'
- return util.insert_file(conn, file, name, sep)
- def insert_rows(conn, rows):
- 'Populate a table by reading a row-iterator'
- return util.insert_rows(conn, name, rows)
- dic = dict(
- tt = tt,
- name = tt.__name__,
- insert_row = connmethod(insert(tt)),
- insert_rows = connmethod(insert_rows),
- insert_file = connmethod(insert_file),
- _registry = {},
- )
- subc = type(name.capitalize(), (cls,), dic)
- cls._registry[name, fields] = subc
- return subc
-
- @classmethod
- def reflect(cls, conn, name):
- "Ex. insert = DTable.reflect(mydb, 'book')"
- fields = util.get_fields(conn, name)
- return cls.type(name, fields)(conn)
@classmethod
def create(cls, conn, name, fields, force=False):
- util.create_table(conn, name, fields, force)
- return cls.reflect(conn, name)
-
- @connmethod
- def insert_row(conn, row, **kw):
- "Dynamically replaced in subclasses"
-
- @connmethod
- def insert_rows(conn, row, **kw):
- "Dynamically replaced in subclasses"
-
- @connmethod
- def insert_file(conn, row, **kw):
- "Dynamically replaced in subclasses"
+ util.create_view(conn, name, fields, force)
+ return cls(conn, name)
- def __init__(self, conn):
- if self.__class__ in (DTable, KTable):
- raise TypeError('You cannot instantiate the ABC %s' %
- self.__class__.__name__)
- self.tt # raise an AttributeError if not set correctly
+ def __init__(self, conn, name, fields=(), query=''):
self.conn = conn
+ self.name = name
+ if query:
+ self.query = '(%s) AS %s' % (query, name)
+ s = 'SELECT * FROM %s WHERE 1=0' % self.query
+ fields = [r.name for name in conn.execute(s).descr]
+ else:
+ self.query = name
+ fields = fields or util.get_fields(conn, name)
+ self.tt = tabletuple(name, fields)
def select(self, clause='', *args):
"Select rows from the table"
fields = ', '.join(self.tt._fields)
- templ = 'SELECT %s FROM %s %s' % (fields, self.name, clause)
+ templ = 'SELECT %s FROM %s %s' % (fields, self.query, clause)
if args:
return do(templ, ntuple=self.tt)(self.conn, templ, *args)
- return self.conn.execute(templ, ntuple=self.tt)
+ else:
+ return self.conn.execute(templ, ntuple=self.tt)
+
+ def count(self, clause=''):
+ "Count the number of rows satisfying the given clause"
+ templ = 'SELECT COUNT(*) FROM %s %s' % (self.query, clause)
+ if args:
+ return do(templ)(self.conn, templ, *args)
+ return self.conn.execute(templ, scalar=True)
+
+ def __iter__(self):
+ return self.select()
+
+ def __len__(self):
+ "Return the total number of rows in the table"
+ return self.count()
+
+class DTable(DView):
+ """
+ A simple table class for database tables without a primary key.
+ The only methods are insert_row, insert_file, delete, truncate, select.
+ """
+
+ @classmethod
+ def create(cls, conn, name, fields, force=False):
+ util.create_table(conn, name, fields, force)
+ return cls(conn, name, fields)
+
+ def insert_rows(self, rows):
+ 'Populate a table by reading a row-iterator'
+ return util.insert_rows(self.conn, self.name, rows)
+
+ def insert_file(self, file, sep='1t'):
+ 'Populate a table by reading a file-like object'
+ return util.insert_file(self.conn, file, self.name, sep)
+
+ def __init__(self, conn, name, fields=()):
+ self.conn = conn
+ self.name = self.query = name
+ if not fields:
+ fields = util.get_fields(conn, name)
+ self.tt = namedtuple(name, fields)
+ self.insert_row = insert(self.tt)
def delete(self, clause=''):
"Delete rows from the table"
@@ -195,49 +193,22 @@ class DTable(object):
def truncate(self):
"Truncate the table"
return util.truncate_table(self.conn, self.name)
-
- def count(self, clause=''):
- "Count the number of rows satisfying the given clause"
- templ = 'SELECT COUNT(*) FROM %s %s' % (self.name, clause)
- if args:
- return do(templ)(self.conn, templ, *args)
- return self.conn.execute(templ, scalar=True)
-
- def __len__(self):
- "Return the total number of rows in the table"
- return self.count()
class KTable(DTable):
"""
An object oriented wrapper for database tables with a primary key.
"""
- _registry = {}
- @classmethod
- def type(cls, name, kfields, dfields):
+ def __init__(cls, name, kfields=(), dfields=()):
"Ex. Book = KTable.type('book', 'serial', 'title author')"
+ kfields = kfields or util.get_kfields(conn, name)
+ dfields = dfields or util.get_dfields(conn, name)
if not kfields:
raise TypeError('table %s has no primary key!' % name)
- kfields = tuple(kfields)
- dfields = tuple(dfields)
- if (name, kfields, dfields) in cls._registry:
- return cls._registry[name, kfields, dfields]
- tt = tabletuple(name, kfields, dfields)
- d = dict(tt=tt, name=name, _registry = {})
+ self.tt = tabletuple(name, kfields, dfields)
for nam in ('insert', 'delete', 'select', 'update', 'update_or_insert'):
- func = globals()[nam](tt)
- cmethod = connmethod(func)
- d[nam + '_row'] = cmethod
- subc = type(name.capitalize(), (cls,), d)
- cls._registry[name, kfields, dfields] = subc
- return subc
-
- @classmethod
- def reflect(cls, conn, name):
- "Ex. book = KTable.reflect(mydb, 'book')"
- kfields = util.get_kfields(conn, name)
- dfields = util.get_dfields(conn, name)
- return cls.type(name, kfields, dfields)(conn)
+ func = globals()[nam](self.tt)
+ setattr(self, nam + '_row', func)
def __contains__(self, key):
try:
diff --git a/sqlplain/util.py b/sqlplain/util.py
index a2ac214..910744a 100644
--- a/sqlplain/util.py
+++ b/sqlplain/util.py
@@ -4,7 +4,8 @@ Notice: create_db and drop_db are not transactional.
import os, sys, re
from sqlplain.uri import URI
-from sqlplain import lazyconnect, Transaction, do
+from sqlplain import lazyconnect, do
+from sqlplain.connection import Transaction
from sqlplain.namedtuple import namedtuple
VERSION = re.compile(r'(\d[\d\.-]+)')
@@ -43,7 +44,7 @@ def drop_db(uri):
"Drop an existing database"
_call('drop_db', URI(uri))
-# helper for createdb
+# helper for create_db
def _collect(directory, exts):
'''
Read the files with a given set of extensions from a directory
@@ -139,7 +140,7 @@ def insert_rows(conn, tname, rows):
n = conn.execute(templ, row)
for row in it:
n += conn.execute(templ, row)
- return n
+ return
def insert_file(conn, fname, tname, sep=','):
"Bulk insert a CSV file into a table"""
@@ -147,6 +148,10 @@ def insert_file(conn, fname, tname, sep=','):
########################## introspection routines ######################
+def get_tables(conn):
+ "Return the names of the tables in the current database"
+ return _call('get_tables', conn)
+
def exists_table(conn, tname):
"Check if a table exists"
return _call('exists_table', conn, tname)