documentation

This commit is contained in:
Itai Shirav 2016-06-26 16:52:25 +03:00
parent 27ee24843a
commit 64f8cde1c0
4 changed files with 80 additions and 12 deletions

View File

@ -32,9 +32,9 @@ Models are defined in a way reminiscent of Django's ORM:
engine = engines.MergeTree('birthday', ('first_name', 'last_name', 'birthday')) engine = engines.MergeTree('birthday', ('first_name', 'last_name', 'birthday'))
It is possible to provide a default value for a field, instead of it's "natural" default (empty string for string fields, zero for numeric fields etc.). It is possible to provide a default value for a field, instead of its "natural" default (empty string for string fields, zero for numeric fields etc.).
See below for the supported model field types. See below for the supported field types and table engines.
Using Models Using Models
------------ ------------
@ -48,8 +48,8 @@ Once you have a model, you can create model instances:
>>> dan.first_name >>> dan.first_name
u'Dan' u'Dan'
When values are assigned to a model fields, they are immediately converted to their Pythonic data type. When values are assigned to model fields, they are immediately converted to their Pythonic data type.
In case the value is invalid, a ValueError is raised: In case the value is invalid, a ``ValueError`` is raised:
.. code:: python .. code:: python
@ -61,7 +61,10 @@ In case the value is invalid, a ValueError is raised:
>>> suzy.birthday = '1922-05-31' >>> suzy.birthday = '1922-05-31'
ValueError: DateField out of range - 1922-05-31 is not between 1970-01-01 and 2038-01-19 ValueError: DateField out of range - 1922-05-31 is not between 1970-01-01 and 2038-01-19
To write your instances to ClickHouse, you need a Database instance: Inserting to the Database
-------------------------
To write your instances to ClickHouse, you need a ``Database`` instance:
.. code:: python .. code:: python
@ -76,16 +79,53 @@ If necessary, you can specify a different database URL and optional credentials:
db = Database('my_test_db', db_url='http://192.168.1.1:8050', username='scott', password='tiger') db = Database('my_test_db', db_url='http://192.168.1.1:8050', username='scott', password='tiger')
Using the Database instance you can create a table for your model, and insert instances to it: Using the ``Database`` instance you can create a table for your model, and insert instances to it:
.. code:: python .. code:: python
db.create_table(Person) db.create_table(Person)
db.insert([dan, suzy]) db.insert([dan, suzy])
The insert method can take any iterable of model instances, but they all must belong to the same model class. The ``insert`` method can take any iterable of model instances, but they all must belong to the same model class.
Reading from the Database
-------------------------
Loading model instances from the database is simple:
.. code:: python
for person in db.select("SELECT * FROM my_test_db.person", model_class=Person):
print person.first_name, person.last_name
Do not include a ``FORMAT`` clause in the query, since the ORM automatically sets the format to ``TabSeparatedWithNamesAndTypes``.
It is possible to select only a subset of the columns, and the rest will receive their default values:
.. code:: python
for person in db.select("SELECT first_name FROM my_test_db.person WHERE last_name='Smith'", model_class=Person):
print person.first_name
Specifying a model class is not required. In case you do not provide a model class, an ad-hoc class will
be defined based on the column names and types returned by the query:
.. code:: python
for row in db.select("SELECT max(height) as max_height FROM my_test_db.person"):
print row.max_height
Counting
--------
The ``Database`` class also supports counting records easily:
.. code:: python
>>> db.count(Person)
117
>>> db.count(Person, conditions="height > 1.90")
6
Field Types Field Types
----------- -----------
@ -106,6 +146,10 @@ Currently the following field types are supported:
- DateField - DateField
- DateTimeField - DateTimeField
Table Engines
-------------
TBD
Development Development
@ -119,4 +163,4 @@ After cloning the project, run the following commands::
To run the tests, ensure that the ClickHouse server is running on http://localhost:8123/ (this is the default), and run:: To run the tests, ensure that the ClickHouse server is running on http://localhost:8123/ (this is the default), and run::
bin/nosetests bin/nosetests

View File

@ -17,6 +17,7 @@ class Database(object):
self._send('CREATE DATABASE IF NOT EXISTS ' + db_name) self._send('CREATE DATABASE IF NOT EXISTS ' + db_name)
def create_table(self, model_class): def create_table(self, model_class):
# TODO check that model has an engine
self._send(model_class.create_table_sql(self.db_name)) self._send(model_class.create_table_sql(self.db_name))
def drop_table(self, model_class): def drop_table(self, model_class):

View File

@ -15,23 +15,30 @@ class Field(object):
self.default = default or self.class_default self.default = default or self.class_default
def to_python(self, value): def to_python(self, value):
""" '''
Converts the input value into the expected Python data type, raising ValueError if the Converts the input value into the expected Python data type, raising ValueError if the
data can't be converted. Returns the converted value. Subclasses should override this. data can't be converted. Returns the converted value. Subclasses should override this.
""" '''
return value return value
def validate(self, value): def validate(self, value):
'''
Called after to_python to validate that the value is suitable for the field's database type.
Subclasses should override this.
'''
pass pass
def _range_check(self, value, min_value, max_value): def _range_check(self, value, min_value, max_value):
'''
Utility method to check that the given value is between min_value and max_value.
'''
if value < min_value or value > max_value: if value < min_value or value > max_value:
raise ValueError('%s out of range - %s is not between %s and %s' % (self.__class__.__name__, value, min_value, max_value)) raise ValueError('%s out of range - %s is not between %s and %s' % (self.__class__.__name__, value, min_value, max_value))
def get_db_prep_value(self, value): def get_db_prep_value(self, value):
""" '''
Returns the field's value prepared for interacting with the database. Returns the field's value prepared for interacting with the database.
""" '''
return value return value

View File

@ -58,6 +58,10 @@ class Model(object):
setattr(self, name, field.default) setattr(self, name, field.default)
def __setattr__(self, name, value): def __setattr__(self, name, value):
'''
When setting a field value, converts the value to its Pythonic type and validates it.
This may raise a ValueError.
'''
field = self.get_field(name) field = self.get_field(name)
if field: if field:
value = field.to_python(value) value = field.to_python(value)
@ -65,15 +69,24 @@ class Model(object):
super(Model, self).__setattr__(name, value) super(Model, self).__setattr__(name, value)
def get_field(self, name): def get_field(self, name):
'''
Get a Field instance given its name, or None if not found.
'''
field = getattr(self.__class__, name, None) field = getattr(self.__class__, name, None)
return field if isinstance(field, Field) else None return field if isinstance(field, Field) else None
@classmethod @classmethod
def table_name(cls): def table_name(cls):
'''
Returns the model's database table name.
'''
return cls.__name__.lower() return cls.__name__.lower()
@classmethod @classmethod
def create_table_sql(cls, db_name): def create_table_sql(cls, db_name):
'''
Returns the SQL command for creating a table for this model.
'''
parts = ['CREATE TABLE IF NOT EXISTS %s.%s (' % (db_name, cls.table_name())] parts = ['CREATE TABLE IF NOT EXISTS %s.%s (' % (db_name, cls.table_name())]
cols = [] cols = []
for name, field in cls._fields: for name, field in cls._fields:
@ -86,6 +99,9 @@ class Model(object):
@classmethod @classmethod
def drop_table_sql(cls, db_name): def drop_table_sql(cls, db_name):
'''
Returns the SQL command for deleting this model's table.
'''
return 'DROP TABLE IF EXISTS %s.%s' % (db_name, cls.table_name()) return 'DROP TABLE IF EXISTS %s.%s' % (db_name, cls.table_name())
@classmethod @classmethod