watsonx.data SaaS版のPresto エンジンにpythonからアクセス

\* IBM Cloud上のSaaS版についての記載です

[watsonx.data](https://www.ibm.com/jp-ja/products/watsonx-data)は「単一の統合データ・プラットフォームですべての企業データを収集、保存、照会、分析するためのデータ管理ソリューションです。オープン・データ・フォーマットに最適化された、柔軟で信頼性の高いプラットフォームを提供します」とのことで、いわゆるデータレイクハウスです。SW版とSaaS版があります。

当記事はwatsonx.data SaaS版のPrestoエンジンにpythonからアクセスするための方法について説明します。
尚、公式ドキュメントでは[Connecting to Presto server](https://cloud.ibm.com/docs/watsonxdata?topic=watsonxdata-con-presto-serv)に記述されている内容です。

今回試した実行環境は以下です:

- Python 3.11.4
- Apple M1 Pro


> 2024/08/08 最新の情報で改訂

# 1. 事前準備
[watsonx.data SaaS版のPrestoエンジンにアクセスするための事前情報取得](https://qiita.com/nishikyon/items/2687572b6ecce1e54bb5)を参考に
watsonx.data SaaS版のPrestoエンジンにアクセスするための事前情報
- ホスト名
- ポート番号
- IBM API key
- IBM IAM token　(オプション)


を取得しておいてください。

尚、[ドキュメント](https://cloud.ibm.com/docs/watsonxdata?topic=watsonxdata-con-presto-serv#get-api-iam-token)によるとIBM IAM tokenの方がワークロード的におすすめとのことです。

ただIBM API key は一度取得したら削除するまで使えますが、IBM IAM tokenは作成後1時間有効となり、有効期限があります。どちらも使用可能なので、状況に合わせて選択してください。


# 2. 必要な情報の変数へのセット
一旦必要な情報を変数にセットします：
- 接続するusernameはIBM apikey を使う場合は`ibmlhapikey`, IBM IAM tokenを使う場合は `ibmlhtoken_<your-username>`とします。　
    - IBM IAM tokenを使う場合の`ibmlhtoken_<your-username>`の`<your-username>`部分にはアクセスに使用するIBM CloudのIDを入れてください
    - IBM CloudのIDが`aaa@bbb.com` であれば　`ibmlhtoken_aaa@bbb.com`
    - （2024/08/08現在ibmlhtokenではアクセスできなかったので修正）
    - 
- 接続するpasswordは`IBM API keyの値`または`IBM IAM tokenの値`、どちらか使う方をセットします。
- hostnameはホスト名を文字列でセットします
- portnumberはポート番号を**数値**でセットします。


```python
username="<ibmlhapikey または ibmlhtoken_<your-username> >" # IBM apikey を使う場合は ibmlhapikey, IBM IAM tokenをを使う場合は ibmlhtoken_<your-username>
password="<IBM apikey または IBM IAM tokenの値>" # IBM apikey または IBM IAM token
hostname="<ホスト名>" #　ホスト名
portnumber=99999 # ポート番号(数値)
```

参考までにIBM IAM tokenをを使う場合は、上記のコードのusernameは`ibmlhtoken_<your-username>`、passwordにIBM apikeyの値を入れ、次に以下を実行すればのpasswordがIBM IAM tokenで上書きされます。
- 上記`<your-username>`は必ず自分のものに書き換えてください: 
    - IBM CloudのIDが`aaa@bbb.com` であれば　`ibmlhtoken_aaa@bbb.com`
```python
import requests
import sys

endpoint = 'https://iam.cloud.ibm.com/identity/token'
headers = {}
headers ['content-type'] = "application/x-www-form-urlencoded"
params = {}
params['grant_type'] = "urn:ibm:params:oauth:grant-type:apikey"
params['apikey'] = password

try:
    r = requests.post(endpoint,  headers=headers, data=params)
except Exception as err:
    print("RESTful call failed. Detailed information follows.")
    print(err)
    sys.exit()

try:
    access_token = r.json()['access_token']
    #print(r.json())
except:
    print("RESTful call did not return an access token.")
    print(r.json())
    sys.exit()


password = access_token
```


# 2. presto-python-clientライブラリを使ったアクセス (dbapi使用)
参考:
- [Connecting to Presto engine using Python scripts](https://cloud.ibm.com/docs/watsonxdata?topic=watsonxdata-con-presto-serv#conn-to-prestpython)
- presto-python-client: https://github.com/prestodb/presto-python-client

こちらは公式ドキュメントに記載された方法です。

presto-python-client未導入の場合は、事前に導入しておきます。
```shell-session
pip install presto-python-client
```

以下はprestoエンジンを作成すると標準で作成されるテーブル`tpch.tiny.customer`に、DB接続後、SQLを実行するサンプルコードでです。
```python
import prestodb

with prestodb.dbapi.connect(
   host=hostname,
   port=portnumber,
   user=username,
   # catalog='tpch', # オプション、指定しなくとも繋がる
   # schema='tiny',  # オプション、指定しなくとも繋がる
   http_scheme='https',
   auth=prestodb.auth.BasicAuthentication(username,password)
   ) as conn:
    # SQLの実行例
    cur = conn.cursor()
    cur.execute('select * from tpch.tiny.customer limit 10') # サンプルSQL
    rows = cur.fetchall()
    print(rows)
```
出力は以下のようになります:
`[[902, 'Customer#000000902', 'A1hnMyYPSkXf7QgOPD2H', 4, '14-209-883-5797', 5858.48, 'AUTOMOBILE', ' sometimes regular epitaphs. furiously regular gifts against'], [903, 'Customer#000000903', 'URTiQupkhObWG39,kZ3CfU', 7, '17-706-779-2078', 509.23, 'FURNITURE', 'kly doggedly even instructions. regular, regular accounts along the even, bold packa'], [904, 'Customer#000000904', 'YdJEbNygDU6DrgWXQY6orasq', 5, '15-940-929-4572', 9562.82, 'BUILDING', 'nic dolphins alongside of the slyly final ideas run'], [905, 'Customer#000000905', 'f iyVEgCU2lZZPCebx5bGp5', 3, '13-803-156-2231', -600.73, 'BUILDING', 'slyly closely ironic dolphins. blithely ironic asymptotes haggle carefully ironic theodolites. furiously'], [906, 'Customer#000000906', '1Uavkms1A5z', 6, '16-594-569-6627', -613.45, 'HOUSEHOLD', ' accounts. furiously silent ins'], [907, 'Customer#000000907', 'UeVLwnnpccsG1pbQmN7pzD', 10, '20-501-816-7673', 5751.31, 'HOUSEHOLD', 'sits haggle quickly above the excuses. slyly ironic packages print furiously. carefully pending reques'], [908, 'Customer#000000908', 'Fa5bchMKUMsaNKOXAiu9pX  ME', 12, '22-814-669-9320', 3215.96, 'AUTOMOBILE', 'quickly express packages. blithely fina'], [909, 'Customer#000000909', 'b 2X284A5AGpt8skuYwMvTyK68srMKikPst6X', 24, '34-717-350-9722', 5565.58, 'MACHINERY', 'se carefully around the special, regular requests. ironic theodolites cajole quickly theodolites. regular, ev'], [910, 'Customer#000000910', 'Qg8TJTCT1mJ9H', 9, '19-899-463-4292', 5794.69, 'BUILDING', 'uctions. silent requests after the regular theodolites haggle furiously across the ca'], [911, 'Customer#000000911', 'VS0fia,lJ RvUf68 l4Unv,Vx', 13, '23-121-746-7339', 6364.6, 'BUILDING', 'gular foxes! permanently regular packages wake. quickly regular deposits detect blithely. carefully express']]`





# 3. pandasライブラリを使ったアクセス (sqlalchemy使用)

pandasをよく使う人にはこちらの方が馴染みやすいですよね。

参考: 
- [SQLAlchemy](https://docs.sqlalchemy.org/en/20/index.html)
- [pandas.read_sql_query](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_sql_query.html)

sqlalchemy未導入の場合は、事前に導入しておきます。
```shell-session
pip install SQLAlchemy
```

以下はprestoエンジンを作成すると標準で作成されるテーブル`tpch.tiny.customer`に、DB接続後、SQLを実行し、pandasのDataFrameに結果を入れるコードです。
```py
import pandas as pd
from sqlalchemy import create_engine
from sqlalchemy import text as q_text

connect_args={'protocol': 'https'}

# create engine
engine = create_engine(
   f"presto://{username}:{password}@{hostname}:{portnumber}",
   connect_args=connect_args
   )

sqlstr = 'select * from tpch.tiny.customer limit 10'
df = pd.read_sql_query(q_text(sqlstr), engine.connect())
```
> 2024/08/06 SQLAlchemy の ver2.0以上に対応させてCode修正 

>2023/12/20追記:
実行時`Can't load plugin: sqlalchemy.dialects:presto` というエラーが出た場合は、以下のコマンドで`pyhive[presto]`を追加導入してください。
>```sh
>pip install pyhive[presto]
>```

Jupyrer notebook でのdfの出力は以下のようになります：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294516/75367cf0-551f-496a-51b3-ea65cfac6995.png)

以上です。

# 4. 関連リンク
- [watsonx.data SaaS版のPrestoエンジンにアクセスするための事前情報取得](https://qiita.com/nishikyon/items/2687572b6ecce1e54bb5)
- [watsonx.data SaaS版のPresto エンジンに Presto CLI からアクセス](https://qiita.com/nishikyon/items/2f3ecaa13fe0e7cb4e5c)
- [watsonx.data SaaS版のPresto エンジンにDBeaverからアクセス](https://qiita.com/nishikyon/items/3aa33e5501570bc12827)