General description
===================
Independently of lambda type there are several general things whose applicable for every lambda.
Luna lambda tools
-----------------
It is required the `luna-lambda-tools` package which is available at `VL pypi `_ for development.
This package is available as *luna_lambda_tools* library and will be used in all examples below.
To install `luna-lambda-tools` locally, execute:
.. code-block:: bash
pip install --trusted-host pypi.visionlabs.ru --extra-index-url http://pypi.visionlabs.ru/root/public/+simple luna-lambda-tools
The `luna-lambda-tools` package decided in two parts - public and private: private part is not intended for user and
does not guarantee backward compatibility, public part is intended for user and guarantees backward compatibility.
.. raw:: html
The public part code description.
.. autosummary::
:toctree: _autosummary
:recursive:
luna_lambda_tools.public
.. raw:: html
User context
------------
It is possible to make the lambda do some stuff after startup and before shutdown declaring `UserCtx` class
with asynchronous `onStart` and/or `onShutdown` functions.
For example, it is possible to initialize and close connection to database:
.. code-block:: python
from luna_lambda_tools import logger
def setupDB():
...
def closeDBConnections():
...
class UserCtx:
async def onStart(self):
logger.info("start lambda")
setupDB()
async def onShutdown(self):
logger.info("shutdown lambda")
closeDBConnections()
It is possible to use initialize `UserCtx` at application start, use during request processing and close at
application shutdown:
.. literalinclude:: examples/201/user_context_default_lambda/lambda/lambda_main.py
:caption: lambda_main.py
:language: python
.. literalinclude:: examples/201/user_context_default_lambda/make_request.py
:caption: request example
:language: python
This example demonstrates making requests to external server using for session for all requests:
.. literalinclude:: examples/201/user_context_external_requests_lambda/lambda/lambda_main.py
:caption: lambda_main.py
:language: python
.. literalinclude:: examples/201/user_context_external_requests_lambda/make_request.py
:caption: request example
:language: python
Logger
------
It is possible to use logger from luna-lambda-tools anywhere in lambda.
.. literalinclude:: examples/201/logger_lambda/lambda/lambda_main.py
:caption: lambda_main.py
:language: python
.. literalinclude:: examples/201/logger_lambda/make_request.py
:caption: request example
:language: python
Lambda modules structure
------------------------
It is possible to add and use more than one python module. Here is an example of file structure and
python source code which is required for proper lambda work.
.. code-block::
:caption: Archive file structure
├──lambda_main.py
├──path
│ ├──fileinpath1.py
│ └──fileinpath2.py
└──file1.py
.. literalinclude:: examples/201/lambda_modules_structure/lambda/lambda_main.py
:caption: lambda_main.py
:language: python
.. literalinclude:: examples/201/lambda_modules_structure/lambda/file1.py
:caption: file1.py
:language: python
.. literalinclude:: examples/201/lambda_modules_structure/lambda/path/fileinpath1.py
:caption: fileinpath1.py
:language: python
.. literalinclude:: examples/201/lambda_modules_structure/lambda/path/fileinpath2.py
:caption: fileinpath2.py
:language: python
.. literalinclude:: examples/201/lambda_modules_structure/make_request.py
:caption: request example
:language: python
User configuration file
-----------------------
It is possible to add a configuration file for lambda in YAML format. The file must be named *lambda_config.yml* and placed in the root of the zip archive.
.. code-block::
:caption: Archive file structure
├──lambda_main.py
└──lambda_config.yml
You can import this configuration to your lambda code and use as python dictionary object.
.. literalinclude:: examples/201/user_configuration_file/lambda/lambda_config.yml
:caption: lambda_config.yml
:language: yaml
.. literalinclude:: examples/201/user_configuration_file/lambda/lambda_main.py
:caption: lambda_main.py
:language: python
.. literalinclude:: examples/201/user_configuration_file/make_request.py
:caption: request example
:language: python
Lambda exceptions
-----------------
It is possible to separate exceptions into two types: expected and unexpected.
Expected exceptions must be inherited from `UserException`. For example:
.. literalinclude:: examples/400/lambda_exceptions/lambda/lambda_main.py
:caption: lambda_main.py
:language: python
.. literalinclude:: examples/400/lambda_exceptions/make_request.py
:caption: request example
:language: python
In the case presented above for any request with images quantity other than two, it will raise `ImageCountException` and
then, *luna-handlers* will process exception and return a response to the user with 400 status code and `expected two images in request`
message in detail.
Unexpected exceptions will proceed in another way. If any exception occurs, *luna-handlers* will return a response to
the user with 500 status code and exception text in detail. For example:
.. code-block::
:caption: lambda_main.py
from luna_lambda_tools import StandaloneLambdaRequest
async def main(request: StandaloneLambdaRequest) -> dict:
abc = 1/0
...
will cause response with `division by zero` as exception detail.
Luna services clients
---------------------
The lambda can use luna-services for different operations.
The clients from `luna-lambda-tools` represent wrap for VL luna3 library (also available at `VL pypi `_).
As well as `luna-lambda-tools`, `luna3` has a public part that is intended for user and guarantee backward compatibility.
Example of clients usage for *standalone*/*handlers* lambdas (for clients usage in *tasks* lambdas see `tasks lambda examples <./tasks.html#tasks-lambda-examples>`_):
.. literalinclude:: examples/201/luna_services_clients/lambda/lambda_main.py
:caption: lambda_main.py
:language: python
.. literalinclude:: examples/201/luna_services_clients/make_request.py
:caption: request example
:language: python
.. toctree::
:maxdepth: 2
luna_clients
It also possible to check whether luna service enabled or disabled using several properties:
.. code-block::
:caption: lambda_main.py
from luna_lambda_tools import StandaloneLambdaRequest
async def main(request: StandaloneLambdaRequest) -> dict:
request.logger.info(f"events enabled: {request.eventsEnabled}")
request.logger.info(f"sender enabled: {request.senderEnabled}")
request.logger.info(f"handlers enabled: {request.handlersEnabled}")
...
It can be useful if lambda can optional use one of this service, for example, save event if *Luna-Events* is enabled:
.. code-block::
:caption: lambda_main.py
from luna_lambda_tools import StandaloneLambdaRequest
async def main(request: StandaloneLambdaRequest) -> dict:
if request.eventsEnabled:
request.clients.events.saveEvents(events=[...])
return {"status": "success"}
Additional routes
---------------------
Each lambda type can be extended with additional routes with custom functions.
For example:
- *standalone* lambda with additional routes. Additional routes for *handlers* lambda can be added the same way.
.. literalinclude:: examples/201/standalone_lambda_additional_routes/lambda/lambda_main.py
:caption: lambda_main.py
:language: python
.. literalinclude:: examples/201/standalone_lambda_additional_routes/make_request.py
:caption: request example
:language: python
- *tasks* lambda with additional routes.
.. literalinclude:: examples/201/tasks_lambda_additional_routes/lambda/lambda_main.py
:caption: lambda_main.py
:language: python
Each handler that needs to be added to lambda must be part of the constant array `ROUTES_TO_ADD` within `lambda_main.py` file, otherwise handler will be ignored.
**Note that `ROUTES_TO_ADD` assignment must follow this format:** ``ROUTES_TO_ADD = [AdditionalHandler, ...]``. **Assignments from functions, other variables, etc. will cause validation error.**
There are strict requirements for the format of custom handlers:
- Handler must be declared within `lambda_main.py`
- Handler must be inherited from `LambdaUserHandler`
- Handler must have `route` attribute assignment within class body. **Note that `route` must be assigned to a constant or formatted string, not to a function result or other variable**
- Handler must implement at least one of the async methods: `post`, `get`, `put`, `patch`, `head`, `options`, `delete`
- If route includes path parameters, this parameters must be included into method signature with the same name. See routing format description `here `_.
- Custom route must not match default routes such as `/healthcheck`, `/main`, `/docs/spec`, `/config`
**Note that `route` and http `post`, `get`, `put`, `patch`, `head`, `options`, `delete` methods must be declared within one final class (not in parent classes)**
Breaking one of this requirement will cause lambda validation error
Some examples of handlers declaration.
- `route` without parameters.
.. code-block::
:caption: lambda_main.py
class AdditionalHandler(LambdaUserHandler):
route: str = "/new_route"
async def post(self):
return self.sendResponse(201, json={"response": parameter})
ROUTES_TO_ADD = [AdditionalHandler]
- `route` with two parameters.
.. code-block::
:caption: lambda_main.py
class AdditionalHandler(LambdaUserHandler):
route: str = r"/new_route//"
async def post(self, parameterOne, parameterTwo):
return self.sendResponse(201, json={"response": parameter})
ROUTES_TO_ADD = [AdditionalHandler]
- Handler with inheritance.
.. code-block::
:caption: lambda_main.py
class BaseAdditionalHandler(LambdaUserHandler):
def getResponse(self, parameter):
return {"response": parameter}
class AdditionalHandler(BaseAdditionalHandler):
route: str = r"/new_route/"
async def post(self, parameter):
return self.sendResponse(201, json=self.getResponse(parameter))
ROUTES_TO_ADD = [AdditionalHandler]