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]