<h1 align="center">Welcome to Lamina</h1>
<p align="center">
<a href="https://pypi.org/project/py-lamina/" target="_blank">
<img alt="PyPI" src="https://img.shields.io/pypi/v/py-lamina"/></a>
<a href="https://www.python.org" target="_blank">
<img alt="PyPI - Python Version" src="https://img.shields.io/pypi/pyversions/py-lamina"/>
</a>
<a href="https://github.com/megalus/lamina/blob/main/LICENSE" target="_blank">
<img alt="License: MIT" src="https://img.shields.io/github/license/megalus/lamina"/>
</a>
<a href="https://github.com/megalus/lamina/actions/workflows/tests.yml" target="_blank">
<img alt="Build Status" src="https://img.shields.io/github/actions/workflow/status/megalus/lamina/tests.yml?branch=main"/>
</a>
</p>
## Overview
Lamina (from Portuguese "lâmina", meaning "layer" or "blade") is a lightweight decorator library for AWS Lambda functions. It adds a powerful layer to your Lambda handlers, simplifying development by:
- Integrating both synchronous and asynchronous code in a single function
- Using Pydantic models for robust input and output data validation
- Handling errors gracefully with appropriate HTTP status codes
- Formatting responses according to AWS API Gateway expectations
- Supporting different content types (JSON, HTML, plain text)
- Providing convenient access to the original event and context objects
## Why use Lamina?
AWS Lambda functions often require repetitive boilerplate code for input validation, error handling, and response formatting. Lamina eliminates this boilerplate, allowing you to focus on your business logic while it handles:
- Input validation using Pydantic models
- Error handling with appropriate HTTP status codes
- Response formatting with content type control
- Support for both synchronous and asynchronous functions
- Custom headers support
- AWS Step Functions integration
## Installation
```shell
$ pip install py-lamina
```
Lamina requires Python 3.11 or later and has dependencies on:
- pydantic - For data validation
- asgiref - For async/sync conversion utilities
- loguru - For logging
## Usage
### Basic Example
Create the models for Input and Output data:
```python
# schemas.py
from pydantic import BaseModel
class ExampleInput(BaseModel):
name: str
age: int
class ExampleOutput(BaseModel):
message: str
```
Create your AWS Lambda handler:
```python
# main.py
from typing import Any, Dict
from lamina import lamina, Request
from .schemas import ExampleInput, ExampleOutput
@lamina(schema_in=ExampleInput, schema_out=ExampleOutput)
def handler(request: Request) -> Dict[str, Any]:
response = {"message": f"Hello {request.data.name}, you are {request.data.age} years old!"}
return response
```
### Asynchronous Handlers
Lamina seamlessly supports both synchronous and asynchronous handlers:
```python
# main.py
import asyncio
from typing import Any, Dict
from lamina import lamina, Request
from .schemas import ExampleInput, ExampleOutput
@lamina(schema_in=ExampleInput, schema_out=ExampleOutput)
async def handler(request: Request) -> Dict[str, Any]:
# Perform async operations
await asyncio.sleep(1)
response = {"message": f"Hello {request.data.name}, you are {request.data.age} years old!"}
return response
```
### Customizing Responses
#### Status Codes
The default status code is 200. You can customize it by returning a tuple:
```python
@lamina(schema_in=ExampleInput, schema_out=ExampleOutput)
def handler(request: Request):
response = {"message": f"Hello {request.data.name}, you are {request.data.age} years old!"}
return response, 201 # Created status code
```
#### Content Types
Lamina autodiscovers the content-type based on the return type:
```python
from lamina import lamina, Request
@lamina(schema_in=ExampleInput)
def handler(request: Request):
html = f"""
<html>
<head><title>User Profile</title></head>
<body>
<h1>Hello {request.data.name}!</h1>
<p>You are {request.data.age} years old.</p>
</body>
</html>
"""
return html
```
You can explicitly set the content type using the `content_type` parameter:
```python
@lamina(schema_in=ExampleInput, content_type="text/plain; charset=utf-8")
def handler(request: Request):
return f"Hello {request.data.name}, you are {request.data.age} years old!"
```
#### Custom Headers
You can add custom headers by returning them as the third element in the response tuple:
```python
@lamina(schema_in=ExampleInput)
def handler(request: Request):
response = {"message": f"Hello {request.data.name}!"}
return response, 200, {
"Cache-Control": "max-age=3600",
"X-Custom-Header": "custom-value"
}
```
## Hooks
Lamina provides four extensibility points executed around your handler.
Configuration (pyproject.toml):
```toml
[tool.lamina]
pre_parse_callback = "lamina.hooks.pre_parse"
pre_execute_callback = "lamina.hooks.pre_execute"
pos_execute_callback = "lamina.hooks.pos_execute"
pre_response_callback = "lamina.hooks.pre_response"
```
Environment variables override these values at runtime:
- LAMINA_PRE_PARSE_CALLBACK
- LAMINA_PRE_EXECUTE_CALLBACK
- LAMINA_POS_EXECUTE_CALLBACK
- LAMINA_PRE_RESPONSE_CALLBACK
Hook signatures and responsibilities:
- pre_parse(event, context) -> event
- pre_execute(request, event, context) -> request
- pos_execute(response, request) -> response
- pre_response(body, request) -> body
### The Request Object
The `Request` object provides access to:
- `data`: The validated input data (as a Pydantic model if schema_in is provided)
- `event`: The original AWS Lambda event
- `context`: The original AWS Lambda context
### Using Without Schemas
You can use Lamina without schemas for more flexibility:
```python
import json
from lamina import lamina, Request
@lamina()
def handler(request: Request):
# Parse the body manually
body = json.loads(request.event["body"])
name = body.get("name", "Guest")
age = body.get("age", "unknown")
return {
"message": f"Hello {name}, you are {age} years old!"
}
```
> **Note**: Without a schema_in, the `request.data` attribute contains the raw body string from the event. You'll need to parse and validate it manually.
### AWS Step Functions Integration
Lamina supports AWS Step Functions with the `step_functions` parameter:
```python
@lamina(schema_in=ExampleInput, schema_out=ExampleOutput, step_functions=True)
def handler(request: Request):
# For Step Functions, the input is directly available as the event
# No need to parse from event["body"]
return {
"message": f"Step function processed for {request.data.name}"
}
```
### Error Handling
Lamina automatically handles common errors:
- **Validation Errors**: Returns 400 Bad Request with detailed validation messages
- **Type Errors**: Returns 400 Bad Request when input cannot be parsed
- **Serialization Errors**: Returns 500 Internal Server Error when output cannot be serialized
- **Unhandled Exceptions**: Returns 500 Internal Server Error with the error message
All errors are logged using the loguru library for easier debugging.
## Contributing
Contributions are welcome! Here's how you can help:
1. **Fork the repository** and clone it locally
2. **Create a new branch** for your feature or bugfix
3. **Make your changes** and add tests if applicable
4. **Run the tests** to ensure they pass: `poetry run pytest`
5. **Submit a pull request** with a clear description of your changes
Please make sure your code follows the project's style guidelines by running:
```shell
poetry run pre-commit run --all
```
### Development Setup
1. Clone the repository
2. Install dependencies with Poetry:
```shell
poetry install
```
3. Install pre-commit hooks:
```shell
poetry run pre-commit install
```
## License
This project is licensed under the terms of the MIT license.
Raw data
{
"_id": null,
"home_page": "https://github.com/megalus/lamina",
"name": "py-lamina",
"maintainer": null,
"docs_url": null,
"requires_python": "<4,>=3.11",
"maintainer_email": null,
"keywords": "aws, lambda, decorator",
"author": "Chris Maillefaud",
"author_email": "chrismaille@users.noreply.github.com",
"download_url": "https://files.pythonhosted.org/packages/e4/70/fc2a4794b1b9a176d1a9d6b0a31c6d960cf8e840c3878f2b674c1a7f333a/py_lamina-5.0.2.tar.gz",
"platform": null,
"description": "<h1 align=\"center\">Welcome to Lamina</h1>\n\n<p align=\"center\">\n<a href=\"https://pypi.org/project/py-lamina/\" target=\"_blank\">\n<img alt=\"PyPI\" src=\"https://img.shields.io/pypi/v/py-lamina\"/></a>\n<a href=\"https://www.python.org\" target=\"_blank\">\n<img alt=\"PyPI - Python Version\" src=\"https://img.shields.io/pypi/pyversions/py-lamina\"/>\n</a>\n<a href=\"https://github.com/megalus/lamina/blob/main/LICENSE\" target=\"_blank\">\n<img alt=\"License: MIT\" src=\"https://img.shields.io/github/license/megalus/lamina\"/>\n</a>\n<a href=\"https://github.com/megalus/lamina/actions/workflows/tests.yml\" target=\"_blank\">\n<img alt=\"Build Status\" src=\"https://img.shields.io/github/actions/workflow/status/megalus/lamina/tests.yml?branch=main\"/>\n</a>\n</p>\n\n## Overview\n\nLamina (from Portuguese \"l\u00e2mina\", meaning \"layer\" or \"blade\") is a lightweight decorator library for AWS Lambda functions. It adds a powerful layer to your Lambda handlers, simplifying development by:\n\n- Integrating both synchronous and asynchronous code in a single function\n- Using Pydantic models for robust input and output data validation\n- Handling errors gracefully with appropriate HTTP status codes\n- Formatting responses according to AWS API Gateway expectations\n- Supporting different content types (JSON, HTML, plain text)\n- Providing convenient access to the original event and context objects\n\n## Why use Lamina?\n\nAWS Lambda functions often require repetitive boilerplate code for input validation, error handling, and response formatting. Lamina eliminates this boilerplate, allowing you to focus on your business logic while it handles:\n\n- Input validation using Pydantic models\n- Error handling with appropriate HTTP status codes\n- Response formatting with content type control\n- Support for both synchronous and asynchronous functions\n- Custom headers support\n- AWS Step Functions integration\n\n## Installation\n\n```shell\n$ pip install py-lamina\n```\n\nLamina requires Python 3.11 or later and has dependencies on:\n- pydantic - For data validation\n- asgiref - For async/sync conversion utilities\n- loguru - For logging\n\n## Usage\n\n### Basic Example\n\nCreate the models for Input and Output data:\n\n```python\n# schemas.py\nfrom pydantic import BaseModel\n\nclass ExampleInput(BaseModel):\n name: str\n age: int\n\nclass ExampleOutput(BaseModel):\n message: str\n```\n\nCreate your AWS Lambda handler:\n\n```python\n# main.py\nfrom typing import Any, Dict\nfrom lamina import lamina, Request\nfrom .schemas import ExampleInput, ExampleOutput\n\n@lamina(schema_in=ExampleInput, schema_out=ExampleOutput)\ndef handler(request: Request) -> Dict[str, Any]:\n response = {\"message\": f\"Hello {request.data.name}, you are {request.data.age} years old!\"}\n return response\n```\n\n### Asynchronous Handlers\n\nLamina seamlessly supports both synchronous and asynchronous handlers:\n\n```python\n# main.py\nimport asyncio\nfrom typing import Any, Dict\nfrom lamina import lamina, Request\nfrom .schemas import ExampleInput, ExampleOutput\n\n@lamina(schema_in=ExampleInput, schema_out=ExampleOutput)\nasync def handler(request: Request) -> Dict[str, Any]:\n # Perform async operations\n await asyncio.sleep(1)\n response = {\"message\": f\"Hello {request.data.name}, you are {request.data.age} years old!\"}\n return response\n```\n\n### Customizing Responses\n\n#### Status Codes\n\nThe default status code is 200. You can customize it by returning a tuple:\n\n```python\n@lamina(schema_in=ExampleInput, schema_out=ExampleOutput)\ndef handler(request: Request):\n response = {\"message\": f\"Hello {request.data.name}, you are {request.data.age} years old!\"}\n return response, 201 # Created status code\n```\n\n#### Content Types\n\nLamina autodiscovers the content-type based on the return type:\n\n```python\nfrom lamina import lamina, Request\n\n@lamina(schema_in=ExampleInput)\ndef handler(request: Request):\n html = f\"\"\"\n <html>\n <head><title>User Profile</title></head>\n <body>\n <h1>Hello {request.data.name}!</h1>\n <p>You are {request.data.age} years old.</p>\n </body>\n </html>\n \"\"\"\n return html\n```\n\nYou can explicitly set the content type using the `content_type` parameter:\n\n```python\n@lamina(schema_in=ExampleInput, content_type=\"text/plain; charset=utf-8\")\ndef handler(request: Request):\n return f\"Hello {request.data.name}, you are {request.data.age} years old!\"\n```\n\n#### Custom Headers\n\nYou can add custom headers by returning them as the third element in the response tuple:\n\n```python\n@lamina(schema_in=ExampleInput)\ndef handler(request: Request):\n response = {\"message\": f\"Hello {request.data.name}!\"}\n return response, 200, {\n \"Cache-Control\": \"max-age=3600\",\n \"X-Custom-Header\": \"custom-value\"\n }\n```\n\n## Hooks\n\nLamina provides four extensibility points executed around your handler.\n\nConfiguration (pyproject.toml):\n\n```toml\n[tool.lamina]\npre_parse_callback = \"lamina.hooks.pre_parse\"\npre_execute_callback = \"lamina.hooks.pre_execute\"\npos_execute_callback = \"lamina.hooks.pos_execute\"\npre_response_callback = \"lamina.hooks.pre_response\"\n```\n\nEnvironment variables override these values at runtime:\n- LAMINA_PRE_PARSE_CALLBACK\n- LAMINA_PRE_EXECUTE_CALLBACK\n- LAMINA_POS_EXECUTE_CALLBACK\n- LAMINA_PRE_RESPONSE_CALLBACK\n\nHook signatures and responsibilities:\n- pre_parse(event, context) -> event\n- pre_execute(request, event, context) -> request\n- pos_execute(response, request) -> response\n- pre_response(body, request) -> body\n\n### The Request Object\n\nThe `Request` object provides access to:\n\n- `data`: The validated input data (as a Pydantic model if schema_in is provided)\n- `event`: The original AWS Lambda event\n- `context`: The original AWS Lambda context\n\n### Using Without Schemas\n\nYou can use Lamina without schemas for more flexibility:\n\n```python\nimport json\nfrom lamina import lamina, Request\n\n@lamina()\ndef handler(request: Request):\n # Parse the body manually\n body = json.loads(request.event[\"body\"])\n name = body.get(\"name\", \"Guest\")\n age = body.get(\"age\", \"unknown\")\n\n return {\n \"message\": f\"Hello {name}, you are {age} years old!\"\n }\n```\n\n> **Note**: Without a schema_in, the `request.data` attribute contains the raw body string from the event. You'll need to parse and validate it manually.\n\n### AWS Step Functions Integration\n\nLamina supports AWS Step Functions with the `step_functions` parameter:\n\n```python\n@lamina(schema_in=ExampleInput, schema_out=ExampleOutput, step_functions=True)\ndef handler(request: Request):\n # For Step Functions, the input is directly available as the event\n # No need to parse from event[\"body\"]\n return {\n \"message\": f\"Step function processed for {request.data.name}\"\n }\n```\n\n### Error Handling\n\nLamina automatically handles common errors:\n\n- **Validation Errors**: Returns 400 Bad Request with detailed validation messages\n- **Type Errors**: Returns 400 Bad Request when input cannot be parsed\n- **Serialization Errors**: Returns 500 Internal Server Error when output cannot be serialized\n- **Unhandled Exceptions**: Returns 500 Internal Server Error with the error message\n\nAll errors are logged using the loguru library for easier debugging.\n\n## Contributing\n\nContributions are welcome! Here's how you can help:\n\n1. **Fork the repository** and clone it locally\n2. **Create a new branch** for your feature or bugfix\n3. **Make your changes** and add tests if applicable\n4. **Run the tests** to ensure they pass: `poetry run pytest`\n5. **Submit a pull request** with a clear description of your changes\n\nPlease make sure your code follows the project's style guidelines by running:\n```shell\npoetry run pre-commit run --all\n```\n\n### Development Setup\n\n1. Clone the repository\n2. Install dependencies with Poetry:\n ```shell\n poetry install\n ```\n3. Install pre-commit hooks:\n ```shell\n poetry run pre-commit install\n ```\n\n## License\n\nThis project is licensed under the terms of the MIT license.\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Add a new layer (\"l\u00e2mina\") to AWS lambda functions",
"version": "5.0.2",
"project_urls": {
"Homepage": "https://github.com/megalus/lamina",
"Repository": "https://github.com/megalus/lamina"
},
"split_keywords": [
"aws",
" lambda",
" decorator"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "ac55bd39235f238b2637b6de20cc5c60af465ff2ba2cc170448c82d46e64d86c",
"md5": "61a6f79a3e0cf615b36ff66d490b6273",
"sha256": "c37a9acd447ebff3ccda75007392066c44ccaaa942bf8e7a91be446e50136190"
},
"downloads": -1,
"filename": "py_lamina-5.0.2-py3-none-any.whl",
"has_sig": false,
"md5_digest": "61a6f79a3e0cf615b36ff66d490b6273",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4,>=3.11",
"size": 9346,
"upload_time": "2025-09-14T14:42:11",
"upload_time_iso_8601": "2025-09-14T14:42:11.030031Z",
"url": "https://files.pythonhosted.org/packages/ac/55/bd39235f238b2637b6de20cc5c60af465ff2ba2cc170448c82d46e64d86c/py_lamina-5.0.2-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "e470fc2a4794b1b9a176d1a9d6b0a31c6d960cf8e840c3878f2b674c1a7f333a",
"md5": "cc450f3615ecdf23a70287fd408d658f",
"sha256": "d3eb624c837fd99e3b9bc33771a00004ad6759b109651ce493335724967dcd87"
},
"downloads": -1,
"filename": "py_lamina-5.0.2.tar.gz",
"has_sig": false,
"md5_digest": "cc450f3615ecdf23a70287fd408d658f",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4,>=3.11",
"size": 8092,
"upload_time": "2025-09-14T14:42:12",
"upload_time_iso_8601": "2025-09-14T14:42:12.297307Z",
"url": "https://files.pythonhosted.org/packages/e4/70/fc2a4794b1b9a176d1a9d6b0a31c6d960cf8e840c3878f2b674c1a7f333a/py_lamina-5.0.2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-09-14 14:42:12",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "megalus",
"github_project": "lamina",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "py-lamina"
}