graphql-python / graphene-sqlalchemy

Graphene SQLAlchemy integration
http://docs.graphene-python.org/projects/sqlalchemy/en/latest/
MIT License
981 stars 228 forks source link

flask_sqlalchemy example : different types with the same name in the schema: EmployeeConnection, EmployeeConnection. #153

Open tomahim opened 6 years ago

tomahim commented 6 years ago

Hi,

I followed the instruction to use the flask_sqlalchemy example with a Python 2.7 version, when I run the app.py script, I've got the following error :

(py27) λ python app.py                                                                                                                      
Traceback (most recent call last):                                                                                                          
  File "app.py", line 7, in <module>                                                                                                        
    from schema import schema                                                                                                               
  File "D:\Thomas\Dev\graphene-sqlalchemy\examples\flask_sqlalchemy\schema.py", line 60, in <module>                                        
    schema = graphene.Schema(query=Query, types=[Department, Employee, Role])         

....

  File "D:\Programmes\Anaconda2\envs\py27\lib\site-packages\graphene\types\typemap.py", line 99, in graphene_reducer                        
    ).format(_type.graphene_type, type)                                                                                                     
AssertionError: Found different types with the same name in the schema: EmployeeConnection, EmployeeConnection.  

What am I doing wrong ?

Thank you

stan-sack commented 6 years ago

+1

cdestiawan commented 6 years ago

+1

mahrz24 commented 6 years ago

+1

wichert commented 6 years ago

I suspect that #98 will fix this.

young-k commented 6 years ago

+1

cekk commented 6 years ago

+1

ghost commented 6 years ago

+1

Seems like this error only occurs, when using sqlalchemy.orm.relationship in the model class with a backref.

btw. tested with Python 3.6 and 3.7

kowenzhang commented 6 years ago

+1 with python3.6

pangxudong commented 6 years ago

+1 with python3.6

suxin1 commented 6 years ago

+1 with python3.6

hoffrocket commented 6 years ago

any quick fix to get a working example going?

ivanvorona commented 6 years ago

+1

ivanvorona commented 6 years ago

@suxin1 - your fix doesn't work for me, i got this error: TypeError: __init_subclass_with_meta__() got an unexpected keyword argument 'emp loyees' or this(if exactly like in your example): raise ImportError("%s doesn't look like a module path" % dotted_path) ImportError: Employee doesn't look like a module path

suxin1 commented 6 years ago

@ivanvorona So tricky. I actually did not test it. Sorry for my bad assumption. I'll post what i did in the original post.

ivanvorona commented 6 years ago

@suxin1 thank you for your code but i was lazy reading it :) anyway, the fix is as simple as replacing "EmployeeConnection" with "EmployeeConnections", following post helped me identify root cause: https://github.com/graphql-python/graphene-django/issues/185#issuecomment-388469296 hope will help someone else, all the best.

tomahim commented 6 years ago

@ivanvorona @suxin1 Good work, renaming to EmployeeConnections fix the example !

However for my own understanding, why this error occurs for EmployeeConnection and not for RoleConnection and DepartmentConnection ? I just try to identify the real issue, but I don't understand why the EmployeeConnection exists at the first place.

shahinism commented 6 years ago

@ivanvorona Looks like there is nothing special about that plural s. Name EmployeeConnection anything but EmployeeConnection (eg. EmployeeCon, ShahinConnection) will work just fine :thinking:

shahinism commented 6 years ago

@tomahim It should be the relationship field (the only significant difference between the two models :sweat_smile: ). Graphene-SQLAlchemy produces a connection to resolve employees field inside allDepartment query, Looks like that connection will get the same name as EmployeeConnection in final mapping that has been passed the graphene_reducer.

delgadofarid commented 6 years ago

+1

Forever-Young commented 5 years ago

What about changing auto-naming here:

        if use_connection and not connection:
            # We create the connection automatically
            if not connection_class:
                connection_class = Connection

            connection = connection_class.create_type(
                "{}Connection".format(cls.__name__), node=cls
            )

to something having distinct auto-generated suffix/prefix?

RonaldZhao commented 5 years ago

I solved this problem by changing a few names in schema.py, here is my code:

import graphene
from graphene import relay
from graphene_sqlalchemy import SQLAlchemyObjectType, SQLAlchemyConnectionField
from models import Department, Employee

class DepartmentNode(SQLAlchemyObjectType):
    class Meta:
        model = Department
        interfaces = (relay.Node,)

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

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

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

class Query(graphene.ObjectType):
    node = relay.Node.Field()
    all_employees = SQLAlchemyConnectionField(EmployeeConnection)
    all_departments = SQLAlchemyConnectionField(DepartmentConnection, sort=None)

schema = graphene.Schema(query=Query)

But why? I do not know, just lucky.

thejcannon commented 5 years ago

Two questions I have:

  1. How do I reference the generated connection? If it satisfies my needs in all_employees I should just be able to reference the connection.
  2. How can I specify the connection to use for the relationship? If the generated connection doesn't meet my needs, I'd like to be able to specify the connection type to use.

Having the same connection schema or slightly differing ones both with similar names sounds confusing and not ideal.

thejcannon commented 5 years ago

I think I answered my own questions by looking at SQLAlchemyObjectType.__init_subclass_with_meta__.

  1. It looks like you can reference the generated connection by accessing _meta.connection on the class itself. (E.g. Department._meta.connection)
  2. You can specify the connection through the Meta option connection.

