Skip to content

Commit

Permalink
Merge pull request #16 from mirumee/typing
Browse files Browse the repository at this point in the history
feat: add strict typing
  • Loading branch information
pkucmus authored Dec 28, 2024
2 parents 3a8ba24 + cbd6add commit 2bde2fe
Show file tree
Hide file tree
Showing 30 changed files with 764 additions and 1,007 deletions.
162 changes: 152 additions & 10 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
Expand All @@ -12,20 +19,155 @@ lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

.ruff_cache/
__pycache__
# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock

# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock

.DS_Store
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock

# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.pytest_cache
.python-version
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

package
*.zip
.mypy_cache
.coverage
# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

# PyPI configuration file
.pypirc

.ruff_cache/
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
# Smyth

<div align="center">
<img src="docs/assets/logo_white_small.svg" width="300" role="img">

[![docs](https://img.shields.io/badge/Docs-Smyth-f5c03b.svg?style=flat&logo=materialformkdocs)](https://mirumee.github.io/smyth/)
![pypi](https://img.shields.io/pypi/v/smyth?style=flat)
![licence](https://img.shields.io/pypi/l/smyth?style=flat)
![pypi downloads](https://img.shields.io/pypi/dm/smyth?style=flat)
![pyversion](https://img.shields.io/pypi/pyversions/smyth?style=flat)
</div>

Smyth is a versatile tool designed to enhance your AWS Lambda development experience. It is a pure Python tool that allows for easy customization and state persistence, making your Lambda development more efficient and developer-friendly.
Smyth is a tool designed to enhance your AWS Lambda development experience by mocking an AWS Lambda environment on your **local machine**. It is a pure Python tool that allows for easy customization and state persistence, making your Lambda development more efficient and developer-friendly.

## Features

Expand Down Expand Up @@ -43,6 +47,9 @@ handler_path = "my_project.handlers.saleor.handler.saleor_http_handler"
url_path = "/saleor/{path:path}"
```

> [!TIP]
> Check the [documentation](https://mirumee.github.io/smyth/user_guide/all_settings/) for more configuration options.
Run Smyth with:
```bash
python -m smyth
Expand Down Expand Up @@ -75,11 +82,6 @@ To utilize the VS Code debugger with the Smyth tool, you can set up your `launch

The combination of Uvicorn reload process and HTTP server process with what is being done with the Lambda processes is tricky. If a Lambda process is doing something and the HTTP server is killed in the wrong moment it's likely going to bork your terminal. This is not solved yet. It's best to use in a Docker container or have the ability to `kill -9 {PID of the Uvicorn reload process}` at hand.

## TODO

- [ ] Write tests
- [x] Publish on PyPi

## Name

This name blends "Smith" (as in a blacksmith, someone who works in a forge) with "Py" for Python, altering the spelling to "Smyth". Blacksmiths are craftsmen who work with metal in a forge, shaping it into desired forms. Similarly, "Smyth" suggests a tool that helps developers craft and shape their serverless projects with the precision and skill of a smith, but in the realm of Python programming. This name retains the essence of craftsmanship and transformation inherent in a forge while being associated with Python.
Expand Down
55 changes: 38 additions & 17 deletions docs/user_guide/all_settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,24 @@ Here's a list of all the settings, including those that are simpler but equally

## Smyth Settings

### Host
### Socket Binding

`host` - `str` (default: `"0.0.0.0"`) Used by Uvicorn to bind to an address.

### Port

`port` - `int` (default: `8080`) Used by Uvicorn as the bind port.

### Log Level
### Logging

`log_level` - `str` (default: `"INFO"`) Sets the logging level for the `uvicorn` and `smyth` logging handlers.

### Smyth Path Prefix
### Smyth Internals

`smyth_path_prefix` - `str` (default: `"/smyth"`) The path prefix used for Smyth's status endpoint. Change this if, for any reason, it collides with your path routing.

### Environment

`env` - `dict[str, str]` (default: `{}`) Environment variables to apply to every handler. Read more about [environment variables here](environment.md).

## Handler Settings

### Handler Path
Expand All @@ -28,28 +30,47 @@ Here's a list of all the settings, including those that are simpler but equally

### URL Path

`url_path` - `str` (required) The Starlette routing path on which your handler will be exposed.
`url_path` - `str` (required) The [Starlette routing](https://www.starlette.io/routing/#http-routing) path on which your handler will be exposed.

### Timeout
### Environment

`timeout` - `float` (default: `None`, which means no timeout) The time in seconds after which the Lambda Handler raises a Timeout Exception, simulating Lambda's real-life timeouts.
`env` - `dict[str, str]` (default: `{}`) Environment variables to apply to this handler - keys defined here take precedence over the ones defined in `tool.smyth.env` and be otherwise merged. Read more about [environment variables here](environment.md).

### Event Data Function
### Customization

`event_data_function_path` - `str` (default: `"smyth.event.generate_api_gw_v2_event_data"`) Read more about [event functions here](event_functions.md).

### Context Data Function

`context_data_function_path` - `str` (default: `"smyth.context.generate_context_data"`) A function similar to the [event generator](event_functions.md), but it constructs the `context`, adding some metadata from Smyth's runtime. You can create and use your own.

### Log Level

`log_level` - `str` (default: `"INFO"`) Log level for Smyth's runner function, which is still part of Smyth but already running in the subprocess. Note that the logging of your Lambda handler code should be set separately.
### Behaviour

### Concurrency
`timeout` - `float` (default: `None`, which means no timeout) The time in seconds after which the Lambda Handler raises a Timeout Exception, simulating Lambda's real-life timeouts.

`concurrency` - `int` (default: `1`) Read more about [concurrency here](concurrency.md).

### Strategy Generator

`strategy_generator_path` - `str` (default: `"smyth.runner.strategy.first_warm"`) Read more about [dispatch strategies here](concurrency.md/#dispatch-strategy).

### Logging

`log_level` - `str` (default: `"INFO"`) Log level for Smyth's runner function, which is still part of Smyth but already running in the subprocess. Note that the logging of your Lambda handler code should be set separately.


## `pyproject.toml` example

```toml title='pyproject.toml' linenums="1"
[tool.smyth]
host = "0.0.0.0"
port = 8080
log_level = "INFO"
smyth_path_prefix = "/smyth"

[tool.smyth.handlers.lambda_handler]
handler_path = "myproject.app.lambda_handler"
url_path = "{path:path}"
timeout = 300
event_data_function_path = "smyth.event.generate_api_gw_v2_event_data"
context_data_function_path = "smyth.context.generate_context_data"
log_level = "DEBUG"
concurrency = 3
strategy_generator_path = "smyth.runner.strategy.first_warm"
```
3 changes: 2 additions & 1 deletion docs/user_guide/custom_entrypoint.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@ Here's an example `smyth_conf.py` file:
import uvicorn
from smyth.server.app import SmythStarlette
from smyth.smyth import Smyth
from smyth.types import EventData, RunnerProcessProtocol, SmythHandler
from starlette.requests import Request


def my_handler(event, context):
return {"statusCode": 200, "body": "Hello, World!"}

async def my_event_data_generator(request: Request):
async def my_event_data_generator(request: Request, smyth_handler: SmythHandler, process: RunnerProcessProtocol) -> EventData:
return {
"requestContext": {
"http": {
Expand Down
61 changes: 61 additions & 0 deletions docs/user_guide/environment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Environment

Smyth allows you to overwrite the environemnt variables that are passed to the handlers to better reflect the actual AWS Lambda environment while allowing you to change things while developing locally.


```toml title='pyproject.toml' linenums="1"
[tool.smyth]
host = "0.0.0.0"
port = 8080
...

[tool.smyth.env]
AWS_ENDPOINT = "http://localstack:4566"
AWS_LAMBDA_FUNCTION_VERSION = "$SMYTH"

[tool.smyth.handlers.my_special_version_handler]
handler_path = "mypyoject.app.my_special_version_handler"
url_path = "{path:path}"
...

[tool.smyth.handlers.my_special_version_handler.env]
AWS_LAMBDA_FUNCTION_VERSION = "34"
```

The config above allows you to set a specific env var for every defined handler and overwrite or set
specific values for individual handlers. In the example every handler would receive the
`AWS_ENDPOINT = "http://localstack:4566"` and `AWS_LAMBDA_FUNCTION_VERSION = "$SMYTH"` env vars with
the exception of `my_special_version_handler` which will have a different version.

## Fake context

The `smyth.runner.fake_context.FakeLambdaContext` class used by Smyth will also consume of of the environment variables.

## Default variables

In the table bellow you will find which keys are set by Smyth when a handler is being invoked.
Smyth will look for the key in the following order:

1. the handler configuration `env` key
2. the smyth global configuration `env` key
3. `os.environ`
4. when none of the above contain the key the default value is assigned

| Key | Default Value |
| ----------------------------------- | ---------------------------------------------------------------------- |
| `"_HANDLER"` | `self.lambda_handler_path` |
| `"AWS_ACCESS_KEY_ID"` | `"000000000000"` |
| `"AWS_SECRET_ACCESS_KEY"` | `"test"` |
| `"AWS_SESSION_TOKEN"` | `"test"` |
| `"AWS_DEFAULT_REGION"` | `"eu-central-1"` |
| `"AWS_REGION"` | `"eu-central-1"` |
| `"AWS_EXECUTION_ENV"` | `"AWS_Lambda_python{sys.version_info.major}.{sys.version_info.minor}"` |
| `"AWS_LAMBDA_FUNCTION_MEMORY_SIZE"` | `"128"` |
| `"AWS_LAMBDA_FUNCTION_NAME"` | `self.name` |
| `"AWS_LAMBDA_FUNCTION_VERSION"` | `"$LATEST"` |
| `"AWS_LAMBDA_INITIALIZATION_TYPE"` | `"on-demand"` |
| `"AWS_LAMBDA_LOG_GROUP_NAME"` | `"/aws/lambda/{self.name}"` |
| `"AWS_LAMBDA_LOG_STREAM_NAME"` | `"{strftime('%Y/%m/%d')}/[$LATEST]smyth_aws_lambda_log_stream_name"` |
| `"AWS_LAMBDA_RUNTIME_API"` | `"127.0.0.1:9001"` |
| `"AWS_XRAY_CONTEXT_MISSING"` | `"LOG_ERROR"` |
| `"AWS_XRAY_DAEMON_ADDRESS"` | `"127.0.0.1:2000"` |
5 changes: 4 additions & 1 deletion docs/user_guide/event_functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ The first one builds a minimal API Gateway Proxy V2 event to simulate a Lambda b
If you need to work with events not covered by Smyth, you can create and provide your own. Assuming a simplified API Gateway V1 event, you can create a generator like this:

```python title="my_project/src/smyth_utils/event.py" linenums="1"
async def generate_api_gw_v1_event_data(request: Request):
from smyth.types import EventData, RunnerProcessProtocol, SmythHandler


async def generate_api_gw_v1_event_data(request: Request, smyth_handler: SmythHandler, process: RunnerProcessProtocol) -> EventData:
source_ip = None
if request.client:
source_ip = request.client.host
Expand Down
Loading

0 comments on commit 2bde2fe

Please sign in to comment.