Thanks for wanting to contribute. The codebase is intentionally simple — each AWS service is a single self-contained Python file inside ministack/services/. Adding a new service or fixing a bug should take minutes, not hours.
ministack/
├── ministack/
│ ├── app.py # ASGI entry point, service routing, reset endpoint
│ ├── core/
│ │ ├── responses.py # json_response, error_response_json, new_uuid
│ │ ├── router.py # detect_service(), SERVICE_PATTERNS
│ │ ├── lambda_runtime.py
│ │ └── persistence.py
│ └── services/
│ ├── s3.py, sqs.py, sns.py, dynamodb.py, ...
│ └── cognito.py # example of a two-client service file
├── tests/
│ ├── conftest.py # pytest fixtures (boto3 clients)
│ └── test_services.py # all integration tests
├── Dockerfile
├── pyproject.toml
└── CHANGELOG.md
For infrastructure changes (Dockerfiles, CI/CD, pyproject, dependencies), open an issue first. PRs containing such changes without a prior issue will be rejected.
This section applies only when you are adding a brand-new AWS service (a new file under
ministack/services/).
Before writing any code for a new service, open a GitHub issue. Use the enhancement label and describe:
- Which AWS service (full name + the API namespace, e.g.
inspector2,appconfig,qbusiness). - Which operations you actually need — not the full API surface. MiniStack favors the operations real users hit (CRUD + the handful of list/get calls SDKs and Terraform providers rely on) over wire-format completeness for every action.
- A real use case — what tool, framework, or workflow drove the need (Terraform module, CDK construct, app code, integration test, etc.). "I want full parity" is not a use case.
- Scope boundaries — what's explicitly out of scope for the first PR (e.g. "no async job lifecycle, no findings persistence, no scheduled scans" for Inspector v2). It's fine to ship a partial service; it's not fine to ship a 50-operation stub where 45 return empty lists.
A maintainer will confirm the scope, flag overlap with existing work, and point you at the right protocol (JSON vs XML/Query vs REST) before you write code. This saves you from large PRs that get rejected for scope drift, AWS-parity gaps, or duplicating in-flight work.
PRs that add a new service without a corresponding scoped issue will be closed and the contributor asked to open one. This rule is specific to new services — it's the only way we keep MiniStack's AWS parity bar high while staying a one-file-per-service codebase.
Every service follows the same 4-step pattern:
"""
MyService Emulator.
JSON-based API via X-Amz-Target.
Supports: OperationOne, OperationTwo, ...
"""
import json
import logging
from ministack.core.responses import json_response, error_response_json, new_uuid
logger = logging.getLogger("myservice")
ACCOUNT_ID = "000000000000"
REGION = "us-east-1"
_state: dict = {} # in-memory storage
async def handle_request(method, path, headers, body, query_params):
target = headers.get("x-amz-target", "")
action = target.split(".")[-1] if "." in target else ""
try:
data = json.loads(body) if body else {}
except json.JSONDecodeError:
return error_response_json("SerializationException", "Invalid JSON", 400)
handlers = {
"OperationOne": _operation_one,
"OperationTwo": _operation_two,
}
handler = handlers.get(action)
if not handler:
return error_response_json("InvalidAction", f"Unknown action: {action}", 400)
return handler(data)
def _operation_one(data):
return json_response({"result": "ok"})
def _operation_two(data):
return json_response({})
def reset():
_state.clear()Protocol guide:
- JSON services (DynamoDB, SecretsManager, Glue, Athena, Cognito, etc.) — use
json_response/error_response_json, route viaX-Amz-Target - XML/Query services (S3, SQS, SNS, IAM, STS, RDS, ElastiCache, EC2) — build XML responses, route via
Actionquery param; use_xml(status, root_tag, inner)pattern; verify field names against botocore shapes viaLoader().load_service_model() - REST services (Lambda, ECS, Route53) — route via URL path
from ministack.services import myservice
SERVICE_REGISTRY = {
# ... existing ...
"myservice": {"module": "myservice"},
}If the service needs aliases, add them in the registry entry.
SERVICE_PATTERNS = {
# ... existing ...
"myservice": {
"target_prefixes": ["AWSMyService"], # for X-Amz-Target routing
"host_patterns": [r"myservice\."], # for host-based routing
},
}Add any credential scope or Action-based routing as needed.
@pytest.fixture(scope="session")
def mysvc():
return make_client("myservice")def test_myservice_operation_one(mysvc):
resp = mysvc.operation_one(Param="value")
assert resp["result"] == "ok"Note: If your service has operations that mutate global state or require isolation (like calling the reset endpoint), add them to the _SERIAL_TESTS set in conftest.py. This ensures they run sequentially after the parallel-safe tests.
# Start the stack
docker compose up -d
# Install test dependencies
pip install boto3 pytest pytest-xdist duckdb docker cbor2
# Parallel-safe phase: run tests that are safe to run concurrently
pytest tests/ -v -n 4 --dist=loadfile -m "not serial"
# Serial/global-state phase: run tests that mutate runtime state or require isolation
pytest tests/ -v -m serial
# Run a specific service
pytest tests/ -v -k "cognito"- One file per service — keep everything for a service in
ministack/services/myservice.py - Imports — always
from ministack.core.responses import ..., neverfrom core.responses import ... - In-memory state — use module-level dicts (
_things: dict = {}) - reset() — every service must expose a
reset()that clears all module-level state; it's called by/_ministack/reset - No external AWS deps — no
boto3,botocore, oraws-sdkin service code - Minimal dependencies —
duckdbanddockerare optional; guard withtry/except ImportError - Error responses — match real AWS error codes and HTTP status codes as closely as possible
- Logging —
logger = logging.getLogger("servicename"); DEBUG for request details, INFO for significant events
- New service file in
ministack/services/ - Registered in
ministack/app.pySERVICE_REGISTRY - Detection patterns added to
ministack/core/router.py - Fixture added to
tests/conftest.py - Tests added and passing (
pytest tests/ -v) - Linting passes (
ruff check ministack/) - Service added to the table in
README.md - Entry added to
CHANGELOG.md
High-value contributions right now:
- CloudFront — distribution CRUD, invalidations, origin configuration
- CodeBuild / CodePipeline — CI/CD pipeline stubs
- AppSync — GraphQL API CRUD
- SQS FIFO — message group / deduplication support
- More Cognito flows — hosted UI, federated identity providers, custom auth triggers
Open a GitHub Discussion or file an issue with the question label.