add courses

This commit is contained in:
2025-07-02 19:50:53 -03:00
parent 8901f44ca3
commit 9b927bdbcd
14 changed files with 1413 additions and 0 deletions

5
courses-events/Makefile Normal file
View File

@@ -0,0 +1,5 @@
build:
sam build --use-container
deploy: build
sam deploy --debug

View File

@@ -0,0 +1,13 @@
import os
import boto3
def get_dynamodb_client():
if os.getenv('AWS_LAMBDA_FUNCTION_NAME'):
return boto3.client('dynamodb')
return boto3.client('dynamodb', endpoint_url='http://localhost:8000')
dynamodb_client = get_dynamodb_client()

View File

@@ -0,0 +1,4 @@
import os
COURSE_TABLE: str = os.environ.get('COURSE_TABLE') # type: ignore
API_URL = 'https://eduseg.com.br/api'

View File

View File

@@ -0,0 +1,35 @@
import requests
from aws_lambda_powertools.utilities.data_classes import (
DynamoDBStreamEvent,
event_source,
)
from aws_lambda_powertools.utilities.typing import LambdaContext
from layercake.dateutils import now
from layercake.dynamodb import DynamoDBPersistenceLayer
from boto3clients import dynamodb_client
from config import API_URL, COURSE_TABLE
course_layer = DynamoDBPersistenceLayer(COURSE_TABLE, dynamodb_client)
@event_source(data_class=DynamoDBStreamEvent)
def lambda_handler(event: DynamoDBStreamEvent, context: LambdaContext) -> bool:
r = requests.get(f'{API_URL}/courses.json')
r.raise_for_status()
data = r.json()
now_ = now()
with course_layer.batch_writer() as batch:
for course in data:
batch.put_item(
{
'id': {'S': course['id']},
'sk': {'S': 'metadata#unit_price'},
'unit_price': {'N': str(course['metadata']['unit_price'])},
'create_date': {'S': now_.isoformat()},
}
)
return True

View File

@@ -0,0 +1,33 @@
[project]
name = "courses-events"
version = "0.1.0"
description = ""
readme = ""
requires-python = ">=3.13"
dependencies = [
"layercake",
]
[dependency-groups]
dev = [
"pytest>=8.3.4",
"pytest-cov>=6.0.0",
"ruff>=0.9.1",
]
[tool.pytest.ini_options]
pythonpath = ["app/"]
addopts = "--cov --cov-report html -v"
[tool.ruff]
target-version = "py311"
src = ["app"]
[tool.ruff.format]
quote-style = "single"
[tool.ruff.lint]
select = ["E", "F", "I"]
[tool.uv.sources]
layercake = { path = "../layercake" }

View File

@@ -0,0 +1,3 @@
{
"extraPaths": ["app/"]
}

View File

@@ -0,0 +1,9 @@
version = 0.1
[default.deploy.parameters]
stack_name = "saladeaula-courses-events"
resolve_s3 = true
s3_prefix = "courses-events"
region = "sa-east-1"
confirm_changeset = false
capabilities = "CAPABILITY_IAM"
image_repositories = []

View File

@@ -0,0 +1,47 @@
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Parameters:
CourseTable:
Type: String
Default: saladeaula_courses
Globals:
Function:
CodeUri: app/
Runtime: python3.13
Tracing: Active
Architectures:
- x86_64
Layers:
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:79
Environment:
Variables:
TZ: America/Sao_Paulo
LOG_LEVEL: DEBUG
POWERTOOLS_LOGGER_SAMPLE_RATE: 0.1
POWERTOOLS_LOGGER_LOG_EVENT: true
DYNAMODB_PARTITION_KEY: id
COURSE_TABLE: !Ref CourseTable
Resources:
EventLog:
Type: AWS::Logs::LogGroup
Properties:
RetentionInDays: 90
EventDailySyncCourseMetadataFunction:
Type: AWS::Serverless::Function
Properties:
Handler: events.daily_sync_course_metadata.lambda_handler
LoggingConfig:
LogGroup: !Ref EventLog
Policies:
- DynamoDBWritePolicy:
TableName: !Ref CourseTable
Events:
Rule:
Type: ScheduleV2
Properties:
ScheduleExpression: cron(0 0 * * ? *)
ScheduleExpressionTimezone: America/Sao_Paulo

View File

View File

@@ -0,0 +1,61 @@
import os
from dataclasses import dataclass
import pytest
PYTEST_TABLE_NAME = 'pytest'
PK = 'id'
SK = 'sk'
# https://docs.pytest.org/en/7.1.x/reference/reference.html#pytest.hookspec.pytest_configure
def pytest_configure():
os.environ['TZ'] = 'America/Sao_Paulo'
os.environ['DYNAMODB_PARTITION_KEY'] = PK
os.environ['DYNAMODB_SORT_KEY'] = SK
os.environ['COURSE_TABLE'] = PYTEST_TABLE_NAME
@dataclass
class LambdaContext:
function_name: str = 'test'
memory_limit_in_mb: int = 128
invoked_function_arn: str = 'arn:aws:lambda:eu-west-1:809313241:function:test'
aws_request_id: str = '52fdfc07-2182-154f-163f-5f0f9a621d72'
@pytest.fixture
def lambda_context() -> LambdaContext:
return LambdaContext()
@pytest.fixture
def dynamodb_client():
from boto3clients import dynamodb_client as client
client.create_table(
AttributeDefinitions=[
{'AttributeName': PK, 'AttributeType': 'S'},
{'AttributeName': SK, 'AttributeType': 'S'},
],
TableName=PYTEST_TABLE_NAME,
KeySchema=[
{'AttributeName': PK, 'KeyType': 'HASH'},
{'AttributeName': SK, 'KeyType': 'RANGE'},
],
ProvisionedThroughput={
'ReadCapacityUnits': 123,
'WriteCapacityUnits': 123,
},
)
yield client
client.delete_table(TableName=PYTEST_TABLE_NAME)
@pytest.fixture()
def dynamodb_persistence_layer(dynamodb_client):
from layercake.dynamodb import DynamoDBPersistenceLayer
return DynamoDBPersistenceLayer(PYTEST_TABLE_NAME, dynamodb_client)

View File

View File

@@ -0,0 +1,18 @@
import app.events.daily_sync_course_metadata as app
from aws_lambda_powertools.utilities.typing import LambdaContext
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
def test_daily_sync_course_metadata(
dynamodb_client,
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
lambda_context: LambdaContext,
):
app.lambda_handler({}, lambda_context) # type: ignore
collection = dynamodb_persistence_layer.collection
item = collection.get_item(
KeyPair('a810dd22-56c0-4d9b-8cd2-7e2ee9c45839', 'metadata#unit_price')
)
assert item

1185
courses-events/uv.lock generated Normal file

File diff suppressed because it is too large Load Diff