def resolve_model(cls, name): if inspect.isclass(name) and issubclass(name, cls.BaseClass): return name subclasses = { ModelClass.__name__: ModelClass for ModelClass in cls.all_models() } permutations = [name, camel(name), camel(name, upper_segments=0)] for name in permutations: if name in subclasses: return subclasses[name] if name.lower().endswith('s'): singular = name.rstrip('sS') if singular in subclasses: return subclasses[singular] if name.lower().endswith('ies'): singular = name[:-3] + 'y' if singular in subclasses: return subclasses[singular] for name in permutations: if name in cls.BaseClass.metadata.tables: return cls.BaseClass.metadata.tables[name] raise ValueError('Unrecognized model: {}'.format(name))
def JSONColumnMixin(column_name, fields, admin_only=False): """ Creates a new mixin class with a JSON column named column_name. The newly created mixin class will have a SQLAlchemy JSON Column, named `column_name`, along with two other attributes column_name_fields and column_name_qualified_fields which describe the fields that the JSON Column is expected to hold. For example:: >>> SocialMediaMixin = JSONColumnMixin('social_media', ['Twitter', 'LinkedIn']) >>> SocialMediaMixin.social_media # doctest: +ELLIPSIS Column('social_media', JSON(), ... server_default=DefaultClause('{}', for_update=False)) >>> SocialMediaMixin._social_media_fields OrderedDict([('twitter', 'Twitter'), ('linked_in', 'LinkedIn')]) >>> SocialMediaMixin._social_media_qualified_fields OrderedDict([('social_media__twitter', 'twitter'), ('social_media__linked_in', 'linked_in')]) Instances of the newly created mixin class have convenience accessors for the attributes defined by `fields`, both directly and using their fully qualified forms:: >>> sm = SocialMediaMixin() >>> sm.twitter = 'https://twitter.com/MAGFest' # Get and set "twitter" directly >>> sm.twitter 'https://twitter.com/MAGFest' >>> sm.social_media__twitter # Get and set qualified "social_media__twitter" 'https://twitter.com/MAGFest' >>> sm.social_media__twitter = '@MAGFest' >>> sm.social_media__twitter '@MAGFest' >>> sm.social_media # Actual column updated appropriately {'linked_in': '', 'twitter': '@MAGFest'} Args: column_name (str): The name of the column. fields (list): A list of field names you expect the column to hold. This can be: - A single string, if you're only expecting the column to hold a single field. - A list of strings, which will be treated as the column labels, and converted from CamelCase to under_score for the fields. - A map of {string: string}, which will be treated as a mapping of field names to field labels. Returns: type: A new mixin class with a JSON column named column_name. """ fields_name = '_{}_fields'.format(column_name) qualified_fields_name = '_{}_qualified_fields'.format(column_name) if isinstance(fields, Mapping): fields = OrderedDict([(fieldify(k), v) for k, v in fields.items()]) else: fields = OrderedDict([(fieldify(s), s) for s in listify(fields)]) qualified_fields = OrderedDict([(column_name + '__' + s, s) for s in fields.keys()]) column = Column(column_name, JSON, default={}, server_default='{}') attrs = { column_name: column, fields_name: fields, qualified_fields_name: qualified_fields } _Mixin = type(camel(column_name) + 'Mixin', (object, ), attrs) def _Mixin__init__(self, *args, **kwargs): setattr(self, column_name, {}) for attr in getattr(self.__class__, fields_name).keys(): setattr(self, attr, kwargs.pop(attr, '')) super(_Mixin, self).__init__(*args, **kwargs) _Mixin.__init__ = _Mixin__init__ def _Mixin__declare_last__(cls): setattr(getattr(cls, column_name), 'admin_only', admin_only) column = cls.__table__.columns.get(column_name) setattr(column, 'admin_only', admin_only) _Mixin.__declare_last__ = classmethod(_Mixin__declare_last__) def _Mixin__unqualify(cls, name): if name in getattr(cls, qualified_fields_name): return getattr(cls, qualified_fields_name)[name] else: return name _Mixin.unqualify = classmethod(_Mixin__unqualify) def _Mixin__getattr__(self, name): name = self.unqualify(name) if name in getattr(self.__class__, fields_name): return getattr(self, column_name).get(name, '') else: return super(_Mixin, self).__getattr__(name) _Mixin.__getattr__ = _Mixin__getattr__ def _Mixin__setattr__(self, name, value): name = self.unqualify(name) if name in getattr(self.__class__, fields_name): fields = getattr(self, column_name) if fields.get(name) != value: fields[name] = value super(_Mixin, self).__setattr__(column_name, dict(fields)) else: super(_Mixin, self).__setattr__(name, value) _Mixin.__setattr__ = _Mixin__setattr__ return _Mixin
def JSONColumnMixin(column_name, fields, admin_only=False): """ Creates a new mixin class with a JSON column named column_name. The newly created mixin class will have a SQLAlchemy JSON Column, named `column_name`, along with two other attributes column_name_fields and column_name_qualified_fields which describe the fields that the JSON Column is expected to hold. For example:: >>> SocialMediaMixin = JSONColumnMixin('social_media', ['Twitter', 'LinkedIn']) >>> SocialMediaMixin.social_media # doctest: +ELLIPSIS Column('social_media', JSON(), ... server_default=DefaultClause('{}', for_update=False)) >>> SocialMediaMixin._social_media_fields OrderedDict([('twitter', 'Twitter'), ('linked_in', 'LinkedIn')]) >>> SocialMediaMixin._social_media_qualified_fields OrderedDict([('social_media__twitter', 'twitter'), ('social_media__linked_in', 'linked_in')]) Instances of the newly created mixin class have convenience accessors for the attributes defined by `fields`, both directly and using their fully qualified forms:: >>> sm = SocialMediaMixin() >>> sm.twitter = 'https://twitter.com/MAGFest' # Get and set "twitter" directly >>> sm.twitter 'https://twitter.com/MAGFest' >>> sm.social_media__twitter # Get and set qualified "social_media__twitter" 'https://twitter.com/MAGFest' >>> sm.social_media__twitter = '@MAGFest' >>> sm.social_media__twitter '@MAGFest' >>> sm.social_media # Actual column updated appropriately {'linked_in': '', 'twitter': '@MAGFest'} Args: column_name (str): The name of the column. fields (list): A list of field names you expect the column to hold. This can be: - A single string, if you're only expecting the column to hold a single field. - A list of strings, which will be treated as the column labels, and converted from CamelCase to under_score for the fields. - A map of {string: string}, which will be treated as a mapping of field names to field labels. Returns: type: A new mixin class with a JSON column named column_name. """ fields_name = '_{}_fields'.format(column_name) qualified_fields_name = '_{}_qualified_fields'.format(column_name) if isinstance(fields, Mapping): fields = OrderedDict([(fieldify(k), v) for k, v in fields.items()]) else: fields = OrderedDict([(fieldify(s), s) for s in listify(fields)]) qualified_fields = OrderedDict([(column_name + '__' + s, s) for s in fields.keys()]) column = Column(column_name, JSON, default={}, server_default='{}') attrs = { column_name: column, fields_name: fields, qualified_fields_name: qualified_fields } _Mixin = type(camel(column_name) + 'Mixin', (object,), attrs) def _Mixin__init__(self, *args, **kwargs): setattr(self, column_name, {}) for attr in getattr(self.__class__, fields_name).keys(): setattr(self, attr, kwargs.pop(attr, '')) super(_Mixin, self).__init__(*args, **kwargs) _Mixin.__init__ = _Mixin__init__ def _Mixin__declare_last__(cls): setattr(getattr(cls, column_name), 'admin_only', admin_only) column = cls.__table__.columns.get(column_name) setattr(column, 'admin_only', admin_only) _Mixin.__declare_last__ = classmethod(_Mixin__declare_last__) def _Mixin__unqualify(cls, name): if name in getattr(cls, qualified_fields_name): return getattr(cls, qualified_fields_name)[name] else: return name _Mixin.unqualify = classmethod(_Mixin__unqualify) def _Mixin__getattr__(self, name): name = self.unqualify(name) if name in getattr(self.__class__, fields_name): return getattr(self, column_name).get(name, '') else: return super(_Mixin, self).__getattr__(name) _Mixin.__getattr__ = _Mixin__getattr__ def _Mixin__setattr__(self, name, value): name = self.unqualify(name) if name in getattr(self.__class__, fields_name): fields = getattr(self, column_name) if fields.get(name) != value: fields[name] = value super(_Mixin, self).__setattr__(column_name, dict(fields)) else: super(_Mixin, self).__setattr__(name, value) _Mixin.__setattr__ = _Mixin__setattr__ return _Mixin