Merge pull request #146 from karec/master

Add meta attribute identifier for SQLAlchemyNode
This commit is contained in:
Syrus Akbary 2016-04-13 13:16:15 -07:00
commit 4d0d542d35
8 changed files with 105 additions and 10 deletions

View File

@ -12,9 +12,14 @@ default_query = '''
allEmployees { allEmployees {
edges { edges {
node { node {
id id,
name name,
department { department {
id,
name
},
role {
id,
name name
} }
} }

View File

@ -14,7 +14,7 @@ def init_db():
# import all modules here that might define models so that # import all modules here that might define models so that
# they will be registered properly on the metadata. Otherwise # they will be registered properly on the metadata. Otherwise
# you will have to import them first before calling init_db() # you will have to import them first before calling init_db()
from models import Department, Employee from models import Department, Employee, Role
Base.metadata.drop_all(bind=engine) Base.metadata.drop_all(bind=engine)
Base.metadata.create_all(bind=engine) Base.metadata.create_all(bind=engine)
@ -24,10 +24,15 @@ def init_db():
hr = Department(name='Human Resources') hr = Department(name='Human Resources')
db_session.add(hr) db_session.add(hr)
peter = Employee(name='Peter', department=engineering) manager = Role(name='manager')
db_session.add(manager)
engineer = Role(name='engineer')
db_session.add(engineer)
peter = Employee(name='Peter', department=engineering, role=engineer)
db_session.add(peter) db_session.add(peter)
roy = Employee(name='Roy', department=engineering) roy = Employee(name='Roy', department=engineering, role=engineer)
db_session.add(roy) db_session.add(roy)
tracy = Employee(name='Tracy', department=hr) tracy = Employee(name='Tracy', department=hr, role=manager)
db_session.add(tracy) db_session.add(tracy)
db_session.commit() db_session.commit()

View File

@ -10,6 +10,12 @@ class Department(Base):
name = Column(String) name = Column(String)
class Role(Base):
__tablename__ = 'roles'
role_id = Column(Integer, primary_key=True)
name = Column(String)
class Employee(Base): class Employee(Base):
__tablename__ = 'employee' __tablename__ = 'employee'
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
@ -19,9 +25,15 @@ class Employee(Base):
# Employee record was created # Employee record was created
hired_on = Column(DateTime, default=func.now()) hired_on = Column(DateTime, default=func.now())
department_id = Column(Integer, ForeignKey('department.id')) department_id = Column(Integer, ForeignKey('department.id'))
role_id = Column(Integer, ForeignKey('roles.role_id'))
# Use cascade='delete,all' to propagate the deletion of a Department onto its Employees # Use cascade='delete,all' to propagate the deletion of a Department onto its Employees
department = relationship( department = relationship(
Department, Department,
backref=backref('employees', backref=backref('employees',
uselist=True, uselist=True,
cascade='delete,all')) cascade='delete,all'))
role = relationship(
Role,
backref=backref('roles',
uselist=True,
cascade='delete,all'))

View File

@ -4,6 +4,7 @@ from graphene.contrib.sqlalchemy import (SQLAlchemyConnectionField,
SQLAlchemyNode) SQLAlchemyNode)
from models import Department as DepartmentModel from models import Department as DepartmentModel
from models import Employee as EmployeeModel from models import Employee as EmployeeModel
from models import Role as RoleModel
schema = graphene.Schema() schema = graphene.Schema()
@ -22,8 +23,18 @@ class Employee(SQLAlchemyNode):
model = EmployeeModel model = EmployeeModel
@schema.register
class Role(SQLAlchemyNode):
class Meta:
model = RoleModel
identifier = 'role_id'
class Query(graphene.ObjectType): class Query(graphene.ObjectType):
node = relay.NodeField() node = relay.NodeField(Employee)
all_employees = SQLAlchemyConnectionField(Employee) all_employees = SQLAlchemyConnectionField(Employee)
all_roles = SQLAlchemyConnectionField(Role)
role = relay.NodeField(Role)
schema.query = Query schema.query = Query

View File

@ -2,7 +2,7 @@ from ...core.classtypes.objecttype import ObjectTypeOptions
from ...relay.types import Node from ...relay.types import Node
from ...relay.utils import is_node from ...relay.utils import is_node
VALID_ATTRS = ('model', 'only_fields', 'exclude_fields') VALID_ATTRS = ('model', 'only_fields', 'exclude_fields', 'identifier')
class SQLAlchemyOptions(ObjectTypeOptions): class SQLAlchemyOptions(ObjectTypeOptions):
@ -10,6 +10,7 @@ class SQLAlchemyOptions(ObjectTypeOptions):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(SQLAlchemyOptions, self).__init__(*args, **kwargs) super(SQLAlchemyOptions, self).__init__(*args, **kwargs)
self.model = None self.model = None
self.identifier = "id"
self.valid_attrs += VALID_ATTRS self.valid_attrs += VALID_ATTRS
self.only_fields = None self.only_fields = None
self.exclude_fields = [] self.exclude_fields = []

View File

@ -11,6 +11,12 @@ association_table = Table('association', Base.metadata,
Column('reporter_id', Integer, ForeignKey('reporters.id'))) Column('reporter_id', Integer, ForeignKey('reporters.id')))
class Editor(Base):
__tablename__ = 'editors'
editor_id = Column(Integer(), primary_key=True)
name = Column(String(100))
class Pet(Base): class Pet(Base):
__tablename__ = 'pets' __tablename__ = 'pets'
id = Column(Integer(), primary_key=True) id = Column(Integer(), primary_key=True)

View File

@ -7,7 +7,7 @@ from graphene import relay
from graphene.contrib.sqlalchemy import (SQLAlchemyConnectionField, from graphene.contrib.sqlalchemy import (SQLAlchemyConnectionField,
SQLAlchemyNode, SQLAlchemyObjectType) SQLAlchemyNode, SQLAlchemyObjectType)
from .models import Article, Base, Reporter from .models import Article, Base, Reporter, Editor
db = create_engine('sqlite:///test_sqlalchemy.sqlite3') db = create_engine('sqlite:///test_sqlalchemy.sqlite3')
@ -37,6 +37,8 @@ def setup_fixtures(session):
session.add(reporter2) session.add(reporter2)
article = Article(headline='Hi!') article = Article(headline='Hi!')
session.add(article) session.add(article)
editor = Editor(name="John")
session.add(editor)
session.commit() session.commit()
@ -187,3 +189,51 @@ def test_should_node(session):
result = schema.execute(query) result = schema.execute(query)
assert not result.errors assert not result.errors
assert result.data == expected assert result.data == expected
def test_should_custom_identifier(session):
setup_fixtures(session)
class EditorNode(SQLAlchemyNode):
class Meta:
model = Editor
identifier = "editor_id"
class Query(graphene.ObjectType):
node = relay.NodeField(EditorNode)
all_editors = SQLAlchemyConnectionField(EditorNode)
query = '''
query EditorQuery {
allEditors {
edges {
node {
id,
name
}
}
},
node(id: "RWRpdG9yTm9kZTox") {
name
}
}
'''
expected = {
'allEditors': {
'edges': [{
'node': {
'id': 'RWRpdG9yTm9kZTox',
'name': 'John'
}
}]
},
'node': {
'name': 'John'
}
}
schema = graphene.Schema(query=Query, session=session)
result = schema.execute(query)
assert not result.errors
assert result.data == expected

View File

@ -109,12 +109,17 @@ class SQLAlchemyNode(six.with_metaclass(
class Meta: class Meta:
abstract = True abstract = True
def to_global_id(self):
id_ = getattr(self.instance, self._meta.identifier)
return self.global_id(id_)
@classmethod @classmethod
def get_node(cls, id, info=None): def get_node(cls, id, info=None):
try: try:
model = cls._meta.model model = cls._meta.model
identifier = cls._meta.identifier
query = get_query(model, info) query = get_query(model, info)
instance = query.filter(model.id == id).one() instance = query.filter(getattr(model, identifier) == id).one()
return cls(instance) return cls(instance)
except NoResultFound: except NoResultFound:
return None return None