Skip to content

Added sort support #101

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jun 22, 2018
24 changes: 17 additions & 7 deletions docs/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,32 @@ Schema Examples


Search all Models with Union
-----------------
----------------------------

.. code:: python

class Book(SQLAlchemyObjectType):
class Meta:
model = BookModel
interfaces = (relay.Node,)




class BookConnection(relay.Connection):
class Meta:
node = Book


class Author(SQLAlchemyObjectType):
class Meta:
model = AuthorModel
interfaces = (relay.Node,)


class AuthorConnection(relay.Connection):
class Meta:
node = Author


class SearchResult(graphene.Union):
class Meta:
types = (Book, Author)
Expand All @@ -29,8 +39,8 @@ Search all Models with Union
search = graphene.List(SearchResult, q=graphene.String()) # List field for search results

# Normal Fields
all_books = SQLAlchemyConnectionField(Book)
all_authors = SQLAlchemyConnectionField(Author)
all_books = SQLAlchemyConnectionField(BookConnection)
all_authors = SQLAlchemyConnectionField(AuthorConnection)

def resolve_search(self, info, **args):
q = args.get("q") # Search query
Expand All @@ -47,13 +57,13 @@ Search all Models with Union
# Query Authors
authors = author_query.filter(AuthorModel.name.contains(q)).all()

return authors + books # Combine lists
return authors + books # Combine lists

schema = graphene.Schema(query=Query, types=[Book, Author, SearchResult])

Example GraphQL query

.. code:: GraphQL
.. code::

