Hey developers! Continuing from the previous post, we’ll implement the file translation endpoint using Python, AWS Lambda, and a clean Hexagonal Architecture. Let’s dive in! You can check out my GitHub for the complete code.
The Translation Record Model
We’ll reuse the Record model from the translate
endpoint. This time, instead the input and output text we will store the base64 encoded string of the the input and output bytes
<span>#translate_file/models.py </span><span>from</span> <span>dataclasses</span> <span>import</span> <span>dataclass</span><span>,</span> <span>field</span><span>from</span> <span>datetime</span> <span>import</span> <span>datetime</span><span>@dataclass</span><span>class</span> <span>Record</span><span>:</span><span>id</span><span>:</span> <span>str</span><span>input_text</span><span>:</span> <span>str</span><span>output_text</span><span>:</span> <span>str</span><span>created_at</span><span>:</span> <span>datetime</span> <span>=</span> <span>field</span><span>(</span><span>default</span><span>=</span><span>datetime</span><span>.</span><span>now</span><span>)</span><span>#translate_file/models.py </span> <span>from</span> <span>dataclasses</span> <span>import</span> <span>dataclass</span><span>,</span> <span>field</span> <span>from</span> <span>datetime</span> <span>import</span> <span>datetime</span> <span>@dataclass</span> <span>class</span> <span>Record</span><span>:</span> <span>id</span><span>:</span> <span>str</span> <span>input_text</span><span>:</span> <span>str</span> <span>output_text</span><span>:</span> <span>str</span> <span>created_at</span><span>:</span> <span>datetime</span> <span>=</span> <span>field</span><span>(</span><span>default</span><span>=</span><span>datetime</span><span>.</span><span>now</span><span>)</span>#translate_file/models.py from dataclasses import dataclass, field from datetime import datetime @dataclass class Record: id: str input_text: str output_text: str created_at: datetime = field(default=datetime.now)
Enter fullscreen mode Exit fullscreen mode
Next, let’s define the ports that handle different aspects of our application:
-
FilePersistencePort
– Responsible for storing raw byte data to a file. -
RequestPersistencePort
– Similar toTextPersistencePort
, this port manages the persistence of request input and output. -
TranslationPort
– Handles the translation of file content. These ports act as abstractions, making our system more modular and easier to extend.
<span>#translate_file/ports.py </span><span>from</span> <span>typing</span> <span>import</span> <span>Protocol</span><span>from</span> <span>models</span> <span>import</span> <span>Record</span><span>class</span> <span>RequestPersistencePort</span><span>(</span><span>Protocol</span><span>):</span><span>def</span> <span>save</span><span>(</span><span>self</span><span>,</span> <span>input</span><span>:</span> <span>bytes</span><span>,</span> <span>output</span><span>:</span> <span>bytes</span><span>)</span> <span>-></span> <span>Record</span><span>:</span><span>pass</span><span>class</span> <span>FilePersistencePort</span><span>(</span><span>Protocol</span><span>):</span><span>def</span> <span>save</span><span>(</span><span>self</span><span>,</span> <span>file</span><span>:</span> <span>bytes</span><span>,</span> <span>extension</span><span>:</span> <span>str</span><span>)</span> <span>-></span> <span>str</span><span>:</span><span>pass</span><span>class</span> <span>TranslationPort</span><span>(</span><span>Protocol</span><span>):</span><span>def</span> <span>translate</span><span>(</span><span>self</span><span>,</span> <span>file</span><span>:</span> <span>bytes</span><span>,</span> <span>content_Type</span><span>:</span> <span>str</span><span>,</span> <span>lang</span><span>:</span> <span>str</span><span>)</span> <span>-></span> <span>bytes</span><span>:</span><span>pass</span><span>#translate_file/ports.py </span> <span>from</span> <span>typing</span> <span>import</span> <span>Protocol</span> <span>from</span> <span>models</span> <span>import</span> <span>Record</span> <span>class</span> <span>RequestPersistencePort</span><span>(</span><span>Protocol</span><span>):</span> <span>def</span> <span>save</span><span>(</span><span>self</span><span>,</span> <span>input</span><span>:</span> <span>bytes</span><span>,</span> <span>output</span><span>:</span> <span>bytes</span><span>)</span> <span>-></span> <span>Record</span><span>:</span> <span>pass</span> <span>class</span> <span>FilePersistencePort</span><span>(</span><span>Protocol</span><span>):</span> <span>def</span> <span>save</span><span>(</span><span>self</span><span>,</span> <span>file</span><span>:</span> <span>bytes</span><span>,</span> <span>extension</span><span>:</span> <span>str</span><span>)</span> <span>-></span> <span>str</span><span>:</span> <span>pass</span> <span>class</span> <span>TranslationPort</span><span>(</span><span>Protocol</span><span>):</span> <span>def</span> <span>translate</span><span>(</span><span>self</span><span>,</span> <span>file</span><span>:</span> <span>bytes</span><span>,</span> <span>content_Type</span><span>:</span> <span>str</span><span>,</span> <span>lang</span><span>:</span> <span>str</span><span>)</span> <span>-></span> <span>bytes</span><span>:</span> <span>pass</span>#translate_file/ports.py from typing import Protocol from models import Record class RequestPersistencePort(Protocol): def save(self, input: bytes, output: bytes) -> Record: pass class FilePersistencePort(Protocol): def save(self, file: bytes, extension: str) -> str: pass class TranslationPort(Protocol): def translate(self, file: bytes, content_Type: str, lang: str) -> bytes: pass
Enter fullscreen mode Exit fullscreen mode
Now, let’s define the adapters that implement our ports:
-
RequestPersistenceAdapter
– Stores the request input and output in DynamoDB and returns aRecord
object. -
FilePersistenceAdapter
– Saves the file as an object in an S3 bucket and returns a pre-signed URL for access. -
AWSTranslateAdapter
– Uses AWS Translate to process the file and outputs the translated file as bytes. By following this adapter pattern, we keep our architecture clean, modular, and easily extendable.
<span>#translate_file/adapters.py </span><span>import</span> <span>boto3</span><span>import</span> <span>base64</span><span>from</span> <span>uuid</span> <span>import</span> <span>uuid4</span><span>from</span> <span>datetime</span> <span>import</span> <span>datetime</span><span>from</span> <span>models</span> <span>import</span> <span>Record</span><span>class</span> <span>RequestPersistenceAdapter</span><span>:</span><span>"""</span><span> Implementation of RequestPersistencePort using DynamoDB as storage. </span><span>"""</span><span>def</span> <span>__init__</span><span>(</span><span>self</span><span>,</span> <span>table_name</span><span>:</span> <span>str</span><span>):</span><span>dynamodb</span> <span>=</span> <span>boto3</span><span>.</span><span>resource</span><span>(</span><span>"</span><span>dynamodb</span><span>"</span><span>)</span><span>self</span><span>.</span><span>table</span> <span>=</span> <span>dynamodb</span><span>.</span><span>Table</span><span>(</span><span>table_name</span><span>)</span><span>def</span> <span>save</span><span>(</span><span>self</span><span>,</span> <span>input</span><span>:</span> <span>bytes</span><span>,</span> <span>output</span><span>:</span> <span>bytes</span><span>)</span> <span>-></span> <span>Record</span><span>:</span><span>"""</span><span> Save input and output byptes to DynamoDB. Args: input: The input bytes to save output: The output bytes to save Returns: Record object containing saved data </span><span>"""</span><span>id</span> <span>=</span> <span>str</span><span>(</span><span>uuid4</span><span>())</span><span>created_at</span> <span>=</span> <span>datetime</span><span>.</span><span>now</span><span>()</span><span>input_text</span> <span>=</span> <span>base64</span><span>.</span><span>b64encode</span><span>(</span><span>input</span><span>).</span><span>decode</span><span>()</span><span>output_text</span> <span>=</span> <span>base64</span><span>.</span><span>b64encode</span><span>(</span><span>output</span><span>).</span><span>decode</span><span>()</span><span>self</span><span>.</span><span>table</span><span>.</span><span>put_item</span><span>(</span><span>Item</span><span>=</span><span>{</span><span>"</span><span>id</span><span>"</span><span>:</span> <span>str</span><span>(</span><span>uuid4</span><span>()),</span><span>"</span><span>input_text</span><span>"</span><span>:</span> <span>input_text</span><span>,</span><span>"</span><span>output_text</span><span>"</span><span>:</span> <span>output_text</span><span>,</span><span>"</span><span>created_at</span><span>"</span><span>:</span> <span>str</span><span>(</span><span>created_at</span><span>),</span><span>}</span><span>)</span><span>record</span> <span>=</span> <span>Record</span><span>(</span><span>id</span><span>,</span> <span>input_text</span><span>,</span> <span>output_text</span><span>,</span> <span>created_at</span><span>)</span><span>return</span> <span>record</span><span>class</span> <span>FilePersistenceAdpater</span><span>:</span><span>"""</span><span> Implementation of FilePersistencePort using S3 </span><span>"""</span><span>def</span> <span>__init__</span><span>(</span><span>self</span><span>,</span> <span>bucket_name</span><span>:</span> <span>str</span><span>):</span><span>self</span><span>.</span><span>client</span> <span>=</span> <span>boto3</span><span>.</span><span>client</span><span>(</span><span>"</span><span>s3</span><span>"</span><span>)</span><span>self</span><span>.</span><span>bucket_name</span> <span>=</span> <span>bucket_name</span><span>def</span> <span>save</span><span>(</span><span>self</span><span>,</span> <span>file</span><span>:</span> <span>bytes</span><span>,</span> <span>extension</span><span>:</span> <span>str</span><span>)</span> <span>-></span> <span>str</span><span>:</span><span>"""</span><span> Save file as object in S3 bucket Args: file: The file to save Returns: The url to the file </span><span>"""</span><span>key</span> <span>=</span> <span>str</span><span>(</span><span>uuid4</span><span>()).</span><span>replace</span><span>(</span><span>"</span><span>-</span><span>"</span><span>,</span> <span>""</span><span>)</span><span>if</span> <span>extension</span><span>:</span><span>key</span> <span>+=</span> <span>f</span><span>"</span><span>.</span><span>{</span><span>extension</span><span>}</span><span>"</span><span>response</span> <span>=</span> <span>self</span><span>.</span><span>client</span><span>.</span><span>put_object</span><span>(</span><span>Key</span><span>=</span><span>key</span><span>,</span> <span>Body</span><span>=</span><span>file</span><span>,</span> <span>Bucket</span><span>=</span><span>self</span><span>.</span><span>bucket_name</span><span>)</span><span>url</span> <span>=</span> <span>self</span><span>.</span><span>client</span><span>.</span><span>generate_presigned_url</span><span>(</span><span>"</span><span>get_object</span><span>"</span><span>,</span> <span>Params</span><span>=</span><span>{</span><span>"</span><span>Bucket</span><span>"</span><span>:</span> <span>self</span><span>.</span><span>bucket_name</span><span>,</span> <span>"</span><span>Key</span><span>"</span><span>:</span> <span>key</span><span>},</span> <span>ExpiresIn</span><span>=</span><span>300</span><span>)</span><span>return</span> <span>url</span><span>class</span> <span>AWSTranslateAdapter</span><span>:</span><span>"""</span><span> Implementation of TranslationPort using AWSTranslate. </span><span>"""</span><span>def</span> <span>__init__</span><span>(</span><span>self</span><span>):</span><span>self</span><span>.</span><span>client</span> <span>=</span> <span>boto3</span><span>.</span><span>client</span><span>(</span><span>"</span><span>translate</span><span>"</span><span>)</span><span>def</span> <span>translate</span><span>(</span><span>self</span><span>,</span> <span>file</span><span>:</span> <span>bytes</span><span>,</span> <span>content_type</span><span>:</span> <span>str</span><span>,</span> <span>lang</span><span>:</span> <span>str</span><span>)</span> <span>-></span> <span>bytes</span><span>:</span><span>"""</span><span> Translate input file to lang Args: file: The file to translate lang: The language code to translate to Returns: The translated file </span><span>"""</span><span>result</span> <span>=</span> <span>self</span><span>.</span><span>client</span><span>.</span><span>translate_document</span><span>(</span><span>Document</span><span>=</span><span>{</span><span>"</span><span>Content</span><span>"</span><span>:</span> <span>file</span><span>,</span> <span>"</span><span>ContentType</span><span>"</span><span>:</span> <span>content_type</span><span>},</span><span>SourceLanguageCode</span><span>=</span><span>"</span><span>auto</span><span>"</span><span>,</span><span>TargetLanguageCode</span><span>=</span><span>lang</span><span>,</span><span>)</span><span>translated_file</span> <span>=</span> <span>result</span><span>[</span><span>"</span><span>TranslatedDocument</span><span>"</span><span>][</span><span>"</span><span>Content</span><span>"</span><span>]</span><span>return</span> <span>translated_file</span><span>#translate_file/adapters.py </span> <span>import</span> <span>boto3</span> <span>import</span> <span>base64</span> <span>from</span> <span>uuid</span> <span>import</span> <span>uuid4</span> <span>from</span> <span>datetime</span> <span>import</span> <span>datetime</span> <span>from</span> <span>models</span> <span>import</span> <span>Record</span> <span>class</span> <span>RequestPersistenceAdapter</span><span>:</span> <span>"""</span><span> Implementation of RequestPersistencePort using DynamoDB as storage. </span><span>"""</span> <span>def</span> <span>__init__</span><span>(</span><span>self</span><span>,</span> <span>table_name</span><span>:</span> <span>str</span><span>):</span> <span>dynamodb</span> <span>=</span> <span>boto3</span><span>.</span><span>resource</span><span>(</span><span>"</span><span>dynamodb</span><span>"</span><span>)</span> <span>self</span><span>.</span><span>table</span> <span>=</span> <span>dynamodb</span><span>.</span><span>Table</span><span>(</span><span>table_name</span><span>)</span> <span>def</span> <span>save</span><span>(</span><span>self</span><span>,</span> <span>input</span><span>:</span> <span>bytes</span><span>,</span> <span>output</span><span>:</span> <span>bytes</span><span>)</span> <span>-></span> <span>Record</span><span>:</span> <span>"""</span><span> Save input and output byptes to DynamoDB. Args: input: The input bytes to save output: The output bytes to save Returns: Record object containing saved data </span><span>"""</span> <span>id</span> <span>=</span> <span>str</span><span>(</span><span>uuid4</span><span>())</span> <span>created_at</span> <span>=</span> <span>datetime</span><span>.</span><span>now</span><span>()</span> <span>input_text</span> <span>=</span> <span>base64</span><span>.</span><span>b64encode</span><span>(</span><span>input</span><span>).</span><span>decode</span><span>()</span> <span>output_text</span> <span>=</span> <span>base64</span><span>.</span><span>b64encode</span><span>(</span><span>output</span><span>).</span><span>decode</span><span>()</span> <span>self</span><span>.</span><span>table</span><span>.</span><span>put_item</span><span>(</span> <span>Item</span><span>=</span><span>{</span> <span>"</span><span>id</span><span>"</span><span>:</span> <span>str</span><span>(</span><span>uuid4</span><span>()),</span> <span>"</span><span>input_text</span><span>"</span><span>:</span> <span>input_text</span><span>,</span> <span>"</span><span>output_text</span><span>"</span><span>:</span> <span>output_text</span><span>,</span> <span>"</span><span>created_at</span><span>"</span><span>:</span> <span>str</span><span>(</span><span>created_at</span><span>),</span> <span>}</span> <span>)</span> <span>record</span> <span>=</span> <span>Record</span><span>(</span><span>id</span><span>,</span> <span>input_text</span><span>,</span> <span>output_text</span><span>,</span> <span>created_at</span><span>)</span> <span>return</span> <span>record</span> <span>class</span> <span>FilePersistenceAdpater</span><span>:</span> <span>"""</span><span> Implementation of FilePersistencePort using S3 </span><span>"""</span> <span>def</span> <span>__init__</span><span>(</span><span>self</span><span>,</span> <span>bucket_name</span><span>:</span> <span>str</span><span>):</span> <span>self</span><span>.</span><span>client</span> <span>=</span> <span>boto3</span><span>.</span><span>client</span><span>(</span><span>"</span><span>s3</span><span>"</span><span>)</span> <span>self</span><span>.</span><span>bucket_name</span> <span>=</span> <span>bucket_name</span> <span>def</span> <span>save</span><span>(</span><span>self</span><span>,</span> <span>file</span><span>:</span> <span>bytes</span><span>,</span> <span>extension</span><span>:</span> <span>str</span><span>)</span> <span>-></span> <span>str</span><span>:</span> <span>"""</span><span> Save file as object in S3 bucket Args: file: The file to save Returns: The url to the file </span><span>"""</span> <span>key</span> <span>=</span> <span>str</span><span>(</span><span>uuid4</span><span>()).</span><span>replace</span><span>(</span><span>"</span><span>-</span><span>"</span><span>,</span> <span>""</span><span>)</span> <span>if</span> <span>extension</span><span>:</span> <span>key</span> <span>+=</span> <span>f</span><span>"</span><span>.</span><span>{</span><span>extension</span><span>}</span><span>"</span> <span>response</span> <span>=</span> <span>self</span><span>.</span><span>client</span><span>.</span><span>put_object</span><span>(</span><span>Key</span><span>=</span><span>key</span><span>,</span> <span>Body</span><span>=</span><span>file</span><span>,</span> <span>Bucket</span><span>=</span><span>self</span><span>.</span><span>bucket_name</span><span>)</span> <span>url</span> <span>=</span> <span>self</span><span>.</span><span>client</span><span>.</span><span>generate_presigned_url</span><span>(</span> <span>"</span><span>get_object</span><span>"</span><span>,</span> <span>Params</span><span>=</span><span>{</span><span>"</span><span>Bucket</span><span>"</span><span>:</span> <span>self</span><span>.</span><span>bucket_name</span><span>,</span> <span>"</span><span>Key</span><span>"</span><span>:</span> <span>key</span><span>},</span> <span>ExpiresIn</span><span>=</span><span>300</span> <span>)</span> <span>return</span> <span>url</span> <span>class</span> <span>AWSTranslateAdapter</span><span>:</span> <span>"""</span><span> Implementation of TranslationPort using AWSTranslate. </span><span>"""</span> <span>def</span> <span>__init__</span><span>(</span><span>self</span><span>):</span> <span>self</span><span>.</span><span>client</span> <span>=</span> <span>boto3</span><span>.</span><span>client</span><span>(</span><span>"</span><span>translate</span><span>"</span><span>)</span> <span>def</span> <span>translate</span><span>(</span><span>self</span><span>,</span> <span>file</span><span>:</span> <span>bytes</span><span>,</span> <span>content_type</span><span>:</span> <span>str</span><span>,</span> <span>lang</span><span>:</span> <span>str</span><span>)</span> <span>-></span> <span>bytes</span><span>:</span> <span>"""</span><span> Translate input file to lang Args: file: The file to translate lang: The language code to translate to Returns: The translated file </span><span>"""</span> <span>result</span> <span>=</span> <span>self</span><span>.</span><span>client</span><span>.</span><span>translate_document</span><span>(</span> <span>Document</span><span>=</span><span>{</span><span>"</span><span>Content</span><span>"</span><span>:</span> <span>file</span><span>,</span> <span>"</span><span>ContentType</span><span>"</span><span>:</span> <span>content_type</span><span>},</span> <span>SourceLanguageCode</span><span>=</span><span>"</span><span>auto</span><span>"</span><span>,</span> <span>TargetLanguageCode</span><span>=</span><span>lang</span><span>,</span> <span>)</span> <span>translated_file</span> <span>=</span> <span>result</span><span>[</span><span>"</span><span>TranslatedDocument</span><span>"</span><span>][</span><span>"</span><span>Content</span><span>"</span><span>]</span> <span>return</span> <span>translated_file</span>#translate_file/adapters.py import boto3 import base64 from uuid import uuid4 from datetime import datetime from models import Record class RequestPersistenceAdapter: """ Implementation of RequestPersistencePort using DynamoDB as storage. """ def __init__(self, table_name: str): dynamodb = boto3.resource("dynamodb") self.table = dynamodb.Table(table_name) def save(self, input: bytes, output: bytes) -> Record: """ Save input and output byptes to DynamoDB. Args: input: The input bytes to save output: The output bytes to save Returns: Record object containing saved data """ id = str(uuid4()) created_at = datetime.now() input_text = base64.b64encode(input).decode() output_text = base64.b64encode(output).decode() self.table.put_item( Item={ "id": str(uuid4()), "input_text": input_text, "output_text": output_text, "created_at": str(created_at), } ) record = Record(id, input_text, output_text, created_at) return record class FilePersistenceAdpater: """ Implementation of FilePersistencePort using S3 """ def __init__(self, bucket_name: str): self.client = boto3.client("s3") self.bucket_name = bucket_name def save(self, file: bytes, extension: str) -> str: """ Save file as object in S3 bucket Args: file: The file to save Returns: The url to the file """ key = str(uuid4()).replace("-", "") if extension: key += f".{extension}" response = self.client.put_object(Key=key, Body=file, Bucket=self.bucket_name) url = self.client.generate_presigned_url( "get_object", Params={"Bucket": self.bucket_name, "Key": key}, ExpiresIn=300 ) return url class AWSTranslateAdapter: """ Implementation of TranslationPort using AWSTranslate. """ def __init__(self): self.client = boto3.client("translate") def translate(self, file: bytes, content_type: str, lang: str) -> bytes: """ Translate input file to lang Args: file: The file to translate lang: The language code to translate to Returns: The translated file """ result = self.client.translate_document( Document={"Content": file, "ContentType": content_type}, SourceLanguageCode="auto", TargetLanguageCode=lang, ) translated_file = result["TranslatedDocument"]["Content"] return translated_file
Enter fullscreen mode Exit fullscreen mode
Next, we define the Handler class to process requests sent to the Lambda function. The files and target language are uploaded using multipart/form-data.
To preserve the integrity of the binary data AWS API Gateway base64 encodes the body when the binaryMediaTypes field is set to multipart/form-data. The TranslationRequest
class is responsible for:
- Decoding the request body and parsing multipart/form-data.
- Extracting the uploaded files.
- Identifying the target language for translation.
<span>#translate_file/main.py </span><span>import</span> <span>os</span><span>import</span> <span>json</span><span>import</span> <span>base64</span><span>import</span> <span>logging</span><span>from</span> <span>dataclasses</span> <span>import</span> <span>dataclass</span><span>from</span> <span>typing</span> <span>import</span> <span>Dict</span><span>,</span> <span>Any</span><span>,</span> <span>Tuple</span><span>from</span> <span>requests_toolbelt</span> <span>import</span> <span>MultipartDecoder</span><span>from</span> <span>ports</span> <span>import</span> <span>RequestPersistencePort</span><span>,</span> <span>FilePersistencePort</span><span>,</span> <span>TranslationPort</span><span>from</span> <span>adapters</span> <span>import</span> <span>(</span><span>RequestPersistenceAdapter</span><span>,</span><span>FilePersistenceAdpater</span><span>,</span><span>AWSTranslateAdapter</span><span>,</span><span>)</span><span>logger</span> <span>=</span> <span>logging</span><span>.</span><span>getLogger</span><span>(</span><span>__name__</span><span>)</span><span>@dataclass</span><span>class</span> <span>TranslationRequest</span><span>:</span><span>"""</span><span>Dataclass for translation requests</span><span>"""</span><span>files</span><span>:</span> <span>list</span><span>lang</span><span>:</span> <span>str</span><span>@staticmethod</span><span>def</span> <span>get_name</span><span>(</span><span>header</span><span>:</span> <span>bytes</span><span>)</span> <span>-></span> <span>str</span><span>:</span><span>return</span> <span>header</span><span>.</span><span>decode</span><span>().</span><span>split</span><span>(</span><span>"</span><span>;</span><span>"</span><span>)[</span><span>1</span><span>].</span><span>split</span><span>(</span><span>"</span><span>=</span><span>"</span><span>)[</span><span>1</span><span>].</span><span>strip</span><span>(</span><span>'"'</span><span>)</span><span>@staticmethod</span><span>def</span> <span>get_file_info</span><span>(</span><span>header</span><span>:</span> <span>bytes</span><span>)</span> <span>-></span> <span>Tuple</span><span>[</span><span>str</span><span>,</span> <span>str</span><span>]:</span><span>filename</span><span>,</span> <span>extension</span> <span>=</span> <span>None</span><span>,</span> <span>None</span><span>if</span> <span>len</span><span>(</span><span>header</span><span>.</span><span>decode</span><span>().</span><span>split</span><span>(</span><span>"</span><span>;</span><span>"</span><span>))</span> <span>>=</span> <span>3</span><span>:</span><span>filename</span> <span>=</span> <span>header</span><span>.</span><span>decode</span><span>().</span><span>split</span><span>(</span><span>"</span><span>;</span><span>"</span><span>)[</span><span>2</span><span>].</span><span>split</span><span>(</span><span>"</span><span>=</span><span>"</span><span>)[</span><span>1</span><span>].</span><span>strip</span><span>(</span><span>'"'</span><span>)</span><span>file_ext</span> <span>=</span> <span>filename</span><span>.</span><span>split</span><span>(</span><span>"</span><span>.</span><span>"</span><span>)</span><span>if</span> <span>len</span><span>(</span><span>file_ext</span><span>)</span> <span>==</span> <span>2</span><span>:</span><span>extension</span> <span>=</span> <span>file_ext</span><span>[</span><span>1</span><span>]</span><span>return</span> <span>filename</span><span>,</span> <span>extension</span><span>@classmethod</span><span>def</span> <span>from_dict</span><span>(</span><span>cls</span><span>,</span> <span>data</span><span>:</span> <span>Dict</span><span>)</span> <span>-></span> <span>"</span><span>TranslationRequest</span><span>"</span><span>:</span><span>body</span> <span>=</span> <span>data</span><span>[</span><span>"</span><span>body</span><span>"</span><span>]</span><span>content_type</span> <span>=</span> <span>data</span><span>[</span><span>"</span><span>headers</span><span>"</span><span>].</span><span>get</span><span>(</span><span>"</span><span>content-type</span><span>"</span><span>,</span> <span>None</span><span>)</span> <span>or</span> <span>data</span><span>[</span><span>"</span><span>headers</span><span>"</span><span>].</span><span>get</span><span>(</span><span>"</span><span>Content-Type</span><span>"</span><span>,</span> <span>None</span><span>)</span><span>files</span> <span>=</span> <span>[]</span><span>lang</span> <span>=</span> <span>""</span><span>if</span> <span>data</span><span>[</span><span>"</span><span>isBase64Encoded</span><span>"</span><span>]:</span><span>body</span> <span>=</span> <span>base64</span><span>.</span><span>b64decode</span><span>(</span><span>body</span><span>)</span><span>decoder</span> <span>=</span> <span>MultipartDecoder</span><span>(</span><span>body</span><span>,</span> <span>content_type</span><span>)</span><span>for</span> <span>part</span> <span>in</span> <span>decoder</span><span>.</span><span>parts</span><span>:</span><span>filename</span><span>,</span> <span>extension</span> <span>=</span> <span>cls</span><span>.</span><span>get_file_info</span><span>(</span><span>part</span><span>.</span><span>headers</span><span>[</span><span>b</span><span>"</span><span>Content-Disposition</span><span>"</span><span>]</span><span>)</span><span>if</span> <span>filename</span><span>:</span><span>content_type</span> <span>=</span> <span>part</span><span>.</span><span>headers</span><span>[</span><span>b</span><span>"</span><span>content-type</span><span>"</span><span>].</span><span>decode</span><span>()</span><span>files</span><span>.</span><span>append</span><span>((</span><span>part</span><span>.</span><span>content</span><span>,</span> <span>content_type</span><span>,</span> <span>extension</span><span>))</span><span>elif</span> <span>cls</span><span>.</span><span>get_name</span><span>(</span><span>part</span><span>.</span><span>headers</span><span>[</span><span>b</span><span>"</span><span>Content-Disposition</span><span>"</span><span>])</span> <span>==</span> <span>"</span><span>lang</span><span>"</span><span>:</span><span>lang</span> <span>=</span> <span>part</span><span>.</span><span>content</span><span>.</span><span>decode</span><span>()</span><span>if</span> <span>not</span> <span>files</span><span>:</span><span>raise</span> <span>ValueError</span><span>(</span><span>"</span><span>files must be provided</span><span>"</span><span>)</span><span>if</span> <span>not</span> <span>lang</span><span>:</span><span>raise</span> <span>ValueError</span><span>(</span><span>"</span><span>lang must be a string</span><span>"</span><span>)</span><span>return</span> <span>cls</span><span>(</span><span>files</span><span>,</span> <span>lang</span><span>)</span><span>class</span> <span>Handler</span><span>:</span><span>def</span> <span>__init__</span><span>(</span><span>self</span><span>,</span><span>request_port</span><span>:</span> <span>RequestPersistencePort</span><span>,</span><span>file_port</span><span>:</span> <span>FilePersistencePort</span><span>,</span><span>translate_port</span><span>:</span> <span>TranslationPort</span><span>,</span><span>):</span><span>self</span><span>.</span><span>request_port</span> <span>=</span> <span>request_port</span><span>self</span><span>.</span><span>translate_port</span> <span>=</span> <span>translate_port</span><span>self</span><span>.</span><span>file_port</span> <span>=</span> <span>file_port</span><span>def</span> <span>__call__</span><span>(</span><span>self</span><span>,</span> <span>request</span><span>,</span> <span>*</span><span>args</span><span>):</span><span>"""</span><span> Process a translation request. Args: request: dict containing the request data Returns: dict with status code and response body </span><span>"""</span><span>try</span><span>:</span><span>request</span> <span>=</span> <span>TranslationRequest</span><span>.</span><span>from_dict</span><span>(</span><span>request</span><span>)</span><span>except </span><span>(</span><span>json</span><span>.</span><span>JSONDecodeError</span><span>,</span> <span>KeyError</span><span>)</span> <span>as</span> <span>e</span><span>:</span><span>logger</span><span>.</span><span>exception</span><span>(</span><span>f</span><span>"</span><span>Invalid request: </span><span>{</span><span>str</span><span>(</span><span>e</span><span>)</span><span>}</span><span>"</span><span>)</span><span>return</span> <span>self</span><span>.</span><span>_get_error_response</span><span>(</span><span>"</span><span>Invalid request</span><span>"</span><span>,</span> <span>status_code</span><span>=</span><span>400</span><span>)</span><span>urls</span> <span>=</span> <span>[]</span><span>try</span><span>:</span><span>lang</span> <span>=</span> <span>request</span><span>.</span><span>lang</span><span>for</span> <span>file</span> <span>in</span> <span>request</span><span>.</span><span>files</span><span>:</span><span>result</span> <span>=</span> <span>self</span><span>.</span><span>translate_port</span><span>.</span><span>translate</span><span>(</span><span>file</span><span>[</span><span>0</span><span>],</span> <span>file</span><span>[</span><span>1</span><span>],</span> <span>lang</span><span>)</span><span>output</span> <span>=</span> <span>self</span><span>.</span><span>request_port</span><span>.</span><span>save</span><span>(</span><span>file</span><span>[</span><span>0</span><span>],</span> <span>result</span><span>)</span><span>logger</span><span>.</span><span>info</span><span>(</span><span>f</span><span>"</span><span>Saved record with ID: </span><span>{</span><span>output</span><span>.</span><span>id</span><span>}</span><span>"</span><span>)</span><span>url</span> <span>=</span> <span>self</span><span>.</span><span>file_port</span><span>.</span><span>save</span><span>(</span><span>result</span><span>,</span> <span>file</span><span>[</span><span>2</span><span>])</span><span>urls</span><span>.</span><span>append</span><span>(</span><span>url</span><span>)</span><span>return</span> <span>self</span><span>.</span><span>_get_success_response</span><span>(</span><span>urls</span><span>)</span><span>except</span> <span>Exception</span> <span>as</span> <span>e</span><span>:</span><span>logger</span><span>.</span><span>exception</span><span>(</span><span>f</span><span>"</span><span>Error translating: </span><span>{</span><span>str</span><span>(</span><span>e</span><span>)</span><span>}</span><span>"</span><span>)</span><span>return</span> <span>self</span><span>.</span><span>_get_error_response</span><span>(</span><span>"</span><span>An error was encountered</span><span>"</span><span>,</span> <span>status_code</span><span>=</span><span>500</span><span>)</span><span>def</span> <span>_get_success_response</span><span>(</span><span>self</span><span>,</span> <span>urls</span><span>:</span> <span>list</span><span>):</span><span>"""</span><span> Generate a successful response. Args: url: Path to the output file Returns: Dictionary with status code and response body </span><span>"""</span><span>return</span> <span>{</span><span>"</span><span>statusCode</span><span>"</span><span>:</span> <span>"</span><span>200</span><span>"</span><span>,</span><span>"</span><span>headers</span><span>"</span><span>:</span> <span>{</span><span>"</span><span>Access-Control-Allow-Headers</span><span>"</span><span>:</span> <span>"</span><span>Content-Type</span><span>"</span><span>,</span><span>"</span><span>Access-Control-Allow-Origin</span><span>"</span><span>:</span> <span>"</span><span>*</span><span>"</span><span>,</span><span>"</span><span>Access-Control-Allow-Methods</span><span>"</span><span>:</span> <span>"</span><span>OPTIONS,POST</span><span>"</span><span>},</span><span>"</span><span>body</span><span>"</span><span>:</span> <span>json</span><span>.</span><span>dumps</span><span>({</span><span>"</span><span>urls</span><span>"</span><span>:</span> <span>urls</span><span>})}</span><span>def</span> <span>_get_error_response</span><span>(</span><span>self</span><span>,</span> <span>error</span><span>:</span> <span>str</span><span>,</span> <span>status_code</span><span>:</span> <span>int</span><span>):</span><span>"""</span><span> Generate an error response. Args: error: The error message status_code: HTTP status code Returns: Dictionary with status code and response body </span><span>"""</span><span>return</span> <span>{</span><span>"</span><span>statusCode</span><span>"</span><span>:</span> <span>str</span><span>(</span><span>status_code</span><span>),</span><span>"</span><span>headers</span><span>"</span><span>:</span> <span>{</span><span>"</span><span>Access-Control-Allow-Headers</span><span>"</span><span>:</span> <span>"</span><span>Content-Type</span><span>"</span><span>,</span><span>"</span><span>Access-Control-Allow-Origin</span><span>"</span><span>:</span> <span>"</span><span>*</span><span>"</span><span>,</span><span>"</span><span>Access-Control-Allow-Methods</span><span>"</span><span>:</span> <span>"</span><span>OPTIONS,POST</span><span>"</span><span>},</span><span>"</span><span>body</span><span>"</span><span>:</span> <span>json</span><span>.</span><span>dumps</span><span>({</span><span>"</span><span>detail</span><span>"</span><span>:</span> <span>error</span><span>})}</span><span>request_port</span> <span>=</span> <span>RequestPersistenceAdapter</span><span>(</span><span>os</span><span>.</span><span>environ</span><span>.</span><span>get</span><span>(</span><span>"</span><span>DYNAMODB_TABLE</span><span>"</span><span>))</span><span>file_port</span> <span>=</span> <span>FilePersistenceAdpater</span><span>(</span><span>os</span><span>.</span><span>environ</span><span>.</span><span>get</span><span>(</span><span>"</span><span>S3_BUCKET</span><span>"</span><span>))</span><span>translate_port</span> <span>=</span> <span>AWSTranslateAdapter</span><span>()</span><span>handler</span> <span>=</span> <span>Handler</span><span>(</span><span>request_port</span><span>,</span> <span>file_port</span><span>,</span> <span>translate_port</span><span>)</span><span>#translate_file/main.py </span> <span>import</span> <span>os</span> <span>import</span> <span>json</span> <span>import</span> <span>base64</span> <span>import</span> <span>logging</span> <span>from</span> <span>dataclasses</span> <span>import</span> <span>dataclass</span> <span>from</span> <span>typing</span> <span>import</span> <span>Dict</span><span>,</span> <span>Any</span><span>,</span> <span>Tuple</span> <span>from</span> <span>requests_toolbelt</span> <span>import</span> <span>MultipartDecoder</span> <span>from</span> <span>ports</span> <span>import</span> <span>RequestPersistencePort</span><span>,</span> <span>FilePersistencePort</span><span>,</span> <span>TranslationPort</span> <span>from</span> <span>adapters</span> <span>import</span> <span>(</span> <span>RequestPersistenceAdapter</span><span>,</span> <span>FilePersistenceAdpater</span><span>,</span> <span>AWSTranslateAdapter</span><span>,</span> <span>)</span> <span>logger</span> <span>=</span> <span>logging</span><span>.</span><span>getLogger</span><span>(</span><span>__name__</span><span>)</span> <span>@dataclass</span> <span>class</span> <span>TranslationRequest</span><span>:</span> <span>"""</span><span>Dataclass for translation requests</span><span>"""</span> <span>files</span><span>:</span> <span>list</span> <span>lang</span><span>:</span> <span>str</span> <span>@staticmethod</span> <span>def</span> <span>get_name</span><span>(</span><span>header</span><span>:</span> <span>bytes</span><span>)</span> <span>-></span> <span>str</span><span>:</span> <span>return</span> <span>header</span><span>.</span><span>decode</span><span>().</span><span>split</span><span>(</span><span>"</span><span>;</span><span>"</span><span>)[</span><span>1</span><span>].</span><span>split</span><span>(</span><span>"</span><span>=</span><span>"</span><span>)[</span><span>1</span><span>].</span><span>strip</span><span>(</span><span>'"'</span><span>)</span> <span>@staticmethod</span> <span>def</span> <span>get_file_info</span><span>(</span><span>header</span><span>:</span> <span>bytes</span><span>)</span> <span>-></span> <span>Tuple</span><span>[</span><span>str</span><span>,</span> <span>str</span><span>]:</span> <span>filename</span><span>,</span> <span>extension</span> <span>=</span> <span>None</span><span>,</span> <span>None</span> <span>if</span> <span>len</span><span>(</span><span>header</span><span>.</span><span>decode</span><span>().</span><span>split</span><span>(</span><span>"</span><span>;</span><span>"</span><span>))</span> <span>>=</span> <span>3</span><span>:</span> <span>filename</span> <span>=</span> <span>header</span><span>.</span><span>decode</span><span>().</span><span>split</span><span>(</span><span>"</span><span>;</span><span>"</span><span>)[</span><span>2</span><span>].</span><span>split</span><span>(</span><span>"</span><span>=</span><span>"</span><span>)[</span><span>1</span><span>].</span><span>strip</span><span>(</span><span>'"'</span><span>)</span> <span>file_ext</span> <span>=</span> <span>filename</span><span>.</span><span>split</span><span>(</span><span>"</span><span>.</span><span>"</span><span>)</span> <span>if</span> <span>len</span><span>(</span><span>file_ext</span><span>)</span> <span>==</span> <span>2</span><span>:</span> <span>extension</span> <span>=</span> <span>file_ext</span><span>[</span><span>1</span><span>]</span> <span>return</span> <span>filename</span><span>,</span> <span>extension</span> <span>@classmethod</span> <span>def</span> <span>from_dict</span><span>(</span><span>cls</span><span>,</span> <span>data</span><span>:</span> <span>Dict</span><span>)</span> <span>-></span> <span>"</span><span>TranslationRequest</span><span>"</span><span>:</span> <span>body</span> <span>=</span> <span>data</span><span>[</span><span>"</span><span>body</span><span>"</span><span>]</span> <span>content_type</span> <span>=</span> <span>data</span><span>[</span><span>"</span><span>headers</span><span>"</span><span>].</span><span>get</span><span>(</span><span>"</span><span>content-type</span><span>"</span><span>,</span> <span>None</span><span>)</span> <span>or</span> <span>data</span><span>[</span> <span>"</span><span>headers</span><span>"</span><span>].</span><span>get</span><span>(</span><span>"</span><span>Content-Type</span><span>"</span><span>,</span> <span>None</span><span>)</span> <span>files</span> <span>=</span> <span>[]</span> <span>lang</span> <span>=</span> <span>""</span> <span>if</span> <span>data</span><span>[</span><span>"</span><span>isBase64Encoded</span><span>"</span><span>]:</span> <span>body</span> <span>=</span> <span>base64</span><span>.</span><span>b64decode</span><span>(</span><span>body</span><span>)</span> <span>decoder</span> <span>=</span> <span>MultipartDecoder</span><span>(</span><span>body</span><span>,</span> <span>content_type</span><span>)</span> <span>for</span> <span>part</span> <span>in</span> <span>decoder</span><span>.</span><span>parts</span><span>:</span> <span>filename</span><span>,</span> <span>extension</span> <span>=</span> <span>cls</span><span>.</span><span>get_file_info</span><span>(</span> <span>part</span><span>.</span><span>headers</span><span>[</span><span>b</span><span>"</span><span>Content-Disposition</span><span>"</span><span>]</span> <span>)</span> <span>if</span> <span>filename</span><span>:</span> <span>content_type</span> <span>=</span> <span>part</span><span>.</span><span>headers</span><span>[</span><span>b</span><span>"</span><span>content-type</span><span>"</span><span>].</span><span>decode</span><span>()</span> <span>files</span><span>.</span><span>append</span><span>((</span><span>part</span><span>.</span><span>content</span><span>,</span> <span>content_type</span><span>,</span> <span>extension</span><span>))</span> <span>elif</span> <span>cls</span><span>.</span><span>get_name</span><span>(</span><span>part</span><span>.</span><span>headers</span><span>[</span><span>b</span><span>"</span><span>Content-Disposition</span><span>"</span><span>])</span> <span>==</span> <span>"</span><span>lang</span><span>"</span><span>:</span> <span>lang</span> <span>=</span> <span>part</span><span>.</span><span>content</span><span>.</span><span>decode</span><span>()</span> <span>if</span> <span>not</span> <span>files</span><span>:</span> <span>raise</span> <span>ValueError</span><span>(</span><span>"</span><span>files must be provided</span><span>"</span><span>)</span> <span>if</span> <span>not</span> <span>lang</span><span>:</span> <span>raise</span> <span>ValueError</span><span>(</span><span>"</span><span>lang must be a string</span><span>"</span><span>)</span> <span>return</span> <span>cls</span><span>(</span><span>files</span><span>,</span> <span>lang</span><span>)</span> <span>class</span> <span>Handler</span><span>:</span> <span>def</span> <span>__init__</span><span>(</span> <span>self</span><span>,</span> <span>request_port</span><span>:</span> <span>RequestPersistencePort</span><span>,</span> <span>file_port</span><span>:</span> <span>FilePersistencePort</span><span>,</span> <span>translate_port</span><span>:</span> <span>TranslationPort</span><span>,</span> <span>):</span> <span>self</span><span>.</span><span>request_port</span> <span>=</span> <span>request_port</span> <span>self</span><span>.</span><span>translate_port</span> <span>=</span> <span>translate_port</span> <span>self</span><span>.</span><span>file_port</span> <span>=</span> <span>file_port</span> <span>def</span> <span>__call__</span><span>(</span><span>self</span><span>,</span> <span>request</span><span>,</span> <span>*</span><span>args</span><span>):</span> <span>"""</span><span> Process a translation request. Args: request: dict containing the request data Returns: dict with status code and response body </span><span>"""</span> <span>try</span><span>:</span> <span>request</span> <span>=</span> <span>TranslationRequest</span><span>.</span><span>from_dict</span><span>(</span><span>request</span><span>)</span> <span>except </span><span>(</span><span>json</span><span>.</span><span>JSONDecodeError</span><span>,</span> <span>KeyError</span><span>)</span> <span>as</span> <span>e</span><span>:</span> <span>logger</span><span>.</span><span>exception</span><span>(</span><span>f</span><span>"</span><span>Invalid request: </span><span>{</span><span>str</span><span>(</span><span>e</span><span>)</span><span>}</span><span>"</span><span>)</span> <span>return</span> <span>self</span><span>.</span><span>_get_error_response</span><span>(</span><span>"</span><span>Invalid request</span><span>"</span><span>,</span> <span>status_code</span><span>=</span><span>400</span><span>)</span> <span>urls</span> <span>=</span> <span>[]</span> <span>try</span><span>:</span> <span>lang</span> <span>=</span> <span>request</span><span>.</span><span>lang</span> <span>for</span> <span>file</span> <span>in</span> <span>request</span><span>.</span><span>files</span><span>:</span> <span>result</span> <span>=</span> <span>self</span><span>.</span><span>translate_port</span><span>.</span><span>translate</span><span>(</span><span>file</span><span>[</span><span>0</span><span>],</span> <span>file</span><span>[</span><span>1</span><span>],</span> <span>lang</span><span>)</span> <span>output</span> <span>=</span> <span>self</span><span>.</span><span>request_port</span><span>.</span><span>save</span><span>(</span><span>file</span><span>[</span><span>0</span><span>],</span> <span>result</span><span>)</span> <span>logger</span><span>.</span><span>info</span><span>(</span><span>f</span><span>"</span><span>Saved record with ID: </span><span>{</span><span>output</span><span>.</span><span>id</span><span>}</span><span>"</span><span>)</span> <span>url</span> <span>=</span> <span>self</span><span>.</span><span>file_port</span><span>.</span><span>save</span><span>(</span><span>result</span><span>,</span> <span>file</span><span>[</span><span>2</span><span>])</span> <span>urls</span><span>.</span><span>append</span><span>(</span><span>url</span><span>)</span> <span>return</span> <span>self</span><span>.</span><span>_get_success_response</span><span>(</span><span>urls</span><span>)</span> <span>except</span> <span>Exception</span> <span>as</span> <span>e</span><span>:</span> <span>logger</span><span>.</span><span>exception</span><span>(</span><span>f</span><span>"</span><span>Error translating: </span><span>{</span><span>str</span><span>(</span><span>e</span><span>)</span><span>}</span><span>"</span><span>)</span> <span>return</span> <span>self</span><span>.</span><span>_get_error_response</span><span>(</span><span>"</span><span>An error was encountered</span><span>"</span><span>,</span> <span>status_code</span><span>=</span><span>500</span><span>)</span> <span>def</span> <span>_get_success_response</span><span>(</span><span>self</span><span>,</span> <span>urls</span><span>:</span> <span>list</span><span>):</span> <span>"""</span><span> Generate a successful response. Args: url: Path to the output file Returns: Dictionary with status code and response body </span><span>"""</span> <span>return</span> <span>{</span><span>"</span><span>statusCode</span><span>"</span><span>:</span> <span>"</span><span>200</span><span>"</span><span>,</span> <span>"</span><span>headers</span><span>"</span><span>:</span> <span>{</span> <span>"</span><span>Access-Control-Allow-Headers</span><span>"</span><span>:</span> <span>"</span><span>Content-Type</span><span>"</span><span>,</span> <span>"</span><span>Access-Control-Allow-Origin</span><span>"</span><span>:</span> <span>"</span><span>*</span><span>"</span><span>,</span> <span>"</span><span>Access-Control-Allow-Methods</span><span>"</span><span>:</span> <span>"</span><span>OPTIONS,POST</span><span>"</span> <span>},</span> <span>"</span><span>body</span><span>"</span><span>:</span> <span>json</span><span>.</span><span>dumps</span><span>({</span><span>"</span><span>urls</span><span>"</span><span>:</span> <span>urls</span><span>})}</span> <span>def</span> <span>_get_error_response</span><span>(</span><span>self</span><span>,</span> <span>error</span><span>:</span> <span>str</span><span>,</span> <span>status_code</span><span>:</span> <span>int</span><span>):</span> <span>"""</span><span> Generate an error response. Args: error: The error message status_code: HTTP status code Returns: Dictionary with status code and response body </span><span>"""</span> <span>return</span> <span>{</span><span>"</span><span>statusCode</span><span>"</span><span>:</span> <span>str</span><span>(</span><span>status_code</span><span>),</span> <span>"</span><span>headers</span><span>"</span><span>:</span> <span>{</span> <span>"</span><span>Access-Control-Allow-Headers</span><span>"</span><span>:</span> <span>"</span><span>Content-Type</span><span>"</span><span>,</span> <span>"</span><span>Access-Control-Allow-Origin</span><span>"</span><span>:</span> <span>"</span><span>*</span><span>"</span><span>,</span> <span>"</span><span>Access-Control-Allow-Methods</span><span>"</span><span>:</span> <span>"</span><span>OPTIONS,POST</span><span>"</span> <span>},</span> <span>"</span><span>body</span><span>"</span><span>:</span> <span>json</span><span>.</span><span>dumps</span><span>({</span><span>"</span><span>detail</span><span>"</span><span>:</span> <span>error</span><span>})}</span> <span>request_port</span> <span>=</span> <span>RequestPersistenceAdapter</span><span>(</span><span>os</span><span>.</span><span>environ</span><span>.</span><span>get</span><span>(</span><span>"</span><span>DYNAMODB_TABLE</span><span>"</span><span>))</span> <span>file_port</span> <span>=</span> <span>FilePersistenceAdpater</span><span>(</span><span>os</span><span>.</span><span>environ</span><span>.</span><span>get</span><span>(</span><span>"</span><span>S3_BUCKET</span><span>"</span><span>))</span> <span>translate_port</span> <span>=</span> <span>AWSTranslateAdapter</span><span>()</span> <span>handler</span> <span>=</span> <span>Handler</span><span>(</span><span>request_port</span><span>,</span> <span>file_port</span><span>,</span> <span>translate_port</span><span>)</span>#translate_file/main.py import os import json import base64 import logging from dataclasses import dataclass from typing import Dict, Any, Tuple from requests_toolbelt import MultipartDecoder from ports import RequestPersistencePort, FilePersistencePort, TranslationPort from adapters import ( RequestPersistenceAdapter, FilePersistenceAdpater, AWSTranslateAdapter, ) logger = logging.getLogger(__name__) @dataclass class TranslationRequest: """Dataclass for translation requests""" files: list lang: str @staticmethod def get_name(header: bytes) -> str: return header.decode().split(";")[1].split("=")[1].strip('"') @staticmethod def get_file_info(header: bytes) -> Tuple[str, str]: filename, extension = None, None if len(header.decode().split(";")) >= 3: filename = header.decode().split(";")[2].split("=")[1].strip('"') file_ext = filename.split(".") if len(file_ext) == 2: extension = file_ext[1] return filename, extension @classmethod def from_dict(cls, data: Dict) -> "TranslationRequest": body = data["body"] content_type = data["headers"].get("content-type", None) or data[ "headers"].get("Content-Type", None) files = [] lang = "" if data["isBase64Encoded"]: body = base64.b64decode(body) decoder = MultipartDecoder(body, content_type) for part in decoder.parts: filename, extension = cls.get_file_info( part.headers[b"Content-Disposition"] ) if filename: content_type = part.headers[b"content-type"].decode() files.append((part.content, content_type, extension)) elif cls.get_name(part.headers[b"Content-Disposition"]) == "lang": lang = part.content.decode() if not files: raise ValueError("files must be provided") if not lang: raise ValueError("lang must be a string") return cls(files, lang) class Handler: def __init__( self, request_port: RequestPersistencePort, file_port: FilePersistencePort, translate_port: TranslationPort, ): self.request_port = request_port self.translate_port = translate_port self.file_port = file_port def __call__(self, request, *args): """ Process a translation request. Args: request: dict containing the request data Returns: dict with status code and response body """ try: request = TranslationRequest.from_dict(request) except (json.JSONDecodeError, KeyError) as e: logger.exception(f"Invalid request: {str(e)}") return self._get_error_response("Invalid request", status_code=400) urls = [] try: lang = request.lang for file in request.files: result = self.translate_port.translate(file[0], file[1], lang) output = self.request_port.save(file[0], result) logger.info(f"Saved record with ID: {output.id}") url = self.file_port.save(result, file[2]) urls.append(url) return self._get_success_response(urls) except Exception as e: logger.exception(f"Error translating: {str(e)}") return self._get_error_response("An error was encountered", status_code=500) def _get_success_response(self, urls: list): """ Generate a successful response. Args: url: Path to the output file Returns: Dictionary with status code and response body """ return {"statusCode": "200", "headers": { "Access-Control-Allow-Headers": "Content-Type", "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "OPTIONS,POST" }, "body": json.dumps({"urls": urls})} def _get_error_response(self, error: str, status_code: int): """ Generate an error response. Args: error: The error message status_code: HTTP status code Returns: Dictionary with status code and response body """ return {"statusCode": str(status_code), "headers": { "Access-Control-Allow-Headers": "Content-Type", "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "OPTIONS,POST" }, "body": json.dumps({"detail": error})} request_port = RequestPersistenceAdapter(os.environ.get("DYNAMODB_TABLE")) file_port = FilePersistenceAdpater(os.environ.get("S3_BUCKET")) translate_port = AWSTranslateAdapter() handler = Handler(request_port, file_port, translate_port)
Enter fullscreen mode Exit fullscreen mode
Finally we add the required dependencies to a requirement.txt.
#requirements.txtrequests-toolbelt==1.0.0#requirements.txt requests-toolbelt==1.0.0#requirements.txt requests-toolbelt==1.0.0
Enter fullscreen mode Exit fullscreen mode
Super! With our endpoint scripts complete, we now have a functional API for handling translation requests.
In the next installment, we’ll dive into writing the Terraform configuration to provision our infrastructure and deploy our API seamlessly. Stay tuned!
暂无评论内容