Skip to content

Set up lambda local dev environment#78

Open
kevinfiol wants to merge 12 commits into
mainfrom
kf/local-aws-2
Open

Set up lambda local dev environment#78
kevinfiol wants to merge 12 commits into
mainfrom
kf/local-aws-2

Conversation

@kevinfiol

@kevinfiol kevinfiol commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

Related Issue

#79

Changes Proposed

  • Rename lambda workspace member to did_lambda (see reasoning)
  • Add localstack service to compose.yml
  • Add sqs-poller service to compose.yml
  • Add a way to upload eICR/RR and manifest documents to S3 bucket (docker/uploader.html)
  • Add a way to view S3/DynamoDB resources on local (stackport service)
  • Make pydantic a explicit dependency of core and did_lambda.

Additional Information

  1. For the localstack version, I've pinned it to localstack:community-archive which is the tag that points to the last version of localstack before they began requiring licenses for the free version.

  2. The lambda is run as a separate service (not from within localstack).

  3. This is based on information we've gathered during our last tech sync with Geo and the DiD Engineering Sync — Pipeline Integration & Infra document.

Testing

  1. Run docker compose up --env-file .env.local -d
  2. Navigate to localhost:8081 to see the uploader. Try uploading an eICR and/or an RR.
  3. Navigate to localhost:8080 to see stackport. Navigate to the S3 bucket under the S3 resource to find the manifest.json under DIDInput/ and your eICR/RR under eICRMessageV2/ and/or RRMessageV2, namespaced with the same persistenceId UUID.
  4. Run docker compose logs -f did_lambda to follow lambda logs. You should have seen an event with your upload similar to:
did_lambda-1  | {"level":"INFO","location":"lambda_handler:31","message":"eicr='eICRMessageV2/05008b80-7244-467d-87b0-7ad1c27e31b9/cda_eicr_test.xml' rr=None setId='1.2.840.114350.1.13.380.3.7.1.1' versionNumber=3","timestamp":"2026-07-21 16:24:37,944+0000","service":"service_undefined"}
  1. Run docker compose down to spin down containers

Test watch mode

  1. Run docker compose up --env-file .env.local --watch to watch for changes.
  2. Try uploading an eICR, and view the lambda logs.
  3. Try making a change in lambda_function.py that will show up in the logs.
  4. This should make the did_lambda container restart. On next eICR upload, your log changes should be reflected.

Checklist for Primary Reviewer

  • Any large-scale changes have been deployed and smoke tested
  • Any content updates (user-facing error messages, etc) have been approved by content team
  • Any dependencies introduced have been vetted and discussed

Comment thread docker/lambda.Dockerfile
RUN uv export \
--no-emit-workspace \
--package lambda \
--package did_lambda \

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was the package rename necessary for some reason?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I ran into this issue: https://docs.astral.sh/ruff/rules/invalid-module-name/#why-is-this-bad

Particularly this part:

Further, in order for Python modules to be importable, they must be valid identifiers. As such, they cannot start with a digit, or collide with hard keywords, like import or class.

In our case, lambda is a reserved Python keyword, so having the module named lambda was causing issues with ty and importing sibling modules under /packages/lambda/src/lambda.

DanielSass
DanielSass previously approved these changes Jul 21, 2026

@DanielSass DanielSass left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

curious about the package rename, but otherwise this looks dope! thanks Kevin

description = ""
dependencies = [
"lxml>=6.0.2",
"pydantic>=2.13.4",

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move pydantic to be an explicit dependency of core and did_lambda after running into module resolution issues with the lambda container.

Comment on lines +17 to +36
def lambda_handler(event: SQSEvent, _context: LambdaContext) -> dict:
"""Download manifests and the XML objects they reference."""
for sqs_record in event.records:
for s3_record in sqs_record.decoded_nested_s3_event.records:
if s3_record.event_name != "ObjectCreated:Put":
continue

bucket = s3_record.s3.bucket.name
manifest_key = s3_record.s3.get_object.key

# fetch manifest
try:
manifest = _get_manifest(bucket, manifest_key)
for file in manifest.files:
logger.info(file)
except ValidationError:
logger.error("Invalid manifest file")
raise

return {"statusCode": 200, "message": "OK"}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented some basic functionality in the lambda for us to refine in #76

Right now all it does is:

  1. Listen for SQSEvents, specifically events from S3 with the ObjectCreated:Put event name
  2. Fetches the manifest.json from the event
  3. Parses it using a pydantic model, and logs the data

Comment on lines +1 to +18
"""Models for S3 manifest files."""

from pydantic import BaseModel, Field


class ManifestRecord(BaseModel):
"""An eICR and its reportability response."""

eicr: str
rr: str | None = None
setId: str
versionNumber: int


class Manifest(BaseModel):
"""A manifest containing files to process."""

files: list[ManifestRecord] = Field(alias="Files")

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suspect these models to change with further discussions with APHL.

I've left the RR as "optional" to allow us to test eICRs locally without having to also upload RRs.

Comment thread lefthook.yml

typecheck:
glob: "*.py"
glob: "packages/**/*.py"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor change to lefthook to only lint/typecheck Python in our packages.

This was causing an issue with docker/sqs-poller.py; since it is a self-contained script, ty was reporting that it could not resolve its dependencies (boto3 and httpx)

Comment thread docker/sqs-poller.py
@@ -0,0 +1,95 @@
#!/usr/bin/env -S uv run

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a generic script that is run as a separate service in compose.yml.

Since we are not running the lambda as a service under localstack, we need another way for SQS events to invoke the lambda.

@kevinfiol
kevinfiol marked this pull request as ready for review July 21, 2026 19:10
@kevinfiol
kevinfiol requested a review from a team as a code owner July 21, 2026 19:10
Comment thread compose.yml
path: ./uv.lock

localstack:
image: docker.io/localstack/localstack:community-archive

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mentioned this during share time, but I briefly tested using floci by changing the image here, and it worked seamlessly without having to change any of the init scripts or env variables.

I haven't evaluated other AWS emulator alternatives yet, so this is something we could explore in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants