0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

.envとenv.pyの関係が分からずで環境変数を設定するときに沼ったハナシ

Posted at

.envファイルとは

開発環境(development)や本番環境(production)で異なる設定を簡単に管理できるファイルです。

APIキーやデータベースのパスワードなどをコードに直接書かず、.env ファイルに保存することで、機密情報がソースコードに混在するのを防ぐことができます。

開発環境ではローカルのデータベース、本番環境ではクラウドのデータベースを使用するなどが可能。

.envを使用する際は、以下のコードを実行します。

$ pip install python-dotenv

これは、.env ファイルに保存されている環境変数を簡単に読み込み、Pythonプログラム内で利用できるようにするライブラリです。

.env
ENV = "product"
PORT = 80

MYSQL_USER = "user" 
MYSQL_PASSWORD = "password123"
MYSQL_HOST = "db"
MYSQL_PORT = "3306"
env.py
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()で環境変数にアクセスできます。

docker-compose.yml
version: "3.8"
services:
  app:
    build: .
    env_file:
      - .env
env.py
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

さてここからが本題

とある開発(ちなみに初開発)の初期設定で以下のようなものがありました。

  1. .env.exampleをコピペする
  2. .envを作成
  3. そこに任意の値を入力する

なるほど、と思い早速.env.exampleをコピペして.envファイルを作成しました。

ざっくりとした中身はこんな感じです。

.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の中はこんな感じ。

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
ENV = "production"

としてしまっているではありませんか〜!

そうなると、、、

env.py
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 = "〇〇"が本番と同じ名前にならないように注意しよう!
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?