SQLAlchemy Type Engines - Essential SQLAlchemy, 2nd Edition (2010)

Essential SQLAlchemy, 2nd Edition (2010)

Chapter 4. SQLAlchemy Type Engines

This chapter introduces the SQLAlchemy type system. It covers the built-in types provided by SQLAlchemy: database-independent types and database-specific types. It then tells you how to create your own custom types for use in mapping application data onto your database schema.

Type System Overview

When defining the MetaData used by your application, it is necessary to supply the SQL data type used by each column of each table (unless the tables are defined with autoload=True, in which case SQLAlchemy provides the data types for you). These SQL data types are actually instances of SQLAlchemy-provided classes known as TypeEngines. TypeEngine objects convert Python values to native database values and vice versa. For instance, String(40) is an instance of a TypeEngine that represents a VARCHAR(40). TypeEngines also supply SQL text for use when creating tables using metadata.create_all() or table.create().

SQLAlchemy provides three different ways of constructing types for use in your application. First, it provides a set of generic TypeEngines, which are fairly portable across different database engines. Second, it provides database server-specific TypeEngines, which can be used to exploit particular types supported by certain databases. Third, SQLAlchemy allows you to define application-specific custom TypeEngines if you wish to further customize object conversion to/from the database.

Built-in Types

SQLAlchemy provides a fairly complete set of built-in TypeEngines for support of basic SQL column types. The SQLAlchemy-provided TypeEngines are broken into the generic types (those portable across multiple database engines) and the dialect-specific types, which work only on particular databases.

Note

If you want to keep your application portable across database servers, it is a good idea to stick to the generic types and (possibly) application-specific custom types, as any code that relies on database dialect-specific TypeEngines will need to be modified if the database changes. In the SQLAlchemy tradition of not getting in your way, however, full support is provided for dialect-specific TypeEngines if you wish to exploit database server-specific types.

Generic Types

The generic TypeEngines provided by SQLAlchemy are found in the sqlalchemy.types package. These TypeEngines cover a fairly complete set of portable column types. The TypeEngines supported, their corresponding Python type, and their SQL representation, are listed inTable 4-1. Note that there are several TypeEngines defined in all caps (such as CLOB). These are derived from other TypeEngines and may or may not be further specialized to allow finer-grained specification of the underlying database type.

Table 4-1. Built-in generic TypeEngines

Class name

Python type

SQL type (for SQLite driver)

Arguments

String

string

TEXT or VARCHAR

length (default is unbounded)

Integer

int

INTEGER

none

SmallInteger

int

SMALLINT

none

Numeric

float, Decimal

NUMERIC

precision=10 , length=2

Float(Numeric)

float

NUMERIC

precision=10

DateTime

datetime.datetime

TIMESTAMP

none

Date

datetime.date

DATE

none

Time

datetime.time

TIME

none

Binary

byte string

BLOB

length (default is unbounded)

Boolean

bool

BOOLEAN

none

Unicode

unicode

TEXT or VARCHAR

length (default is unbounded)

PickleType

any object that can be pickled

BLOB

none

FLOAT(Numeric)

float, Decimal

NUMERIC

precision=10 ,length=2

TEXT(String)

string

TEXT

length (default is unbounded)

DECIMAL(Numeric)

float, Decimal

NUMERIC

precision=10,length=2

INT, INTEGER(Integer)

int

INTEGER

none

TIMESTAMP(DateTime)

datetime.datetime

TIMESTAMP

none

DATETIME(DateTime)

datetime.datetime

TIMESTAMP

none

CLOB(String)

string

TEXT

length (default is unbounded)

VARCHAR(String)

string

VARCHAR or TEXT

length (default is unbounded)

CHAR(String)

string

CHAR or TEXT

length (default is unbounded)

NCHAR(Unicode)

string

VARCHAR, NCHAR, or TEXT

length (default is unbounded)

BLOB(Binary)

byte string

BLOB

length (default is unbounded)

BOOLEAN(Boolean)

bool

BOOLEAN

none

When using TypeEngines to specify columns in Tables, you can use an instance of the TypeEngine class or the class itself. If you use the class, the default parameters will be used when constructing the SQL type. For instance, the following Python code:

test_table3 = Table(

'test3', metadata,

Column('c0', Numeric),

Column('c1', Numeric(4,6)),

Column('c3', String),

Column('c4', String(10)))

yields the following SQL creation (in SQLite):

CREATE TABLE test3 (

c0 NUMERIC(10, 2),

c1 NUMERIC(4, 6),

c3 TEXT,

c4 VARCHAR(10)

)

Dialect-Specific Types

To generate appropriate dialect-specific SQL CREATE TABLE statements from these generic types, SQLAlchemy compiles those generic TypeEngines into dialect-specific TypeEngines. In some cases, in addition to implementing the generic types, a dialect may provide dialect-specific types (such as IP address, etc.).

Some of the dialect-specific types don’t actually provide any special support for converting between database values and Python values; these are generally used for completeness, particularly when reflecting tables. In this case, no conversion is done between the value supplied by the DB-API implementation and the application. This behavior is indicated in the following tables by listing “none” as the Python type for that TypeEngine. Tables 4-2 through 4-5 list some of the types provided by particular database engines that are not automatically used by SQLAlchemy.

Table 4-2. MS SQL server types

Class name

Python type

SQL type

Arguments

MSMoney

none

MONEY

none

MSSmallMoney

none

SMALLMONEY

none

AdoMSNVarchar

unicode

NVARCHAR

length

MSBigInteger

int

BIGINT

none

MSTinyInteger

int

TINYINT

none

MSVariant

none

SQL_VARIANT

none

MSUniqueIdentifier

none

UNIQUEIDENTIFIER

none

Table 4-3. MySQL types

Class name

Python type

SQL type

Arguments

MSEnum

string

ENUM

values

MSTinyInteger

int

TINYINT

length

MSBigInteger

int

BIGINT

length

MSDouble

float

DOUBLE

length=10,precision=2

MSTinyText

string

TINYTEXT

none

MSMediumText

string

MEDIUMTEXT

none

MSLongText

string

LONGTEXT

none

MSNVarChar

unicode

NATIONAL VARCHAR

length

MSTinyBlob

byte string

TINYBLOB

none

MSMediumBlob

byte string

MEDIUMBLOB

none

MSLongBlob

byte string

LONGBLOB

none

MSBinary

byte string

BINARY

length

MSVarBinary

byte string

VARBINARY

length

MSSet

set

SET

set values

MSYear

int

YEAR

length

MSBit

long

BIT

length

Table 4-4. Oracle types

Class name

Python type

SQL type

Arguments

OracleRaw

byte string

RAW

length

Table 4-5. PostgreSQL types

Class name

Python type

SQL type

Arguments

PGArray

any TypeEngine

type engine[]

TypeEngine

PGBigInteger

int, long

BIGINT

none

PGInet

none

INET

none

PGInterval

none

INTERVAL

none

Application-Specific Custom Types

Although SQLAlchemy provides a rich set of generic and database-specific types, it is sometimes helpful to be able to create application-specific custom types. For instance, you may wish to emulate enumerations in a database engine that does not support enumerations by restricting the values that can be stored in a column.

In SQLAlchemy, there are two ways to create an application-specific custom type. If you wish to implement a type that is similar to an existing TypeEngine, you would implement a TypeDecorator. If your implementation is more involved, you can directly subclass TypeEngine.

Implementing a TypeDecorator

To implement a TypeDecorator, you must provide the base TypeEngine you are “implementing” as well as two functions, convert_bind_param() and convert_result_value(). convert_bind_param( self, value, engine) is used to convert Python values to SQL values suitable for the DB-API driver, and convert_result_value( self, value, engine) is used to convert SQL values from the DB-API driver back into Python values. The implemented TypeEngine is specified in the impl attribute on the TypeDecorator.

For instance, if you wish to implement a type for validating that a particular Integer column contains only the values 0, 1, 2, and 3 (e.g., to implement an enumerated type in a database that does not support enumerated types), you would implement the following TypeDecorator:

from sqlalchemy import types

class MyCustomEnum(types.TypeDecorator):

impl=types.Integer

def __init__(self, enum_values, *l, **kw):

types.TypeDecorator.__init__(self, *l, **kw)

self._enum_values = enum_values

def convert_bind_param(self, value, engine):

result = self.impl.convert_bind_param(value, engine)

if result not in self._enum_values:

raise TypeError, (

"Value %s must be one of %s" % (result, self._enum_values))

return result

def convert_result_value(self, value, engine):

'Do nothing here'

return self.impl.convert_result_value(value, engine)

It is not necessary to specify in a TypeDecorator the SQL type used to implement the column, as this will be obtained from the impl attribute. The TypeDecorator is used only when an existing TypeEngine provides the correct SQL type for the type you are implementing.

Performance-Conscious TypeDecorators

SQLAlchemy has a second, undocumented (at the time of this book’s writing) interface for providing bind parameter and result value conversion. If you provide a bind_processor() or result_processor() method in your TypeDecorator, then these will be used instead of theconvert_bind_param() and convert_result_value() methods. The new “processor” interface methods take a database dialect as a parameter and return a conversion function (a “processor”) that takes a single value parameter and returns the (possibly converted) value. If no processing is necessary, you can simply return None rather than a new processor:

>>> from sqlalchemy import types

>>> import sqlalchemy.databases.sqlite as sqlite

>>>

>>> class MyCustomEnum(types.TypeDecorator):

... impl = types.Integer

... def __init__(self, enum_values, *l, **kw):

... types.TypeDecorator.__init__(self, *l, **kw)

... self._enum_values = enum_values

... def bind_processor(self, dialect):

... impl_processor = self.impl.bind_processor(dialect)

... if impl_processor:

... def processor(value):

... result = impl_processor(value)

... assert value in self._enum_values, \

... "Value %s must be one of %s" % (result,

... self._enum_values)

... return result

... else:

... def processor(value):

... assert value in self._enum_values, \

... "Value %s must be one of %s" % (value,

... self._enum_values)

... return value

... return processor

...

>>> mce=MyCustomEnum([1,2,3])

>>> processor = mce.bind_processor(sqlite.dialect())

>>> print processor(1)

1

>>> print processor(5)

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

File "<stdin>", line 17, in processor

AssertionError: Value 5 must be one of [1, 2, 3]

Creating a New TypeEngine

If creating a TypeDecorator is insufficient for your new type (such as when supporting a new SQL type), you can directly subclass the TypeEngine class. In this case, in addition to providing the convert_bind_param() and convert_result_value() methods, you must also provide the get_col_spec method for SQLAlchemy to use in its create_table() implementation.

To create a new TypeEngine to implement the SQL type “NEWTYPE”, for instance, you might use the following class declaration:

class NewType(types.TypeEngine):

def __init__(self, *args):

self._args = args

def get_col_spec(self):

return 'NEWTYPE(%s)' % ','.join(self._args)

def convert_bind_param(self, value, engine):

return value

def convert_result_value(self, value, engine):

return value