graphene-sqlalchemy icon indicating copy to clipboard operation
graphene-sqlalchemy copied to clipboard

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

Open tomahim opened this issue 5 years ago • 29 comments

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

tomahim avatar Aug 01 '18 20:08 tomahim

+1

stan-sack avatar Aug 03 '18 23:08 stan-sack

+1

cdestiawan avatar Aug 04 '18 13:08 cdestiawan

+1

mahrz24 avatar Aug 06 '18 13:08 mahrz24

I suspect that #98 will fix this.

wichert avatar Aug 07 '18 13:08 wichert

+1

young-k avatar Aug 08 '18 05:08 young-k

+1

cekk avatar Aug 11 '18 19:08 cekk

+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

ghost avatar Aug 14 '18 21:08 ghost

+1 with python3.6

kowenzhang avatar Aug 15 '18 02:08 kowenzhang

+1 with python3.6

pangxudong avatar Aug 17 '18 14:08 pangxudong

+1 with python3.6

suxin1 avatar Aug 17 '18 17:08 suxin1

any quick fix to get a working example going?

hoffrocket avatar Aug 17 '18 20:08 hoffrocket

+1

ivanvorona avatar Aug 19 '18 15:08 ivanvorona

@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

ivanvorona avatar Aug 20 '18 11:08 ivanvorona

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

suxin1 avatar Aug 20 '18 13:08 suxin1

@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.

ivanvorona avatar Aug 20 '18 19:08 ivanvorona

@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.

tomahim avatar Aug 24 '18 12:08 tomahim

@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 avatar Aug 30 '18 12:08 shahinism

@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.

shahinism avatar Aug 30 '18 12:08 shahinism

+1

delgadofarid avatar Oct 02 '18 02:10 delgadofarid

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?

Forever-Young avatar Nov 21 '18 07:11 Forever-Young

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.

RonaldZhao avatar Dec 23 '18 05:12 RonaldZhao

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 avatar Apr 01 '19 20:04 thejcannon

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)

thejcannon avatar Apr 01 '19 20:04 thejcannon

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

mhdsyarif avatar Apr 09 '19 12:04 mhdsyarif

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)

zmwangx avatar Jun 14 '19 03:06 zmwangx

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.

mfrlin avatar Sep 05 '19 13:09 mfrlin

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

kaguru avatar Nov 07 '19 22:11 kaguru

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.

KrishyV avatar Oct 20 '20 16:10 KrishyV

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!!

klintan avatar Mar 06 '21 22:03 klintan