testcontainers-aws 0.1.0__tar.gz

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 (34) hide show
  1. testcontainers_aws-0.1.0/LICENSE +20 -0
  2. testcontainers_aws-0.1.0/MANIFEST.in +7 -0
  3. testcontainers_aws-0.1.0/PKG-INFO +576 -0
  4. testcontainers_aws-0.1.0/README.md +540 -0
  5. testcontainers_aws-0.1.0/examples/auto_init.py +99 -0
  6. testcontainers_aws-0.1.0/examples/basic_usage.py +35 -0
  7. testcontainers_aws-0.1.0/examples/lambda_example.py +140 -0
  8. testcontainers_aws-0.1.0/examples/lambda_sns_integration.py +201 -0
  9. testcontainers_aws-0.1.0/examples/pytest_example.py +88 -0
  10. testcontainers_aws-0.1.0/examples/sns_example.py +163 -0
  11. testcontainers_aws-0.1.0/pyproject.toml +73 -0
  12. testcontainers_aws-0.1.0/requirements-dev.txt +8 -0
  13. testcontainers_aws-0.1.0/requirements.txt +3 -0
  14. testcontainers_aws-0.1.0/setup.cfg +4 -0
  15. testcontainers_aws-0.1.0/testcontainers_aws/__init__.py +12 -0
  16. testcontainers_aws-0.1.0/testcontainers_aws/core.py +261 -0
  17. testcontainers_aws-0.1.0/testcontainers_aws/fixtures.py +291 -0
  18. testcontainers_aws-0.1.0/testcontainers_aws/services/__init__.py +17 -0
  19. testcontainers_aws-0.1.0/testcontainers_aws/services/base.py +25 -0
  20. testcontainers_aws-0.1.0/testcontainers_aws/services/dynamodb.py +168 -0
  21. testcontainers_aws-0.1.0/testcontainers_aws/services/kinesis.py +181 -0
  22. testcontainers_aws-0.1.0/testcontainers_aws/services/lambda_service.py +334 -0
  23. testcontainers_aws-0.1.0/testcontainers_aws/services/s3.py +104 -0
  24. testcontainers_aws-0.1.0/testcontainers_aws/services/sns.py +307 -0
  25. testcontainers_aws-0.1.0/testcontainers_aws/services/sqs.py +167 -0
  26. testcontainers_aws-0.1.0/testcontainers_aws.egg-info/PKG-INFO +576 -0
  27. testcontainers_aws-0.1.0/testcontainers_aws.egg-info/SOURCES.txt +32 -0
  28. testcontainers_aws-0.1.0/testcontainers_aws.egg-info/dependency_links.txt +1 -0
  29. testcontainers_aws-0.1.0/testcontainers_aws.egg-info/requires.txt +10 -0
  30. testcontainers_aws-0.1.0/testcontainers_aws.egg-info/top_level.txt +1 -0
  31. testcontainers_aws-0.1.0/tests/__init__.py +1 -0
  32. testcontainers_aws-0.1.0/tests/test_core.py +117 -0
  33. testcontainers_aws-0.1.0/tests/test_lambda_sns.py +405 -0
  34. testcontainers_aws-0.1.0/tests/test_services.py +150 -0
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 testcontainers-aws contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ SOFTWARE.
@@ -0,0 +1,7 @@
1
+ include README.md
2
+ include LICENSE
3
+ include requirements.txt
4
+ include requirements-dev.txt
5
+ recursive-include testcontainers_aws *.py
6
+ recursive-include tests *.py
7
+ recursive-include examples *.py
@@ -0,0 +1,576 @@
1
+ Metadata-Version: 2.4
2
+ Name: testcontainers-aws
3
+ Version: 0.1.0
4
+ Summary: Simplified AWS service testing with Testcontainers - orchestrate LocalStack and AWS services for integration tests
5
+ Author-email: Manoj Kengudelu <mkengudelu@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/manojkken/testcontainers-aws
8
+ Project-URL: Documentation, https://github.com/manojkken/testcontainers-aws#readme
9
+ Project-URL: Repository, https://github.com/manojkken/testcontainers-aws
10
+ Project-URL: Issues, https://github.com/manojkken/testcontainers-aws/issues
11
+ Keywords: testcontainers,aws,testing,localstack,docker,s3,dynamodb,sqs
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Software Development :: Testing
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Requires-Python: >=3.8
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: testcontainers[localstack]>=4.0.0
27
+ Requires-Dist: boto3>=1.28.0
28
+ Requires-Dist: botocore>=1.31.0
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
31
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
32
+ Requires-Dist: black>=23.0.0; extra == "dev"
33
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
34
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
35
+ Dynamic: license-file
36
+
37
+ # testcontainers-aws
38
+
39
+ **Simplified AWS service testing with Testcontainers** - Orchestrate LocalStack and AWS services for integration tests with minimal boilerplate.
40
+
41
+ [![PyPI version](https://badge.fury.io/py/testcontainers-aws.svg)](https://badge.fury.io/py/testcontainers-aws)
42
+ [![Python Versions](https://img.shields.io/pypi/pyversions/testcontainers-aws.svg)](https://pypi.org/project/testcontainers-aws/)
43
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
44
+
45
+ ## Why testcontainers-aws?
46
+
47
+ Testing AWS services locally is tedious:
48
+ - Manual container setup for each service
49
+ - Repetitive boto3 client configuration
50
+ - Boilerplate code for resource initialization
51
+ - No pytest fixtures for common patterns
52
+
53
+ **testcontainers-aws** solves this:
54
+
55
+ ```python
56
+ from testcontainers_aws import AWSTestEnvironment
57
+
58
+ # Before: Manual setup with testcontainers
59
+ with LocalStackContainer() as localstack:
60
+ localstack.execInContainer("awslocal", "s3", "mb", "s3://bucket")
61
+ client = boto3.client('s3', endpoint_url=localstack.get_url(), ...)
62
+ # More configuration...
63
+
64
+ # After: One line with testcontainers-aws
65
+ with AWSTestEnvironment(services=['s3', 'dynamodb']) as aws:
66
+ s3 = aws.get_s3_client() # Pre-configured, ready to use
67
+ aws.s3.create_bucket('test-bucket') # Helper methods
68
+ ```
69
+
70
+ ## Features
71
+
72
+ - ✅ **Single container orchestration** - Spin up multiple AWS services in one LocalStack container
73
+ - ✅ **Auto-configured boto3 clients** - No manual endpoint/credential setup
74
+ - ✅ **Service helper methods** - Create buckets, tables, queues with simple method calls
75
+ - ✅ **Pytest fixtures** - Ready-to-use fixtures for common testing patterns
76
+ - ✅ **Resource auto-initialization** - Define resources declaratively, created on startup
77
+ - ✅ **Automatic cleanup** - Resources cleaned up after tests complete
78
+
79
+ ## Installation
80
+
81
+ ```bash
82
+ pip install testcontainers-aws
83
+ ```
84
+
85
+ Requires:
86
+ - Python 3.8+
87
+ - Docker installed and running
88
+
89
+ ## Quick Start
90
+
91
+ ### Basic Usage
92
+
93
+ ```python
94
+ from testcontainers_aws import AWSTestEnvironment
95
+
96
+ # Create environment with services
97
+ with AWSTestEnvironment(services=['s3', 'dynamodb', 'sqs']) as aws:
98
+ # Get pre-configured clients
99
+ s3 = aws.get_s3_client()
100
+ dynamodb = aws.get_dynamodb_client()
101
+ sqs = aws.get_sqs_client()
102
+
103
+ # Use service helper methods
104
+ aws.s3.create_bucket('test-bucket')
105
+ aws.s3.put_object('test-bucket', 'key.txt', b'Hello World')
106
+
107
+ # Use boto3 clients directly
108
+ response = s3.get_object(Bucket='test-bucket', Key='key.txt')
109
+ print(response['Body'].read()) # b'Hello World'
110
+ ```
111
+
112
+ ### With Auto-Initialization
113
+
114
+ ```python
115
+ # Define resources upfront - created automatically
116
+ with AWSTestEnvironment(
117
+ services=['s3', 'dynamodb', 'sqs'],
118
+ init_config={
119
+ 's3_buckets': ['uploads', 'archives'],
120
+ 'dynamodb_tables': [
121
+ {
122
+ 'name': 'users',
123
+ 'key_schema': [{'AttributeName': 'id', 'KeyType': 'HASH'}],
124
+ 'attribute_definitions': [{'AttributeName': 'id', 'AttributeType': 'S'}]
125
+ }
126
+ ],
127
+ 'sqs_queues': ['notifications', 'tasks']
128
+ }
129
+ ) as aws:
130
+ # Everything is ready to use
131
+ aws.s3.put_object('uploads', 'file.txt', b'data')
132
+ aws.dynamodb.put_item('users', {'id': {'S': '123'}, 'name': {'S': 'Alice'}})
133
+ ```
134
+
135
+ ### With Pytest
136
+
137
+ ```python
138
+ import pytest
139
+ from testcontainers_aws.fixtures import aws_environment, s3_bucket
140
+
141
+ def test_s3_operations(aws_environment):
142
+ """Test with session-scoped environment (shared across tests)."""
143
+ s3 = aws_environment.get_s3_client()
144
+ aws_environment.s3.create_bucket('test-bucket')
145
+
146
+ # Your test code
147
+ aws_environment.s3.put_object('test-bucket', 'test.txt', b'content')
148
+ assert s3.list_objects_v2(Bucket='test-bucket')['KeyCount'] == 1
149
+
150
+ def test_with_auto_cleanup_bucket(s3_bucket, aws_environment):
151
+ """Test with function-scoped bucket (auto-created and cleaned up)."""
152
+ aws_environment.s3.put_object(s3_bucket, 'file.txt', b'data')
153
+
154
+ # Bucket is automatically deleted after test
155
+ ```
156
+
157
+ ## Supported Services
158
+
159
+ | Service | Client Method | Helper Property |
160
+ |---------|--------------|-----------------|
161
+ | S3 | `get_s3_client()` | `aws.s3` |
162
+ | DynamoDB | `get_dynamodb_client()` | `aws.dynamodb` |
163
+ | SQS | `get_sqs_client()` | `aws.sqs` |
164
+ | Kinesis | `get_kinesis_client()` | `aws.kinesis` |
165
+ | SNS | `get_sns_client()` | `aws.sns` |
166
+ | Lambda | `get_lambda_client()` | `aws.lambda_` |
167
+
168
+ ## Service Helpers
169
+
170
+ ### S3 Operations
171
+
172
+ ```python
173
+ # Create bucket
174
+ aws.s3.create_bucket('my-bucket')
175
+
176
+ # Upload object
177
+ aws.s3.put_object('my-bucket', 'path/file.txt', b'content')
178
+
179
+ # Download object
180
+ response = aws.s3.get_object('my-bucket', 'path/file.txt')
181
+
182
+ # Delete bucket (with force to delete all objects)
183
+ aws.s3.delete_bucket('my-bucket', force=True)
184
+
185
+ # List all buckets
186
+ buckets = aws.s3.list_buckets()
187
+ ```
188
+
189
+ ### DynamoDB Operations
190
+
191
+ ```python
192
+ # Create table
193
+ aws.dynamodb.create_table(
194
+ name='users',
195
+ key_schema=[{'AttributeName': 'id', 'KeyType': 'HASH'}],
196
+ attribute_definitions=[{'AttributeName': 'id', 'AttributeType': 'S'}]
197
+ )
198
+
199
+ # Put item
200
+ aws.dynamodb.put_item('users', {'id': {'S': '123'}, 'name': {'S': 'Alice'}})
201
+
202
+ # Get item
203
+ item = aws.dynamodb.get_item('users', {'id': {'S': '123'}})
204
+
205
+ # Query table
206
+ results = aws.dynamodb.query(
207
+ 'users',
208
+ 'id = :id',
209
+ {':id': {'S': '123'}}
210
+ )
211
+
212
+ # List all tables
213
+ tables = aws.dynamodb.list_tables()
214
+ ```
215
+
216
+ ### SQS Operations
217
+
218
+ ```python
219
+ # Create queue
220
+ response = aws.sqs.create_queue('my-queue')
221
+ queue_url = response['QueueUrl']
222
+
223
+ # Send message
224
+ aws.sqs.send_message(queue_url, 'Hello from SQS')
225
+
226
+ # Receive messages
227
+ messages = aws.sqs.receive_messages(queue_url, max_messages=10)
228
+
229
+ # Delete message
230
+ for msg in messages:
231
+ aws.sqs.delete_message(queue_url, msg['ReceiptHandle'])
232
+
233
+ # List queues
234
+ queues = aws.sqs.list_queues()
235
+ ```
236
+
237
+ ### Kinesis Operations
238
+
239
+ ```python
240
+ # Create stream
241
+ aws.kinesis.create_stream('my-stream', shard_count=1)
242
+
243
+ # Put record
244
+ aws.kinesis.put_record('my-stream', b'data', partition_key='key1')
245
+
246
+ # Put multiple records
247
+ aws.kinesis.put_records('my-stream', [
248
+ {'Data': b'record1', 'PartitionKey': 'key1'},
249
+ {'Data': b'record2', 'PartitionKey': 'key2'}
250
+ ])
251
+
252
+ # Get shard iterator
253
+ shard_iterator = aws.kinesis.get_shard_iterator(
254
+ 'my-stream',
255
+ 'shardId-000000000000',
256
+ 'TRIM_HORIZON'
257
+ )
258
+
259
+ # Get records
260
+ records = aws.kinesis.get_records(shard_iterator)
261
+
262
+ # List streams
263
+ streams = aws.kinesis.list_streams()
264
+ ```
265
+
266
+ ### Lambda Operations
267
+
268
+ ```python
269
+ from testcontainers_aws.services.lambda_service import create_lambda_zip
270
+
271
+ # Create Lambda function
272
+ code = create_lambda_zip("""
273
+ def handler(event, context):
274
+ return {'statusCode': 200, 'body': 'Hello World'}
275
+ """)
276
+
277
+ aws.lambda_.create_function(
278
+ function_name='my-function',
279
+ runtime='python3.11',
280
+ handler='index.handler',
281
+ code=code,
282
+ environment_variables={'ENV': 'test'}
283
+ )
284
+
285
+ # Invoke function
286
+ response = aws.lambda_.invoke_function(
287
+ 'my-function',
288
+ payload={'key': 'value'}
289
+ )
290
+
291
+ result = json.loads(response['Payload'].read())
292
+
293
+ # Update function code
294
+ new_code = create_lambda_zip("def handler(event, context): return {'updated': True}")
295
+ aws.lambda_.update_function_code('my-function', new_code)
296
+
297
+ # List functions
298
+ functions = aws.lambda_.list_functions()
299
+
300
+ # Delete function
301
+ aws.lambda_.delete_function('my-function')
302
+ ```
303
+
304
+ ### SNS Operations
305
+
306
+ ```python
307
+ # Create topic
308
+ response = aws.sns.create_topic('notifications')
309
+ topic_arn = response['TopicArn']
310
+
311
+ # Publish message
312
+ aws.sns.publish(
313
+ topic_arn=topic_arn,
314
+ message='Hello from SNS!',
315
+ subject='Notification'
316
+ )
317
+
318
+ # Subscribe SQS queue to topic
319
+ aws.sns.subscribe(
320
+ topic_arn=topic_arn,
321
+ protocol='sqs',
322
+ endpoint='arn:aws:sqs:us-east-1:000000000000:my-queue'
323
+ )
324
+
325
+ # Subscribe Lambda to topic
326
+ aws.sns.subscribe(
327
+ topic_arn=topic_arn,
328
+ protocol='lambda',
329
+ endpoint='arn:aws:lambda:us-east-1:000000000000:function:my-function'
330
+ )
331
+
332
+ # Publish batch messages
333
+ aws.sns.publish_batch(
334
+ topic_arn=topic_arn,
335
+ messages=[
336
+ {'Id': '1', 'Message': 'First message'},
337
+ {'Id': '2', 'Message': 'Second message'}
338
+ ]
339
+ )
340
+
341
+ # Get topic attributes
342
+ attributes = aws.sns.get_topic_attributes(topic_arn)
343
+
344
+ # List topics
345
+ topics = aws.sns.list_topics()
346
+
347
+ # List subscriptions
348
+ subscriptions = aws.sns.list_subscriptions_by_topic(topic_arn)
349
+
350
+ # Delete topic
351
+ aws.sns.delete_topic(topic_arn)
352
+ ```
353
+
354
+ ## Pytest Fixtures
355
+
356
+ testcontainers-aws provides several built-in fixtures:
357
+
358
+ ```python
359
+ from testcontainers_aws.fixtures import (
360
+ aws_environment, # Session-scoped AWS environment
361
+ aws_environment_function, # Function-scoped AWS environment
362
+ s3_client, # Session-scoped S3 client
363
+ dynamodb_client, # Session-scoped DynamoDB client
364
+ sqs_client, # Session-scoped SQS client
365
+ kinesis_client, # Session-scoped Kinesis client
366
+ lambda_client, # Session-scoped Lambda client
367
+ sns_client, # Session-scoped SNS client
368
+ s3_bucket, # Function-scoped bucket (auto-cleanup)
369
+ dynamodb_table, # Function-scoped table (auto-cleanup)
370
+ sqs_queue, # Function-scoped queue (auto-cleanup)
371
+ kinesis_stream, # Function-scoped stream (auto-cleanup)
372
+ lambda_function, # Function-scoped Lambda function (auto-cleanup)
373
+ sns_topic, # Function-scoped SNS topic (auto-cleanup)
374
+ )
375
+ ```
376
+
377
+ ### Custom Fixtures
378
+
379
+ Create your own fixtures for common patterns:
380
+
381
+ ```python
382
+ import pytest
383
+ from testcontainers_aws import AWSTestEnvironment
384
+
385
+ @pytest.fixture(scope='session')
386
+ def aws_with_data():
387
+ """Custom fixture with pre-seeded data."""
388
+ with AWSTestEnvironment(
389
+ services=['dynamodb'],
390
+ init_config={
391
+ 'dynamodb_tables': [
392
+ {
393
+ 'name': 'users',
394
+ 'key_schema': [{'AttributeName': 'id', 'KeyType': 'HASH'}],
395
+ 'attribute_definitions': [{'AttributeName': 'id', 'AttributeType': 'S'}]
396
+ }
397
+ ]
398
+ }
399
+ ) as aws:
400
+ # Seed test data
401
+ for i in range(10):
402
+ aws.dynamodb.put_item('users', {
403
+ 'id': {'S': f'user-{i}'},
404
+ 'name': {'S': f'User {i}'}
405
+ })
406
+
407
+ yield aws
408
+ ```
409
+
410
+ ## Configuration
411
+
412
+ ### Specify LocalStack Version
413
+
414
+ ```python
415
+ with AWSTestEnvironment(
416
+ services=['s3'],
417
+ image_tag='2.3.0' # Specific LocalStack version
418
+ ) as aws:
419
+ # ...
420
+ ```
421
+
422
+ ### Access Container Details
423
+
424
+ ```python
425
+ with AWSTestEnvironment(services=['s3']) as aws:
426
+ # Get endpoint URL
427
+ endpoint = aws.endpoint_url
428
+ print(f"LocalStack running at: {endpoint}")
429
+ ```
430
+
431
+ ## Examples
432
+
433
+ See the [examples/](examples/) directory for complete working examples:
434
+
435
+ - `basic_usage.py` - Simple S3 operations
436
+ - `pytest_example.py` - Pytest integration examples
437
+ - `auto_init.py` - Auto-initialization patterns
438
+ - `lambda_example.py` - Lambda function creation and invocation
439
+ - `sns_example.py` - SNS topics, publishing, and subscriptions
440
+ - `lambda_sns_integration.py` - Event-driven architecture with Lambda and SNS
441
+
442
+ ## Real-World Use Cases
443
+
444
+ ### Testing S3 Upload/Download Logic
445
+
446
+ ```python
447
+ def test_file_storage_service(aws_environment):
448
+ storage = FileStorageService(
449
+ s3_client=aws_environment.get_s3_client(),
450
+ bucket='uploads'
451
+ )
452
+
453
+ aws_environment.s3.create_bucket('uploads')
454
+
455
+ # Test upload
456
+ storage.upload_file('test.txt', b'content')
457
+
458
+ # Test download
459
+ data = storage.download_file('test.txt')
460
+ assert data == b'content'
461
+ ```
462
+
463
+ ### Testing DynamoDB Repository
464
+
465
+ ```python
466
+ def test_user_repository(aws_environment):
467
+ # Create table
468
+ aws_environment.dynamodb.create_table(
469
+ name='users',
470
+ key_schema=[{'AttributeName': 'user_id', 'KeyType': 'HASH'}],
471
+ attribute_definitions=[{'AttributeName': 'user_id', 'AttributeType': 'S'}]
472
+ )
473
+
474
+ repo = UserRepository(aws_environment.get_dynamodb_client())
475
+
476
+ # Test operations
477
+ repo.create_user('123', 'Alice')
478
+ user = repo.get_user('123')
479
+ assert user['name'] == 'Alice'
480
+ ```
481
+
482
+ ### Testing Event-Driven Architecture
483
+
484
+ ```python
485
+ def test_event_processor(aws_environment):
486
+ from testcontainers_aws.services.lambda_service import create_lambda_zip
487
+
488
+ # Create SNS topic
489
+ topic_response = aws_environment.sns.create_topic('events')
490
+ topic_arn = topic_response['TopicArn']
491
+
492
+ # Create Lambda processor
493
+ code = create_lambda_zip("""
494
+ def handler(event, context):
495
+ for record in event.get('Records', []):
496
+ # Process SNS message
497
+ message = record['Sns']['Message']
498
+ print(f'Processing: {message}')
499
+ return {'statusCode': 200}
500
+ """)
501
+
502
+ aws_environment.lambda_.create_function(
503
+ function_name='event-processor',
504
+ runtime='python3.11',
505
+ handler='index.handler',
506
+ code=code
507
+ )
508
+
509
+ # Subscribe Lambda to SNS
510
+ lambda_arn = 'arn:aws:lambda:us-east-1:000000000000:function:event-processor'
511
+ aws_environment.sns.subscribe(
512
+ topic_arn=topic_arn,
513
+ protocol='lambda',
514
+ endpoint=lambda_arn
515
+ )
516
+
517
+ # Publish event
518
+ aws_environment.sns.publish(
519
+ topic_arn=topic_arn,
520
+ message='{"type": "user_created", "user_id": "123"}'
521
+ )
522
+ ```
523
+
524
+ ## Contributing
525
+
526
+ Contributions welcome! Please:
527
+
528
+ 1. Fork the repository
529
+ 2. Create a feature branch
530
+ 3. Add tests for new functionality
531
+ 4. Ensure all tests pass: `pytest tests/`
532
+ 5. Submit a pull request
533
+
534
+ ## Development Setup
535
+
536
+ ```bash
537
+ # Clone repository
538
+ git clone https://github.com/yourusername/testcontainers-aws.git
539
+ cd testcontainers-aws
540
+
541
+ # Create virtual environment
542
+ python -m venv venv
543
+ source venv/bin/activate # On Windows: venv\Scripts\activate
544
+
545
+ # Install dev dependencies
546
+ pip install -r requirements-dev.txt
547
+
548
+ # Run tests
549
+ pytest tests/ -v
550
+
551
+ # Format code
552
+ black testcontainers_aws tests
553
+
554
+ # Type checking
555
+ mypy testcontainers_aws
556
+ ```
557
+
558
+ ## License
559
+
560
+ MIT License - see [LICENSE](LICENSE) file for details.
561
+
562
+ ## Credits
563
+
564
+ Built on top of:
565
+ - [testcontainers-python](https://github.com/testcontainers/testcontainers-python)
566
+ - [LocalStack](https://github.com/localstack/localstack)
567
+ - [boto3](https://github.com/boto/boto3)
568
+
569
+ ## Support
570
+
571
+ - GitHub Issues: [Report bugs or request features](https://github.com/yourusername/testcontainers-aws/issues)
572
+ - Documentation: [Full API documentation](https://github.com/yourusername/testcontainers-aws#readme)
573
+
574
+ ---
575
+
576
+ Made with ❤️ for the AWS testing community