AWS Lambdaでpsycopg2を使う

# 前書き
Redshiftにquery投げるバッチをlambda使って書いてデプロイするとこんなエラーになった
ローカルでは動くのに。

```
ModuleNotFoundError: No module named 'psycopg2._psycopg'
```

一応こういう方法で回避もできるがそうするとlocalで動かすのがめんどくさくなる。
https://github.com/jkehler/awslambda-psycopg2

## 解決策
これでOK!
https://github.com/Miserlou/lambda-packages
READEMEにも書いてある通りzappaと組み合わせて使うととても楽
zappaってflask使うケースしか見つからないけどlambdaをcronで動かすだけという使い方もできます。

[完成品](https://github.com/tky/zappa-psql)はこちら

# pipenv
pythonの開発環境に関して深入りする気は無いですがここではpipenv使います
必要なライブラリのインストールしてPipenvシェルを有効化

```
$ pipenv install
$ pipenv install zappa
$ pipenv install psycopg2
$ pipenv install psycopg2-binary  // これがないとlambda上で動かなかった。
$ pipenv install --dev pytest
$ pipenv shell
```

# zappa
zappaの設定に関してはいろいろ記事があるので説明は割愛しますがこんな感じ

```
$ zappa init

Your Zappa configuration can support multiple production stages, like 'dev', 'staging', and 'production'.
What do you want to call this environment (default 'dev'):
```

enter

```
AWS Lambda and API Gateway are only available in certain regions. Let's check to make sure you have a profile set up in one that will work.
We found the following profiles: default, mine. Which would you like us to use? (default 'default'):
```

お好みでどうぞ。
僕は自分のアカウントでmineというprofileを作っているのでそちらを使っています。
awsのアカウントってどうしてもいくつか必要になってしまうので何か指定しておいたほうが無難だと思います。

```
Your Zappa deployments will need to be uploaded to a private S3 bucket.
If you don't have a bucket yet, we'll create one for you too.
What do you want to call your bucket? (default 'zappa-xxxxx'):
```

zappaが使うs3 bucket。特別な理由がなければそのままenter

```
What's the modular path to your app's function?
This will likely be something like 'your_module.app'.
Where is your app's function?: 
```

今回はlambdaだけなので使わないです。
あとで消すので適当にtest.appとか入れておきます。

```
You can optionally deploy to all available regions in order to provide fast global service.
If you are using Zappa for the first time, you probably don't want to do this!
Would you like to deploy this application globally? (default 'n') [y/n/(p)rimary]:
```

いらないよ！って言ってくれているのでそのままenter

```
Okay, here's your zappa_settings.json:

{
    "dev": {
        "app_function": "test.app",
        "aws_region": "ap-northeast-1",
        "profile_name": "mine",  // 自分のawsのprofile
        "project_name": "zappa-psql",
        "runtime": "python3.7",
        "s3_bucket": "zappa-xxxx" // uniqなS3バケット名
    }
}

Does this look okay? (default 'y') [y/n]:
```

enterでzappa_settings.jsonができるので編集

```
{
    "dev": {
        "app_function": "test.app",  この行を削除する
        "aws_region": "ap-northeast-1",
        "profile_name": "mine",
        "project_name": "zappa-psql",
        "apigateway_enabled": false, これを追加
        "runtime": "python3.7",
        "s3_bucket": "zappa-xxxx"
    }
}
```

# Redshiftに接続
DBに接続してprintするだけ

```scripts/main.py
import os
import psycopg2


def lambda_handler(event, context):
    params = dict(
        user=os.getenv('DB_USER'),
        password=os.getenv('DB_PASSWORD'),
        host=os.getenv('DB_HOST'),
        port=os.getenv('DB_PORT'),
        database=os.getenv('DB_NAME')
    )

    with psycopg2.connect(**params) as connection:
        with connection.cursor() as cur:
            cur.execute("select * from users")
            for user in cur.fetchall():
                print(user)

```

# 動作確認
一応動作確認

```tests/test_main.py
from unittest import TestCase

from scripts.main import lambda_handler

class SampleTestCase(TestCase):

    def test_lambda_handler(self):
        lambda_handler(None, None)
```

環境変数を準備。
localで用意したpsqlに接続。
Redshiftとかに直接繋いでもいいけど。

```tests/.env
DB_USER=postgres
DB_PASSWORD=
DB_HOST=localhost
DB_PORT=5432
DB_NAME=sample
```

今更ですがsampleというDBがあってusersっていうテーブルがある前提です

```
$ psql -Upostgres sample

sample=# \d users;
                       Table "public.users"
 Column |          Type          | Collation | Nullable | Default
--------+------------------------+-----------+----------+---------
 id     | integer                |           | not null |
 name   | character varying(100) |           | not null |
```

## テスト実行

```
PIPENV_DOTENV_LOCATION=tests/.env pipenv run pytest -v --capture=no

tests/test_main.py::SampleTestCase::test_lambda_handler (1, 'andy')
(2, 'bob')
PASSED
```
selectの結果が表示された

# デプロイ

## zappa_settingの追加

環境変数(environment_variables)の追加と、triger(events)の設定。
expressionはcronのフォーマットでもかけるけどさっさと動作確認したいので毎分実行

```zappa_settings.json
{
    "dev": {
        "aws_region": "ap-northeast-1",
        "profile_name": "mine",
        "project_name": "zappa-psql",
        "apigateway_enabled": false,
        "runtime": "python3.7",
        "s3_bucket": "zappa-xxxx",
        "events": [{
            "function": "scripts.main.lambda_handler",
            "expression": "rate(1 minute)"
        }],
        "environment_variables": {
          "DB_USER": "your_value",
          "DB_PASSWORD": "your_value",
          "DB_PORT": "your_value",
          "DB_NAME": "your_value"
        }
    }
}
```

## デプロイ実行

```
zappa deploy dev
```

[aws console](https://ap-northeast-1.console.aws.amazon.com/lambda/home?region=ap-northeast-1#/functions)で作ったlambdaを確認

cloudwatchにselectの結果が表示されるのを待つ

## 後始末

```
zappa undeploy dev
Are you sure you want to undeploy? [y/n] y
```