Snowflake Python Connectorのための環境を direnv + pip-tools で構築してみる！

# １. はじめに

今回は、Snowflake Python Connectorのための環境をdirenvとpip-toolsを使って構築してみた記事です！

今回の作業環境は以下のとおりです。

【作業環境】
・ チップ Apple M1
・ macOS Ventura 13.3

※その他、Snowflake関連記事はコチラです！参考までにご覧ください！

https://qiita.com/se_yataro/items/bd86e3744b2546cbf84c

https://qiita.com/se_yataro/items/55601060d69208a87523

https://qiita.com/se_yataro/items/21a7fcac1adcbc6ddd6b

# ２. direnv（python標準のvenv）とpip-toolsで環境構築する！

## ① Python環境の構築

Python環境を構築する際にはpipenvを利用していましたが（それくらいしか知らなかった😅）、今回は[こちらのブログ](https://blog.uedder.com/2020_python_develop_envirionment.html)を参考にさせていただき、direnvを利用してpipenvではなくpython標準のvenvによる環境構築を行ってみました！


## ② direnvで仮想環境立ち上げの自動化

[direnv](https://github.com/direnv/direnv)を使うと、特定のディレクトリに出入りすると仮想環境の立ち上げ・終了が自動化できます。
立ち上げ忘れとかがなくなるので、すごく楽でした！

direnvはHomebrewでインストールできます。

```bash
brew install direnv
```

brewでインストールしたら、こちらにあるように`.zshrc`等にフックを登録します。

`~/.zshrc`に下記を追加する。

```
eval "$(direnv hook zsh)"
```

次に、仮想環境を構築するディレクトリに`.envrc`を作成します。
その`.envrc`に下記のとおり書いておくと、そのディレクトリに移動すると自動で仮想環境がactivate/deactivateされます。

```
layout python
```

**※Python バージョンを指定した仮想環境の作成**
任意の Python バージョンを指定したい場合は、[pyenv](https://github.com/pyenv/pyenv#homebrew-in-macos) を使いましょう。（pyenvはpythonのバージョン管理ソフト。brewでインストール可能。インストール後に.zshrcにaliasを要設定。）

pyenvがインストール済なら`.envrc`を

```
layout python ~/.pyenv/versions/3.X.X/bin/python
```

のように Python のパスを末尾に書けば、3.X.X のバージョンで仮想環境が作成されます。
（事前にpyenvで使いたいバージョンをインストールしておく必要があります。）


## ③ pip-toolsでライブラリ管理

ライブラリ管理はpip-toolsで行います。使い方は[こちらのQiita](https://qiita.com/ryu22e/items/ad3f8f3df30886d23661)がわかりやすかったです！

作成した仮想環境でpip-toolsをインストールします。

```bash
pip install pip-tools
```

次に、`requirements.in`というファイルを作業フォルダ内に作成して、インストールしたいライブラリを記述します。
今回は、下記の２つを記述しました。

```
snowflake-connector-python
python-dotenv
```

`requirements.in`を作成したら、下記のコマンドで`requirements.txt`を作成します。

```bash
pip-compile requirements.in
```

そして、`pip-sync`で`requirements.txt`を読み込んで、必要なパッケージをインストールします。

```bash
pip-sync
```

> 普通のpip installと違って、以前インストールしていたけど使わなくなったパッケージをアンインストールしてくれます。
> 

とのこと。（上記Qiita記事より引用）

## ④ python-dotenvを使って環境変数を設定する

環境変数を使ってsnoflakeへのログイン情報を管理するために、`python-dotenv`を使います。
[こちらのQiita](https://qiita.com/harukikaneko/items/b004048f8d1eca44cba9)がわかりやすかったです！
※`python-dotenv`はpip-toolsの`requirements.in`に記載したのでインストールされています。

まず、作業フォルダ内に`.env`ファイルを作ります。（ターミナルから作成していっても良いです。）
次に、ログイン情報を記載しましょう。

```
SNOWFLAKE_USER="<各自のユーザーネーム>"
SNOWFLAKE_PWD="<各自のパスワード>"
SNOWFLAKE_ACC="<アカウント>"
```

python内での呼び出しはこんな感じです。

```python
import os
from dotenv import load_dotenv

load_dotenv() #.envファイルの読み込み

# 読み込んだ環境変数をpythonの変数に代入
USER = os.getenv('SNOWFLAKE_USER')
PASSWORD = os.getenv('SNOWFLAKE_PWD')
ACCOUNT = os.getenv('SNOWFLAKE_ACC')
```

以上で、準備ＯＫです。
ここまでで、こんな形のフォルダ構成になっています。

```
.
├── .direnv/python-3.xx.x   # direnvによって自動で作られた仮想環境
├── .env
├── .envrc
├── requirements.in
└── requirements.txt

```

#### direnv（pyenv）とpip-toolsで共通の環境を構築する

`direnv`の`.envrc`でのpythonバージョンの指定と、`pip-tools`のrequirements.txtによるライブラリ管理で、これらのファイルを共有すれば共通の環境を再現できます！

## ⑤ Snowflake Python Connectorを使った接続

あとは、[公式チュートリアル](https://quickstarts.snowflake.com/guide/getting_started_with_python/index.html)に従って進めれば接続できます！！！！
以下、参考までにチュートリアルで使ったpythonコードです。

```python
import snowflake.connector
import os
from dotenv import load_dotenv

load_dotenv() #.envファイルの読み込み

# 読み込んだ環境変数をpythonの変数に代入
USER = os.getenv('SNOWFLAKE_USER')
PASSWORD = os.getenv('SNOWFLAKE_PWD')
ACCOUNT = os.getenv('SNOWFLAKE_ACC')

# Snowflakeへのコネクション作成
conn = snowflake.connector.connect(
    user=USER,
    password=PASSWORD,
    account=ACCOUNT,
    session_parameters={
        'QUERY_TAG': 'EndOfMonthFinancials',
    }
)

# ウェアハウスの作成と使用
conn.cursor().execute("CREATE WAREHOUSE IF NOT EXISTS tiny_warehouse_mg")
conn.cursor().execute("USE WAREHOUSE tiny_warehouse_mg")

# データベースの作成と使用
conn.cursor().execute("CREATE DATABASE IF NOT EXISTS testdb")
conn.cursor().execute("USE DATABASE testdb")

# スキーマの作成と使用
conn.cursor().execute("CREATE SCHEMA IF NOT EXISTS testschema")
conn.cursor().execute("USE SCHEMA testschema")

# テーブルの作成
conn.cursor().execute(
    "CREATE OR REPLACE TABLE "
    "test_table(col1 integer, col2 string)")

# データインサート
conn.cursor().execute(
    "INSERT INTO test_table(col1, col2) "
    "VALUES(123, 'test string1'),(456, 'test string2')")

# ファイルからアップロードしてインサート
conn.cursor().execute("PUT file:///tmp/data/file* @%test_table") # ステージング
conn.cursor().execute("COPY INTO test_table") # コピー

# クエリの実行
# パターン１
col1, col2 = conn.cursor().execute("SELECT col1, col2 FROM test_table").fetchone()
print('{0}, {1}'.format(col1, col2))

# パターン２
for (col1, col2) in conn.cursor().execute("SELECT col1, col2 FROM test_table"):
	print('{0}, {1}'.format(col1, col2))

# コネクションを切断
conn.close()
```