0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

django-environでDB接続情報が正しくパースできない

Posted at

django-environとは?

  • django-environはdjango内で環境変数を簡単に使えるようにするためのパッケージです

事象の説明

  • python runserver実行時に下記のようなエラーになってしまいました
test-py3.13xxx@EBCI-xxx:~/work/test$ poetry run python manage.py runserver
UserWarning: Engine not recognized from url: {'NAME': 'ysql\\x3a//test_user\\x3aP@ssw0rd1!@127.0.0.1\\x3a3306/test_db', 'USER': '', 'PASSWORD': '', 'HOST': '', 'PORT': '', 'ENGINE': ''}
  warnings.warn("Engine not recognized from url: {}".format(config))
UserWarning: Engine not recognized from url: {'NAME': 'ysql\\x3a//test_user\\x3aP@ssw0rd1!@127.0.0.1\\x3a3306/test_db', 'USER': '', 'PASSWORD': '', 'HOST': '', 'PORT': '', 'ENGINE': ''}
  warnings.warn("Engine not recognized from url: {}".format(config))
Watching for file changes with StatReloader
Performing system checks...
  • ぱっと見、DB接続情報が正しくパースできていないように思えます
  • .envファイルは下記のように設定していました
DATABASE_URL="mysql://test_user:P@ssw0rd1!@127.0.0.3306/test_db"

試したこと1(GitHubでissueがないか確認)

  • DATABASE_URLがカスタムバックエンドにアンダースコアが含まれている場合に正しく動作しないというissueがありました
  • このissueのコメントにThis might be a Py compatibility issue. I think url parsing fails at environ.py L#375というものを発見
  • でもどうすればいいのかわからず..

試したこと2(ソースコードの確認)

  • django-environを使っているなら、settings.py内で環境変数の取得を行っているのは下記当たりかと思います
env = environ.Env(DEBUG=(bool, False))
environ.Env.read_env(os.path.join(BASE_DIR, ".env"))
  • shellで確認してみます
env = environ.Env(DEBUG=(bool, False))
env("DATABASE_URL")
-> 'mysql\\x3a//test_user\\x3aPssw0rd1!@127.0.0.1\\x3a3306/test_db'
    def get_value(self, var, cast=None, default=NOTSET, parse_default=False):
        """Return value for given environment variable.

        :param str var:
            Name of variable.
        :param collections.abc.Callable or None cast:
            Type to cast return value as.
        :param default:
             If var not present in environ, return this instead.
        :param bool parse_default:
            Force to parse default.
        :returns: Value from environment or default (if set).
        :rtype: typing.IO[typing.Any]
        """

        logger.debug(
            "get '%s' casted as '%s' with default '%s'",
            var, cast, default)

        var_name = f'{self.prefix}{var}'
        if var_name in self.scheme:
            var_info = self.scheme[var_name]

            try:
                has_default = len(var_info) == 2
            except TypeError:
                has_default = False

            if has_default:
                if not cast:
                    cast = var_info[0]

                if default is self.NOTSET:
                    try:
                        default = var_info[1]
                    except IndexError:
                        pass
            else:
                if not cast:
                    cast = var_info

        try:
            value = self.ENVIRON[var_name]
        except KeyError as exc:
            if default is self.NOTSET:
                error_msg = f'Set the {var_name} environment variable'
                raise ImproperlyConfigured(error_msg) from exc

            value = default

        # Resolve any proxied values
        prefix = b'$' if isinstance(value, bytes) else '$'
        escape = rb'\$' if isinstance(value, bytes) else r'\$'
        if hasattr(value, 'startswith') and value.startswith(prefix):
            value = value.lstrip(prefix)
            value = self.get_value(value, cast=cast, default=default)

        if self.escape_proxy and hasattr(value, 'replace'):
            value = value.replace(escape, prefix)

        # Smart casting
        if self.smart_cast:
            if cast is None and default is not None and \
                    not isinstance(default, NoValue):
                cast = type(default)

        value = None if default is None and value == '' else value

        if value != default or (parse_default and value is not None):
            value = self.parse_value(value, cast)

        return value
  • 409行目で指定された名前(今回はDATABASE_URL)に対応する値を取得していそうです
value = self.ENVIRON[var_name]
  • self.ENVIRONos.environみたいなので、この中身を見てみます
>>> import os
>>> os.environ
>>> 'mysql\\x3a//test_user\\x3aPssw0rd1!@127.0.0.1\\x3a3306/test_db'
  • .envから環境変数化する際にエスケープされてしまい、それによりdjango内で環境変数を参照した際に誤認識してしまっていそうです

原因

  • DATABASE_URLを設定する際はシングルクォートで囲うのがよさそうです
  • ダブルクォーテーションで囲ってしまうと「!」などの文字列がコマンドとして認識されてしまうようです
DATABASE_URL='mysql://test_user:P@ssw0rd1!@127.0.0.3306/test_db'
  • Bashにおいては、シングルクォートは完全にリテラルとして扱われるのに対して、ダブルクォートりは変数やコマンドが展開されることに注意しましょう!
0
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?