From 2e1b78fe3f2597e2580c064ae34cb723616eb4bf Mon Sep 17 00:00:00 2001 From: "gravy@gravyjoneslocker.uk" Date: Tue, 3 Oct 2023 09:33:43 +0100 Subject: [PATCH 1/8] Incremental Changes --- src/strawberry_sqlalchemy_mapper/mapper.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/strawberry_sqlalchemy_mapper/mapper.py b/src/strawberry_sqlalchemy_mapper/mapper.py index 11b098e..bdfdd56 100644 --- a/src/strawberry_sqlalchemy_mapper/mapper.py +++ b/src/strawberry_sqlalchemy_mapper/mapper.py @@ -160,6 +160,8 @@ def __init__( extra_sqlalchemy_type_to_strawberry_type_map: Optional[ Mapping[Type[TypeEngine], Type[Any]] ] = None, + edge_type: Optional[Type] = None, + connection_type: Optional[Type] = None, ) -> None: if model_to_type_name is None: model_to_type_name = self._default_model_to_type_name @@ -181,6 +183,9 @@ def __init__( self._related_type_models = set() self._related_interface_models = set() + self.edge_type = edge_type + self.connection_type = connection_type + @staticmethod def _default_model_to_type_name(model: Type[BaseModelType]) -> str: return model.__name__ @@ -220,6 +225,8 @@ def _edge_type_for(self, type_name: str) -> Type[Any]: Get or create a corresponding Edge model for the given type (to support future pagination) """ + if self.edge_type is not None: + return self.edge_type edge_name = f"{type_name}Edge" if edge_name not in self.edge_types: self.edge_types[edge_name] = edge_type = strawberry.type( @@ -238,6 +245,8 @@ def _connection_type_for(self, type_name: str) -> Type[Any]: Get or create a corresponding Connection model for the given type (to support future pagination) """ + if self.connection_type is not None: + return self.connection_type[ForwardRef(type_name)] connection_name = f"{type_name}Connection" if connection_name not in self.connection_types: self.connection_types[connection_name] = connection_type = strawberry.type( @@ -269,6 +278,8 @@ def _convert_column_to_strawberry_type( """ if isinstance(column.type, Enum): type_annotation = column.type.python_type + if not hasattr(column.type, "_enum_definition"): + type_annotation = strawberry.enum(type_annotation) elif isinstance(column.type, ARRAY): item_type = self._convert_column_to_strawberry_type( Column(column.type.item_type, nullable=False) From 9154bf5783be0ace8f5dc688c2e78ac6ba243ded Mon Sep 17 00:00:00 2001 From: "gravy@gravyjoneslocker.uk" Date: Thu, 12 Oct 2023 08:34:33 +0100 Subject: [PATCH 2/8] Switched to eval --- .pre-commit-config.yaml | 6 ------ src/strawberry_sqlalchemy_mapper/mapper.py | 5 ++--- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e39d61e..10a0ad6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,12 +5,6 @@ repos: - id: black exclude: ^tests/\w+/snapshots/ - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.289 - hooks: - - id: ruff - exclude: ^tests/\w+/snapshots/ - - repo: https://github.com/patrick91/pre-commit-alex rev: aa5da9e54b92ab7284feddeaf52edf14b1690de3 hooks: diff --git a/src/strawberry_sqlalchemy_mapper/mapper.py b/src/strawberry_sqlalchemy_mapper/mapper.py index bdfdd56..15a2653 100644 --- a/src/strawberry_sqlalchemy_mapper/mapper.py +++ b/src/strawberry_sqlalchemy_mapper/mapper.py @@ -1,4 +1,3 @@ -import ast import asyncio import collections.abc import dataclasses @@ -638,7 +637,7 @@ def convert(type_: Any) -> Any: if key in mapper.columns or key in mapper.relationships: continue if key in model.__annotations__: - annotation = ast.literal_eval(model.__annotations__[key]) + annotation = eval(model.__annotations__[key]) for ( # type: ignore[assignment] sqlalchemy_type, strawberry_type, @@ -679,7 +678,7 @@ def convert(type_: Any) -> Any: if "typing" in annotation: # Try to evaluate from existing typing imports annotation = annotation[7:] - annotation = ast.literal_eval(annotation) + annotation = eval(annotation) except NameError: raise UnsupportedDescriptorType(key) self._add_annotation( From c58dd402972e4648866a7f5ca3263de9b028c9ac Mon Sep 17 00:00:00 2001 From: "gravy@gravyjoneslocker.uk" Date: Thu, 12 Oct 2023 11:22:05 +0100 Subject: [PATCH 3/8] Updated Name Handling --- src/strawberry_sqlalchemy_mapper/mapper.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/strawberry_sqlalchemy_mapper/mapper.py b/src/strawberry_sqlalchemy_mapper/mapper.py index 15a2653..bf68576 100644 --- a/src/strawberry_sqlalchemy_mapper/mapper.py +++ b/src/strawberry_sqlalchemy_mapper/mapper.py @@ -555,6 +555,7 @@ def type( model: Type[BaseModelType], make_interface=False, use_federation=False, + **kwargs, ) -> Callable[[Type[object]], Any]: """ Decorate a type with this to register it as a strawberry type @@ -703,15 +704,19 @@ def convert(type_: Any) -> Any: # (because they may not have default values) type_.__annotations__.update(old_annotations) + type_name = type_.__name__ + if "name" in kwargs: + type_name = kwargs["name"] + if make_interface: - mapped_type = strawberry.interface(type_) - self.mapped_interfaces[type_.__name__] = mapped_type + mapped_type = strawberry.interface(type_, **kwargs) + self.mapped_interfaces[type_name] = mapped_type elif use_federation: - mapped_type = strawberry.federation.type(type_) - self.mapped_types[type_.__name__] = mapped_type + mapped_type = strawberry.federation.type(type_, **kwargs) + self.mapped_types[type_name] = mapped_type else: - mapped_type = strawberry.type(type_) - self.mapped_types[type_.__name__] = mapped_type + mapped_type = strawberry.type(type_, **kwargs) + self.mapped_types[type_name] = mapped_type setattr(mapped_type, _GENERATED_FIELD_KEYS_KEY, generated_field_keys) setattr(mapped_type, _ORIGINAL_TYPE_KEY, type_) return mapped_type From c76a081d2cd42db1190463a88d3f396740bb5536 Mon Sep 17 00:00:00 2001 From: "gravy@gravyjoneslocker.uk" Date: Thu, 12 Oct 2023 12:15:20 +0100 Subject: [PATCH 4/8] Fixed Association Handling --- src/strawberry_sqlalchemy_mapper/mapper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strawberry_sqlalchemy_mapper/mapper.py b/src/strawberry_sqlalchemy_mapper/mapper.py index bf68576..fb23f97 100644 --- a/src/strawberry_sqlalchemy_mapper/mapper.py +++ b/src/strawberry_sqlalchemy_mapper/mapper.py @@ -643,7 +643,7 @@ def convert(type_: Any) -> Any: sqlalchemy_type, strawberry_type, ) in self.sqlalchemy_type_to_strawberry_type_map.items(): - if isinstance(annotation, sqlalchemy_type): + if annotation == sqlalchemy_type: self._add_annotation( type_, key, strawberry_type, generated_field_keys ) From 34703a77979c57f42b5345bc70905811bce9659f Mon Sep 17 00:00:00 2001 From: Ed Sandoe Date: Mon, 26 Feb 2024 14:53:54 +0000 Subject: [PATCH 5/8] Implemented Unique on Loader Results --- src/strawberry_sqlalchemy_mapper/loader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/strawberry_sqlalchemy_mapper/loader.py b/src/strawberry_sqlalchemy_mapper/loader.py index 40047e0..f45100e 100644 --- a/src/strawberry_sqlalchemy_mapper/loader.py +++ b/src/strawberry_sqlalchemy_mapper/loader.py @@ -48,10 +48,10 @@ def __init__( async def _scalars_all(self, *args, **kwargs): if self._async_bind_factory: async with self._async_bind_factory() as bind: - return (await bind.scalars(*args, **kwargs)).all() + return (await bind.scalars(*args, **kwargs)).unique().all() else: assert self._bind is not None - return self._bind.scalars(*args, **kwargs).all() + return self._bind.scalars(*args, **kwargs).unique().all() def loader_for(self, relationship: RelationshipProperty) -> DataLoader: """ From 77cda8ca62f0e6e4c35a1cd06dbaba50c300d2c4 Mon Sep 17 00:00:00 2001 From: Ed Sandoe Date: Thu, 29 Feb 2024 13:01:11 +0000 Subject: [PATCH 6/8] Revert Connection Functionality --- src/strawberry_sqlalchemy_mapper/mapper.py | 25 ++-------------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/src/strawberry_sqlalchemy_mapper/mapper.py b/src/strawberry_sqlalchemy_mapper/mapper.py index fb23f97..d342b04 100644 --- a/src/strawberry_sqlalchemy_mapper/mapper.py +++ b/src/strawberry_sqlalchemy_mapper/mapper.py @@ -244,21 +244,7 @@ def _connection_type_for(self, type_name: str) -> Type[Any]: Get or create a corresponding Connection model for the given type (to support future pagination) """ - if self.connection_type is not None: - return self.connection_type[ForwardRef(type_name)] - connection_name = f"{type_name}Connection" - if connection_name not in self.connection_types: - self.connection_types[connection_name] = connection_type = strawberry.type( - dataclasses.make_dataclass( - connection_name, - [ - ("edges", List[self._edge_type_for(type_name)]), # type: ignore - ], - ) - ) - setattr(connection_type, _GENERATED_FIELD_KEYS_KEY, ["edges"]) - setattr(connection_type, _IS_GENERATED_CONNECTION_TYPE_KEY, True) - return self.connection_types[connection_name] + return List[ForwardRef(type_name)] def _get_polymorphic_base_model( self, model: Type[BaseModelType] @@ -449,14 +435,7 @@ def connection_resolver_for( Return an async field resolver for the given relationship that returns a Connection instead of an array of objects. """ - relationship_resolver = self.relationship_resolver_for(relationship) - if relationship.uselist: - return self.make_connection_wrapper_resolver( - relationship_resolver, - self.model_to_type_or_interface_name(relationship.entity.entity), # type: ignore[arg-type] - ) - else: - return relationship_resolver + return self.relationship_resolver_for(relationship) def _is_connection_type(self, type_: Union[Type[Any], ForwardRef]) -> bool: """ From 75e90508cc44ef44a6a5bdb580abced7ebd70b28 Mon Sep 17 00:00:00 2001 From: Ed Sandoe Date: Mon, 25 Mar 2024 12:28:56 +0000 Subject: [PATCH 7/8] Revert "Revert Connection Functionality" This reverts commit 77cda8ca62f0e6e4c35a1cd06dbaba50c300d2c4. --- src/strawberry_sqlalchemy_mapper/mapper.py | 25 ++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/strawberry_sqlalchemy_mapper/mapper.py b/src/strawberry_sqlalchemy_mapper/mapper.py index d342b04..fb23f97 100644 --- a/src/strawberry_sqlalchemy_mapper/mapper.py +++ b/src/strawberry_sqlalchemy_mapper/mapper.py @@ -244,7 +244,21 @@ def _connection_type_for(self, type_name: str) -> Type[Any]: Get or create a corresponding Connection model for the given type (to support future pagination) """ - return List[ForwardRef(type_name)] + if self.connection_type is not None: + return self.connection_type[ForwardRef(type_name)] + connection_name = f"{type_name}Connection" + if connection_name not in self.connection_types: + self.connection_types[connection_name] = connection_type = strawberry.type( + dataclasses.make_dataclass( + connection_name, + [ + ("edges", List[self._edge_type_for(type_name)]), # type: ignore + ], + ) + ) + setattr(connection_type, _GENERATED_FIELD_KEYS_KEY, ["edges"]) + setattr(connection_type, _IS_GENERATED_CONNECTION_TYPE_KEY, True) + return self.connection_types[connection_name] def _get_polymorphic_base_model( self, model: Type[BaseModelType] @@ -435,7 +449,14 @@ def connection_resolver_for( Return an async field resolver for the given relationship that returns a Connection instead of an array of objects. """ - return self.relationship_resolver_for(relationship) + relationship_resolver = self.relationship_resolver_for(relationship) + if relationship.uselist: + return self.make_connection_wrapper_resolver( + relationship_resolver, + self.model_to_type_or_interface_name(relationship.entity.entity), # type: ignore[arg-type] + ) + else: + return relationship_resolver def _is_connection_type(self, type_: Union[Type[Any], ForwardRef]) -> bool: """ From 7a96f6394b7afd6d2038ec32994d2f0ebd6527dc Mon Sep 17 00:00:00 2001 From: Ed Sandoe Date: Mon, 25 Mar 2024 12:30:06 +0000 Subject: [PATCH 8/8] Made Relay Optional --- src/strawberry_sqlalchemy_mapper/mapper.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/strawberry_sqlalchemy_mapper/mapper.py b/src/strawberry_sqlalchemy_mapper/mapper.py index fb23f97..4ac8f95 100644 --- a/src/strawberry_sqlalchemy_mapper/mapper.py +++ b/src/strawberry_sqlalchemy_mapper/mapper.py @@ -161,7 +161,9 @@ def __init__( ] = None, edge_type: Optional[Type] = None, connection_type: Optional[Type] = None, + use_relay: bool=False, ) -> None: + self.use_relay = use_relay if model_to_type_name is None: model_to_type_name = self._default_model_to_type_name self.model_to_type_name = model_to_type_name @@ -244,6 +246,8 @@ def _connection_type_for(self, type_name: str) -> Type[Any]: Get or create a corresponding Connection model for the given type (to support future pagination) """ + if not self.use_relay: + return List[ForwardRef(type_name)] if self.connection_type is not None: return self.connection_type[ForwardRef(type_name)] connection_name = f"{type_name}Connection" @@ -449,6 +453,8 @@ def connection_resolver_for( Return an async field resolver for the given relationship that returns a Connection instead of an array of objects. """ + if not self.use_relay: + return self.relationship_resolver_for(relationship) relationship_resolver = self.relationship_resolver_for(relationship) if relationship.uselist: return self.make_connection_wrapper_resolver(