hi -
this is pretty well written, which is good to see. I got your test to run and the error is "TypeError: can't escape Type to binary" and that's being raised by psycopg2 for me.
your type is actually wrapping a nested type and there are issues in how that is working. first off the float needs to be a type instance, using self.inner = sqlalchemy.types.to_instance(inner), or otherwise pass a Float() explicitly.
then if we look what's coming in, the values are still seen as Binary, not as a simple string / float:
(Pdb) parameters
{'user_type': <psycopg2.extensions.Binary object at 0x7fb602b9d810>, 'pgp_sym_encrypt_1': 'PASSPHRASE_USER', 'reputation': <psycopg2.extensions.Binary object at 0x7fb602bad6c0>, 'pgp_sym_encrypt_2': 'PASSPHRASE_USER'}
next, the type handlers for Enum and Float, of which the former is necessary, aren't invoked at all because your "inner" type is not known to SQLAlchemy. so we have to add handlers for these as:
def process_bind_param(self, value, dialect):
proc = self.inner.bind_processor(dialect)
return proc(value)
def process_result_value(self, value, dialect):
proc = self.inner.result_processor(dialect, None)
return proc(value)
but that is still not enough because this TypeDecorator is running the BYTEA value processors on top of those in any case, putting the value inside of a psycopg2.Binary() which still fails, with the same message but different type, "TypeError: can't escape str to binary". so we have to also ensure we set the correct type for that nested bind parameter:
def bind_expression(self, bindvalue):
bindvalue.type = self.inner
bindvalue = sqlalchemy.cast(bindvalue, sqlalchemy.String)
return sqlalchemy.func.pgp_sym_encrypt(bindvalue, self.passphrase)
but still wrong because String is not instantiated, leading to the wrong CAST:
def bind_expression(self, bindvalue):
bindvalue.type = self.inner
bindvalue = sqlalchemy.cast(bindvalue, sqlalchemy.String())
return sqlalchemy.func.pgp_sym_encrypt(bindvalue, self.passphrase)
INSERT now succeeds.
SELECT fails with
sqlalchemy.exc.ProgrammingError: (psycopg2.errors.UndefinedObject) type "type" does not exist
LINE 1: ...ym_decrypt(users.user_type, 'PASSPHRASE_USER') AS type) AS u...
^
[SQL: SELECT
users.id AS users_id, CAST(pgp_sym_decrypt(users.user_type, %(pgp_sym_decrypt_1)s) AS type) AS users_user_type, CAST(pgp_sym_decrypt(users.reputation, %(pgp_sym_decrypt_2)s) AS FLOAT) AS users_reputation
FROM users]
still a "type" getting in there , what's happening is that your Enum is a "native" enum, that is, it expects Postgresql to have a "CREATE TYPE" happening in there, but because your TypeDecorator is holding onto that Enum, none of that is happening so there is no "type". Hoping you don't want to use a native enum in any case here as they are more trouble than they're worth; setting the Enum as native=False removes the cumbsersome requirement for a "CREATE TYPE":
user_type = sqlalchemy.Column(
Encrypted(sqlalchemy.types.Enum(Type, native_enum=False), PASSPHRASE_USER)
)
and that brings it all up:
class Encrypted(sqlalchemy.types.TypeDecorator):
impl = postgresql.BYTEA
def __init__(self, inner, passphrase):
super().__init__()
self.inner = types.to_instance(inner)
self.passphrase = passphrase
def process_bind_param(self, value, dialect):
proc = self.inner.bind_processor(dialect)
return proc(value)
def process_result_value(self, value, dialect):
proc = self.inner.result_processor(dialect, None)
return proc(value)
def bind_expression(self, bindvalue):
bindvalue.type = self.inner
bindvalue = sqlalchemy.cast(bindvalue, sqlalchemy.String())
return sqlalchemy.func.pgp_sym_encrypt(bindvalue, self.passphrase)
def column_expression(self, col):
value = sqlalchemy.func.pgp_sym_decrypt(col, self.passphrase)
value = sqlalchemy.cast(value, self.inner)
return value
class User(Base):
__tablename__ = "users"
id = sqlalchemy.Column(sqlalchemy.types.BigInteger, primary_key=True)
user_type = sqlalchemy.Column(
Encrypted(sqlalchemy.types.Enum(Type, native_enum=False), PASSPHRASE_USER)
)
reputation = sqlalchemy.Column(
Encrypted(sqlalchemy.types.Float, PASSPHRASE_USER)
)
--
SQLAlchemy -
The Python SQL Toolkit and Object Relational Mapper
---
You received this message because you are subscribed to the Google Groups "sqlalchemy" group.
Attachments: