Python3でDatastore を動かしてみた

# 概要
GAE上でDjango・Pythonを動かしてWebサービスを作るに当たり、[GoogleDatastore](https://cloud.google.com/datastore/?hl=ja)を使いました。
この記事ではPython3でGoogleDatastoreを操作する方法をまとめます。

## GoogleDatastoreって
詳しくは上記のリンク参照。簡潔に言うと...

- NoSQL
- RESTfulインターフェース
- シームレスかつ自動にスケール

## 参考
https://googleapis.github.io/google-cloud-python/latest/datastore/index.html

# 導入
## install
ライブラリをpip3でインストールします。

```
pip3 install google-cloud-datastore
```

## IAM
ローカル環境でDatastoreAPIを叩くには、IAMの設定が必要です。

```
# サービスアカウントを作成
gcloud iam service-accounts create [NAME] 

# 権限付与
gcloud projects add-iam-policy-binding [PROJECT_ID] --member "serviceAccount:[NAME]@[PROJECT_ID].iam.gserviceaccount.com" --role "roles/owner"

# キーファイル作成
# 
gcloud iam service-accounts keys create [FILE_NAME].json --iam-account [NAME]@[PROJECT_ID].iam.gserviceaccount.com

# 環境変数の設定: [FILE_NAME].jsonのPATHを指定する
# .bashrcにも書いておきましょう。
export GOOGLE_APPLICATION_CREDENTIALS="[PATH]"

```

# 操作
## エンティティの作成
まずはエンティティを作成してみます。

```python
from google.cloud import datastore

# クライアントの設定
client = datastore.Client()

# エンティティ
key = client.key("Task2")
entity = datastore.Entity(key)
entity['description'] = "Buy milk"
entity['username'] = "Qiita"
client.put(entity)

```

そうすると下記のように、Task2という種類のエンティティが登録されます。
<img width="488" alt="スクリーンショット 2019-03-05 20.51.15.png" src="https://qiita-image-store.s3.amazonaws.com/0/199902/a755eaf3-c579-740d-7d86-aab4ae470a6e.png">


ちなみに下記の方法でも作成可能です

```py
entity = datastore.Entity(key)
entity.update({
    "description": "Buy milk",
    "username": "Qiita",
})
client.put(entity)
```

## ID指定でエンティティを取得
先ほど作成したエンティティを、ID指定で取得してみます。

```py
id = 5703374003503104
key = client.key("Task2", id)
entity = client.get(key)
print(entity)
# <Entity('Task2', 5703374003503104) {'username': 'Qiita', 'description': 'Buy milk'}>
```

要素の取得は、`entity.get('要素名')`を使うか、`entity['要素名']`で取得できます

```py
print(entity.get("username"))
# 'Qiita'
print(entity["username"])
# 'Qiita'

# getの場合、第2引数に要素名を取得できない場合に代入する値を指定可能
entity.get("comment", "No comment")
# 'No comment'
```


## クエリ
クエリのため、テストデータを増やします。

```py
key = client.key("Task2")
# A-san
entityA = datastore.Entity(key)
dataA = dict()
dataA['username']='A-san'
dataA['description']='Help B-san'
entityA.update(dataA)
# B-san
entityB = datastore.Entity(key)
dataB = dict()
dataB['username']='B-san'
dataB['description']='Buy beer'
entityB.update(dataB)
# リスト内のエンティティをDatastoreに保存
client.put_multi([entityA, entityB])
```

username=Qiitaの条件でクエリを実行してみます。

```py
# kindにエンティティ種類名を指定
query = client.query(kind="Task2")

# filter：　要素名、演算子、値の順に指定
query.add_filter("username", "=", "Qiita")

# fetchで取得。
list(query.fetch())
[<Entity('Task2', 5703374003503104) {'username': 'Qiita', 'description': 'Buy milk'}>]

```

## ソート
`query.order`で、ソートを指定可能

```py
query = client.query(kind="Task2")
query.order=["username"]
list(query.fetch())
# [
#     <Entity('Task2', 5150370657468416) {'username': 'A-san', 'description': 'Help B-san'}>, 
#     <Entity('Task2', 5713320610889728) {'username': 'B-san', 'description': 'Clean desk up'}>, 
#     <Entity('Task2', 5703374003503104) {'username': 'Qiita', 'description': 'Buy milk'}>
# ]
```

## distinct_on
ある要素で、重複を省いて一覧を取得したいときは、`query.distinct_on`を使うと良い

```py
# username一覧を取得する
query = client.query(kind="Task2")
query.distinct_on = ['username']
usernames = [
    e.get('username') for e in query.fetch()
]
```


## index
Datatoreでは、~~面倒なことに、~~複数要素でフィルターかソートするためにはindexを作る必要がある。
例えば、前準備なしに書きを実行すると。。。

```py
query = client.query(kind="Task2")
query.add_filter("username", "=", "Qiita")
query.order=["description"]
list(query.fetch())
```

こんなエラーが返ってきます。

```
google.api_core.exceptions.FailedPrecondition: 400 no matching index found. recommended index is:
- kind: Task2
  properties:
  - name: username
  - name: description
```

言われたとおり、index.yamlを作成してみます。

```yaml:index.yaml
indexes:
- kind: Task2
  properties:
  - name: username
  - name: description
```

下記コマンドを叩いて、index.yamlを登録します。

```sh
gcloud datastore indexes create index.yaml 
```

index.yamlで登録したインデックスは即座に反映されないようです。少し時間をおいてから、同じクエリを試してみるとエラーが出ないはずです。

# ちなみに
写真から次の旅行先を探すことをコンセプトにしたWebサービス [【Where to Visit?】](https://gae2.fk-management.com)を作りました。
本サービスはGoogleAppEngine上のDjangoで動いるのですが、データ保存先としてGoogleDatastoreを利用しています。
こっちの記事も読んでみてください。↓
[GAE×DjangoでWebサービスを開発してみた](https://qiita.com/tfuruya/items/f935b37e4007ea890574
)