From 7bbeb669b9170550e6a34e265783e18f8470e519 Mon Sep 17 00:00:00 2001 From: ilvalle Date: Tue, 27 Jan 2015 14:04:24 +0100 Subject: [PATCH] updated pg8000 imports --- gluon/contrib/pg8000/__init__.py | 21 +++++--- gluon/contrib/pg8000/_version.py | 88 ++++++++++++++++---------------- gluon/contrib/pg8000/core.py | 77 ++++++++++++++++------------ gluon/dal.py | 7 ++- 4 files changed, 108 insertions(+), 85 deletions(-) diff --git a/gluon/contrib/pg8000/__init__.py b/gluon/contrib/pg8000/__init__.py index 698a63f3..c30a8cb6 100644 --- a/gluon/contrib/pg8000/__init__.py +++ b/gluon/contrib/pg8000/__init__.py @@ -38,7 +38,7 @@ for fmt in ( import datetime import time -from pg8000.six import binary_type, integer_types, PY2 +from .six import binary_type, integer_types, PY2 min_int2, max_int2 = -2 ** 15, 2 ** 15 min_int4, max_int4 = -2 ** 31, 2 ** 31 @@ -274,7 +274,7 @@ class Interval(object): def __neq__(self, other): return not self.__eq__(other) -import pg8000.core +from .core import Connection def connect( @@ -291,6 +291,10 @@ def connect( provided, pg8000 looks first for the PGUSER then the USER environment variables. + If your server character encoding is not ``ascii`` or ``utf8``, then + you need to provide ``user`` as bytes, eg. + ``"my_name".encode('EUC-JP')``. + :keyword host: The hostname of the PostgreSQL server to connect with. Providing this parameter is necessary for TCP/IP connections. One of either ``host`` @@ -311,6 +315,10 @@ def connect( optional; if omitted, the PostgreSQL server will assume the database name is the same as the username. + If your server character encoding is not ``ascii`` or ``utf8``, then + you need to provide ``database`` as bytes, eg. + ``"my_db".encode('EUC-JP')``. + :keyword password: The user password to connect to the server with. This parameter is optional; if omitted and the database server requests password-based @@ -324,7 +332,7 @@ def connect( :rtype: A :class:`Connection` object. """ - return pg8000.core.Connection( + return Connection( user, host, unix_sock, port, database, password, ssl) apilevel = "2.0" @@ -472,7 +480,7 @@ def Binary(value): return value -from pg8000.core import utc, Connection, Cursor +from .core import utc, Cursor __all__ = [ Warning, Bytea, DataError, DatabaseError, connect, InterfaceError, @@ -481,10 +489,11 @@ __all__ = [ ArrayDimensionsNotConsistentError, ArrayContentNotSupportedError, utc, Connection, Cursor] -from ._version import get_versions -__version__ = get_versions()['version'] """Version string for pg8000. .. versionadded:: 1.9.11 """ + +from ._version import get_versions +__version__ = get_versions()['version'] del get_versions diff --git a/gluon/contrib/pg8000/_version.py b/gluon/contrib/pg8000/_version.py index 77df96f3..982cf246 100644 --- a/gluon/contrib/pg8000/_version.py +++ b/gluon/contrib/pg8000/_version.py @@ -1,20 +1,26 @@ # This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag -# feature). Distribution tarballs (build by setup.py sdist) and build +# feature). Distribution tarballs (built by setup.py sdist) and build # directories (produced by setup.py build) will contain a much shorter file # that just contains the computed version number. # This file is released into the public domain. Generated by -# versioneer-0.10 (https://github.com/warner/python-versioneer) +# versioneer-0.12 (https://github.com/warner/python-versioneer) # these strings will be replaced by git during git-archive git_refnames = "$Format:%d$" git_full = "$Format:%H$" +# these strings are filled in when 'setup.py versioneer' creates _version.py +tag_prefix = "" +parentdir_prefix = "pg8000-" +versionfile_source = "pg8000/_version.py" -import subprocess +import os import sys +import re +import subprocess import errno @@ -50,38 +56,49 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): return stdout -import re -import os.path +def versions_from_parentdir(parentdir_prefix, root, verbose=False): + # Source tarballs conventionally unpack into a directory that includes + # both the project name and a version string. + dirname = os.path.basename(root) + if not dirname.startswith(parentdir_prefix): + if verbose: + print( + "guessing rootdir is '%s', but '%s' doesn't start with " + "prefix '%s'" % (root, dirname, parentdir_prefix)) + return None + return {"version": dirname[len(parentdir_prefix):], "full": ""} -def get_expanded_variables(versionfile_abs): +def git_get_keywords(versionfile_abs): # the code embedded in _version.py can just fetch the value of these - # variables. When used from setup.py, we don't want to import - # _version.py, so we do it with a regexp instead. This function is not - # used from _version.py. - variables = {} + # keywords. When used from setup.py, we don't want to import _version.py, + # so we do it with a regexp instead. This function is not used from + # _version.py. + keywords = {} try: f = open(versionfile_abs, "r") for line in f.readlines(): if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) if mo: - variables["refnames"] = mo.group(1) + keywords["refnames"] = mo.group(1) if line.strip().startswith("git_full ="): mo = re.search(r'=\s*"(.*)"', line) if mo: - variables["full"] = mo.group(1) + keywords["full"] = mo.group(1) f.close() except EnvironmentError: pass - return variables + return keywords -def versions_from_expanded_variables(variables, tag_prefix, verbose=False): - refnames = variables["refnames"].strip() +def git_versions_from_keywords(keywords, tag_prefix, verbose=False): + if not keywords: + return {} # keyword-finding function failed to find keywords + refnames = keywords["refnames"].strip() if refnames.startswith("$Format"): if verbose: - print("variables are unexpanded, not using") + print("keywords are unexpanded, not using") return {} # unexpanded, so not in an unpacked git-archive tarball refs = set([r.strip() for r in refnames.strip("()").split(",")]) # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of @@ -107,18 +124,20 @@ def versions_from_expanded_variables(variables, tag_prefix, verbose=False): r = ref[len(tag_prefix):] if verbose: print("picking %s" % r) - return {"version": r, "full": variables["full"].strip()} + return { + "version": r, + "full": keywords["full"].strip()} # no suitable tags, so we use the full revision id if verbose: print("no suitable tags, using full revision id") return { - "version": variables["full"].strip(), - "full": variables["full"].strip()} + "version": keywords["full"].strip(), + "full": keywords["full"].strip()} -def versions_from_vcs(tag_prefix, root, verbose=False): +def git_versions_from_vcs(tag_prefix, root, verbose=False): # this runs 'git' from the root of the source tree. This only gets called - # if the git-archive 'subst' variables were *not* expanded, and + # if the git-archive 'subst' keywords were *not* expanded, and # _version.py hasn't already been rewritten with a short version string, # meaning we're inside a checked out source tree. @@ -150,31 +169,14 @@ def versions_from_vcs(tag_prefix, root, verbose=False): return {"version": tag, "full": full} -def versions_from_parentdir(parentdir_prefix, root, verbose=False): - # Source tarballs conventionally unpack into a directory that includes - # both the project name and a version string. - dirname = os.path.basename(root) - if not dirname.startswith(parentdir_prefix): - if verbose: - print( - "guessing rootdir is '%s', but '%s' doesn't start with prefix " - "'%s'" % (root, dirname, parentdir_prefix)) - return None - return {"version": dirname[len(parentdir_prefix):], "full": ""} - -tag_prefix = "" -parentdir_prefix = "pg8000-" -versionfile_source = "pg8000/_version.py" - - def get_versions(default={"version": "unknown", "full": ""}, verbose=False): # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have # __file__, we can work backwards from there to the root. Some # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which - # case we can only use expanded variables. + # case we can only use expanded keywords. - variables = {"refnames": git_refnames, "full": git_full} - ver = versions_from_expanded_variables(variables, tag_prefix, verbose) + keywords = {"refnames": git_refnames, "full": git_full} + ver = git_versions_from_keywords(keywords, tag_prefix, verbose) if ver: return ver @@ -183,11 +185,11 @@ def get_versions(default={"version": "unknown", "full": ""}, verbose=False): # versionfile_source is the relative path from the top of the source # tree (where the .git directory might live) to this file. Invert # this to find the root from __file__. - for i in range(len(versionfile_source.split("/"))): + for i in range(len(versionfile_source.split(os.sep))): root = os.path.dirname(root) except NameError: return default - return (versions_from_vcs(tag_prefix, root, verbose) + return (git_versions_from_vcs(tag_prefix, root, verbose) or versions_from_parentdir(parentdir_prefix, root, verbose) or default) diff --git a/gluon/contrib/pg8000/core.py b/gluon/contrib/pg8000/core.py index 83a79d41..f19481bd 100644 --- a/gluon/contrib/pg8000/core.py +++ b/gluon/contrib/pg8000/core.py @@ -29,7 +29,7 @@ __author__ = "Mathieu Fenniak" import datetime from datetime import timedelta -from pg8000 import ( +from . import ( Interval, min_int2, max_int2, min_int4, max_int4, min_int8, max_int8, Bytea, NotSupportedError, ProgrammingError, InternalError, IntegrityError, OperationalError, DatabaseError, InterfaceError, Error, @@ -45,11 +45,10 @@ import threading from struct import pack from hashlib import md5 from decimal import Decimal -import pg8000 from collections import deque, defaultdict from itertools import count, islice -from pg8000.six.moves import map -from pg8000.six import b, PY2, integer_types, next, PRE_26, text_type, u +from .six.moves import map +from .six import b, PY2, integer_types, next, PRE_26, text_type, u from sys import exc_info from uuid import UUID from copy import deepcopy @@ -57,6 +56,12 @@ from calendar import timegm import os from distutils.version import LooseVersion +try: + from json import loads +except ImportError: + pass # Can only use JSON with Python 2.6 and above + + ZERO = timedelta(0) @@ -891,7 +896,7 @@ class Connection(object): return error def __init__(self, user, host, unix_sock, port, database, password, ssl): - self._client_encoding = "ascii" + self._client_encoding = "utf8" self._commands_with_count = ( b("INSERT"), b("DELETE"), b("UPDATE"), b("MOVE"), b("FETCH"), b("COPY"), b("SELECT")) @@ -911,6 +916,9 @@ class Connection(object): else: self.user = user + if isinstance(self.user, text_type): + self.user = self.user.encode('utf8') + self.password = password self.autocommit = False self._xid = None @@ -1085,6 +1093,10 @@ class Connection(object): def bool_recv(d, o, l): return d[o] == "\x01" + def json_in(data, offset, length): + return loads(unicode( # noqa + data[offset: offset + length], self._client_encoding)) + else: def text_recv(data, offset, length): return str( @@ -1093,6 +1105,10 @@ class Connection(object): def bool_recv(data, offset, length): return data[offset] == 1 + def json_in(data, offset, length): + return loads( + str(data[offset: offset + length], self._client_encoding)) + def time_in(data, offset, length): hour = int(data[offset:offset + 2]) minute = int(data[offset + 3:offset + 5]) @@ -1131,6 +1147,7 @@ class Connection(object): 25: (FC_BINARY, text_recv), # TEXT type 26: (FC_TEXT, int_in), # oid 28: (FC_TEXT, int_in), # xid + 114: (FC_TEXT, json_in), # json 700: (FC_BINARY, float4_recv), # float4 701: (FC_BINARY, float8_recv), # float8 705: (FC_BINARY, text_recv), # unknown @@ -1157,6 +1174,7 @@ class Connection(object): 1700: (FC_TEXT, numeric_in), # NUMERIC 2275: (FC_BINARY, text_recv), # cstring 2950: (FC_BINARY, uuid_recv), # uuid + 3802: (FC_TEXT, json_in), # jsonb }) self.py_types = { @@ -1183,7 +1201,7 @@ class Connection(object): } if PY2: - self.py_types[pg8000.Bytea] = (17, FC_BINARY, bytea_send) # bytea + self.py_types[Bytea] = (17, FC_BINARY, bytea_send) # bytea self.py_types[text_type] = (705, FC_TEXT, text_out) # unknown self.py_types[long] = (705, FC_TEXT, unknown_out) # noqa @@ -1242,11 +1260,12 @@ class Connection(object): # String - A parameter name (user, database, or options) # String - Parameter value protocol = 196608 - val = bytearray(i_pack(protocol) + b("user\x00")) - val.extend(user.encode("ascii") + NULL_BYTE) + val = bytearray( + i_pack(protocol) + b("user\x00") + self.user + NULL_BYTE) if database is not None: - val.extend( - b("database\x00") + database.encode("ascii") + NULL_BYTE) + if isinstance(database, text_type): + database = database.encode('utf8') + val.extend(b("database\x00") + database + NULL_BYTE) val.append(0) self._write(i_pack(len(val) + 4)) self._write(val) @@ -1421,9 +1440,9 @@ class Connection(object): self._usock.close() self._sock = None except AttributeError: - raise pg8000.InterfaceError("connection is closed") + raise InterfaceError("connection is closed") except ValueError: - raise pg8000.InterfaceError("connection is closed") + raise InterfaceError("connection is closed") def close(self): """Closes the database connection. @@ -1478,10 +1497,8 @@ class Connection(object): "server requesting MD5 password authentication, but no " "password was provided") pwd = b("md5") + md5( - md5( - self.password.encode("ascii") + - self.user.encode("ascii")).hexdigest().encode("ascii") + - salt).hexdigest().encode("ascii") + md5(self.password.encode("ascii") + self.user). + hexdigest().encode("ascii") + salt).hexdigest().encode("ascii") # Byte1('p') - Identifies the message as a password message. # Int32 - Message length including self. # String - The password. Password may be encrypted. @@ -1529,26 +1546,22 @@ class Connection(object): count = h_unpack(data)[0] idx = 2 for i in range(count): - field = {'name': data[idx:data.find(NULL_BYTE, idx)]} - idx += len(field['name']) + 1 - field.update( - dict(zip(( - "table_oid", "column_attrnum", "type_oid", - "type_size", "type_modifier", "format"), - ihihih_unpack(data, idx)))) + name = data[idx:data.find(NULL_BYTE, idx)] + idx += len(name) + 1 + field = dict( + zip(( + "table_oid", "column_attrnum", "type_oid", "type_size", + "type_modifier", "format"), ihihih_unpack(data, idx))) + field['name'] = name idx += 18 cursor.ps['row_desc'].append(field) - try: - field['pg8000_fc'], field['func'] = self.pg_types[ - field['type_oid']] - except KeyError: - raise NotSupportedError( - "type oid " + exc_info()[1] + " not supported") + field['pg8000_fc'], field['func'] = \ + self.pg_types[field['type_oid']] def execute(self, cursor, operation, vals): if vals is None: vals = () - paramstyle = pg8000.paramstyle + from . import paramstyle cache = self._caches[paramstyle] try: @@ -1700,11 +1713,11 @@ class Connection(object): self._write(FLUSH_MSG) except ValueError: if str(exc_info()[1]) == "write to closed file": - raise pg8000.InterfaceError("connection is closed") + raise InterfaceError("connection is closed") else: raise exc_info()[1] except AttributeError: - raise pg8000.InterfaceError("connection is closed") + raise InterfaceError("connection is closed") def send_EXECUTE(self, cursor): # Byte1('E') - Identifies the message as an execute message. diff --git a/gluon/dal.py b/gluon/dal.py index 79381718..15ad2e1a 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- - from pydal import DAL as pyDAL from pydal import Field, SQLCustomType, geoPoint, geoLine, geoPolygon @@ -128,9 +127,9 @@ from pydal.drivers import DRIVERS if not DRIVERS.get('pymysql'): from .contrib import pymysql DRIVERS['pymysql'] = pymysql -#if not DRIVERS.get('pg8000'): -# from .contrib import pg8000 -# DRIVERS['pg8000'] = pg8000 +if not DRIVERS.get('pg8000'): + from .contrib import pg8000 + DRIVERS['pg8000'] = pg8000 if not DRIVERS.get('pyodbc'): from .contrib import pypyodbc as pyodbc DRIVERS['pyodbc'] = pyodbc