diff --git a/http-api/.gitignore b/http-api/.gitignore index 40b371b..74d68c9 100644 --- a/http-api/.gitignore +++ b/http-api/.gitignore @@ -1,2 +1,4 @@ .env -samlocal.json +env.json +dynamodb_volume/ +elastic_volume/ diff --git a/http-api/Makefile b/http-api/Makefile index b047a31..bc9d9bc 100644 --- a/http-api/Makefile +++ b/http-api/Makefile @@ -16,3 +16,9 @@ pytest: htmlcov: pytest uv run python -m http.server 80 -d htmlcov + +up: + docker compose up -d + +down: + docker compose down diff --git a/http-api/compose.yaml b/http-api/compose.yaml new file mode 100644 index 0000000..909a514 --- /dev/null +++ b/http-api/compose.yaml @@ -0,0 +1,34 @@ +services: + dynamodb: + image: amazon/dynamodb-local:latest + container_name: dynamodb + ports: + - 8000:8000 + volumes: + - ./dynamodb_volume:/home/dynamodblocal/data + working_dir: /home/dynamodblocal + command: "-jar DynamoDBLocal.jar -sharedDb -dbPath ./data" + + elastic: + image: docker.elastic.co/elasticsearch/elasticsearch:8.11.3 + container_name: elastic + environment: + - discovery.type=single-node + - bootstrap.memory_lock=true + - xpack.security.enabled=false + - xpack.security.http.ssl.enabled=false + - xpack.security.transport.ssl.enabled=false + - "ES_JAVA_OPTS=-Xms512m -Xmx512m" + - http.cors.allow-origin="*" + - http.cors.enabled=true + - http.cors.allow-credentials=true + - http.cors.allow-methods=OPTIONS, HEAD, GET, POST, PUT, DELETE + - http.cors.allow-headers=X-Requested-With, X-Auth-Token, Content-Type, Content-Length, Authorization, Access-Control-Allow-Headers, Accept, x-elastic-client-meta + ulimits: + memlock: + soft: -1 + hard: -1 + volumes: + - ./elastic_volume:/usr/share/elasticsearch/data + ports: + - 9200:9200 diff --git a/http-api/samlocal.json.sample b/http-api/env.json.sample similarity index 82% rename from http-api/samlocal.json.sample rename to http-api/env.json.sample index 76ab273..0243a3f 100644 --- a/http-api/samlocal.json.sample +++ b/http-api/env.json.sample @@ -4,5 +4,6 @@ "USER_TABLE": "test-users", "ORDER_TABLE": "test-orders", "ENROLLMENT_TABLE": "test-enrollments" + "COURSE_TABLE": "test-courses" } } diff --git a/http-api/routes/courses/__init__.py b/http-api/routes/courses/__init__.py index 0ce483a..6188337 100644 --- a/http-api/routes/courses/__init__.py +++ b/http-api/routes/courses/__init__.py @@ -1,7 +1,6 @@ import json from http import HTTPStatus -import boto3 from aws_lambda_powertools.event_handler import Response, content_types from aws_lambda_powertools.event_handler.api_gateway import Router from elasticsearch import Elasticsearch @@ -9,19 +8,23 @@ from layercake.dynamodb import DynamoDBPersistenceLayer from pydantic import BaseModel import elastic +from boto3clients import dynamodb_client from course import create_course from models import Course, Org from settings import COURSE_TABLE, ELASTIC_CONN router = Router() -dynamodb_client = boto3.client('dynamodb') elastic_client = Elasticsearch(**ELASTIC_CONN) course_layer = DynamoDBPersistenceLayer(COURSE_TABLE, dynamodb_client) -@router.get('/', compress=True, tags=['Course']) +@router.get( + '/', + compress=True, + tags=['Course'], + summary='Get courses', +) def get_courses(): - """Get a list of courses based on the query parameters.""" event = router.current_event query = event.get_query_string_value('query', '{}') page_size = event.get_query_string_value('page_size', '25') diff --git a/http-api/samconfig.toml b/http-api/samconfig.toml index 2937038..c1cc16d 100644 --- a/http-api/samconfig.toml +++ b/http-api/samconfig.toml @@ -10,5 +10,5 @@ image_repositories = [] [default.local_start_api.parameters] debug = true -env_vars = "samlocal.json" +env_vars = "env.json" warm_containers = "EAGER" diff --git a/http-api/uv.lock b/http-api/uv.lock index 5b88ff5..5175699 100644 --- a/http-api/uv.lock +++ b/http-api/uv.lock @@ -444,7 +444,7 @@ wheels = [ [[package]] name = "layercake" -version = "0.1.3" +version = "0.1.4" source = { directory = "../layercake" } dependencies = [ { name = "aws-lambda-powertools", extra = ["all"] }, diff --git a/layercake/layercake/dynamodb.py b/layercake/layercake/dynamodb.py index 14b5def..f38ee0d 100644 --- a/layercake/layercake/dynamodb.py +++ b/layercake/layercake/dynamodb.py @@ -106,14 +106,22 @@ if TYPE_CHECKING: @dataclass class PrefixKey(str): prefix: str + delimiter: str | None = '#' else: class PrefixKey(str): - def __init__(self, prefix: str | None = None) -> None: + def __new__(cls, prefix: str, delimiter: str | None = '#') -> str: + if not delimiter: + return super().__new__(cls, prefix) + + return super().__new__(cls, prefix + delimiter) + + def __init__(self, prefix: str, delimiter: str = '#') -> None: # __init__ is used to store the parameters for later reference. # For immutable types like str, __init__ cannot change the instance's value. self.prefix = prefix + self.delimiter = delimiter class Key(ABC, dict): @@ -672,10 +680,9 @@ class DynamoDBCollection: match key.get(PK), key.get(SK): case ComposeKey(), _: # Remove prefix from Partition Key - prefix = key[PK].prefix + key[PK].delimiter - items = _remove_prefix(items, PK, prefix) + items = _remove_prefix(items, PK, key[PK].prefix + key[PK].delimiter) case _, PrefixKey(): # Remove prefix from Sort Key - items = _remove_prefix(items, SK, key[SK].prefix) + items = _remove_prefix(items, SK, key[SK]) return { 'items': items, diff --git a/layercake/pyproject.toml b/layercake/pyproject.toml index 068eff3..b5bf237 100644 --- a/layercake/pyproject.toml +++ b/layercake/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "layercake" -version = "0.1.3" +version = "0.1.4" description = "Add your description here" readme = "README.md" authors = [ diff --git a/layercake/tests/test_dynamodb.py b/layercake/tests/test_dynamodb.py index aea3162..246d264 100644 --- a/layercake/tests/test_dynamodb.py +++ b/layercake/tests/test_dynamodb.py @@ -58,9 +58,12 @@ def test_keypair(): def test_prefixkey(): key = PrefixKey('emails') - assert key == 'emails' + assert key == 'emails#' assert isinstance(key, PrefixKey) + delimiter = PrefixKey('emails', None) + assert delimiter == 'emails' + def test_transact_write_items( dynamodb_seeds, @@ -195,7 +198,7 @@ def test_collection_get_items( # This data was added from seeds emails = collect.get_items( - KeyPair('5OxmMjL-ujoR5IMGegQz', PrefixKey('emails#')), + KeyPair('5OxmMjL-ujoR5IMGegQz', PrefixKey('emails')), ) assert emails == { 'items': [ diff --git a/layercake/uv.lock b/layercake/uv.lock index cbfde2a..0ec971a 100644 --- a/layercake/uv.lock +++ b/layercake/uv.lock @@ -417,7 +417,7 @@ wheels = [ [[package]] name = "layercake" -version = "0.1.1" +version = "0.1.4" source = { editable = "." } dependencies = [ { name = "aws-lambda-powertools", extra = ["all"] },