From 6dca2794cd85e3ae77b6c11e3ec81d9fe81175e9 Mon Sep 17 00:00:00 2001 From: Julien Nakache Date: Tue, 11 Feb 2020 16:31:35 -0500 Subject: [PATCH 1/3] Add class property `connection` to `SQLAlchemyObjectType` (#263) Currently to get the default connection of a `SQLAlchemyObjectType`, you have to go through `_meta`. For example, `PetType._meta.connection`. This adds a public way to get it. --- graphene_sqlalchemy/converter.py | 2 +- graphene_sqlalchemy/fields.py | 6 +++--- graphene_sqlalchemy/tests/test_fields.py | 8 ++++---- graphene_sqlalchemy/tests/test_types.py | 10 ++++++++++ graphene_sqlalchemy/types.py | 2 ++ 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/graphene_sqlalchemy/converter.py b/graphene_sqlalchemy/converter.py index 4ff55eed..ae90001b 100644 --- a/graphene_sqlalchemy/converter.py +++ b/graphene_sqlalchemy/converter.py @@ -42,7 +42,7 @@ def dynamic_type(): **field_kwargs ) elif direction in (interfaces.ONETOMANY, interfaces.MANYTOMANY): - if _type._meta.connection: + if _type.connection: # TODO Add a way to override connection_field_factory return connection_field_factory(relationship_prop, registry, **field_kwargs) return Field( diff --git a/graphene_sqlalchemy/fields.py b/graphene_sqlalchemy/fields.py index 840204ae..254319f9 100644 --- a/graphene_sqlalchemy/fields.py +++ b/graphene_sqlalchemy/fields.py @@ -24,10 +24,10 @@ def type(self): assert issubclass(_type, SQLAlchemyObjectType), ( "SQLALchemyConnectionField only accepts SQLAlchemyObjectType types, not {}" ).format(_type.__name__) - assert _type._meta.connection, "The type {} doesn't have a connection".format( + assert _type.connection, "The type {} doesn't have a connection".format( _type.__name__ ) - return _type._meta.connection + return _type.connection @property def model(self): @@ -115,7 +115,7 @@ def get_resolver(self, parent_resolver): def from_relationship(cls, relationship, registry, **field_kwargs): model = relationship.mapper.entity model_type = registry.get_type_for_model(model) - return cls(model_type._meta.connection, resolver=get_batch_resolver(relationship), **field_kwargs) + return cls(model_type.connection, resolver=get_batch_resolver(relationship), **field_kwargs) def default_connection_field_factory(relationship, registry, **field_kwargs): diff --git a/graphene_sqlalchemy/tests/test_fields.py b/graphene_sqlalchemy/tests/test_fields.py index 557ff114..9ed3c4aa 100644 --- a/graphene_sqlalchemy/tests/test_fields.py +++ b/graphene_sqlalchemy/tests/test_fields.py @@ -31,7 +31,7 @@ def resolver(_obj, _info): return Promise.resolve([]) result = UnsortedSQLAlchemyConnectionField.connection_resolver( - resolver, Pet._meta.connection, Pet, None, None + resolver, Pet.connection, Pet, None, None ) assert isinstance(result, Promise) @@ -51,18 +51,18 @@ def test_type_assert_object_has_connection(): def test_sort_added_by_default(): - field = SQLAlchemyConnectionField(Pet._meta.connection) + field = SQLAlchemyConnectionField(Pet.connection) assert "sort" in field.args assert field.args["sort"] == Pet.sort_argument() def test_sort_can_be_removed(): - field = SQLAlchemyConnectionField(Pet._meta.connection, sort=None) + field = SQLAlchemyConnectionField(Pet.connection, sort=None) assert "sort" not in field.args def test_custom_sort(): - field = SQLAlchemyConnectionField(Pet._meta.connection, sort=Editor.sort_argument()) + field = SQLAlchemyConnectionField(Pet.connection, sort=Editor.sort_argument()) assert field.args["sort"] == Editor.sort_argument() diff --git a/graphene_sqlalchemy/tests/test_types.py b/graphene_sqlalchemy/tests/test_types.py index fda8e659..bf563b6e 100644 --- a/graphene_sqlalchemy/tests/test_types.py +++ b/graphene_sqlalchemy/tests/test_types.py @@ -4,6 +4,7 @@ from graphene import (Dynamic, Field, GlobalID, Int, List, Node, NonNull, ObjectType, Schema, String) +from graphene.relay import Connection from ..converter import convert_sqlalchemy_composite from ..fields import (SQLAlchemyConnectionField, @@ -46,6 +47,15 @@ class Meta: assert reporter == reporter_node +def test_connection(): + class ReporterType(SQLAlchemyObjectType): + class Meta: + model = Reporter + interfaces = (Node,) + + assert issubclass(ReporterType.connection, Connection) + + def test_sqlalchemy_default_fields(): @convert_sqlalchemy_composite.register(CompositeFullName) def convert_composite_class(composite, registry): diff --git a/graphene_sqlalchemy/types.py b/graphene_sqlalchemy/types.py index 2ed5110e..ef189b38 100644 --- a/graphene_sqlalchemy/types.py +++ b/graphene_sqlalchemy/types.py @@ -325,6 +325,8 @@ def __init_subclass_with_meta__( _meta.connection = connection _meta.id = id or "id" + cls.connection = connection # Public way to get the connection + super(SQLAlchemyObjectType, cls).__init_subclass_with_meta__( _meta=_meta, interfaces=interfaces, **options ) From 4c5b4d1972d6ae09d228952fdd0ff2d717d851a3 Mon Sep 17 00:00:00 2001 From: Julien Nakache Date: Tue, 11 Feb 2020 20:41:50 -0500 Subject: [PATCH 2/3] [documentation] Fix Connection patterns (#264) --- docs/examples.rst | 14 ++------------ docs/tips.rst | 7 +------ docs/tutorial.rst | 14 ++------------ examples/flask_sqlalchemy/schema.py | 8 ++++---- examples/nameko_sqlalchemy/README.md | 1 - examples/nameko_sqlalchemy/database.py | 2 +- examples/nameko_sqlalchemy/schema.py | 15 ++++++--------- graphene_sqlalchemy/tests/test_query.py | 14 +++----------- graphene_sqlalchemy/tests/test_sort_enums.py | 20 ++++++++------------ 9 files changed, 27 insertions(+), 68 deletions(-) diff --git a/docs/examples.rst b/docs/examples.rst index 283a0f5e..2013cfbb 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -13,22 +13,12 @@ Search all Models with Union 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) @@ -39,8 +29,8 @@ Search all Models with Union search = graphene.List(SearchResult, q=graphene.String()) # List field for search results # Normal Fields - all_books = SQLAlchemyConnectionField(BookConnection) - all_authors = SQLAlchemyConnectionField(AuthorConnection) + all_books = SQLAlchemyConnectionField(Book.connection) + all_authors = SQLAlchemyConnectionField(Author.connection) def resolve_search(self, info, **args): q = args.get("q") # Search query diff --git a/docs/tips.rst b/docs/tips.rst index 1fd39107..baa8233f 100644 --- a/docs/tips.rst +++ b/docs/tips.rst @@ -50,13 +50,8 @@ Given the model model = Pet - class PetConnection(Connection): - class Meta: - node = PetNode - - class Query(ObjectType): - allPets = SQLAlchemyConnectionField(PetConnection) + allPets = SQLAlchemyConnectionField(PetNode.connection) some of the allowed queries are diff --git a/docs/tutorial.rst b/docs/tutorial.rst index bc5ee62d..3c4c135e 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -102,28 +102,18 @@ 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() # Allows sorting over multiple columns, by default over the primary key - all_employees = SQLAlchemyConnectionField(EmployeeConnection) + all_employees = SQLAlchemyConnectionField(Employee.connection) # Disable sorting over this field - all_departments = SQLAlchemyConnectionField(DepartmentConnection, sort=None) + all_departments = SQLAlchemyConnectionField(Department.connection, sort=None) schema = graphene.Schema(query=Query) diff --git a/examples/flask_sqlalchemy/schema.py b/examples/flask_sqlalchemy/schema.py index 9ed09464..ea525e3b 100644 --- a/examples/flask_sqlalchemy/schema.py +++ b/examples/flask_sqlalchemy/schema.py @@ -29,11 +29,11 @@ class Query(graphene.ObjectType): node = relay.Node.Field() # Allow only single column sorting all_employees = SQLAlchemyConnectionField( - Employee, sort=Employee.sort_argument()) + Employee.connection, sort=Employee.sort_argument()) # Allows sorting over multiple columns, by default over the primary key - all_roles = SQLAlchemyConnectionField(Role) + all_roles = SQLAlchemyConnectionField(Role.connection) # Disable sorting over this field - all_departments = SQLAlchemyConnectionField(Department, sort=None) + all_departments = SQLAlchemyConnectionField(Department.connection, sort=None) -schema = graphene.Schema(query=Query, types=[Department, Employee, Role]) +schema = graphene.Schema(query=Query) diff --git a/examples/nameko_sqlalchemy/README.md b/examples/nameko_sqlalchemy/README.md index 6302cb33..e0803895 100644 --- a/examples/nameko_sqlalchemy/README.md +++ b/examples/nameko_sqlalchemy/README.md @@ -46,7 +46,6 @@ Now the following command will setup the database, and start the server: ```bash ./run.sh - ``` Now head on over to postman and send POST request to: diff --git a/examples/nameko_sqlalchemy/database.py b/examples/nameko_sqlalchemy/database.py index 01e76ca6..ca4d4122 100644 --- a/examples/nameko_sqlalchemy/database.py +++ b/examples/nameko_sqlalchemy/database.py @@ -14,7 +14,7 @@ def init_db(): # import all modules here that might define models so that # they will be registered properly on the metadata. Otherwise # you will have to import them first before calling init_db() - from .models import Department, Employee, Role + from models import Department, Employee, Role Base.metadata.drop_all(bind=engine) Base.metadata.create_all(bind=engine) diff --git a/examples/nameko_sqlalchemy/schema.py b/examples/nameko_sqlalchemy/schema.py index a33cab9b..ced300b3 100644 --- a/examples/nameko_sqlalchemy/schema.py +++ b/examples/nameko_sqlalchemy/schema.py @@ -8,31 +8,28 @@ class Department(SQLAlchemyObjectType): - class Meta: model = DepartmentModel - interfaces = (relay.Node, ) + interfaces = (relay.Node,) class Employee(SQLAlchemyObjectType): - class Meta: model = EmployeeModel - interfaces = (relay.Node, ) + interfaces = (relay.Node,) class Role(SQLAlchemyObjectType): - class Meta: model = RoleModel - interfaces = (relay.Node, ) + interfaces = (relay.Node,) class Query(graphene.ObjectType): node = relay.Node.Field() - all_employees = SQLAlchemyConnectionField(Employee) - all_roles = SQLAlchemyConnectionField(Role) + all_employees = SQLAlchemyConnectionField(Employee.connection) + all_roles = SQLAlchemyConnectionField(Role.connection) role = graphene.Field(Role) -schema = graphene.Schema(query=Query, types=[Department, Employee, Role]) +schema = graphene.Schema(query=Query) diff --git a/graphene_sqlalchemy/tests/test_query.py b/graphene_sqlalchemy/tests/test_query.py index 45272e0b..39140814 100644 --- a/graphene_sqlalchemy/tests/test_query.py +++ b/graphene_sqlalchemy/tests/test_query.py @@ -1,5 +1,5 @@ import graphene -from graphene.relay import Connection, Node +from graphene.relay import Node from ..converter import convert_sqlalchemy_composite from ..fields import SQLAlchemyConnectionField @@ -96,14 +96,10 @@ class Meta: model = Article interfaces = (Node,) - class ArticleConnection(Connection): - class Meta: - node = ArticleNode - class Query(graphene.ObjectType): node = Node.Field() reporter = graphene.Field(ReporterNode) - all_articles = SQLAlchemyConnectionField(ArticleConnection) + all_articles = SQLAlchemyConnectionField(ArticleNode.connection) def resolve_reporter(self, _info): return session.query(Reporter).first() @@ -230,13 +226,9 @@ class Meta: model = Editor interfaces = (Node,) - class EditorConnection(Connection): - class Meta: - node = EditorNode - class Query(graphene.ObjectType): node = Node.Field() - all_editors = SQLAlchemyConnectionField(EditorConnection) + all_editors = SQLAlchemyConnectionField(EditorNode.connection) query = """ query { diff --git a/graphene_sqlalchemy/tests/test_sort_enums.py b/graphene_sqlalchemy/tests/test_sort_enums.py index 1eb106da..d6f6965d 100644 --- a/graphene_sqlalchemy/tests/test_sort_enums.py +++ b/graphene_sqlalchemy/tests/test_sort_enums.py @@ -2,7 +2,7 @@ import sqlalchemy as sa from graphene import Argument, Enum, List, ObjectType, Schema -from graphene.relay import Connection, Node +from graphene.relay import Node from ..fields import SQLAlchemyConnectionField from ..types import SQLAlchemyObjectType @@ -249,22 +249,18 @@ class Meta: model = Pet interfaces = (Node,) - class PetConnection(Connection): - class Meta: - node = PetNode - class Query(ObjectType): - defaultSort = SQLAlchemyConnectionField(PetConnection) - nameSort = SQLAlchemyConnectionField(PetConnection) - multipleSort = SQLAlchemyConnectionField(PetConnection) - descSort = SQLAlchemyConnectionField(PetConnection) + defaultSort = SQLAlchemyConnectionField(PetNode.connection) + nameSort = SQLAlchemyConnectionField(PetNode.connection) + multipleSort = SQLAlchemyConnectionField(PetNode.connection) + descSort = SQLAlchemyConnectionField(PetNode.connection) singleColumnSort = SQLAlchemyConnectionField( - PetConnection, sort=Argument(PetNode.sort_enum()) + PetNode.connection, sort=Argument(PetNode.sort_enum()) ) noDefaultSort = SQLAlchemyConnectionField( - PetConnection, sort=PetNode.sort_argument(has_default=False) + PetNode.connection, sort=PetNode.sort_argument(has_default=False) ) - noSort = SQLAlchemyConnectionField(PetConnection, sort=None) + noSort = SQLAlchemyConnectionField(PetNode.connection, sort=None) query = """ query sortTest { From 7a48d3dd6b297c6d13d01df1f268a16e52f3deb0 Mon Sep 17 00:00:00 2001 From: Julien Nakache Date: Tue, 11 Feb 2020 21:10:44 -0500 Subject: [PATCH 3/3] Bump promise to 2.3 (#265) It contains a fix for a thread-safety issue in Dataloader. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f16c8ff5..7b350c39 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ requirements = [ # To keep things simple, we only support newer versions of Graphene "graphene>=2.1.3,<3", - "promise>=2.1", + "promise>=2.3", # Tests fail with 1.0.19 "SQLAlchemy>=1.2,<2", "six>=1.10.0,<2",