Your first sosw Lambda¶
In this tutorial you will build, unit-test, deploy and invoke a complete AWS Lambda function on
sosw — from an empty directory to a running function — using
AWS SAM.
The function itself is deliberately simple: it receives a greeting request and answers with the identity of the AWS account it runs in — enough to exercise every framework mechanism you will use in real functions: configuration, automatic clients, statistics, warm start and unit tests.
Prerequisites¶
An AWS account and credentials configured for the AWS CLI.
SAM CLI installed.
Python 3.12+ (the examples use 3.14, matching the Lambda runtime).
1. Project layout¶
mkdir -p hello-sosw/src hello-sosw/tests
cd hello-sosw
hello-sosw/
├── src/
│ ├── app.py # the Processor and the lambda_handler
│ └── requirements.txt # sosw
├── tests/
│ └── test_app.py # unit tests, no network
└── template.yaml # SAM template
src/requirements.txt is a single line:
sosw
2. The Processor¶
Create src/app.py:
"""hello-sosw: my first Lambda bootstrapped with sosw."""
import logging
from sosw.app import LambdaGlobals, get_lambda_handler, Processor as SoswProcessor
from sosw.components.benchmark import benchmark
logger = logging.getLogger()
logger.setLevel(logging.INFO)
class Processor(SoswProcessor):
DEFAULT_CONFIG = {
'disable_ddb_config': True, # No external config lookup (yet) - see step 6.
'init_clients': ['sts'], # Automatically initialize `self.sts_client`.
'greeting': 'Hello',
}
sts_client = None
def __call__(self, event, **kwargs):
super().__call__(event)
name = event.get('name', 'world')
account = self.get_account()
self.result['greetings_sent'] += 1
return {
'message': f"{self._c('greeting')}, {name}! Running in account {account}.",
'stats': self.get_stats(),
}
@benchmark
def get_account(self):
return self.sts_client.get_caller_identity()['Account']
global_vars = LambdaGlobals()
lambda_handler = get_lambda_handler(Processor, global_vars)
Things to notice:
init_clients: ['sts']— no matching sosw component exists, so the Processor falls back to a plainboto3.client('sts')and assigns it toself.sts_client. One line instead of the usual client plumbing.self._c('greeting')reads from the layered config — you will override it without a redeployment in step 6.disable_ddb_config: Truekeeps the cold start free of the DynamoDB config lookup until we actually create the table.lambda_handleris generated once at import time; the Processor instance survives warm invocations (see Warm start).
3. Unit tests before deployment¶
Create tests/test_app.py. sosw functions are designed to be tested without any AWS access:
import os
import sys
import unittest
from unittest.mock import MagicMock, patch
os.environ['STAGE'] = 'test'
os.environ['autotest'] = 'True'
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
from app import Processor
class ProcessorTestCase(unittest.TestCase):
@patch('boto3.client')
def setUp(self, mock_boto_client):
self.processor = Processor()
self.processor.sts_client = MagicMock()
self.processor.sts_client.get_caller_identity.return_value = {'Account': '000000000000'}
def test_call__greets(self):
result = self.processor({'name': 'sosw'})
self.assertEqual(result['message'], "Hello, sosw! Running in account 000000000000.")
def test_call__counts_stats(self):
self.processor({})
self.assertEqual(self.processor.stats['processor_calls'], 1)
self.assertEqual(self.processor.stats['calls_get_account'], 1) # From @benchmark.
if __name__ == '__main__':
unittest.main()
pip install sosw
python -m unittest discover tests -v
Since this Processor sets disable_ddb_config, there is no config lookup to mock. For
Processors that do use the config table, add
with patch.object(Processor, 'get_config', return_value={}): around the construction —
see the Quickstart.
4. The SAM template¶
Create template.yaml:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: hello-sosw - my first Lambda bootstrapped with sosw.
Resources:
HelloSoswFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: hello-sosw
CodeUri: src/
Handler: app.lambda_handler
Runtime: python3.14
Timeout: 30
MemorySize: 256
Environment:
Variables:
STAGE: production
Outputs:
FunctionArn:
Value: !GetAtt HelloSoswFunction.Arn
Note
Larger projects usually mount sosw from a shared Lambda Layer instead of packaging it
into every function — see Lambda Layer.
5. Deploy and invoke¶
sam build
sam deploy --guided # first time; accept the defaults, stack name: hello-sosw
aws lambda invoke --function-name hello-sosw \
--cli-binary-format raw-in-base64-out \
--payload '{"name": "sosw"}' /dev/stdout
You should see your greeting with the real account id, plus the stats counters — including
the @benchmark timing of get_account. Invoke it twice and compare: the second (warm)
invocation carries total_* accumulators from the first one and skips initialization entirely.
Check the logs:
sam logs --name HelloSoswFunction --stack-name hello-sosw
The handler logs the assembled config on the cold start and the stats of every invocation.
6. Reconfigure without redeploying (optional)¶
Create the DynamoDB config table (schema in Configuration), grant the
function role dynamodb:Query on it, and remove the disable_ddb_config line from
DEFAULT_CONFIG. After a redeploy, the Processor will look for the record
config_name = 'hello-sosw_config' on every cold start. Try it:
aws dynamodb put-item --table-name config --item '{
"env": {"S": "production"},
"config_name": {"S": "hello-sosw_config"},
"config_value": {"S": "{\"greeting\": \"Bonjour\"}"}
}'
New containers now greet in French — no deployment involved. This layering is the single most
operationally useful habit of sosw functions: code ships defaults, the table holds the knobs.
7. Cleanup¶
sam delete --stack-name hello-sosw
Next steps¶
Serve HTTP with the same pattern: HTTP API with LambdaApi.
Long-running workflows with checkpoints: durable functions.
The full concepts behind what you just used: Concepts.