class File(db.Model, IntBase): __tablename__ = 'grano_file' file_name = db.Column(db.Unicode) mime_type = db.Column(db.Unicode) project_id = db.Column(db.Integer, db.ForeignKey('grano_project.id')) author_id = db.Column(db.Integer, db.ForeignKey('grano_account.id')) data = db.Column(db.LargeBinary) properties = db.relationship('Property', backref='value_file', cascade='all, delete, delete-orphan', lazy='dynamic') @property def fh(self): return StringIO(self.data) def to_dict_index(self): return { 'id': self.id, 'project': self.project.to_dict_index(), 'api_url': url_for('files_api.view', id=self.id), 'serve_api_url': url_for('files_api.serve', id=self.id), 'file_name': self.file_name, 'mime_type': self.mime_type } def to_dict(self): """ Full serialization of the file metadata. """ return self.to_dict_index()
class Project(db.Model, IntBase): __tablename__ = 'grano_project' slug = db.Column(db.Unicode) label = db.Column(db.Unicode) private = db.Column(db.Boolean, default=False) settings = db.Column(MutableDict.as_mutable(JSONEncodedDict)) author_id = db.Column(db.Integer, db.ForeignKey('grano_account.id')) relations = db.relationship('Relation', backref='project', lazy='dynamic', cascade='all, delete, delete-orphan') entities = db.relationship('Entity', backref='project', lazy='dynamic', cascade='all, delete, delete-orphan') pipelines = db.relationship('Pipeline', backref='project', lazy='dynamic', cascade='all, delete, delete-orphan') schemata = db.relationship('Schema', backref='project', lazy='dynamic', cascade='all, delete, delete-orphan') permissions = db.relationship('Permission', backref='project', lazy='dynamic', cascade='all, delete, delete-orphan') files = db.relationship('File', backref='project', lazy='dynamic', cascade='all, delete, delete-orphan') def get_attribute(self, obj, name): for schema in self.schemata: if schema.obj == obj: for attr in schema.attributes: if attr.name == name: return attr @classmethod def by_slug(cls, slug): q = db.session.query(cls).filter_by(slug=slug) return q.first() def to_dict_index(self): return { 'slug': self.slug, 'label': self.label, 'private': self.private, 'api_url': url_for('projects_api.view', slug=self.slug), 'entities_count': self.entities.count(), 'relations_count': self.relations.count() } def to_dict(self): data = self.to_dict_index() data['settings'] = self.settings data['author'] = self.author.to_dict_index() data['schemata_index_url'] = url_for('schemata_api.index', slug=self.slug) data['entities_index_url'] = url_for('entities_api.index', project=self.slug) data['relations_index_url'] = url_for('relations_api.index', project=self.slug) return data
class _CoreBase(object): created_at = db.Column(db.DateTime, default=datetime.utcnow) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) @classmethod def by_id(cls, id): q = db.session.query(cls).filter_by(id=id) return q.first() @classmethod def all(cls): return db.session.query(cls)
class BidiRelation(db.Model): __tablename__ = 'grano_relation_bidi' id = db.Column(db.Unicode, primary_key=True) created_at = db.Column(db.DateTime) updated_at = db.Column(db.DateTime) reverse = db.Column(db.Boolean) relation_id = db.Column(db.Unicode) source_id = db.Column(db.Unicode) target_id = db.Column(db.Unicode) project_id = db.Column(db.Integer) schema_id = db.Column(db.Integer) author_id = db.Column(db.Integer)
class Attribute(db.Model): """ Attributes are specific properties of a schema for either an entity or a relation. They materialize as columns on the joined sub-table for the schema. """ __tablename__ = 'schema_attribute' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.Unicode) label = db.Column(db.Unicode) type = db.Column(db.Unicode) help = db.Column(db.Unicode) missing = db.Column(db.Unicode) schema_id = db.Column(db.Integer, db.ForeignKey('schema.id')) schema = db.relationship(Schema, backref=db.backref('attributes', lazy='dynamic')) created_at = db.Column(db.DateTime, default=datetime.utcnow) updated_at = db.Column(db.DateTime, onupdate=datetime.utcnow) @classmethod def create(cls, name, data): obj = cls() obj.update(name, data) return obj def update(self, name, data): self.name = name self.label = data.get('label') self.type = data.get('type') self.help = data.get('help') self.missing = data.get('missing') db.session.add(self) db.session.flush() def delete(self): db.session.delete(self) def as_dict(self): return { 'label': self.label, 'type': self.type, 'help': self.help, 'missing': self.missing } @property def column(self): # TODO: do we also need some typecasting mechanism? type_ = ATTRIBUTE_TYPES_DB[self.type] return db.Column(self.name, type_) def __repr__(self): return "<Attribute(%s,%s)>" % (self.id, self.name)
class Attribute(db.Model, IntBase): __tablename__ = 'attribute' name = db.Column(db.Unicode()) label = db.Column(db.Unicode()) description = db.Column(db.Unicode()) hidden = db.Column(db.Boolean()) schema_id = db.Column(db.Integer, db.ForeignKey('schema.id')) @classmethod def by_name(cls, schema, name): q = db.session.query(cls) q = q.filter_by(schema=schema) q = q.filter_by(name=name) return q.first()
class EntityProperty(Property): __mapper_args__ = {'polymorphic_identity': 'entity'} entity_id = db.Column(db.Unicode(), db.ForeignKey('entity.id'), index=True) def _set_obj(self, obj): self.entity = obj
class Pipeline(db.Model, IntBase): __tablename__ = 'grano_pipeline' STATUS_PENDING = 'pending' STATUS_RUNNING = 'running' STATUS_FAILED = 'failed' STATUS_COMPLETE = 'complete' operation = db.Column(db.Unicode) label = db.Column(db.Unicode) status = db.Column(db.Unicode) percent_complete = db.Column(db.Integer, default=int) project_id = db.Column(db.Integer, db.ForeignKey('grano_project.id')) author_id = db.Column(db.Integer, db.ForeignKey('grano_account.id')) config = db.Column(MutableDict.as_mutable(JSONEncodedDict)) started_at = db.Column(db.DateTime, default=datetime.utcnow) ended_at = db.Column(db.DateTime) entries = db.relationship('LogEntry', backref='pipeline', lazy='dynamic', cascade='all, delete, delete-orphan') def has_errors(self): q = self.entries.filter_by(level=logging.ERROR) return q.count() > 0 def to_dict_index(self): return { 'id': self.id, 'label': self.label, 'project': self.project.to_dict_short() if self.project else None, 'author': self.author.to_dict_index(), 'api_url': url_for('pipelines_api.view', id=self.id), 'operation': self.operation, 'status': self.status, 'created_at': self.created_at, 'updated_at': self.updated_at, 'started_at': self.started_at, 'ended_at': self.ended_at, 'config': self.config, 'percent_complete': self.percent_complete } def to_dict(self): """ Full serialization of the file metadata. """ data = self.to_dict_index() return data
class Project(db.Model, IntBase): __tablename__ = 'project' slug = db.Column(db.Unicode) label = db.Column(db.Unicode) settings = db.Column(MutableDict.as_mutable(JSONEncodedDict)) author_id = db.Column(db.Integer, db.ForeignKey('account.id')) relations = db.relationship('Relation', backref='project', lazy='dynamic') entities = db.relationship('Entity', backref='project', lazy='dynamic') schemata = db.relationship('Schema', backref='project', lazy='dynamic') @classmethod def by_slug(cls, slug): q = db.session.query(cls).filter_by(slug=slug) return q.first()
class Schema(db.Model, IntBase): __tablename__ = 'schema' name = db.Column(db.Unicode()) label = db.Column(db.Unicode()) label_in = db.Column(db.Unicode()) label_out = db.Column(db.Unicode()) hidden = db.Column(db.Boolean()) obj = db.Column(db.Unicode()) attributes = db.relationship(Attribute, backref='schema', lazy='joined') properties = db.relationship(Property, backref='schema', lazy='dynamic') relations = db.relationship('Relation', backref='schema', lazy='dynamic') project_id = db.Column(db.Integer, db.ForeignKey('project.id')) def get_attribute(self, name): for attribute in self.attributes: if attribute.name == name: return attribute @classmethod def by_name(cls, project, name): q = db.session.query(cls).filter_by(name=name) q = q.filter_by(project=project) return q.first() @classmethod def by_obj_name(cls, project, obj, name): q = db.session.query(cls) q = q.filter_by(project=project) q = q.filter_by(name=name) q = q.filter_by(obj=obj) return q.first()
class Relation(db.Model, UUIDBase, PropertyBase): __tablename__ = 'relation' PropertyClass = RelationProperty schema_id = db.Column(db.Integer, db.ForeignKey('schema.id'), index=True) source_id = db.Column(db.Unicode, db.ForeignKey('entity.id'), index=True) target_id = db.Column(db.Unicode, db.ForeignKey('entity.id'), index=True) project_id = db.Column(db.Integer, db.ForeignKey('project.id')) author_id = db.Column(db.Integer, db.ForeignKey('account.id')) properties = db.relationship(RelationProperty, order_by=RelationProperty.created_at.desc(), backref='relation', lazy='dynamic') @property def schemata(self): return [self.schema]
class RelationProperty(Property): __mapper_args__ = {'polymorphic_identity': 'relation'} relation_id = db.Column(db.Unicode(), db.ForeignKey('relation.id'), index=True) def _set_obj(self, obj): self.relation = obj
class Relation(db.Model, UUIDBase, PropertyBase): __tablename__ = 'grano_relation' #PropertyClass = RelationProperty schema_id = db.Column(db.Integer, db.ForeignKey('grano_schema.id'), index=True) source_id = db.Column(db.Unicode, db.ForeignKey('grano_entity.id'), index=True) target_id = db.Column(db.Unicode, db.ForeignKey('grano_entity.id'), index=True) project_id = db.Column(db.Integer, db.ForeignKey('grano_project.id')) author_id = db.Column(db.Integer, db.ForeignKey('grano_account.id')) properties = db.relationship(Property, order_by=Property.created_at.desc(), cascade='all, delete, delete-orphan', backref='relation', lazy='dynamic') @property def schemata(self): return [self.schema] def to_dict_base(self): return { 'id': self.id, 'properties': {}, 'project': self.project.to_dict_index(), 'api_url': url_for('relations_api.view', id=self.id), 'schema': self.schema.to_dict_index(), 'source': self.source.to_dict_index(), 'target': self.target.to_dict_index() } def to_dict(self): data = self.to_dict_base() for prop in self.active_properties: name, prop = prop.to_dict_kv() data['properties'][name] = prop return data def to_dict_index(self): data = self.to_dict_base() for prop in self.active_properties: name, prop = prop.to_dict_kv() data['properties'][name] = prop return data
def _make_cls(self): """ Generate a new type, mapped through SQLAlchemy. This will be a joined subtable to either an entity or a relation and retain a copy of its composite primary key plus any attributes defined in the schema. """ prefix = self.parent_cls.__tablename__ # inherit primary key: cls = { '__tablename__': prefix + '__' + self.name, 'id': db.Column(db.String(36), primary_key=True), 'serial': db.Column(db.BigInteger, primary_key=True) } # set up inheritance: cls['__mapper_args__'] = { 'polymorphic_identity': self.name, 'inherit_condition': db.and_(cls['id'] == self.parent_cls.id, cls['serial'] == self.parent_cls.serial) } cls['__table_args__'] = {'extend_existing': True} # set up the specific attributes: for attribute in self.attributes: cls[attribute.name] = attribute.column # make an as_dict method: def as_dict(ins): d = self.parent_cls.as_dict(ins) for attribute in self.attributes: d[attribute.name] = \ getattr(ins, attribute.name) return d cls['as_dict'] = as_dict self._cls = type(str(self.name), (self.parent_cls, ), cls)
class LogEntry(db.Model, IntBase): __tablename__ = 'grano_log_entry' pipeline_id = db.Column(db.Integer, db.ForeignKey('grano_pipeline.id')) level = db.Column(db.Integer) message = db.Column(db.Unicode) error = db.Column(db.Unicode) details = db.Column(MutableDict.as_mutable(JSONEncodedDict)) def to_dict_index(self): return { 'id': self.id, 'level': self.level, 'message': self.message, 'error': self.error, 'api_url': url_for('log_entries_api.view_entry', pipeline_id=self.pipeline.id, id=self.id), 'created_at': self.created_at, 'updated_at': self.updated_at } def to_dict(self): """ Full serialization of the file metadata. """ data = self.to_dict_index() data['details'] = self.details return data
class Permission(db.Model, IntBase): __tablename__ = 'grano_permission' reader = db.Column(db.Boolean) editor = db.Column(db.Boolean) admin = db.Column(db.Boolean) account_id = db.Column(db.Integer, db.ForeignKey('grano_account.id')) project_id = db.Column(db.Integer, db.ForeignKey('grano_project.id')) @classmethod def by_project_and_id(cls, project, id): q = db.session.query(cls).filter_by(id=id) q = q.filter_by(project=project) return q.first() def to_dict_index(self): return { 'id': self.id, 'reader': self.reader, 'editor': self.editor, 'admin': self.admin, 'project': self.project.to_dict_index(), 'account': self.account.to_dict_index(), 'api_url': url_for('permissions_api.view', slug=self.project.slug, id=self.id) } def to_dict(self): return self.to_dict_index()
class Attribute(db.Model, IntBase): __tablename__ = 'grano_attribute' DATATYPES = { 'string': 'value_string', 'integer': 'value_integer', 'float': 'value_float', 'datetime': 'value_datetime', 'boolean': 'value_boolean', 'file': 'value_file_id' } name = db.Column(db.Unicode()) label = db.Column(db.Unicode()) description = db.Column(db.Unicode()) hidden = db.Column(db.Boolean()) datatype = db.Column(db.Unicode()) schema_id = db.Column(db.Integer, db.ForeignKey('grano_schema.id')) properties = db.relationship('Property', backref='attribute', cascade='all, delete, delete-orphan', lazy='dynamic') @property def value_column(self): return self.DATATYPES.get(self.datatype) @classmethod def all_named(cls, name): q = db.session.query(cls) q = q.filter_by(name=name) return q.all() @classmethod def by_schema_and_name(cls, schema, name): q = db.session.query(cls) q = q.filter_by(schema=schema) q = q.filter_by(name=name) return q.first() def to_index(self): return { 'name': self.name, 'label': self.label, 'datatype': self.datatype } def to_dict(self): data = self.to_index() data['id'] = self.id data['hidden'] = self.hidden if self.description and len(self.description): data['description'] = self.description return data
class Account(db.Model, IntBase): __tablename__ = 'account' github_id = db.Column(db.Unicode) twitter_id = db.Column(db.Unicode) facebook_id = db.Column(db.Unicode) full_name = db.Column(db.Unicode) login = db.Column(db.Unicode) email = db.Column(db.Unicode) api_key = db.Column(db.Unicode, default=make_token) projects = db.relationship('Project', backref='author', lazy='dynamic') properties = db.relationship('Property', backref='author', lazy='dynamic') relations = db.relationship('Relation', backref='author', lazy='dynamic') entities = db.relationship('Entity', backref='author', lazy='dynamic') @property def display_name(self): if self.full_name is not None and len(self.full_name.strip()): return self.full_name if self.login is not None and len(self.login.strip()): return self.login return self.email @classmethod def by_api_key(cls, api_key): q = db.session.query(cls).filter_by(api_key=api_key) return q.first() @classmethod def by_login(cls, login): q = db.session.query(cls).filter_by(login=login) return q.first() @classmethod def by_github_id(cls, github_id): q = db.session.query(cls).filter_by(github_id=str(github_id)) return q.first() @classmethod def by_twitter_id(cls, twitter_id): q = db.session.query(cls).filter_by(twitter_id=str(twitter_id)) return q.first() @classmethod def by_facebook_id(cls, facebook_id): q = db.session.query(cls).filter_by(facebook_id=str(facebook_id)) return q.first()
class Relation(db.Model, RevisionedMixIn, ViewMixIn): """ Edge data type. This is never instantiated directly, only through a schema definition which will create a joined subtype. """ __tablename__ = relation_table_name id = db.Column(db.String(36), primary_key=True, default=util.make_id) serial = db.Column(db.BigInteger, primary_key=True, default=util.make_serial) type = db.Column(db.Unicode) __mapper_args__ = {'polymorphic_on': type} __table_args__ = {'extend_existing': True} current = db.Column(db.Boolean) created_at = db.Column(db.DateTime, default=datetime.utcnow) source_id = db.Column(db.String(36)) target_id = db.Column(db.String(36)) def update_values(self, schema, data): self.source_id = data.get('source').id self.target_id = data.get('target').id def as_dict(self): return { 'id': self.id, 'serial': self.serial, 'type': self.type, 'current': self.current, 'created_at': self.created_at, 'source': self.source_id, 'target': self.target_id } def as_deep_dict(self): data = self.as_dict() data['source'] = self.source.as_dict() data['target'] = self.target.as_dict() return data def as_nx(self, graph): d = util.graph_values(self.as_dict()) return graph.add_edge(self.source_id, self.target_id, **d) def __repr__(self): return "<Relation:%s(%s,%s,%s)>" % (self.type, self.id, self.source_id, self.target_id)
class Property(db.Model, IntBase): __tablename__ = 'property' schema_id = db.Column(db.Integer, db.ForeignKey('schema.id')) author_id = db.Column(db.Integer, db.ForeignKey('account.id')) name = db.Column(db.Unicode(), index=True) value = db.Column(db.Unicode()) source_url = db.Column(db.Unicode()) active = db.Column(db.Boolean()) obj = db.Column(db.String(20)) __mapper_args__ = {'polymorphic_on': obj} def to_dict(self): return { 'name': self.name, 'schema': self.schema.name, 'value': self.value, 'source_url': self.source_url, 'active': self.active }
class Entity(db.Model, UUIDBase, PropertyBase): __tablename__ = 'entity' PropertyClass = EntityProperty same_as = db.Column(db.Unicode, db.ForeignKey('entity.id'), nullable=True) project_id = db.Column(db.Integer, db.ForeignKey('project.id')) author_id = db.Column(db.Integer, db.ForeignKey('account.id')) schemata = db.relationship('Schema', secondary=entity_schema, backref=db.backref('entities', lazy='dynamic')) inbound = db.relationship('Relation', lazy='dynamic', backref='target', primaryjoin='Entity.id==Relation.target_id') outbound = db.relationship('Relation', lazy='dynamic', backref='source', primaryjoin='Entity.id==Relation.source_id') properties = db.relationship(EntityProperty, backref='entity', order_by=EntityProperty.created_at.desc(), lazy='joined') @property def names(self): return [p for p in self.properties if p.name == 'name'] @classmethod def by_name(cls, project, name, only_active=False): q = db.session.query(cls) a = q.filter(cls.project == project) q = cls._filter_property(q, 'name', name, only_active=only_active) return q.first() @property def inbound_schemata(self): from grano.model.relation import Relation q = db.session.query(Schema) q = q.join(Schema.relations) q = q.filter(Relation.target_id == self.id) return q.distinct() def inbound_by_schema(self, schema): q = self.inbound.filter_by(schema=schema) return q @property def outbound_schemata(self): from grano.model.relation import Relation q = db.session.query(Schema) q = q.join(Schema.relations) q = q.filter(Relation.source_id == self.id) return q.distinct() def outbound_by_schema(self, schema): q = self.outbound.filter_by(schema=schema) return q @property def degree(self): return self.inbound.count() + self.outbound.count()
class Schema(db.Model, IntBase): __tablename__ = 'grano_schema' name = db.Column(db.Unicode()) label = db.Column(db.Unicode()) hidden = db.Column(db.Boolean()) obj = db.Column(db.Unicode()) meta = db.Column(MutableDict.as_mutable(JSONEncodedDict)) project_id = db.Column(db.Integer, db.ForeignKey('grano_project.id')) parent_id = db.Column(db.Integer, db.ForeignKey('grano_schema.id'), nullable=True) local_attributes = db.relationship(Attribute, backref='schema', lazy='dynamic', cascade='all, delete, delete-orphan') relations = db.relationship('Relation', backref='schema', lazy='dynamic', cascade='all, delete, delete-orphan') entities = db.relationship('Entity', backref='schema', lazy='dynamic', cascade='all, delete, delete-orphan') children = db.relationship('Schema', lazy='dynamic', backref=db.backref('parent', remote_side='Schema.id')) @property def inherited_attributes(self): if self.parent is None: return [] return self.parent.attributes @property def attributes(self): return list(self.local_attributes) + self.inherited_attributes def get_attribute(self, name): for attribute in self.attributes: if attribute.name == name: return attribute def is_circular(self, path=None): if path is None: path = [] if self.name in path: return True elif self.parent is None: return False else: path.append(self.name) return self.parent.is_circular(path) def is_parent(self, other): if self.parent is None: return False if self.parent == other: return True return self.parent.is_parent(other) def common_parent(self, other): if self == other or self.is_parent(other): return self return self.common_parent(other.parent) @classmethod def by_name(cls, project, name): q = db.session.query(cls).filter_by(name=name) q = q.filter_by(project=project) return q.first() @classmethod def by_obj_name(cls, project, obj, name): q = db.session.query(cls) q = q.filter_by(project=project) q = q.filter_by(name=name) q = q.filter_by(obj=obj) return q.first() def to_dict_index(self): return { 'name': self.name, 'label': self.label, 'hidden': self.hidden, 'obj': self.obj, 'api_url': url_for('schemata_api.view', slug=self.project.slug, name=self.name) } def to_dict(self): data = self.to_dict_index() data['id'] = self.id data['meta'] = self.meta if self.parent is not None: data['parent'] = self.parent.to_dict_index() else: data['parent'] = None data['project'] = self.project.to_dict_short() data['attributes'] = [a.to_dict() for a in self.local_attributes] for attr in self.inherited_attributes: d = attr.to_dict() d['inherited'] = True data['attributes'].append(d) return data
class Entity(db.Model, UUIDBase, PropertyBase): __tablename__ = 'grano_entity' same_as = db.Column(db.Unicode, db.ForeignKey('grano_entity.id'), nullable=True) project_id = db.Column(db.Integer, db.ForeignKey('grano_project.id')) author_id = db.Column(db.Integer, db.ForeignKey('grano_account.id')) schema_id = db.Column(db.Integer, db.ForeignKey('grano_schema.id'), index=True) degree_in = db.Column(db.Integer) degree_out = db.Column(db.Integer) degree = db.Column(db.Integer) inbound = db.relationship('Relation', lazy='dynamic', backref='target', primaryjoin='Entity.id==Relation.target_id', cascade='all, delete, delete-orphan') outbound = db.relationship('Relation', lazy='dynamic', backref='source', primaryjoin='Entity.id==Relation.source_id', cascade='all, delete, delete-orphan') properties = db.relationship(Property, backref='entity', order_by=Property.created_at.desc(), cascade='all, delete, delete-orphan', lazy='joined') @property def names(self): return [p for p in self.properties if p.name == 'name'] @classmethod def by_name(cls, project, name, only_active=False): q = cls.by_name_many(project, name, only_active=only_active) return q.first() @classmethod def by_name_many(cls, project, name, only_active=False): q = db.session.query(cls) q = q.filter(cls.project == project) q = cls._filter_property(q, 'name', name, only_active=only_active) return q def to_dict_index(self): """ Convert an entity to the REST API form. """ data = { 'id': self.id, 'degree': self.degree, 'degree_in': self.degree_in, 'degree_out': self.degree_out, 'project': self.project.to_dict_short(), 'schema': self.schema.to_dict_index(), 'api_url': url_for('entities_api.view', id=self.id), 'properties': {} } for prop in self.active_properties: name, prop = prop.to_dict_kv() data['properties'][name] = prop if self.same_as: data['same_as'] = self.same_as data['same_as_url'] = url_for('entities_api.view', id=self.same_as) return data def to_dict(self): """ Full serialization of the entity. """ data = self.to_dict_index() data['created_at'] = self.created_at data['updated_at'] = self.updated_at if data['degree_in'] > 0: data['inbound_url'] = url_for('relations_api.index', target=self.id) if data['degree_out'] > 0: data['outbound_url'] = url_for('relations_api.index', source=self.id) return data def to_index(self): """ Convert an entity to a form appropriate for search indexing. """ data = self.to_dict() data['names'] = [] for prop in self.properties: if prop.name == 'name': data['names'].append(prop.value) return data
class Schema(db.Model): """ A schema defines a specific subtype of either an entity or a relation. This can mean any graph element, such as a person, company or other actor type for entities - or a type of social, economic or political link for a relation (e.g. ownership, school attendance, ..). A schema is defined through a model structure that contains necessary metadata to handle the schema both internally and via the user interface. """ __tablename__ = 'schema' ENTITY = 'entity' RELATION = 'relation' TYPES = [ENTITY, RELATION] id = db.Column(db.Integer, primary_key=True) name = db.Column(db.Unicode) label = db.Column(db.Unicode) entity = db.Column(db.Unicode) network_id = db.Column(db.Integer, db.ForeignKey('network.id')) network = db.relationship('Network', backref=db.backref('schemata', lazy='dynamic')) created_at = db.Column(db.DateTime, default=datetime.utcnow) updated_at = db.Column(db.DateTime, onupdate=datetime.utcnow) @classmethod def create(cls, network, entity, data): obj = cls() obj.update(network, entity, data) return obj def update(self, network, entity, data): self.network = network self.name = str(data.get('name')) self.label = data.get('label') self.entity = entity db.session.add(self) db.session.flush() attributes = data.get('attributes', {}) for attribute in self.attributes: if attribute.name in attributes: del attributes[attribute.name] else: attribute.delete() for name, data in attributes.items(): attr = Attribute.create(name, data) self.attributes.append(attr) db.session.flush() self.migrate() def migrate(self): self._make_cls() table = self._cls.__table__ if not table.exists(db.engine): table.create(db.engine) else: table.metadata.bind = db.engine for attribute in self.attributes: try: col = table.c[attribute.name] col.create() except Exception as e: #log.exception(e) pass def delete(self): for attribute in self.attributes: attribute.delete() db.session.delete(self) @property def cls(self): if not hasattr(self, '_cls'): self._make_cls() return self._cls @property def parent_cls(self): return { self.ENTITY: self.network.Entity, self.RELATION: self.network.Relation, }.get(self.entity) def _make_cls(self): """ Generate a new type, mapped through SQLAlchemy. This will be a joined subtable to either an entity or a relation and retain a copy of its composite primary key plus any attributes defined in the schema. """ prefix = self.parent_cls.__tablename__ # inherit primary key: cls = { '__tablename__': prefix + '__' + self.name, 'id': db.Column(db.String(36), primary_key=True), 'serial': db.Column(db.BigInteger, primary_key=True) } # set up inheritance: cls['__mapper_args__'] = { 'polymorphic_identity': self.name, 'inherit_condition': db.and_(cls['id'] == self.parent_cls.id, cls['serial'] == self.parent_cls.serial) } cls['__table_args__'] = {'extend_existing': True} # set up the specific attributes: for attribute in self.attributes: cls[attribute.name] = attribute.column # make an as_dict method: def as_dict(ins): d = self.parent_cls.as_dict(ins) for attribute in self.attributes: d[attribute.name] = \ getattr(ins, attribute.name) return d cls['as_dict'] = as_dict self._cls = type(str(self.name), (self.parent_cls, ), cls) def as_dict(self): attrs = [(a.name, a.as_dict()) for a in self.attributes] return { 'id': self.id, 'name': self.name, 'label': self.label, 'entity': self.entity, 'attributes': dict(attrs) } def __repr__(self): return "<Schema(%s,%s)>" % (self.id, self.name)
from grano.core import db from grano.model.common import UUIDBase, PropertyBase from grano.model.schema import Schema from grano.model.relation import Relation from grano.model.property import EntityProperty entity_schema = db.Table( 'entity_schema', db.Column('entity_id', db.Unicode, db.ForeignKey('entity.id')), db.Column('schema_id', db.Integer, db.ForeignKey('schema.id'))) class Entity(db.Model, UUIDBase, PropertyBase): __tablename__ = 'entity' PropertyClass = EntityProperty same_as = db.Column(db.Unicode, db.ForeignKey('entity.id'), nullable=True) project_id = db.Column(db.Integer, db.ForeignKey('project.id')) author_id = db.Column(db.Integer, db.ForeignKey('account.id')) schemata = db.relationship('Schema', secondary=entity_schema, backref=db.backref('entities', lazy='dynamic')) inbound = db.relationship('Relation', lazy='dynamic', backref='target', primaryjoin='Entity.id==Relation.target_id') outbound = db.relationship('Relation', lazy='dynamic',
def column(self): # TODO: do we also need some typecasting mechanism? type_ = ATTRIBUTE_TYPES_DB[self.type] return db.Column(self.name, type_)
class Schema(db.Model, IntBase): __tablename__ = 'grano_schema' name = db.Column(db.Unicode()) label = db.Column(db.Unicode()) hidden = db.Column(db.Boolean()) obj = db.Column(db.Unicode()) meta = db.Column(MutableDict.as_mutable(JSONEncodedDict)) attributes = db.relationship(Attribute, backref='schema', lazy='dynamic', cascade='all, delete, delete-orphan') properties = db.relationship(Property, backref='schema', lazy='dynamic', cascade='all, delete, delete-orphan') relations = db.relationship('Relation', backref='schema', lazy='dynamic', cascade='all, delete, delete-orphan') project_id = db.Column(db.Integer, db.ForeignKey('grano_project.id')) def get_attribute(self, name): for attribute in self.attributes: if attribute.name == name: return attribute @classmethod def by_name(cls, project, name): q = db.session.query(cls).filter_by(name=name) q = q.filter_by(project=project) return q.first() @classmethod def by_obj_name(cls, project, obj, name): q = db.session.query(cls) q = q.filter_by(project=project) q = q.filter_by(name=name) q = q.filter_by(obj=obj) return q.first() def to_dict_index(self): return { 'name': self.name, 'default': self.name == ENTITY_DEFAULT_SCHEMA, 'label': self.label, 'hidden': self.hidden, 'meta': self.meta, 'obj': self.obj, 'api_url': url_for('schemata_api.view', slug=self.project.slug, name=self.name) } def to_dict(self): data = self.to_dict_index() data['id'] = self.id data['project'] = self.project.to_dict_index() data['attributes'] = [a.to_dict() for a in self.attributes] return data
class Entity(db.Model, RevisionedMixIn, ViewMixIn): """ Node type, never really instantiated directly. """ __tablename__ = entity_table_name id = db.Column(db.String(36), primary_key=True, default=util.make_id) serial = db.Column(db.BigInteger, primary_key=True, default=util.make_serial) type = db.Column(db.Unicode) __mapper_args__ = {'polymorphic_on': type} __table_args__ = {'extend_existing': True} current = db.Column(db.Boolean) created_at = db.Column(db.DateTime, default=datetime.utcnow) slug = db.Column(db.Unicode) title = db.Column(db.Unicode) _fts = db.Column(util.TSVector) def update_values(self, schema, data): self.title = data.get('title') self.slug = util.slugify(self.title) text = self.title for attribute in schema.attributes: text += ' ' + unicode(getattr(self, attribute.name) or '') self._fts = util.TSVector.make_text(db.engine, text) def delete(self, schema): super(Entity, self).delete(schema) # TODO: how to get relation schemata? #for relation in self.incoming: # relation.delete() def as_dict(self): return { 'id': self.id, 'serial': self.serial, 'type': self.type, 'current': self.current, 'slug': self.slug, 'title': self.title, 'created_at': self.created_at } def as_deep_dict(self): data = self.as_dict() data['incoming'], data['outgoing'] = [], [] for rel in self.incoming: reldata = rel.as_dict() reldata['source'] = rel.source.as_dict() data['incoming'].append(reldata) for rel in self.outgoing: reldata = rel.as_dict() reldata['target'] = rel.target.as_dict() data['outgoing'].append(reldata) return data def as_nx(self, graph): d = util.graph_values(self.as_dict()) return graph.add_node(self.id, **d) def __repr__(self): return "<Entity:%s(%s,%s)>" % (self.type, self.id, self.slug)
class Account(db.Model): __tablename__ = 'account' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.Unicode) fullname = db.Column(db.Unicode) email = db.Column(db.Unicode) _password = db.Column('password', db.Unicode) created_at = db.Column(db.DateTime, default=datetime.utcnow) updated_at = db.Column(db.DateTime, onupdate=datetime.utcnow) def _set_password(self, password): """Hash password on the fly.""" hashed_password = password if isinstance(password, unicode): password_8bit = password.encode('UTF-8') else: password_8bit = password salt = sha1() salt.update(os.urandom(60)) hash = sha1() hash.update(salt.hexdigest() + password_8bit) hashed_password = salt.hexdigest() + hash.hexdigest() if not isinstance(hashed_password, unicode): hashed_password = hashed_password.decode('UTF-8') self._password = hashed_password def _get_password(self): """Return the password hashed""" return self._password password = db.synonym('_password', \ descriptor=property(_get_password, _set_password)) def validate_password(self, password): """ Check the password against existing credentials. :param password: the password that was provided by the user to try and authenticate. This is the clear text version that we will need to match against the hashed one in the database. :type password: unicode object. :return: Whether the password is valid. :rtype: bool """ hashed_pass = sha1() hashed_pass.update(self._password[:40] + password) return self._password[40:] == hashed_pass.hexdigest() @classmethod def create(cls, data): obj = cls() obj.name = data.get('name') obj.update(data) return obj def update(self, data): self.fullname = data.get('fullname') self.email = data.get('email') self.password = data.get('password') db.session.add(self) db.session.flush() @property def display_name(self): return self.fullname or self.name def get_id(self): return unicode(self.name) def is_active(self): return True def is_anonymous(self): return False def is_authenticated(self): return True def as_dict(self): return { 'id': self.id, 'name': self.name, 'fullname': self.fullname, 'created_at': self.created_at, 'updated_at': self.updated_at, } @classmethod def by_name(self, name): q = db.session.query(Account) q = q.filter_by(name=name) return q.first() def __repr__(self): return '<Account(%r)>' % (self.name)
class Account(db.Model, IntBase): __tablename__ = 'grano_account' github_id = db.Column(db.Unicode) twitter_id = db.Column(db.Unicode) facebook_id = db.Column(db.Unicode) full_name = db.Column(db.Unicode) login = db.Column(db.Unicode) email = db.Column(db.Unicode) api_key = db.Column(db.Unicode, default=make_token) projects = db.relationship('Project', backref='author', lazy='dynamic') pipelines = db.relationship('Pipeline', backref='author', lazy='dynamic') files = db.relationship('File', backref='author', lazy='dynamic') properties = db.relationship('Property', backref='author', lazy='dynamic') relations = db.relationship('Relation', backref='author', lazy='dynamic') entities = db.relationship('Entity', backref='author', lazy='dynamic') permissions = db.relationship('Permission', backref='account', lazy='dynamic', cascade='all, delete, delete-orphan') @property def display_name(self): if self.full_name is not None and len(self.full_name.strip()): return self.full_name if self.login is not None and len(self.login.strip()): return self.login return self.email @classmethod def by_api_key(cls, api_key): q = db.session.query(cls).filter_by(api_key=api_key) return q.first() @classmethod def by_login(cls, login): q = db.session.query(cls).filter_by(login=login) return q.first() @classmethod def by_github_id(cls, github_id): q = db.session.query(cls).filter_by(github_id=str(github_id)) return q.first() @classmethod def by_twitter_id(cls, twitter_id): q = db.session.query(cls).filter_by(twitter_id=str(twitter_id)) return q.first() @classmethod def by_facebook_id(cls, facebook_id): q = db.session.query(cls).filter_by(facebook_id=str(facebook_id)) return q.first() def to_dict_index(self): return { 'id': self.id, 'api_url': url_for('accounts_api.view', id=self.id), 'display_name': self.display_name } def to_dict(self): data = self.to_dict_index() data['login'] = self.login data['full_name'] = self.full_name data['github_id'] = self.github_id data['twitter_id'] = self.twitter_id data['facebook_id'] = self.facebook_id data['created_at'] = self.created_at data['updated_at'] = self.updated_at data['email'] = self.email return data