From 73412353c0fa5a78587d56979b60b13c8972f38d Mon Sep 17 00:00:00 2001 From: keithrozario Date: Thu, 16 Apr 2020 08:07:08 +0800 Subject: [PATCH 1/3] new acceptance tests --- tests/acceptance_tests/_test_ssm.py | 18 ++++-------------- tests/acceptance_tests/serverless.yml | 3 ++- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/tests/acceptance_tests/_test_ssm.py b/tests/acceptance_tests/_test_ssm.py index a0a8b0f..c891429 100644 --- a/tests/acceptance_tests/_test_ssm.py +++ b/tests/acceptance_tests/_test_ssm.py @@ -2,6 +2,7 @@ import boto3 from lambda_cache import ssm +from datetime import datetime # this file is packaged in the lambda using serverless.yml from variables_data import * @@ -30,19 +31,8 @@ def multi_parameter_2(event, context): client = boto3.client('ssm') response = client.put_parameter( Name=ssm_parameter, - Value='string', - Type='String'|'StringList'|'SecureString', - KeyId='string', - Overwrite=True|False, - AllowedPattern='string', - Tags=[ - { - 'Key': 'string', - 'Value': 'string' - }, - ], - Tier='Standard'|'Advanced'|'Intelligent-Tiering', - Policies='string' -) + Value=datetime.now().isoformat(), + Type='String', + Overwrite=True) return generic_return(message) \ No newline at end of file diff --git a/tests/acceptance_tests/serverless.yml b/tests/acceptance_tests/serverless.yml index c9c7484..b30f959 100644 --- a/tests/acceptance_tests/serverless.yml +++ b/tests/acceptance_tests/serverless.yml @@ -19,6 +19,7 @@ provider: Action: - ssm:GetParameter - ssm:GetParameters + - ssm:PutParameter Resource: - Fn::Join: - ":" @@ -89,7 +90,7 @@ package: - tests/** functions: - single_handler: + acceptance_test: handler: _test_ssm.multi_parameter_2 layers: - arn:aws:lambda:ap-southeast-1:908645878701:layer:pylayers-python38-defaultlambda-cache:1 From 9e2ddb3ebe85a52886de85a4014b121017e3b023 Mon Sep 17 00:00:00 2001 From: keithrozario Date: Thu, 16 Apr 2020 10:05:36 +0800 Subject: [PATCH 2/3] fixed issues --- README.md | 8 ++++---- lambda_cache/__init__.py | 15 +++++++++++++-- lambda_cache/caching_logic.py | 6 ++++++ lambda_cache/exceptions.py | 20 +++++++++++++++++--- lambda_cache/s3.py | 5 +++-- pyproject.toml | 8 ++++---- 6 files changed, 47 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 58bb9c4..f503fb9 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@

lambda-cache

Simple Caching for AWS Lambda