book(id: "Qm9vazow") {
id
Expand Down
61 changes: 58 additions & 3 deletions docs/tips.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
Tips
====

Tips
====

Querying
--------

Expand All @@ -30,3 +27,61 @@ For make querying to the database work, there are two alternatives:
If you don't specify any, the following error will be displayed:

``A query in the model Base or a session in the schema is required for querying.``

Sorting
-------

By default the SQLAlchemyConnectionField sorts the result elements over the primary key(s).
The query has a `sort` argument which allows to sort over a different column(s)

Given the model

.. code:: python

class Pet(Base):
__tablename__ = 'pets'
id = Column(Integer(), primary_key=True)
name = Column(String(30))
pet_kind = Column(Enum('cat', 'dog', name='pet_kind'), nullable=False)


class PetNode(SQLAlchemyObjectType):
class Meta:
model = Pet


class PetConnection(Connection):
class Meta:
node = PetNone


class Query(ObjectType):
allPets = SQLAlchemyConnectionField(PetConnection)

some of the allowed queries are

- Sort in ascending order over the `name` column

.. code::

allPets(sort: name_asc){
edges {
node {
name
}
}
}

- Sort in descending order over the `per_kind` column and in ascending order over the `name` column

.. code::

allPets(sort: [pet_kind_desc, name_asc]) {
edges {
node {
name
petKind
}
}
}

15 changes: 14 additions & 1 deletion docs/tutorial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -102,15 +102,28 @@ Create ``flask_sqlalchemy/schema.py`` and type the following:
interfaces = (relay.Node, )


class DepartmentConnection(relay.Connection):
class Meta:
node = Department


class Employee(SQLAlchemyObjectType):
class Meta:
model = EmployeeModel
interfaces = (relay.Node, )


class EmployeeConnection(relay.Connection):
class Meta:
node = Employee


class Query(graphene.ObjectType):
node = relay.Node.Field()
all_employees = SQLAlchemyConnectionField(Employee)
# Allows sorting over multiple columns, by default over the primary key
all_employees = SQLAlchemyConnectionField(EmployeeConnection)
# Disable sorting over this field
all_departments = SQLAlchemyConnectionField(DepartmentConnection, sort=None)

schema = graphene.Schema(query=Query)

Expand Down
37 changes: 30 additions & 7 deletions examples/flask_sqlalchemy/schema.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,60 @@
import graphene
from graphene import relay
from graphene_sqlalchemy import SQLAlchemyConnectionField, SQLAlchemyObjectType
from graphene_sqlalchemy import SQLAlchemyConnectionField, SQLAlchemyObjectType, utils
from models import Department as DepartmentModel
from models import Employee as EmployeeModel
from models import Role as RoleModel


class Department(SQLAlchemyObjectType):

class Meta:
model = DepartmentModel
interfaces = (relay.Node, )


class Employee(SQLAlchemyObjectType):
class DepartmentConnection(relay.Connection):
class Meta:
node = Department


class Employee(SQLAlchemyObjectType):
class Meta:
model = EmployeeModel
interfaces = (relay.Node, )


class Role(SQLAlchemyObjectType):
class EmployeeConnection(relay.Connection):
class Meta:
node = Employee


class Role(SQLAlchemyObjectType):
class Meta:
model = RoleModel
interfaces = (relay.Node, )


class RoleConnection(relay.Connection):
class Meta:
node = Role


SortEnumEmployee = utils.sort_enum_for_model(EmployeeModel, 'SortEnumEmployee',
lambda c, d: c.upper() + ('_ASC' if d else '_DESC'))


class Query(graphene.ObjectType):
node = relay.Node.Field()
all_employees = SQLAlchemyConnectionField(Employee)
all_roles = SQLAlchemyConnectionField(Role)
role = graphene.Field(Role)
# Allow only single column sorting
all_employees = SQLAlchemyConnectionField(
EmployeeConnection,
sort=graphene.Argument(
SortEnumEmployee,
default_value=utils.EnumValue('id_asc', EmployeeModel.id.asc())))
# Allows sorting over multiple columns, by default over the primary key
all_roles = SQLAlchemyConnectionField(RoleConnection)
# Disable sorting over this field
all_departments = SQLAlchemyConnectionField(DepartmentConnection, sort=None)


schema = graphene.Schema(query=Query, types=[Department, Employee, Role])
38 changes: 31 additions & 7 deletions graphene_sqlalchemy/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,28 @@
from promise import is_thenable, Promise
from sqlalchemy.orm.query import Query

from graphene.relay import ConnectionField
from graphene.relay import Connection, ConnectionField
from graphene.relay.connection import PageInfo
from graphql_relay.connection.arrayconnection import connection_from_list_slice

from .utils import get_query
from .utils import get_query, sort_argument_for_model


class SQLAlchemyConnectionField(ConnectionField):
class UnsortedSQLAlchemyConnectionField(ConnectionField):

@property
def model(self):
return self.type._meta.node._meta.model

@classmethod
def get_query(cls, model, info, **args):
return get_query(model, info.context)
def get_query(cls, model, info, sort=None, **args):
query = get_query(model, info.context)
if sort is not None:
if isinstance(sort, str):
query = query.order_by(sort.value)
else:
query = query.order_by(*(col.value for col in sort))
return query

@classmethod
def resolve_connection(cls, connection_type, model, info, args, resolved):
Expand Down Expand Up @@ -55,7 +61,25 @@ def get_resolver(self, parent_resolver):
return partial(self.connection_resolver, parent_resolver, self.type, self.model)


__connectionFactory = SQLAlchemyConnectionField
class SQLAlchemyConnectionField(UnsortedSQLAlchemyConnectionField):

def __init__(self, type, *args, **kwargs):
if 'sort' not in kwargs and issubclass(type, Connection):
# Let super class raise if type is not a Connection
try:
model = type.Edge.node._type._meta.model
kwargs.setdefault('sort', sort_argument_for_model(model))
except Exception:
raise Exception(
'Cannot create sort argument for {}. A model is required. Set the "sort" argument'
' to None to disabling the creation of the sort query argument'.format(type.__name__)
)
elif 'sort' in kwargs and kwargs['sort'] is None:
del kwargs['sort']
super(SQLAlchemyConnectionField, self).__init__(type, *args, **kwargs)


__connectionFactory = UnsortedSQLAlchemyConnectionField


def createConnectionField(_type):
Expand All @@ -69,4 +93,4 @@ def registerConnectionFieldFactory(factoryMethod):

def unregisterConnectionFieldFactory():
global __connectionFactory
__connectionFactory = SQLAlchemyConnectionField
__connectionFactory = UnsortedSQLAlchemyConnectionField
4 changes: 2 additions & 2 deletions graphene_sqlalchemy/tests/test_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from ..converter import (convert_sqlalchemy_column,
convert_sqlalchemy_composite,
convert_sqlalchemy_relationship)
from ..fields import SQLAlchemyConnectionField
from ..fields import UnsortedSQLAlchemyConnectionField
from ..registry import Registry
from ..types import SQLAlchemyObjectType
from .models import Article, Pet, Reporter
Expand Down Expand Up @@ -205,7 +205,7 @@ class Meta:

dynamic_field = convert_sqlalchemy_relationship(Reporter.pets.property, A._meta.registry)
assert isinstance(dynamic_field, graphene.Dynamic)
assert isinstance(dynamic_field.get_type(), SQLAlchemyConnectionField)
assert isinstance(dynamic_field.get_type(), UnsortedSQLAlchemyConnectionField)


def test_should_manytoone_convert_connectionorlist():
Expand Down
38 changes: 38 additions & 0 deletions graphene_sqlalchemy/tests/test_fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from graphene.relay import Connection
import pytest

from ..fields import SQLAlchemyConnectionField
from ..types import SQLAlchemyObjectType
from ..utils import sort_argument_for_model
from .models import Pet as PetModel, Editor


class Pet(SQLAlchemyObjectType):
class Meta:
model = PetModel


class PetConn(Connection):
class Meta:
node = Pet


def test_sort_added_by_default():
arg = SQLAlchemyConnectionField(PetConn)
assert 'sort' in arg.args
assert arg.args['sort'] == sort_argument_for_model(PetModel)


def test_sort_can_be_removed():
arg = SQLAlchemyConnectionField(PetConn, sort=None)
assert 'sort' not in arg.args


def test_custom_sort():
arg = SQLAlchemyConnectionField(PetConn, sort=sort_argument_for_model(Editor))
assert arg.args['sort'] == sort_argument_for_model(Editor)


def test_init_raises():
with pytest.raises(Exception, match='Cannot create sort'):
SQLAlchemyConnectionField(Connection)
Loading