lambda-forge-cli-aws 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. lambda_forge_cli_aws-1.0.0.dist-info/METADATA +352 -0
  2. lambda_forge_cli_aws-1.0.0.dist-info/RECORD +80 -0
  3. lambda_forge_cli_aws-1.0.0.dist-info/WHEEL +4 -0
  4. lambda_forge_cli_aws-1.0.0.dist-info/entry_points.txt +2 -0
  5. lambda_forge_cli_aws-1.0.0.dist-info/licenses/LICENSE +189 -0
  6. lambdaforge/__init__.py +10 -0
  7. lambdaforge/bedrock_client.py +109 -0
  8. lambdaforge/cli.py +428 -0
  9. lambdaforge/deploy.py +308 -0
  10. lambdaforge/logs.py +185 -0
  11. lambdaforge/renderer.py +72 -0
  12. lambdaforge/spec_generator.py +171 -0
  13. lambdaforge/status.py +111 -0
  14. lambdaforge/templates.py +106 -0
  15. lambdaforge/upgrade.py +231 -0
  16. templates/python-api/cookiecutter.json +12 -0
  17. templates/python-api/{{cookiecutter.project_slug}}/.github/workflows/dependabot.yml +22 -0
  18. templates/python-api/{{cookiecutter.project_slug}}/.github/workflows/deploy.yml +90 -0
  19. templates/python-api/{{cookiecutter.project_slug}}/.gitignore +65 -0
  20. templates/python-api/{{cookiecutter.project_slug}}/Dockerfile +11 -0
  21. templates/python-api/{{cookiecutter.project_slug}}/README.md +130 -0
  22. templates/python-api/{{cookiecutter.project_slug}}/infrastructure/app.js +26 -0
  23. templates/python-api/{{cookiecutter.project_slug}}/infrastructure/cdk.json +20 -0
  24. templates/python-api/{{cookiecutter.project_slug}}/infrastructure/lib/stack.js +155 -0
  25. templates/python-api/{{cookiecutter.project_slug}}/infrastructure/package.json +17 -0
  26. templates/python-api/{{cookiecutter.project_slug}}/pyproject.toml +66 -0
  27. templates/python-api/{{cookiecutter.project_slug}}/requirements-dev.txt +8 -0
  28. templates/python-api/{{cookiecutter.project_slug}}/requirements.txt +3 -0
  29. templates/python-api/{{cookiecutter.project_slug}}/src/handler.py +93 -0
  30. templates/python-api/{{cookiecutter.project_slug}}/src/utils/logger.py +39 -0
  31. templates/python-api/{{cookiecutter.project_slug}}/tests/conftest.py +22 -0
  32. templates/python-api/{{cookiecutter.project_slug}}/tests/test_handler.py +68 -0
  33. templates/python-cron/cookiecutter.json +13 -0
  34. templates/python-cron/{{cookiecutter.project_slug}}/.gitignore +65 -0
  35. templates/python-cron/{{cookiecutter.project_slug}}/Dockerfile +11 -0
  36. templates/python-cron/{{cookiecutter.project_slug}}/infrastructure/app.js +25 -0
  37. templates/python-cron/{{cookiecutter.project_slug}}/infrastructure/cdk.json +3 -0
  38. templates/python-cron/{{cookiecutter.project_slug}}/infrastructure/lib/stack.js +55 -0
  39. templates/python-cron/{{cookiecutter.project_slug}}/infrastructure/package.json +17 -0
  40. templates/python-cron/{{cookiecutter.project_slug}}/pyproject.toml +56 -0
  41. templates/python-cron/{{cookiecutter.project_slug}}/requirements.txt +2 -0
  42. templates/python-cron/{{cookiecutter.project_slug}}/src/handler.py +53 -0
  43. templates/python-cron/{{cookiecutter.project_slug}}/tests/conftest.py +22 -0
  44. templates/python-cron/{{cookiecutter.project_slug}}/tests/test_handler.py +48 -0
  45. templates/python-queue/cookiecutter.json +12 -0
  46. templates/python-queue/{{cookiecutter.project_slug}}/.gitignore +65 -0
  47. templates/python-queue/{{cookiecutter.project_slug}}/Dockerfile +11 -0
  48. templates/python-queue/{{cookiecutter.project_slug}}/infrastructure/app.js +25 -0
  49. templates/python-queue/{{cookiecutter.project_slug}}/infrastructure/cdk.json +3 -0
  50. templates/python-queue/{{cookiecutter.project_slug}}/infrastructure/lib/stack.js +79 -0
  51. templates/python-queue/{{cookiecutter.project_slug}}/infrastructure/package.json +17 -0
  52. templates/python-queue/{{cookiecutter.project_slug}}/pyproject.toml +56 -0
  53. templates/python-queue/{{cookiecutter.project_slug}}/requirements.txt +2 -0
  54. templates/python-queue/{{cookiecutter.project_slug}}/src/handler.py +64 -0
  55. templates/python-queue/{{cookiecutter.project_slug}}/tests/conftest.py +22 -0
  56. templates/python-queue/{{cookiecutter.project_slug}}/tests/test_handler.py +60 -0
  57. templates/python-s3/cookiecutter.json +13 -0
  58. templates/python-s3/{{cookiecutter.project_slug}}/.gitignore +65 -0
  59. templates/python-s3/{{cookiecutter.project_slug}}/Dockerfile +11 -0
  60. templates/python-s3/{{cookiecutter.project_slug}}/infrastructure/app.js +25 -0
  61. templates/python-s3/{{cookiecutter.project_slug}}/infrastructure/cdk.json +3 -0
  62. templates/python-s3/{{cookiecutter.project_slug}}/infrastructure/lib/stack.js +89 -0
  63. templates/python-s3/{{cookiecutter.project_slug}}/infrastructure/package.json +17 -0
  64. templates/python-s3/{{cookiecutter.project_slug}}/pyproject.toml +56 -0
  65. templates/python-s3/{{cookiecutter.project_slug}}/requirements.txt +2 -0
  66. templates/python-s3/{{cookiecutter.project_slug}}/src/handler.py +86 -0
  67. templates/python-s3/{{cookiecutter.project_slug}}/tests/conftest.py +22 -0
  68. templates/python-s3/{{cookiecutter.project_slug}}/tests/test_handler.py +71 -0
  69. templates/python-stream/cookiecutter.json +12 -0
  70. templates/python-stream/{{cookiecutter.project_slug}}/.gitignore +65 -0
  71. templates/python-stream/{{cookiecutter.project_slug}}/Dockerfile +11 -0
  72. templates/python-stream/{{cookiecutter.project_slug}}/infrastructure/app.js +25 -0
  73. templates/python-stream/{{cookiecutter.project_slug}}/infrastructure/cdk.json +3 -0
  74. templates/python-stream/{{cookiecutter.project_slug}}/infrastructure/lib/stack.js +81 -0
  75. templates/python-stream/{{cookiecutter.project_slug}}/infrastructure/package.json +17 -0
  76. templates/python-stream/{{cookiecutter.project_slug}}/pyproject.toml +56 -0
  77. templates/python-stream/{{cookiecutter.project_slug}}/requirements.txt +2 -0
  78. templates/python-stream/{{cookiecutter.project_slug}}/src/handler.py +69 -0
  79. templates/python-stream/{{cookiecutter.project_slug}}/tests/conftest.py +22 -0
  80. templates/python-stream/{{cookiecutter.project_slug}}/tests/test_handler.py +64 -0
