.envファイルとは
開発環境(development)や本番環境(production)で異なる設定を簡単に管理できるファイルです。
APIキーやデータベースのパスワードなどをコードに直接書かず、.env ファイルに保存することで、機密情報がソースコードに混在するのを防ぐことができます。
開発環境ではローカルのデータベース、本番環境ではクラウドのデータベースを使用するなどが可能。
.envを使用する際は、以下のコードを実行します。
$ pip install python-dotenv
これは、.env ファイルに保存されている環境変数を簡単に読み込み、Pythonプログラム内で利用できるようにするライブラリです。
ENV = "product"
PORT = 80
MYSQL_USER = "user"
MYSQL_PASSWORD = "password123"
MYSQL_HOST = "db"
MYSQL_PORT = "3306"
from dotenv import load_dotenv
import os
# .envファイルを読み込む
load_dotenv()
# 環境変数を取得
user = os.getenv("MYSQL_USER")
host = os.getenv("MYSQL_HOST")
port = os.getenv("MYSQL_PORT")
print(f"Connecting to MySQL: {user}@{host}:{port}")
## Connecting to MySQL: user@db:3306
なお、docker-compose.ymlで以下のように定義している場合、load_dotenv()は不要で、.env ファイルの内容がコンテナ内の環境変数として自動的に設定されるため、os.environ.get()で環境変数にアクセスできます。
version: "3.8"
services:
app:
build: .
env_file:
- .env
import os
# 環境変数の取得
user = os.environ.get("MYSQL_USER")
host = os.environ.get("MYSQL_HOST")
port = os.environ.get("MYSQL_PORT")
print(f"Connecting to MySQL: {user}@{host}:{port}")
## Connecting to MySQL: user@db:3306
さてここからが本題
とある開発(ちなみに初開発)の初期設定で以下のようなものがありました。
- .env.exampleをコピペする
- .envを作成
- そこに任意の値を入力する
なるほど、と思い早速.env.exampleをコピペして.envファイルを作成しました。
ざっくりとした中身はこんな感じです。
ENV = "production"
PORT = 80
MYSQL_USER = "user"
MYSQL_PASSWORD = "password123"
MYSQL_ROOT_PASSWORD = "rootpassword123"
MYSQL_HOST = "db"
MYSQL_PORT = "3306"
MYSQL_DB_NAME = "my_database"
INSTANCE_CONNECTION_NAME = "my-project-id:region:instance-id"
そしてenv.pyの中はこんな感じ。
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
# 追記
from database import Base
import os
MYSQL_USER = os.environ.get('MYSQL_USER')
MYSQL_PASSWORD = os.environ.get('MYSQL_PASSWORD')
MYSQL_HOST = os.environ.get('MYSQL_HOST')
MYSQL_DB_NAME = os.environ.get('MYSQL_DB_NAME')
INSTANCE_CONNECTION_NAME = os.environ.get('INSTANCE_CONNECTION_NAME')
ENV = os.environ.get('ENV')
is_production = os.environ.get('ENV') == 'production'
config = context.config
target_metadata = Base.metadata
if is_production:
if INSTANCE_CONNECTION_NAME:
config.set_main_option(
"sqlalchemy.url",
f"mysql+pymysql://{MYSQL_USER}:{MYSQL_PASSWORD}@/{MYSQL_DB_NAME}?charset=utf8mb4&unix_socket=/cloudsql/{INSTANCE_CONNECTION_NAME}"
)
else:
config.set_main_option(
"sqlalchemy.url",
f"mysql+pymysql://{MYSQL_USER}:{MYSQL_PASSWORD}@cloudsql-proxy:3306/{MYSQL_DB_NAME}?charset=utf8mb4"
)
else:
config.set_main_option(
"sqlalchemy.url",
f"mysql+pymysql://{MYSQL_USER}:{MYSQL_PASSWORD}@{MYSQL_HOST}:3306/{MYSQL_DB_NAME}?charset=utf8mb4"
)
そしてコンテナ内で「alembic upgrade head」を実行しても、一向にデータベースが作成されない……
sqlalchemy.exc.OperationalError: (pymysql.err.OperationalError) (2003, "Can't connect to MySQL server on 'localhost' ([Errno 2] No such file or directory)")
ひたすら、「localhost に接続できない」というエラーが出てきてしまいました。MySQLコンテナは起動していて、FastAPIコンテナからMySQLコンテナへの接続もできているのにも関わらずです。
「接続先はlocalhostじゃないんだけどな〜。dbなんだけどなぁ〜」って思っていたところ、自分が触ったファイルはどこかってのを改めて考えてみました。
3の「そこに任意の値を入力する」です。
ENV = "production"
としてしまっているではありませんか〜!
そうなると、、、
if is_production:
if INSTANCE_CONNECTION_NAME:
config.set_main_option(
"sqlalchemy.url",
f"mysql+pymysql://{MYSQL_USER}:{MYSQL_PASSWORD}@/{MYSQL_DB_NAME}?charset=utf8mb4&unix_socket=/cloudsql/{INSTANCE_CONNECTION_NAME}"
)
else:
config.set_main_option(
"sqlalchemy.url",
f"mysql+pymysql://{MYSQL_USER}:{MYSQL_PASSWORD}@cloudsql-proxy:3306/{MYSQL_DB_NAME}?charset=utf8mb4"
)
else:
config.set_main_option(
"sqlalchemy.url",
f"mysql+pymysql://{MYSQL_USER}:{MYSQL_PASSWORD}@{MYSQL_HOST}:3306/{MYSQL_DB_NAME}?charset=utf8mb4"
)
このif文がTrueになって、それから先が実行されてしまいます。その結果、本番環境用のデータベースに接続しようとしてエラーが出てしまったわけです。
まとめ
env.py は Alembic を使ったデータベースのマイグレーションを実行する際に使用されるスクリプトで、マイグレーションの挙動を定義するファイル。
alembic.ini から設定情報(sqlalchemy.url など)を読み取ったり、動的に設定を変更したりする他、データベースに接続するためのエンジンを構築します。
【今回の学び】
- .envファイルには任意の値を設定してOK
- しかし、ENV = "〇〇"が本番と同じ名前にならないように注意しよう!