From 621c79067b602ff22c54f961703fcc1c4d3177f3 Mon Sep 17 00:00:00 2001 From: kerthcet Date: Tue, 10 Feb 2026 12:11:02 +0000 Subject: [PATCH 1/5] Support create user with uuid Signed-off-by: kerthcet --- alphatrion/server/graphql/resolvers.py | 2 + alphatrion/server/graphql/types.py | 2 + alphatrion/storage/sqlstore.py | 47 +++++++---- .../server/test_graphql_mutation.py | 82 +++++++++++++++++++ 4 files changed, 115 insertions(+), 18 deletions(-) diff --git a/alphatrion/server/graphql/resolvers.py b/alphatrion/server/graphql/resolvers.py index 244664a..2984c78 100644 --- a/alphatrion/server/graphql/resolvers.py +++ b/alphatrion/server/graphql/resolvers.py @@ -294,6 +294,7 @@ class GraphQLMutations: def create_user(input: CreateUserInput) -> User: metadb = runtime.graphql_runtime().metadb user_id = metadb.create_user( + uuid=uuid.UUID(input.id) if input.id else None, username=input.username, email=input.email, avatar_url=input.avatar_url, @@ -337,6 +338,7 @@ def update_user(input: UpdateUserInput) -> User: def create_team(input: CreateTeamInput) -> Team: metadb = runtime.graphql_runtime().metadb team_id = metadb.create_team( + uuid=uuid.UUID(input.id) if input.id else None, name=input.name, description=input.description, meta=input.meta, diff --git a/alphatrion/server/graphql/types.py b/alphatrion/server/graphql/types.py index 03d35d8..60c19f0 100644 --- a/alphatrion/server/graphql/types.py +++ b/alphatrion/server/graphql/types.py @@ -145,6 +145,7 @@ class Metric: # Input types for mutations @strawberry.input class CreateUserInput: + id: strawberry.ID | None = None username: str email: str avatar_url: str | None = None @@ -153,6 +154,7 @@ class CreateUserInput: @strawberry.input class CreateTeamInput: + id: strawberry.ID | None = None name: str description: str | None = None meta: JSON | None = None diff --git a/alphatrion/storage/sqlstore.py b/alphatrion/storage/sqlstore.py index 1aeefc0..481bc06 100644 --- a/alphatrion/storage/sqlstore.py +++ b/alphatrion/storage/sqlstore.py @@ -31,8 +31,15 @@ def __init__(self, db_url: str, init_tables: bool = False): # ---------- Team APIs ---------- + # If uuid is provided, we will use the provided uuid for the new team. + # This is useful for binding with external team management system where + # the team id is already determined. def create_team( - self, name: str, description: str | None = None, meta: dict | None = None + self, + name: str, + uuid: uuid.UUID | None = None, + description: str | None = None, + meta: dict | None = None, ) -> uuid.UUID: session = self._session() new_team = Team( @@ -40,6 +47,9 @@ def create_team( description=description, meta=meta, ) + if uuid is not None: + new_team.uuid = uuid + session.add(new_team) session.commit() team_id = new_team.uuid @@ -71,27 +81,34 @@ def list_user_teams(self, user_id: uuid.UUID) -> list[Team]: # ---------- User APIs ---------- + # If uuid is provided, we will use the provided uuid for the new user. + # This is useful for binding with external user management system where + # the user id is already determined. def create_user( self, username: str, email: str, + uuid: uuid.UUID | None = None, avatar_url: str | None = None, team_id: uuid.UUID | None = None, meta: dict | None = None, ) -> uuid.UUID: + user = User( + username=username, + email=email, + avatar_url=avatar_url, + meta=meta, + ) + if uuid is not None: + user.uuid = uuid + # If team_id is not provided, we will just create the user # without any team association. if team_id is None: session = self._session() - new_user = User( - username=username, - email=email, - avatar_url=avatar_url, - meta=meta, - ) - session.add(new_user) + session.add(user) session.commit() - user_id = new_user.uuid + user_id = user.uuid session.close() return user_id @@ -100,23 +117,17 @@ def create_user( # add to the team in a transaction. session = self._session() try: - new_user = User( - username=username, - email=email, - avatar_url=avatar_url, - meta=meta, - ) - session.add(new_user) + session.add(user) session.flush() # flush to get the new user's id new_member = TeamMember( - user_id=new_user.uuid, + user_id=user.uuid, team_id=team_id, ) session.add(new_member) session.commit() - user_id = new_user.uuid + user_id = user.uuid except Exception as e: session.rollback() raise e diff --git a/tests/integration/server/test_graphql_mutation.py b/tests/integration/server/test_graphql_mutation.py index 3b51d97..95f8a30 100644 --- a/tests/integration/server/test_graphql_mutation.py +++ b/tests/integration/server/test_graphql_mutation.py @@ -61,6 +61,49 @@ def test_create_team_mutation(): assert team.name == "Test Team" +def test_create_team_mutation_with_uuid(): + """Test creating a team via GraphQL mutation""" + init(init_tables=True) + id = uuid.uuid4() # Generate a UUID to use for the new team + + mutation = """ + mutation { + createTeam(input: { + id: "{id}" + name: "Test Team" + description: "A team created via mutation" + meta: {foo: "bar", count: 42} + }) { + id + name + description + meta + createdAt + updatedAt + totalProjects + totalExperiments + totalRuns + } + } + """ + response = schema.execute_sync( + mutation, + variable_values={"id": str(id)}, + ) + assert response.errors is None + assert response.data["createTeam"]["name"] == "Test Team" + assert response.data["createTeam"]["description"] == "A team created via mutation" + assert response.data["createTeam"]["meta"] == {"foo": "bar", "count": 42} + assert response.data["createTeam"]["totalProjects"] == 0 + assert response.data["createTeam"]["totalExperiments"] == 0 + assert response.data["createTeam"]["totalRuns"] == 0 + + # Verify team was actually created in database + assert response.data["createTeam"]["id"] == str( + uuid + ) # Verify the returned ID matches the provided UUID + + def test_create_user_mutation(): """Test creating a user via GraphQL mutation""" init(init_tables=True) @@ -109,6 +152,45 @@ def test_create_user_mutation(): assert user.username == username +def test_create_user_mutation_with_uuid(): + """Test creating a user via GraphQL mutation""" + init(init_tables=True) + uuid = uuid.uuid4() # Generate a UUID to use for the new user + + username = unique_username("testuser") + email = unique_email("testuser") + + mutation = f""" + mutation {{ + createUser(input: {{ + id: "{uuid}" + username: "{username}" + email: "{email}" + meta: {{role: "engineer", level: "senior"}} + }}) {{ + id + username + email + meta + createdAt + updatedAt + teams {{ + id + name + }} + }} + }} + """ + response = schema.execute_sync( + mutation, + variable_values={}, + ) + assert response.errors is None + assert response.data["createUser"]["id"] == str( + uuid + ) # Verify the returned ID matches the provided UUID + + def test_add_user_to_team_mutation(): """Test adding a user to a team via mutation""" init(init_tables=True) From d6827091705b5b2380e564564fbeead51c5b4a0a Mon Sep 17 00:00:00 2001 From: kerthcet Date: Tue, 10 Feb 2026 13:45:16 +0000 Subject: [PATCH 2/5] fix test Signed-off-by: kerthcet --- tests/integration/server/test_graphql_mutation.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/integration/server/test_graphql_mutation.py b/tests/integration/server/test_graphql_mutation.py index 95f8a30..b3b47f7 100644 --- a/tests/integration/server/test_graphql_mutation.py +++ b/tests/integration/server/test_graphql_mutation.py @@ -155,7 +155,7 @@ def test_create_user_mutation(): def test_create_user_mutation_with_uuid(): """Test creating a user via GraphQL mutation""" init(init_tables=True) - uuid = uuid.uuid4() # Generate a UUID to use for the new user + id = uuid.uuid4() # Generate a UUID to use for the new user username = unique_username("testuser") email = unique_email("testuser") @@ -163,7 +163,7 @@ def test_create_user_mutation_with_uuid(): mutation = f""" mutation {{ createUser(input: {{ - id: "{uuid}" + id: "{id}" username: "{username}" email: "{email}" meta: {{role: "engineer", level: "senior"}} @@ -187,7 +187,7 @@ def test_create_user_mutation_with_uuid(): ) assert response.errors is None assert response.data["createUser"]["id"] == str( - uuid + id ) # Verify the returned ID matches the provided UUID From a6fd2294a2e8934cca45cf3e918a934037047271 Mon Sep 17 00:00:00 2001 From: kerthcet Date: Tue, 10 Feb 2026 13:50:44 +0000 Subject: [PATCH 3/5] fix test Signed-off-by: kerthcet --- tests/integration/server/test_graphql_mutation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/server/test_graphql_mutation.py b/tests/integration/server/test_graphql_mutation.py index b3b47f7..ee28909 100644 --- a/tests/integration/server/test_graphql_mutation.py +++ b/tests/integration/server/test_graphql_mutation.py @@ -163,7 +163,7 @@ def test_create_user_mutation_with_uuid(): mutation = f""" mutation {{ createUser(input: {{ - id: "{id}" + id: "{str(id)}" username: "{username}" email: "{email}" meta: {{role: "engineer", level: "senior"}} From 6ec8d3875b63c8fa2eb77fb1f71721dda7fa37cb Mon Sep 17 00:00:00 2001 From: kerthcet Date: Tue, 10 Feb 2026 14:14:49 +0000 Subject: [PATCH 4/5] fix test Signed-off-by: kerthcet --- .../server/test_graphql_mutation.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/integration/server/test_graphql_mutation.py b/tests/integration/server/test_graphql_mutation.py index ee28909..f844a7d 100644 --- a/tests/integration/server/test_graphql_mutation.py +++ b/tests/integration/server/test_graphql_mutation.py @@ -66,14 +66,14 @@ def test_create_team_mutation_with_uuid(): init(init_tables=True) id = uuid.uuid4() # Generate a UUID to use for the new team - mutation = """ - mutation { - createTeam(input: { - id: "{id}" + mutation = f""" + mutation {{ + createTeam(input: {{ + id: "{str(id)}" name: "Test Team" description: "A team created via mutation" - meta: {foo: "bar", count: 42} - }) { + meta: {{foo: "bar", count: 42}} + }}) {{ id name description @@ -83,12 +83,12 @@ def test_create_team_mutation_with_uuid(): totalProjects totalExperiments totalRuns - } - } + }} + }} """ response = schema.execute_sync( mutation, - variable_values={"id": str(id)}, + variable_values={}, ) assert response.errors is None assert response.data["createTeam"]["name"] == "Test Team" From dda2e7b60643afa651af2a72fb79851fc0fb6561 Mon Sep 17 00:00:00 2001 From: kerthcet Date: Tue, 10 Feb 2026 14:19:27 +0000 Subject: [PATCH 5/5] fix test Signed-off-by: kerthcet --- tests/integration/server/test_graphql_mutation.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/tests/integration/server/test_graphql_mutation.py b/tests/integration/server/test_graphql_mutation.py index f844a7d..ecf7269 100644 --- a/tests/integration/server/test_graphql_mutation.py +++ b/tests/integration/server/test_graphql_mutation.py @@ -92,16 +92,8 @@ def test_create_team_mutation_with_uuid(): ) assert response.errors is None assert response.data["createTeam"]["name"] == "Test Team" - assert response.data["createTeam"]["description"] == "A team created via mutation" - assert response.data["createTeam"]["meta"] == {"foo": "bar", "count": 42} - assert response.data["createTeam"]["totalProjects"] == 0 - assert response.data["createTeam"]["totalExperiments"] == 0 - assert response.data["createTeam"]["totalRuns"] == 0 - # Verify team was actually created in database - assert response.data["createTeam"]["id"] == str( - uuid - ) # Verify the returned ID matches the provided UUID + assert response.data["createTeam"]["id"] == str(id) # Verify the returned ID matches the provided UUID def test_create_user_mutation():