@@ -0,0 +1,90 @@
1
+ name: Deploy {{ cookiecutter.project_name }}
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ id-token: write
10
+ contents: read
11
+
12
+ env:
13
+ AWS_REGION: us-east-1
14
+ STACK_NAME: {{ cookiecutter.project_slug }}-prod
15
+
16
+ jobs:
17
+ test:
18
+ name: Test + Lint
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+
23
+ - uses: actions/setup-python@v5
24
+ with:
25
+ python-version: "3.12"
26
+ cache: "pip"
27
+
28
+ - name: Install dependencies
29
+ run: |
30
+ python -m pip install --upgrade pip
31
+ pip install -r requirements-dev.txt
32
+
33
+ - name: Lint (ruff)
34
+ run: ruff check .
35
+
36
+ - name: Type check (mypy)
37
+ run: mypy src/
38
+
39
+ - name: Test (pytest)
40
+ run: pytest --cov-report=xml
41
+ env:
42
+ AWS_DEFAULT_REGION: us-east-1
43
+ AWS_ACCESS_KEY_ID: test
44
+ AWS_SECRET_ACCESS_KEY: test
45
+
46
+ - name: Upload coverage
47
+ uses: codecov/codecov-action@v3
48
+ if: always()
49
+ with:
50
+ file: coverage.xml
51
+
52
+ deploy:
53
+ name: Deploy to AWS
54
+ needs: test
55
+ runs-on: ubuntu-latest
56
+ environment: production
57
+ steps:
58
+ - uses: actions/checkout@v4
59
+
60
+ - name: Setup Node.js
61
+ uses: actions/setup-node@v4
62
+ with:
63
+ node-version: "20"
64
+
65
+ - uses: actions/setup-python@v5
66
+ with:
67
+ python-version: "3.12"
68
+
69
+ {% raw %}
70
+ - name: Configure AWS credentials (OIDC)
71
+ uses: aws-actions/configure-aws-credentials@v4
72
+ with:
73
+ role-to-assume: ${{ secrets.AWS_DEPLOY_ROLE_ARN }}
74
+ aws-region: ${{ env.AWS_REGION }}
75
+
76
+ - name: Install CDK
77
+ run: npm install -g aws-cdk
78
+
79
+ - name: Install CDK dependencies
80
+ working-directory: infrastructure
81
+ run: npm install
82
+
83
+ - name: CDK Diff
84
+ working-directory: infrastructure
85
+ run: cdk diff ${{ env.STACK_NAME }} || true
86
+
87
+ - name: CDK Deploy
88
+ working-directory: infrastructure
89
+ run: cdk deploy ${{ env.STACK_NAME }} --require-approval never
90
+ {% endraw %}
@@ -0,0 +1,65 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib64/
14
+ parts/
15
+ sdist/
16
+ var/
17
+ wheels/
18
+ *.egg-info/
19
+ .installed.cfg
20
+ *.egg
21
+ MANIFEST
22
+
23
+ # Virtual environments
24
+ .venv/
25
+ venv/
26
+ env/
27
+ ENV/
28
+
29
+ # Testing
30
+ .pytest_cache/
31
+ .coverage
32
+ .coverage.*
33
+ htmlcov/
34
+ .tox/
35
+ .nox/
36
+ coverage.xml
37
+ *.cover
38
+
39
+ # Type checking
40
+ .mypy_cache/
41
+ .ruff_cache/
42
+
43
+ # IDEs
44
+ .idea/
45
+ .vscode/
46
+ *.swp
47
+ *.swo
48
+ .DS_Store
49
+
50
+ # CDK
51
+ cdk.out/
52
+ cdk.context.json
53
+
54
+ # Lambda
55
+ *.zip
56
+
57
+ # Environment
58
+ .env
59
+ .env.local
60
+ .env.*.local
61
+
62
+ # Secrets
63
+ *.pem
64
+ *.key
65
+ secrets.yml
@@ -0,0 +1,11 @@
1
+ FROM public.ecr.aws/lambda/python:3.12-arm64
2
+
3
+ # Install dependencies first for better layer caching
4
+ COPY requirements.txt ${LAMBDA_TASK_ROOT}/
5
+ RUN pip install --no-cache-dir -r requirements.txt
6
+
7
+ # Copy function code
8
+ COPY src/ ${LAMBDA_TASK_ROOT}/
9
+
10
+ # Set the handler
11
+ CMD [ "handler.handler" ]
@@ -0,0 +1,130 @@
1
+ # {{ cookiecutter.project_name }}
2
+
3
+ > {{ cookiecutter.description }}
4
+
5
+ Serverless API built with AWS Lambda + API Gateway, generated by [LambdaForge](https://github.com/lambdaforge/lambdaforge).
6
+
7
+ ## Architecture
8
+
9
+ ```
10
+ Client -> API Gateway (REST) -> Lambda (ARM64) -> DynamoDB
11
+ |
12
+ -> CloudWatch Logs/Metrics/Traces (X-Ray)
13
+ ```
14
+
15
+ - **Runtime**: Python 3.12 (ARM64 - 20% cheaper, 19% faster than x86_64)
16
+ - **Observability**: aws-lambda-powertools (structured logs, metrics, traces)
17
+ - **IaC**: AWS CDK TypeScript
18
+ - **Cost**: Designed to fit in AWS Free Tier (<1M req/month)
19
+
20
+ ## Project Structure
21
+
22
+ ```
23
+ {{ cookiecutter.project_slug }}/
24
+ ├── src/
25
+ │ ├── handler.py # Lambda entry point
26
+ │ └── utils/
27
+ │ └── logger.py # Powertools logger config
28
+ ├── tests/
29
+ │ ├── conftest.py # pytest fixtures (moto for AWS mocking)
30
+ │ └── test_handler.py # Unit + integration tests
31
+ ├── infrastructure/
32
+ │ ├── app.js # CDK app entry (JavaScript)
33
+ │ ├── cdk.json # CDK config
34
+ │ ├── package.json # CDK dependencies
35
+ │ └── lib/
36
+ │ └── stack.js # CDK stack (multi-env: dev/staging/prod)
37
+ ├── .kiro/
38
+ │ └── specs/ # Generated Kiro specs
39
+ ├── Dockerfile # Container image (used by CDK)
40
+ ├── pyproject.toml # Dependencies + tooling config
41
+ └── README.md
42
+ ```
43
+
44
+ ## Local Development
45
+
46
+ ```bash
47
+ # Install dependencies
48
+ pip install -r requirements-dev.txt
49
+
50
+ # Run tests (uses moto for AWS mocking - no real AWS calls)
51
+ pytest
52
+
53
+ # Run with type checking + linting
54
+ ruff check .
55
+ mypy src/
56
+ ```
57
+
58
+ ## Deployment
59
+
60
+ ```bash
61
+ # First time: bootstrap CDK
62
+ npx cdk bootstrap aws://ACCOUNT_ID/REGION
63
+
64
+ # Deploy
65
+ npx cdk deploy
66
+
67
+ # Or via LambdaForge
68
+ lambdaforge deploy --stack {{ cookiecutter.project_slug }}-dev
69
+ ```
70
+
71
+ ## API
72
+
73
+ ### `POST /items`
74
+
75
+ Create a new item.
76
+
77
+ **Request body**:
78
+ ```json
79
+ {
80
+ "key": "user-123",
81
+ "data": {
82
+ "name": "Ada Lovelace",
83
+ "role": "engineer"
84
+ }
85
+ }
86
+ ```
87
+
88
+ **Response** (201 Created):
89
+ ```json
90
+ {
91
+ "key": "user-123",
92
+ "data": { "name": "Ada Lovelace", "role": "engineer" },
93
+ "createdAt": "2026-07-23T10:12:43.123Z"
94
+ }
95
+ ```
96
+
97
+ ### `GET /items/{key}`
98
+
99
+ Retrieve an item by key.
100
+
101
+ **Response** (200 OK):
102
+ ```json
103
+ {
104
+ "key": "user-123",
105
+ "data": { "name": "Ada Lovelace", "role": "engineer" },
106
+ "createdAt": "2026-07-23T10:12:43.123Z",
107
+ "updatedAt": "2026-07-23T10:12:43.123Z"
108
+ }
109
+ ```
110
+
111
+ ## Observability
112
+
113
+ - **Logs**: CloudWatch Logs (`/aws/lambda/{{ cookiecutter.project_slug }}`)
114
+ - **Metrics**: CloudWatch Metrics (custom namespace `{{ cookiecutter.project_slug }}`)
115
+ - **Traces**: X-Ray (end-to-end through API Gateway + Lambda + DynamoDB)
116
+ - **Dashboard**: CloudWatch Dashboard created by CDK stack
117
+
118
+ ## Cost Estimate (Free Tier)
119
+
120
+ | Resource | Free Tier | Estimated |
121
+ |----------|-----------|-----------|
122
+ | Lambda | 1M req + 400k GB-s | $0 (under) |
123
+ | API Gateway | 1M calls | $0 (under) |
124
+ | DynamoDB | 25 GB + 25 WCU/RCU | $0 (under) |
125
+
126
+ Beyond Free Tier (worst case 10M req/month): **~$5/month**
127
+
128
+ ## License
129
+
130
+ MIT © {{ cookiecutter.author_name }}
@@ -0,0 +1,26 @@
1
+ const cdk = require('aws-cdk-lib');
2
+ const { {{ cookiecutter.project_name|replace('-', '')|capitalize }}Stack } = require('./lib/stack');
3
+
4
+ const app = new cdk.App();
5
+
6
+ // Environment from CDK context (default: dev)
7
+ const envName = app.node.tryGetContext('env') || 'dev';
8
+
9
+ new {{ cookiecutter.project_name|replace('-', '')|capitalize }}Stack(app, `{{ cookiecutter.project_name }}-${envName}`, {
10
+ stackName: `{{ cookiecutter.project_name }}-${envName}`,
11
+ env: {
12
+ account: process.env.CDK_DEFAULT_ACCOUNT,
13
+ region: process.env.CDK_DEFAULT_REGION || '{{ cookiecutter.aws_region }}',
14
+ },
15
+ description: `{{ cookiecutter.description }} (${envName})`,
16
+ tags: {
17
+ Project: '{{ cookiecutter.project_name }}',
18
+ Environment: envName,
19
+ ManagedBy: 'LambdaForge',
20
+ CostCenter: '{{ cookiecutter.project_name }}',
21
+ },
22
+ // Pass environment to the stack
23
+ envName: envName,
24
+ });
25
+
26
+ app.synth();
@@ -0,0 +1,20 @@
1
+ {
2
+ "app": "node app.js",
3
+ "watch": {
4
+ "include": ["**"],
5
+ "exclude": [
6
+ "README.md",
7
+ "cdk*.json",
8
+ "node_modules",
9
+ "tests"
10
+ ]
11
+ },
12
+ "context": {
13
+ "@aws-cdk/aws-lambda:recognizeLayerVersion": true,
14
+ "@aws-cdk/core:checkSecretUsage": true,
15
+ "@aws-cdk/core:target-partitions": ["aws", "aws-cn"],
16
+ "@aws-cdk/aws-iam:minimizePolicies": true,
17
+ "@aws-cdk/customresources:installLatestAwsSdkDefault": false,
18
+ "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true
19
+ }
20
+ }
@@ -0,0 +1,155 @@
1
+ const cdk = require('aws-cdk-lib');
2
+ const lambda = require('aws-cdk-lib/aws-lambda');
3
+ const apigateway = require('aws-cdk-lib/aws-apigateway');
4
+ const dynamodb = require('aws-cdk-lib/aws-dynamodb');
5
+ const logs = require('aws-cdk-lib/aws-logs');
6
+ const cloudwatch = require('aws-cdk-lib/aws-cloudwatch');
7
+
8
+ /**
9
+ * Environment-specific configuration.
10
+ * Adjust memory, timeout, and policies per environment.
11
+ */
12
+ const ENV_CONFIG = {
13
+ dev: {
14
+ memorySize: {{ cookiecutter.memory_mb }},
15
+ timeout: {{ cookiecutter.timeout_seconds }},
16
+ logRetention: logs.RetentionDays.ONE_DAY,
17
+ removalPolicy: cdk.RemovalPolicy.DESTROY,
18
+ throttlingRate: 50,
19
+ throttlingBurst: 100,
20
+ },
21
+ staging: {
22
+ memorySize: {{ cookiecutter.memory_mb }},
23
+ timeout: {{ cookiecutter.timeout_seconds }},
24
+ logRetention: logs.RetentionDays.ONE_WEEK,
25
+ removalPolicy: cdk.RemovalPolicy.DESTROY,
26
+ throttlingRate: 100,
27
+ throttlingBurst: 200,
28
+ },
29
+ prod: {
30
+ memorySize: 1024,
31
+ timeout: {{ cookiecutter.timeout_seconds }},
32
+ logRetention: logs.RetentionDays.ONE_MONTH,
33
+ removalPolicy: cdk.RemovalPolicy.RETAIN,
34
+ throttlingRate: 200,
35
+ throttlingBurst: 400,
36
+ },
37
+ };
38
+
39
+ /**
40
+ * CDK Stack for {{ cookiecutter.project_name }}
41
+ *
42
+ * Generated by LambdaForge — production-ready Lambda with:
43
+ * - API Gateway REST API with throttling
44
+ * - DynamoDB table (on-demand)
45
+ * - X-Ray tracing
46
+ * - CloudWatch logs + dashboard
47
+ * - Well-Architected tags
48
+ * - Multi-environment support (dev/staging/prod)
49
+ */
50
+ class {{ cookiecutter.project_name|replace('-', '')|capitalize }}Stack extends cdk.Stack {
51
+ constructor(scope, id, props) {
52
+ super(scope, id, props);
53
+
54
+ const envName = props.envName || 'dev';
55
+ const config = ENV_CONFIG[envName] || ENV_CONFIG.dev;
56
+
57
+ // ============================================================
58
+ // DynamoDB Table
59
+ // ============================================================
60
+ const table = new dynamodb.Table(this, 'ItemsTable', {
61
+ partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING },
62
+ billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
63
+ pointInTimeRecoverySpecification: { pointInTimeRecoveryEnabled: true },
64
+ removalPolicy: config.removalPolicy,
65
+ });
66
+
67
+ // ============================================================
68
+ // Lambda Function (ARM64 — 20% cheaper)
69
+ // ============================================================
70
+ const fn = new lambda.Function(this, '{{ cookiecutter.project_name|replace('-', '')|capitalize }}Function', {
71
+ runtime: lambda.Runtime.PYTHON_3_12,
72
+ architecture: lambda.Architecture.ARM_64,
73
+ handler: 'handler.handler',
74
+ code: lambda.Code.fromAsset('../src'),
75
+ memorySize: config.memorySize,
76
+ timeout: cdk.Duration.seconds(config.timeout),
77
+ tracing: lambda.Tracing.ACTIVE,
78
+ logGroup: new logs.LogGroup(this, 'FunctionLogGroup', {
79
+ logGroupName: `/aws/lambda/{{ cookiecutter.project_slug }}-${envName}`,
80
+ retention: config.logRetention,
81
+ removalPolicy: config.removalPolicy,
82
+ }),
83
+ environment: {
84
+ TABLE_NAME: table.tableName,
85
+ STAGE: envName,
86
+ POWERTOOLS_SERVICE_NAME: '{{ cookiecutter.project_slug }}',
87
+ LOG_LEVEL: envName === 'prod' ? 'WARNING' : 'INFO',
88
+ },
89
+ });
90
+
91
+ table.grantReadWriteData(fn);
92
+
93
+ // ============================================================
94
+ // API Gateway
95
+ // ============================================================
96
+ const api = new apigateway.RestApi(this, '{{ cookiecutter.project_name|replace('-', '')|capitalize }}Api', {
97
+ restApiName: `{{ cookiecutter.project_slug }}-${envName}`,
98
+ description: `{{ cookiecutter.description }} (${envName})`,
99
+ deployOptions: {
100
+ stageName: envName,
101
+ tracingEnabled: true,
102
+ loggingLevel: apigateway.MethodLoggingLevel.INFO,
103
+ dataTraceEnabled: false,
104
+ throttlingRateLimit: config.throttlingRate,
105
+ throttlingBurstLimit: config.throttlingBurst,
106
+ },
107
+ });
108
+
109
+ const items = api.root.addResource('items');
110
+ items.addMethod('GET', new apigateway.LambdaIntegration(fn));
111
+ items.addMethod('POST', new apigateway.LambdaIntegration(fn));
112
+
113
+ const health = api.root.addResource('health');
114
+ health.addMethod('GET', new apigateway.LambdaIntegration(fn));
115
+
116
+ // ============================================================
117
+ // CloudWatch Dashboard
118
+ // ============================================================
119
+ const dashboard = new cloudwatch.Dashboard(this, '{{ cookiecutter.project_name|replace('-', '')|capitalize }}Dashboard', {
120
+ dashboardName: `{{ cookiecutter.project_slug }}-${envName}`,
121
+ });
122
+
123
+ dashboard.addWidgets(
124
+ new cloudwatch.GraphWidget({
125
+ title: 'Invocations & Errors',
126
+ left: [fn.metricInvocations()],
127
+ right: [fn.metricErrors()],
128
+ }),
129
+ new cloudwatch.GraphWidget({
130
+ title: 'Duration (ms)',
131
+ left: [
132
+ fn.metricDuration({ statistic: 'p50' }),
133
+ fn.metricDuration({ statistic: 'p95' }),
134
+ ],
135
+ }),
136
+ );
137
+
138
+ // ============================================================
139
+ // Tags (Well-Architected)
140
+ // ============================================================
141
+ cdk.Tags.of(this).add('Project', '{{ cookiecutter.project_name }}');
142
+ cdk.Tags.of(this).add('ManagedBy', 'LambdaForge');
143
+ cdk.Tags.of(this).add('Environment', envName);
144
+
145
+ // ============================================================
146
+ // Outputs
147
+ // ============================================================
148
+ new cdk.CfnOutput(this, 'ApiUrl', { value: api.url });
149
+ new cdk.CfnOutput(this, 'TableName', { value: table.tableName });
150
+ new cdk.CfnOutput(this, 'FunctionName', { value: fn.functionName });
151
+ new cdk.CfnOutput(this, 'Environment', { value: envName });
152
+ }
153
+ }
154
+
155
+ module.exports = { {{ cookiecutter.project_name|replace('-', '')|capitalize }}Stack };
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "{{ cookiecutter.project_slug }}-infrastructure",
3
+ "version": "0.1.0",
4
+ "description": "AWS CDK infrastructure for {{ cookiecutter.project_name }}",
5
+ "private": true,
6
+ "scripts": {
7
+ "cdk": "cdk",
8
+ "synth": "cdk synth",
9
+ "deploy": "cdk deploy --require-approval never",
10
+ "destroy": "cdk destroy --force",
11
+ "diff": "cdk diff"
12
+ },
13
+ "dependencies": {
14
+ "aws-cdk-lib": "^2.170.0",
15
+ "constructs": "^10.4.0"
16
+ }
17
+ }
@@ -0,0 +1,66 @@
1
+ [project]
2
+ name = "{{ cookiecutter.project_slug }}"
3
+ version = "0.1.0"
4
+ description = "{{ cookiecutter.description }}"
5
+ authors = [
6
+ {name = "{{ cookiecutter.author_name }}", email = "{{ cookiecutter.author_email }}"}
7
+ ]
8
+ readme = "README.md"
9
+ requires-python = ">=3.12"
10
+ license = {text = "MIT"}
11
+ keywords = ["aws", "lambda", "serverless", "kiro"]
12
+
13
+ dependencies = [
14
+ "aws-lambda-powertools[tracer,logger,metrics]>=3.0.0",
15
+ "boto3>=1.34.0",
16
+ "pydantic>=2.5.0",
17
+ ]
18
+
19
+ [project.optional-dependencies]
20
+ dev = [
21
+ "pytest>=7.4.0",
22
+ "pytest-cov>=4.1.0",
23
+ "moto[dynamodb,apigateway]>=5.0.0",
24
+ "ruff>=0.1.0",
25
+ "mypy>=1.7.0",
26
+ "aws-cdk-lib>=2.130.0",
27
+ "constructs>=10.3.0",
28
+ ]
29
+
30
+ [build-system]
31
+ requires = ["hatchling"]
32
+ build-backend = "hatchling.build"
33
+
34
+ [tool.hatch.build.targets.wheel]
35
+ packages = ["src"]
36
+
37
+ [tool.pytest.ini_options]
38
+ testpaths = ["tests"]
39
+ python_files = ["test_*.py"]
40
+ python_functions = ["test_*"]
41
+ addopts = "-v --strict-markers --cov=src --cov-report=term-missing"
42
+
43
+ [tool.ruff]
44
+ line-length = 100
45
+ target-version = "py312"
46
+
47
+ [tool.ruff.lint]
48
+ select = ["E", "F", "I", "B", "UP", "N", "S", "RUF"]
49
+ ignore = ["S101"] # allow assert in tests
50
+
51
+ [tool.mypy]
52
+ python_version = "3.12"
53
+ strict = true
54
+ warn_return_any = true
55
+ disallow_untyped_defs = true
56
+
57
+ [tool.coverage.run]
58
+ source = ["src"]
59
+ omit = ["tests/*"]
60
+
61
+ [tool.coverage.report]
62
+ exclude_lines = [
63
+ "pragma: no cover",
64
+ "if __name__ == .__main__.:",
65
+ "raise NotImplementedError",
66
+ ]
@@ -0,0 +1,8 @@
1
+ -r requirements.txt
2
+ pytest>=7.4.0
3
+ pytest-cov>=4.1.0
4
+ moto[dynamodb,apigateway]>=5.0.0
5
+ ruff>=0.1.0
6
+ mypy>=1.7.0
7
+ aws-cdk-lib>=2.130.0
8
+ constructs>=10.3.0
@@ -0,0 +1,3 @@
1
+ aws-lambda-powertools[tracer,logger,metrics]>=3.0.0
2
+ boto3>=1.34.0
3
+ pydantic>=2.5.0
@@ -0,0 +1,93 @@
1
+ """{{ cookiecutter.project_name }} — Lambda handler.
2
+
3
+ API Gateway trigger with structured logging and error handling.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import os
8
+ from typing import Any
9
+
10
+ import boto3
11
+ from aws_lambda_powertools import Logger, Tracer
12
+ from aws_lambda_powertools.event_handler import APIGatewayRestResolver
13
+ from aws_lambda_powertools.logging import correlation_paths
14
+ from aws_lambda_powertools.utilities.typing import LambdaContext
15
+
16
+ # Initialize powertools
17
+ logger = Logger(service="{{ cookiecutter.project_slug }}")
18
+ tracer = Tracer(service="{{ cookiecutter.project_slug }}")
19
+
20
+ # API Gateway resolver
21
+ app = APIGatewayRestResolver()
22
+
23
+ # Environment variables
24
+ TABLE_NAME = os.environ.get("TABLE_NAME", "")
25
+ STAGE = os.environ.get("STAGE", "dev")
26
+
27
+ # AWS clients (initialized once per container)
28
+ dynamodb = boto3.resource("dynamodb")
29
+
30
+
31
+ @app.get("/health")
32
+ @tracer.capture_method
33
+ def health_check() -> dict[str, Any]:
34
+ """Health check endpoint."""
35
+ return {"status": "ok", "stage": STAGE}
36
+
37
+
38
+ @app.get("/items")
39
+ @tracer.capture_method
40
+ def list_items() -> dict[str, Any]:
41
+ """List all items from the DynamoDB table."""
42
+ if not TABLE_NAME:
43
+ logger.warning("TABLE_NAME not set, returning empty list")
44
+ return {"items": [], "count": 0}
45
+
46
+ try:
47
+ table = dynamodb.Table(TABLE_NAME)
48
+ response = table.scan()
49
+ items = response.get("Items", [])
50
+
51
+ logger.info("items_listed", extra={"count": len(items)})
52
+ return {"items": items, "count": len(items)}
53
+
54
+ except Exception:
55
+ logger.exception("list_items_failed")
56
+ raise
57
+
58
+
59
+ @app.post("/items")
60
+ @tracer.capture_method
61
+ def create_item() -> dict[str, Any]:
62
+ """Create a new item in the DynamoDB table."""
63
+ import uuid
64
+ from datetime import datetime
65
+
66
+ body = app.current_event.json_body
67
+ if not body or "name" not in body:
68
+ return {"error": "Missing required field: name"}, 400
69
+
70
+ item = {
71
+ "id": str(uuid.uuid4()),
72
+ "name": body["name"],
73
+ "created_at": datetime.utcnow().isoformat(),
74
+ **body,
75
+ }
76
+
77
+ try:
78
+ table = dynamodb.Table(TABLE_NAME)
79
+ table.put_item(Item=item)
80
+
81
+ logger.info("item_created", extra={"item_id": item["id"]})
82
+ return {"item": item}, 201
83
+
84
+ except Exception:
85
+ logger.exception("create_item_failed")
86
+ raise
87
+
88
+
89
+ @logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_REST)
90
+ @tracer.capture_lambda_handler
91
+ def handler(event: dict, context: LambdaContext) -> dict[str, Any]:
92
+ """Main Lambda handler — routes to APIGatewayRestResolver."""
93
+ return app.resolve(event, context)