So if you wanted to fix the example "correctly", don't define the "Connection" classes, and use the generated ones:

class Query(graphene.ObjectType):
    node = relay.Node.Field()
    all_employees = SQLAlchemyConnectionField(Employee._meta.connection)
    all_departments = SQLAlchemyConnectionField(Department._meta.connection, sort=None)
mhdsyarif commented 5 years ago

I think I answered my own questions by looking at SQLAlchemyObjectType.__init_subclass_with_meta__.

  1. It looks like you can reference the generated connection by accessing _meta.connection on the class itself. (E.g. Department._meta.connection)
  2. You can specify the connection through the Meta option connection.

So if you wanted to fix the example "correctly", don't define the "Connection" classes, and use the generated ones:

class Query(graphene.ObjectType):
    node = relay.Node.Field()
    all_employees = SQLAlchemyConnectionField(Employee._meta.connection)
    all_departments = SQLAlchemyConnectionField(Department._meta.connection, sort=None)

Work for me

zmwangx commented 5 years ago

Expanding on previous comments, here's a working example of a custom connection type (graphene-sqlalchemy==2.2.0):

from graphene import Int, NonNull, ObjectType
from graphene.relay import Connection, Node
from graphene_sqlalchemy import SQLAlchemyObjectType, SQLAlchemyConnectionField

# Implements the totalCount field in a connection.
class CountedConnection(Connection):
    class Meta:
        # Being abstract is important because we can't reference the
        # node type here without creating a circular reference. Also, it
        # makes reuse easy.
        #
        # The node type will be populated later with
        # `CountedConnection.create_class()` in `Foo`'s
        # `__init_subclass_with_meta__()`.
        abstract = True

    total_count = NonNull(Int)

    def resolve_total_count(self, info, **kwargs):
        return self.length

# FooConnection is autogenerated due to the Node interface.
class Foo(SQLAlchemyObjectType):
    class Meta:
        model = FooModel
        interfaces = (Node,)
        connection_class = CountedConnection

# The connection field can be customized too.
class FooConnectionField(SQLAlchemyConnectionField):
    pass

class Query(ObjectType):
    fooList = FooConnectionField(Foo)
mfrlin commented 5 years ago

Ignore fixes with renaming, this comment is on the right track.

I think I answered my own questions by looking at SQLAlchemyObjectType.__init_subclass_with_meta__.

  1. It looks like you can reference the generated connection by accessing _meta.connection on the class itself. (E.g. Department._meta.connection)
  2. You can specify the connection through the Meta option connection.

So if you wanted to fix the example "correctly", don't define the "Connection" classes, and use the generated ones:

class Query(graphene.ObjectType):
    node = relay.Node.Field()
    all_employees = SQLAlchemyConnectionField(Employee._meta.connection)
    all_departments = SQLAlchemyConnectionField(Department._meta.connection, sort=None)

Example has been since fixed with:

class Query(graphene.ObjectType):
    node = relay.Node.Field()
    # Allow only single column sorting
    all_employees = SQLAlchemyConnectionField(
        Employee, sort=Employee.sort_argument())
    # Allows sorting over multiple columns, by default over the primary key
    all_roles = SQLAlchemyConnectionField(Role)
    # Disable sorting over this field
    all_departments = SQLAlchemyConnectionField(Department, sort=None)

So you do not have to access private variable _meta.

This issue should be closed along with pull requests #162.

kaguru commented 5 years ago

Rename Connections Classes :- from DepartmentConnection to DepartmentConnections from EmployeeConnection to EmployeeConnections

KrishyV commented 4 years ago

I was facing this issue trying to reuse an enum and I do not use Relay like most of the people here. After looking through the source code which was referenced in this comment , I found a method to reuse the auto-generated types if you need to reference them.

@classmethod
    def enum_for_field(cls, field_name):
        return enum_for_field(cls, field_name)

I'll share my solution in-case someone down the road needs this.

class UpdatePickingListMutation(graphene.Mutation):
    picking_list = graphene.List(PickingListQuery)

    class Arguments:
        pickingListStatus = graphene.Argument(PickingListQuery.enum_for_field('pickingListStatus'))

This allowed me to reference the enum from my SQLAlchemy models that was converted into a graphene type.

class PickingList(SQLAlchemyObjectType):
    class Meta:
        model = PickingListModel

TLDR: Use enum_for_field(name_of_sqlalchemy_column) to reference the auto-generated Graphene type if you are using Enum.

klintan commented 3 years ago

I was facing this issue trying to reuse an enum and I do not use Relay like most of the people here. After looking through the source code which was referenced in this comment , I found a method to reuse the auto-generated types if you need to reference them.

@classmethod
    def enum_for_field(cls, field_name):
        return enum_for_field(cls, field_name)

I'll share my solution in-case someone down the road needs this.

class UpdatePickingListMutation(graphene.Mutation):
    picking_list = graphene.List(PickingListQuery)

    class Arguments:
        pickingListStatus = graphene.Argument(PickingListQuery.enum_for_field('pickingListStatus'))

This allowed me to reference the enum from my SQLAlchemy models that was converted into a graphene type.

class PickingList(SQLAlchemyObjectType):
    class Meta:
        model = PickingListModel

TLDR: Use enum_for_field(name_of_sqlalchemy_column) to reference the auto-generated Graphene type if you are using Enum.

Oh man this really needs to be documented somewhere. Wasted too much time on this and separately found this exact solution.

Thanks for sharing it!!