Skip to content

Instantly share code, notes, and snippets.

@darjeeling
Forked from dahlia/hstore.py
Created July 18, 2012 12:08
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save darjeeling/3135816 to your computer and use it in GitHub Desktop.
Save darjeeling/3135816 to your computer and use it in GitHub Desktop.
PostgreSQL hstore + SQLAlchemy
""":mod:`hstore` --- Using PostgreSQL hstore with SQLAlchemy
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. note::
I released it under Public Domain. Feel free to use!
It provides :class:`Hstore` type which makes you to store Python
dictionaries into hstore columns in PostgreSQL. For example::
from sqlalchemy import Column
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.types import Integer, Unicode
from hstore import Hstore
Base = declarative_base()
class Person(Base):
'''Person model class that can store extra data as well.'''
id = Column(Integer, primary_key=True)
name = Column(Unicode, nullable=False)
extra_data = Column(Hstore, nullable=False, default={})
__tablename__ = 'people'
and then you can use it like:
>>> p = Person(name=u'Hong Minhee',
... extra_data={'twitter': 'hongminhee', 'github': 'dahlia'})
>>> session.add(p)
>>> session.flush()
It will be stored in PostgreSQL using hstore::
# SELECT * FROM people;
id | name | extra_data
----+-------------+--------------------------------------------
1 | Hong Minhee | "twitter"=>"hongminhee", "github"=>"dahlia"
(1 row)
"""
import collections
import sqlalchemy.types
__license__ = 'Public Domain'
class Hstore(sqlalchemy.types.UserDefinedType, sqlalchemy.types.MutableType):
"""The ``hstore`` type that stores a dictionary. It can take an any
instance of :class:`collections.Mapping`.
It can be extended to store other types than string e.g.::
class IntegerBooleanHstore(Hstore):
'''The ``hstore`` type for integer keys and boolean values.'''
def map_bind_key(self, key):
if key is not None:
return unicode(key)
def map_bind_value(self, value):
if value is not None:
return u't' if value else u'f'
def map_result_key(self, key):
if key is not None:
return int(key)
def map_result_value(self, value):
if value is not None:
return value == u't'
:param value_nullable: to prevent ``None`` (``NULL``) for dictionary
values, set it ``True``. default is ``False``
:type value_nullable: :class:`bool`
"""
def __init__(self, value_nullable=True):
self.value_nullable = bool(value_nullable)
def map_bind_key(self, key):
"""The mapping function that is used for binding keys. The default
implementation is just a string identity function.
:param key: a key object to bind
:returns: a mapped key string
:rtype: :class:`unicode`
"""
if key is None:
return
if not isinstance(key, basestring):
raise TypeError('hstore key must be a string, not ' + repr(key))
return unicode(key)
def map_bind_value(self, value):
"""The mapping function that is used for binding values.
The default implementation is just a string identity function.
:param value: a value to bind
:returns: a mapped value string
:rtype: :class:`unicode`
"""
if value is None:
return
if not isinstance(value, basestring):
raise TypeError('hstore value must be a string, not ' +
repr(value))
return unicode(value)
def map_result_key(self, key):
"""The mapping function that is used for resulting keys. The default
implementation is just an identity function.
:param key: a raw key of the result
:type key: :class:`unicode`
:returns: a mapped key object
"""
return key
def map_result_value(self, value):
"""The mapping function that is used for resulting values.
The default implementation is just an identity function.
:param key: a raw value of the result
:type key: :class:`unicode`
:returns: a mapped value
"""
return value
def get_col_spec(self):
return 'hstore'
def is_mutable(self):
return True
def compare_values(self, x, y):
x = None if x is None else dict(x)
y = None if y is None else dict(y)
return x == y
def copy_value(self, value):
if value is not None:
return dict(value)
def bind_processor(self, dialect):
def process(value):
if value is None:
return
if not isinstance(value, collections.Mapping):
raise TypeError('expected a collections.Mapping object, not '
+ repr(value))
items = getattr(value, 'iteritems', value.items)()
map_bind_key = self.map_bind_key
def map_key(key):
if key is None:
raise TypeError('hstore key cannot be None')
return map_bind_key(key)
if self.value_nullable:
map_value = self.map_bind_value
else:
map_bind_value = self.map_bind_value
def map_value(value):
if value is None:
raise TypeError('hstore value cannot be None')
return map_bind_value(value)
return dict((map_key(k), map_value(v)) for k, v in items)
return process
def result_processor(self, dialect, coltype):
def process(value):
map_key = self.map_result_key
map_value = self.map_result_value
return dict((map_key(k), map_value(v))
for k, v in value.iteritems())
return process
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment