From d8c60defba9ae1d550405d7acf9b0aa494a3e0d1 Mon Sep 17 00:00:00 2001 From: edraft Date: Sat, 27 Sep 2025 21:57:33 +0200 Subject: [PATCH] Further gql improvements & added test data #181 --- example/api/src/main.py | 53 ++++++++++++------- example/api/src/model/__init__.py | 0 example/api/src/model/post.py | 30 +++++++++++ example/api/src/model/post_dao.py | 11 ++++ example/api/src/model/post_query.py | 38 +++++++++++++ example/api/src/queries/hello.py | 39 ++++++++++++++ example/api/src/scripts/0-posts.sql | 10 ++++ example/api/src/test_data_seeder.py | 31 +++++++++++ example/database/src/model/city.py | 8 +-- example/database/src/model/user.py | 8 +-- .../auth/schema/_administration/api_key.py | 8 +-- .../auth/schema/_administration/auth_user.py | 11 ++-- .../schema/_administration/auth_user_dao.py | 2 +- .../schema/_permission/api_key_permission.py | 6 +-- .../cpl/auth/schema/_permission/permission.py | 10 ++-- .../cpl/auth/schema/_permission/role.py | 10 ++-- .../schema/_permission/role_permission.py | 10 ++-- .../cpl/auth/schema/_permission/role_user.py | 6 +-- .../cpl/core/utils/credential_manager.py | 9 ++-- .../database/abc/data_access_object_abc.py | 12 +++-- .../cpl/database/abc/db_join_model_abc.py | 6 +-- .../cpl/database/abc/db_model_abc.py | 6 +-- .../cpl/database/model/database_settings.py | 2 +- .../cpl/database/schema/executed_migration.py | 8 +-- .../cpl/graphql/schema/collection.py | 2 +- src/cpl-graphql/cpl/graphql/schema/query.py | 53 +++++++++++++++++-- .../cpl/graphql/schema/sort/sort_order.py | 4 +- 27 files changed, 305 insertions(+), 88 deletions(-) create mode 100644 example/api/src/model/__init__.py create mode 100644 example/api/src/model/post.py create mode 100644 example/api/src/model/post_dao.py create mode 100644 example/api/src/model/post_query.py create mode 100644 example/api/src/scripts/0-posts.sql create mode 100644 example/api/src/test_data_seeder.py diff --git a/example/api/src/main.py b/example/api/src/main.py index bfb953fb..777f62fe 100644 --- a/example/api/src/main.py +++ b/example/api/src/main.py @@ -1,7 +1,7 @@ from starlette.responses import JSONResponse from api.src.queries.cities import CityGraphType, CityFilter, CitySort -from api.src.queries.hello import UserGraphType +from api.src.queries.hello import UserGraphType, AuthUserFilter, AuthUserSort, AuthUserGraphType from api.src.queries.user import UserFilter, UserSort from cpl.api.api_module import ApiModule from cpl.application.application_builder import ApplicationBuilder @@ -14,9 +14,12 @@ from cpl.core.utils.cache import Cache from cpl.database.mysql.mysql_module import MySQLModule from cpl.graphql.application.graphql_app import GraphQLApp from cpl.graphql.graphql_module import GraphQLModule +from model.post_dao import PostDao +from model.post_query import PostFilter, PostSort, PostGraphType from queries.hello import HelloQuery from scoped_service import ScopedService from service import PingService +from test_data_seeder import TestDataSeeder def main(): @@ -27,29 +30,38 @@ def main(): Configuration.add_json_file(f"appsettings.{Environment.get_host_name()}.json", optional=True) # builder.services.add_logging() - builder.services.add_structured_logging() - builder.services.add_transient(PingService) - builder.services.add_module(MySQLModule) - builder.services.add_module(ApiModule) - builder.services.add_module(GraphQLModule) + ( + builder.services.add_structured_logging() + .add_transient(PingService) + .add_module(MySQLModule) + .add_module(ApiModule) + .add_module(GraphQLModule) + .add_scoped(ScopedService) + .add_cache(AuthUser) + .add_cache(Role) + .add_transient(CityGraphType) + .add_transient(CityFilter) + .add_transient(CitySort) + .add_transient(UserGraphType) + .add_transient(UserFilter) + .add_transient(UserSort) + .add_transient(AuthUserGraphType) + .add_transient(AuthUserFilter) + .add_transient(AuthUserSort) + .add_transient(HelloQuery) + # posts + .add_transient(PostDao) + .add_transient(PostGraphType) + .add_transient(PostFilter) + .add_transient(PostSort) - builder.services.add_scoped(ScopedService) - - builder.services.add_cache(AuthUser) - builder.services.add_cache(Role) - - builder.services.add_transient(CityGraphType) - builder.services.add_transient(CityFilter) - builder.services.add_transient(CitySort) - - builder.services.add_transient(UserGraphType) - builder.services.add_transient(UserFilter) - builder.services.add_transient(UserSort) - - builder.services.add_transient(HelloQuery) + # test data + .add_singleton(TestDataSeeder) + ) app = builder.build() app.with_logging() + app.with_migrations("./scripts") app.with_authentication() app.with_authorization() @@ -66,6 +78,7 @@ def main(): schema = app.with_graphql() schema.query.string_field("ping", resolver=lambda: "pong") schema.query.with_query("hello", HelloQuery) + schema.query.dao_collection_field(PostGraphType, PostDao, "posts", PostFilter, PostSort) app.with_playground() app.with_graphiql() diff --git a/example/api/src/model/__init__.py b/example/api/src/model/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/example/api/src/model/post.py b/example/api/src/model/post.py new file mode 100644 index 00000000..a2d22d60 --- /dev/null +++ b/example/api/src/model/post.py @@ -0,0 +1,30 @@ +from datetime import datetime +from typing import Self + +from cpl.core.typing import SerialId +from cpl.database.abc import DbModelABC + + +class Post(DbModelABC[Self]): + + def __init__( + self, + id: int, + title: str, + content: str, + deleted: bool = False, + editor_id: SerialId | None = None, + created: datetime | None = None, + updated: datetime | None = None, + ): + DbModelABC.__init__(self, id, deleted, editor_id, created, updated) + self._title = title + self._content = content + + @property + def title(self) -> str: + return self._title + + @property + def content(self) -> str: + return self._content diff --git a/example/api/src/model/post_dao.py b/example/api/src/model/post_dao.py new file mode 100644 index 00000000..da283fef --- /dev/null +++ b/example/api/src/model/post_dao.py @@ -0,0 +1,11 @@ +from cpl.database.abc import DbModelDaoABC +from model.post import Post + + +class PostDao(DbModelDaoABC): + + def __init__(self): + DbModelDaoABC.__init__(self, Post, "posts") + + self.attribute(Post.title, str) + self.attribute(Post.content, str) \ No newline at end of file diff --git a/example/api/src/model/post_query.py b/example/api/src/model/post_query.py new file mode 100644 index 00000000..6e25dddc --- /dev/null +++ b/example/api/src/model/post_query.py @@ -0,0 +1,38 @@ +from cpl.graphql.schema.filter.filter import Filter +from cpl.graphql.schema.graph_type import GraphType +from cpl.graphql.schema.sort.sort import Sort +from cpl.graphql.schema.sort.sort_order import SortOrder +from model.post import Post + +class PostFilter(Filter[Post]): + def __init__(self): + Filter.__init__(self) + self.field("id", int) + self.field("title", str) + self.field("content", str) + +class PostSort(Sort[Post]): + def __init__(self): + Sort.__init__(self) + self.field("id", SortOrder) + self.field("title", SortOrder) + self.field("content", SortOrder) + + +class PostGraphType(GraphType[Post]): + + def __init__(self): + GraphType.__init__(self) + + self.int_field( + "id", + resolver=lambda root: root.id, + ) + self.string_field( + "title", + resolver=lambda root: root.title, + ) + self.string_field( + "content", + resolver=lambda root: root.content, + ) \ No newline at end of file diff --git a/example/api/src/queries/hello.py b/example/api/src/queries/hello.py index 2f2ba633..addd9173 100644 --- a/example/api/src/queries/hello.py +++ b/example/api/src/queries/hello.py @@ -1,11 +1,43 @@ from api.src.queries.cities import CityFilter, CitySort, CityGraphType, City from api.src.queries.user import User, UserFilter, UserSort, UserGraphType from cpl.api.middleware.request import get_request +from cpl.auth.schema import AuthUserDao, AuthUser +from cpl.graphql.schema.filter.filter import Filter +from cpl.graphql.schema.graph_type import GraphType from cpl.graphql.schema.query import Query +from cpl.graphql.schema.sort.sort import Sort +from cpl.graphql.schema.sort.sort_order import SortOrder users = [User(i, f"User {i}") for i in range(1, 101)] cities = [City(i, f"City {i}") for i in range(1, 101)] +class AuthUserFilter(Filter[AuthUser]): + def __init__(self): + Filter.__init__(self) + self.field("id", int) + self.field("username", str) + + +class AuthUserSort(Sort[AuthUser]): + def __init__(self): + Sort.__init__(self) + self.field("id", SortOrder) + self.field("username", SortOrder) + +class AuthUserGraphType(GraphType[AuthUser]): + + def __init__(self): + GraphType.__init__(self) + + self.int_field( + "id", + resolver=lambda root: root.id, + ) + self.string_field( + "username", + resolver=lambda root: root.username, + ) + class HelloQuery(Query): def __init__(self): Query.__init__(self) @@ -28,3 +60,10 @@ class HelloQuery(Query): CitySort, resolver=lambda: cities, ) + self.dao_collection_field( + AuthUserGraphType, + AuthUserDao, + "authUsers", + AuthUserFilter, + AuthUserSort, + ) diff --git a/example/api/src/scripts/0-posts.sql b/example/api/src/scripts/0-posts.sql new file mode 100644 index 00000000..bf2ecc62 --- /dev/null +++ b/example/api/src/scripts/0-posts.sql @@ -0,0 +1,10 @@ +CREATE TABLE IF NOT EXISTS `posts` ( + `id` INT(30) NOT NULL AUTO_INCREMENT, + `title` VARCHAR(64) NOT NULL, + `content` VARCHAR(512) NOT NULL, + deleted BOOLEAN NOT NULL DEFAULT FALSE, + editorId INT NULL, + created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY(`id`) +); \ No newline at end of file diff --git a/example/api/src/test_data_seeder.py b/example/api/src/test_data_seeder.py new file mode 100644 index 00000000..f50eea6f --- /dev/null +++ b/example/api/src/test_data_seeder.py @@ -0,0 +1,31 @@ +from faker import Faker + +from cpl.database.abc import DataSeederABC +from cpl.query import Enumerable +from model.post import Post +from model.post_dao import PostDao + + +fake = Faker() + + +class TestDataSeeder(DataSeederABC): + + def __init__(self, posts: PostDao): + DataSeederABC.__init__(self) + + self._posts = posts + + async def seed(self): + if await self._posts.count() == 0: + await self._seed_posts() + + async def _seed_posts(self): + posts = Enumerable.range(0, 100).select( + lambda x: Post( + id=0, + title=fake.sentence(nb_words=6), + content=fake.paragraph(nb_sentences=6), + ) + ).to_list() + await self._posts.create_many(posts, skip_editor=True) diff --git a/example/database/src/model/city.py b/example/database/src/model/city.py index c98bef85..2d61f92f 100644 --- a/example/database/src/model/city.py +++ b/example/database/src/model/city.py @@ -5,16 +5,16 @@ from cpl.core.typing import SerialId from cpl.database.abc.db_model_abc import DbModelABC -class City(DbModelABC): +class City(DbModelABC[Self]): def __init__( self, id: int, name: str, zip: str, deleted: bool = False, - editor_id: Optional[SerialId] = None, - created: Optional[datetime] = None, - updated: Optional[datetime] = None, + editor_id: SerialId | None = None, + created: datetime | None= None, + updated: datetime | None= None, ): DbModelABC.__init__(self, id, deleted, editor_id, created, updated) self._name = name diff --git a/example/database/src/model/user.py b/example/database/src/model/user.py index 445c56b7..e0116423 100644 --- a/example/database/src/model/user.py +++ b/example/database/src/model/user.py @@ -5,7 +5,7 @@ from cpl.core.typing import SerialId from cpl.database.abc.db_model_abc import DbModelABC -class User(DbModelABC): +class User(DbModelABC[Self]): def __init__( self, @@ -13,9 +13,9 @@ class User(DbModelABC): name: str, city_id: int = 0, deleted: bool = False, - editor_id: Optional[SerialId] = None, - created: Optional[datetime] = None, - updated: Optional[datetime] = None, + editor_id: SerialId | None = None, + created: datetime | None= None, + updated: datetime | None= None, ): DbModelABC.__init__(self, id, deleted, editor_id, created, updated) self._name = name diff --git a/src/cpl-auth/cpl/auth/schema/_administration/api_key.py b/src/cpl-auth/cpl/auth/schema/_administration/api_key.py index 16f57a7d..995628e2 100644 --- a/src/cpl-auth/cpl/auth/schema/_administration/api_key.py +++ b/src/cpl-auth/cpl/auth/schema/_administration/api_key.py @@ -1,6 +1,6 @@ import secrets from datetime import datetime -from typing import Optional, Union +from typing import Optional, Union, Self from async_property import async_property @@ -16,7 +16,7 @@ from cpl.dependency.service_provider import ServiceProvider _logger = Logger(__name__) -class ApiKey(DbModelABC): +class ApiKey(DbModelABC[Self]): def __init__( self, @@ -25,8 +25,8 @@ class ApiKey(DbModelABC): key: Union[str, bytes], deleted: bool = False, editor_id: Optional[Id] = None, - created: Optional[datetime] = None, - updated: Optional[datetime] = None, + created: datetime | None= None, + updated: datetime | None= None, ): DbModelABC.__init__(self, id, deleted, editor_id, created, updated) self._identifier = identifier diff --git a/src/cpl-auth/cpl/auth/schema/_administration/auth_user.py b/src/cpl-auth/cpl/auth/schema/_administration/auth_user.py index 5409e468..e9eff14d 100644 --- a/src/cpl-auth/cpl/auth/schema/_administration/auth_user.py +++ b/src/cpl-auth/cpl/auth/schema/_administration/auth_user.py @@ -1,6 +1,6 @@ import uuid from datetime import datetime -from typing import Optional +from typing import Optional, Self from async_property import async_property from keycloak import KeycloakGetError @@ -13,15 +13,15 @@ from cpl.database.logger import DBLogger from cpl.dependency import get_provider -class AuthUser(DbModelABC): +class AuthUser(DbModelABC[Self]): def __init__( self, id: SerialId, keycloak_id: str, deleted: bool = False, - editor_id: Optional[SerialId] = None, - created: Optional[datetime] = None, - updated: Optional[datetime] = None, + editor_id: SerialId | None = None, + created: datetime | None= None, + updated: datetime | None= None, ): DbModelABC.__init__(self, id, deleted, editor_id, created, updated) self._keycloak_id = keycloak_id @@ -87,4 +87,3 @@ class AuthUser(DbModelABC): self._keycloak_id = str(uuid.UUID(int=0)) await auth_user_dao.update(self) - diff --git a/src/cpl-auth/cpl/auth/schema/_administration/auth_user_dao.py b/src/cpl-auth/cpl/auth/schema/_administration/auth_user_dao.py index 8963259f..4b27549a 100644 --- a/src/cpl-auth/cpl/auth/schema/_administration/auth_user_dao.py +++ b/src/cpl-auth/cpl/auth/schema/_administration/auth_user_dao.py @@ -5,7 +5,7 @@ from cpl.auth.schema._administration.auth_user import AuthUser from cpl.database import TableManager from cpl.database.abc import DbModelDaoABC from cpl.database.external_data_temp_table_builder import ExternalDataTempTableBuilder -from cpl.dependency import ServiceProvider +from cpl.dependency.context import get_provider class AuthUserDao(DbModelDaoABC[AuthUser]): diff --git a/src/cpl-auth/cpl/auth/schema/_permission/api_key_permission.py b/src/cpl-auth/cpl/auth/schema/_permission/api_key_permission.py index 8a7f8e4b..59132955 100644 --- a/src/cpl-auth/cpl/auth/schema/_permission/api_key_permission.py +++ b/src/cpl-auth/cpl/auth/schema/_permission/api_key_permission.py @@ -15,9 +15,9 @@ class ApiKeyPermission(DbJoinModelABC): api_key_id: SerialId, permission_id: SerialId, deleted: bool = False, - editor_id: Optional[SerialId] = None, - created: Optional[datetime] = None, - updated: Optional[datetime] = None, + editor_id: SerialId | None = None, + created: datetime | None= None, + updated: datetime | None= None, ): DbJoinModelABC.__init__(self, api_key_id, permission_id, id, deleted, editor_id, created, updated) self._api_key_id = api_key_id diff --git a/src/cpl-auth/cpl/auth/schema/_permission/permission.py b/src/cpl-auth/cpl/auth/schema/_permission/permission.py index e5bb046d..8db9c477 100644 --- a/src/cpl-auth/cpl/auth/schema/_permission/permission.py +++ b/src/cpl-auth/cpl/auth/schema/_permission/permission.py @@ -1,20 +1,20 @@ from datetime import datetime -from typing import Optional +from typing import Optional, Self from cpl.core.typing import SerialId from cpl.database.abc import DbModelABC -class Permission(DbModelABC): +class Permission(DbModelABC[Self]): def __init__( self, id: SerialId, name: str, description: str, deleted: bool = False, - editor_id: Optional[SerialId] = None, - created: Optional[datetime] = None, - updated: Optional[datetime] = None, + editor_id: SerialId | None = None, + created: datetime | None= None, + updated: datetime | None= None, ): DbModelABC.__init__(self, id, deleted, editor_id, created, updated) self._name = name diff --git a/src/cpl-auth/cpl/auth/schema/_permission/role.py b/src/cpl-auth/cpl/auth/schema/_permission/role.py index 325fec91..24a5d82d 100644 --- a/src/cpl-auth/cpl/auth/schema/_permission/role.py +++ b/src/cpl-auth/cpl/auth/schema/_permission/role.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import Optional +from typing import Optional, Self from async_property import async_property @@ -9,16 +9,16 @@ from cpl.database.abc import DbModelABC from cpl.dependency import ServiceProvider -class Role(DbModelABC): +class Role(DbModelABC[Self]): def __init__( self, id: SerialId, name: str, description: str, deleted: bool = False, - editor_id: Optional[SerialId] = None, - created: Optional[datetime] = None, - updated: Optional[datetime] = None, + editor_id: SerialId | None = None, + created: datetime | None= None, + updated: datetime | None= None, ): DbModelABC.__init__(self, id, deleted, editor_id, created, updated) self._name = name diff --git a/src/cpl-auth/cpl/auth/schema/_permission/role_permission.py b/src/cpl-auth/cpl/auth/schema/_permission/role_permission.py index 33b60f04..82bacb4a 100644 --- a/src/cpl-auth/cpl/auth/schema/_permission/role_permission.py +++ b/src/cpl-auth/cpl/auth/schema/_permission/role_permission.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import Optional +from typing import Optional, Self from async_property import async_property @@ -8,16 +8,16 @@ from cpl.database.abc import DbModelABC from cpl.dependency import ServiceProvider -class RolePermission(DbModelABC): +class RolePermission(DbModelABC[Self]): def __init__( self, id: SerialId, role_id: SerialId, permission_id: SerialId, deleted: bool = False, - editor_id: Optional[SerialId] = None, - created: Optional[datetime] = None, - updated: Optional[datetime] = None, + editor_id: SerialId | None = None, + created: datetime | None= None, + updated: datetime | None= None, ): DbModelABC.__init__(self, id, deleted, editor_id, created, updated) self._role_id = role_id diff --git a/src/cpl-auth/cpl/auth/schema/_permission/role_user.py b/src/cpl-auth/cpl/auth/schema/_permission/role_user.py index 6f1f659e..5db0f892 100644 --- a/src/cpl-auth/cpl/auth/schema/_permission/role_user.py +++ b/src/cpl-auth/cpl/auth/schema/_permission/role_user.py @@ -15,9 +15,9 @@ class RoleUser(DbJoinModelABC): user_id: SerialId, role_id: SerialId, deleted: bool = False, - editor_id: Optional[SerialId] = None, - created: Optional[datetime] = None, - updated: Optional[datetime] = None, + editor_id: SerialId | None = None, + created: datetime | None= None, + updated: datetime | None= None, ): DbJoinModelABC.__init__(self, id, user_id, role_id, deleted, editor_id, created, updated) self._user_id = user_id diff --git a/src/cpl-core/cpl/core/utils/credential_manager.py b/src/cpl-core/cpl/core/utils/credential_manager.py index d030dc94..126afd6a 100644 --- a/src/cpl-core/cpl/core/utils/credential_manager.py +++ b/src/cpl-core/cpl/core/utils/credential_manager.py @@ -2,10 +2,6 @@ import os from cryptography.fernet import Fernet -from cpl.core.log.logger import Logger - -_logger = Logger(__name__) - class CredentialManager: r"""Handles credential encryption and decryption""" @@ -14,6 +10,7 @@ class CredentialManager: @classmethod def with_secret(cls, file: str = None): + from cpl.core.log import Logger if file is None: file = ".secret" @@ -25,12 +22,12 @@ class CredentialManager: with open(file, "w") as secret_file: secret_file.write(Fernet.generate_key().decode()) secret_file.close() - _logger.warning("Secret file not found, regenerating") + Logger(__name__).warning("Secret file not found, regenerating") with open(file, "r") as secret_file: secret = secret_file.read().strip() if secret == "" or secret is None: - _logger.fatal("No secret found in .secret file.") + Logger(__name__).fatal("No secret found in .secret file.") cls._secret = str(secret) diff --git a/src/cpl-database/cpl/database/abc/data_access_object_abc.py b/src/cpl-database/cpl/database/abc/data_access_object_abc.py index 95a12e05..44f2a0bf 100644 --- a/src/cpl-database/cpl/database/abc/data_access_object_abc.py +++ b/src/cpl-database/cpl/database/abc/data_access_object_abc.py @@ -46,6 +46,10 @@ class DataAccessObjectABC(ABC, Generic[T_DBM]): def table_name(self) -> str: return self._table_name + @property + def type(self) -> Type[T_DBM]: + return self._model_type + def has_attribute(self, attr_name: Attribute) -> bool: """ Check if the attribute exists in the DAO @@ -490,16 +494,16 @@ class DataAccessObjectABC(ABC, Generic[T_DBM]): table, join_condition = self.__foreign_tables[attr] builder.with_left_join(table, join_condition) - if filters: + if filters is not None: await self._build_conditions(builder, filters, external_table_deps) - if sorts: + if sorts is not None: self._build_sorts(builder, sorts, external_table_deps) - if take: + if take is not None: builder.with_limit(take) - if skip: + if skip is not None: builder.with_offset(skip) for external_table in external_table_deps: diff --git a/src/cpl-database/cpl/database/abc/db_join_model_abc.py b/src/cpl-database/cpl/database/abc/db_join_model_abc.py index c81bd50d..55327419 100644 --- a/src/cpl-database/cpl/database/abc/db_join_model_abc.py +++ b/src/cpl-database/cpl/database/abc/db_join_model_abc.py @@ -12,9 +12,9 @@ class DbJoinModelABC[T](DbModelABC[T]): source_id: Id, foreign_id: Id, deleted: bool = False, - editor_id: Optional[SerialId] = None, - created: Optional[datetime] = None, - updated: Optional[datetime] = None, + editor_id: SerialId | None = None, + created: datetime | None= None, + updated: datetime | None= None, ): DbModelABC.__init__(self, id, deleted, editor_id, created, updated) diff --git a/src/cpl-database/cpl/database/abc/db_model_abc.py b/src/cpl-database/cpl/database/abc/db_model_abc.py index edbd1f3b..5791afe3 100644 --- a/src/cpl-database/cpl/database/abc/db_model_abc.py +++ b/src/cpl-database/cpl/database/abc/db_model_abc.py @@ -10,9 +10,9 @@ class DbModelABC(ABC, Generic[T]): self, id: Id, deleted: bool = False, - editor_id: Optional[SerialId] = None, - created: Optional[datetime] = None, - updated: Optional[datetime] = None, + editor_id: SerialId | None = None, + created: datetime | None= None, + updated: datetime | None= None, ): self._id = id self._deleted = deleted diff --git a/src/cpl-database/cpl/database/model/database_settings.py b/src/cpl-database/cpl/database/model/database_settings.py index ccf1ad44..fa6154af 100644 --- a/src/cpl-database/cpl/database/model/database_settings.py +++ b/src/cpl-database/cpl/database/model/database_settings.py @@ -1,6 +1,6 @@ from typing import Optional -from cpl.core.configuration import Configuration +from cpl.core.configuration.configuration import Configuration from cpl.core.configuration.configuration_model_abc import ConfigurationModelABC diff --git a/src/cpl-database/cpl/database/schema/executed_migration.py b/src/cpl-database/cpl/database/schema/executed_migration.py index 3b9ed1c5..02b99dc3 100644 --- a/src/cpl-database/cpl/database/schema/executed_migration.py +++ b/src/cpl-database/cpl/database/schema/executed_migration.py @@ -1,15 +1,15 @@ from datetime import datetime -from typing import Optional +from typing import Optional, Self from cpl.database.abc import DbModelABC -class ExecutedMigration(DbModelABC): +class ExecutedMigration(DbModelABC[Self]): def __init__( self, migration_id: str, - created: Optional[datetime] = None, - modified: Optional[datetime] = None, + created: datetime | None= None, + modified: datetime | None= None, ): DbModelABC.__init__(self, migration_id, False, created, modified) diff --git a/src/cpl-graphql/cpl/graphql/schema/collection.py b/src/cpl-graphql/cpl/graphql/schema/collection.py index 68b8aa69..6cac07a8 100644 --- a/src/cpl-graphql/cpl/graphql/schema/collection.py +++ b/src/cpl-graphql/cpl/graphql/schema/collection.py @@ -18,7 +18,7 @@ class CollectionGraphTypeFactory: gql_type = strawberry.type( type( - f"{node_type.__name__}Collection", + f"{node_type.__name__.replace("GraphType", "")}Collection", (), { "__annotations__": { diff --git a/src/cpl-graphql/cpl/graphql/schema/query.py b/src/cpl-graphql/cpl/graphql/schema/query.py index a453734a..5539c4c6 100644 --- a/src/cpl-graphql/cpl/graphql/schema/query.py +++ b/src/cpl-graphql/cpl/graphql/schema/query.py @@ -4,6 +4,7 @@ from typing import Callable, Type, Any, Optional import strawberry from strawberry.exceptions import StrawberryException +from cpl.database.abc.data_access_object_abc import DataAccessObjectABC from cpl.dependency.inject import inject from cpl.dependency.service_provider import ServiceProvider from cpl.graphql.abc.strawberry_protocol import StrawberryProtocol @@ -69,9 +70,6 @@ class Query(StrawberryProtocol): sort_type: Type[StrawberryProtocol], resolver: Callable, ) -> Field: - # self._schema.with_type(filter_type) - # self._schema.with_type(sort_type) - def _resolve_collection(filter=None, sort=None, skip=0, take=10): items = resolver() if filter: @@ -103,6 +101,53 @@ class Query(StrawberryProtocol): f.with_argument(int, "take", default_value=10) return f + def dao_collection_field( + self, + t: Type[StrawberryProtocol], + dao_type: Type[DataAccessObjectABC], + name: str, + filter_type: Type[StrawberryProtocol], + sort_type: Type[StrawberryProtocol], + ) -> Field: + assert issubclass(dao_type, DataAccessObjectABC), "dao_type must be a subclass of DataAccessObjectABC" + dao = self._provider.get_service(dao_type) + if not dao: + raise ValueError(f"DAO '{dao_type.__name__}' not registered in service provider") + + filter = self._provider.get_service(filter_type) + if not filter: + raise ValueError(f"Filter '{filter_type.__name__}' not registered in service provider") + + sort = self._provider.get_service(sort_type) + if not sort: + raise ValueError(f"Sort '{sort_type.__name__}' not registered in service provider") + + async def _resolver(filter=None, sort=None, take=10, skip=0): + sort_dict = None + + if sort is not None: + sort_dict = {} + for k, v in sort.__dict__.items(): + if v is None: + continue + + if isinstance(v, SortOrder): + sort_dict[k] = str(v.value).lower() + continue + + sort_dict[k] = str(v).lower() + + total_count = await dao.count(filter) + data = await dao.find_by(filter, sort_dict, take, skip) + return Collection(nodes=data, total_count=total_count, count=len(data)) + + f = self.field(name, CollectionGraphTypeFactory.get(t), _resolver) + f.with_argument(filter.to_strawberry(), "filter") + f.with_argument(sort.to_strawberry(), "sort") + f.with_argument(int, "skip", default_value=0) + f.with_argument(int, "take", default_value=10) + return f + @staticmethod def _build_resolver(f: "Field"): params: list[inspect.Parameter] = [] @@ -164,4 +209,4 @@ class Query(StrawberryProtocol): namespace[name] = self._field_to_strawberry(f) namespace["__annotations__"] = annotations - return strawberry.type(type(f"{self.__class__.__name__}GraphType", (), namespace)) + return strawberry.type(type(f"{self.__class__.__name__.replace("GraphType", "")}", (), namespace)) diff --git a/src/cpl-graphql/cpl/graphql/schema/sort/sort_order.py b/src/cpl-graphql/cpl/graphql/schema/sort/sort_order.py index cc3122a4..cb8e8177 100644 --- a/src/cpl-graphql/cpl/graphql/schema/sort/sort_order.py +++ b/src/cpl-graphql/cpl/graphql/schema/sort/sort_order.py @@ -2,5 +2,5 @@ from enum import Enum, auto class SortOrder(Enum): - ASC = auto() - DESC = auto() \ No newline at end of file + ASC = "ASC" + DESC = "DESC" \ No newline at end of file