Finished Release v0.6.1

This commit is contained in:
Itai Shirav 2016-07-12 08:27:18 +03:00
commit a727c3cf01
4 changed files with 51 additions and 6 deletions

View File

@ -91,6 +91,25 @@ It is possible to select only a subset of the columns, and the rest will receive
for person in db.select("SELECT first_name FROM my_test_db.person WHERE last_name='Smith'", model_class=Person): for person in db.select("SELECT first_name FROM my_test_db.person WHERE last_name='Smith'", model_class=Person):
print person.first_name print person.first_name
SQL Placeholders
****************
There are a couple of special placeholders that you can use inside the SQL to make it easier to write:
``$db`` and ``$table``. The first one is replaced by the database name, and the second is replaced by
the database name plus table name (but is available only when the model is specified).
So instead of this::
db.select("SELECT * FROM my_test_db.person", model_class=Person)
you can use::
db.select("SELECT * FROM $db.person", model_class=Person)
or even::
db.select("SELECT * FROM $table", model_class=Person)
Ad-Hoc Models Ad-Hoc Models
************* *************

View File

@ -5,6 +5,7 @@ from utils import escape, parse_tsv, import_submodules
from math import ceil from math import ceil
import datetime import datetime
import logging import logging
from string import Template
Page = namedtuple('Page', 'objects number_of_objects pages_total number page_size') Page = namedtuple('Page', 'objects number_of_objects pages_total number page_size')
@ -41,7 +42,7 @@ class Database(object):
return # model_instances is empty return # model_instances is empty
model_class = first_instance.__class__ model_class = first_instance.__class__
def gen(): def gen():
yield 'INSERT INTO `%s`.`%s` FORMAT TabSeparated\n' % (self.db_name, model_class.table_name()) yield self._substitute('INSERT INTO $table FORMAT TabSeparated\n', model_class)
yield first_instance.to_tsv() yield first_instance.to_tsv()
yield '\n' yield '\n'
for instance in i: for instance in i:
@ -50,14 +51,16 @@ class Database(object):
self._send(gen()) self._send(gen())
def count(self, model_class, conditions=None): def count(self, model_class, conditions=None):
query = 'SELECT count() FROM `%s`.`%s`' % (self.db_name, model_class.table_name()) query = 'SELECT count() FROM $table'
if conditions: if conditions:
query += ' WHERE ' + conditions query += ' WHERE ' + conditions
query = self._substitute(query, model_class)
r = self._send(query) r = self._send(query)
return int(r.text) if r.text else 0 return int(r.text) if r.text else 0
def select(self, query, model_class=None, settings=None): def select(self, query, model_class=None, settings=None):
query += ' FORMAT TabSeparatedWithNamesAndTypes' query += ' FORMAT TabSeparatedWithNamesAndTypes'
query = self._substitute(query, model_class)
r = self._send(query, settings, True) r = self._send(query, settings, True)
lines = r.iter_lines() lines = r.iter_lines()
field_names = parse_tsv(next(lines)) field_names = parse_tsv(next(lines))
@ -70,11 +73,12 @@ class Database(object):
count = self.count(model_class, conditions) count = self.count(model_class, conditions)
pages_total = int(ceil(count / float(page_size))) pages_total = int(ceil(count / float(page_size)))
offset = (page_num - 1) * page_size offset = (page_num - 1) * page_size
query = 'SELECT * FROM `%s`.`%s`' % (self.db_name, model_class.table_name()) query = 'SELECT * FROM $table'
if conditions: if conditions:
query += ' WHERE ' + conditions query += ' WHERE ' + conditions
query += ' ORDER BY %s' % order_by query += ' ORDER BY %s' % order_by
query += ' LIMIT %d, %d' % (offset, page_size) query += ' LIMIT %d, %d' % (offset, page_size)
query = self._substitute(query, model_class)
return Page( return Page(
objects=list(self.select(query, model_class, settings)), objects=list(self.select(query, model_class, settings)),
number_of_objects=count, number_of_objects=count,
@ -100,7 +104,8 @@ class Database(object):
def _get_applied_migrations(self, migrations_package_name): def _get_applied_migrations(self, migrations_package_name):
from migrations import MigrationHistory from migrations import MigrationHistory
self.create_table(MigrationHistory) self.create_table(MigrationHistory)
query = "SELECT module_name from `%s`.`%s` WHERE package_name = '%s'" % (self.db_name, MigrationHistory.table_name(), migrations_package_name) query = "SELECT module_name from $table WHERE package_name = '%s'" % migrations_package_name
query = self._substitute(query, MigrationHistory)
return set(obj.module_name for obj in self.select(query)) return set(obj.module_name for obj in self.select(query))
def _send(self, data, settings=None, stream=False): def _send(self, data, settings=None, stream=False):
@ -117,3 +122,14 @@ class Database(object):
if self.password: if self.password:
params['password'] = password params['password'] = password
return params return params
def _substitute(self, query, model_class=None):
'''
Replaces $db and $table placeholders in the query.
'''
if '$' in query:
mapping = dict(db="`%s`" % self.db_name)
if model_class:
mapping['table'] = "`%s`.`%s`" % (self.db_name, model_class.table_name())
query = Template(query).substitute(mapping)
return query

View File

@ -8,6 +8,8 @@ class ModelBase(type):
A metaclass for ORM models. It adds the _fields list to model classes. A metaclass for ORM models. It adds the _fields list to model classes.
''' '''
ad_hoc_model_cache = {}
def __new__(cls, name, bases, attrs): def __new__(cls, name, bases, attrs):
new_cls = super(ModelBase, cls).__new__(cls, name, bases, attrs) new_cls = super(ModelBase, cls).__new__(cls, name, bases, attrs)
# Collect fields from parent classes # Collect fields from parent classes
@ -25,13 +27,21 @@ class ModelBase(type):
def create_ad_hoc_model(cls, fields): def create_ad_hoc_model(cls, fields):
# fields is a list of tuples (name, db_type) # fields is a list of tuples (name, db_type)
import fields as orm_fields import fields as orm_fields
# Check if model exists in cache
cache_key = unicode(fields)
if cache_key in cls.ad_hoc_model_cache:
return cls.ad_hoc_model_cache[cache_key]
# Create an ad hoc model class
attrs = {} attrs = {}
for name, db_type in fields: for name, db_type in fields:
field_class = db_type + 'Field' field_class = db_type + 'Field'
if not hasattr(orm_fields, field_class): if not hasattr(orm_fields, field_class):
raise NotImplementedError('No field class for %s' % db_type) raise NotImplementedError('No field class for %s' % db_type)
attrs[name] = getattr(orm_fields, field_class)() attrs[name] = getattr(orm_fields, field_class)()
return cls.__new__(cls, 'AdHocModel', (Model,), attrs) model_class = cls.__new__(cls, 'AdHocModel', (Model,), attrs)
# Add the model class to the cache
cls.ad_hoc_model_cache[cache_key] = model_class
return model_class
class Model(object): class Model(object):

View File

@ -22,7 +22,7 @@ class MigrationsTestCase(unittest.TestCase):
self.database.drop_table(MigrationHistory) self.database.drop_table(MigrationHistory)
def tableExists(self, model_class): def tableExists(self, model_class):
query = "EXISTS TABLE `%s`.`%s`" % (self.database.db_name, model_class.table_name()) query = "EXISTS TABLE $db.`%s`" % model_class.table_name()
return next(self.database.select(query)).result == 1 return next(self.database.select(query)).result == 1
def getTableFields(self, model_class): def getTableFields(self, model_class):