-![PackageStatus](https://img.shields.io/static/v1?label=status&message=beta&color=blueviolet?style=flat-square) [![PyPI version](https://badge.fury.io/py/lambda-cache.svg)](https://badge.fury.io/py/lambda-cache) ![PythonSupport](https://img.shields.io/static/v1?label=python&message=3.6%20|%203.7|%203.8&color=blue?style=flat-square&logo=python) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) +## Release info: +![PackageStatus](https://img.shields.io/pypi/status/lambda-cache) [![PyPI version](https://badge.fury.io/py/lambda-cache.svg)](https://badge.fury.io/py/lambda-cache) ![PythonSupport](https://img.shields.io/static/v1?label=python&message=3.6%20|%203.7|%203.8&color=blue?style=flat-square&logo=python) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![Documentation Status](https://readthedocs.org/projects/lambda-cache/badge/?version=latest)](https://lambda-cache.readthedocs.io/en/latest/?badge=latest) -![Test](https://github.com/keithrozario/lambda-cache/workflows/Test/badge.svg?branch=release) [![Coverage Status](https://coveralls.io/repos/github/keithrozario/lambda-cache/badge.svg?branch=release)](https://coveralls.io/github/keithrozario/lambda-cache?branch=release) [![Documentation Status](https://readthedocs.org/projects/lambda-cache/badge/?version=latest)](https://lambda-cache.readthedocs.io/en/latest/?badge=latest) - - [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) +## Build info: +![Test](https://github.com/keithrozario/lambda-cache/workflows/Test/badge.svg?branch=release) [![Coverage Status](https://coveralls.io/repos/github/keithrozario/lambda-cache/badge.svg?branch=release)](https://coveralls.io/github/keithrozario/lambda-cache?branch=release) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/ad70a44cb3e54d7ba600edc5fa89635c)](https://www.codacy.com/manual/keithrozario/lambda-cache?utm_source=github.com&utm_medium=referral&utm_content=keithrozario/lambda-cache&utm_campaign=Badge_Grade) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) # Introduction diff --git a/lambda_cache/__init__.py b/lambda_cache/__init__.py index 1fae966..15001a2 100644 --- a/lambda_cache/__init__.py +++ b/lambda_cache/__init__.py @@ -1,5 +1,16 @@ -__all__ = ["cache", "get_entry"] -__version__ = "0.8.0" +# -*- coding: utf-8 -*- + +""" +lambda-cache +~~~~~~~~~~~~ + +A python package for caching within AWS Lambda Functions + +Full Documentation is at . +:license: MIT, see LICENSE for more details. +""" + +__version__ = "0.8.1" from .ssm import cache, get_entry from .secrets_manager import cache, get_entry diff --git a/lambda_cache/caching_logic.py b/lambda_cache/caching_logic.py index a8c4912..adf4f3a 100644 --- a/lambda_cache/caching_logic.py +++ b/lambda_cache/caching_logic.py @@ -5,6 +5,7 @@ def get_decorator(**kwargs): + """ Args: argument (string, list, dict) : argument to be passed to the missed function @@ -34,6 +35,7 @@ def inner_function(event, context): def get_value(**kwargs): + """ returns value of check_cache. """ @@ -50,6 +52,7 @@ def check_cache( send_details=False, **kwargs ): + """ Executes the caching logic, checks cache for entry If entry doesn't exist, returns entry_value by calling the miss function with entry_name and var_name @@ -97,6 +100,7 @@ def check_cache( def get_entry_name(argument, entry_name): + """ argument is either SSM Parameter, Secret in Secrets Manager or Key in S3 bucket: SSM Parameter names can include only the following symbols and letters: a-zA-Z0-9_.-/ @@ -136,6 +140,7 @@ def get_entry_name(argument, entry_name): def get_entry_age(entry_name): + """ Args: entry_name(string): Name of entry to get age for @@ -180,6 +185,7 @@ def update_cache(entry_name, entry_value): def get_entry_from_cache(entry_name): + """ Gets entry value from the cache diff --git a/lambda_cache/exceptions.py b/lambda_cache/exceptions.py index 9874911..16545a6 100644 --- a/lambda_cache/exceptions.py +++ b/lambda_cache/exceptions.py @@ -1,11 +1,17 @@ class LambdaCacheError(Exception): - """Base class for exceptions in this module.""" + + """ + Base class for exceptions in this module. + """ pass class ArgumentTypeNotSupportedError(LambdaCacheError): - """Raised when Argument is not supported by the function.""" + + """ + Raised when Argument is not supported by the function. + """ def __init__(self, message): self.message = message @@ -13,7 +19,10 @@ def __init__(self, message): class NoEntryNameError(LambdaCacheError): - """Raised when No entry_name is provided.""" + + """ + Raised when No entry_name is provided. + """ def __init__(self, message=False): self.message = "No entry_name provided" @@ -21,6 +30,11 @@ def __init__(self, message=False): class InvalidS3UriError(LambdaCacheError): + + """ + s3Uri provided in invalid format + """ + def __init__(self, invalid_uri): self.message = f"Expected Valid s3uri of the form 's3://bucket-name/path/to/file', given: {invalid_uri}" self.Code = "InvalidS3UriError" diff --git a/lambda_cache/s3.py b/lambda_cache/s3.py index 05a7a3d..35e57a5 100644 --- a/lambda_cache/s3.py +++ b/lambda_cache/s3.py @@ -1,8 +1,8 @@ import boto3 from datetime import datetime, timezone -from .caching_logic import get_decorator, get_value, get_entry_name -from .exceptions import ArgumentTypeNotSupportedError, InvalidS3UriError +from .caching_logic import get_decorator, get_value +from .exceptions import InvalidS3UriError def cache( @@ -62,6 +62,7 @@ def get_entry( def get_object_from_s3(**kwargs): + """ Gets parameter value from the System manager Parameter store diff --git a/pyproject.toml b/pyproject.toml index 35a0a6e..60c676b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,11 @@ [tool.poetry] name = "lambda-cache" -version = "0.8.0" +version = "0.8.1" description = "Python utility for simple caching in AWS Lambda functions" authors = ["keithrozario "] -documentation = "https://simple-lambda-cache.readthedocs.io/en/latest/" -repository = "https://github.com/keithrozario/simple_lambda_cache" -homepage = "https://github.com/keithrozario/simple_lambda_cache" +documentation = "https://lambda-cache.readthedocs.io/en/latest/" +repository = "https://github.com/keithrozario/lambda-cache" +homepage = "https://github.com/keithrozario/lambda-cache" classifiers=[ "Development Status :: 4 - Beta", "Intended Audience :: Developers", From 0891df5d63e963cdc96d3337219454acbe6f7e5d Mon Sep 17 00:00:00 2001 From: keithrozario Date: Thu, 16 Apr 2020 18:16:00 +0800 Subject: [PATCH 3/3] latest docs --- .gitignore | 3 + README.md | 298 +++++++++++++++++++++++++++--- docs/images/installed_package.png | Bin 0 -> 64403 bytes docs/install.md | 18 +- 4 files changed, 287 insertions(+), 32 deletions(-) create mode 100644 docs/images/installed_package.png diff --git a/.gitignore b/.gitignore index f6b4b7d..10a7f3b 100644 --- a/.gitignore +++ b/.gitignore @@ -138,3 +138,6 @@ dmypy.json # Terraform .terraform/ *.tfstate + +# serverless +.serverless/ diff --git a/README.md b/README.md index f503fb9..062a668 100644 --- a/README.md +++ b/README.md @@ -1,65 +1,311 @@

lambda-cache

Simple Caching for AWS Lambda

-## Release info: -![PackageStatus](https://img.shields.io/pypi/status/lambda-cache) [![PyPI version](https://badge.fury.io/py/lambda-cache.svg)](https://badge.fury.io/py/lambda-cache) ![PythonSupport](https://img.shields.io/static/v1?label=python&message=3.6%20|%203.7|%203.8&color=blue?style=flat-square&logo=python) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![Documentation Status](https://readthedocs.org/projects/lambda-cache/badge/?version=latest)](https://lambda-cache.readthedocs.io/en/latest/?badge=latest) +![PackageStatus](https://img.shields.io/pypi/status/lambda-cache) [![PyPI version](https://badge.fury.io/py/lambda-cache.svg)](https://badge.fury.io/py/lambda-cache) ![PythonSupport](https://img.shields.io/static/v1?label=python&message=3.6%20|%203.7%20|%203.8&color=blue?style=flat-square&logo=python) ![License](https://img.shields.io/pypi/l/lambda-cache) [![Documentation Status](https://readthedocs.org/projects/lambda-cache/badge/?version=latest)](https://lambda-cache.readthedocs.io/en/latest/?badge=latest) -## Build info: ![Test](https://github.com/keithrozario/lambda-cache/workflows/Test/badge.svg?branch=release) [![Coverage Status](https://coveralls.io/repos/github/keithrozario/lambda-cache/badge.svg?branch=release)](https://coveralls.io/github/keithrozario/lambda-cache?branch=release) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/ad70a44cb3e54d7ba600edc5fa89635c)](https://www.codacy.com/manual/keithrozario/lambda-cache?utm_source=github.com&utm_medium=referral&utm_content=keithrozario/lambda-cache&utm_campaign=Badge_Grade) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) # Introduction -![Screenshot](docs/images/lambda_cache.png) +

-_lambda-cache_ helps you cache data in your Lambda function **across** invocations. The package utilizes the internal memory of the lambda function's [execution context](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-context.html) to assist in caching, which in turn: +_lambda-cache_ helps you cache data in your Lambda function **across** invocations. The package utilizes the internal memory of the lambda function's [execution context](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-context.html) to store data across multiple invocations. Doing this: * Reduces load on back-end systems * Reduces the execution time of the lambda -* Guarantees functions will reference latest data after caches have expired -* Reduces expensive network calls to APIs with throttling limits (or high charges) +* Guarantees functions will reference latest data after cache expiry +* Reduces calls to APIs throttling limits or high charges -_lambda-cache_ prioritizes simplicity over performance or flexibility. The goal is to provide the **simplest** way for developers to cache data across lambda invocations. +_lambda-cache_ prioritizes simplicity over performance and flexibility. -The package is purpose-built for running in AWS Lambda functions, and currently supports SSM Parameters, Secrets from Secrets Manager and S3 Objects. +The package is purpose-built for AWS Lambda functions, and currently supports SSM Parameters, Secrets from Secrets Manager and S3 Objects. # Installation - $ pip install lambda-cache +Include the package in your functions code zip-file using the following: -Refer to [docs](https://lambda-cache.readthedocs.io/en/latest/) for how to include into your lambda function. + $ pip install lambda-cache -t /path/of/function + +Refer to [docs](https://lambda-cache.readthedocs.io/en/latest/install/) for how to include into your lambda function. # Usage -To begin caching parameters, secrets or S3, decorate your function's handler with the right decorator: +Below we describe further features of the package. For more info refer to the [user guide](https://lambda-cache.readthedocs.io/en/latest/user_guide/). + + +* [SSM - Parameter Store](#SSM-ParameterStore) + * [Cache single parameter](#Cachesingleparameter) + * [Change cache expiry](#Changecacheexpiry) + * [Change cache entry settings](#Changecacheentrysettings) + * [Cache multiple parameters](#Cachemultipleparameters) + * [Decorator stacking](#Decoratorstacking) + * [Cache invalidation](#Cacheinvalidation) + * [Return Values](#ReturnValues) +* [Secrets Manager](#SecretsManager) + * [Cache single secret](#Cachesinglesecret) + * [Change Cache expiry](#ChangeCacheexpiry) + * [Change Cache entry settings](#ChangeCacheentrysettings) + * [Decorator stacking](#Decoratorstacking-1) + * [Cache Invalidation](#CacheInvalidation) + * [Return Values](#ReturnValues-1) +* [S3](#S3) + * [Cache a single file](#Cacheasinglefile) + * [Change Cache expiry](#ChangeCacheexpiry-1) + * [Check file before download](#Checkfilebeforedownload) + + + +## SSM - Parameter Store + +### Cache single parameter +To cache a parameter from ssm, decorate your handler function like so: ```python -from lambda_cache import ssm, secrets_manager, s3 +from lambda_cache import ssm -# Decorators injects cache entry into context object -@ssm.cache(parameter='/prod/app/var') -@secrets_manager.cache(name='/prod/db/conn_string') -@s3.cache(s3Uri='s3://bucket_name/path/to/object.json') +@ssm.cache(parameter='/production/app/var') +def handler(event, context): + var = getattr(context,'var') + response = do_something(var) + return response +``` +All invocations of this function over in the next minute will reference the parameter from the function's internal cache, rather than making a network call to ssm. After one minute has lapsed, the the next invocation will invoke `get_parameter` to refresh the cache. The parameter value will be injected into the `context` object of your lambda handler. + +### Change cache expiry + +The default `max_age_in_seconds` settings is 60 seconds (1 minute), it defines the maximum age of a parameter that is acceptable to the handler function. Cache entries older than this, will be refreshed. To set a longer cache duration (e.g 5 minutes), change the setting like so: + +```python +from lambda_cache import ssm + +@ssm.cache(parameter='/production/app/var', max_age_in_seconds=300) +def handler(event, context): + var = getattr(context,'var') + response = do_something(var) + return response +``` + +_Note: The caching logic runs only at invocation, regardless of how long the function runs. A 15 minute lambda function will not refresh the parameter, unless explicitly refreshed using `get_entry` method. The library is primary interested in caching 'across' invocation rather than 'within' an invocation_ + +### Change cache entry settings + +The default name of the parameter is the string after the last slash('/') character of its name. This means `/production/app/var` and `test/app/var` both resolve to just `var`. To over-ride this default, use `entry_name` setting like so: + +```python +from lambda_cache import ssm + +@ssm.cache(parameter='/production/app/var', entry_name='new_var') +def handler(event, context): + var = getattr(context,'new_var') + response = do_something(var) + return response +``` + +### Cache multiple parameters + +To cache multiple entries at once, pass a list of parameters to the parameter argument. This method groups all the parameter value under one python dictionary, stored in the Lambda Context under the `entry_name`. + +_Note: When using this method, `entry_name` is a required parameter, if not present `NoEntryNameError` exception is thrown._ + +```python +from lambda_cache import ssm + +@ssm.cache(parameter=['/app/var1', '/app/var2'], entry_name='parameters') def handler(event, context): + var1 = getattr(context,'parameters').get('var1') + var2 = getattr(context,'parameters').get('var2') + response = do_something(var) + return response +``` + +Under the hood, we use the `get_parameters` API call for boto3, which translate to a single network call for multiple parameters. You can group all parameters types in a single call, including `String`, `StringList` and `SecureString`. `StringList` will return as a list, while all other types will return as plain-text strings. The library does not support returning `SecureString` parameters in encrypted form, and will only return plain-text strings regardless of String type. + +_Note: for this method to work, ensure you have both `ssm:GetParameter` **and** `ssm:GetParameters` (with the 's' at the end) in your function's permission policy_ + +### Decorator stacking + +If you wish to cache multiple parameters with different expiry times, stack the decorators. In this example, `var1` will be refreshed every 30 seconds, `var2` will be refreshed after 60. + +```python +@ssm.cache(parameter='/production/app/var1', max_age_in_seconds=30) +@ssm.cache(parameter='/production/app/var2', max_age_in_seconds=60) +def handler(event, context): + var1 = getattr(context,'var1') + var2 = getattr(context,'var2') + response = do_something(var) + return response +``` +_Note: Decorator stacking performs one API call per decorator, which might result is slower performance_ + +### Cache invalidation - # Parameter from SSM - var = getattr(context, 'var') +If you require a fresh value at some point of the code, you can force a refresh using the `ssm.get_entry` function, and setting the `max_age_in_seconds` argument to 0. + +```python +from lambda_cache import ssm + +@ssm.cache(parameter='/prod/var') +def handler(event, context): + + if event.get('refresh'): + # refresh parameter + var = ssm.get_entry(parameter='/prod/var', max_age_in_seconds=0) + else: + var = getattr(context,'var') + + response = do_something(var) + return response +``` + +You may also use `ssm.get_entry` to get a parameter entry from anywhere in your functions code. + +To only get parameter once in the lifetime of the function, set `max_age_in_seconds` to some arbitary large number ~36000 (10 hours). + +### Return Values + +Caching supports `String`, `SecureString` and `StringList` parameters with no change required (ensure you have `kms:Decrypt` permission for `SecureString`). For simplicity, `StringList` parameters are automatically converted into list (delimited by comma), while `String` and `SecureString` both return the single string value of the parameter. + +## Secrets Manager + +### Cache single secret + +Secret support is similar, but uses the `secret.cache` decorator. + +```python +from lambda_cache import secret + +@secret.cache(name='/prod/db/conn_string') +def handler(event, context): + conn_string = getattr(context,'conn_string') + return context +``` - # Secret from Secrets Manager - secret = getattr(context, 'conn_string') - # Object from S3 +### Change Cache expiry + +The default `max_age_in_seconds` settings is 60 seconds (1 minute), it defines how long a parameter should be kept in cache before it is refreshed from ssm. To configure longer or shorter times, modify this argument like so: + +```python +from lambda_cache import secret + +@secret.cache(name='/prod/db/conn_string', max_age_in_seconds=300) +def handler(event, context): + var = getattr(context,'conn_string') + response = do_something(var) + return response +``` + +_Note: The caching logic runs only at invocation, regardless of how long the function runs. A 15 minute lambda function will not refresh the parameter, unless explicitly refreshed using get_cache_ssm. The library is primary interested in caching 'across' invocation rather than 'within' an invocation_ + +### Change Cache entry settings + +The name of the secret is simply shortened to the string after the last slash('/') character of the secret's name. This means `/prod/db/conn_string` and `/test/db/conn_string` resolve to just `conn_string`. To over-ride this default, use `entry_name`: + +```python +from lambda_cache import secret + +@secret.cache(name='/prod/db/conn_string', entry_name='new_var') +def handler(event, context): + var = getattr(context,'new_var') + response = do_something(var) + return response +``` + +### Decorator stacking + +If you wish to cache multiple secrets, you can use decorator stacking. + +```python +@secret.cache(name='/prod/db/conn_string', max_age_in_seconds=30) +@secret.cache(name='/prod/app/elk_username_password', max_age_in_seconds=60) +def handler(event, context): + var1 = getattr(context,'conn_string') + var2 = getattr(context,'elk_username_password') + response = do_something(var) + return response +``` + +_Note: Decorator stacking performs one API call per decorator, which might result is slower performance._ + +### Cache Invalidation + +To invalidate a secret, use the `get_entry`, setting the `max_age_in_seconds=0`. +```python +from lambda_cache import secret + +@secret.cache(name='/prod/db/conn_string') +def handler(event, context): + + if event.get('refresh'): + var = secret.get_entry(name='/prod/db/conn_string', max_age_in_seconds=0) + else: + var = getattr(context,'conn_string') + response = do_something(var) + return response +``` + +### Return Values + +Secrets Manager supports both string and binary secrets. For simplicity we will cache the secret in the format it is stored. It is up to the calling application to process the return as Binary or Strings. + +## S3 + +S3 support is considered _experimental_ for now, but withing the python community we see a lot of folks pull down files from S3 for use in AI/ML models. + +Files downloaded from s3 are automatically stored in the `/tmp` directory of the lambda function. This is the only writable directory within lambda, and has a 512MB of storage space. + +### Cache a single file +To download a file from S3 use the the same decorator pattern: + + +```python +from lambda_cache import s3 + +@s3.cache(s3Uri='s3://bucket_name/path/to/object.json') +def s3_download_entry_name(event, context): with open("/tmp/object.json") as file_data: status = json.loads(file_data.read())['status'] - response = do_something(var,secret,status) + return status +``` - return response +### Change Cache expiry + +The default `max_age_in_seconds` settings is 60 seconds (1 minute), it defines how long a file should be kept in `/tmp` before it is refreshed from S3. To configure longer or shorter times, modify this argument like so: + +```python +from lambda_cache import s3 + +@s3.cache(s3Uri='s3://bucket_name/path/to/object.json', max_age_in_seconds=300) +def s3_download_entry_name(event, context): + with open("/tmp/object.json") as file_data: + status = json.loads(file_data.read())['status'] + + return status ``` -The first invocation of the function will populate the cache, after which all invocations over the next 60 seconds, will reference the parameter from the function's internal cache, without making a network calls to ssm, secrets manager or S3. After 60 seconds, the the next invocation will refresh the cache from the respective back-ends. +_Note: The caching logic runs only at invocation, regardless of how long the function runs. A 15 minute lambda function will not refresh the object, unless explicitly refreshed using `s3.get_entry`. The library is primary interested in caching 'across' invocation rather than 'within' an invocation_ + +### Check file before download + +By default, _lambda_cache_ will download the file once at cache has expired, however, to save on network bandwidth (and possibly time), we can set the `check_before_download` parameter to True. This will check the age of the object in S3 and download **only** if the object has changed since the last download. + +```python +from lambda_cache import s3 + +@s3.cache(s3Uri='s3://bucket_name/path/to/object.json', max_age_in_seconds=300, check_before_download=True) +def s3_download_entry_name(event, context): + with open("/tmp/object.json") as file_data: + status = json.loads(file_data.read())['status'] + + return status +``` -Refer to [docs](https://lambda-cache.readthedocs.io/en/latest/user_guide/) for how to change cache cache timings, cache entry names,and how to invalidate caches. +_Note: we use the GetHead object call to verify the objects `last_modified_date`. This simplifies the IAM policy of the function, as it still only requires the `s3:GetObject` permission. However, this is still a GET requests, and will be charged as such, for smaller objects it might be cheaper to just download the object_ # Credit diff --git a/docs/images/installed_package.png b/docs/images/installed_package.png new file mode 100644 index 0000000000000000000000000000000000000000..2cae9de6062e3934b852496b0c5510c1e85dd64b GIT binary patch literal 64403 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

_CV40)Vnc;sy7=xbGD*c@l44aJ|L? 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 IZsh&H0H&GL?f?J) literal 0 HcmV?d00001 diff --git a/docs/install.md b/docs/install.md index 2cbba04..d26c6e8 100644 --- a/docs/install.md +++ b/docs/install.md @@ -4,11 +4,15 @@ Unlike other packages, _lambda_cache_ was designed to operate specifically withi There are two general options to using it. -## Using the publicly available layer from Klayers +## Manual Installation -[Klayers](https://github.com/keithrozario/Klayers) is a project that publishes AWS Lambda Layers for public consumption. A Lambda layer is way to pre-package code for easy deployments into any Lambda function. +Because _lambda-cache_ is a pure python package, you can manually include it in your lambda function, like so: -You can 'install' _lambda_cache_ by simply including the latest layer arn in your lambda function. + $ pip install lambda-cache -t /path/to/function + +Once installed you will see the following directory structure in your lambda function via the console: + +![Installed Package](images/installed_package.png) ## Using Serverless Framework @@ -18,10 +22,12 @@ simply ensure that _simple_lambda_cache_ is part of your `requirements.txt` file $ pip install lambda-cache +## Using the publicly available layer from Klayers + +[Klayers](https://github.com/keithrozario/Klayers) is a project that publishes AWS Lambda Layers for public consumption. A Lambda layer is way to pre-package code for easy deployments into any Lambda function. + +You can 'install' _lambda_cache_ by simply including the latest layer arn in your lambda function. -## Manual Installation -Because _lambda_cache_ is a pure python package, you can also manually include it in your lambda function, like so: - $ pip install lambda-cache -t /path/to/function