Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/sqlalchemy/dialects/postgresql/pg8000.py : 40%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# postgresql/pg8000.py
2# Copyright (C) 2005-2020 the SQLAlchemy authors and contributors <see AUTHORS
3# file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: http://www.opensource.org/licenses/mit-license.php
7r"""
8.. dialect:: postgresql+pg8000
9 :name: pg8000
10 :dbapi: pg8000
11 :connectstring: postgresql+pg8000://user:password@host:port/dbname[?key=value&key=value...]
12 :url: https://pythonhosted.org/pg8000/
14.. note::
16 The pg8000 dialect is **not tested as part of SQLAlchemy's continuous
17 integration** and may have unresolved issues. The recommended PostgreSQL
18 dialect is psycopg2.
20.. _pg8000_unicode:
22Unicode
23-------
25pg8000 will encode / decode string values between it and the server using the
26PostgreSQL ``client_encoding`` parameter; by default this is the value in
27the ``postgresql.conf`` file, which often defaults to ``SQL_ASCII``.
28Typically, this can be changed to ``utf-8``, as a more useful default::
30 #client_encoding = sql_ascii # actually, defaults to database
31 # encoding
32 client_encoding = utf8
34The ``client_encoding`` can be overridden for a session by executing the SQL:
36SET CLIENT_ENCODING TO 'utf8';
38SQLAlchemy will execute this SQL on all new connections based on the value
39passed to :func:`_sa.create_engine` using the ``client_encoding`` parameter::
41 engine = create_engine(
42 "postgresql+pg8000://user:pass@host/dbname", client_encoding='utf8')
45.. _pg8000_isolation_level:
47pg8000 Transaction Isolation Level
48-------------------------------------
50The pg8000 dialect offers the same isolation level settings as that
51of the :ref:`psycopg2 <psycopg2_isolation_level>` dialect:
53* ``READ COMMITTED``
54* ``READ UNCOMMITTED``
55* ``REPEATABLE READ``
56* ``SERIALIZABLE``
57* ``AUTOCOMMIT``
59.. versionadded:: 0.9.5 support for AUTOCOMMIT isolation level when using
60 pg8000.
62.. seealso::
64 :ref:`postgresql_isolation_level`
66 :ref:`psycopg2_isolation_level`
69""" # noqa
70import decimal
71import re
73from .base import _DECIMAL_TYPES
74from .base import _FLOAT_TYPES
75from .base import _INT_TYPES
76from .base import PGCompiler
77from .base import PGDialect
78from .base import PGExecutionContext
79from .base import PGIdentifierPreparer
80from .base import UUID
81from .json import JSON
82from ... import exc
83from ... import processors
84from ... import types as sqltypes
85from ... import util
86from ...sql.elements import quoted_name
89try:
90 from uuid import UUID as _python_UUID # noqa
91except ImportError:
92 _python_UUID = None
95class _PGNumeric(sqltypes.Numeric):
96 def result_processor(self, dialect, coltype):
97 if self.asdecimal:
98 if coltype in _FLOAT_TYPES:
99 return processors.to_decimal_processor_factory(
100 decimal.Decimal, self._effective_decimal_return_scale
101 )
102 elif coltype in _DECIMAL_TYPES or coltype in _INT_TYPES:
103 # pg8000 returns Decimal natively for 1700
104 return None
105 else:
106 raise exc.InvalidRequestError(
107 "Unknown PG numeric type: %d" % coltype
108 )
109 else:
110 if coltype in _FLOAT_TYPES:
111 # pg8000 returns float natively for 701
112 return None
113 elif coltype in _DECIMAL_TYPES or coltype in _INT_TYPES:
114 return processors.to_float
115 else:
116 raise exc.InvalidRequestError(
117 "Unknown PG numeric type: %d" % coltype
118 )
121class _PGNumericNoBind(_PGNumeric):
122 def bind_processor(self, dialect):
123 return None
126class _PGJSON(JSON):
127 def result_processor(self, dialect, coltype):
128 if dialect._dbapi_version > (1, 10, 1):
129 return None # Has native JSON
130 else:
131 return super(_PGJSON, self).result_processor(dialect, coltype)
134class _PGUUID(UUID):
135 def bind_processor(self, dialect):
136 if not self.as_uuid:
138 def process(value):
139 if value is not None:
140 value = _python_UUID(value)
141 return value
143 return process
145 def result_processor(self, dialect, coltype):
146 if not self.as_uuid:
148 def process(value):
149 if value is not None:
150 value = str(value)
151 return value
153 return process
156class PGExecutionContext_pg8000(PGExecutionContext):
157 pass
160class PGCompiler_pg8000(PGCompiler):
161 def visit_mod_binary(self, binary, operator, **kw):
162 return (
163 self.process(binary.left, **kw)
164 + " %% "
165 + self.process(binary.right, **kw)
166 )
168 def post_process_text(self, text):
169 if "%%" in text:
170 util.warn(
171 "The SQLAlchemy postgresql dialect "
172 "now automatically escapes '%' in text() "
173 "expressions to '%%'."
174 )
175 return text.replace("%", "%%")
178class PGIdentifierPreparer_pg8000(PGIdentifierPreparer):
179 def _escape_identifier(self, value):
180 value = value.replace(self.escape_quote, self.escape_to_quote)
181 return value.replace("%", "%%")
184class PGDialect_pg8000(PGDialect):
185 driver = "pg8000"
187 supports_unicode_statements = True
189 supports_unicode_binds = True
191 default_paramstyle = "format"
192 supports_sane_multi_rowcount = True
193 execution_ctx_cls = PGExecutionContext_pg8000
194 statement_compiler = PGCompiler_pg8000
195 preparer = PGIdentifierPreparer_pg8000
196 description_encoding = "use_encoding"
198 colspecs = util.update_copy(
199 PGDialect.colspecs,
200 {
201 sqltypes.Numeric: _PGNumericNoBind,
202 sqltypes.Float: _PGNumeric,
203 JSON: _PGJSON,
204 sqltypes.JSON: _PGJSON,
205 UUID: _PGUUID,
206 },
207 )
209 def __init__(self, client_encoding=None, **kwargs):
210 PGDialect.__init__(self, **kwargs)
211 self.client_encoding = client_encoding
213 def initialize(self, connection):
214 self.supports_sane_multi_rowcount = self._dbapi_version >= (1, 9, 14)
215 super(PGDialect_pg8000, self).initialize(connection)
217 @util.memoized_property
218 def _dbapi_version(self):
219 if self.dbapi and hasattr(self.dbapi, "__version__"):
220 return tuple(
221 [
222 int(x)
223 for x in re.findall(
224 r"(\d+)(?:[-\.]?|$)", self.dbapi.__version__
225 )
226 ]
227 )
228 else:
229 return (99, 99, 99)
231 @classmethod
232 def dbapi(cls):
233 return __import__("pg8000")
235 def create_connect_args(self, url):
236 opts = url.translate_connect_args(username="user")
237 if "port" in opts:
238 opts["port"] = int(opts["port"])
239 opts.update(url.query)
240 return ([], opts)
242 def is_disconnect(self, e, connection, cursor):
243 return "connection is closed" in str(e)
245 def set_isolation_level(self, connection, level):
246 level = level.replace("_", " ")
248 # adjust for ConnectionFairy possibly being present
249 if hasattr(connection, "connection"):
250 connection = connection.connection
252 if level == "AUTOCOMMIT":
253 connection.autocommit = True
254 elif level in self._isolation_lookup:
255 connection.autocommit = False
256 cursor = connection.cursor()
257 cursor.execute(
258 "SET SESSION CHARACTERISTICS AS TRANSACTION "
259 "ISOLATION LEVEL %s" % level
260 )
261 cursor.execute("COMMIT")
262 cursor.close()
263 else:
264 raise exc.ArgumentError(
265 "Invalid value '%s' for isolation_level. "
266 "Valid isolation levels for %s are %s or AUTOCOMMIT"
267 % (level, self.name, ", ".join(self._isolation_lookup))
268 )
270 def set_client_encoding(self, connection, client_encoding):
271 # adjust for ConnectionFairy possibly being present
272 if hasattr(connection, "connection"):
273 connection = connection.connection
275 cursor = connection.cursor()
276 cursor.execute("SET CLIENT_ENCODING TO '" + client_encoding + "'")
277 cursor.execute("COMMIT")
278 cursor.close()
280 def do_begin_twophase(self, connection, xid):
281 connection.connection.tpc_begin((0, xid, ""))
283 def do_prepare_twophase(self, connection, xid):
284 connection.connection.tpc_prepare()
286 def do_rollback_twophase(
287 self, connection, xid, is_prepared=True, recover=False
288 ):
289 connection.connection.tpc_rollback((0, xid, ""))
291 def do_commit_twophase(
292 self, connection, xid, is_prepared=True, recover=False
293 ):
294 connection.connection.tpc_commit((0, xid, ""))
296 def do_recover_twophase(self, connection):
297 return [row[1] for row in connection.connection.tpc_recover()]
299 def on_connect(self):
300 fns = []
302 def on_connect(conn):
303 conn.py_types[quoted_name] = conn.py_types[util.text_type]
305 fns.append(on_connect)
307 if self.client_encoding is not None:
309 def on_connect(conn):
310 self.set_client_encoding(conn, self.client_encoding)
312 fns.append(on_connect)
314 if self.isolation_level is not None:
316 def on_connect(conn):
317 self.set_isolation_level(conn, self.isolation_level)
319 fns.append(on_connect)
321 if len(fns) > 0:
323 def on_connect(conn):
324 for fn in fns:
325 fn(conn)
327 return on_connect
328 else:
329 return None
332dialect = PGDialect_pg8000