From d2dd666fff1769079e934355ca361e3f4d206e5a Mon Sep 17 00:00:00 2001 From: FSGabrsek Date: Thu, 19 Mar 2026 15:25:19 +0100 Subject: [PATCH] initial (1)(1).docx --- = | 0 Makefile | 5 +- backend/Dockerfile | 2 +- backend/api/di/providers.py | 7 +- backend/api/rpc/auction_servicer.py | 138 ++++++---- backend/generated/auction_connect.py | 383 ++++++++++++++++++++++++++ backend/generated/auction_pb2.py | 44 +-- backend/generated/auction_pb2.pyi | 79 ++++++ backend/generated/auction_pb2_grpc.py | 294 -------------------- backend/main.py | 96 +++---- backend/pyproject.toml | 11 +- backend/uv.lock | 159 ++++++----- buf.gen.yaml | 20 ++ buf.yaml | 3 + docker-compose.yml | 2 +- frontend/.gitignore | 3 - frontend/Dockerfile | 6 +- frontend/buf.gen.yaml | 7 - frontend/src/gen/auction_pb.ts | 264 ++++++++++++++++++ resolve | 0 transferring | 0 21 files changed, 998 insertions(+), 525 deletions(-) create mode 100644 = create mode 100644 backend/generated/auction_connect.py create mode 100644 backend/generated/auction_pb2.pyi delete mode 100644 backend/generated/auction_pb2_grpc.py create mode 100644 buf.gen.yaml create mode 100644 buf.yaml delete mode 100644 frontend/buf.gen.yaml create mode 100644 frontend/src/gen/auction_pb.ts create mode 100644 resolve create mode 100644 transferring diff --git a/= b/= new file mode 100644 index 0000000..e69de29 diff --git a/Makefile b/Makefile index e52505c..bcf6114 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: run grpcui +.PHONY: run grpcui generate run: cd backend && uv run python main.py @@ -6,4 +6,7 @@ run: grpcui: grpcui -plaintext localhost:50051 +generate: + buf generate + diff --git a/backend/Dockerfile b/backend/Dockerfile index fa25ad3..d7262be 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -25,7 +25,7 @@ COPY --from=builder /app /app RUN mkdir -p /app/data ENV DB_PATH=/app/data/auction.db -# FastAPI (HTTP) + gRPC ports +# FastAPI (HTTP) + ConnectRPC ports EXPOSE 8000 50051 # Run via the uv-managed venv diff --git a/backend/api/di/providers.py b/backend/api/di/providers.py index aafd42a..f1908aa 100644 --- a/backend/api/di/providers.py +++ b/backend/api/di/providers.py @@ -1,10 +1,13 @@ """ Dishka providers for the auction application. -Scope hierarchy (per gRPC call / HTTP request): +These providers are used by the FastAPI REST API only. The ConnectRPC +service uses manual per-request DI instead of Dishka. + +Scope hierarchy (per HTTP request): APP scope - └── REQUEST scope (one per call/request — like .NET AddScoped or FastAPI Depends) + └── REQUEST scope (one per HTTP request — like .NET AddScoped or FastAPI Depends) ├── aiosqlite.Connection ← opened here, closed on scope exit ├── AuctionRepository ← depends on Connection └── AuctionService ← depends on AuctionRepository diff --git a/backend/api/rpc/auction_servicer.py b/backend/api/rpc/auction_servicer.py index fa20065..ec26b25 100644 --- a/backend/api/rpc/auction_servicer.py +++ b/backend/api/rpc/auction_servicer.py @@ -1,24 +1,25 @@ """ -gRPC servicer for the auction application. +ConnectRPC servicer for the auction application. -Each RPC method is decorated with @inject so that Dishka resolves -FromDishka[AuctionService] from the REQUEST scope that was automatically -opened by DishkaAioInterceptor before the call reaches this handler. +Implements the generated AuctionService Protocol from auction_connect.py. +Each handler creates its own dependency chain manually (no Dishka for +connect-python) — aiosqlite.Connection → AuctionRepository → AuctionService. The flow per call: - 1. DishkaAioInterceptor opens a REQUEST scope on the container. - 2. Dishka creates (in order): Connection → AuctionRepository → AuctionService. - 3. The @inject decorator resolves AuctionService and passes it in. - 4. The handler runs. - 5. DishkaAioInterceptor exits the REQUEST scope → Connection is closed. + 1. ConnectRPC ASGI app dispatches the request to the matching handler. + 2. The handler opens a DB connection, creates the repo + service. + 3. The handler runs the business logic. + 4. The connection is closed via ``async with``. """ -import grpc -from dishka.integrations.grpcio import FromDishka, inject +import aiosqlite +from connectrpc.code import Code +from connectrpc.errors import ConnectError +from connectrpc.request import RequestContext from generated import auction_pb2 -from generated import auction_pb2_grpc from service.auction_service import AuctionService +from service.database import AuctionRepository, DB_PATH from service.models import CreateItemRequest, PlaceBidRequest @@ -40,49 +41,64 @@ def _bid_to_proto(bid) -> auction_pb2.BidResponse: # type: ignore[name-defined] ) -class AuctionServicer(auction_pb2_grpc.AuctionServiceServicer): +async def _make_service() -> tuple[aiosqlite.Connection, AuctionService]: + """Create a fresh Connection → Repository → Service chain.""" + conn = await aiosqlite.connect(DB_PATH) + repo = AuctionRepository(conn) + return conn, AuctionService(repo) + + +class AuctionServicer: """ - gRPC servicer. Contains no state — all dependencies arrive via - Dishka injection on a per-call basis (REQUEST scope). + ConnectRPC servicer implementing the generated ``AuctionService`` Protocol. + + Each method manually creates a per-request dependency chain + (Connection → AuctionRepository → AuctionService) and ensures + the connection is closed when done. """ - # ── Items ────────────────────────────────────────────────────────────────── + # ── Items ────────────────────────────────────────────────────────────── - @inject - async def CreateItem( + async def create_item( self, request: auction_pb2.CreateItemRequest, # type: ignore[name-defined] - context: grpc.aio.ServicerContext, - service: FromDishka[AuctionService], + ctx: RequestContext, ) -> auction_pb2.ItemResponse: # type: ignore[name-defined] - item = await service.create_item( - CreateItemRequest( - title=request.title, - description=request.description, - starting_price=request.starting_price, + conn, service = await _make_service() + try: + item = await service.create_item( + CreateItemRequest( + title=request.title, + description=request.description, + starting_price=request.starting_price, + ) ) - ) - return _item_to_proto(item) + return _item_to_proto(item) + finally: + await conn.close() - @inject - async def ListItems( + async def list_items( self, request: auction_pb2.Empty, # type: ignore[name-defined] - context: grpc.aio.ServicerContext, - service: FromDishka[AuctionService], + ctx: RequestContext, ) -> auction_pb2.ListItemsResponse: # type: ignore[name-defined] - items = await service.list_items() - return auction_pb2.ListItemsResponse(items=[_item_to_proto(i) for i in items]) + conn, service = await _make_service() + try: + items = await service.list_items() + return auction_pb2.ListItemsResponse( + items=[_item_to_proto(i) for i in items] + ) + finally: + await conn.close() - # ── Bids ─────────────────────────────────────────────────────────────────── + # ── Bids ─────────────────────────────────────────────────────────────── - @inject - async def PlaceBid( + async def place_bid( self, request: auction_pb2.PlaceBidRequest, # type: ignore[name-defined] - context: grpc.aio.ServicerContext, - service: FromDishka[AuctionService], + ctx: RequestContext, ) -> auction_pb2.BidResponse: # type: ignore[name-defined] + conn, service = await _make_service() try: bid = await service.place_bid( PlaceBidRequest( @@ -91,33 +107,37 @@ async def PlaceBid( bidder_name=request.bidder_name, ) ) + return _bid_to_proto(bid) except ValueError as exc: - await context.abort(grpc.StatusCode.INVALID_ARGUMENT, str(exc)) - raise # unreachable after abort, but satisfies the type checker - return _bid_to_proto(bid) + raise ConnectError(Code.INVALID_ARGUMENT, str(exc)) + finally: + await conn.close() - @inject - async def ListBids( + async def list_bids( self, request: auction_pb2.ListBidsRequest, # type: ignore[name-defined] - context: grpc.aio.ServicerContext, - service: FromDishka[AuctionService], + ctx: RequestContext, ) -> auction_pb2.ListBidsResponse: # type: ignore[name-defined] - bids = await service.list_bids(request.item_id) - return auction_pb2.ListBidsResponse(bids=[_bid_to_proto(b) for b in bids]) + conn, service = await _make_service() + try: + bids = await service.list_bids(request.item_id) + return auction_pb2.ListBidsResponse(bids=[_bid_to_proto(b) for b in bids]) + finally: + await conn.close() - @inject - async def GetWinningBid( + async def get_winning_bid( self, request: auction_pb2.GetWinningBidRequest, # type: ignore[name-defined] - context: grpc.aio.ServicerContext, - service: FromDishka[AuctionService], + ctx: RequestContext, ) -> auction_pb2.BidResponse: # type: ignore[name-defined] - bid = await service.get_winning_bid(request.item_id) - if bid is None: - await context.abort( - grpc.StatusCode.NOT_FOUND, - f"No bids found for item {request.item_id}", - ) - raise RuntimeError("unreachable") - return _bid_to_proto(bid) + conn, service = await _make_service() + try: + bid = await service.get_winning_bid(request.item_id) + if bid is None: + raise ConnectError( + Code.NOT_FOUND, + f"No bids found for item {request.item_id}", + ) + return _bid_to_proto(bid) + finally: + await conn.close() diff --git a/backend/generated/auction_connect.py b/backend/generated/auction_connect.py new file mode 100644 index 0000000..6368966 --- /dev/null +++ b/backend/generated/auction_connect.py @@ -0,0 +1,383 @@ +# -*- coding: utf-8 -*- +# Generated by https://github.com/connectrpc/connect-python. DO NOT EDIT! +# source: auction.proto + +from collections.abc import AsyncGenerator, AsyncIterator, Iterable, Iterator, Mapping +from typing import Protocol + +from connectrpc.client import ConnectClient, ConnectClientSync +from connectrpc.code import Code +from connectrpc.compression import Compression +from connectrpc.errors import ConnectError +from connectrpc.interceptor import Interceptor, InterceptorSync +from connectrpc.method import IdempotencyLevel, MethodInfo +from connectrpc.request import Headers, RequestContext +from connectrpc.server import ConnectASGIApplication, ConnectWSGIApplication, Endpoint, EndpointSync +import auction_pb2 as auction__pb2 + + +class AuctionService(Protocol): + async def create_item(self, request: auction__pb2.CreateItemRequest, ctx: RequestContext) -> auction__pb2.ItemResponse: + raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + + async def list_items(self, request: auction__pb2.Empty, ctx: RequestContext) -> auction__pb2.ListItemsResponse: + raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + + async def place_bid(self, request: auction__pb2.PlaceBidRequest, ctx: RequestContext) -> auction__pb2.BidResponse: + raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + + async def list_bids(self, request: auction__pb2.ListBidsRequest, ctx: RequestContext) -> auction__pb2.ListBidsResponse: + raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + + async def get_winning_bid(self, request: auction__pb2.GetWinningBidRequest, ctx: RequestContext) -> auction__pb2.BidResponse: + raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + + +class AuctionServiceASGIApplication(ConnectASGIApplication[AuctionService]): + def __init__(self, service: AuctionService | AsyncGenerator[AuctionService], *, interceptors: Iterable[Interceptor]=(), read_max_bytes: int | None = None, compressions: Iterable[Compression] | None = None) -> None: + super().__init__( + service=service, + endpoints=lambda svc: { + "/auction.AuctionService/CreateItem": Endpoint.unary( + method=MethodInfo( + name="CreateItem", + service_name="auction.AuctionService", + input=auction__pb2.CreateItemRequest, + output=auction__pb2.ItemResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=svc.create_item, + ), + "/auction.AuctionService/ListItems": Endpoint.unary( + method=MethodInfo( + name="ListItems", + service_name="auction.AuctionService", + input=auction__pb2.Empty, + output=auction__pb2.ListItemsResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=svc.list_items, + ), + "/auction.AuctionService/PlaceBid": Endpoint.unary( + method=MethodInfo( + name="PlaceBid", + service_name="auction.AuctionService", + input=auction__pb2.PlaceBidRequest, + output=auction__pb2.BidResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=svc.place_bid, + ), + "/auction.AuctionService/ListBids": Endpoint.unary( + method=MethodInfo( + name="ListBids", + service_name="auction.AuctionService", + input=auction__pb2.ListBidsRequest, + output=auction__pb2.ListBidsResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=svc.list_bids, + ), + "/auction.AuctionService/GetWinningBid": Endpoint.unary( + method=MethodInfo( + name="GetWinningBid", + service_name="auction.AuctionService", + input=auction__pb2.GetWinningBidRequest, + output=auction__pb2.BidResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=svc.get_winning_bid, + ), + }, + interceptors=interceptors, + read_max_bytes=read_max_bytes, + compressions=compressions, + ) + + @property + def path(self) -> str: + """Returns the URL path to mount the application to when serving multiple applications.""" + return "/auction.AuctionService" + + +class AuctionServiceClient(ConnectClient): + async def create_item( + self, + request: auction__pb2.CreateItemRequest, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> auction__pb2.ItemResponse: + return await self.execute_unary( + request=request, + method=MethodInfo( + name="CreateItem", + service_name="auction.AuctionService", + input=auction__pb2.CreateItemRequest, + output=auction__pb2.ItemResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + + async def list_items( + self, + request: auction__pb2.Empty, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> auction__pb2.ListItemsResponse: + return await self.execute_unary( + request=request, + method=MethodInfo( + name="ListItems", + service_name="auction.AuctionService", + input=auction__pb2.Empty, + output=auction__pb2.ListItemsResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + + async def place_bid( + self, + request: auction__pb2.PlaceBidRequest, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> auction__pb2.BidResponse: + return await self.execute_unary( + request=request, + method=MethodInfo( + name="PlaceBid", + service_name="auction.AuctionService", + input=auction__pb2.PlaceBidRequest, + output=auction__pb2.BidResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + + async def list_bids( + self, + request: auction__pb2.ListBidsRequest, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> auction__pb2.ListBidsResponse: + return await self.execute_unary( + request=request, + method=MethodInfo( + name="ListBids", + service_name="auction.AuctionService", + input=auction__pb2.ListBidsRequest, + output=auction__pb2.ListBidsResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + + async def get_winning_bid( + self, + request: auction__pb2.GetWinningBidRequest, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> auction__pb2.BidResponse: + return await self.execute_unary( + request=request, + method=MethodInfo( + name="GetWinningBid", + service_name="auction.AuctionService", + input=auction__pb2.GetWinningBidRequest, + output=auction__pb2.BidResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + + +class AuctionServiceSync(Protocol): + def create_item(self, request: auction__pb2.CreateItemRequest, ctx: RequestContext) -> auction__pb2.ItemResponse: + raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + def list_items(self, request: auction__pb2.Empty, ctx: RequestContext) -> auction__pb2.ListItemsResponse: + raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + def place_bid(self, request: auction__pb2.PlaceBidRequest, ctx: RequestContext) -> auction__pb2.BidResponse: + raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + def list_bids(self, request: auction__pb2.ListBidsRequest, ctx: RequestContext) -> auction__pb2.ListBidsResponse: + raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + def get_winning_bid(self, request: auction__pb2.GetWinningBidRequest, ctx: RequestContext) -> auction__pb2.BidResponse: + raise ConnectError(Code.UNIMPLEMENTED, "Not implemented") + + +class AuctionServiceWSGIApplication(ConnectWSGIApplication): + def __init__(self, service: AuctionServiceSync, interceptors: Iterable[InterceptorSync]=(), read_max_bytes: int | None = None, compressions: Iterable[Compression] | None = None) -> None: + super().__init__( + endpoints={ + "/auction.AuctionService/CreateItem": EndpointSync.unary( + method=MethodInfo( + name="CreateItem", + service_name="auction.AuctionService", + input=auction__pb2.CreateItemRequest, + output=auction__pb2.ItemResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=service.create_item, + ), + "/auction.AuctionService/ListItems": EndpointSync.unary( + method=MethodInfo( + name="ListItems", + service_name="auction.AuctionService", + input=auction__pb2.Empty, + output=auction__pb2.ListItemsResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=service.list_items, + ), + "/auction.AuctionService/PlaceBid": EndpointSync.unary( + method=MethodInfo( + name="PlaceBid", + service_name="auction.AuctionService", + input=auction__pb2.PlaceBidRequest, + output=auction__pb2.BidResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=service.place_bid, + ), + "/auction.AuctionService/ListBids": EndpointSync.unary( + method=MethodInfo( + name="ListBids", + service_name="auction.AuctionService", + input=auction__pb2.ListBidsRequest, + output=auction__pb2.ListBidsResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=service.list_bids, + ), + "/auction.AuctionService/GetWinningBid": EndpointSync.unary( + method=MethodInfo( + name="GetWinningBid", + service_name="auction.AuctionService", + input=auction__pb2.GetWinningBidRequest, + output=auction__pb2.BidResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + function=service.get_winning_bid, + ), + }, + interceptors=interceptors, + read_max_bytes=read_max_bytes, + compressions=compressions, + ) + + @property + def path(self) -> str: + """Returns the URL path to mount the application to when serving multiple applications.""" + return "/auction.AuctionService" + + +class AuctionServiceClientSync(ConnectClientSync): + def create_item( + self, + request: auction__pb2.CreateItemRequest, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> auction__pb2.ItemResponse: + return self.execute_unary( + request=request, + method=MethodInfo( + name="CreateItem", + service_name="auction.AuctionService", + input=auction__pb2.CreateItemRequest, + output=auction__pb2.ItemResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + + def list_items( + self, + request: auction__pb2.Empty, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> auction__pb2.ListItemsResponse: + return self.execute_unary( + request=request, + method=MethodInfo( + name="ListItems", + service_name="auction.AuctionService", + input=auction__pb2.Empty, + output=auction__pb2.ListItemsResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + + def place_bid( + self, + request: auction__pb2.PlaceBidRequest, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> auction__pb2.BidResponse: + return self.execute_unary( + request=request, + method=MethodInfo( + name="PlaceBid", + service_name="auction.AuctionService", + input=auction__pb2.PlaceBidRequest, + output=auction__pb2.BidResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + + def list_bids( + self, + request: auction__pb2.ListBidsRequest, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> auction__pb2.ListBidsResponse: + return self.execute_unary( + request=request, + method=MethodInfo( + name="ListBids", + service_name="auction.AuctionService", + input=auction__pb2.ListBidsRequest, + output=auction__pb2.ListBidsResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) + + def get_winning_bid( + self, + request: auction__pb2.GetWinningBidRequest, + *, + headers: Headers | Mapping[str, str] | None = None, + timeout_ms: int | None = None, + ) -> auction__pb2.BidResponse: + return self.execute_unary( + request=request, + method=MethodInfo( + name="GetWinningBid", + service_name="auction.AuctionService", + input=auction__pb2.GetWinningBidRequest, + output=auction__pb2.BidResponse, + idempotency_level=IdempotencyLevel.UNKNOWN, + ), + headers=headers, + timeout_ms=timeout_ms, + ) diff --git a/backend/generated/auction_pb2.py b/backend/generated/auction_pb2.py index 4b47164..2bdde58 100644 --- a/backend/generated/auction_pb2.py +++ b/backend/generated/auction_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: auction.proto -# Protobuf Python Version: 6.31.1 +# Protobuf Python Version: 7.34.0 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -11,9 +11,9 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 6, - 31, - 1, + 7, + 34, + 0, '', 'auction.proto' ) @@ -24,7 +24,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\rauction.proto\x12\x07\x61uction\"\x07\n\x05\x45mpty\"O\n\x11\x43reateItemRequest\x12\r\n\x05title\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x16\n\x0estarting_price\x18\x03 \x01(\x02\"V\n\x0cItemResponse\x12\n\n\x02id\x18\x01 \x01(\x05\x12\r\n\x05title\x18\x02 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\x12\x16\n\x0estarting_price\x18\x04 \x01(\x02\"9\n\x11ListItemsResponse\x12$\n\x05items\x18\x01 \x03(\x0b\x32\x15.auction.ItemResponse\"G\n\x0fPlaceBidRequest\x12\x0f\n\x07item_id\x18\x01 \x01(\x05\x12\x0e\n\x06\x61mount\x18\x02 \x01(\x02\x12\x13\n\x0b\x62idder_name\x18\x03 \x01(\t\"O\n\x0b\x42idResponse\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x0f\n\x07item_id\x18\x02 \x01(\x05\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x02\x12\x13\n\x0b\x62idder_name\x18\x04 \x01(\t\"\"\n\x0fListBidsRequest\x12\x0f\n\x07item_id\x18\x01 \x01(\x05\"6\n\x10ListBidsResponse\x12\"\n\x04\x62ids\x18\x01 \x03(\x0b\x32\x14.auction.BidResponse\"\'\n\x14GetWinningBidRequest\x12\x0f\n\x07item_id\x18\x01 \x01(\x05\x32\xcd\x02\n\x0e\x41uctionService\x12?\n\nCreateItem\x12\x1a.auction.CreateItemRequest\x1a\x15.auction.ItemResponse\x12\x37\n\tListItems\x12\x0e.auction.Empty\x1a\x1a.auction.ListItemsResponse\x12:\n\x08PlaceBid\x12\x18.auction.PlaceBidRequest\x1a\x14.auction.BidResponse\x12?\n\x08ListBids\x12\x18.auction.ListBidsRequest\x1a\x19.auction.ListBidsResponse\x12\x44\n\rGetWinningBid\x12\x1d.auction.GetWinningBidRequest\x1a\x14.auction.BidResponseb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\rauction.proto\x12\x07\x61uction\"\x07\n\x05\x45mpty\"r\n\x11\x43reateItemRequest\x12\x14\n\x05title\x18\x01 \x01(\tR\x05title\x12 \n\x0b\x64\x65scription\x18\x02 \x01(\tR\x0b\x64\x65scription\x12%\n\x0estarting_price\x18\x03 \x01(\x02R\rstartingPrice\"}\n\x0cItemResponse\x12\x0e\n\x02id\x18\x01 \x01(\x05R\x02id\x12\x14\n\x05title\x18\x02 \x01(\tR\x05title\x12 \n\x0b\x64\x65scription\x18\x03 \x01(\tR\x0b\x64\x65scription\x12%\n\x0estarting_price\x18\x04 \x01(\x02R\rstartingPrice\"@\n\x11ListItemsResponse\x12+\n\x05items\x18\x01 \x03(\x0b\x32\x15.auction.ItemResponseR\x05items\"c\n\x0fPlaceBidRequest\x12\x17\n\x07item_id\x18\x01 \x01(\x05R\x06itemId\x12\x16\n\x06\x61mount\x18\x02 \x01(\x02R\x06\x61mount\x12\x1f\n\x0b\x62idder_name\x18\x03 \x01(\tR\nbidderName\"o\n\x0b\x42idResponse\x12\x0e\n\x02id\x18\x01 \x01(\x05R\x02id\x12\x17\n\x07item_id\x18\x02 \x01(\x05R\x06itemId\x12\x16\n\x06\x61mount\x18\x03 \x01(\x02R\x06\x61mount\x12\x1f\n\x0b\x62idder_name\x18\x04 \x01(\tR\nbidderName\"*\n\x0fListBidsRequest\x12\x17\n\x07item_id\x18\x01 \x01(\x05R\x06itemId\"<\n\x10ListBidsResponse\x12(\n\x04\x62ids\x18\x01 \x03(\x0b\x32\x14.auction.BidResponseR\x04\x62ids\"/\n\x14GetWinningBidRequest\x12\x17\n\x07item_id\x18\x01 \x01(\x05R\x06itemId2\xcd\x02\n\x0e\x41uctionService\x12?\n\nCreateItem\x12\x1a.auction.CreateItemRequest\x1a\x15.auction.ItemResponse\x12\x37\n\tListItems\x12\x0e.auction.Empty\x1a\x1a.auction.ListItemsResponse\x12:\n\x08PlaceBid\x12\x18.auction.PlaceBidRequest\x1a\x14.auction.BidResponse\x12?\n\x08ListBids\x12\x18.auction.ListBidsRequest\x1a\x19.auction.ListBidsResponse\x12\x44\n\rGetWinningBid\x12\x1d.auction.GetWinningBidRequest\x1a\x14.auction.BidResponseb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -34,21 +34,21 @@ _globals['_EMPTY']._serialized_start=26 _globals['_EMPTY']._serialized_end=33 _globals['_CREATEITEMREQUEST']._serialized_start=35 - _globals['_CREATEITEMREQUEST']._serialized_end=114 - _globals['_ITEMRESPONSE']._serialized_start=116 - _globals['_ITEMRESPONSE']._serialized_end=202 - _globals['_LISTITEMSRESPONSE']._serialized_start=204 - _globals['_LISTITEMSRESPONSE']._serialized_end=261 - _globals['_PLACEBIDREQUEST']._serialized_start=263 - _globals['_PLACEBIDREQUEST']._serialized_end=334 - _globals['_BIDRESPONSE']._serialized_start=336 - _globals['_BIDRESPONSE']._serialized_end=415 - _globals['_LISTBIDSREQUEST']._serialized_start=417 - _globals['_LISTBIDSREQUEST']._serialized_end=451 - _globals['_LISTBIDSRESPONSE']._serialized_start=453 - _globals['_LISTBIDSRESPONSE']._serialized_end=507 - _globals['_GETWINNINGBIDREQUEST']._serialized_start=509 - _globals['_GETWINNINGBIDREQUEST']._serialized_end=548 - _globals['_AUCTIONSERVICE']._serialized_start=551 - _globals['_AUCTIONSERVICE']._serialized_end=884 + _globals['_CREATEITEMREQUEST']._serialized_end=149 + _globals['_ITEMRESPONSE']._serialized_start=151 + _globals['_ITEMRESPONSE']._serialized_end=276 + _globals['_LISTITEMSRESPONSE']._serialized_start=278 + _globals['_LISTITEMSRESPONSE']._serialized_end=342 + _globals['_PLACEBIDREQUEST']._serialized_start=344 + _globals['_PLACEBIDREQUEST']._serialized_end=443 + _globals['_BIDRESPONSE']._serialized_start=445 + _globals['_BIDRESPONSE']._serialized_end=556 + _globals['_LISTBIDSREQUEST']._serialized_start=558 + _globals['_LISTBIDSREQUEST']._serialized_end=600 + _globals['_LISTBIDSRESPONSE']._serialized_start=602 + _globals['_LISTBIDSRESPONSE']._serialized_end=662 + _globals['_GETWINNINGBIDREQUEST']._serialized_start=664 + _globals['_GETWINNINGBIDREQUEST']._serialized_end=711 + _globals['_AUCTIONSERVICE']._serialized_start=714 + _globals['_AUCTIONSERVICE']._serialized_end=1047 # @@protoc_insertion_point(module_scope) diff --git a/backend/generated/auction_pb2.pyi b/backend/generated/auction_pb2.pyi new file mode 100644 index 0000000..435b440 --- /dev/null +++ b/backend/generated/auction_pb2.pyi @@ -0,0 +1,79 @@ +from google.protobuf.internal import containers as _containers +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class Empty(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class CreateItemRequest(_message.Message): + __slots__ = ("title", "description", "starting_price") + TITLE_FIELD_NUMBER: _ClassVar[int] + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + STARTING_PRICE_FIELD_NUMBER: _ClassVar[int] + title: str + description: str + starting_price: float + def __init__(self, title: _Optional[str] = ..., description: _Optional[str] = ..., starting_price: _Optional[float] = ...) -> None: ... + +class ItemResponse(_message.Message): + __slots__ = ("id", "title", "description", "starting_price") + ID_FIELD_NUMBER: _ClassVar[int] + TITLE_FIELD_NUMBER: _ClassVar[int] + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + STARTING_PRICE_FIELD_NUMBER: _ClassVar[int] + id: int + title: str + description: str + starting_price: float + def __init__(self, id: _Optional[int] = ..., title: _Optional[str] = ..., description: _Optional[str] = ..., starting_price: _Optional[float] = ...) -> None: ... + +class ListItemsResponse(_message.Message): + __slots__ = ("items",) + ITEMS_FIELD_NUMBER: _ClassVar[int] + items: _containers.RepeatedCompositeFieldContainer[ItemResponse] + def __init__(self, items: _Optional[_Iterable[_Union[ItemResponse, _Mapping]]] = ...) -> None: ... + +class PlaceBidRequest(_message.Message): + __slots__ = ("item_id", "amount", "bidder_name") + ITEM_ID_FIELD_NUMBER: _ClassVar[int] + AMOUNT_FIELD_NUMBER: _ClassVar[int] + BIDDER_NAME_FIELD_NUMBER: _ClassVar[int] + item_id: int + amount: float + bidder_name: str + def __init__(self, item_id: _Optional[int] = ..., amount: _Optional[float] = ..., bidder_name: _Optional[str] = ...) -> None: ... + +class BidResponse(_message.Message): + __slots__ = ("id", "item_id", "amount", "bidder_name") + ID_FIELD_NUMBER: _ClassVar[int] + ITEM_ID_FIELD_NUMBER: _ClassVar[int] + AMOUNT_FIELD_NUMBER: _ClassVar[int] + BIDDER_NAME_FIELD_NUMBER: _ClassVar[int] + id: int + item_id: int + amount: float + bidder_name: str + def __init__(self, id: _Optional[int] = ..., item_id: _Optional[int] = ..., amount: _Optional[float] = ..., bidder_name: _Optional[str] = ...) -> None: ... + +class ListBidsRequest(_message.Message): + __slots__ = ("item_id",) + ITEM_ID_FIELD_NUMBER: _ClassVar[int] + item_id: int + def __init__(self, item_id: _Optional[int] = ...) -> None: ... + +class ListBidsResponse(_message.Message): + __slots__ = ("bids",) + BIDS_FIELD_NUMBER: _ClassVar[int] + bids: _containers.RepeatedCompositeFieldContainer[BidResponse] + def __init__(self, bids: _Optional[_Iterable[_Union[BidResponse, _Mapping]]] = ...) -> None: ... + +class GetWinningBidRequest(_message.Message): + __slots__ = ("item_id",) + ITEM_ID_FIELD_NUMBER: _ClassVar[int] + item_id: int + def __init__(self, item_id: _Optional[int] = ...) -> None: ... diff --git a/backend/generated/auction_pb2_grpc.py b/backend/generated/auction_pb2_grpc.py deleted file mode 100644 index c997ffe..0000000 --- a/backend/generated/auction_pb2_grpc.py +++ /dev/null @@ -1,294 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" - -import grpc -import warnings - -from generated import auction_pb2 as auction__pb2 - -GRPC_GENERATED_VERSION = "1.78.0" -GRPC_VERSION = grpc.__version__ -_version_not_supported = False - -try: - from grpc._utilities import first_version_is_lower - - _version_not_supported = first_version_is_lower( - GRPC_VERSION, GRPC_GENERATED_VERSION - ) -except ImportError: - _version_not_supported = True - -if _version_not_supported: - raise RuntimeError( - f"The grpc package installed is at version {GRPC_VERSION}," - + " but the generated code in auction_pb2_grpc.py depends on" - + f" grpcio>={GRPC_GENERATED_VERSION}." - + f" Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}" - + f" or downgrade your generated code using grpcio-tools<={GRPC_VERSION}." - ) - - -class AuctionServiceStub(object): - """── Service definition ────────────────────────────────────────────────────────""" - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.CreateItem = channel.unary_unary( - "/auction.AuctionService/CreateItem", - request_serializer=auction__pb2.CreateItemRequest.SerializeToString, - response_deserializer=auction__pb2.ItemResponse.FromString, - _registered_method=True, - ) - self.ListItems = channel.unary_unary( - "/auction.AuctionService/ListItems", - request_serializer=auction__pb2.Empty.SerializeToString, - response_deserializer=auction__pb2.ListItemsResponse.FromString, - _registered_method=True, - ) - self.PlaceBid = channel.unary_unary( - "/auction.AuctionService/PlaceBid", - request_serializer=auction__pb2.PlaceBidRequest.SerializeToString, - response_deserializer=auction__pb2.BidResponse.FromString, - _registered_method=True, - ) - self.ListBids = channel.unary_unary( - "/auction.AuctionService/ListBids", - request_serializer=auction__pb2.ListBidsRequest.SerializeToString, - response_deserializer=auction__pb2.ListBidsResponse.FromString, - _registered_method=True, - ) - self.GetWinningBid = channel.unary_unary( - "/auction.AuctionService/GetWinningBid", - request_serializer=auction__pb2.GetWinningBidRequest.SerializeToString, - response_deserializer=auction__pb2.BidResponse.FromString, - _registered_method=True, - ) - - -class AuctionServiceServicer(object): - """── Service definition ────────────────────────────────────────────────────────""" - - def CreateItem(self, request, context): - """Items""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details("Method not implemented!") - raise NotImplementedError("Method not implemented!") - - def ListItems(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details("Method not implemented!") - raise NotImplementedError("Method not implemented!") - - def PlaceBid(self, request, context): - """Bids""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details("Method not implemented!") - raise NotImplementedError("Method not implemented!") - - def ListBids(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details("Method not implemented!") - raise NotImplementedError("Method not implemented!") - - def GetWinningBid(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details("Method not implemented!") - raise NotImplementedError("Method not implemented!") - - -def add_AuctionServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - "CreateItem": grpc.unary_unary_rpc_method_handler( - servicer.CreateItem, - request_deserializer=auction__pb2.CreateItemRequest.FromString, - response_serializer=auction__pb2.ItemResponse.SerializeToString, - ), - "ListItems": grpc.unary_unary_rpc_method_handler( - servicer.ListItems, - request_deserializer=auction__pb2.Empty.FromString, - response_serializer=auction__pb2.ListItemsResponse.SerializeToString, - ), - "PlaceBid": grpc.unary_unary_rpc_method_handler( - servicer.PlaceBid, - request_deserializer=auction__pb2.PlaceBidRequest.FromString, - response_serializer=auction__pb2.BidResponse.SerializeToString, - ), - "ListBids": grpc.unary_unary_rpc_method_handler( - servicer.ListBids, - request_deserializer=auction__pb2.ListBidsRequest.FromString, - response_serializer=auction__pb2.ListBidsResponse.SerializeToString, - ), - "GetWinningBid": grpc.unary_unary_rpc_method_handler( - servicer.GetWinningBid, - request_deserializer=auction__pb2.GetWinningBidRequest.FromString, - response_serializer=auction__pb2.BidResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - "auction.AuctionService", rpc_method_handlers - ) - server.add_generic_rpc_handlers((generic_handler,)) - server.add_registered_method_handlers("auction.AuctionService", rpc_method_handlers) - - -# This class is part of an EXPERIMENTAL API. -class AuctionService(object): - """── Service definition ────────────────────────────────────────────────────────""" - - @staticmethod - def CreateItem( - request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None, - ): - return grpc.experimental.unary_unary( - request, - target, - "/auction.AuctionService/CreateItem", - auction__pb2.CreateItemRequest.SerializeToString, - auction__pb2.ItemResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True, - ) - - @staticmethod - def ListItems( - request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None, - ): - return grpc.experimental.unary_unary( - request, - target, - "/auction.AuctionService/ListItems", - auction__pb2.Empty.SerializeToString, - auction__pb2.ListItemsResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True, - ) - - @staticmethod - def PlaceBid( - request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None, - ): - return grpc.experimental.unary_unary( - request, - target, - "/auction.AuctionService/PlaceBid", - auction__pb2.PlaceBidRequest.SerializeToString, - auction__pb2.BidResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True, - ) - - @staticmethod - def ListBids( - request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None, - ): - return grpc.experimental.unary_unary( - request, - target, - "/auction.AuctionService/ListBids", - auction__pb2.ListBidsRequest.SerializeToString, - auction__pb2.ListBidsResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True, - ) - - @staticmethod - def GetWinningBid( - request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None, - ): - return grpc.experimental.unary_unary( - request, - target, - "/auction.AuctionService/GetWinningBid", - auction__pb2.GetWinningBidRequest.SerializeToString, - auction__pb2.BidResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True, - ) diff --git a/backend/main.py b/backend/main.py index 6158e5e..f2e1517 100644 --- a/backend/main.py +++ b/backend/main.py @@ -2,17 +2,18 @@ Application entrypoint. Starts both servers concurrently inside a single asyncio event loop: - - FastAPI on http://0.0.0.0:8000 (REST, OpenAPI docs at /docs) - - gRPC on 0.0.0.0:50051 + - FastAPI on http://0.0.0.0:8000 (REST, OpenAPI docs at /docs) + - ConnectRPC on http://0.0.0.0:50051 (Connect protocol over HTTP) -A single dishka AsyncContainer is shared between the two servers so that -APP-scoped dependencies are created once. Each HTTP request and each gRPC -call independently enters REQUEST scope, gets its own DB connection, and -closes it on exit. +The ConnectRPC service uses manual per-request DI (no Dishka) — +each handler creates its own Connection → AuctionRepository → AuctionService. -Container scope per request: +The FastAPI app still uses Dishka for DI, but without GrpcioProvider +(which is no longer needed since we dropped grpcio). + +Container scope per HTTP request: APP (whole process lifetime) - └── REQUEST (per HTTP call / per gRPC call) + └── REQUEST (per HTTP call) ├── aiosqlite.Connection — opened & closed by dishka ├── AuctionRepository └── AuctionService @@ -20,20 +21,24 @@ import asyncio import logging +import os +import sys from contextlib import asynccontextmanager -import grpc import uvicorn from dishka import make_async_container from dishka.integrations.fastapi import FastapiProvider, setup_dishka -from dishka.integrations.grpcio import DishkaAioInterceptor, GrpcioProvider from fastapi import FastAPI -from grpc_reflection.v1alpha import reflection + +# The generated auction_connect.py uses `import auction_pb2` (bare module name) +# because protoc-gen-connect-python generates imports relative to its output dir. +# We add the `generated` directory to sys.path so that bare import resolves. +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "generated")) from api.di.providers import DatabaseProvider, ServiceProvider from api.http.routes import router as auction_router from api.rpc.auction_servicer import AuctionServicer -from generated import auction_pb2, auction_pb2_grpc +from generated.auction_connect import AuctionServiceASGIApplication from service.database import init_db logging.basicConfig( @@ -42,54 +47,29 @@ ) log = logging.getLogger(__name__) -GRPC_HOST = "0.0.0.0" -GRPC_PORT = 50051 +CONNECT_HOST = "0.0.0.0" +CONNECT_PORT = 50051 HTTP_HOST = "0.0.0.0" HTTP_PORT = 8000 -# ── gRPC server ─────────────────────────────────────────────────────────────── - - -class SafeDishkaAioInterceptor(DishkaAioInterceptor): - """ - Thin guard around DishkaAioInterceptor that skips injection for handlers - that aren't managed by dishka (e.g. the gRPC reflection service). - - The upstream interceptor assumes intercept_service() always returns a - non-None RpcMethodHandler, but reflection's streaming handler can return - None for unmatched methods, causing an AttributeError on 'unary_unary'. - """ - - async def intercept_service(self, continuation, handler_call_details): - handler = await continuation(handler_call_details) - if handler is None: - return handler +# ── ConnectRPC server ───────────────────────────────────────────────────────── - async def passthrough(_): - return handler - return await super().intercept_service(passthrough, handler_call_details) +async def run_connect() -> None: + """Run the ConnectRPC ASGI application on port 50051 via uvicorn.""" + servicer = AuctionServicer() + connect_app = AuctionServiceASGIApplication(servicer) - -async def run_grpc(container) -> None: - server = grpc.aio.server( - interceptors=[SafeDishkaAioInterceptor(container)], - ) - auction_pb2_grpc.add_AuctionServiceServicer_to_server(AuctionServicer(), server) - - # Server reflection — lets JetBrains / grpcurl discover services without a .proto file - service_names = ( - auction_pb2.DESCRIPTOR.services_by_name["AuctionService"].full_name, - reflection.SERVICE_NAME, + config = uvicorn.Config( + app=connect_app, + host=CONNECT_HOST, + port=CONNECT_PORT, + log_level="info", ) - reflection.enable_server_reflection(service_names, server) - - listen_addr = f"{GRPC_HOST}:{GRPC_PORT}" - server.add_insecure_port(listen_addr) - log.info("gRPC server starting on %s", listen_addr) - await server.start() - await server.wait_for_termination() + server = uvicorn.Server(config) + log.info("ConnectRPC server starting on http://%s:%d", CONNECT_HOST, CONNECT_PORT) + await server.serve() # ── FastAPI app factory ─────────────────────────────────────────────────────── @@ -104,7 +84,7 @@ async def lifespan(app: FastAPI): app = FastAPI( title="Auction API", - description="Mock auction backend — gRPC + FastAPI with dishka DI", + description="Mock auction backend — ConnectRPC + FastAPI with dishka DI", version="0.1.0", lifespan=lifespan, ) @@ -121,19 +101,17 @@ async def main() -> None: log.info("Initialising database...") await init_db() - # Build the shared DI container. - # GrpcioProvider — lets grpc.ServicerContext / Message be used in providers. + # Build the shared DI container (for FastAPI only — ConnectRPC uses manual DI). # FastapiProvider — lets fastapi.Request be used in providers. container = make_async_container( DatabaseProvider(), ServiceProvider(), - GrpcioProvider(), FastapiProvider(), ) fastapi_app = create_fastapi_app(container) - # Configure uvicorn programmatically so we can run it alongside gRPC. + # Configure uvicorn programmatically so we can run it alongside ConnectRPC. uvicorn_config = uvicorn.Config( app=fastapi_app, host=HTTP_HOST, @@ -143,12 +121,12 @@ async def main() -> None: uvicorn_server = uvicorn.Server(uvicorn_config) log.info("Starting FastAPI on http://%s:%d", HTTP_HOST, HTTP_PORT) - log.info("Starting gRPC on %s:%d", GRPC_HOST, GRPC_PORT) + log.info("Starting ConnectRPC on http://%s:%d", CONNECT_HOST, CONNECT_PORT) # Run both servers concurrently; either one shutting down ends the process. await asyncio.gather( uvicorn_server.serve(), - run_grpc(container), + run_connect(), ) diff --git a/backend/pyproject.toml b/backend/pyproject.toml index cd18469..aa47c94 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -5,12 +5,15 @@ description = "Add your description here" requires-python = ">=3.14" dependencies = [ "aiosqlite>=0.22.1", + "connect-python>=0.9.0", "dishka>=1.9.1", "fastapi>=0.135.1", - "grpcio>=1.78.0", - "grpcio-reflection>=1.78.0", - "grpcio-tools>=1.78.0", - "protobuf>=6.33.5", + "protobuf>=7.34.0", "pydantic>=2.12.5", "uvicorn>=0.42.0", ] + +[dependency-groups] +dev = [ + "protoc-gen-connect-python>=0.9.0", +] diff --git a/backend/uv.lock b/backend/uv.lock index 371749f..08a0bca 100644 --- a/backend/uv.lock +++ b/backend/uv.lock @@ -62,6 +62,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "connect-python" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, + { name = "pyqwest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/fc/0e4798c53e2754f5de36ecf4d198706cb23711d603df6c008f6e7b5b21ae/connect_python-0.9.0.tar.gz", hash = "sha256:a188ec843b0f5953b7e1b88061af50ad91c9aaa2e982d7a89a63ae5c1fff932e", size = 46094, upload-time = "2026-03-19T02:40:42.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/15/5b42df2d9d34e5103f2b69e4f6a4aeb47c52589eaac8d53eb5b0a40eabaa/connect_python-0.9.0-py3-none-any.whl", hash = "sha256:896171fa7236d4e1557e3f7eee76daa8c9dd762f2c21662515f2060f1b542574", size = 63381, upload-time = "2026-03-19T02:40:40.743Z" }, +] + [[package]] name = "dishka" version = "1.9.1" @@ -93,117 +106,102 @@ version = "0.1.0" source = { virtual = "." } dependencies = [ { name = "aiosqlite" }, + { name = "connect-python" }, { name = "dishka" }, { name = "fastapi" }, - { name = "grpcio" }, - { name = "grpcio-reflection" }, - { name = "grpcio-tools" }, { name = "protobuf" }, { name = "pydantic" }, { name = "uvicorn" }, ] +[package.dev-dependencies] +dev = [ + { name = "protoc-gen-connect-python" }, +] + [package.metadata] requires-dist = [ { name = "aiosqlite", specifier = ">=0.22.1" }, + { name = "connect-python", specifier = ">=0.9.0" }, { name = "dishka", specifier = ">=1.9.1" }, { name = "fastapi", specifier = ">=0.135.1" }, - { name = "grpcio", specifier = ">=1.78.0" }, - { name = "grpcio-reflection", specifier = ">=1.78.0" }, - { name = "grpcio-tools", specifier = ">=1.78.0" }, - { name = "protobuf", specifier = ">=6.33.5" }, + { name = "protobuf", specifier = ">=7.34.0" }, { name = "pydantic", specifier = ">=2.12.5" }, { name = "uvicorn", specifier = ">=0.42.0" }, ] +[package.metadata.requires-dev] +dev = [{ name = "protoc-gen-connect-python", specifier = ">=0.9.0" }] + [[package]] -name = "grpcio" -version = "1.78.0" +name = "h11" +version = "0.16.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/06/8a/3d098f35c143a89520e568e6539cc098fcd294495910e359889ce8741c84/grpcio-1.78.0.tar.gz", hash = "sha256:7382b95189546f375c174f53a5fa873cef91c4b8005faa05cc5b3beea9c4f1c5", size = 12852416, upload-time = "2026-02-06T09:57:18.093Z" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/f2/b56e43e3c968bfe822fa6ce5bca10d5c723aa40875b48791ce1029bb78c7/grpcio-1.78.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:e87cbc002b6f440482b3519e36e1313eb5443e9e9e73d6a52d43bd2004fcfd8e", size = 5920591, upload-time = "2026-02-06T09:56:20.758Z" }, - { url = "https://files.pythonhosted.org/packages/5d/81/1f3b65bd30c334167bfa8b0d23300a44e2725ce39bba5b76a2460d85f745/grpcio-1.78.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:c41bc64626db62e72afec66b0c8a0da76491510015417c127bfc53b2fe6d7f7f", size = 11813685, upload-time = "2026-02-06T09:56:24.315Z" }, - { url = "https://files.pythonhosted.org/packages/0e/1c/bbe2f8216a5bd3036119c544d63c2e592bdf4a8ec6e4a1867592f4586b26/grpcio-1.78.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8dfffba826efcf366b1e3ccc37e67afe676f290e13a3b48d31a46739f80a8724", size = 6487803, upload-time = "2026-02-06T09:56:27.367Z" }, - { url = "https://files.pythonhosted.org/packages/16/5c/a6b2419723ea7ddce6308259a55e8e7593d88464ce8db9f4aa857aba96fa/grpcio-1.78.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:74be1268d1439eaaf552c698cdb11cd594f0c49295ae6bb72c34ee31abbe611b", size = 7173206, upload-time = "2026-02-06T09:56:29.876Z" }, - { url = "https://files.pythonhosted.org/packages/df/1e/b8801345629a415ea7e26c83d75eb5dbe91b07ffe5210cc517348a8d4218/grpcio-1.78.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be63c88b32e6c0f1429f1398ca5c09bc64b0d80950c8bb7807d7d7fb36fb84c7", size = 6693826, upload-time = "2026-02-06T09:56:32.305Z" }, - { url = "https://files.pythonhosted.org/packages/34/84/0de28eac0377742679a510784f049738a80424b17287739fc47d63c2439e/grpcio-1.78.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3c586ac70e855c721bda8f548d38c3ca66ac791dc49b66a8281a1f99db85e452", size = 7277897, upload-time = "2026-02-06T09:56:34.915Z" }, - { url = "https://files.pythonhosted.org/packages/ca/9c/ad8685cfe20559a9edb66f735afdcb2b7d3de69b13666fdfc542e1916ebd/grpcio-1.78.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:35eb275bf1751d2ffbd8f57cdbc46058e857cf3971041521b78b7db94bdaf127", size = 8252404, upload-time = "2026-02-06T09:56:37.553Z" }, - { url = "https://files.pythonhosted.org/packages/3c/05/33a7a4985586f27e1de4803887c417ec7ced145ebd069bc38a9607059e2b/grpcio-1.78.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:207db540302c884b8848036b80db352a832b99dfdf41db1eb554c2c2c7800f65", size = 7696837, upload-time = "2026-02-06T09:56:40.173Z" }, - { url = "https://files.pythonhosted.org/packages/73/77/7382241caf88729b106e49e7d18e3116216c778e6a7e833826eb96de22f7/grpcio-1.78.0-cp314-cp314-win32.whl", hash = "sha256:57bab6deef2f4f1ca76cc04565df38dc5713ae6c17de690721bdf30cb1e0545c", size = 4142439, upload-time = "2026-02-06T09:56:43.258Z" }, - { url = "https://files.pythonhosted.org/packages/48/b2/b096ccce418882fbfda4f7496f9357aaa9a5af1896a9a7f60d9f2b275a06/grpcio-1.78.0-cp314-cp314-win_amd64.whl", hash = "sha256:dce09d6116df20a96acfdbf85e4866258c3758180e8c49845d6ba8248b6d0bbb", size = 4929852, upload-time = "2026-02-06T09:56:45.885Z" }, + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] [[package]] -name = "grpcio-reflection" -version = "1.78.0" +name = "idna" +version = "3.11" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "grpcio" }, - { name = "protobuf" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/31/06/337546aae558675f79cae2a8c1ce0c9b1952cbc5c28b01878f68d040f5bb/grpcio_reflection-1.78.0.tar.gz", hash = "sha256:e6e60c0b85dbcdf963b4d4d150c0f1d238ba891d805b575c52c0365d07fc0c40", size = 19098, upload-time = "2026-02-06T10:01:52.225Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/df/6d/4d095d27ccd049865ecdafc467754e9e47ad0f677a30dda969c3590f6582/grpcio_reflection-1.78.0-py3-none-any.whl", hash = "sha256:06fcfde9e6888cdd12e9dd1cf6dc7c440c2e9acf420f696ccbe008672ed05b60", size = 22800, upload-time = "2026-02-06T10:01:33.822Z" }, + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, ] [[package]] -name = "grpcio-tools" -version = "1.78.0" +name = "importlib-metadata" +version = "8.7.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "grpcio" }, - { name = "protobuf" }, - { name = "setuptools" }, + { name = "zipp" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8b/d1/cbefe328653f746fd319c4377836a25ba64226e41c6a1d7d5cdbc87a459f/grpcio_tools-1.78.0.tar.gz", hash = "sha256:4b0dd86560274316e155d925158276f8564508193088bc43e20d3f5dff956b2b", size = 5393026, upload-time = "2026-02-06T09:59:59.53Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/5e/278f3831c8d56bae02e3acc570465648eccf0a6bbedcb1733789ac966803/grpcio_tools-1.78.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:8b080d0d072e6032708a3a91731b808074d7ab02ca8fb9847b6a011fdce64cd9", size = 2546270, upload-time = "2026-02-06T09:59:07.426Z" }, - { url = "https://files.pythonhosted.org/packages/a3/d9/68582f2952b914b60dddc18a2e3f9c6f09af9372b6f6120d6cf3ec7f8b4e/grpcio_tools-1.78.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:8c0ad8f8f133145cd7008b49cb611a5c6a9d89ab276c28afa17050516e801f79", size = 5705731, upload-time = "2026-02-06T09:59:09.856Z" }, - { url = "https://files.pythonhosted.org/packages/70/68/feb0f9a48818ee1df1e8b644069379a1e6ef5447b9b347c24e96fd258e5d/grpcio_tools-1.78.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2f8ea092a7de74c6359335d36f0674d939a3c7e1a550f4c2c9e80e0226de8fe4", size = 2593896, upload-time = "2026-02-06T09:59:12.23Z" }, - { url = "https://files.pythonhosted.org/packages/1f/08/a430d8d06e1b8d33f3e48d3f0cc28236723af2f35e37bd5c8db05df6c3aa/grpcio_tools-1.78.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:da422985e0cac822b41822f43429c19ecb27c81ffe3126d0b74e77edec452608", size = 2905298, upload-time = "2026-02-06T09:59:14.458Z" }, - { url = "https://files.pythonhosted.org/packages/71/0a/348c36a3eae101ca0c090c9c3bc96f2179adf59ee0c9262d11cdc7bfe7db/grpcio_tools-1.78.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4fab1faa3fbcb246263e68da7a8177d73772283f9db063fb8008517480888d26", size = 2656186, upload-time = "2026-02-06T09:59:16.949Z" }, - { url = "https://files.pythonhosted.org/packages/1d/3f/18219f331536fad4af6207ade04142292faa77b5cb4f4463787988963df8/grpcio_tools-1.78.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dd9c094f73f734becae3f20f27d4944d3cd8fb68db7338ee6c58e62fc5c3d99f", size = 3109859, upload-time = "2026-02-06T09:59:19.202Z" }, - { url = "https://files.pythonhosted.org/packages/5b/d9/341ea20a44c8e5a3a18acc820b65014c2e3ea5b4f32a53d14864bcd236bc/grpcio_tools-1.78.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:2ed51ce6b833068f6c580b73193fc2ec16468e6bc18354bc2f83a58721195a58", size = 3657915, upload-time = "2026-02-06T09:59:21.839Z" }, - { url = "https://files.pythonhosted.org/packages/fb/f4/5978b0f91611a64371424c109dd0027b247e5b39260abad2eaee66b6aa37/grpcio_tools-1.78.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:05803a5cdafe77c8bdf36aa660ad7a6a1d9e49bc59ce45c1bade2a4698826599", size = 3324724, upload-time = "2026-02-06T09:59:24.402Z" }, - { url = "https://files.pythonhosted.org/packages/b2/80/96a324dba99cfbd20e291baf0b0ae719dbb62b76178c5ce6c788e7331cb1/grpcio_tools-1.78.0-cp314-cp314-win32.whl", hash = "sha256:f7c722e9ce6f11149ac5bddd5056e70aaccfd8168e74e9d34d8b8b588c3f5c7c", size = 1015505, upload-time = "2026-02-06T09:59:26.3Z" }, - { url = "https://files.pythonhosted.org/packages/3b/d1/909e6a05bfd44d46327dc4b8a78beb2bae4fb245ffab2772e350081aaf7e/grpcio_tools-1.78.0-cp314-cp314-win_amd64.whl", hash = "sha256:7d58ade518b546120ec8f0a8e006fc8076ae5df151250ebd7e82e9b5e152c229", size = 1190196, upload-time = "2026-02-06T09:59:28.359Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, ] [[package]] -name = "h11" -version = "0.16.0" +name = "opentelemetry-api" +version = "1.40.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +dependencies = [ + { name = "importlib-metadata" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, + { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, ] [[package]] -name = "idna" -version = "3.11" +name = "protobuf" +version = "7.34.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/00/04a2ab36b70a52d0356852979e08b44edde0435f2115dc66e25f2100f3ab/protobuf-7.34.0.tar.gz", hash = "sha256:3871a3df67c710aaf7bb8d214cc997342e63ceebd940c8c7fc65c9b3d697591a", size = 454726, upload-time = "2026-02-27T00:30:25.421Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, + { url = "https://files.pythonhosted.org/packages/13/c4/6322ab5c8f279c4c358bc14eb8aefc0550b97222a39f04eb3c1af7a830fa/protobuf-7.34.0-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:8e329966799f2c271d5e05e236459fe1cbfdb8755aaa3b0914fa60947ddea408", size = 429248, upload-time = "2026-02-27T00:30:14.924Z" }, + { url = "https://files.pythonhosted.org/packages/45/99/b029bbbc61e8937545da5b79aa405ab2d9cf307a728f8c9459ad60d7a481/protobuf-7.34.0-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:9d7a5005fb96f3c1e64f397f91500b0eb371b28da81296ae73a6b08a5b76cdd6", size = 325753, upload-time = "2026-02-27T00:30:17.247Z" }, + { url = "https://files.pythonhosted.org/packages/cc/79/09f02671eb75b251c5550a1c48e7b3d4b0623efd7c95a15a50f6f9fc1e2e/protobuf-7.34.0-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:4a72a8ec94e7a9f7ef7fe818ed26d073305f347f8b3b5ba31e22f81fd85fca02", size = 340200, upload-time = "2026-02-27T00:30:18.672Z" }, + { url = "https://files.pythonhosted.org/packages/b5/57/89727baef7578897af5ed166735ceb315819f1c184da8c3441271dbcfde7/protobuf-7.34.0-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:964cf977e07f479c0697964e83deda72bcbc75c3badab506fb061b352d991b01", size = 324268, upload-time = "2026-02-27T00:30:20.088Z" }, + { url = "https://files.pythonhosted.org/packages/1f/3e/38ff2ddee5cc946f575c9d8cc822e34bde205cf61acf8099ad88ef19d7d2/protobuf-7.34.0-cp310-abi3-win32.whl", hash = "sha256:f791ec509707a1d91bd02e07df157e75e4fb9fbdad12a81b7396201ec244e2e3", size = 426628, upload-time = "2026-02-27T00:30:21.555Z" }, + { url = "https://files.pythonhosted.org/packages/cb/71/7c32eaf34a61a1bae1b62a2ac4ffe09b8d1bb0cf93ad505f42040023db89/protobuf-7.34.0-cp310-abi3-win_amd64.whl", hash = "sha256:9f9079f1dde4e32342ecbd1c118d76367090d4aaa19da78230c38101c5b3dd40", size = 437901, upload-time = "2026-02-27T00:30:22.836Z" }, + { url = "https://files.pythonhosted.org/packages/a4/e7/14dc9366696dcb53a413449881743426ed289d687bcf3d5aee4726c32ebb/protobuf-7.34.0-py3-none-any.whl", hash = "sha256:e3b914dd77fa33fa06ab2baa97937746ab25695f389869afdf03e81f34e45dc7", size = 170716, upload-time = "2026-02-27T00:30:23.994Z" }, ] [[package]] -name = "protobuf" -version = "6.33.5" +name = "protoc-gen-connect-python" +version = "0.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/25/7c72c307aafc96fa87062aa6291d9f7c94836e43214d43722e86037aac02/protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c", size = 444465, upload-time = "2026-01-29T21:51:33.494Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/79/af92d0a8369732b027e6d6084251dd8e782c685c72da161bd4a2e00fbabb/protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b", size = 425769, upload-time = "2026-01-29T21:51:21.751Z" }, - { url = "https://files.pythonhosted.org/packages/55/75/bb9bc917d10e9ee13dee8607eb9ab963b7cf8be607c46e7862c748aa2af7/protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c", size = 437118, upload-time = "2026-01-29T21:51:24.022Z" }, - { url = "https://files.pythonhosted.org/packages/a2/6b/e48dfc1191bc5b52950246275bf4089773e91cb5ba3592621723cdddca62/protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5", size = 427766, upload-time = "2026-01-29T21:51:25.413Z" }, - { url = "https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190", size = 324638, upload-time = "2026-01-29T21:51:26.423Z" }, - { url = "https://files.pythonhosted.org/packages/c5/f5/65d838092fd01c44d16037953fd4c2cc851e783de9b8f02b27ec4ffd906f/protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd", size = 339411, upload-time = "2026-01-29T21:51:27.446Z" }, - { url = "https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0", size = 323465, upload-time = "2026-01-29T21:51:28.925Z" }, - { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" }, + { url = "https://files.pythonhosted.org/packages/4a/c3/216e30a2153eba4141f373e1266ff372107009674095a60ea718909736b4/protoc_gen_connect_python-0.9.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:821c9023e02db313b88c037f9a425316c01c2de58b3e3947c001ff1f060d4cac", size = 1876792, upload-time = "2026-03-19T02:40:51.527Z" }, + { url = "https://files.pythonhosted.org/packages/6e/61/a577047b2b2832eb331777e6ce9a1fddffe09ea760023f0586f56019d5c5/protoc_gen_connect_python-0.9.0-py3-none-macosx_11_0_x86_64.whl", hash = "sha256:507f1c6a9de6c6d2442f6d1bf1c639cc699c510c5abacb1b63535c578c5fe3ae", size = 2021337, upload-time = "2026-03-19T02:40:53.697Z" }, + { url = "https://files.pythonhosted.org/packages/30/01/c4f798f586fc82fdb794ce0504651f9c68dff716343d028bdcecb8f88c29/protoc_gen_connect_python-0.9.0-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:3ffb917bce4a5d8af0af804711b24fc627e06843faefca3b5c6f700b7bd7307e", size = 1822549, upload-time = "2026-03-19T02:40:55.216Z" }, + { url = "https://files.pythonhosted.org/packages/43/b3/8c6bac0e73c7c837297f078d0025fc1dbfa72834dfa8c78a1453cd123837/protoc_gen_connect_python-0.9.0-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:7d563196a2d43797b1a72edfa7fdf2aa00dccefa9ca969e19466f7928049f1cf", size = 2011338, upload-time = "2026-03-19T02:40:56.818Z" }, + { url = "https://files.pythonhosted.org/packages/0c/48/4adb0616be7617c8f2bdbec490e1086640c6eb9b4ece35ad6228cdc26c51/protoc_gen_connect_python-0.9.0-py3-none-win_amd64.whl", hash = "sha256:bd83efaff9d2eb703b69d057e95569755eec875a3728ce2f4944acb6f03538f6", size = 2062026, upload-time = "2026-03-19T02:40:58.416Z" }, + { url = "https://files.pythonhosted.org/packages/e1/ea/7a4522f935f6b6c156054e283d0d00431ae52a5d677c5148a174e92aa6f0/protoc_gen_connect_python-0.9.0-py3-none-win_arm64.whl", hash = "sha256:0f6d7e11ccd82009f951da2f2770ef817fd1dc5664de8fedbef60372ef6ab256", size = 1843570, upload-time = "2026-03-19T02:40:59.991Z" }, ] [[package]] @@ -261,12 +259,26 @@ wheels = [ ] [[package]] -name = "setuptools" -version = "82.0.1" +name = "pyqwest" +version = "0.4.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" } +dependencies = [ + { name = "opentelemetry-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6e/e3/cf7e1eaa975fff450f3886d6297a3041e37eb424c9a9f6531bab7c9d29b3/pyqwest-0.4.1.tar.gz", hash = "sha256:08ff72951861d2bbdd9e9e98e3ed710c81c47ec66652a5622645c68c71d9f609", size = 440370, upload-time = "2026-03-06T02:32:43.207Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" }, + { url = "https://files.pythonhosted.org/packages/4a/6f/ed9be2ee96d209ba81467abf4c15f20973c676992597019399998adb5da0/pyqwest-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d1ae7a901f58c0d1456ce7012ccb60c4ef85cbc3d6daa9b17a43415b362a3f74", size = 5005846, upload-time = "2026-03-06T02:32:11.677Z" }, + { url = "https://files.pythonhosted.org/packages/ec/29/cb412b9e5b0a1f72cf63b5b551df18aa580aafa020f907fe27c794482362/pyqwest-0.4.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:588f95168779902a734db2a39af353768888a87aa1d91c93002a3132111e72b0", size = 5377385, upload-time = "2026-03-06T02:32:13.821Z" }, + { url = "https://files.pythonhosted.org/packages/84/9e/be8c0192c2fb177834870de10ece2751cd38ca1d357908112a8da6a26106/pyqwest-0.4.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b97a3adfa54188029e93361bacb248ca81272d9085cb6189e4a2a2586c4346e", size = 5422653, upload-time = "2026-03-06T02:32:15.518Z" }, + { url = "https://files.pythonhosted.org/packages/18/74/98afc627c0b91bb3e0214daf3dfbbd348d504574d4c6843a890a0dcc6f33/pyqwest-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2351d5b142e26df482c274d405185dc56f060559038a8e5e0e5feb8241bb4bb3", size = 5543025, upload-time = "2026-03-06T02:32:17.254Z" }, + { url = "https://files.pythonhosted.org/packages/17/1d/c79c78103aa90a1eff56b5841c1f24bd4ca950957116387de1f1e3291066/pyqwest-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1fae17504ea83166e495fe93d9f2bfc22dc331dd68bca354a18597e3d1020984", size = 5723286, upload-time = "2026-03-06T02:32:18.8Z" }, + { url = "https://files.pythonhosted.org/packages/24/5b/975b4275ee49cff860f5680dd4ed7f9d74c4c2294cc7c829012e69077e71/pyqwest-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:05320841aaa40af070ceb55bfd557f623b5f8aeca1831f97da79b5965775a549", size = 4596486, upload-time = "2026-03-06T02:32:20.813Z" }, + { url = "https://files.pythonhosted.org/packages/ae/ed/08ba859cf528451a9325e5a71c13db8b9aeb7cda794d1e6b7f4d3b3d581d/pyqwest-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:84e396c6ba396daa974dba2d7090264af26dcfce074d7812c2d7125602969da3", size = 5001684, upload-time = "2026-03-06T02:32:22.332Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ed/b75026973f77cba73c2c6785107cd30407ca8285a7159a0a443801fdd30d/pyqwest-0.4.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f98b11081b3e0d117fda4e03fee6925d870c334fa35085362e980a44e118ab9", size = 5375558, upload-time = "2026-03-06T02:32:24.148Z" }, + { url = "https://files.pythonhosted.org/packages/36/21/2b22d1117c440b020269dbd292f47890579ae5a78d14022a294eb558710b/pyqwest-0.4.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:952842d7f4935ff42d55fdfbf7f0538997b48c62e4aa9a20e4b42bce97ed82a4", size = 5424612, upload-time = "2026-03-06T02:32:25.663Z" }, + { url = "https://files.pythonhosted.org/packages/74/9a/0b3d77903e0bfbfb6a836050aa08ff3d6efae332ce429980146dcd15b151/pyqwest-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:32e313d2357624a54e60f14976bdf22e41267871b913d51ec7b41be492a0c442", size = 5542133, upload-time = "2026-03-06T02:32:27.191Z" }, + { url = "https://files.pythonhosted.org/packages/2e/3b/fcbfa0f1e8a64ebca0b28ec8f638defddbba47461d755b33658347f8ed84/pyqwest-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:284e2c99cbebb257ff84c14f14aa87f658ebe57ddfc833aa1d2fd6a3c4687a37", size = 5724980, upload-time = "2026-03-06T02:32:29.102Z" }, + { url = "https://files.pythonhosted.org/packages/2d/d8/d6710bbb38f6a715135f7c8a8e5c6227d69299a2b7e989c81315a08054e7/pyqwest-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:a7b8d8ae51ccf6375a9e82e5b38d2129ee3121acf4933a37e541f4fe04a5f758", size = 4577924, upload-time = "2026-03-06T02:32:31.013Z" }, ] [[package]] @@ -314,3 +326,12 @@ sdist = { url = "https://files.pythonhosted.org/packages/e3/ad/4a96c425be6fb67e0 wheels = [ { url = "https://files.pythonhosted.org/packages/0a/89/f8827ccff89c1586027a105e5630ff6139a64da2515e24dafe860bd9ae4d/uvicorn-0.42.0-py3-none-any.whl", hash = "sha256:96c30f5c7abe6f74ae8900a70e92b85ad6613b745d4879eb9b16ccad15645359", size = 68830, upload-time = "2026-03-16T06:19:48.325Z" }, ] + +[[package]] +name = "zipp" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, +] diff --git a/buf.gen.yaml b/buf.gen.yaml new file mode 100644 index 0000000..5ce41c7 --- /dev/null +++ b/buf.gen.yaml @@ -0,0 +1,20 @@ +version: v2 +plugins: + # Frontend: TypeScript protobuf stubs for ConnectRPC Web + - local: frontend/node_modules/.bin/protoc-gen-es + out: frontend/src/gen + opt: target=ts + + # Backend: Python protobuf messages + - remote: buf.build/protocolbuffers/python + out: backend/generated + + # Backend: Python protobuf type stubs (.pyi) + - remote: buf.build/protocolbuffers/pyi + out: backend/generated + + # Backend: ConnectRPC Python server + client stubs + - local: backend/.venv/bin/protoc-gen-connect-python + out: backend/generated +inputs: + - directory: backend/proto diff --git a/buf.yaml b/buf.yaml new file mode 100644 index 0000000..0387b37 --- /dev/null +++ b/buf.yaml @@ -0,0 +1,3 @@ +version: v2 +modules: + - path: backend/proto diff --git a/docker-compose.yml b/docker-compose.yml index 5767e5e..ac9a4ec 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,7 @@ services: dockerfile: Dockerfile ports: - "8000:8000" # FastAPI (REST / OpenAPI docs at /docs) - - "50051:50051" # gRPC + - "50051:50051" # ConnectRPC volumes: - auction-data:/app/data # persistent SQLite storage environment: diff --git a/frontend/.gitignore b/frontend/.gitignore index fa93af8..cd68f14 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -37,6 +37,3 @@ __screenshots__/ # Vite *.timestamp-*-*.mjs - -# Generated protobuf/ConnectRPC stubs -src/gen/ diff --git a/frontend/Dockerfile b/frontend/Dockerfile index d56f4aa..7526a2b 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -19,9 +19,9 @@ COPY backend/proto/ /proto/ # Copy the rest of the frontend source COPY frontend/ . -# Point buf.gen.yaml at the copied proto location -# (in Docker the relative ../backend/proto path doesn't exist) -RUN sed -i 's|directory: ../backend/proto|directory: /proto|' buf.gen.yaml +# Create a frontend-only buf.gen.yaml for Docker +# (the shared root config references backend plugins that aren't available here) +RUN printf 'version: v2\nplugins:\n - local: node_modules/.bin/protoc-gen-es\n out: src/gen\n opt: target=ts\ninputs:\n - directory: /proto\n' > buf.gen.yaml # Generate ConnectRPC TypeScript stubs from proto RUN pnpm generate diff --git a/frontend/buf.gen.yaml b/frontend/buf.gen.yaml deleted file mode 100644 index b30b581..0000000 --- a/frontend/buf.gen.yaml +++ /dev/null @@ -1,7 +0,0 @@ -version: v2 -plugins: - - local: protoc-gen-es - out: src/gen - opt: target=ts -inputs: - - directory: ../backend/proto diff --git a/frontend/src/gen/auction_pb.ts b/frontend/src/gen/auction_pb.ts new file mode 100644 index 0000000..993e8fe --- /dev/null +++ b/frontend/src/gen/auction_pb.ts @@ -0,0 +1,264 @@ +// @generated by protoc-gen-es v2.11.0 with parameter "target=ts" +// @generated from file auction.proto (package auction, syntax proto3) +/* eslint-disable */ + +import type { GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2"; +import { fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2"; +import type { Message } from "@bufbuild/protobuf"; + +/** + * Describes the file auction.proto. + */ +export const file_auction: GenFile = /*@__PURE__*/ + fileDesc("Cg1hdWN0aW9uLnByb3RvEgdhdWN0aW9uIgcKBUVtcHR5Ik8KEUNyZWF0ZUl0ZW1SZXF1ZXN0Eg0KBXRpdGxlGAEgASgJEhMKC2Rlc2NyaXB0aW9uGAIgASgJEhYKDnN0YXJ0aW5nX3ByaWNlGAMgASgCIlYKDEl0ZW1SZXNwb25zZRIKCgJpZBgBIAEoBRINCgV0aXRsZRgCIAEoCRITCgtkZXNjcmlwdGlvbhgDIAEoCRIWCg5zdGFydGluZ19wcmljZRgEIAEoAiI5ChFMaXN0SXRlbXNSZXNwb25zZRIkCgVpdGVtcxgBIAMoCzIVLmF1Y3Rpb24uSXRlbVJlc3BvbnNlIkcKD1BsYWNlQmlkUmVxdWVzdBIPCgdpdGVtX2lkGAEgASgFEg4KBmFtb3VudBgCIAEoAhITCgtiaWRkZXJfbmFtZRgDIAEoCSJPCgtCaWRSZXNwb25zZRIKCgJpZBgBIAEoBRIPCgdpdGVtX2lkGAIgASgFEg4KBmFtb3VudBgDIAEoAhITCgtiaWRkZXJfbmFtZRgEIAEoCSIiCg9MaXN0Qmlkc1JlcXVlc3QSDwoHaXRlbV9pZBgBIAEoBSI2ChBMaXN0Qmlkc1Jlc3BvbnNlEiIKBGJpZHMYASADKAsyFC5hdWN0aW9uLkJpZFJlc3BvbnNlIicKFEdldFdpbm5pbmdCaWRSZXF1ZXN0Eg8KB2l0ZW1faWQYASABKAUyzQIKDkF1Y3Rpb25TZXJ2aWNlEj8KCkNyZWF0ZUl0ZW0SGi5hdWN0aW9uLkNyZWF0ZUl0ZW1SZXF1ZXN0GhUuYXVjdGlvbi5JdGVtUmVzcG9uc2USNwoJTGlzdEl0ZW1zEg4uYXVjdGlvbi5FbXB0eRoaLmF1Y3Rpb24uTGlzdEl0ZW1zUmVzcG9uc2USOgoIUGxhY2VCaWQSGC5hdWN0aW9uLlBsYWNlQmlkUmVxdWVzdBoULmF1Y3Rpb24uQmlkUmVzcG9uc2USPwoITGlzdEJpZHMSGC5hdWN0aW9uLkxpc3RCaWRzUmVxdWVzdBoZLmF1Y3Rpb24uTGlzdEJpZHNSZXNwb25zZRJECg1HZXRXaW5uaW5nQmlkEh0uYXVjdGlvbi5HZXRXaW5uaW5nQmlkUmVxdWVzdBoULmF1Y3Rpb24uQmlkUmVzcG9uc2ViBnByb3RvMw"); + +/** + * @generated from message auction.Empty + */ +export type Empty = Message<"auction.Empty"> & { +}; + +/** + * Describes the message auction.Empty. + * Use `create(EmptySchema)` to create a new message. + */ +export const EmptySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_auction, 0); + +/** + * @generated from message auction.CreateItemRequest + */ +export type CreateItemRequest = Message<"auction.CreateItemRequest"> & { + /** + * @generated from field: string title = 1; + */ + title: string; + + /** + * @generated from field: string description = 2; + */ + description: string; + + /** + * @generated from field: float starting_price = 3; + */ + startingPrice: number; +}; + +/** + * Describes the message auction.CreateItemRequest. + * Use `create(CreateItemRequestSchema)` to create a new message. + */ +export const CreateItemRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_auction, 1); + +/** + * @generated from message auction.ItemResponse + */ +export type ItemResponse = Message<"auction.ItemResponse"> & { + /** + * @generated from field: int32 id = 1; + */ + id: number; + + /** + * @generated from field: string title = 2; + */ + title: string; + + /** + * @generated from field: string description = 3; + */ + description: string; + + /** + * @generated from field: float starting_price = 4; + */ + startingPrice: number; +}; + +/** + * Describes the message auction.ItemResponse. + * Use `create(ItemResponseSchema)` to create a new message. + */ +export const ItemResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_auction, 2); + +/** + * @generated from message auction.ListItemsResponse + */ +export type ListItemsResponse = Message<"auction.ListItemsResponse"> & { + /** + * @generated from field: repeated auction.ItemResponse items = 1; + */ + items: ItemResponse[]; +}; + +/** + * Describes the message auction.ListItemsResponse. + * Use `create(ListItemsResponseSchema)` to create a new message. + */ +export const ListItemsResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_auction, 3); + +/** + * @generated from message auction.PlaceBidRequest + */ +export type PlaceBidRequest = Message<"auction.PlaceBidRequest"> & { + /** + * @generated from field: int32 item_id = 1; + */ + itemId: number; + + /** + * @generated from field: float amount = 2; + */ + amount: number; + + /** + * @generated from field: string bidder_name = 3; + */ + bidderName: string; +}; + +/** + * Describes the message auction.PlaceBidRequest. + * Use `create(PlaceBidRequestSchema)` to create a new message. + */ +export const PlaceBidRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_auction, 4); + +/** + * @generated from message auction.BidResponse + */ +export type BidResponse = Message<"auction.BidResponse"> & { + /** + * @generated from field: int32 id = 1; + */ + id: number; + + /** + * @generated from field: int32 item_id = 2; + */ + itemId: number; + + /** + * @generated from field: float amount = 3; + */ + amount: number; + + /** + * @generated from field: string bidder_name = 4; + */ + bidderName: string; +}; + +/** + * Describes the message auction.BidResponse. + * Use `create(BidResponseSchema)` to create a new message. + */ +export const BidResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_auction, 5); + +/** + * @generated from message auction.ListBidsRequest + */ +export type ListBidsRequest = Message<"auction.ListBidsRequest"> & { + /** + * @generated from field: int32 item_id = 1; + */ + itemId: number; +}; + +/** + * Describes the message auction.ListBidsRequest. + * Use `create(ListBidsRequestSchema)` to create a new message. + */ +export const ListBidsRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_auction, 6); + +/** + * @generated from message auction.ListBidsResponse + */ +export type ListBidsResponse = Message<"auction.ListBidsResponse"> & { + /** + * @generated from field: repeated auction.BidResponse bids = 1; + */ + bids: BidResponse[]; +}; + +/** + * Describes the message auction.ListBidsResponse. + * Use `create(ListBidsResponseSchema)` to create a new message. + */ +export const ListBidsResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_auction, 7); + +/** + * @generated from message auction.GetWinningBidRequest + */ +export type GetWinningBidRequest = Message<"auction.GetWinningBidRequest"> & { + /** + * @generated from field: int32 item_id = 1; + */ + itemId: number; +}; + +/** + * Describes the message auction.GetWinningBidRequest. + * Use `create(GetWinningBidRequestSchema)` to create a new message. + */ +export const GetWinningBidRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_auction, 8); + +/** + * @generated from service auction.AuctionService + */ +export const AuctionService: GenService<{ + /** + * Items + * + * @generated from rpc auction.AuctionService.CreateItem + */ + createItem: { + methodKind: "unary"; + input: typeof CreateItemRequestSchema; + output: typeof ItemResponseSchema; + }, + /** + * @generated from rpc auction.AuctionService.ListItems + */ + listItems: { + methodKind: "unary"; + input: typeof EmptySchema; + output: typeof ListItemsResponseSchema; + }, + /** + * Bids + * + * @generated from rpc auction.AuctionService.PlaceBid + */ + placeBid: { + methodKind: "unary"; + input: typeof PlaceBidRequestSchema; + output: typeof BidResponseSchema; + }, + /** + * @generated from rpc auction.AuctionService.ListBids + */ + listBids: { + methodKind: "unary"; + input: typeof ListBidsRequestSchema; + output: typeof ListBidsResponseSchema; + }, + /** + * @generated from rpc auction.AuctionService.GetWinningBid + */ + getWinningBid: { + methodKind: "unary"; + input: typeof GetWinningBidRequestSchema; + output: typeof BidResponseSchema; + }, +}> = /*@__PURE__*/ + serviceDesc(file_auction, 0); + diff --git a/resolve b/resolve new file mode 100644 index 0000000..e69de29 diff --git a/transferring b/transferring new file mode 100644 index 0000000..e69de29