Google Cloud Functionsでgoogle-api-python-clientを利用してfile_cache is unavailableエラーになったときの対応方法

## 概要

Google Cloud FunctionsでPython(Beta)を利用して、他のGCPサービスを利用する場合、google-api-python-clientを利用するのが便利です。

googleapis / google-api-python-client
https://github.com/googleapis/google-api-python-client

ただし現状、```ImportError``` が発生するケースがあるので、その対応方法です。(2018/10/31時点)

### 発生するエラー

```
ImportError: file_cache is unavailable when using oauth2client >= 4.0.0 or google-auth
```

### 回避策: ```cache_discovery``` を無効化する

```txt:requirements.txt
google-api-python-client
oauth2client
```

```python:main.py
from googleapiclient import discovery

from httplib2 import Http

from oauth2client.client import GoogleCredentials


def check_discovery_error(request):
  # Cloud Functions環境から認証情報を取得する
  credentials = GoogleCredentials.get_application_default()

  cloudBuild = discovery.build(
    'cloudbuild', 'v1',   # Cloud Buildじゃなく、他のサービスでもおｋ
    http=credentials.authorize(Http()),
    cache_discovery=False)# cache_discovery=Falseしてエラーを回避する

  request = cloudBuild.projects().builds().list(projectId='[GCPのプロジェクトID]')
  response = request.execute()
  print(response)

```


## 再現してみる

### 前提

- GCPプロジェクトが利用可能
- ```gcloud beta``` が端末にインストール済みで利用可

### 実装の用意

```sh
> mkdir 任意のディレクトリ
> cd 任意のディレクトリ
> touch requirements.txt
> touch main.py
```

```txt:requirements.txt
google-api-python-client
```

今回はCloud Buildからジョブリストを取得する実装にしていますが、他のサービスでもおｋです。

```python:main.py(回避前)
from googleapiclient import discovery


def check_discovery_error(request):
  cloudBuild = discovery.build('cloudbuild', 'v1')
  request = cloudBuild.projects().builds().list(projectId='GCPのプロジェクトID')
  response = request.execute()
  print(response)

```

これをCloud Functionsにデプロイして実行します。

```sh
> gcloud beta functions deploy check_discovery_error \
    --trigger-http \
    --runtime=python37

> gcloud beta functions call check_discovery_error
```

実行すると、以下のようにエラーが出力されます。

```sh:ログ(一部抜粋)
> gcloud functions logs read check_discovery_error

E          from oauth2client.contrib.locked_file import LockedFile
E      ModuleNotFoundError: No module named 'oauth2client'
E      
E      During handling of the above exception, another exception occurred:
E      
E      Traceback (most recent call last):
E        File "/env/local/lib/python3.7/site-packages/googleapiclient/discovery_cache/file_cache.py", line 37, in <module>
E          from oauth2client.locked_fileimport LockedFile
E      ModuleNotFoundError: No module named 'oauth2client'
E      
E      During handling of the above exception, another exception occurred:
E      
E      Traceback (most recent call last):
E        File "/env/local/lib/python3.7/site-packages/googleapiclient/discovery_cache/__init__.py", line 41, in autodetect
E          from . import file_cache
E        File "/env/local/lib/python3.7/site-packages/googleapiclient/discovery_cache/file_cache.py", line 41, in <module>
E          'file_cache is unavailable when using oauth2client >= 4.0.0 or google-auth')
E      ImportError: file_cache is unavailable when using oauth2client >= 4.0.0 or google-auth
I      URL being requested: GET https://www.googleapis.com/discovery/v1/apis/cloudbuild/v1/rest
I      URL being requested: GET https://cloudbuild.googleapis.com/v1/projects/[GCPのプロジェクトID]/builds?alt=json
I      {'builds': [{'id': (略)
D      Function execution took 138 ms, finished with status code: 200
```

## 原因

調べて見るとあちこちでissueが上がっていますが、いまのところ、根本的な解決はされてないようです。

file_cache is unavailable when using oauth2client >= 4.0.0
https://github.com/googleapis/google-api-python-client/issues/299

File caching for the oauth2client >= 4.0.0
https://github.com/googleapis/google-api-python-client/issues/325

## 回避策

issueのコメントに回避策がコメントされていました。ありがたや^^


### ```cache_discovery``` を無効化する

ライブラリのインポート時にキャッシュを利用すると、oauth2clientのバージョンチェックに引っかかる？みたいなので、無効化して回避すればよいみたいです。

https://github.com/googleapis/google-api-python-client/issues/299#issuecomment-268915510
> It seems to be the better way to silence this error, if one does not care about the cache, is to simply specify cache_discovery=False when performing discovery.build() -- this avoids the broken code path.

以下のコメントの実装方法を参考にしました。
https://github.com/googleapis/google-api-python-client/issues/299#issuecomment-427118293

```discovery.build``` は ```cache_discovery``` パラメータ指定する場合、```http``` パラメータも指定する必要があったので、```oauth2client``` を用いて```Credentials``` を取得するようにしました。

```txt:requirements.txt
google-api-python-client
oauth2client
```

```python:main.py(対応版)
from googleapiclient import discovery

from httplib2 import Http

from oauth2client.client import GoogleCredentials


def check_discovery_error(request):
  # Cloud Functions環境から認証情報を取得する
  credentials = GoogleCredentials.get_application_default()

  cloudBuild = discovery.build(
    'cloudbuild', 'v1',   # Cloud Buildじゃなく、他のサービスでもおｋ
    http=credentials.authorize(Http()),
    cache_discovery=False)# cache_discovery=Falseしてエラーを回避する

  request = cloudBuild.projects().builds().list(projectId='[GCPのプロジェクトID]')
  response = request.execute()
  print(response)

```
再びデプロイと実行して、エラー出力がなくなっていることが確認できました。
やったぜ。

```sh:一部抜粋
D      Function execution started
I      URL being requested: GET https://www.googleapis.com/discovery/v1/apis/cloudbuild/v1/rest
I      URL being requested: GET https://cloudbuild.googleapis.com/v1/projects/[GCPのプロジェクトID]/builds?alt=json
I      {'builds': [{'id': (略)
D      Function execution took 2352 ms, finished with status code: 200
```

## 参考

file_cache is unavailable when using oauth2client >= 4.0.0
https://github.com/googleapis/google-api-python-client/issues/299

File caching for the oauth2client >= 4.0.0
https://github.com/googleapis/google-api-python-client/issues/325

Google Driveにpythonでデータを登録する & Siderを使ってチェックしてみる
https://qiita.com/akiko-pusu/items/f05a5dcba544e97c057c

[GoogleCloudPlatform] API Client Libraryを用いてGoogle Cloud APIを利用する
https://qiita.com/j-un/items/dc46b3b766a7afb4080c