-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
22d293c
commit 0f0ab1c
Showing
6 changed files
with
292 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,4 +4,6 @@ | |
**/__pycache__ | ||
.pytest_cache | ||
.ruff_cache | ||
junit | ||
junit | ||
aws/dependencies | ||
*.zip |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import inspect | ||
import re | ||
import os | ||
import subprocess | ||
import shutil | ||
import zipfile | ||
from imaginate_api.date.routes import images_by_date | ||
from imaginate_api.utils import build_result, calculate_date | ||
from imaginate_api.schemas.date_info import DateInfo | ||
|
||
|
||
CWD = os.path.dirname(os.path.realpath(__file__)) | ||
LAMBDA_LIBRARIES = """import os | ||
import json | ||
from enum import Enum | ||
from http import HTTPStatus | ||
from base64 import b64encode | ||
# External libraries from pymongo: | ||
from pymongo import MongoClient | ||
from gridfs import GridFS | ||
from bson.objectid import ObjectId | ||
""" | ||
LAMBDA_SETUP = """db_name = 'imaginate_dev' | ||
conn_uri = os.environ.get('MONGO_TOKEN') | ||
client = MongoClient(conn_uri) | ||
db = client[db_name] | ||
fs = GridFS(db) | ||
""" | ||
LAMBDA_FUNC = """def handler(event, context): | ||
if event and 'queryStringParameters' in event and event['queryStringParameters'] and 'day' in event['queryStringParameters']: | ||
return images_by_date(event['queryStringParameters']['day']) | ||
else: | ||
return {'statusCode': HTTPStatus.BAD_REQUEST, 'body': json.dumps('Invalid date')} | ||
""" | ||
LAMBDA_SUBS = { | ||
"abort": "return {'statusCode': HTTPStatus.BAD_REQUEST, 'body': json.dumps('Invalid date')}", | ||
"@bp.route": "", # Remove this from function decorator | ||
"return jsonify": "return {'statusCode': HTTPStatus.OK, 'body': json.dumps(out)}", | ||
} | ||
|
||
|
||
# Meta-program our source function to substitute Flask related libraries | ||
def edit_source_function(source_function: str) -> str: | ||
for sub in LAMBDA_SUBS: | ||
source_function = re.sub( | ||
r"^(\s*)" + sub + r".*$", | ||
r"\g<1>" + LAMBDA_SUBS[sub], | ||
source_function, | ||
flags=re.MULTILINE, | ||
) | ||
return source_function.strip() | ||
|
||
|
||
if __name__ == "__main__": | ||
# The main function AWS Lambda will directly invoke | ||
source_function = edit_source_function(inspect.getsource(images_by_date)) | ||
|
||
# Order all the required external code needed: helper functions, classes and source function | ||
all_functions = [ | ||
inspect.getsource(DateInfo), | ||
inspect.getsource(build_result), | ||
inspect.getsource(calculate_date), | ||
source_function, | ||
] | ||
|
||
# Save a .py file of our Lambda function code (mainly for verification purposes) | ||
with open("aws/index.py", "w") as f: | ||
f.write(LAMBDA_LIBRARIES + "\n") # Libaries defined as constants | ||
f.write(LAMBDA_SETUP + "\n") | ||
f.write("\n".join(all_functions) + "\n\n") # Functions retrieved from source code | ||
f.write(LAMBDA_FUNC) | ||
|
||
# Following documentation from here: | ||
# https://www.mongodb.com/developer/products/atlas/awslambda-pymongo/ | ||
subprocess.run("mkdir dependencies", shell=True, cwd=CWD) | ||
subprocess.run( | ||
"pip install --upgrade --target ./dependencies pymongo", shell=True, cwd=CWD | ||
) | ||
shutil.make_archive("aws", "zip", "aws/dependencies") | ||
zf = zipfile.ZipFile("aws.zip", "a") | ||
zf.write("aws/index.py", "index.py") | ||
zf.close() | ||
print("aws.zip successfully saved at root directory!") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
# Sourced from: https://gist.github.com/magnetikonline/c314952045eee8e8375b82bc7ec68e88 | ||
AWSTemplateFormatVersion: "2010-09-09" | ||
Description: Imaginate API Gateway and Lambda function | ||
|
||
Parameters: | ||
apiGatewayName: | ||
Type: String | ||
Default: ImaginateApi | ||
apiGatewayStageName: | ||
Type: String | ||
AllowedPattern: '[a-z0-9]+' | ||
Default: call | ||
apiGatewayHTTPMethod: | ||
Type: String | ||
Default: GET | ||
lambdaFunctionName: | ||
Type: String | ||
AllowedPattern: '[a-zA-Z0-9]+[a-zA-Z0-9-]+[a-zA-Z0-9]+' | ||
Default: get-images | ||
mongoToken: | ||
Type: String | ||
NoEcho: true | ||
|
||
Resources: | ||
apiGateway: | ||
Type: AWS::ApiGateway::RestApi | ||
Properties: | ||
Description: Example API Gateway | ||
EndpointConfiguration: | ||
Types: | ||
- REGIONAL | ||
Name: !Ref apiGatewayName | ||
|
||
apiGatewayRootMethod: | ||
Type: AWS::ApiGateway::Method | ||
Properties: | ||
AuthorizationType: NONE | ||
RequestParameters: | ||
method.request.querystring.day: true | ||
HttpMethod: !Ref apiGatewayHTTPMethod | ||
Integration: | ||
IntegrationHttpMethod: POST | ||
Type: AWS_PROXY | ||
RequestParameters: | ||
integration.request.querystring.day: method.request.querystring.day | ||
Uri: !Sub | ||
- arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations | ||
- lambdaArn: !GetAtt lambdaFunction.Arn | ||
ResourceId: !GetAtt apiGateway.RootResourceId | ||
RestApiId: !Ref apiGateway | ||
|
||
apiGatewayDeployment: | ||
Type: AWS::ApiGateway::Deployment | ||
DependsOn: | ||
- apiGatewayRootMethod | ||
Properties: | ||
RestApiId: !Ref apiGateway | ||
StageName: !Ref apiGatewayStageName | ||
|
||
lambdaFunction: | ||
Type: AWS::Lambda::Function | ||
Properties: | ||
Code: | ||
ZipFile: | | ||
def handler(event, context): | ||
pass | ||
Description: Get images by date | ||
FunctionName: !Ref lambdaFunctionName | ||
Handler: index.handler | ||
Role: !GetAtt lambdaIAMRole.Arn | ||
Runtime: python3.12 | ||
Environment: | ||
Variables: | ||
MONGO_TOKEN: !Ref mongoToken | ||
|
||
lambdaApiGatewayInvoke: | ||
Type: AWS::Lambda::Permission | ||
Properties: | ||
Action: lambda:InvokeFunction | ||
FunctionName: !GetAtt lambdaFunction.Arn | ||
Principal: apigateway.amazonaws.com | ||
# NOTE: If the route is NOT at API Gateway root, `SourceArn` would take the form of: | ||
# arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${apiGateway}/${apiGatewayStageName}/${apiGatewayHTTPMethod}/PATH_PART | ||
SourceArn: !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${apiGateway}/${apiGatewayStageName}/${apiGatewayHTTPMethod}/ | ||
|
||
lambdaIAMRole: | ||
Type: AWS::IAM::Role | ||
Properties: | ||
AssumeRolePolicyDocument: | ||
Version: "2012-10-17" | ||
Statement: | ||
- Action: | ||
- sts:AssumeRole | ||
Effect: Allow | ||
Principal: | ||
Service: | ||
- lambda.amazonaws.com | ||
Policies: | ||
- PolicyDocument: | ||
Version: "2012-10-17" | ||
Statement: | ||
- Action: | ||
- logs:CreateLogGroup | ||
- logs:CreateLogStream | ||
- logs:PutLogEvents | ||
Effect: Allow | ||
Resource: | ||
- !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${lambdaFunctionName}:* | ||
PolicyName: lambda | ||
|
||
lambdaLogGroup: | ||
Type: AWS::Logs::LogGroup | ||
Properties: | ||
LogGroupName: !Sub /aws/lambda/${lambdaFunctionName} | ||
RetentionInDays: 90 | ||
|
||
Outputs: | ||
apiGatewayInvokeURL: | ||
Value: !Sub https://${apiGateway}.execute-api.${AWS::Region}.amazonaws.com/${apiGatewayStageName} | ||
lambdaArn: | ||
Value: !GetAtt lambdaFunction.Arn |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import os | ||
import json | ||
from enum import Enum | ||
from http import HTTPStatus | ||
from base64 import b64encode | ||
|
||
# External libraries from pymongo: | ||
from pymongo import MongoClient | ||
from gridfs import GridFS | ||
from bson.objectid import ObjectId | ||
|
||
db_name = "imaginate_dev" | ||
conn_uri = os.environ.get("MONGO_TOKEN") | ||
client = MongoClient(conn_uri) | ||
db = client[db_name] | ||
fs = GridFS(db) | ||
|
||
|
||
class DateInfo(Enum): | ||
START_DATE = 1722484800 # Timestamp for August 1st, 2024 | ||
SECONDS_PER_DAY = 86400 | ||
|
||
|
||
def build_result( | ||
_id: ObjectId, real: bool, date: int, theme: str, status: str, filename: str | ||
): | ||
return { | ||
"filename": filename, | ||
"url": "image/read/" + str(_id), | ||
"real": real, | ||
"date": date, | ||
"theme": theme, | ||
"status": status, | ||
} | ||
|
||
|
||
def calculate_date(day: str | int | None): | ||
if day is not None: | ||
if isinstance(day, str): | ||
day = int(day) | ||
if day >= DateInfo.START_DATE.value: | ||
return day | ||
return DateInfo.START_DATE.value + day * DateInfo.SECONDS_PER_DAY.value | ||
return None | ||
|
||
|
||
def images_by_date(day): | ||
try: | ||
date = calculate_date(day) | ||
if not date: | ||
return {"statusCode": HTTPStatus.BAD_REQUEST, "body": json.dumps("Invalid date")} | ||
except ValueError: | ||
return {"statusCode": HTTPStatus.BAD_REQUEST, "body": json.dumps("Invalid date")} | ||
|
||
res = fs.find({"date": date}) | ||
out = [] | ||
for document in res: | ||
current_res = build_result( | ||
document._id, | ||
document.real, | ||
document.date, | ||
document.theme, | ||
document.status, | ||
document.filename, | ||
) | ||
encoded_data = b64encode(document.read()) | ||
current_res["data"] = encoded_data.decode("utf-8") | ||
out.append(current_res) | ||
return {"statusCode": HTTPStatus.OK, "body": json.dumps(out)} | ||
|
||
|
||
def handler(event, context): | ||
if ( | ||
event | ||
and "queryStringParameters" in event | ||
and "day" in event["queryStringParameters"] | ||
): | ||
return images_by_date(event["queryStringParameters"]["day"]) | ||
else: | ||
return {"statusCode": HTTPStatus.BAD_REQUEST, "body": json.dumps("Invalid date")} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
from enum import Enum | ||
|
||
|
||
class DateInfo(Enum): | ||
START_DATE = 1722484800 # timestamp for august 1st, 2024 | ||
START_DATE = 1722484800 # Timestamp for August 1st, 2024 | ||
SECONDS_PER_DAY = 86400 |