|
| 1 | +"""The Athena Client Class.""" |
| 2 | + |
| 3 | +import types |
| 4 | +from collections.abc import AsyncIterator |
| 5 | + |
| 6 | +import grpc |
| 7 | + |
| 8 | +from athena_client.client.athena_options import AthenaOptions |
| 9 | +from athena_client.client.exceptions import AthenaError |
| 10 | +from athena_client.client.transformers.brotli_compressor import BrotliCompressor |
| 11 | +from athena_client.client.transformers.classification_input import ( |
| 12 | + ClassificationInputTransformer, |
| 13 | +) |
| 14 | +from athena_client.client.transformers.image_resizer import ImageResizer |
| 15 | +from athena_client.client.transformers.request_batcher import RequestBatcher |
| 16 | +from athena_client.generated.athena.athena_pb2 import ( |
| 17 | + ClassifyRequest, |
| 18 | + ClassifyResponse, |
| 19 | + RequestEncoding, |
| 20 | +) |
| 21 | +from athena_client.grpc_wrappers.classifier_service import ( |
| 22 | + ClassifierServiceClient, |
| 23 | +) |
| 24 | + |
| 25 | + |
| 26 | +class AthenaClient: |
| 27 | + """The Athena Client Class. |
| 28 | +
|
| 29 | + This class provides coroutine methods for interacting with the |
| 30 | + Athena service. |
| 31 | +
|
| 32 | + Attributes: |
| 33 | + options (AthenaOptions): Configuration options for the Athena client. |
| 34 | +
|
| 35 | + """ |
| 36 | + |
| 37 | + def __init__( |
| 38 | + self, channel: grpc.aio.Channel, options: AthenaOptions |
| 39 | + ) -> None: |
| 40 | + """Initialize the Athena Client. |
| 41 | +
|
| 42 | + Args: |
| 43 | + channel (grpc.aio.Channel): The gRPC channel to use for |
| 44 | + communication. |
| 45 | + options (AthenaOptions): Configuration options for the |
| 46 | + Athena client. |
| 47 | +
|
| 48 | + """ |
| 49 | + self.options = options |
| 50 | + self.channel = channel |
| 51 | + self.classifier = ClassifierServiceClient(self.channel) |
| 52 | + self.deployment_id = options.deployment_id |
| 53 | + self.max_batch_size = options.max_batch_size |
| 54 | + |
| 55 | + async def classify_images( |
| 56 | + self, images: AsyncIterator[bytes] |
| 57 | + ) -> AsyncIterator[ClassifyResponse]: |
| 58 | + """Classify an image using the Athena service. |
| 59 | +
|
| 60 | + Args: |
| 61 | + images (AsyncIterator[Image]): The images to classify in an |
| 62 | + asynchronous iterator. |
| 63 | +
|
| 64 | + Yields: |
| 65 | + ClassifyResponse: The classification responses. |
| 66 | +
|
| 67 | + """ |
| 68 | + # Build middleware pipeline |
| 69 | + image_stream = images |
| 70 | + |
| 71 | + if self.options.resize_images: |
| 72 | + image_stream = ImageResizer(image_stream) |
| 73 | + |
| 74 | + if self.options.compress_images: |
| 75 | + image_stream = BrotliCompressor(image_stream) |
| 76 | + |
| 77 | + input_transformer = ClassificationInputTransformer( |
| 78 | + image_stream, |
| 79 | + deployment_id=self.options.deployment_id, |
| 80 | + affiliate=self.options.affiliate, |
| 81 | + request_encoding=RequestEncoding.REQUEST_ENCODING_BROTLI, |
| 82 | + ) |
| 83 | + |
| 84 | + request_batcher = RequestBatcher( |
| 85 | + input_transformer, |
| 86 | + deployment_id=self.deployment_id, |
| 87 | + max_batch_size=self.max_batch_size, |
| 88 | + ) |
| 89 | + |
| 90 | + async for response in self.classifier.classify(request_batcher): |
| 91 | + yield response |
| 92 | + |
| 93 | + async def close(self) -> None: |
| 94 | + """Close the client and GRPC channel.""" |
| 95 | + await self.channel.close() |
| 96 | + |
| 97 | + async def __aenter__(self) -> "AthenaClient": |
| 98 | + """Context manager entrypoint. |
| 99 | +
|
| 100 | + Registers the client with the server by sending a |
| 101 | + classification request. With a deployment ID. |
| 102 | + """ |
| 103 | + |
| 104 | + # Register the client with the server, just send one deployment ID. |
| 105 | + async def init_request() -> AsyncIterator[ClassifyRequest]: |
| 106 | + yield ClassifyRequest(deployment_id=self.deployment_id) |
| 107 | + |
| 108 | + responses = self.classifier.classify(init_request()) |
| 109 | + async for response in responses: |
| 110 | + if response.global_error: |
| 111 | + raise AthenaError(response.global_error.message) |
| 112 | + |
| 113 | + return self |
| 114 | + |
| 115 | + async def __aexit__( |
| 116 | + self, |
| 117 | + exc_type: type[BaseException] | None, |
| 118 | + exc_val: BaseException | None, |
| 119 | + exc_tb: types.TracebackType | None, |
| 120 | + ) -> None: |
| 121 | + """Context manager exit.""" |
| 122 | + await self.close() |
0 commit comments