Skip to content

Instantly share code, notes, and snippets.

@kurtbrose
Last active January 18, 2023 01:35
Show Gist options
  • Save kurtbrose/2ef30cc457b8ce711ae3f19b58e1e0cb to your computer and use it in GitHub Desktop.
Save kurtbrose/2ef30cc457b8ce711ae3f19b58e1e0cb to your computer and use it in GitHub Desktop.
file for parsing .env files in python
"""
Utility for parsing .env files, as used by docker-compose and a few others.
"""
import re
QUOTED_STRING = r'("(?:(\\.|[^"\\])*)")'
SINGLE_QUOTED_STRING = r"('(?:(\\.|[^'\\])*)')"
VAL = fr'([^\s]+|{QUOTED_STRING}|{SINGLE_QUOTED_STRING})'
LINE = fr'^\s*(?P<key>{VAL})\s*=\s*(?P<val>{VAL})\s*(#.*)?$'
def _strip_quote(val):
quotes = ('"', "'")
for quote in quotes:
if val[0] == quote and val[-1] == quote:
return val[1:-1].replace("\\", "")
return val
def parse_lines(lines):
vals = {}
for num, line in enumerate(lines):
if not line.strip():
continue # skip blank lines
match = re.match(LINE, line)
if not match:
if line.lstrip()[0] != '#':
raise ValueError(f'could not parse line {num + 1}: {line}')
continue
groups = match.groupdict()
vals[_strip_quote(groups['key'])] = _strip_quote(groups['val'])
return vals
def parse_file(path):
with open(path, encoding="utf-8") as file:
return parse_lines(file)
from unittest import TestCase
import dotenv
class DotEnvTest(TestCase):
def test(self):
assert dotenv.parse_lines([
'a1 = b1',
'a2 = b2 # ignore comment',
' a3=b3 ',
'a4="b4\\" "',
"a5='b5\\' '",
' # ...', # ignore comment only line
' ', # ignore blank line
]) == {'a1': 'b1', 'a2': 'b2', 'a3': 'b3', 'a4': 'b4" ', 'a5': "b5' "}
def test_error(self):
with self.assertRaises(ValueError):
dotenv.parse_lines(['invalid line'])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment