From e7d61d594b8a89535696436a667a526bd2999fb2 Mon Sep 17 00:00:00 2001 From: Mateusz Susik Date: Fri, 24 Oct 2014 13:27:29 +0200 Subject: Psycopg2 JSONB support. --- lib/sqlalchemy/dialects/postgresql/psycopg2.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) (limited to 'lib/sqlalchemy/dialects') diff --git a/lib/sqlalchemy/dialects/postgresql/psycopg2.py b/lib/sqlalchemy/dialects/postgresql/psycopg2.py index 1a2a1ffe4..f90d3054a 100644 --- a/lib/sqlalchemy/dialects/postgresql/psycopg2.py +++ b/lib/sqlalchemy/dialects/postgresql/psycopg2.py @@ -261,7 +261,7 @@ from .base import PGDialect, PGCompiler, \ ENUM, ARRAY, _DECIMAL_TYPES, _FLOAT_TYPES,\ _INT_TYPES from .hstore import HSTORE -from .json import JSON +from .json import JSON, JSONB logger = logging.getLogger('sqlalchemy.dialects.postgresql') @@ -326,6 +326,15 @@ class _PGJSON(JSON): else: return super(_PGJSON, self).result_processor(dialect, coltype) + +class _PGJSONB(JSONB): + + def result_processor(self, dialect, coltype): + if dialect._has_native_jsonb: + return None + else: + return super(_PGJSONB, self).result_processor(dialect, coltype) + # When we're handed literal SQL, ensure it's a SELECT query. Since # 8.3, combining cursors and "FOR UPDATE" has been fine. SERVER_SIDE_CURSOR_RE = re.compile( @@ -416,6 +425,7 @@ class PGDialect_psycopg2(PGDialect): _has_native_hstore = False _has_native_json = False + _has_native_jsonb = False colspecs = util.update_copy( PGDialect.colspecs, @@ -424,7 +434,8 @@ class PGDialect_psycopg2(PGDialect): ENUM: _PGEnum, # needs force_unicode sqltypes.Enum: _PGEnum, # needs force_unicode HSTORE: _PGHStore, - JSON: _PGJSON + JSON: _PGJSON, + JSONB: _PGJSONB } ) @@ -453,6 +464,7 @@ class PGDialect_psycopg2(PGDialect): self._hstore_oids(connection.connection) \ is not None self._has_native_json = self.psycopg2_version >= (2, 5) + self._has_native_jsonb = self.psycopg2_version >= (2, 5, 4) # http://initd.org/psycopg/docs/news.html#what-s-new-in-psycopg-2-0-9 self.supports_sane_multi_rowcount = self.psycopg2_version >= (2, 0, 9) -- cgit v1.2.1 From f94d75ede5f5d2ed28d72ff98ca7caca016e5506 Mon Sep 17 00:00:00 2001 From: Shaun Stanworth Date: Sun, 9 Nov 2014 14:52:31 +0000 Subject: Added psycopg2cffi dialect --- lib/sqlalchemy/dialects/postgresql/__init__.py | 2 +- lib/sqlalchemy/dialects/postgresql/psycopg2.py | 17 ++++++-- lib/sqlalchemy/dialects/postgresql/psycopg2cffi.py | 46 ++++++++++++++++++++++ 3 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 lib/sqlalchemy/dialects/postgresql/psycopg2cffi.py (limited to 'lib/sqlalchemy/dialects') diff --git a/lib/sqlalchemy/dialects/postgresql/__init__.py b/lib/sqlalchemy/dialects/postgresql/__init__.py index 1cff8e3a0..01a846314 100644 --- a/lib/sqlalchemy/dialects/postgresql/__init__.py +++ b/lib/sqlalchemy/dialects/postgresql/__init__.py @@ -5,7 +5,7 @@ # This module is part of SQLAlchemy and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php -from . import base, psycopg2, pg8000, pypostgresql, zxjdbc +from . import base, psycopg2, pg8000, pypostgresql, zxjdbc, psycopg2cffi base.dialect = psycopg2.dialect diff --git a/lib/sqlalchemy/dialects/postgresql/psycopg2.py b/lib/sqlalchemy/dialects/postgresql/psycopg2.py index fe27da8b6..5246abf1c 100644 --- a/lib/sqlalchemy/dialects/postgresql/psycopg2.py +++ b/lib/sqlalchemy/dialects/postgresql/psycopg2.py @@ -512,9 +512,19 @@ class PGDialect_psycopg2(PGDialect): import psycopg2 return psycopg2 + @classmethod + def _psycopg2_extensions(cls): + from psycopg2 import extensions + return extensions + + @classmethod + def _psycopg2_extras(cls): + from psycopg2 import extras + return extras + @util.memoized_property def _isolation_lookup(self): - from psycopg2 import extensions + extensions = self._psycopg2_extensions() return { 'AUTOCOMMIT': extensions.ISOLATION_LEVEL_AUTOCOMMIT, 'READ COMMITTED': extensions.ISOLATION_LEVEL_READ_COMMITTED, @@ -536,7 +546,8 @@ class PGDialect_psycopg2(PGDialect): connection.set_isolation_level(level) def on_connect(self): - from psycopg2 import extras, extensions + extras = self._psycopg2_extras() + extensions = self._psycopg2_extensions() fns = [] if self.client_encoding is not None: @@ -586,7 +597,7 @@ class PGDialect_psycopg2(PGDialect): @util.memoized_instancemethod def _hstore_oids(self, conn): if self.psycopg2_version >= (2, 4): - from psycopg2 import extras + extras = self._psycopg2_extras() oids = extras.HstoreAdapter.get_oids(conn) if oids is not None and oids[0]: return oids[0:2] diff --git a/lib/sqlalchemy/dialects/postgresql/psycopg2cffi.py b/lib/sqlalchemy/dialects/postgresql/psycopg2cffi.py new file mode 100644 index 000000000..5217c5561 --- /dev/null +++ b/lib/sqlalchemy/dialects/postgresql/psycopg2cffi.py @@ -0,0 +1,46 @@ +# testing/engines.py +# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors +# +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php +""" +.. dialect:: postgresql+psycopg2cffi + :name: psycopg2cffi + :dbapi: psycopg2cffi + :connectstring: \ +postgresql+psycopg2cffi://user:password@host:port/dbname\ +[?key=value&key=value...] + :url: http://pypi.python.org/pypi/psycopg2cffi/ + +`psycopg2cffi` is an adaptation of `psycopg2`, using CFFI for the C +layer. This makes it suitable for use in e.g. PyPy. Documentation +is as per `psycopg2`. + +.. seealso:: + + :mod:`sqlalchemy.dialects.postgresql.psycopg2` + +""" +from .psycopg2 import PGDialect_psycopg2 + + +class PGDialect_psycopg2cffi(PGDialect_psycopg2): + driver = 'psycopg2cffi' + + @classmethod + def dbapi(cls): + return __import__('psycopg2cffi') + + @classmethod + def _psycopg2_extensions(cls): + root = __import__('psycopg2cffi', fromlist=['extensions']) + return root.extensions + + @classmethod + def _psycopg2_extras(cls): + root = __import__('psycopg2cffi', fromlist=['extras']) + return root.extras + + +dialect = PGDialect_psycopg2cffi -- cgit v1.2.1 From a826ff366bf979bcbb4f075d3c33540232b91873 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Mon, 26 Jan 2015 18:02:46 -0500 Subject: - additional test adjustments for pypy / psycopg2cffi. This consists mainly of adjusting fixtures to ensure connections are closed explicitly. psycopg2cffi also handles unicode bind parameter names differently than psycopg2, and seems to possibly have a little less control over floating point values at least in one test which is marked as a "fail", though will see if it runs differently on linux than osx.. - changelog for psycopg2cffi, fixes #3052 --- lib/sqlalchemy/dialects/postgresql/psycopg2cffi.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'lib/sqlalchemy/dialects') diff --git a/lib/sqlalchemy/dialects/postgresql/psycopg2cffi.py b/lib/sqlalchemy/dialects/postgresql/psycopg2cffi.py index 5217c5561..f5c475d90 100644 --- a/lib/sqlalchemy/dialects/postgresql/psycopg2cffi.py +++ b/lib/sqlalchemy/dialects/postgresql/psycopg2cffi.py @@ -1,5 +1,5 @@ # testing/engines.py -# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors +# Copyright (C) 2005-2015 the SQLAlchemy authors and contributors # # # This module is part of SQLAlchemy and is released under @@ -13,9 +13,11 @@ postgresql+psycopg2cffi://user:password@host:port/dbname\ [?key=value&key=value...] :url: http://pypi.python.org/pypi/psycopg2cffi/ -`psycopg2cffi` is an adaptation of `psycopg2`, using CFFI for the C +``psycopg2cffi`` is an adaptation of ``psycopg2``, using CFFI for the C layer. This makes it suitable for use in e.g. PyPy. Documentation -is as per `psycopg2`. +is as per ``psycopg2``. + +.. versionadded:: 1.0.0 .. seealso:: @@ -27,6 +29,7 @@ from .psycopg2 import PGDialect_psycopg2 class PGDialect_psycopg2cffi(PGDialect_psycopg2): driver = 'psycopg2cffi' + supports_unicode_statements = True @classmethod def dbapi(cls): -- cgit v1.2.1 From bf70f556b382dc376783efbcb598e0fab71ee235 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sat, 31 Jan 2015 19:04:54 -0500 Subject: - Added support for the :class:`postgresql.JSONB` datatype when using psycopg2 2.5.4 or greater, which features native conversion of JSONB data so that SQLAlchemy's converters must be disabled; additionally, the newly added psycopg2 extension ``extras.register_default_jsonb`` is used to establish a JSON deserializer passed to the dialect via the ``json_deserializer`` argument. Also repaired the Postgresql integration tests which weren't actually round-tripping the JSONB type as opposed to the JSON type. Pull request courtesy Mateusz Susik. - Repaired the use of the "array_oid" flag when registering the HSTORE type with older psycopg2 versions < 2.4.3, which does not support this flag, as well as use of the native json serializer hook "register_default_json" with user-defined ``json_deserializer`` on psycopg2 versions < 2.5, which does not include native json. --- lib/sqlalchemy/dialects/postgresql/psycopg2.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) (limited to 'lib/sqlalchemy/dialects') diff --git a/lib/sqlalchemy/dialects/postgresql/psycopg2.py b/lib/sqlalchemy/dialects/postgresql/psycopg2.py index 21011b68b..26e45fed2 100644 --- a/lib/sqlalchemy/dialects/postgresql/psycopg2.py +++ b/lib/sqlalchemy/dialects/postgresql/psycopg2.py @@ -263,14 +263,17 @@ HSTORE type The ``psycopg2`` DBAPI includes an extension to natively handle marshalling of the HSTORE type. The SQLAlchemy psycopg2 dialect will enable this extension -by default when it is detected that the target database has the HSTORE -type set up for use. In other words, when the dialect makes the first +by default when psycopg2 version 2.4 or greater is used, and +it is detected that the target database has the HSTORE type set up for use. +In other words, when the dialect makes the first connection, a sequence like the following is performed: 1. Request the available HSTORE oids using ``psycopg2.extras.HstoreAdapter.get_oids()``. If this function returns a list of HSTORE identifiers, we then determine that the ``HSTORE`` extension is present. + This function is **skipped** if the version of psycopg2 installed is + less than version 2.4. 2. If the ``use_native_hstore`` flag is at its default of ``True``, and we've detected that ``HSTORE`` oids are available, the @@ -583,19 +586,22 @@ class PGDialect_psycopg2(PGDialect): hstore_oids = self._hstore_oids(conn) if hstore_oids is not None: oid, array_oid = hstore_oids + kw = {'oid': oid} if util.py2k: - extras.register_hstore(conn, oid=oid, - array_oid=array_oid, - unicode=True) - else: - extras.register_hstore(conn, oid=oid, - array_oid=array_oid) + kw['unicode'] = True + if self.psycopg2_version >= (2, 4, 3): + kw['array_oid'] = array_oid + extras.register_hstore(conn, **kw) fns.append(on_connect) if self.dbapi and self._json_deserializer: def on_connect(conn): - extras.register_default_json( - conn, loads=self._json_deserializer) + if self._has_native_json: + extras.register_default_json( + conn, loads=self._json_deserializer) + if self._has_native_jsonb: + extras.register_default_jsonb( + conn, loads=self._json_deserializer) fns.append(on_connect) if fns: -- cgit v1.2.1 From b2189da65019ed2f44e77933a122619489319c5a Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sun, 1 Feb 2015 19:00:07 -0500 Subject: - Repaired support for Postgresql UUID types in conjunction with the ARRAY type when using psycopg2. The psycopg2 dialect now employs use of the psycopg2.extras.register_uuid() hook so that UUID values are always passed to/from the DBAPI as UUID() objects. The :paramref:`.UUID.as_uuid` flag is still honored, except with psycopg2 we need to convert returned UUID objects back into strings when this is disabled. fixes #2940 --- lib/sqlalchemy/dialects/postgresql/psycopg2.py | 38 ++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) (limited to 'lib/sqlalchemy/dialects') diff --git a/lib/sqlalchemy/dialects/postgresql/psycopg2.py b/lib/sqlalchemy/dialects/postgresql/psycopg2.py index 26e45fed2..4f1e04f20 100644 --- a/lib/sqlalchemy/dialects/postgresql/psycopg2.py +++ b/lib/sqlalchemy/dialects/postgresql/psycopg2.py @@ -312,10 +312,15 @@ from ... import types as sqltypes from .base import PGDialect, PGCompiler, \ PGIdentifierPreparer, PGExecutionContext, \ ENUM, ARRAY, _DECIMAL_TYPES, _FLOAT_TYPES,\ - _INT_TYPES + _INT_TYPES, UUID from .hstore import HSTORE from .json import JSON, JSONB +try: + from uuid import UUID as _python_UUID +except ImportError: + _python_UUID = None + logger = logging.getLogger('sqlalchemy.dialects.postgresql') @@ -388,6 +393,26 @@ class _PGJSONB(JSONB): else: return super(_PGJSONB, self).result_processor(dialect, coltype) + +class _PGUUID(UUID): + def bind_processor(self, dialect): + if not self.as_uuid and dialect.use_native_uuid: + nonetype = type(None) + + def process(value): + if value is not None: + value = _python_UUID(value) + return value + return process + + def result_processor(self, dialect, coltype): + if not self.as_uuid and dialect.use_native_uuid: + def process(value): + if value is not None: + value = str(value) + return value + return process + # When we're handed literal SQL, ensure it's a SELECT query. Since # 8.3, combining cursors and "FOR UPDATE" has been fine. SERVER_SIDE_CURSOR_RE = re.compile( @@ -488,18 +513,20 @@ class PGDialect_psycopg2(PGDialect): sqltypes.Enum: _PGEnum, # needs force_unicode HSTORE: _PGHStore, JSON: _PGJSON, - JSONB: _PGJSONB + JSONB: _PGJSONB, + UUID: _PGUUID } ) def __init__(self, server_side_cursors=False, use_native_unicode=True, client_encoding=None, - use_native_hstore=True, + use_native_hstore=True, use_native_uuid=True, **kwargs): PGDialect.__init__(self, **kwargs) self.server_side_cursors = server_side_cursors self.use_native_unicode = use_native_unicode self.use_native_hstore = use_native_hstore + self.use_native_uuid = use_native_uuid self.supports_unicode_binds = use_native_unicode self.client_encoding = client_encoding if self.dbapi and hasattr(self.dbapi, '__version__'): @@ -575,6 +602,11 @@ class PGDialect_psycopg2(PGDialect): self.set_isolation_level(conn, self.isolation_level) fns.append(on_connect) + if self.dbapi and self.use_native_uuid: + def on_connect(conn): + extras.register_uuid(None, conn) + fns.append(on_connect) + if self.dbapi and self.use_native_unicode: def on_connect(conn): extensions.register_type(extensions.UNICODE, conn) -- cgit v1.2.1 From 503a40ad7080344f75cf3347197c44b574c60a9c Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Thu, 5 Feb 2015 16:17:23 -0500 Subject: - The MySQL dialect now renders TIMESTAMP with NULL / NOT NULL in all cases, so that MySQL 5.6.6 with the ``explicit_defaults_for_timestamp`` flag enabled will will allow TIMESTAMP to continue to work as expected when ``nullable=False``. Existing applications are unaffected as SQLAlchemy has always emitted NULL for a TIMESTAMP column that is ``nullable=True``. fixes #3155 --- lib/sqlalchemy/dialects/mysql/base.py | 88 ++++++++++++++++------------------- 1 file changed, 40 insertions(+), 48 deletions(-) (limited to 'lib/sqlalchemy/dialects') diff --git a/lib/sqlalchemy/dialects/mysql/base.py b/lib/sqlalchemy/dialects/mysql/base.py index c8e33bfb2..cbb108f5e 100644 --- a/lib/sqlalchemy/dialects/mysql/base.py +++ b/lib/sqlalchemy/dialects/mysql/base.py @@ -370,10 +370,11 @@ collection. TIMESTAMP Columns and NULL -------------------------- -MySQL enforces that a column which specifies the TIMESTAMP datatype implicitly -includes a default value of CURRENT_TIMESTAMP, even though this is not -stated, and additionally sets the column as NOT NULL, the opposite behavior -vs. that of all other datatypes:: +MySQL historically enforces that a column which specifies the +TIMESTAMP datatype implicitly includes a default value of +CURRENT_TIMESTAMP, even though this is not stated, and additionally +sets the column as NOT NULL, the opposite behavior vs. that of all +other datatypes:: mysql> CREATE TABLE ts_test ( -> a INTEGER, @@ -400,22 +401,29 @@ with NOT NULL. But when the column is of type TIMESTAMP, an implicit default of CURRENT_TIMESTAMP is generated which also coerces the column to be a NOT NULL, even though we did not specify it as such. -Therefore, the usual "NOT NULL" clause *does not apply* to a TIMESTAMP -column; MySQL selects this implicitly. SQLAlchemy therefore does not render -NOT NULL for a TIMESTAMP column on MySQL. However, it *does* render -NULL when we specify nullable=True, or if we leave nullable absent, as it -also defaults to True. This is to accommodate the essentially -reverse behavior of the NULL flag for TIMESTAMP:: +This behavior of MySQL can be changed on the MySQL side using the +`explicit_defaults_for_timestamp +`_ configuration flag introduced in +MySQL 5.6. With this server setting enabled, TIMESTAMP columns behave like +any other datatype on the MySQL side with regards to defaults and nullability. - from sqlalchemy import MetaData, TIMESTAMP, Integer, Table, Column, text +However, to accommodate the vast majority of MySQL databases that do not +specify this new flag, SQLAlchemy emits the "NULL" specifier explicitly with +any TIMESTAMP column that does not specify ``nullable=False``. In order +to accommodate newer databases that specify ``explicit_defaults_for_timestamp``, +SQLAlchemy also emits NOT NULL for TIMESTAMP columns that do specify +``nullable=False``. The following example illustrates:: + + from sqlalchemy import MetaData, Integer, Table, Column, text + from sqlalchemy.dialects.mysql import TIMESTAMP m = MetaData() t = Table('ts_test', m, Column('a', Integer), Column('b', Integer, nullable=False), Column('c', TIMESTAMP), - Column('d', TIMESTAMP, nullable=False), - Column('e', TIMESTAMP, nullable=True) + Column('d', TIMESTAMP, nullable=False) ) @@ -423,35 +431,19 @@ reverse behavior of the NULL flag for TIMESTAMP:: e = create_engine("mysql://scott:tiger@localhost/test", echo=True) m.create_all(e) -In the output, we can see that the TIMESTAMP column receives a different -treatment for NULL / NOT NULL vs. that of the INTEGER:: +output:: CREATE TABLE ts_test ( a INTEGER, b INTEGER NOT NULL, c TIMESTAMP NULL, - d TIMESTAMP, - e TIMESTAMP NULL + d TIMESTAMP NOT NULL ) -MySQL above receives the NULL/NOT NULL constraint as is stated in our -original :class:`.Table`:: - - mysql> SHOW CREATE TABLE ts_test; - +---------+--------------------------- - | Table | Create Table - +---------+--------------------------- - | ts_test | CREATE TABLE `ts_test` ( - `a` int(11) DEFAULT NULL, - `b` int(11) NOT NULL, - `c` timestamp NULL DEFAULT NULL, - `d` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', - `e` timestamp NULL DEFAULT NULL - ) ENGINE=MyISAM DEFAULT CHARSET=latin1 - -Be sure to always favor the ``SHOW CREATE TABLE`` output over the -SQLAlchemy-emitted DDL when checking table definitions, as MySQL's -rules can be hard to predict. +.. versionchanged:: 1.0.0 - SQLAlchemy now renders NULL or NOT NULL in all + cases for TIMESTAMP columns, to accommodate + ``explicit_defaults_for_timestamp``. Prior to this version, it will + not render "NOT NULL" for a TIMESTAMP column that is ``nullable=False``. """ @@ -1865,19 +1857,20 @@ class MySQLDDLCompiler(compiler.DDLCompiler): column.type, type_expression=column) ] - default = self.get_column_default_string(column) - if default is not None: - colspec.append('DEFAULT ' + default) - is_timestamp = isinstance(column.type, sqltypes.TIMESTAMP) - if not column.nullable and not is_timestamp: + + if not column.nullable: colspec.append('NOT NULL') # see: http://docs.sqlalchemy.org/en/latest/dialects/ # mysql.html#mysql_timestamp_null - elif column.nullable and is_timestamp and default is None: + elif column.nullable and is_timestamp: colspec.append('NULL') + default = self.get_column_default_string(column) + if default is not None: + colspec.append('DEFAULT ' + default) + if column is column.table._autoincrement_column and \ column.server_default is None: colspec.append('AUTO_INCREMENT') @@ -3007,8 +3000,7 @@ class MySQLTableDefinitionParser(object): if not spec['full']: util.warn("Incomplete reflection of column definition %r" % line) - name, type_, args, notnull = \ - spec['name'], spec['coltype'], spec['arg'], spec['notnull'] + name, type_, args = spec['name'], spec['coltype'], spec['arg'] try: col_type = self.dialect.ischema_names[type_] @@ -3033,7 +3025,6 @@ class MySQLTableDefinitionParser(object): for kw in ('charset', 'collate'): if spec.get(kw, False): type_kw[kw] = spec[kw] - if issubclass(col_type, _EnumeratedValues): type_args = _EnumeratedValues._strip_values(type_args) @@ -3042,11 +3033,12 @@ class MySQLTableDefinitionParser(object): type_instance = col_type(*type_args, **type_kw) - col_args, col_kw = [], {} + col_kw = {} # NOT NULL col_kw['nullable'] = True - if spec.get('notnull', False): + # this can be "NULL" in the case of TIMESTAMP + if spec.get('notnull', False) == 'NOT NULL': col_kw['nullable'] = False # AUTO_INCREMENT @@ -3165,7 +3157,7 @@ class MySQLTableDefinitionParser(object): r'(?: +(?PZEROFILL))?' r'(?: +CHARACTER SET +(?P[\w_]+))?' r'(?: +COLLATE +(?P[\w_]+))?' - r'(?: +(?PNOT NULL))?' + r'(?: +(?P(?:NOT )?NULL))?' r'(?: +DEFAULT +(?P' r'(?:NULL|\x27(?:\x27\x27|[^\x27])*\x27|\w+' r'(?: +ON UPDATE \w+)?)' @@ -3185,7 +3177,7 @@ class MySQLTableDefinitionParser(object): r'%(iq)s(?P(?:%(esc_fq)s|[^%(fq)s])+)%(fq)s +' r'(?P\w+)' r'(?:\((?P(?:\d+|\d+,\d+|\x27(?:\x27\x27|[^\x27])+\x27))\))?' - r'.*?(?PNOT NULL)?' + r'.*?(?P(?:NOT )NULL)?' % quotes ) -- cgit v1.2.1 From 00f37f3984c35fc4a2ee5687e798c5ca36953d6c Mon Sep 17 00:00:00 2001 From: David Marin Date: Thu, 5 Feb 2015 16:51:57 -0800 Subject: fix Python 2-specific byte conversion --- lib/sqlalchemy/dialects/mysql/base.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'lib/sqlalchemy/dialects') diff --git a/lib/sqlalchemy/dialects/mysql/base.py b/lib/sqlalchemy/dialects/mysql/base.py index cbb108f5e..1235de49e 100644 --- a/lib/sqlalchemy/dialects/mysql/base.py +++ b/lib/sqlalchemy/dialects/mysql/base.py @@ -881,7 +881,9 @@ class BIT(sqltypes.TypeEngine): def process(value): if value is not None: v = 0 - for i in map(ord, value): + for i in value: + if not isinstance(i, int): + i = ord(i) # convert byte to int on Python 2 v = v << 8 | i return v return value -- cgit v1.2.1