def is_mapped_class(cls): """Returns ``True`` if and only if the specified SQLAlchemy model class is a mapped class. """ try: sqlalchemy_inspect(cls) return True except: return False
def is_mapped_class(cls): """Returns ``True`` if and only if the specified SQLAlchemy model class is a mapped class. """ try: sqlalchemy_inspect(cls) except NoInspectionAvailable: return False else: return True
def foreign_key_columns(model): """Returns a list of the :class:`sqlalchemy.Column` objects that contain foreign keys for relationships in the specified model class. """ mapper = sqlalchemy_inspect(model) return [c for c in mapper.columns if c.foreign_keys]
def get_related_model(model, relationname): """Gets the class of the model to which `model` is related by the attribute whose name is `relationname`. For example, if we have the model classes :: class Person(Base): __tablename__ = 'person' id = Column(Integer, primary_key=True) articles = relationship('Article') class Article(Base): __tablename__ = 'article' id = Column(Integer, primary_key=True) author_id = Column(Integer, ForeignKey('person.id')) author = relationship('Person') then :: >>> get_related_model(Person, 'articles') <class 'Article'> >>> get_related_model(Article, 'author') <class 'Person'> This function also "sees through" association proxies and returns the model of the proxied remote relation. """ mapper = sqlalchemy_inspect(model) attribute = mapper.all_orm_descriptors[relationname] # HACK This is required for Python 3.3 only. I'm guessing it lazily # loads the attribute or something like that. hasattr(model, relationname) return get_related_model_from_attribute(attribute)
def is_like_list(model_or_instance, relationname): """Decides whether a relation of a SQLAlchemy model is list-like. A relation may be like a list if it behaves like a to-many relation (either lazy or eager) `model_or_instance` may be either a SQLAlchemy model class or an instance of such a class. `relationname` is a string naming a relationship of the given model or instance. """ # Use Python's built-in inspect module to decide whether the # argument is a model or an instance of a model. if not inspect.isclass(model_or_instance): model = get_model(model_or_instance) else: model = model_or_instance mapper = sqlalchemy_inspect(model) relation = mapper.all_orm_descriptors[relationname] if isinstance(relation, AssociationProxy): relation = relation.local_attr return hasattr(relation.property, 'uselist') and relation.property.uselist
def primary_key_names(model): """Returns a list of all the primary keys for a model. The returned list contains the name of each primary key as a string. """ mapper = sqlalchemy_inspect(model) return [column.name for column in mapper.primary_key]
def has_field(model, fieldname): """Returns ``True`` if the `model` has the specified field or if it has a settable hybrid property for this field name. """ descriptors_data = sqlalchemy_inspect(model).all_orm_descriptors._data if fieldname in descriptors_data and hasattr(descriptors_data[fieldname], 'fset'): return getattr(descriptors_data[fieldname], 'fset') is not None return hasattr(model, fieldname)
def foreign_key_columns(model): """Returns a list of the :class:`sqlalchemy.Column` objects that contain foreign keys for relationships in the specified model class. """ inspector = sqlalchemy_inspect(model) all_columns = inspector.columns return [column for column in all_columns if column.foreign_keys]
def has_field(model, fieldname): """Returns ``True`` if the `model` has the specified field or if it has a settable hybrid property for this field name. """ descriptors = sqlalchemy_inspect(model).all_orm_descriptors._data if fieldname in descriptors and hasattr(descriptors[fieldname], 'fset'): return descriptors[fieldname].fset is not None return hasattr(model, fieldname)
def changes_on_update(model): """Returns a best guess at whether the specified SQLAlchemy model class is modified on updates. We guess whether this happens by checking whether any columns of model have the :attr:`sqlalchemy.Column.onupdate` attribute set. """ return any(column.onupdate is not None for column in sqlalchemy_inspect(model).columns)
def attribute_columns(model) -> List[str]: """Returns a list of model's column names that should be considered as attributes.""" inspected_model = sqlalchemy_inspect(model) column_attrs = inspected_model.column_attrs.keys() descriptors = inspected_model.all_orm_descriptors.items() hybrid_columns = [ k for k, d in descriptors if d.extension_type == HYBRID_PROPERTY ] return column_attrs + hybrid_columns
def has_field(model, fieldname): """Returns ``True`` if the `model` has the specified field or if it has a settable hybrid property for this field name. """ if get_db_type(model) == 'mongo': return fieldname in model.structure descriptors = sqlalchemy_inspect(model).all_orm_descriptors._data if fieldname in descriptors and hasattr(descriptors[fieldname], 'fset'): return descriptors[fieldname].fset is not None return hasattr(model, fieldname)
def foreign_key_columns(model): """Returns a list of the :class:`sqlalchemy.Column` objects that contain foreign keys for relationships in the specified model class. """ try: inspector = sqlalchemy_inspect(model) except NoInspectionAvailable: # Well, the inspection of a model class returns a mapper anyway, so # let's just assume the inspection would have returned the mapper. inspector = class_mapper(model) all_columns = inspector.columns return [c for c in all_columns if c.foreign_keys]
def is_relationship(model, fieldname): """Decides whether a field is a relationship (as opposed to a field). `model` is a SQLAlchemy model. `fieldname` is a string naming a field of the given model. This function returns True if and only if the field is a relationship. This function currently does *not* return `True` for association proxies. """ mapper = sqlalchemy_inspect(model) return fieldname in mapper.relationships
def get_relations(model): """Returns a list of relation names of `model` (as a list of strings). For a relationship via an association proxy, this function shows only the remote attribute, not the intermediate relationship. For example, if there is a table for ``Article`` and ``Tag`` and a table associating the two via a many-to-many relationship, :: >>> from sqlalchemy.ext.declarative import declarative_base >>> >>> Base = declarative_base() >>> class Article(Base): ... __tablename__ = 'article' ... id = Column(Integer, primary_key=True) ... tags = association_proxy('articletags', 'tag') ... >>> class ArticleTag(Base): ... __tablename__ = 'articletag' ... article_id = Column(Integer, ForeignKey('article.id'), ... primary_key=True) ... article = relationship(Article, backref=backref('articletags')) ... tag_id = Column(Integer, ForeignKey('tag.id'), ... primary_key=True) ... tag = relationship('Tag') ... >>> class Tag(Base): ... __tablename__ = 'tag' ... id = Column(Integer, primary_key=True) ... name = Column(Unicode) ... >>> get_relations(Article) ['tags'] """ mapper = sqlalchemy_inspect(model) # If we didn't have to deal with association proxies, we could just # do `return list(mapper.relationships)`, but we want to replace all # association attributes with the actual remote attributes, as the # user would expect. Therefore, we get a dictionary mapping # relationship name to association proxy local attribute name, then # replace the key with the value wherever such a key appears in the # list of relationships. alldescriptors = mapper.all_orm_descriptors.items() # TODO In Python 2.7+, this should be a dict comprehension. association_proxies = dict((v.local_attr.key, k) for k, v in alldescriptors if isinstance(v, AssociationProxy)) return [association_proxies.get(r, r) for r in mapper.relationships.keys()]
def to_dict(model, instance, deep=None): """change ORM Model instance to python dict `instance`: model object `deep`: related model dict """ try: instance_type = type(instance) inspected_instance = sqlalchemy_inspect(instance_type) column_attrs = inspected_instance.column_attrs.keys() except NoInspectionAvailable as e: print e return instance result = dict((col, getattr(instance, col)) for col in column_attrs if not (col.startswith('__'))) for key, value in result.items(): if isinstance(value, (datetime.date, datetime.time)): result[key] = value.isoformat() elif isinstance(value, uuid.UUID): result[key] = str(value) elif key not in column_attrs and is_mapped_class(type(value)): result[key] = to_dict(model, value) else: # check alias in `info` col = getattr(model, key) if hasattr(col, 'info') and col.info.get('alias'): alias_dict = col.info['alias'] result[key] = alias_dict.get(value) or value ''' pacakging related instance ''' deep = deep or {} for relation, rdeep in deep.items(): relatedvalue = getattr(instance, relation) if relatedvalue is None: result[relation] = None continue result[relation] = [to_dict(model, inst, rdeep) for inst in relatedvalue] return result
def has_field(model, fieldname): """Returns ``True`` if the `model` has the specified field or if it has a settable hybrid property for this field name. """ mapper = sqlalchemy_inspect(model) # Get all descriptors, which include columns, relationships, and # other things like association proxies and hybrid properties. descriptors = mapper.all_orm_descriptors if fieldname not in descriptors: return False field = descriptors[fieldname] # First, we check whether `fieldname` specifies a settable hybrid # property. This is a bit flimsy: we check whether the `fset` # attribute has been set on the `hybrid_property` instance. The # `fset` instance attribute is only set if the user defined a hybrid # property setter. if hasattr(field, 'fset'): return field.fset is not None # At this point, we simply check that the attribute is not callable. return not callable(getattr(model, fieldname))
def assoc_proxy_scalar_collections(model): """Yields the name of each association proxy collection as a string. This includes each association proxy that proxies to a scalar collection (for example, a list of strings) via an association table. It excludes each association proxy that proxies to a collection of instances (for example, a to-many relationship) via an association object. .. seealso:: :func:`scalar_collection_proxied_relations` .. versionadded:: 1.0.0 """ mapper = sqlalchemy_inspect(model) for k, v in mapper.all_orm_descriptors.items(): if isinstance(v, AssociationProxy) \ and not isinstance(v.remote_attr.property, RelationshipProperty) \ and is_like_list(model, v.local_attr.key): yield k
def is_like_list(model_or_instance, relationname): """Decides whether a relation of a SQLAlchemy model is list-like. A relation may be like a list if it behaves like a to-many relation (either lazy or eager) `model_or_instance` may be either a SQLAlchemy model class or an instance of such a class. `relationname` is a string naming a relationship of the given model or instance. """ # Use Python's built-in inspect module to decide whether the # argument is a model or an instance of a model. if not inspect.isclass(model_or_instance): model = get_model(model_or_instance) else: model = model_or_instance mapper = sqlalchemy_inspect(model) relation = mapper.all_orm_descriptors[relationname] if isinstance(relation, AssociationProxy): relation = relation.local_attr return relation.property.uselist
def to_dict(instance, deep=None, exclude=None, include=None, exclude_relations=None, include_relations=None, include_methods=None): """Returns a dictionary representing the fields of the specified `instance` of a SQLAlchemy model. The returned dictionary is suitable as an argument to :func:`flask.jsonify`; :class:`datetime.date` and :class:`uuid.UUID` objects are converted to string representations, so no special JSON encoder behavior is required. `deep` is a dictionary containing a mapping from a relation name (for a relation of `instance`) to either a list or a dictionary. This is a recursive structure which represents the `deep` argument when calling :func:`!_to_dict` on related instances. When an empty list is encountered, :func:`!_to_dict` returns a list of the string representations of the related instances. If either `include` or `exclude` is not ``None``, exactly one of them must be specified. If both are not ``None``, then this function will raise a :exc:`ValueError`. `exclude` must be a list of strings specifying the columns which will *not* be present in the returned dictionary representation of the object (in other words, it is a blacklist). Similarly, `include` specifies the only columns which will be present in the returned dictionary (in other words, it is a whitelist). .. note:: If `include` is an iterable of length zero (like the empty tuple or the empty list), then the returned dictionary will be empty. If `include` is ``None``, then the returned dictionary will include all columns not excluded by `exclude`. `include_relations` is a dictionary mapping strings representing relation fields on the specified `instance` to a list of strings representing the names of fields on the related model which should be included in the returned dictionary; `exclude_relations` is similar. `include_methods` is a list mapping strings to method names which will be called and their return values added to the returned dictionary. """ if (exclude is not None or exclude_relations is not None) and \ (include is not None or include_relations is not None): raise ValueError('Cannot specify both include and exclude.') # create a list of names of columns, including hybrid properties instance_type = type(instance) columns = [] try: inspected_instance = sqlalchemy_inspect(instance_type) column_attrs = inspected_instance.column_attrs.keys() descriptors = inspected_instance.all_orm_descriptors.items() hybrid_columns = [ k for k, d in descriptors if d.extension_type == hybrid.HYBRID_PROPERTY and not (deep and k in deep) ] columns = column_attrs + hybrid_columns except NoInspectionAvailable: return instance # filter the columns based on exclude and include values if exclude is not None: columns = (c for c in columns if c not in exclude) elif include is not None: columns = (c for c in columns if c in include) # create a dictionary mapping column name to value result = dict((col, getattr(instance, col)) for col in columns if not (col.startswith('__') or col in COLUMN_BLACKLIST)) # add any included methods if include_methods is not None: result.update( dict((method, getattr(instance, method)()) for method in include_methods if not '.' in method)) # Check for objects in the dictionary that may not be serializable by # default. Convert datetime objects to ISO 8601 format, convert UUID # objects to hexadecimal strings, etc. for key, value in result.items(): if "password" == key: del result["password"] elif "current_login_ip" == key: del result["current_login_ip"] elif "last_login_ip" == key: del result["last_login_ip"] else: if isinstance(value, (datetime.date, datetime.time)): result[key] = value.isoformat() elif isinstance(value, uuid.UUID): result[key] = str(value) elif key not in column_attrs and is_mapped_class(type(value)): result[key] = to_dict(value) # recursively call _to_dict on each of the `deep` relations deep = deep or {} for relation, rdeep in deep.items(): # Get the related value so we can see if it is None, a list, a query # (as specified by a dynamic relationship loader), or an actual # instance of a model. relatedvalue = getattr(instance, relation) if relatedvalue is None: result[relation] = None continue # Determine the included and excluded fields for the related model. newexclude = None newinclude = None if exclude_relations is not None and relation in exclude_relations: newexclude = exclude_relations[relation] elif (include_relations is not None and relation in include_relations): newinclude = include_relations[relation] # Determine the included methods for the related model. newmethods = None if include_methods is not None: newmethods = [ method.split('.', 1)[1] for method in include_methods if method.split('.', 1)[0] == relation ] if is_like_list(instance, relation): result[relation] = [ to_dict(inst, rdeep, exclude=newexclude, include=newinclude, include_methods=newmethods) for inst in relatedvalue ] continue # If the related value is dynamically loaded, resolve the query to get # the single instance. if isinstance(relatedvalue, Query): relatedvalue = relatedvalue.one() result[relation] = to_dict(relatedvalue, rdeep, exclude=newexclude, include=newinclude, include_methods=newmethods) return result
def is_mapped_class(cls): try: sqlalchemy_inspect(cls) return True except: return False
def get_relations(model): """Yields the name of each relationship of a model as a string. For a relationship via an association proxy, this function shows only the remote attribute, not the intermediate relationship. For example, if there is a table for ``Article`` and ``Tag`` and a table associating the two via a many-to-many relationship, :: from sqlalchemy import Column from sqlalchemy import ForeignKey from sqlalchemy import Integer from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import relationship Base = declarative_base() class Article(Base): __tablename__ = 'article' id = Column(Integer, primary_key=True) articletags = relationship('ArticleTag') tags = association_proxy('articletags', 'tag', creator=lambda tag: ArticleTag(tag=tag)) class ArticleTag(Base): __tablename__ = 'articletag' article_id = Column(Integer, ForeignKey('article.id'), primary_key=True) tag_id = Column(Integer, ForeignKey('tag.id'), primary_key=True) tag = relationship('Tag') class Tag(self.Base): __tablename__ = 'tag' id = Column(Integer, primary_key=True) then this function reveals the ``tags`` proxy:: >>> list(get_relations(Article)) ['tags'] Similarly, for association proxies that proxy to a scalar collection via an association table, this will show the related model. For example, if there is an association proxy for a scalar collection like this:: from sqlalchemy import Column from sqlalchemy import ForeignKey from sqlalchemy import Integer from sqlalchemy import Table from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import relationship Base = declarative_base() class Article(Base): __tablename__ = 'article' id = Column(Integer, primary_key=True) tags = relationship('Tag', secondary=lambda: articletags_table) tag_names = association_proxy('tags', 'name', creator=lambda s: Tag(name=s)) class Tag(self.Base): __tablename__ = 'tag' id = Column(Integer, primary_key=True) name = Column(Unicode) articletags_table = \ Table('articletags', Base.metadata, Column('article_id', Integer, ForeignKey('article.id'), primary_key=True), Column('tag_id', Integer, ForeignKey('tag.id'), primary_key=True) ) then this function yields only the ``tags`` relationship, not the ``tag_names`` attribute:: >>> list(get_relations(Article)) ['tags'] """ mapper = sqlalchemy_inspect(model) # If we didn't have to deal with association proxies, we could just # do `return list(mapper.relationships)`. # # However, we need to deal with (at least) two different usages of # association proxies: one in which the proxy is to a scalar # collection (like a list of strings) and one in which the proxy is # to a collection of instances (like a to-many relationship). # # First we record each association proxy and the the local attribute # through which it proxies. This information is stored in a mapping # from local attribute key to proxy name. For example, an # association proxy defined like this:: # # tags = associationproxy('articletags', 'tag') # # is stored below as a dictionary entry mapping 'articletags' to # 'tags'. association_proxies = {} for k, v in mapper.all_orm_descriptors.items(): if isinstance(v, AssociationProxy): association_proxies[v.local_attr.key] = k # Next we determine which association proxies represent scalar # collections as opposed to to-many relationships. We need to ignore # these. scalar_collections = set(assoc_proxy_scalar_collections(model)) # Finally we find all plain old relationships and all association # proxy relationships. # # If the association proxy is through an association object, we # yield that too. for r in mapper.relationships.keys(): yield r proxy = association_proxies.get(r) if proxy is not None and proxy not in scalar_collections: yield association_proxies[r]
def to_dict(instance, deep=None, exclude=None, include=None, exclude_relations=None, include_relations=None, include_methods=None): """Returns a dictionary representing the fields of the specified `instance` of a SQLAlchemy model. The returned dictionary is suitable as an argument to :func:`flask.jsonify`; :class:`datetime.date` and :class:`uuid.UUID` objects are converted to string representations, so no special JSON encoder behavior is required. `deep` is a dictionary containing a mapping from a relation name (for a relation of `instance`) to either a list or a dictionary. This is a recursive structure which represents the `deep` argument when calling :func:`!_to_dict` on related instances. When an empty list is encountered, :func:`!_to_dict` returns a list of the string representations of the related instances. If either `include` or `exclude` is not ``None``, exactly one of them must be specified. If both are not ``None``, then this function will raise a :exc:`ValueError`. `exclude` must be a list of strings specifying the columns which will *not* be present in the returned dictionary representation of the object (in other words, it is a blacklist). Similarly, `include` specifies the only columns which will be present in the returned dictionary (in other words, it is a whitelist). .. note:: If `include` is an iterable of length zero (like the empty tuple or the empty list), then the returned dictionary will be empty. If `include` is ``None``, then the returned dictionary will include all columns not excluded by `exclude`. `include_relations` is a dictionary mapping strings representing relation fields on the specified `instance` to a list of strings representing the names of fields on the related model which should be included in the returned dictionary; `exclude_relations` is similar. `include_methods` is a list mapping strings to method names which will be called and their return values added to the returned dictionary. """ if (exclude is not None or exclude_relations is not None) and \ (include is not None or include_relations is not None): raise ValueError('Cannot specify both include and exclude.') # create a list of names of columns, including hybrid properties instance_type = type(instance) columns = [] try: inspected_instance = sqlalchemy_inspect(instance_type) column_attrs = inspected_instance.column_attrs.keys() descriptors = inspected_instance.all_orm_descriptors.items() hybrid_columns = [k for k, d in descriptors if d.extension_type == hybrid.HYBRID_PROPERTY] columns = column_attrs + hybrid_columns except NoInspectionAvailable: return instance # filter the columns based on exclude and include values if exclude is not None: columns = (c for c in columns if c not in exclude) elif include is not None: columns = (c for c in columns if c in include) # create a dictionary mapping column name to value result = dict((col, getattr(instance, col)) for col in columns if not (col.startswith('__') or col in COLUMN_BLACKLIST)) # add any included methods if include_methods is not None: result.update(dict((method, getattr(instance, method)()) for method in include_methods if not '.' in method)) # Check for objects in the dictionary that may not be serializable by # default. Convert datetime objects to ISO 8601 format, convert UUID # objects to hexadecimal strings, etc. for key, value in result.items(): if isinstance(value, (datetime.date, datetime.time)): result[key] = value.isoformat() elif isinstance(value, uuid.UUID): result[key] = str(value) elif key not in column_attrs and is_mapped_class(type(value)): result[key] = to_dict(value) # recursively call _to_dict on each of the `deep` relations deep = deep or {} for relation, rdeep in deep.items(): # Get the related value so we can see if it is None, a list, a query # (as specified by a dynamic relationship loader), or an actual # instance of a model. relatedvalue = getattr(instance, relation) if relatedvalue is None: result[relation] = None continue # Determine the included and excluded fields for the related model. newexclude = None newinclude = None if exclude_relations is not None and relation in exclude_relations: newexclude = exclude_relations[relation] elif (include_relations is not None and relation in include_relations): newinclude = include_relations[relation] # Determine the included methods for the related model. newmethods = None if include_methods is not None: newmethods = [method.split('.', 1)[1] for method in include_methods if method.split('.', 1)[0] == relation] if is_like_list(instance, relation): result[relation] = [to_dict(inst, rdeep, exclude=newexclude, include=newinclude, include_methods=newmethods) for inst in relatedvalue] continue # If the related value is dynamically loaded, resolve the query to get # the single instance. if isinstance(relatedvalue, Query): relatedvalue = relatedvalue.one() result[relation] = to_dict(relatedvalue, rdeep, exclude=newexclude, include=newinclude, include_methods=newmethods) return result
def to_dict(instance, deep=None, exclude=None, include=None, exclude_relations=None, include_relations=None, include_methods=None): COLUMN_BLACKLIST = ('_sa_polymorphic_on', ) if (exclude is not None or exclude_relations is not None) and \ (include is not None or include_relations is not None): raise ValueError('Cannot specify both include and exclude.') # create a list of names of columns, including hybrid properties instance_type = type(instance) columns = [] try: inspected_instance = sqlalchemy_inspect(instance_type) column_attrs = inspected_instance.column_attrs.keys() descriptors = inspected_instance.all_orm_descriptors.items() hybrid_columns = [k for k, d in descriptors if d.extension_type == hybrid.HYBRID_PROPERTY and not (deep and k in deep)] columns = column_attrs + hybrid_columns except NoInspectionAvailable: return instance # filter the columns based on exclude and include values if exclude is not None: columns = (c for c in columns if c not in exclude) elif include is not None: columns = (c for c in columns if c in include) # create a dictionary mapping column name to value result = dict((col, getattr(instance, col)) for col in columns if not (col.startswith('__') or col in COLUMN_BLACKLIST)) # add any included methods if include_methods is not None: for method in include_methods: if '.' not in method: value = getattr(instance, method) # Allow properties and static attributes in include_methods if callable(value): value = value() result[method] = value # Check for objects in the dictionary that may not be serializable by # default. Convert datetime objects to ISO 8601 format, convert UUID # objects to hexadecimal strings, etc. for key, value in result.items(): if isinstance(value, (datetime.date, datetime.time)): result[key] = value.isoformat() elif isinstance(value, uuid.UUID): result[key] = str(value) elif key not in column_attrs and is_mapped_class(type(value)): result[key] = instance.to_dict(value) # recursively call _to_dict on each of the `deep` relations deep = deep or {} for relation, rdeep in deep.items(): # Get the related value so we can see if it is None, a list, a query # (as specified by a dynamic relationship loader), or an actual # instance of a model. relatedvalue = getattr(instance, relation) if relatedvalue is None: result[relation] = None continue # Determine the included and excluded fields for the related model. newexclude = None newinclude = None if exclude_relations is not None and relation in exclude_relations: newexclude = exclude_relations[relation] elif (include_relations is not None and relation in include_relations): newinclude = include_relations[relation] # Determine the included methods for the related model. newmethods = None if include_methods is not None: newmethods = [method.split('.', 1)[1] for method in include_methods if method.split('.', 1)[0] == relation] if is_like_list(instance, relation): result[relation] = [instance.to_dict(inst, rdeep, exclude=newexclude, include=newinclude, include_methods=newmethods) for inst in relatedvalue] continue # If the related value is dynamically loaded, resolve the query to get # the single instance. if isinstance(relatedvalue, Query): relatedvalue = relatedvalue.one() result[relation] = instance.to_dict(relatedvalue, rdeep, exclude=newexclude, include=newinclude, include_methods=newmethods) return result