On Mar 31, 2014, at 5:36 AM, Alex Good <
quix...@googlemail.com> wrote:
> Hi there,
>
> I'm working with a database where the original designer has chosen to represent a large number of values as bitflags. Nearly every table in the database has a 'flags' integer which is bitwise ORred or ANDed with constants stored in the application code to extract the relevant options. I'm trying to wrap this in a custom type which will allow me to refer to these values individually as booleans and I can't figure out the correct approach. Here's an example:
>
>
> So what I want to be able to do is these kind of interactions
>
> m = session.query(TestModel).filter(TestModel.flag_one == True).first()
> m.flag_one = False
> session.add(m.flag_one)
> session.commit()
> assert((m.flags & 1) == 0)
>
> I can see how one would accomplish this with hybrid attributes but with every table having 5 to 10 flags defined on it that would lead to a lot of typing, hence the custom type approach. I attempted the following:
>
> class Flag(TypeDecorator):
> impl = Integer
>
> def __init__(self, flag_constants, flag_name, *args, **kwargs):
> super(Flag, self).__init__(*args, **kwargs)
> self.flag_constants = flag_constants
> self.flag_name = flag_name
>
> def column_expression(self, col):
> return col.op("&")(getattr(self.flag_constants, self.flag_name))
The way you do this is you intercept expressions in Python and convert them on a per-expression basis to what’s needed. You’d be focusing here on the comparator factory feature of types:
http://docs.sqlalchemy.org/en/latest/core/types.html#types-operators
I’d probably map the column just once and provide hybrid accessors on top of it.
However, there is unfortunately a critical issue I’ve just discovered for ticket #3012, which is that we can’t override __and__() and __or__() directly within this system. That’s a bug.
So we’ll work around it using an ad-hoc operator, since your hybrids are doing the work anyway:
class FlagType(TypeDecorator):
impl = Integer
class comparator_factory(TypeDecorator.Comparator):
def _and(self, other):
return self.op("&")(other)
def _or(self, other):
return self.op("|")(other)
class FlagThing(hybrid_property):
def __init__(self, attrib_name, flag):
self.attrib_name = attrib_name
self.flag = flag
super(FlagThing, self).__init__(
fget=self._get,
expr=self._get_expr,
fset=self._set
)
def _get(self, instance):
return (getattr(instance, self.attrib_name) & self.flag > 0)
# if 3012 were fixed, this part wouldn’t be needed
def _get_expr(self, instance):
return (getattr(instance, self.attrib_name)._and(self.flag) > 0)
def _set(self, instance, value):
existing = getattr(instance, self.attrib_name)
if value:
existing |= self.flag
else:
existing ^= self.flag
setattr(instance, self.attrib_name, existing)
class TestModel(Base):
__tablename__ = "testmodel"
id = Column("id", Integer, primary_key=True)
flags = Column("flags", FlagType())
flag_one = FlagThing("flags", TestFlags.flag_value_one)
flag_two = FlagThing("flags", TestFlags.flag_value_two)