From 679a051dc0e5f3c2ea8e789961e60b1443018ba1 Mon Sep 17 00:00:00 2001 From: Gabor Bunkoczi Date: Thu, 4 Sep 2025 14:00:34 +0100 Subject: [PATCH 1/3] Bugfix - serialize/deserialize keys with json module as opposed to simple string conversion --- graphene_sqlalchemy/types.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/graphene_sqlalchemy/types.py b/graphene_sqlalchemy/types.py index 894ebfd..ad595f8 100644 --- a/graphene_sqlalchemy/types.py +++ b/graphene_sqlalchemy/types.py @@ -1,4 +1,5 @@ import inspect +import json import logging import warnings from collections import OrderedDict @@ -557,9 +558,15 @@ def get_query(cls, info): @classmethod def get_node(cls, info, id): + try: + key = json.loads(id) + + except json.decoder.JSONDecodeError: + return None + if not SQL_VERSION_HIGHER_EQUAL_THAN_1_4: try: - return cls.get_query(info).get(id) + return cls.get_query(info).get(key) except NoResultFound: return None @@ -571,14 +578,14 @@ async def get_result() -> Any: return get_result() try: - return cls.get_query(info).get(id) + return cls.get_query(info).get(key) except NoResultFound: return None def resolve_id(self, info): # graphene_type = info.parent_type.graphene_type keys = self.__mapper__.primary_key_from_instance(self) - return str(tuple(keys)) if len(keys) > 1 else keys[0] + return json.dumps(keys if len(keys) > 1 else keys[0]) @classmethod def enum_for_field(cls, field_name): From 0b7d6785c09af293b5e0904b830480f0c4992f74 Mon Sep 17 00:00:00 2001 From: Gabor Bunkoczi Date: Fri, 5 Sep 2025 16:23:20 +0100 Subject: [PATCH 2/3] Composite primary key handling - introduce serializer attribute to SQLAlchemyBase to allow customisation --- graphene_sqlalchemy/types.py | 45 +++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/graphene_sqlalchemy/types.py b/graphene_sqlalchemy/types.py index ad595f8..530278d 100644 --- a/graphene_sqlalchemy/types.py +++ b/graphene_sqlalchemy/types.py @@ -1,5 +1,4 @@ import inspect -import json import logging import warnings from collections import OrderedDict @@ -419,6 +418,28 @@ def construct_fields_and_filters( return fields, filters +class SQLAlchemyPrimaryKeySerializer(object): + """ + Serializes/unserializes primary keys + """ + + DEFAULT = None + + def __init__(self, serialize, deserialize): + self.serialize = serialize + self.deserialize = deserialize + + @classmethod + def default(cls): + if cls.DEFAULT is None: + cls.DEFAULT = cls( + serialize=lambda keys: str(tuple(keys)) if len(keys) > 1 else keys[0], + deserialize=lambda id: id, + ) + + return cls.DEFAULT + + class SQLAlchemyBase(BaseType): """ This class contains initialization code that is common to both ObjectTypes @@ -442,6 +463,7 @@ def __init_subclass_with_meta__( connection_field_factory=None, _meta=None, create_filters=True, + serializer=None, **options, ): # We always want to bypass this hook unless we're defining a concrete @@ -531,6 +553,12 @@ def __init_subclass_with_meta__( cls.connection = connection # Public way to get the connection + if serializer is None: + cls.serializer = SQLAlchemyPrimaryKeySerializer.default() + + else: + cls.serializer = serializer + super(SQLAlchemyBase, cls).__init_subclass_with_meta__( _meta=_meta, interfaces=interfaces, **options ) @@ -558,11 +586,7 @@ def get_query(cls, info): @classmethod def get_node(cls, info, id): - try: - key = json.loads(id) - - except json.decoder.JSONDecodeError: - return None + key = cls.serializer.deserialize(id) if not SQL_VERSION_HIGHER_EQUAL_THAN_1_4: try: @@ -574,7 +598,7 @@ def get_node(cls, info, id): if isinstance(session, AsyncSession): async def get_result() -> Any: - return await session.get(cls._meta.model, id) + return await session.get(cls._meta.model, key) return get_result() try: @@ -585,7 +609,12 @@ async def get_result() -> Any: def resolve_id(self, info): # graphene_type = info.parent_type.graphene_type keys = self.__mapper__.primary_key_from_instance(self) - return json.dumps(keys if len(keys) > 1 else keys[0]) + + try: + return self.serializer.serialize(keys if len(keys) > 1 else keys[0]) + + except Exception as e: + raise ValueError(f"Non-serializable primary key: {e}") from e @classmethod def enum_for_field(cls, field_name): From 94f1f5925ed3678855d155778ae760a18bd57aca Mon Sep 17 00:00:00 2001 From: Gabor Bunkoczi Date: Fri, 5 Sep 2025 17:00:39 +0100 Subject: [PATCH 3/3] Bugfix - use graphene_type to access serializer --- graphene_sqlalchemy/types.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphene_sqlalchemy/types.py b/graphene_sqlalchemy/types.py index 530278d..89f9409 100644 --- a/graphene_sqlalchemy/types.py +++ b/graphene_sqlalchemy/types.py @@ -607,11 +607,11 @@ async def get_result() -> Any: return None def resolve_id(self, info): - # graphene_type = info.parent_type.graphene_type + graphene_type = info.parent_type.graphene_type keys = self.__mapper__.primary_key_from_instance(self) try: - return self.serializer.serialize(keys if len(keys) > 1 else keys[0]) + return graphene_type.serializer.serialize(keys) except Exception as e: raise ValueError(f"Non-serializable primary key: {e}") from e