def url_id(cls): """The URL id""" if cls.__uuid_primary_key__: def url_id_func(self): """The URL id, UUID primary key rendered as a hex string""" return self.id.hex def url_id_is(cls): return SqlHexUuidComparator(cls.id) url_id_func.__name__ = 'url_id' url_id_property = hybrid_property(url_id_func) url_id_property = url_id_property.comparator(url_id_is) return url_id_property else: def url_id_func(self): """The URL id, integer primary key rendered as a string""" return six.text_type(self.id) def url_id_expression(cls): """The URL id, integer primary key""" return cls.id url_id_func.__name__ = 'url_id' url_id_property = hybrid_property(url_id_func) url_id_property = url_id_property.expression(url_id_expression) return url_id_property
class SomeMixin(object): @hybrid.hybrid_property def same_name(self): return self.id def name1(self): return self.id different_name = hybrid.hybrid_property(name1) no_name = hybrid.hybrid_property(lambda self: self.name)
class A(Base): __tablename__ = "a" id = Column(Integer, primary_key=True) name = Column(String(50)) @hybrid.hybrid_property def same_name(self): return self.id def name1(self): return self.id different_name = hybrid.hybrid_property(name1) no_name = hybrid.hybrid_property(lambda self: self.name)
def createSignature(cls): ''' Create the signature name link, only one can be created. ''' def fget(self): if self.signature: return self.signature.name def fset(self, name): assert isinstance(name, str), 'Invalid signature name %s' % name name = name.strip() assert name, 'Empty string is not a valid signature name' session = openSession() try: signatureId, = session.query(Signature.id).filter(Signature.name == name).one() except NoResultFound: signature = Signature() signature.name = name session.add(signature) session.flush((signature,)) signatureId = signature.id self.signatureId = signatureId validation = validate(Mandatory, lambda prop: MaxLen(prop, columnFor(Signature.name).type.length)) return validation(hybrid_property(fget, fset, expr=joinedExpr(Signature, 'name')))
def decorator(func): if not info.get('filterable', True): ret = property(func) else: ret = hybrid_property(func) ret.fget.info = info return ret
def get_property(self, registry, namespace, fieldname, properties): """Return the property of the field .. warning:: In the case of the get is called in classattribute, SQLAlchemy wrap for each call the column, the id of the wrapper is not the same :param registry: current registry :param namespace: name of the model :param fieldname: name of the field :param properties: properties known to the model """ fget = self.wrap_getter_column(fieldname) fset = self.wrap_setter_column(fieldname) fexp = self.wrap_expr_column(fieldname) for func in (fget, fset, fexp): func.__name__ = fieldname hybrid = hybrid_property(fget) hybrid = hybrid.setter(fset) hybrid = hybrid.expression(fexp) return hybrid
def test_attrs_props_prop_added_after_configure(self): class AnonClass(object): pass from sqlalchemy.orm import mapper, column_property from sqlalchemy.ext.hybrid import hybrid_property m = mapper(AnonClass, self.tables.users) eq_( set(inspect(AnonClass).attrs.keys()), set(['id', 'name'])) eq_( set(inspect(AnonClass).all_orm_descriptors.keys()), set(['id', 'name'])) m.add_property('q', column_property(self.tables.users.c.name)) def desc(self): return self.name AnonClass.foob = hybrid_property(desc) eq_( set(inspect(AnonClass).attrs.keys()), set(['id', 'name', 'q'])) eq_( set(inspect(AnonClass).all_orm_descriptors.keys()), set(['id', 'name', 'q', 'foob']))
def mapped_to_slot_property(col, slot_name, slot_transform=lambda x: x): """Assume the attribute in the class as the same name as the table column with "_" prepended""" col_name = "_{}".format(col.name) def fget(self): return getattr(self, col_name) def fset(self, value): v = slot_transform(value) if v is None: if slot_name in self: del self[slot_name] else: self[slot_name] = v setattr(self, col_name, value) def expr(cls): return col return hybrid_property( fget=fget, fset=fset, expr=expr, )
def pure_slot_property(slot_name, slot_transform=lambda x: x): """ Create a property (class must have slots) that maps to a slot :param slot_name: name of the slot :param slot_transform: transformation to operate before assigning value :return: """ def fget(self): # return None if the slot does not exist. alternative could be to raise an exception try: return self[slot_name].value except KeyError: return None def fset(self, value): v = slot_transform(value) if v is None: if slot_name in self: del self[slot_name] else: self[slot_name] = v return hybrid_property( fget=fget, fset=fset, )
def decorate(func): name = func.__name__ @wraps(func) def fget(self): obj = getattr(self, relation) if not obj: return default else: return getattr(obj.name) hybrid = hybrid_property(fget) if class_: def fset(self, value): obj = getattr(self, relation) if not obj: obj = class_(**class_kwargs) setattr(self, relation, obj) setattr(obj, name, value) hybrid.setter(fset) if expression: def expr(cls): return expression hybrid.expression(expr) return hybrid
def DateTimeAtColumn(prefix: str) -> hybrid_property: datetime_key = f'{prefix}_datetime' timezone_key = f'{prefix}_timezone' def getter(self) -> Optional[datetime.datetime]: dt: Optional[datetime.datetime] = getattr(self, datetime_key) tz: Optional[datetime.tzinfo] = getattr(self, timezone_key) if dt and tz: return dt.replace(tzinfo=UTC).astimezone(tz) if dt: return dt.replace(tzinfo=None) return dt def setter(self, dt: datetime.datetime): setattr(self, timezone_key, dt.tzinfo) setattr(self, datetime_key, dt.astimezone(UTC)) def custom_comparator(cls): return DateTimeAtComparator( getattr(cls, datetime_key), getattr(cls, timezone_key), ) return hybrid_property( fget=getter, fset=setter, custom_comparator=custom_comparator, )
def relationshipModel(mappedId, *spec): ''' Creates a relationship with the model, this should only be used in case the mapped model database id is different from the actual model id. @param mappedId: InstrumentedAttribute The mapped model id to create the relation with. @param spec: arguments containing column: string The column name containing the foreign key relation, attention if target is provided then also column needs to be provided, if None provided it will create one automatically. target: string The SQL alchemy relationship target name, if None provided it will create one automatically. ''' assert isinstance(mappedId, InstrumentedAttribute), 'Invalid mapped id %s' % mappedId register = callerLocals() assert '__module__' in register, 'This function should only be used inside meta class definitions' rtype = typeFor(mappedId.class_) assert isinstance(rtype, TypeModel), 'Invalid class %s' % mappedId.class_ assert isinstance(rtype.propertyId, TypeProperty), 'No property id for %s' % rtype assert rtype.propertyId.name != mappedId.property.key, 'Same database id with the model id \'%s\' for %s' % mappedId.class_ column = target = None if spec: column, *spec = spec assert isinstance(column, str), 'Invalid column %s' % column if spec: target, = spec assert isinstance(target, str) and target, 'Invalid target %s' % target if target is None: target = modifyFirst(rtype.name, False) if column is None: column = '%sId' % target register[column] = Column('fk_%s_id' % toUnderscore(target), ForeignKey(mappedId, ondelete='CASCADE'), nullable=False) register[target] = relationship(mappedId.class_, uselist=False, lazy='joined', viewonly=True) def fget(self): rel = getattr(self, target) if rel: return getattr(rel, rtype.propertyId.name) columnId = columnFor(getattr(mappedId.class_, rtype.propertyId.name)) def fset(self, value): setattr(self, column, select([mappedId], columnId == value)) validation = validate(Mandatory, Relation) return validation( hybrid_property(fget, fset, expr=joinedExpr(mappedId.class_, rtype.propertyId.name)))
def summarized_property(name): """ Adds an attribute as hybrid_property which returns the sum of the underlying results if called. Requires the class to define two aggregation functions. Usage:: class Model(): votes = summarized_property('votes') results = relationship('Result', ...) def aggregate_results(self, attribute): return sum(getattr(res, attribute) for res in self.results) @staticmethod def aggregate_results_expression(cls, attribute): expr = select([func.sum(getattr(Result, attribute))]) expr = expr.where(Result.xxx_id == cls.id) expr = expr.label(attribute) return expr """ def getter(self): return self.aggregate_results(name) def expression(cls): return cls.aggregate_results_expression(cls, name) return hybrid_property(getter, expr=expression)
def closure(cls): class_name = cls.__name__ + 'Localized' tablename = plural_name(underscorize(class_name)) if db.metadata.tables.get(tablename) is not None: return # TODO: pass language from the babel detection lang = u'en' cls_columns = cls.__table__.get_children() columns = dict([(c.name, c.copy()) for c in cls_columns if isinstance(c.type, (db.Unicode, db.UnicodeText))]) localized_names = columns.keys() columns.update({ 'parent_id': db.Column(db.Integer, db.ForeignKey(cls.__tablename__ + '.id'), nullable=False), 'parent': db.relationship(cls, backref='localized_ref'), 'locale': db.Column(db.Unicode(255), default=lang, index=True) }) cls_localized = type(class_name, (db.Model, CRUDMixin), columns) for field in localized_names: def getter(self): localized = cls_localized.query.filter_by(parent_id=self.id, locale=lang).first() return getattr(localized, field) or None def setter(self, value): localized = cls_localized.query.filter_by(parent_id=self.id, locale=lang).first() or cls_localized(parent=self, locale=lang) setattr(localized, field, value) localized.save() def expression(self): return db.Query(columns[field]).filter(cls_localized.parent_id == self.id, cls_localized.locale == lang).as_scalar() setattr(cls, field, hybrid_property(getter, setter, expr=expression)) closure(cls)
def test_attrs_props_prop_added_after_configure(self): class AnonClass(object): pass from sqlalchemy.orm import mapper, column_property from sqlalchemy.ext.hybrid import hybrid_property m = mapper(AnonClass, self.tables.users) eq_(set(inspect(AnonClass).attrs.keys()), set(["id", "name"])) eq_( set(inspect(AnonClass).all_orm_descriptors.keys()), set(["id", "name"]), ) m.add_property("q", column_property(self.tables.users.c.name)) def desc(self): return self.name AnonClass.foob = hybrid_property(desc) eq_(set(inspect(AnonClass).attrs.keys()), set(["id", "name", "q"])) eq_( set(inspect(AnonClass).all_orm_descriptors.keys()), set(["id", "name", "q", "foob"]), )
def override_attr(attr_name, parent_name, fget=None): """Create property that overrides an attribute coming from parent. In order to ensure setter functionality at creation time, ``parent`` must be initialized before the overriden attribute. :param attr_name: The name of the attribute to be overriden. :param parent_name: The name of the attribute from which to override the attribute. :param fget: Getter for own property """ own_attr_name = '_' + attr_name def _get(self): parent = getattr(self, parent_name) attr = getattr(self, own_attr_name) fget_ = (lambda self, __: attr) if fget is None else fget return fget_( self, own_attr_name) if attr is not None or not parent else getattr( parent, attr_name) def _set(self, value): parent = getattr(self, parent_name) own_value = getattr(self, own_attr_name) if not parent or own_value is not None or value != getattr( parent, attr_name): setattr(self, own_attr_name, value) def _expr(cls): return getattr(cls, own_attr_name) return hybrid_property(_get, _set, expr=_expr)
def override_attr(attr_name, parent_name, fget=None): """Create property that overrides an attribute coming from parent. In order to ensure setter functionality at creation time, ``parent`` must be initialized before the overriden attribute. :param attr_name: The name of the attribute to be overriden. :param parent_name: The name of the attribute from which to override the attribute. :param fget: Getter for own property """ own_attr_name = '_' + attr_name def _get(self): parent = getattr(self, parent_name) attr = getattr(self, own_attr_name) fget_ = (lambda self, __: attr) if fget is None else fget return fget_(self, own_attr_name) if attr is not None or not parent else getattr(parent, attr_name) def _set(self, value): parent = getattr(self, parent_name) own_value = getattr(self, own_attr_name) if not parent or own_value is not None or value != getattr(parent, attr_name): setattr(self, own_attr_name, value) def _expr(cls): return getattr(cls, own_attr_name) return hybrid_property(_get, _set, expr=_expr)
def test_attrs_props_prop_added_after_configure(self): class Thing(InspectionAttr): pass class AnonClass(object): __foo__ = "bar" __bat__ = Thing() from sqlalchemy.orm import column_property from sqlalchemy.ext.hybrid import hybrid_property m = self.mapper_registry.map_imperatively(AnonClass, self.tables.users) eq_(set(inspect(AnonClass).attrs.keys()), set(["id", "name"])) eq_( set(inspect(AnonClass).all_orm_descriptors.keys()), set(["id", "name"]), ) m.add_property("q", column_property(self.tables.users.c.name)) def desc(self): return self.name AnonClass.foob = hybrid_property(desc) eq_(set(inspect(AnonClass).attrs.keys()), set(["id", "name", "q"])) eq_( set(inspect(AnonClass).all_orm_descriptors.keys()), set(["id", "name", "q", "foob"]), )
def DateTimeAtColumn(prefix: str) -> hybrid_property: datetime_key = f"{prefix}_datetime" timezone_key = f"{prefix}_timezone" def getter(self) -> datetime.datetime | None: dt: datetime.datetime | None = getattr(self, datetime_key) tz: datetime.tzinfo | None = getattr(self, timezone_key) if dt and tz: return dt.astimezone(tz) return dt def setter(self, dt: datetime.datetime): setattr(self, timezone_key, dt.tzinfo) setattr(self, datetime_key, dt.astimezone(UTC)) def custom_comparator(cls): return DateTimeAtComparator( getattr(cls, datetime_key), getattr(cls, timezone_key), ) return hybrid_property( fget=getter, fset=setter, custom_comparator=custom_comparator, )
def get_sqlalchemy_mapping(self, registry, namespace, fieldname, properties): """Return the property of the field :param registry: current registry :param namespace: name of the model :param fieldname: name of the field :param properties: properties known to the model """ def wrap(method): m = self.kwargs.get(method) if m is None: return None def function_method(model_self, *args, **kwargs): if method == 'fget': cls = registry.get(model_self.__registry_name__) if model_self is cls: return hasattr(model_self, m) return getattr(model_self, m)(*args, **kwargs) return function_method fget = wrap('fget') fset = wrap('fset') fdel = wrap('fdel') fexpr = wrap('fexpr') self.format_label(fieldname) properties['loaded_fields'][fieldname] = self.label return hybrid_property(fget, fset, fdel=fdel, expr=fexpr)
def get_sqlalchemy_mapping(self, registry, namespace, fieldname, properties): """Return the property of the field :param registry: current registry :param namespace: name of the model :param fieldname: name of the field :param properties: properties known to the model """ self.fieldname = fieldname self.format_label(fieldname) properties['loaded_fields'][fieldname] = self.label fget = self.get_fget() fset = self.get_fset() fdel = self.get_fdel() fexpr = self.get_fexpr() fcomparator = self.get_fcomparator() for funct in (fget, fset, fdel, fexpr, fcomparator): funct.__name__ = fieldname hybrid = hybrid_property(fget) hybrid = hybrid.setter(fset) hybrid = hybrid.deleter(fdel) if isinstance(self.field, Many2One): hybrid = hybrid.comparator(fcomparator) else: hybrid = hybrid.expression(fexpr) return hybrid
def insert_datetime_field(name, locals_, nullable=True): datetime_key = f'{name}_datetime' timezone_key = f'{name}_timezone' locals_[datetime_key] = Column( DateTime(timezone=False), nullable=nullable, ) locals_[timezone_key] = Column(TimezoneType()) def getter(self): if getattr(self, timezone_key): return getattr(self, timezone_key).localize(getattr(self, datetime_key)) else: return getattr(self, datetime_key) def setter(self, dt: datetime.datetime): if dt.tzinfo is pytz.UTC: d = datetime.datetime.fromtimestamp(calendar.timegm( dt.timetuple())) setattr(self, datetime_key, d - tzlocal.get_localzone().utcoffset(d)) setattr(self, timezone_key, pytz.UTC) else: setattr(self, timezone_key, dt.tzinfo) setattr(self, datetime_key, dt.replace(tzinfo=None)) locals_[f'{name}_at'] = hybrid_property(fget=getter, fset=setter)
def iri_class_decorator(klass): iri_hpropname = '_'+iri_propname setattr(klass, iri_hpropname, column_property(id_to_iri(getattr(klass, iri_id_colname)))) def iri_accessor(self): return getattr(self, iri_hpropname) def iri_expression(klass): return id_to_iri(getattr(klass, iri_id_colname)) def iri_setter(self, val): setattr(self, iri_hpropname, val) setattr(self, iri_id_colname, iri_to_id(val)) def iri_deleter(self): setattr(self, iri_id_colname, None) col = getattr(klass, iri_id_colname) if not col.property.columns[0].nullable: iri_deleter = None prop = hybrid_property( iri_accessor, iri_setter, iri_deleter, iri_expression) setattr(klass, iri_propname, prop) return klass
def make_lr_attr(actor: str) -> hybrid_property: """Create local role hybrid_property attributes.""" def getter(self): """Return the list of user_ids with a local role.""" return principals_by_role(self, actor) def setter(self, values): """Update local role collection.""" set_local_roles_by_role_name(self, actor, values) def expression(cls: ClassItemType): """Expression that return principal ids from database.""" return get_lr_expression(cls, actor) def comparator(cls: ClassItemType): """Return the custom local roles comparator.""" return LocalRolesComparator(cls, actor) lr_attr = hybrid_property( fget=getter, fset=setter, expr=expression, custom_comparator=comparator ) return lr_attr
def setup_hybrids(cls, name, translation_cls, current_locale=get_current_locale, default=None): # The "relationship to aliased class" pattern is in use, and this aliased # object was private to the setup_relationships() function, so pull it out # here so we can use it in a query # See: https://docs.sqlalchemy.org/en/14/orm/join_conditions.html#relationship-to-aliased-class partition_alias = cls.current_translation.entity.entity def _fget(self): return getattr(self.current_translation, name, default) def _fset(self, value): locale_name = current_locale() trans = self.translations.setdefault( locale_name, translation_cls(language_id=locale_name)) setattr(trans, name, value) def _expr(_cls): return getattr(cls._current_translation_partition.c, name) #return getattr(partition_alias, name) log.info('Adding hybrid attribute: %s.%s', cls, name) prop = hybrid_property(fget=_fget, fset=_fset, expr=_expr) setattr(cls, name, prop)
def iri_class_decorator(klass): iri_hpropname = '_' + iri_propname setattr(klass, iri_hpropname, column_property(id_to_iri(getattr(klass, iri_id_colname)))) def iri_accessor(self): return getattr(self, iri_hpropname) def iri_expression(klass): return id_to_iri(getattr(klass, iri_id_colname)) def iri_setter(self, val): setattr(self, iri_hpropname, val) setattr(self, iri_id_colname, iri_to_id(val)) def iri_deleter(self): setattr(self, iri_id_colname, None) col = getattr(klass, iri_id_colname) if not col.property.columns[0].nullable: iri_deleter = None prop = hybrid_property(iri_accessor, iri_setter, iri_deleter, iri_expression) setattr(klass, iri_propname, prop) return klass
def ignore_case_property(text_attr): def getter(self): return CaseInsensitiveWord(getattr(self, text_attr)) def setter(self, value): setattr(self, text_attr, value) return hybrid_property(getter, setter)
def synonym(name): """ Utility function mimicking the behavior of the old SA synonym function with the new hybrid property semantics. """ return hybrid_property(lambda inst: getattr(inst, name), lambda inst, value: setattr(inst, name, value), expr=lambda cls: getattr(cls, name))
def create_hybrid_property(cls, attr_name): """Create a hybrid property that does the rendering of the column. :param attr_name: a name for the attribute the unprocessed value can be accessed through (e.g. `_description`). """ return hybrid_property(cls._render_getter(attr_name), fset=cls._render_setter(attr_name), expr=cls._render_expression(attr_name))
def hybrid_property_gncnumeric(num_col, denom_col): """Return an hybrid_property handling a Decimal represented by a numerator and a denominator column. It assumes the python field related to the sqlcolumn is named as _sqlcolumn. :type num_col: sqlalchemy.sql.schema.Column :type denom_col: sqlalchemy.sql.schema.Column :return: sqlalchemy.ext.hybrid.hybrid_property """ num_name, denom_name = "_{}".format(num_col.name), "_{}".format(denom_col.name) name = num_col.name.split("_")[0] def fset(self, d): if d is None: num, denom = None, None else: if isinstance(d, tuple): d = Decimal(d[0]) / d[1] elif isinstance(d, (int, int, str)): d = Decimal(d) elif isinstance(d, float): raise TypeError(("Received a floating-point number {} where a decimal is expected. " + "Use a Decimal, str, or int instead").format(d)) elif not isinstance(d, Decimal): raise TypeError(("Received an unknown type {} where a decimal is expected. " + "Use a Decimal, str, or int instead").format(type(d).__name__)) sign, digits, exp = d.as_tuple() denom = 10 ** max(-exp, 0) denom_basis = getattr(self, "{}_basis".format(denom_name), None) if denom_basis is not None: denom = denom_basis num = int(d * denom) if not ((-MAX_NUMBER < num < MAX_NUMBER) and (-MAX_NUMBER < denom < MAX_NUMBER)): raise ValueError(("The amount '{}' cannot be represented in GnuCash. " + "Either it is too large or it has too many decimals").format(d)) setattr(self, num_name, num) setattr(self, denom_name, denom) def fget(self): num, denom = getattr(self, num_name), getattr(self, denom_name) if num is None: return else: return Decimal(num) / denom def expr(cls): # todo: cast into Decimal for postgres and for sqlite (for the latter, use sqlite3.register_converter ?) return (cast(num_col, Float) / denom_col).label(name) return hybrid_property( fget=fget, fset=fset, expr=expr, )
def hybrid_property_gncnumeric(num_col, denom_col): """Return an hybrid_property handling a Decimal represented by a numerator and a denominator column. It assumes the python field related to the sqlcolumn is named as _sqlcolumn. :type num_col: sqlalchemy.sql.schema.Column :type denom_col: sqlalchemy.sql.schema.Column :return: sqlalchemy.ext.hybrid.hybrid_property """ num_name, denom_name = "_{}".format(num_col.name), "_{}".format( denom_col.name) name = num_col.name.split("_")[0] def fset(self, d): if d is None: num, denom = None, None else: if isinstance(d, tuple): d = Decimal(d[0]) / d[1] elif isinstance(d, (int, long, str)): d = Decimal(d) elif isinstance(d, float): raise TypeError(( "Received a floating-point number {} where a decimal is expected. " + "Use a Decimal, str, or int instead").format(d)) elif not isinstance(d, Decimal): raise TypeError(( "Received an unknown type {} where a decimal is expected. " + "Use a Decimal, str, or int instead").format( type(d).__name__)) sign, digits, exp = d.as_tuple() denom = 10**max(-exp, 0) denom_basis = getattr(self, "{}_basis".format(denom_name), None) if denom_basis is not None: denom = denom_basis num = int(d * denom) setattr(self, num_name, num) setattr(self, denom_name, denom) def fget(self): num, denom = getattr(self, num_name), getattr(self, denom_name) if num is None: return else: return Decimal(num) / denom def expr(cls): # todo: cast into Decimal for postgres and for sqlite (for the latter, use sqlite3.register_converter ?) return (cast(num_col, Float) / denom_col).label(name) return hybrid_property( fget=fget, fset=fset, expr=expr, )
def year_property(date_attr): def getter(self): date = getattr(self, date_attr) return date and date.year def expr(cls): return extract('year', getattr(cls, date_attr)) return hybrid_property(getter, expr=expr)
def assign_attr_getter_setters(self, attr): setattr( self.model, attr, hybrid_property( fget=translation_getter_factory(attr), fset=translation_setter_factory(attr), expr=lambda cls: getattr(cls.__translatable__['class'], attr) ) )
def synonym(name): """ Utility function mimicking the behavior of the old SA synonym function with the new hybrid property semantics. """ return hybrid_property( lambda inst: getattr(inst, name), lambda inst, value: setattr(inst, name, value), expr=lambda cls: getattr(cls, name), )
def get_property(self, registry, namespace, fieldname, properties): """Return the property of the field :param registry: current registry :param namespace: name of the model :param fieldname: name of the field :param properties: properties known to the model """ return hybrid_property( self.wrap_getter_column(fieldname), self.wrap_setter_column(fieldname), expr=self.wrap_expr_column(fieldname))
def quality_requirement_property(text_attr): def getter(self): return qualities.Requirements(getattr(self, text_attr)) def setter(self, value): if isinstance(value, basestring): setattr(self, text_attr, value) else: setattr(self, text_attr, value.text) prop = hybrid_property(getter, setter) return prop
def quality_requirement_property(text_attr): def getter(self): return qualities.Requirements(getattr(self, text_attr)) def setter(self, value): if isinstance(value, str): setattr(self, text_attr, value) else: setattr(self, text_attr, value.text) prop = hybrid_property(getter, setter) return prop
def hybrid_property_gncnumeric(num_col, denom_col): """Return an hybrid_property handling a Decimal represented by a numerator and a denominator column. It assumes the python field related to the sqlcolumn is named as _sqlcolumn. :type num_col: sqlalchemy.sql.schema.Column :type denom_col: sqlalchemy.sql.schema.Column :return: sqlalchemy.ext.hybrid.hybrid_property """ num_name, denom_name = "_{}".format(num_col.name), "_{}".format( denom_col.name) name = num_col.name.split("_")[0] def fset(self, d): if d is None: num, denom = None, None else: if isinstance(d, tuple): d = Decimal(d[0]) / d[1] elif isinstance(d, (float, int, long, str)): d = Decimal(d) assert isinstance(d, Decimal) sign, digits, exp = d.as_tuple() denom = 10**max(-exp, 0) # print num_name, denom, denom_basis = getattr(self, "{}_basis".format(denom_name), None) if denom_basis is not None: # print "got a basis for ", self, denom_basis denom = denom_basis # print denom num = int(d * denom) setattr(self, num_name, num) setattr(self, denom_name, denom) def fget(self): num, denom = getattr(self, num_name), getattr(self, denom_name) if num is None: return else: return Decimal(num) / denom def expr(cls): # todo: cast into Decimal for postgres and for sqlite (for the latter, use sqlite3.register_converter ?) return (cast(num_col, Float) / denom_col).label(name) return hybrid_property( fget=fget, fset=fset, expr=expr, )
def hybrid_property_gncnumeric(num_col, denom_col): """Return an hybrid_property handling a Decimal represented by a numerator and a denominator column. It assumes the python field related to the sqlcolumn is named as _sqlcolumn. :type num_col: sqlalchemy.sql.schema.Column :type denom_col: sqlalchemy.sql.schema.Column :return: sqlalchemy.ext.hybrid.hybrid_property """ num_name, denom_name = "_{}".format(num_col.name), "_{}".format(denom_col.name) name = num_col.name.split("_")[0] def fset(self, d): if d is None: num, denom = None, None else: if isinstance(d, tuple): d = Decimal(d[0]) / d[1] elif isinstance(d, (float, int, long, str)): d = Decimal(d) assert isinstance(d, Decimal) sign, digits, exp = d.as_tuple() denom = 10 ** max(-exp, 0) # print num_name, denom, denom_basis = getattr(self, "{}_basis".format(denom_name), None) if denom_basis is not None: # print "got a basis for ", self, denom_basis denom = denom_basis # print denom num = int(d * denom) setattr(self, num_name, num) setattr(self, denom_name, denom) def fget(self): num, denom = getattr(self, num_name), getattr(self, denom_name) if num is None: return else: return Decimal(num) / denom def expr(cls): # todo: cast into Decimal for postgres and for sqlite (for the latter, use sqlite3.register_converter ?) return (cast(num_col, Float) / denom_col).label(name) return hybrid_property( fget=fget, fset=fset, expr=expr, )
def mapper(class_, local_table=None, id_attribute='id', slug_expression=None, *args, **kwargs): """ Convenience wrapper around the SA mapper which will set up the hybrid "id" and "slug" attributes required by everest after calling the SA mapper. If you (e.g., for testing purposes) want to clear mappers created with this function, use the :func:`clear_mappers` function in this module. :param str id_attribute: the name of the column in the table to use as ID column (will be aliased to a new "id" attribute in the mapped class) :param slug_expression: function to generate a slug SQL expression given the mapped class as argument. """ mpr = sa_mapper(class_, local_table=local_table, *args, **kwargs) # Set up the ID attribute as a hybrid property, if necessary. if id_attribute != 'id': # Make sure we are not overwriting an already mapped or customized # 'id' attribute. if 'id' in mpr.columns: mpr.dispose() raise ValueError('Attempting to overwrite the mapped "id" ' 'attribute.') elif isdatadescriptor(getattr(class_, 'id', None)): mpr.dispose() raise ValueError('Attempting to overwrite the custom data ' 'descriptor defined for the "id" attribute.') fget = lambda obj: getattr(obj, id_attribute) fset = lambda self, value: setattr(self, id_attribute, value) class_.id = hybrid_property(fget, fset=fset, expr=fget) # Set up the slug attribute as a hybrid property. if slug_expression is None: cls_expr = lambda cls: cast(getattr(cls, 'id'), String) else: cls_expr = slug_expression # If this is a polymorphic class, a base class may already have a # hybrid descriptor set as slug attribute. slug_descr = None for base_cls in class_.__mro__: try: slug_descr = object.__getattribute__(base_cls, 'slug') except AttributeError: pass else: break if isinstance(slug_descr, hybrid_descriptor): descr = slug_descr.descriptor else: descr = slug_descr class_.slug = hybrid_descriptor(descr, expr=cls_expr) return mpr
def sub_table_addon(master_cls, slave_cls, hoist=True): """Slave a sub table to the master table. Example: Among other things hoist all columns from economy into county. class County: @property def produced_grain(self): return self.economy.produced_grain @produced_grain.setter def produced_grain(self, value): self.economy.produced_grain = value @hybrid_property def {col}(self): return self.{sub_table}.{col} f = hybrid_property( lambda self: getattr(getattr(self, 'economy'), 'grain_produced')) setattr(County, 'grain_produced', f) """ master_table_name = master_cls.__table__.name sub_table_name = slave_cls.__table__.name if hoist: cols_to_hoist = set([ strip_leading_underscore(c.name) for c in slave_cls.__table__.c ]) - set( [strip_leading_underscore(c.name) for c in master_cls.__table__.c]) for name in cols_to_hoist: func = hybrid_property(lambda self, name=name: getattr( getattr(self, sub_table_name), name)) setattr(master_cls, name, func) setattr( master_cls, name, func.setter(lambda self, value, name=name: setattr( getattr(self, sub_table_name), name, value))) # add relationships linking county and slave table slave_cls.county_id = db.Column(db.Integer, db.ForeignKey(f'{master_table_name}.id'), nullable=False) slave_cls.county = db.relationship(master_cls.__name__, backref=db.backref(sub_table_name, uselist=False)) # add sub table object to county init compose_into_init(master_cls, slave_cls, sub_table_name)
def test_inspect(self, db): # attach a fake method to Segment where the type is unknown: defval = 'a' Segment._fake_method = \ hybrid_property(lambda self: defval, expr=lambda cls: func.substr(cls.download_code, 1, 1)) insp = Inspector(Segment) attnames = list(insp.attnames(Inspector.PKEY)) assert attnames == ['id'] attnames = list(insp.attnames(Inspector.FKEY, sort=True)) assert 'event_id' in attnames and 'id' not in attnames \ and 'classes' not in attnames and '_fake_method' not in attnames attnames2 = list(insp.attnames(Inspector.FKEY, sort=False)) # sort=False MIGHT return the same attributes order as sorted=True # thus perform a check only if they differ: if attnames != attnames: assert sorted(attnames) == sorted(attnames2) attnames = list(insp.attnames(Inspector.QATT)) assert '_fake_method' in attnames and not 'id' in attnames and \ not 'event_id' in attnames attnames = list(insp.attnames(Inspector.REL, sort=True)) assert 'classes' in attnames and 'id' not in attnames \ and 'event_id' not in attnames and '_fake_method' not in attnames attnames2 = list(insp.attnames(Inspector.REL, sort=False)) # sort=False MIGHT return the same attributes order as sorted=True # thus perform a check only if they differ: if attnames != attnames: assert sorted(attnames) == sorted(attnames2) attnames = insp.attnames(deep=True) for attname in attnames: attval = insp.attval(attname) assert isinstance(attval, QueryableAttribute) if attname == '_fake_method': assert insp.atttype(attname) is None else: assert insp.atttype(attname) is not None seg = db.session.query(Segment).first() insp = Inspector(seg) attnames = insp.attnames(deep=True) for attname in attnames: val = insp.attval(attname) if attname == '_fake_method': assert val == defval if attname.startswith('classes.'): assert isinstance(val, list) else: assert not isinstance(val, (dict, list, set))
def get_sqlalchemy_mapping(self, registry, namespace, fieldname, properties): """Return the property of the field :param registry: current registry :param namespace: name of the model :param fieldname: name of the field :param properties: properties known to the model """ self.format_label(fieldname) properties['loaded_fields'][fieldname] = self.label return hybrid_property(self.get_fget(), self.get_fset(), fdel=self.get_fdel(), expr=self.get_fexpr())
def quality_property(text_attr): def getter(self): return qualities.get(getattr(self, text_attr)) def setter(self, value): if isinstance(value, basestring): setattr(self, text_attr, value) else: setattr(self, text_attr, value.name) def comparator(self): return QualityComparator(getattr(self, text_attr)) prop = hybrid_property(getter, setter) prop.comparator(comparator) return prop
def relationshipModel(mappedId, *spec): ''' Creates a relationship with the model, this should only be used in case the mapped model database id is different from the actual model id. @param mappedId: InstrumentedAttribute The mapped model id to create the relation with. @param spec: arguments containing column: string The column name containing the foreign key relation, attention if target is provided then also column needs to be provided, if None provided it will create one automatically. target: string The SQL alchemy relationship target name, if None provided it will create one automatically. ''' assert isinstance(mappedId, InstrumentedAttribute), 'Invalid mapped id %s' % mappedId register = callerLocals() assert '__module__' in register, 'This function should only be used inside meta class definitions' rtype = typeFor(mappedId.class_) assert isinstance(rtype, TypeModel), 'Invalid class %s' % mappedId.class_ assert isinstance(rtype.propertyId, TypeProperty), 'No property id for %s' % rtype assert rtype.propertyId.name != mappedId.property.key, 'Same database id with the model id \'%s\' for %s' % mappedId.class_ column = target = None if spec: column, *spec = spec assert isinstance(column, str), 'Invalid column %s' % column if spec: target, = spec assert isinstance(target, str) and target, 'Invalid target %s' % target if target is None: target = modifyFirst(rtype.name, False) if column is None: column = '%sId' % target register[column] = Column('fk_%s_id' % toUnderscore(target), ForeignKey(mappedId, ondelete='CASCADE'), nullable=False) register[target] = relationship(mappedId.class_, uselist=False, lazy='joined', viewonly=True) def fget(self): rel = getattr(self, target) if rel: return getattr(rel, rtype.propertyId.name) columnId = columnFor(getattr(mappedId.class_, rtype.propertyId.name)) def fset(self, value): setattr(self, column, select([mappedId], columnId == value)) validation = validate(Mandatory, Relation) return validation(hybrid_property(fget, fset, expr=joinedExpr(mappedId.class_, rtype.propertyId.name)))
def get_sqlalchemy_mapping(self, registry, namespace, fieldname, properties): """Return the property of the field :param registry: current registry :param namespace: name of the model :param fieldname: name of the field :param properties: properties known to the model """ self.format_label(fieldname) properties['loaded_fields'][fieldname] = self.label return hybrid_property( self.get_fget(), self.get_fset(), fdel=self.get_fdel(), expr=self.get_fexpr() )
def create_column(self, cname, remote_type, foreign_key, properties): def wrapper(cls): return SA_Column( cname.attribute_name, remote_type, nullable=self.nullable, unique=self.unique, info=dict(label=self.label, foreign_key=foreign_key)) properties[(anyblok_column_prefix + cname.attribute_name)] = declared_attr(wrapper) properties['loaded_columns'].append(cname.attribute_name) properties['hybrid_property_columns'].append(cname.attribute_name) properties[cname.attribute_name] = hybrid_property( self.wrap_getter_column(cname.attribute_name), super(Many2One, self).wrap_setter_column(cname.attribute_name), expr=self.wrap_expr_column(cname.attribute_name))
def get_property(self, registry, namespace, fieldname, properties): """Return the property of the field .. warning:: In the case of the get is called in classattribute, SQLAlchemy wrap for each call the column, the id of the wrapper is not the same :param registry: current registry :param namespace: name of the model :param fieldname: name of the field :param properties: properties known to the model """ return hybrid_property( self.wrap_getter_column(fieldname), self.wrap_setter_column(fieldname), expr=self.wrap_expr_column(fieldname))
def generate_hybrid(self, property_name): """ Generate a SQLAlchemy hybrid property for given translation model property. :param property_name: Name of the translation model property to generate hybrid property for. """ setattr( self.model, property_name, hybrid_property( fget=self.getter_factory(property_name), fset=self.setter_factory(property_name), expr=lambda cls: getattr( cls.__translatable__['class'], property_name ) ) )
def __new__(mcs, name, bases, attrs): for n, v in attrs.items(): attrName = n[1:] if isinstance(v, Column) and n.startswith('_') and not attrs.has_key(attrName): v.key = attrName v.name = attrName # Add dynamic property setter = ModelPropertySetter(n) attrs[attrName] = hybrid_property( ModelPropertyGetter(n), setter, None, ModelPropertyExpression(n) ) # Add external-key property info = getattr(v, 'info') if info and 'model' in info: columnName = info['column'] if 'column' in info else 'i' attrs[info['get']] = property( ExternalKeyProperty(attrName, info['model'], columnName) ) return DeclarativeMeta.__new__(mcs, name, bases, attrs)
def create_property(cls, localized, columns, field): def getter(self): instance = localized.query.filter_by(id=self.id, locale=lang).first() return instance and getattr(instance, field) or None def setter(self, value): from_db = localized.query.filter_by(id=self.id, locale=lang).first() instance = from_db or localized(parent=self, locale=lang) setattr(instance, field, value) instance.save() def expression(self): return db.Query(columns[field]) \ .filter(localized.parent_id == self.id, localized.locale == lang).as_scalar() setattr(cls, field, hybrid_property(getter, setter, expr=expression))