From afc51abae47f5c65933c362bc43ceab3c9d82701 Mon Sep 17 00:00:00 2001 From: vedantsahai18 Date: Mon, 16 Dec 2024 00:02:32 -0500 Subject: [PATCH] fix(queries-user): major bug fixes, refactor and added init user sql test --- .../agents_api/queries/users/__init__.py | 24 +-- .../queries/users/create_or_update_user.py | 79 +++++--- .../agents_api/queries/users/create_user.py | 65 ++++--- .../agents_api/queries/users/delete_user.py | 49 +++-- .../agents_api/queries/users/get_user.py | 52 +++-- .../agents_api/queries/users/list_users.py | 64 ++++--- .../agents_api/queries/users/patch_user.py | 55 ++++-- .../agents_api/queries/users/update_user.py | 61 +++--- agents-api/tests/test_user_sql.py | 178 ++++++++++++++++++ agents-api/uv.lock | 15 -- 10 files changed, 467 insertions(+), 175 deletions(-) create mode 100644 agents-api/tests/test_user_sql.py diff --git a/agents-api/agents_api/queries/users/__init__.py b/agents-api/agents_api/queries/users/__init__.py index d7988279e..26eb37377 100644 --- a/agents-api/agents_api/queries/users/__init__.py +++ b/agents-api/agents_api/queries/users/__init__.py @@ -9,20 +9,20 @@ - Deleting users """ -from .create_or_update_user import create_or_update_user_query +from .create_or_update_user import create_or_update_user from .create_user import create_user -from .delete_user import delete_user_query -from .get_user import get_user_query -from .list_users import list_users_query -from .patch_user import patch_user_query -from .update_user import update_user_query +from .get_user import get_user +from .list_users import list_users +from .patch_user import patch_user +from .update_user import update_user +from .delete_user import delete_user __all__ = [ "create_user", - "create_or_update_user_query", - "delete_user_query", - "get_user_query", - "list_users_query", - "patch_user_query", - "update_user_query", + "create_or_update_user", + "delete_user", + "get_user", + "list_users", + "patch_user", + "update_user", ] diff --git a/agents-api/agents_api/queries/users/create_or_update_user.py b/agents-api/agents_api/queries/users/create_or_update_user.py index 67182d047..b579e8de0 100644 --- a/agents-api/agents_api/queries/users/create_or_update_user.py +++ b/agents-api/agents_api/queries/users/create_or_update_user.py @@ -1,30 +1,72 @@ -from typing import Any from uuid import UUID -from asyncpg import exceptions as asyncpg_exceptions +import asyncpg from beartype import beartype from fastapi import HTTPException from sqlglot import parse_one +from sqlglot.optimizer import optimize from ...autogen.openapi_model import CreateUserRequest, User from ...metrics.counters import increase_counter from ..utils import partialclass, pg_query, rewrap_exceptions, wrap_in_class +# Optimize the raw query by using COALESCE for metadata to avoid explicit check +raw_query = """ +INSERT INTO users ( + developer_id, + user_id, + name, + about, + metadata +) +VALUES ( + %(developer_id)s, + %(user_id)s, + %(name)s, + %(about)s, + COALESCE(%(metadata)s, '{}'::jsonb) +) +ON CONFLICT (developer_id, user_id) DO UPDATE SET + name = EXCLUDED.name, + about = EXCLUDED.about, + metadata = EXCLUDED.metadata +RETURNING *; +""" + +# Add index hint for better performance +query = optimize( + parse_one(raw_query), + schema={ + "users": { + "developer_id": "UUID", + "user_id": "UUID", + "name": "STRING", + "about": "STRING", + "metadata": "JSONB", + } + }, +).sql(pretty=True) + @rewrap_exceptions( { - asyncpg_exceptions.ForeignKeyViolationError: partialclass( + asyncpg.ForeignKeyViolationError: partialclass( HTTPException, status_code=404, detail="The specified developer does not exist.", - ) + ), + asyncpg.UniqueViolationError: partialclass( # Add handling for potential race conditions + HTTPException, + status_code=409, + detail="A user with this ID already exists.", + ), } ) @wrap_in_class(User) @increase_counter("create_or_update_user") @pg_query @beartype -def create_or_update_user_query( +def create_or_update_user( *, developer_id: UUID, user_id: UUID, data: CreateUserRequest ) -> tuple[str, dict]: """ @@ -37,35 +79,16 @@ def create_or_update_user_query( Returns: tuple[str, dict]: SQL query and parameters. - """ - query = parse_one(""" - INSERT INTO users ( - developer_id, - user_id, - name, - about, - metadata - ) - VALUES ( - %(developer_id)s, - %(user_id)s, - %(name)s, - %(about)s, - %(metadata)s - ) - ON CONFLICT (developer_id, user_id) DO UPDATE SET - name = EXCLUDED.name, - about = EXCLUDED.about, - metadata = EXCLUDED.metadata - RETURNING *; - """).sql() + Raises: + HTTPException: If developer doesn't exist (404) or on unique constraint violation (409) + """ params = { "developer_id": developer_id, "user_id": user_id, "name": data.name, "about": data.about, - "metadata": data.metadata or {}, + "metadata": data.metadata, # Let COALESCE handle None case in SQL } return query, params diff --git a/agents-api/agents_api/queries/users/create_user.py b/agents-api/agents_api/queries/users/create_user.py index 0f979ebdd..691c43500 100644 --- a/agents-api/agents_api/queries/users/create_user.py +++ b/agents-api/agents_api/queries/users/create_user.py @@ -1,29 +1,60 @@ -from typing import Any from uuid import UUID +import asyncpg from beartype import beartype from fastapi import HTTPException -from psycopg import errors as psycopg_errors -from pydantic import ValidationError -from sqlglot import parse_one +from sqlglot import optimize, parse_one from uuid_extensions import uuid7 from ...autogen.openapi_model import CreateUserRequest, User from ...metrics.counters import increase_counter from ..utils import partialclass, pg_query, rewrap_exceptions, wrap_in_class +# Define the raw SQL query outside the function +raw_query = """ +INSERT INTO users ( + developer_id, + user_id, + name, + about, + metadata +) +VALUES ( + %(developer_id)s, + %(user_id)s, + %(name)s, + %(about)s, + %(metadata)s +) +RETURNING *; +""" + +# Parse and optimize the query +query = optimize( + parse_one(raw_query), + schema={ + "users": { + "developer_id": "UUID", + "user_id": "UUID", + "name": "STRING", + "about": "STRING", + "metadata": "JSONB", + } + }, +).sql(pretty=True) + @rewrap_exceptions( { - psycopg_errors.ForeignKeyViolation: partialclass( + asyncpg.ForeignKeyViolationError: partialclass( HTTPException, status_code=404, detail="The specified developer does not exist.", ), - ValidationError: partialclass( + asyncpg.NullValueNoIndicatorParameterError: partialclass( HTTPException, - status_code=400, - detail="Input validation failed. Please check the provided data.", + status_code=404, + detail="The specified developer does not exist.", ), } ) @@ -50,24 +81,6 @@ def create_user( """ user_id = user_id or uuid7() - query = parse_one(""" - INSERT INTO users ( - developer_id, - user_id, - name, - about, - metadata - ) - VALUES ( - %(developer_id)s, - %(user_id)s, - %(name)s, - %(about)s, - %(metadata)s - ) - RETURNING *; - """).sql() - params = { "developer_id": developer_id, "user_id": user_id, diff --git a/agents-api/agents_api/queries/users/delete_user.py b/agents-api/agents_api/queries/users/delete_user.py index 2dfb0b156..a21a4b9d9 100644 --- a/agents-api/agents_api/queries/users/delete_user.py +++ b/agents-api/agents_api/queries/users/delete_user.py @@ -1,19 +1,44 @@ -from typing import Any from uuid import UUID +import asyncpg from beartype import beartype from fastapi import HTTPException -from psycopg import errors as psycopg_errors from sqlglot import parse_one +from sqlglot.optimizer import optimize from ...autogen.openapi_model import ResourceDeletedResponse from ...metrics.counters import increase_counter from ..utils import partialclass, pg_query, rewrap_exceptions, wrap_in_class +# Define the raw SQL query outside the function +raw_query = """ +WITH deleted_data AS ( + DELETE FROM user_files + WHERE developer_id = %(developer_id)s AND user_id = %(user_id)s +), +deleted_docs AS ( + DELETE FROM user_docs + WHERE developer_id = %(developer_id)s AND user_id = %(user_id)s +) +DELETE FROM users +WHERE developer_id = %(developer_id)s AND user_id = %(user_id)s +RETURNING user_id as id, developer_id; +""" + +# Parse and optimize the query +query = optimize( + parse_one(raw_query), + schema={ + "user_files": {"developer_id": "UUID", "user_id": "UUID"}, + "user_docs": {"developer_id": "UUID", "user_id": "UUID"}, + "users": {"developer_id": "UUID", "user_id": "UUID"}, + }, +).sql(pretty=True) + @rewrap_exceptions( { - psycopg_errors.ForeignKeyViolation: partialclass( + asyncpg.ForeignKeyViolationError: partialclass( HTTPException, status_code=404, detail="The specified developer does not exist.", @@ -24,9 +49,9 @@ @increase_counter("delete_user") @pg_query @beartype -def delete_user_query(*, developer_id: UUID, user_id: UUID) -> tuple[list[str], dict]: +def delete_user(*, developer_id: UUID, user_id: UUID) -> tuple[str, dict]: """ - Constructs optimized SQL queries to delete a user and related data. + Constructs optimized SQL query to delete a user and related data. Uses primary key for efficient deletion. Args: @@ -34,15 +59,7 @@ def delete_user_query(*, developer_id: UUID, user_id: UUID) -> tuple[list[str], user_id (UUID): The user's UUID Returns: - tuple[list[str], dict]: List of SQL queries and parameters + tuple[str, dict]: SQL query and parameters """ - query = parse_one(""" - BEGIN; - DELETE FROM user_files WHERE developer_id = %(developer_id)s AND user_id = %(user_id)s; - DELETE FROM user_docs WHERE developer_id = %(developer_id)s AND user_id = %(user_id)s; - DELETE FROM users WHERE developer_id = %(developer_id)s AND user_id = %(user_id)s - RETURNING user_id as id, developer_id; - COMMIT; - """).sql() - - return [query], {"developer_id": developer_id, "user_id": user_id} + + return query, {"developer_id": developer_id, "user_id": user_id} diff --git a/agents-api/agents_api/queries/users/get_user.py b/agents-api/agents_api/queries/users/get_user.py index bccf70ad2..ca5627701 100644 --- a/agents-api/agents_api/queries/users/get_user.py +++ b/agents-api/agents_api/queries/users/get_user.py @@ -1,19 +1,50 @@ -from typing import Any from uuid import UUID +import asyncpg from beartype import beartype from fastapi import HTTPException -from psycopg import errors as psycopg_errors from sqlglot import parse_one +from sqlglot.optimizer import optimize from ...autogen.openapi_model import User from ...metrics.counters import increase_counter from ..utils import partialclass, pg_query, rewrap_exceptions, wrap_in_class +# Define the raw SQL query outside the function +raw_query = """ +SELECT + user_id as id, + developer_id, + name, + about, + metadata, + created_at, + updated_at +FROM users +WHERE developer_id = %(developer_id)s +AND user_id = %(user_id)s; +""" + +# Parse and optimize the query +query = optimize( + parse_one(raw_query), + schema={ + "users": { + "developer_id": "UUID", + "user_id": "UUID", + "name": "STRING", + "about": "STRING", + "metadata": "JSONB", + "created_at": "TIMESTAMP", + "updated_at": "TIMESTAMP", + } + }, +).sql(pretty=True) + @rewrap_exceptions( { - psycopg_errors.ForeignKeyViolation: partialclass( + asyncpg.ForeignKeyViolationError: partialclass( HTTPException, status_code=404, detail="The specified developer does not exist.", @@ -24,7 +55,7 @@ @increase_counter("get_user") @pg_query @beartype -def get_user_query(*, developer_id: UUID, user_id: UUID) -> tuple[str, dict]: +def get_user(*, developer_id: UUID, user_id: UUID) -> tuple[str, dict]: """ Constructs an optimized SQL query to retrieve a user's details. Uses the primary key index (developer_id, user_id) for efficient lookup. @@ -36,18 +67,5 @@ def get_user_query(*, developer_id: UUID, user_id: UUID) -> tuple[str, dict]: Returns: tuple[str, dict]: SQL query and parameters. """ - query = parse_one(""" - SELECT - user_id as id, - developer_id, - name, - about, - metadata, - created_at, - updated_at - FROM users - WHERE developer_id = %(developer_id)s - AND user_id = %(user_id)s; - """).sql() return query, {"developer_id": developer_id, "user_id": user_id} diff --git a/agents-api/agents_api/queries/users/list_users.py b/agents-api/agents_api/queries/users/list_users.py index 3c8a3690c..e6f854410 100644 --- a/agents-api/agents_api/queries/users/list_users.py +++ b/agents-api/agents_api/queries/users/list_users.py @@ -1,19 +1,54 @@ -from typing import Any, Literal +from typing import Literal from uuid import UUID +import asyncpg from beartype import beartype from fastapi import HTTPException -from psycopg import errors as psycopg_errors -from sqlglot import parse_one +from sqlglot import optimize, parse_one from ...autogen.openapi_model import User from ...metrics.counters import increase_counter from ..utils import partialclass, pg_query, rewrap_exceptions, wrap_in_class +# Define the raw SQL query outside the function +raw_query = """ +SELECT + user_id as id, + developer_id, + name, + about, + metadata, + created_at, + updated_at +FROM users +WHERE developer_id = %(developer_id)s + {metadata_clause} + AND deleted_at IS NULL +ORDER BY {sort_by} {direction} NULLS LAST +LIMIT %(limit)s +OFFSET %(offset)s; +""" + +# Parse and optimize the query +query_template = optimize( + parse_one(raw_query), + schema={ + "users": { + "developer_id": "UUID", + "user_id": "UUID", + "name": "STRING", + "about": "STRING", + "metadata": "JSONB", + "created_at": "TIMESTAMP", + "updated_at": "TIMESTAMP", + } + }, +).sql(pretty=True) + @rewrap_exceptions( { - psycopg_errors.ForeignKeyViolation: partialclass( + asyncpg.ForeignKeyViolationError: partialclass( HTTPException, status_code=404, detail="The specified developer does not exist.", @@ -24,7 +59,7 @@ @increase_counter("list_users") @pg_query @beartype -def list_users_query( +def list_users( *, developer_id: UUID, limit: int = 100, @@ -60,21 +95,8 @@ def list_users_query( metadata_clause = "AND metadata @> %(metadata_filter)s" params["metadata_filter"] = metadata_filter - query = parse_one(f""" - SELECT - user_id as id, - developer_id, - name, - about, - metadata, - created_at, - updated_at - FROM users - WHERE developer_id = %(developer_id)s - {metadata_clause} - ORDER BY {sort_by} {direction} - LIMIT %(limit)s - OFFSET %(offset)s; - """).sql() + query = query_template.format( + metadata_clause=metadata_clause, sort_by=sort_by, direction=direction + ) return query, params diff --git a/agents-api/agents_api/queries/users/patch_user.py b/agents-api/agents_api/queries/users/patch_user.py index 40c6aff4d..d491b8e84 100644 --- a/agents-api/agents_api/queries/users/patch_user.py +++ b/agents-api/agents_api/queries/users/patch_user.py @@ -1,19 +1,51 @@ -from typing import Any from uuid import UUID +import asyncpg from beartype import beartype from fastapi import HTTPException -from psycopg import errors as psycopg_errors from sqlglot import parse_one +from sqlglot.optimizer import optimize from ...autogen.openapi_model import PatchUserRequest, ResourceUpdatedResponse from ...metrics.counters import increase_counter from ..utils import partialclass, pg_query, rewrap_exceptions, wrap_in_class +# Define the raw SQL query outside the function +raw_query = """ +UPDATE users +SET {update_parts} +WHERE developer_id = %(developer_id)s +AND user_id = %(user_id)s +RETURNING + user_id as id, + developer_id, + name, + about, + metadata, + created_at, + updated_at; +""" + +# Parse and optimize the query +query_template = optimize( + parse_one(raw_query), + schema={ + "users": { + "developer_id": "UUID", + "user_id": "UUID", + "name": "STRING", + "about": "STRING", + "metadata": "JSONB", + "created_at": "TIMESTAMP", + "updated_at": "TIMESTAMP", + } + }, +).sql(pretty=True) + @rewrap_exceptions( { - psycopg_errors.ForeignKeyViolation: partialclass( + asyncpg.ForeignKeyViolationError: partialclass( HTTPException, status_code=404, detail="The specified developer does not exist.", @@ -24,7 +56,7 @@ @increase_counter("patch_user") @pg_query @beartype -def patch_user_query( +def patch_user( *, developer_id: UUID, user_id: UUID, data: PatchUserRequest ) -> tuple[str, dict]: """ @@ -55,19 +87,6 @@ def patch_user_query( update_parts.append("metadata = metadata || %(metadata)s") params["metadata"] = data.metadata - query = parse_one(f""" - UPDATE users - SET {", ".join(update_parts)} - WHERE developer_id = %(developer_id)s - AND user_id = %(user_id)s - RETURNING - user_id as id, - developer_id, - name, - about, - metadata, - created_at, - updated_at; - """).sql() + query = query_template.format(update_parts=", ".join(update_parts)) return query, params diff --git a/agents-api/agents_api/queries/users/update_user.py b/agents-api/agents_api/queries/users/update_user.py index 58f7ae8b2..9e622e40d 100644 --- a/agents-api/agents_api/queries/users/update_user.py +++ b/agents-api/agents_api/queries/users/update_user.py @@ -1,19 +1,54 @@ -from typing import Any from uuid import UUID +import asyncpg from beartype import beartype from fastapi import HTTPException -from psycopg import errors as psycopg_errors from sqlglot import parse_one +from sqlglot.optimizer import optimize from ...autogen.openapi_model import ResourceUpdatedResponse, UpdateUserRequest from ...metrics.counters import increase_counter from ..utils import partialclass, pg_query, rewrap_exceptions, wrap_in_class +# Define the raw SQL query outside the function +raw_query = """ +UPDATE users +SET + name = %(name)s, + about = %(about)s, + metadata = %(metadata)s +WHERE developer_id = %(developer_id)s +AND user_id = %(user_id)s +RETURNING + user_id as id, + developer_id, + name, + about, + metadata, + created_at, + updated_at; +""" + +# Parse and optimize the query +query = optimize( + parse_one(raw_query), + schema={ + "users": { + "developer_id": "UUID", + "user_id": "UUID", + "name": "STRING", + "about": "STRING", + "metadata": "JSONB", + "created_at": "TIMESTAMP", + "updated_at": "TIMESTAMP", + } + }, +).sql(pretty=True) + @rewrap_exceptions( { - psycopg_errors.ForeignKeyViolation: partialclass( + asyncpg.ForeignKeyViolationError: partialclass( HTTPException, status_code=404, detail="The specified developer does not exist.", @@ -24,7 +59,7 @@ @increase_counter("update_user") @pg_query @beartype -def update_user_query( +def update_user( *, developer_id: UUID, user_id: UUID, data: UpdateUserRequest ) -> tuple[str, dict]: """ @@ -39,24 +74,6 @@ def update_user_query( Returns: tuple[str, dict]: SQL query and parameters """ - query = parse_one(""" - UPDATE users - SET - name = %(name)s, - about = %(about)s, - metadata = %(metadata)s - WHERE developer_id = %(developer_id)s - AND user_id = %(user_id)s - RETURNING - user_id as id, - developer_id, - name, - about, - metadata, - created_at, - updated_at; - """).sql() - params = { "developer_id": developer_id, "user_id": user_id, diff --git a/agents-api/tests/test_user_sql.py b/agents-api/tests/test_user_sql.py new file mode 100644 index 000000000..50b6d096b --- /dev/null +++ b/agents-api/tests/test_user_sql.py @@ -0,0 +1,178 @@ +""" +This module contains tests for SQL query generation functions in the users module. +Tests verify the SQL queries without actually executing them against a database. +""" + +from uuid import UUID + +from uuid_extensions import uuid7 +from ward import raises, test + +from agents_api.autogen.openapi_model import ( + CreateOrUpdateUserRequest, + CreateUserRequest, + PatchUserRequest, + ResourceUpdatedResponse, + UpdateUserRequest, + User, +) +from agents_api.queries.users import ( + create_or_update_user, + create_user, + delete_user, + get_user, + list_users, + patch_user, + update_user, +) +from tests.fixtures import pg_client, test_developer_id, test_user + +# Test UUIDs for consistent testing +TEST_DEVELOPER_ID = UUID("123e4567-e89b-12d3-a456-426614174000") +TEST_USER_ID = UUID("987e6543-e21b-12d3-a456-426614174000") + + +@test("model: create user sql") +def _(client=pg_client, developer_id=test_developer_id): + """Test that a user can be successfully created.""" + + create_user( + developer_id=developer_id, + data=CreateUserRequest( + name="test user", + about="test user about", + ), + client=client, + ) + + +@test("model: create or update user sql") +def _(client=pg_client, developer_id=test_developer_id): + """Test that a user can be successfully created or updated.""" + + create_or_update_user( + developer_id=developer_id, + user_id=uuid7(), + data=CreateOrUpdateUserRequest( + name="test user", + about="test user about", + ), + client=client, + ) + + +@test("model: update user sql") +def _(client=pg_client, developer_id=test_developer_id, user=test_user): + """Test that an existing user's information can be successfully updated.""" + + # Verify that the 'updated_at' timestamp is greater than the 'created_at' timestamp, indicating a successful update. + update_result = update_user( + user_id=user.id, + developer_id=developer_id, + data=UpdateUserRequest( + name="updated user", + about="updated user about", + ), + client=client, + ) + + assert update_result is not None + assert isinstance(update_result, ResourceUpdatedResponse) + assert update_result.updated_at > user.created_at + + +@test("model: get user not exists sql") +def _(client=pg_client, developer_id=test_developer_id): + """Test that retrieving a non-existent user returns an empty result.""" + + user_id = uuid7() + + # Ensure that the query for an existing user returns exactly one result. + try: + get_user( + user_id=user_id, + developer_id=developer_id, + client=client, + ) + except Exception: + pass + else: + assert ( + False + ), "Expected an exception to be raised when retrieving a non-existent user." + + +@test("model: get user exists sql") +def _(client=pg_client, developer_id=test_developer_id, user=test_user): + """Test that retrieving an existing user returns the correct user information.""" + + result = get_user( + user_id=user.id, + developer_id=developer_id, + client=client, + ) + + assert result is not None + assert isinstance(result, User) + + +@test("model: list users sql") +def _(client=pg_client, developer_id=test_developer_id): + """Test that listing users returns a collection of user information.""" + + result = list_users( + developer_id=developer_id, + client=client, + ) + + assert isinstance(result, list) + assert len(result) >= 1 + assert all(isinstance(user, User) for user in result) + + +@test("model: patch user sql") +def _(client=pg_client, developer_id=test_developer_id, user=test_user): + """Test that a user can be successfully patched.""" + + patch_result = patch_user( + developer_id=developer_id, + user_id=user.id, + data=PatchUserRequest( + name="patched user", + about="patched user about", + metadata={"test": "metadata"}, + ), + client=client, + ) + + assert patch_result is not None + assert isinstance(patch_result, ResourceUpdatedResponse) + assert patch_result.updated_at > user.created_at + + +@test("model: delete user sql") +def _(client=pg_client, developer_id=test_developer_id, user=test_user): + """Test that a user can be successfully deleted.""" + + delete_result = delete_user( + developer_id=developer_id, + user_id=user.id, + client=client, + ) + + assert delete_result is not None + assert isinstance(delete_result, ResourceUpdatedResponse) + + # Verify the user no longer exists + try: + get_user( + developer_id=developer_id, + user_id=user.id, + client=client, + ) + except Exception: + pass + else: + assert ( + False + ), "Expected an exception to be raised when retrieving a deleted user." diff --git a/agents-api/uv.lock b/agents-api/uv.lock index 0c5422f0a..01a1178c4 100644 --- a/agents-api/uv.lock +++ b/agents-api/uv.lock @@ -37,7 +37,6 @@ dependencies = [ { name = "pandas" }, { name = "prometheus-client" }, { name = "prometheus-fastapi-instrumentator" }, - { name = "psycopg" }, { name = "pycozo", extra = ["embedded"] }, { name = "pycozo-async" }, { name = "pydantic", extra = ["email"] }, @@ -107,7 +106,6 @@ requires-dist = [ { name = "pandas", specifier = "~=2.2.2" }, { name = "prometheus-client", specifier = "~=0.21.0" }, { name = "prometheus-fastapi-instrumentator", specifier = "~=7.0.0" }, - { name = "psycopg", specifier = ">=3.2.3" }, { name = "pycozo", extras = ["embedded"], specifier = "~=0.7.6" }, { name = "pycozo-async", specifier = "~=0.7.7" }, { name = "pydantic", extras = ["email"], specifier = "~=2.10.2" }, @@ -2194,19 +2192,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/11/91/87fa6f060e649b1e1a7b19a4f5869709fbf750b7c8c262ee776ec32f3028/psutil-6.1.0-cp37-abi3-win_amd64.whl", hash = "sha256:a8fb3752b491d246034fa4d279ff076501588ce8cbcdbb62c32fd7a377d996be", size = 254228 }, ] -[[package]] -name = "psycopg" -version = "3.2.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, - { name = "tzdata", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d1/ad/7ce016ae63e231575df0498d2395d15f005f05e32d3a2d439038e1bd0851/psycopg-3.2.3.tar.gz", hash = "sha256:a5764f67c27bec8bfac85764d23c534af2c27b893550377e37ce59c12aac47a2", size = 155550 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/21/534b8f5bd9734b7a2fcd3a16b1ee82ef6cad81a4796e95ebf4e0c6a24119/psycopg-3.2.3-py3-none-any.whl", hash = "sha256:644d3973fe26908c73d4be746074f6e5224b03c1101d302d9a53bf565ad64907", size = 197934 }, -] - [[package]] name = "ptyprocess" version = "0.7.0"