graphql-python / graphene-sqlalchemy

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

Sorting not working #337

Closed kastolars closed 2 years ago

kastolars commented 2 years ago
def int_timestamp():
    return int(time.time())

class UserActivity(TimestampedModel, Base, DictModel):
    __tablename__ = 'user_activities'
    id = Column(Integer, primary_key=True)
    user_id = Column(Integer, ForeignKey("user.id", ondelete='SET NULL'))
    username = Column(String())
    timestamp = Column(Integer, default=int_timestamp)

class UserActivityModel(SQLAlchemyObjectType):
    class Meta:
        model = UserActivity
        only_fields = ()
        exclude_fields = ('user_id',)
        interfaces = (relay.Node,)

class Query(ObjectType):
    list_user_activities = SQLAlchemyConnectionField(
        type=UserActivityModel,
        sort=UserActivityModel.sort_argument()
    )

    def resolve_list_user_activities(self, info: ResolveInfo, sort=None, first=None, after=None, **kwargs):
        # Build the query
        query = UserActivityModel.get_query(info)
        query = query.filter_by(**kwargs)
        return query.all()

graphql_app = GraphQLApp(schema=Schema(query=Query))

My query in GQL:

query {
    listUserActivities(first: 3, sort: TIMESTAMP_DESC) {
        edges {
            node {
                username
                timestamp
            }
        }
    }
}

The result:

{
    "data": {
        "listUserActivities": {
            "edges": [
                {
                    "node": {
                        "username": "adis@ulap.co",
                        "timestamp": 1644321703
                    }
                },
                {
                    "node": {
                        "username": "adis@ulap.co",
                        "timestamp": 1644334763
                    }
                },
                {
                    "node": {
                        "username": "adis@ulap.co",
                        "timestamp": 1644344156
                    }
                }
            ]
        }
    }
}

What's really strange is the first argument appears to apply a limit to the result, but the sort argument appears to just be swallowed and unused. Switching to TIMESTAMP_ASC produces the same result. I'm trying to find examples online to help with this. What am I doing wrong or what can i try here?

I'm using 2.3.0

erikwrede commented 2 years ago

Hey there, it seems like you're not passing enough information to the UserActivityModel.get_query(info) call. Check the original method: https://github.com/graphql-python/graphene-sqlalchemy/blob/7bf0aa5cabae99e5f8be46ac2a2f0faf30093c97/graphene_sqlalchemy/fields.py#L114

sort is an extra parameter which is None by default. You're only passing info. Consequently, sorting won't be included in your query. The sort parameter should be included in your resolve method's **kwargs, try passing it explicitly to the get_query method!

Let me know if that works.

kastolars commented 2 years ago

Hey there, it seems like you're not passing enough information to the UserActivityModel.get_query(info) call. Check the original method:

https://github.com/graphql-python/graphene-sqlalchemy/blob/7bf0aa5cabae99e5f8be46ac2a2f0faf30093c97/graphene_sqlalchemy/fields.py#L114

sort is an extra parameter which is None by default. You're only passing info. Consequently, sorting won't be included in your query. The sort parameter should be included in your resolve method's **kwargs, try passing it explicitly to the get_query method!

Let me know if that works.

Hi! Thank you for the prompt reply, I really appreciate it.

I've changed the line in the resolver to be UserActivityModel.get_query(info, sort) but when I execute the query I get the following:

{
    "data": {
        "listUserActivities": null
    },
    "errors": [
        {
            "message": "get_query() takes 2 positional arguments but 3 were given",
            "locations": [
                {
                    "line": 2,
                    "column": 2
                }
            ],
            "path": [
                "listUserActivities"
            ]
        }
    ]
}

Perhaps I am not implementing the Model correctly?

kastolars commented 2 years ago

I've read in the updated 2.0 docs that some changes to node connections have been made so I made the following changes:

class UserActivityModel(SQLAlchemyObjectType):

    class Meta:
        model = UserActivity
        only_fields = ()
        exclude_fields = ('user_id',)
        interfaces = (Node,)

class UserActivityConnection(Connection):
    class Meta:
        node = UserActivityModel

class Query(ObjectType):
    list_user_activities = SQLAlchemyConnectionField(UserActivityConnection)

When I'm using Insomnia, the graphql body does detect the sort field and autocompletes valid enums like TIMESTAMP_DESC, it's just that it's being swallowed. So idk how to make sure that the resolver uses the connectionfield get_query

erikwrede commented 2 years ago

Try using the get_query property of the SQLAlchemyConnectionField instead of the ObjectType.

kastolars commented 2 years ago

Okay now I changed the line in the resolver to:

query = SQLAlchemyConnectionField.get_query(info, sort)

But I am getting the following:

{
    "data": {
        "listUserActivities": null
    },
    "errors": [
        {
            "message": "'list' object has no attribute 'context'",
            "locations": [
                {
                    "line": 2,
                    "column": 2
                }
            ],
            "path": [
                "listUserActivities"
            ]
        }
    ]
}
erikwrede commented 2 years ago

Yes, you are getting this error, because you didn't add some additional info that the field's get_query needs. This should do the job (tested it to be safe):

    def resolve_list_user_activities(parent, info, sort=None, first=None, after=None,**kwargs):
        query = SQLAlchemyConnectionField.get_query(UserActivityModel._meta.model, info, sort, **kwargs)
        return query.all()

Your query.filter_by(**kwargs) didn't work for me, but I don't know your intended use for this operation. If you're trying to apply filtering that way, it probably will not work since all possible input fields would have to be specified in the resolver method header.

kastolars commented 2 years ago

Can you show me what ExperimentType looks like in this case?

erikwrede commented 2 years ago

Just a typo, tested it on a different schema - I fixed the example.

kastolars commented 2 years ago

That worked! Thank you so much.

erikwrede commented 2 years ago

Great to hear, I'm closing this issue now. If further problems occur, feel free to reply. Since I saw the attempt to apply filtering to your queries, I suggest looking into this library, before you put in too much duplicate work into providing basic custom filters: https://github.com/art1415926535/graphene-sqlalchemy-filter

It's currently inactive but provides a lot of filtering functionality for graphene-sqlalchemy 2.0. For graphene-sqlalchemy 3, we are currently working on a custom implementation!

github-actions[bot] commented 1 year ago

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related topics referencing this issue.