diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index f18bd54..0000000 --- a/.travis.yml +++ /dev/null @@ -1,20 +0,0 @@ -# Config file for automatic testing at travis-ci.org -# This file will be regenerated if you run travis_pypi_setup.py - -language: python - -env: - - TOXENV=py35 - - TOXENV=py34 - - TOXENV=py33 - - TOXENV=py27 - - TOXENV=py26 - - TOXENV=pypy - -# command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors -install: pip install -U tox - -# command to run tests, e.g. python setup.py test -script: tox - - diff --git a/HISTORY.rst b/HISTORY.rst deleted file mode 100644 index e69de29..0000000 diff --git a/LICENSE b/LICENSE deleted file mode 100644 index cdd3677..0000000 --- a/LICENSE +++ /dev/null @@ -1,15 +0,0 @@ -ISC License - -Copyright (c) 2016, Nick Ficano - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index f65748a..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,11 +0,0 @@ -include HISTORY.rst -include LICENSE -include README.rst -include python/boto/endpoints.json -include aws_lambda/project_template/config.yaml -recursive-include tests *.json *.py *.txt *.yaml -recursive-include templates * -recursive-exclude * __pycache__ -recursive-exclude * *.py[co] - -recursive-include docs *.rst conf.py Makefile make.bat diff --git a/Makefile b/Makefile deleted file mode 100644 index ed7320a..0000000 --- a/Makefile +++ /dev/null @@ -1,33 +0,0 @@ -help: - @echo "clean - remove all build, test, coverage and Python artifacts" - @echo "lint - check style with flake8" - @echo "release - package and upload a release" - @echo "install - install the package to the active Python's site-packages" - -clean: clean-build clean-pyc clean-merge - -clean-build: - rm -fr build/ - rm -fr dist/ - rm -fr .eggs/ - find . -name '*.egg-info' -exec rm -fr {} + - find . -name '*.egg' -exec rm -f {} + - -clean-pyc: - find . -name '*.pyc' -exec rm -f {} + - find . -name '*.pyo' -exec rm -f {} + - find . -name '*~' -exec rm -f {} + - find . -name '__pycache__' -exec rm -fr {} + - -clean-merge: - find . -name '*.orig' -exec rm -f {} + - -lint: - flake8 python-lambda tests - -release: clean - python setup.py sdist upload - python setup.py bdist_wheel upload - -install: clean - python setup.py install diff --git a/README.md b/README.md new file mode 100644 index 0000000..ebf18a8 --- /dev/null +++ b/README.md @@ -0,0 +1,108 @@ +python-λ +======== + +Python-lambda is a toolset for developing and deploying *serverless* Python code in AWS Lambda. + +Description +=========== + +AWS Lambda is a service that allows you to write Python, Java, or Node.js code that gets executed in response to events like http requests or files uploaded to S3. + +Working with Lambda is relatively easy, but the process of bundling and deploying your code is not as simple as it could be. + +The *Python-Lambda* library takes away the guess work of developing your Python-Lambda services by providing you a toolset to streamline the annoying parts. + +Requirements +============ + +* Python 3.6 +* Pip (~8.1.1) +* Virtualenv (~15.0.0) +* Virtualenvwrapper (~4.7.1) + +Getting Started +=============== + +Begin by creating a new virtualenv and project folder. + +```bash + $ virtualenv -p python3 pylambda + $ source pylambda/bin/activate +``` + +Next, download *Python-Lambda* using pip via Github. + +```bash + (pylambda) $ pip install git+https://github.com/jgillard/python-lambda +``` +From your ``pylambda`` directory, run the following to bootstrap your project. + +```bash + (pylambda) $ lambda init +``` + +This will create the following files: ``event.json``, ``__init__.py``, ``service.py``, and ``config.yaml``. + +Next let's open ``service.py``, in here you'll find the following function: + +```python + def handler(event, context): + # Your code goes here! + e = event.get('e') + pi = event.get('pi') + return e + pi +``` + +This is the handler function; this is the function AWS Lambda will invoke in response to an event. You will notice that in the sample code ``e`` and ``pi`` are values in a ``dict``. AWS Lambda uses the ``event`` parameter to pass in event data to the handler. + +So if, for example, your function is responding to an http request, ``event`` will be the ``POST`` JSON data and if your function returns something, the contents will be in your http response payload. + +Next let's open the ``event.json`` file: + +```json + { + "pi": 3.14, + "e": 2.718 + } +``` + +Here you'll find the values of ``e`` and ``pi`` that are being referenced in the sample code. + +If you now try and run: + +```bash + (pylambda) $ lambda invoke -v +``` + +You will get: + +```bash + # 5.858 + + # execution time: 0.00000310s + # function execution timeout: 15s +``` + +As you probably put together, the ``lambda invoke`` command grabs the values stored in the ``event.json`` file and passes them to your function. + +The ``event.json`` file should help you develop your Lambda service locally. You can specify an alternate ``event.json`` file by passing the ``--event-file=.json`` argument to ``lambda invoke``. + +Testing +=============== + +```bash + (pylambda) $ python -m pytest tests/ +``` + +TODO: `lambda test` to wrap pytest + +Deploying +=============== + +Copy the template Terraform from the Usage section here: + +https://github.com/depop/depop-infrastructure/tree/master/modules/tf_community_modules/tf_aws_lambda#usage + +There is also a scheduled Lambda function available: + +https://github.com/depop/depop-infrastructure/tree/master/modules/tf_community_modules/tf_aws_lambda_scheduled#usage diff --git a/README.rst b/README.rst deleted file mode 100644 index 35147df..0000000 --- a/README.rst +++ /dev/null @@ -1,146 +0,0 @@ -======== -python-λ -======== - -.. image:: https://img.shields.io/pypi/v/python-lambda.svg - :alt: Pypi - :target: https://pypi.python.org/pypi/python-lambda/ - -.. image:: https://img.shields.io/pypi/pyversions/python-lambda.svg - :alt: Python Versions - :target: https://pypi.python.org/pypi/python-lambda/ - -Python-lambda is a toolset for developing and deploying *serverless* Python code in AWS Lambda. - -Description -=========== - -AWS Lambda is a service that allows you to write Python, Java, or Node.js code that gets executed in response to events like http requests or files uploaded to S3. - -Working with Lambda is relatively easy, but the process of bundling and deploying your code is not as simple as it could be. - -The *Python-Lambda* library takes away the guess work of developing your Python-Lambda services by providing you a toolset to streamline the annoying parts. - -Requirements -============ - -* Python 2.7 (At the time of writing this, AWS Lambda only supports Python 2.7). -* Pip (~8.1.1) -* Virtualenv (~15.0.0) -* Virtualenvwrapper (~4.7.1) - -Getting Started -=============== - -Begin by creating a new virtualenv and project folder. - -.. code:: bash - - $ mkvirtualenv pylambda - (pylambda) $ mkdir pylambda - -Next, download *Python-Lambda* using pip via pypi. - -.. code:: bash - - (pylambda) $ pip install python-lambda - -From your ``pylambda`` directory, run the following to bootstrap your project. - -.. code:: bash - - (pylambda) $ lambda init - -This will create the following files: ``event.json``, ``__init__.py``, ``service.py``, and ``config.yaml``. - -Let's begin by opening ``config.yaml`` in the text editor of your choice. For the purpose of this tutorial, the only required information is ``aws_access_key_id`` and ``aws_secret_access_key``. You can find these by logging into the AWS management console. - -Next let's open ``service.py``, in here you'll find the following function: - -.. code:: python - - def handler(event, context): - # Your code goes here! - e = event.get('e') - pi = event.get('pi') - return e + pi - - -This is the handler function; this is the function AWS Lambda will invoke in response to an event. You will notice that in the sample code ``e`` and ``pi`` are values in a ``dict``. AWS Lambda uses the ``event`` parameter to pass in event data to the handler. - -So if, for example, your function is responding to an http request, ``event`` will be the ``POST`` JSON data and if your function returns something, the contents will be in your http response payload. - -Next let's open the ``event.json`` file: - -.. code:: json - - { - "pi": 3.14, - "e": 2.718 - } - -Here you'll find the values of ``e`` and ``pi`` that are being referenced in the sample code. - -If you now try and run: - -.. code:: bash - - (pylambda) $ lambda invoke -v - -You will get: - -.. code:: bash - - # 5.858 - - # execution time: 0.00000310s - # function execution timeout: 15s - -As you probably put together, the ``lambda invoke`` command grabs the values stored in the ``event.json`` file and passes them to your function. - -The ``event.json`` file should help you develop your Lambda service locally. You can specify an alternate ``event.json`` file by passing the ``--event-file=.json`` argument to ``lambda invoke``. - -When you're ready to deploy your code to Lambda simply run: - -.. code:: bash - - (pylambda) $ lambda deploy - -The deploy script will evaluate your virtualenv and identify your project dependencies. It will package these up along with your handler function to a zip file that it then uploads to AWS Lambda. - -You can now log into the `AWS Lambda management console `_ to verify the code deployed successfully. - -Wiring to an API endpoint -========================= - -If you're looking to develop a simple microservice you can easily wire your function up to an http endpoint. - -Begin by navigating to your `AWS Lambda management console `_ and clicking on your function. Click the API Endpoints tab and click "Add API endpoint". - -Under API endpoint type select "API Gateway". - -Next change Method to ``POST`` and Security to "Open" and click submit (NOTE: you should secure this for use in production, open security is used for demo purposes). - -At last you need to change the return value of the function to comply with the standard defined for the API Gateway endpoint, the function should now look like this: - -.. code:: python - - def handler(event, context): - # Your code goes here! - e = event.get('e') - pi = event.get('pi') - return { - "statusCode": 200, - "headers": { "Content-Type": "application/json"}, - "body": e + pi - } - -Now try and run: - -.. code:: bash - - $ curl --header "Content-Type:application/json" \ - --request POST \ - --data '{"pi": 3.14, "e": 2.718}' \ - https:// - # 5.8580000000000005 diff --git a/aws_lambda/__init__.py b/aws_lambda/__init__.py index c9c38ba..70f8fef 100755 --- a/aws_lambda/__init__.py +++ b/aws_lambda/__init__.py @@ -1,10 +1,6 @@ # -*- coding: utf-8 -*- -# flake8: noqa -__author__ = 'Nick Ficano' -__email__ = 'nficano@gmail.com' -__version__ = '0.7.0' -from .aws_lambda import deploy, invoke, init, build, cleanup_old_versions +from .aws_lambda import invoke, init # Set default logging handler to avoid "No handler found" warnings. import logging diff --git a/aws_lambda/aws_lambda.py b/aws_lambda/aws_lambda.py index 8e58699..31a403b 100755 --- a/aws_lambda/aws_lambda.py +++ b/aws_lambda/aws_lambda.py @@ -1,93 +1,18 @@ # -*- coding: utf-8 -*- -from __future__ import print_function import json import logging import os import time from imp import load_source -from shutil import copy, copyfile -from tempfile import mkdtemp -import botocore -import boto3 -import pip import yaml -from .helpers import mkdir, read, archive, timestamp +from .helpers import read, custom_copytree log = logging.getLogger(__name__) -def cleanup_old_versions(src, keep_last_versions): - """Deletes old deployed versions of the function in AWS Lambda. - - Won't delete $Latest and any aliased version - - :param str src: - The path to your Lambda ready project (folder must contain a valid - config.yaml and handler module (e.g.: service.py). - :param int keep_last_versions: - The number of recent versions to keep and not delete - """ - if keep_last_versions <= 0: - print("Won't delete all versions. Please do this manually") - else: - path_to_config_file = os.path.join(src, 'config.yaml') - cfg = read(path_to_config_file, loader=yaml.load) - - aws_access_key_id = cfg.get('aws_access_key_id') - aws_secret_access_key = cfg.get('aws_secret_access_key') - - client = get_client('lambda', aws_access_key_id, aws_secret_access_key, - cfg.get('region')) - - response = client.list_versions_by_function( - FunctionName=cfg.get("function_name") - ) - versions = response.get("Versions") - if len(response.get("Versions")) < keep_last_versions: - print("Nothing to delete. (Too few versions published)") - else: - version_numbers = [elem.get("Version") for elem in - versions[1:-keep_last_versions]] - for version_number in version_numbers: - try: - client.delete_function( - FunctionName=cfg.get("function_name"), - Qualifier=version_number - ) - except botocore.exceptions.ClientError as e: - print("Skipping Version {}: {}" - .format(version_number, e.message)) - - -def deploy(src, requirements=False, local_package=None): - """Deploys a new function to AWS Lambda. - - :param str src: - The path to your Lambda ready project (folder must contain a valid - config.yaml and handler module (e.g.: service.py). - :param str local_package: - The path to a local package with should be included in the deploy as - well (and/or is not available on PyPi) - """ - # Load and parse the config file. - path_to_config_file = os.path.join(src, 'config.yaml') - cfg = read(path_to_config_file, loader=yaml.load) - - # Copy all the pip dependencies required to run your code into a temporary - # folder then add the handler file in the root of this directory. - # Zip the contents of this folder into a single file and output to the dist - # directory. - path_to_zip_file = build(src, requirements, local_package) - - if function_exists(cfg, cfg.get('function_name')): - update_function(cfg, path_to_zip_file) - else: - create_function(cfg, path_to_zip_file) - - def invoke(src, alt_event=None, verbose=False): """Simulates a call to your function. @@ -137,71 +62,9 @@ def init(src, minimal=False): Minimal possible template files (excludes event.json). """ - templates_path = os.path.join( + destination = os.path.join( os.path.dirname(os.path.abspath(__file__)), "project_templates") - for filename in os.listdir(templates_path): - if (minimal and filename == 'event.json') or filename.endswith('.pyc'): - continue - destination = os.path.join(templates_path, filename) - copy(destination, src) - - -def build(src, requirements=False, local_package=None): - """Builds the file bundle. - - :param str src: - The path to your Lambda ready project (folder must contain a valid - config.yaml and handler module (e.g.: service.py). - :param str local_package: - The path to a local package with should be included in the deploy as - well (and/or is not available on PyPi) - """ - # Load and parse the config file. - path_to_config_file = os.path.join(src, 'config.yaml') - cfg = read(path_to_config_file, loader=yaml.load) - - # Get the absolute path to the output directory and create it if it doesn't - # already exist. - dist_directory = cfg.get('dist_directory', 'dist') - path_to_dist = os.path.join(src, dist_directory) - mkdir(path_to_dist) - - # Combine the name of the Lambda function with the current timestamp to use - # for the output filename. - function_name = cfg.get('function_name') - output_filename = "{0}-{1}.zip".format(timestamp(), function_name) - - path_to_temp = mkdtemp(prefix='aws-lambda') - pip_install_to_target(path_to_temp, - requirements=requirements, - local_package=local_package) - - # Gracefully handle whether ".zip" was included in the filename or not. - output_filename = ('{0}.zip'.format(output_filename) - if not output_filename.endswith('.zip') - else output_filename) - - files = [] - for filename in os.listdir(src): - if os.path.isfile(filename): - if filename == '.DS_Store': - continue - if filename == 'config.yaml': - continue - files.append(os.path.join(src, filename)) - - # "cd" into `temp_path` directory. - os.chdir(path_to_temp) - for f in files: - _, filename = os.path.split(f) - - # Copy handler file into root of the packages folder. - copyfile(f, os.path.join(path_to_temp, filename)) - - # Zip them together into a single file. - # TODO: Delete temp directory created once the archive has been compiled. - path_to_zip_file = archive('./', path_to_dist, output_filename) - return path_to_zip_file + custom_copytree(destination, src, minimal) def get_callable_handler_function(src, handler): @@ -233,168 +96,3 @@ def get_handler_filename(handler): """ module_name, _ = handler.split('.') return '{0}.py'.format(module_name) - - -def _install_packages(path, packages): - """Install all packages listed to the target directory. - - Ignores any package that includes Python itself and python-lambda as well - since its only needed for deploying and not running the code - - :param str path: - Path to copy installed pip packages to. - :param list packages: - A list of packages to be installed via pip. - """ - def _filter_blacklist(package): - blacklist = ["-i", "#", "Python==", "python-lambda=="] - return all(package.startswith(entry) is False for entry in blacklist) - filtered_packages = filter(_filter_blacklist, packages) - for package in filtered_packages: - if package.startswith('-e '): - package = package.replace('-e ', '') - - print('Installing {package}'.format(package=package)) - pip.main(['install', package, '-t', path, '--ignore-installed']) - - -def pip_install_to_target(path, requirements=False, local_package=None): - """For a given active virtualenv, gather all installed pip packages then - copy (re-install) them to the path provided. - - :param str path: - Path to copy installed pip packages to. - :param bool requirements: - If set, only the packages in the requirements.txt file are installed. - The requirements.txt file needs to be in the same directory as the - project which shall be deployed. - Defaults to false and installs all pacakges found via pip freeze if - not set. - :param str local_package: - The path to a local package with should be included in the deploy as - well (and/or is not available on PyPi) - """ - packages = [] - if not requirements: - print('Gathering pip packages') - packages.extend(pip.operations.freeze.freeze()) - else: - if os.path.exists("requirements.txt"): - print('Gathering requirement packages') - data = read("requirements.txt") - packages.extend(data.splitlines()) - - if not packages: - print('No dependency packages installed!') - - if local_package is not None: - packages.append(local_package) - _install_packages(path, packages) - - -def get_role_name(account_id, role): - """Shortcut to insert the `account_id` and `role` into the iam string.""" - return "arn:aws:iam::{0}:role/{1}".format(account_id, role) - - -def get_account_id(aws_access_key_id, aws_secret_access_key): - """Query STS for a users' account_id""" - client = get_client('sts', aws_access_key_id, aws_secret_access_key) - return client.get_caller_identity().get('Account') - - -def get_client(client, aws_access_key_id, aws_secret_access_key, region=None): - """Shortcut for getting an initialized instance of the boto3 client.""" - - return boto3.client( - client, - aws_access_key_id=aws_access_key_id, - aws_secret_access_key=aws_secret_access_key, - region_name=region - ) - - -def create_function(cfg, path_to_zip_file): - """Register and upload a function to AWS Lambda.""" - - print("Creating your new Lambda function") - byte_stream = read(path_to_zip_file) - aws_access_key_id = cfg.get('aws_access_key_id') - aws_secret_access_key = cfg.get('aws_secret_access_key') - - account_id = get_account_id(aws_access_key_id, aws_secret_access_key) - role = get_role_name(account_id, cfg.get('role', 'lambda_basic_execution')) - - client = get_client('lambda', aws_access_key_id, aws_secret_access_key, - cfg.get('region')) - - func_name = ( - os.environ.get('LAMBDA_FUNCTION_NAME') or cfg.get('function_name') - ) - print('Creating lambda function with name: {}'.format(func_name)) - client.create_function( - FunctionName=func_name, - Runtime=cfg.get('runtime', 'python2.7'), - Role=role, - Handler=cfg.get('handler'), - Code={'ZipFile': byte_stream}, - Description=cfg.get('description'), - Timeout=cfg.get('timeout', 15), - MemorySize=cfg.get('memory_size', 512), - Environment={ - 'Variables': { - key.strip('LAMBDA_'): value - for key, value in os.environ.items() - if key.startswith('LAMBDA_') - } - }, - Publish=True - ) - - -def update_function(cfg, path_to_zip_file): - """Updates the code of an existing Lambda function""" - - print("Updating your Lambda function") - byte_stream = read(path_to_zip_file) - aws_access_key_id = cfg.get('aws_access_key_id') - aws_secret_access_key = cfg.get('aws_secret_access_key') - - account_id = get_account_id(aws_access_key_id, aws_secret_access_key) - role = get_role_name(account_id, cfg.get('role', 'lambda_basic_execution')) - - client = get_client('lambda', aws_access_key_id, aws_secret_access_key, - cfg.get('region')) - - client.update_function_code( - FunctionName=cfg.get('function_name'), - ZipFile=byte_stream, - Publish=True - ) - - client.update_function_configuration( - FunctionName=cfg.get('function_name'), - Role=role, - Handler=cfg.get('handler'), - Description=cfg.get('description'), - Timeout=cfg.get('timeout', 15), - MemorySize=cfg.get('memory_size', 512), - VpcConfig={ - 'SubnetIds': cfg.get('subnet_ids', []), - 'SecurityGroupIds': cfg.get('security_group_ids', []) - } - ) - - -def function_exists(cfg, function_name): - """Check whether a function exists or not""" - - aws_access_key_id = cfg.get('aws_access_key_id') - aws_secret_access_key = cfg.get('aws_secret_access_key') - client = get_client('lambda', aws_access_key_id, aws_secret_access_key, - cfg.get('region')) - functions = client.list_functions().get('Functions', []) - for fn in functions: - if fn.get('FunctionName') == function_name: - return True - return False diff --git a/aws_lambda/helpers.py b/aws_lambda/helpers.py index 7809904..68280d5 100644 --- a/aws_lambda/helpers.py +++ b/aws_lambda/helpers.py @@ -1,12 +1,6 @@ # -*- coding: utf-8 -*- import os -import zipfile -import datetime as dt - - -def mkdir(path): - if not os.path.exists(path): - os.makedirs(path) +import shutil def read(path, loader=None): @@ -16,17 +10,13 @@ def read(path, loader=None): return loader(fh.read()) -def archive(src, dest, filename): - output = os.path.join(dest, filename) - zfh = zipfile.ZipFile(output, 'w', zipfile.ZIP_DEFLATED) - - for root, _, files in os.walk(src): - for file in files: - zfh.write(os.path.join(root, file)) - zfh.close() - return os.path.join(dest, filename) - - -def timestamp(fmt='%Y-%m-%d-%H%M%S'): - now = dt.datetime.utcnow() - return now.strftime(fmt) +def custom_copytree(src, dst, minimal, symlinks=False, ignore=None): + for item in os.listdir(src): + if (minimal and item == 'event.json') or item.endswith('.pyc') or item == '__pycache__': + continue + s = os.path.join(src, item) + d = os.path.join(dst, item) + if os.path.isdir(s): + shutil.copytree(s, d, symlinks, ignore) + else: + shutil.copy(s, d) diff --git a/aws_lambda/project_templates/.gitignore b/aws_lambda/project_templates/.gitignore new file mode 100644 index 0000000..df4e006 --- /dev/null +++ b/aws_lambda/project_templates/.gitignore @@ -0,0 +1,7 @@ +__pycache__/ +bin/ +include/ +lib/ + +*.pyc +pip-selfcheck.json \ No newline at end of file diff --git a/aws_lambda/project_templates/config.yaml b/aws_lambda/project_templates/config.yaml index 7f39794..5f009ab 100644 --- a/aws_lambda/project_templates/config.yaml +++ b/aws_lambda/project_templates/config.yaml @@ -1,15 +1 @@ -region: us-east-1 - -function_name: my_lambda_function handler: service.handler -# role: lambda_basic_execution -description: My first lambda function - -# if access key and secret are left blank, boto will use the credentials -# defined in the [default] section of ~/.aws/credentials. -aws_access_key_id: -aws_secret_access_key: - -# dist_directory: dist -# timeout: 15 -# memory_size: 512 diff --git a/aws_lambda/project_templates/service.py b/aws_lambda/project_templates/service.py index e5bcb68..c2845ee 100644 --- a/aws_lambda/project_templates/service.py +++ b/aws_lambda/project_templates/service.py @@ -1,8 +1,12 @@ # -*- coding: utf-8 -*- +def add(x, y): + return x + y + + def handler(event, context): # Your code goes here! e = event.get('e') pi = event.get('pi') - return e + pi + return add(e, pi) diff --git a/aws_lambda/project_templates/tests/test_function.py b/aws_lambda/project_templates/tests/test_function.py new file mode 100644 index 0000000..acdde87 --- /dev/null +++ b/aws_lambda/project_templates/tests/test_function.py @@ -0,0 +1,21 @@ +import json +import pytest + +from service import * + + +def read(path, loader=None): + with open(path) as fh: + if not loader: + return fh.read() + return loader(fh.read()) + + +def test_add(): + assert(add(1, 1) == 2) + assert(add(1, -1) == 0) + + +def test_handler(): + event = read('event.json', loader=json.loads) + assert(handler(event, None) == pytest.approx(5.858)) diff --git a/requirements.txt b/requirements.txt index 683f195..9e49929 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,3 +8,4 @@ pyaml==15.8.2 python-dateutil==2.5.3 PyYAML==3.11 six==1.10.0 +pytest==3.0.7 diff --git a/scripts/lambda b/scripts/lambda index ab363b5..50ea116 100755 --- a/scripts/lambda +++ b/scripts/lambda @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- import os import click @@ -26,13 +26,6 @@ def init(folder): aws_lambda.init(path) -@click.command(help="Bundles package for deployment.") -@click.option('--use-requirements', default=False, is_flag=True, help='Install all packages defined in requirements.txt') -@click.option('--local-package', default=None, help='Install local package as well.', type=click.Path()) -def build(use_requirements, local_package): - aws_lambda.build(CURRENT_DIR, use_requirements, local_package) - - @click.command(help="Run a local test of your function.") @click.option('--event-file', default=None, help='Alternate event file.') @click.option('--verbose', '-v', is_flag=True) @@ -40,23 +33,7 @@ def invoke(event_file, verbose): aws_lambda.invoke(CURRENT_DIR, event_file, verbose) -@click.command(help="Register and deploy your code to lambda.") -@click.option('--use-requirements', default=False, is_flag=True, help='Install all packages defined in requirements.txt') -@click.option('--local-package', default=None, help='Install local package as well.', type=click.Path()) -def deploy(use_requirements, local_package): - aws_lambda.deploy(CURRENT_DIR, use_requirements, local_package) - - - -@click.command(help="Delete old versions of your functions") -@click.option("--keep-last", type=int, prompt="Please enter the number of recent versions to keep") -def cleanup(keep_last): - aws_lambda.cleanup_old_versions(CURRENT_DIR, keep_last) - if __name__ == '__main__': cli.add_command(init) cli.add_command(invoke) - cli.add_command(deploy) - cli.add_command(build) - cli.add_command(cleanup) cli() diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 0915b87..0000000 --- a/setup.cfg +++ /dev/null @@ -1,15 +0,0 @@ -[bumpversion] -current_version = 0.7.0 -commit = True -tag = True - -[bumpversion:file:setup.py] - -[bumpversion:file:aws_lambda/__init__.py] - -[wheel] -universal = 1 - -[flake8] -exclude = docs - diff --git a/setup.py b/setup.py index acd0624..05144b9 100755 --- a/setup.py +++ b/setup.py @@ -1,56 +1,24 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- import pip from setuptools import setup, find_packages -with open('README.rst') as readme_file: - readme = readme_file.read() - -with open('HISTORY.rst') as history_file: - history = history_file.read() - requirements = pip.req.parse_requirements( "requirements.txt", session=pip.download.PipSession() ) pip_requirements = [str(r.req) for r in requirements] -test_requirements = [ - # TODO: put package test requirements here -] setup( - name='python-lambda', - version='0.7.0', - description="The bare minimum for a Python app running on Amazon Lambda.", - long_description=readme + '\n\n' + history, - author="Nick Ficano", - author_email='nficano@gmail.com', - url='https://github.com/nficano/python-lambda', + name='depop-python-lambda', + version='0.1.0', packages=find_packages(), package_data={ - 'aws_lambda': ['project_templates/*'], + 'aws_lambda': ['project_templates/*', 'project_templates/tests/*'], '': ['*.json'], }, include_package_data=True, scripts=['scripts/lambda'], install_requires=pip_requirements, - license="ISCL", - zip_safe=False, - keywords='python-lambda', - classifiers=[ - 'Development Status :: 2 - Pre-Alpha', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: ISC License (ISCL)', - 'Natural Language :: English', - "Programming Language :: Python :: 2", - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - ], - test_suite='tests', - tests_require=test_requirements ) diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100755 index 40a96af..0000000 --- a/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 951b70c..0000000 --- a/tox.ini +++ /dev/null @@ -1,12 +0,0 @@ -[tox] -envlist = py26, py27, py33, py34, py35 - -[testenv] -setenv = - PYTHONPATH = {toxinidir}:{toxinidir}/python-lambda -commands = python setup.py test - -; If you want to make tox run the tests with the same versions, create a -; requirements.txt with the pinned versions and uncomment the following lines: -; deps = -; -r{toxinidir}/requirements.txt