omlish 0.0.0.dev129__py3-none-any.whl → 0.0.0.dev131__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- omlish/__about__.py +3 -3
- omlish/formats/dotenv.py +166 -152
- omlish/inject/__init__.py +1 -1
- omlish/inject/impl/injector.py +2 -2
- omlish/inject/impl/scopes.py +4 -4
- omlish/inject/scopes.py +2 -2
- omlish/lite/check.py +13 -0
- omlish/lite/fdio/__init__.py +0 -0
- omlish/lite/fdio/corohttp.py +135 -0
- omlish/lite/fdio/handlers.py +67 -0
- omlish/lite/fdio/kqueue.py +102 -0
- omlish/lite/fdio/manager.py +48 -0
- omlish/lite/fdio/pollers.py +212 -0
- omlish/lite/inject.py +66 -8
- omlish/lite/io.py +98 -0
- {omlish-0.0.0.dev129.dist-info → omlish-0.0.0.dev131.dist-info}/METADATA +3 -3
- {omlish-0.0.0.dev129.dist-info → omlish-0.0.0.dev131.dist-info}/RECORD +21 -15
- {omlish-0.0.0.dev129.dist-info → omlish-0.0.0.dev131.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev129.dist-info → omlish-0.0.0.dev131.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev129.dist-info → omlish-0.0.0.dev131.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev129.dist-info → omlish-0.0.0.dev131.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
__version__ = '0.0.0.
|
2
|
-
__revision__ = '
|
1
|
+
__version__ = '0.0.0.dev131'
|
2
|
+
__revision__ = 'd1c2a72e639d89e78ab32d95eaa3e46bf716249c'
|
3
3
|
|
4
4
|
|
5
5
|
#
|
@@ -99,7 +99,7 @@ class Project(ProjectBase):
|
|
99
99
|
'aiosqlite ~= 0.20',
|
100
100
|
'asyncpg ~= 0.30',
|
101
101
|
|
102
|
-
'apsw ~= 3.
|
102
|
+
'apsw ~= 3.47',
|
103
103
|
|
104
104
|
'sqlean.py ~= 3.45',
|
105
105
|
|
omlish/formats/dotenv.py
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# @omlish-lite
|
2
|
+
# ruff: noqa: UP006 UP007 UP037
|
1
3
|
# Copyright (c) 2014, Saurabh Kumar (python-dotenv), 2013, Ted Tieken (django-dotenv-rw), 2013, Jacob Kaplan-Moss
|
2
4
|
# (django-dotenv)
|
3
5
|
#
|
@@ -37,13 +39,7 @@ import typing as ta
|
|
37
39
|
##
|
38
40
|
|
39
41
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
##
|
44
|
-
|
45
|
-
|
46
|
-
_posix_variable: ta.Pattern[str] = re.compile(
|
42
|
+
_dotenv_posix_variable_pat: ta.Pattern[str] = re.compile(
|
47
43
|
r"""
|
48
44
|
\$\{
|
49
45
|
(?P<name>[^}:]*)
|
@@ -56,7 +52,7 @@ _posix_variable: ta.Pattern[str] = re.compile(
|
|
56
52
|
)
|
57
53
|
|
58
54
|
|
59
|
-
class
|
55
|
+
class DotenvAtom(metaclass=abc.ABCMeta):
|
60
56
|
def __ne__(self, other: object) -> bool:
|
61
57
|
result = self.__eq__(other)
|
62
58
|
if result is NotImplemented:
|
@@ -64,16 +60,16 @@ class Atom(metaclass=abc.ABCMeta):
|
|
64
60
|
return not result
|
65
61
|
|
66
62
|
@abc.abstractmethod
|
67
|
-
def resolve(self, env: ta.Mapping[str, str
|
63
|
+
def resolve(self, env: ta.Mapping[str, ta.Optional[str]]) -> str: ...
|
68
64
|
|
69
65
|
|
70
|
-
class
|
66
|
+
class DotenvLiteral(DotenvAtom):
|
71
67
|
def __init__(self, value: str) -> None:
|
72
68
|
super().__init__()
|
73
69
|
self.value = value
|
74
70
|
|
75
71
|
def __repr__(self) -> str:
|
76
|
-
return f'
|
72
|
+
return f'DotenvLiteral(value={self.value})'
|
77
73
|
|
78
74
|
def __eq__(self, other: object) -> bool:
|
79
75
|
if not isinstance(other, self.__class__):
|
@@ -83,18 +79,18 @@ class Literal(Atom):
|
|
83
79
|
def __hash__(self) -> int:
|
84
80
|
return hash((self.__class__, self.value))
|
85
81
|
|
86
|
-
def resolve(self, env: ta.Mapping[str, str
|
82
|
+
def resolve(self, env: ta.Mapping[str, ta.Optional[str]]) -> str:
|
87
83
|
return self.value
|
88
84
|
|
89
85
|
|
90
|
-
class
|
91
|
-
def __init__(self, name: str, default: str
|
86
|
+
class DotenvVariable(DotenvAtom):
|
87
|
+
def __init__(self, name: str, default: ta.Optional[str]) -> None:
|
92
88
|
super().__init__()
|
93
89
|
self.name = name
|
94
90
|
self.default = default
|
95
91
|
|
96
92
|
def __repr__(self) -> str:
|
97
|
-
return f'
|
93
|
+
return f'DotenvVariable(name={self.name}, default={self.default})'
|
98
94
|
|
99
95
|
def __eq__(self, other: object) -> bool:
|
100
96
|
if not isinstance(other, self.__class__):
|
@@ -104,96 +100,96 @@ class Variable(Atom):
|
|
104
100
|
def __hash__(self) -> int:
|
105
101
|
return hash((self.__class__, self.name, self.default))
|
106
102
|
|
107
|
-
def resolve(self, env: ta.Mapping[str, str
|
103
|
+
def resolve(self, env: ta.Mapping[str, ta.Optional[str]]) -> str:
|
108
104
|
default = self.default if self.default is not None else ''
|
109
105
|
result = env.get(self.name, default)
|
110
106
|
return result if result is not None else ''
|
111
107
|
|
112
108
|
|
113
|
-
def
|
109
|
+
def parse_dotenv_variables(value: str) -> ta.Iterator[DotenvAtom]:
|
114
110
|
cursor = 0
|
115
111
|
|
116
|
-
for match in
|
112
|
+
for match in _dotenv_posix_variable_pat.finditer(value):
|
117
113
|
(start, end) = match.span()
|
118
114
|
name = match['name']
|
119
115
|
default = match['default']
|
120
116
|
|
121
117
|
if start > cursor:
|
122
|
-
yield
|
118
|
+
yield DotenvLiteral(value=value[cursor:start])
|
123
119
|
|
124
|
-
yield
|
120
|
+
yield DotenvVariable(name=name, default=default)
|
125
121
|
cursor = end
|
126
122
|
|
127
123
|
length = len(value)
|
128
124
|
if cursor < length:
|
129
|
-
yield
|
125
|
+
yield DotenvLiteral(value=value[cursor:length])
|
130
126
|
|
131
127
|
|
132
128
|
##
|
133
129
|
|
134
130
|
|
135
|
-
def
|
131
|
+
def _make_dotenv_regex(string: str, extra_flags: int = 0) -> ta.Pattern[str]:
|
136
132
|
return re.compile(string, re.UNICODE | extra_flags)
|
137
133
|
|
138
134
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
class
|
135
|
+
_dotenv_newline_pat = _make_dotenv_regex(r'(\r\n|\n|\r)')
|
136
|
+
_dotenv_multiline_whitespace_pat = _make_dotenv_regex(r'\s*', extra_flags=re.MULTILINE)
|
137
|
+
_dotenv_whitespace_pat = _make_dotenv_regex(r'[^\S\r\n]*')
|
138
|
+
_dotenv_export_pat = _make_dotenv_regex(r'(?:export[^\S\r\n]+)?')
|
139
|
+
_dotenv_single_quoted_key_pat = _make_dotenv_regex(r"'([^']+)'")
|
140
|
+
_dotenv_unquoted_key_pat = _make_dotenv_regex(r'([^=\#\s]+)')
|
141
|
+
_dotenv_equal_sign_pat = _make_dotenv_regex(r'(=[^\S\r\n]*)')
|
142
|
+
_dotenv_single_quoted_value_pat = _make_dotenv_regex(r"'((?:\\'|[^'])*)'")
|
143
|
+
_dotenv_double_quoted_value_pat = _make_dotenv_regex(r'"((?:\\"|[^"])*)"')
|
144
|
+
_dotenv_unquoted_value_pat = _make_dotenv_regex(r'([^\r\n]*)')
|
145
|
+
_dotenv_comment_pat = _make_dotenv_regex(r'(?:[^\S\r\n]*#[^\r\n]*)?')
|
146
|
+
_dotenv_end_of_line_pat = _make_dotenv_regex(r'[^\S\r\n]*(?:\r\n|\n|\r|$)')
|
147
|
+
_dotenv_rest_of_line_pat = _make_dotenv_regex(r'[^\r\n]*(?:\r|\n|\r\n)?')
|
148
|
+
_dotenv_double_quote_escapes_pat = _make_dotenv_regex(r"\\[\\'\"abfnrtv]")
|
149
|
+
_dotenv_single_quote_escapes_pat = _make_dotenv_regex(r"\\[\\']")
|
150
|
+
|
151
|
+
|
152
|
+
class DotenvOriginal(ta.NamedTuple):
|
157
153
|
string: str
|
158
154
|
line: int
|
159
155
|
|
160
156
|
|
161
|
-
class
|
162
|
-
key: str
|
163
|
-
value: str
|
164
|
-
original:
|
157
|
+
class DotenvBinding(ta.NamedTuple):
|
158
|
+
key: ta.Optional[str]
|
159
|
+
value: ta.Optional[str]
|
160
|
+
original: DotenvOriginal
|
165
161
|
error: bool
|
166
162
|
|
167
163
|
|
168
|
-
class
|
164
|
+
class _DotenvPosition:
|
169
165
|
def __init__(self, chars: int, line: int) -> None:
|
170
166
|
super().__init__()
|
171
167
|
self.chars = chars
|
172
168
|
self.line = line
|
173
169
|
|
174
170
|
@classmethod
|
175
|
-
def start(cls) -> '
|
171
|
+
def start(cls) -> '_DotenvPosition':
|
176
172
|
return cls(chars=0, line=1)
|
177
173
|
|
178
|
-
def set(self, other: '
|
174
|
+
def set(self, other: '_DotenvPosition') -> None:
|
179
175
|
self.chars = other.chars
|
180
176
|
self.line = other.line
|
181
177
|
|
182
178
|
def advance(self, string: str) -> None:
|
183
179
|
self.chars += len(string)
|
184
|
-
self.line += len(re.findall(
|
180
|
+
self.line += len(re.findall(_dotenv_newline_pat, string))
|
185
181
|
|
186
182
|
|
187
|
-
class
|
183
|
+
class DotenvError(Exception):
|
188
184
|
pass
|
189
185
|
|
190
186
|
|
191
|
-
class
|
187
|
+
class _DotenvReader:
|
192
188
|
def __init__(self, stream: ta.IO[str]) -> None:
|
193
189
|
super().__init__()
|
194
190
|
self.string = stream.read()
|
195
|
-
self.position =
|
196
|
-
self.mark =
|
191
|
+
self.position = _DotenvPosition.start()
|
192
|
+
self.mark = _DotenvPosition.start()
|
197
193
|
|
198
194
|
def has_next(self) -> bool:
|
199
195
|
return self.position.chars < len(self.string)
|
@@ -201,8 +197,8 @@ class _Reader:
|
|
201
197
|
def set_mark(self) -> None:
|
202
198
|
self.mark.set(self.position)
|
203
199
|
|
204
|
-
def get_marked(self) ->
|
205
|
-
return
|
200
|
+
def get_marked(self) -> DotenvOriginal:
|
201
|
+
return DotenvOriginal(
|
206
202
|
string=self.string[self.mark.chars:self.position.chars],
|
207
203
|
line=self.mark.line,
|
208
204
|
)
|
@@ -213,85 +209,85 @@ class _Reader:
|
|
213
209
|
def read(self, count: int) -> str:
|
214
210
|
result = self.string[self.position.chars:self.position.chars + count]
|
215
211
|
if len(result) < count:
|
216
|
-
raise
|
212
|
+
raise DotenvError('read: End of string')
|
217
213
|
self.position.advance(result)
|
218
214
|
return result
|
219
215
|
|
220
216
|
def read_regex(self, regex: ta.Pattern[str]) -> ta.Sequence[str]:
|
221
217
|
match = regex.match(self.string, self.position.chars)
|
222
218
|
if match is None:
|
223
|
-
raise
|
219
|
+
raise DotenvError('read_regex: Pattern not found')
|
224
220
|
self.position.advance(self.string[match.start():match.end()])
|
225
221
|
return match.groups()
|
226
222
|
|
227
223
|
|
228
|
-
def
|
224
|
+
def _decode_dotenv_escapes(regex: ta.Pattern[str], string: str) -> str:
|
229
225
|
def decode_match(match: ta.Match[str]) -> str:
|
230
226
|
return codecs.decode(match.group(0), 'unicode-escape')
|
231
227
|
|
232
228
|
return regex.sub(decode_match, string)
|
233
229
|
|
234
230
|
|
235
|
-
def
|
231
|
+
def _parse_dotenv_key(reader: _DotenvReader) -> ta.Optional[str]:
|
236
232
|
char = reader.peek(1)
|
237
233
|
if char == '#':
|
238
234
|
return None
|
239
235
|
elif char == "'":
|
240
|
-
(key,) = reader.read_regex(
|
236
|
+
(key,) = reader.read_regex(_dotenv_single_quoted_key_pat)
|
241
237
|
else:
|
242
|
-
(key,) = reader.read_regex(
|
238
|
+
(key,) = reader.read_regex(_dotenv_unquoted_key_pat)
|
243
239
|
return key
|
244
240
|
|
245
241
|
|
246
|
-
def
|
247
|
-
(part,) = reader.read_regex(
|
242
|
+
def _parse_dotenv_unquoted_value(reader: _DotenvReader) -> str:
|
243
|
+
(part,) = reader.read_regex(_dotenv_unquoted_value_pat)
|
248
244
|
return re.sub(r'\s+#.*', '', part).rstrip()
|
249
245
|
|
250
246
|
|
251
|
-
def
|
247
|
+
def _parse_dotenv_value(reader: _DotenvReader) -> str:
|
252
248
|
char = reader.peek(1)
|
253
249
|
if char == "'":
|
254
|
-
(value,) = reader.read_regex(
|
255
|
-
return
|
250
|
+
(value,) = reader.read_regex(_dotenv_single_quoted_value_pat)
|
251
|
+
return _decode_dotenv_escapes(_dotenv_single_quote_escapes_pat, value)
|
256
252
|
elif char == '"':
|
257
|
-
(value,) = reader.read_regex(
|
258
|
-
return
|
253
|
+
(value,) = reader.read_regex(_dotenv_double_quoted_value_pat)
|
254
|
+
return _decode_dotenv_escapes(_dotenv_double_quote_escapes_pat, value)
|
259
255
|
elif char in ('', '\n', '\r'):
|
260
256
|
return ''
|
261
257
|
else:
|
262
|
-
return
|
258
|
+
return _parse_dotenv_unquoted_value(reader)
|
263
259
|
|
264
260
|
|
265
|
-
def
|
261
|
+
def _parse_dotenv_binding(reader: _DotenvReader) -> DotenvBinding:
|
266
262
|
reader.set_mark()
|
267
263
|
try:
|
268
|
-
reader.read_regex(
|
264
|
+
reader.read_regex(_dotenv_multiline_whitespace_pat)
|
269
265
|
if not reader.has_next():
|
270
|
-
return
|
266
|
+
return DotenvBinding(
|
271
267
|
key=None,
|
272
268
|
value=None,
|
273
269
|
original=reader.get_marked(),
|
274
270
|
error=False,
|
275
271
|
)
|
276
|
-
reader.read_regex(
|
277
|
-
key =
|
278
|
-
reader.read_regex(
|
272
|
+
reader.read_regex(_dotenv_export_pat)
|
273
|
+
key = _parse_dotenv_key(reader)
|
274
|
+
reader.read_regex(_dotenv_whitespace_pat)
|
279
275
|
if reader.peek(1) == '=':
|
280
|
-
reader.read_regex(
|
281
|
-
value: str
|
276
|
+
reader.read_regex(_dotenv_equal_sign_pat)
|
277
|
+
value: ta.Optional[str] = _parse_dotenv_value(reader)
|
282
278
|
else:
|
283
279
|
value = None
|
284
|
-
reader.read_regex(
|
285
|
-
reader.read_regex(
|
286
|
-
return
|
280
|
+
reader.read_regex(_dotenv_comment_pat)
|
281
|
+
reader.read_regex(_dotenv_end_of_line_pat)
|
282
|
+
return DotenvBinding(
|
287
283
|
key=key,
|
288
284
|
value=value,
|
289
285
|
original=reader.get_marked(),
|
290
286
|
error=False,
|
291
287
|
)
|
292
|
-
except
|
293
|
-
reader.read_regex(
|
294
|
-
return
|
288
|
+
except DotenvError:
|
289
|
+
reader.read_regex(_dotenv_rest_of_line_pat)
|
290
|
+
return DotenvBinding(
|
295
291
|
key=None,
|
296
292
|
value=None,
|
297
293
|
original=reader.get_marked(),
|
@@ -299,54 +295,54 @@ def _parse_binding(reader: _Reader) -> Binding:
|
|
299
295
|
)
|
300
296
|
|
301
297
|
|
302
|
-
def
|
303
|
-
reader =
|
298
|
+
def parse_dotenv_stream(stream: ta.IO[str]) -> ta.Iterator[DotenvBinding]:
|
299
|
+
reader = _DotenvReader(stream)
|
304
300
|
while reader.has_next():
|
305
|
-
yield
|
301
|
+
yield _parse_dotenv_binding(reader)
|
306
302
|
|
307
303
|
|
308
304
|
##
|
309
305
|
|
310
306
|
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
def _with_warn_for_invalid_lines(mappings: ta.Iterator[Binding]) -> ta.Iterator[Binding]:
|
307
|
+
def _dotenv_with_warn_for_invalid_lines(
|
308
|
+
mappings: ta.Iterator[DotenvBinding],
|
309
|
+
log: ta.Optional[logging.Logger] = None,
|
310
|
+
) -> ta.Iterator[DotenvBinding]:
|
318
311
|
for mapping in mappings:
|
319
312
|
if mapping.error:
|
320
|
-
log
|
321
|
-
|
322
|
-
|
323
|
-
|
313
|
+
if log is not None:
|
314
|
+
log.warning(
|
315
|
+
'dotenv could not parse statement starting at line %s',
|
316
|
+
mapping.original.line,
|
317
|
+
)
|
324
318
|
yield mapping
|
325
319
|
|
326
320
|
|
327
|
-
|
321
|
+
StrStrMutableMappingT = ta.TypeVar('StrStrMutableMappingT', bound=ta.MutableMapping[str, str])
|
328
322
|
|
329
323
|
|
330
|
-
class
|
324
|
+
class Dotenv:
|
331
325
|
def __init__(
|
332
326
|
self,
|
333
|
-
path:
|
334
|
-
stream: ta.IO[str]
|
327
|
+
path: ta.Union[str, 'os.PathLike[str]', None] = None,
|
328
|
+
stream: ta.Optional[ta.IO[str]] = None,
|
335
329
|
verbose: bool = False,
|
336
|
-
encoding: str
|
330
|
+
encoding: ta.Optional[str] = None,
|
337
331
|
interpolate: bool = True,
|
338
332
|
override: bool = True,
|
339
|
-
env: ta.Mapping[str, str]
|
333
|
+
env: ta.Optional[ta.Mapping[str, str]] = None,
|
334
|
+
log: ta.Optional[logging.Logger] = None,
|
340
335
|
) -> None:
|
341
336
|
super().__init__()
|
342
|
-
self.path:
|
343
|
-
self.stream: ta.IO[str]
|
344
|
-
self._dict:
|
337
|
+
self.path: ta.Union[str, 'os.PathLike[str]', None] = path
|
338
|
+
self.stream: ta.Optional[ta.IO[str]] = stream
|
339
|
+
self._dict: ta.Optional[ta.Dict[str, ta.Optional[str]]] = None
|
345
340
|
self.verbose: bool = verbose
|
346
|
-
self.encoding: str
|
341
|
+
self.encoding: ta.Optional[str] = encoding
|
347
342
|
self.interpolate: bool = interpolate
|
348
343
|
self.override: bool = override
|
349
344
|
self.env = env or {}
|
345
|
+
self.log = log
|
350
346
|
|
351
347
|
@contextlib.contextmanager
|
352
348
|
def _get_stream(self) -> ta.Iterator[ta.IO[str]]:
|
@@ -357,26 +353,27 @@ class DotEnv:
|
|
357
353
|
yield self.stream
|
358
354
|
else:
|
359
355
|
if self.verbose:
|
360
|
-
log
|
361
|
-
|
362
|
-
|
363
|
-
|
356
|
+
if self.log is not None:
|
357
|
+
self.log.info(
|
358
|
+
'dotenv could not find configuration file %s.',
|
359
|
+
self.path or '.env',
|
360
|
+
)
|
364
361
|
yield io.StringIO('')
|
365
362
|
|
366
|
-
def dict(self) ->
|
363
|
+
def dict(self) -> ta.Dict[str, ta.Optional[str]]:
|
367
364
|
if self._dict:
|
368
365
|
return self._dict
|
369
366
|
|
370
367
|
raw_values = self.parse()
|
371
368
|
|
372
369
|
if self.interpolate:
|
373
|
-
self._dict =
|
370
|
+
self._dict = dotenv_resolve_variables(raw_values, override=self.override, env=self.env)
|
374
371
|
else:
|
375
372
|
self._dict = dict(raw_values)
|
376
373
|
|
377
374
|
return self._dict
|
378
375
|
|
379
|
-
def apply_to(self, dst:
|
376
|
+
def apply_to(self, dst: StrStrMutableMappingT) -> StrStrMutableMappingT:
|
380
377
|
for k, v in self.dict().items():
|
381
378
|
if v is not None:
|
382
379
|
dst[k] = v
|
@@ -384,20 +381,21 @@ class DotEnv:
|
|
384
381
|
del dst[k]
|
385
382
|
return dst
|
386
383
|
|
387
|
-
def parse(self) -> ta.Iterator[
|
384
|
+
def parse(self) -> ta.Iterator[ta.Tuple[str, ta.Optional[str]]]:
|
388
385
|
with self._get_stream() as stream:
|
389
|
-
for mapping in
|
386
|
+
for mapping in _dotenv_with_warn_for_invalid_lines(parse_dotenv_stream(stream), self.log):
|
390
387
|
if mapping.key is not None:
|
391
388
|
yield mapping.key, mapping.value
|
392
389
|
|
393
|
-
def get(self, key: str) -> str
|
390
|
+
def get(self, key: str) -> ta.Optional[str]:
|
394
391
|
data = self.dict()
|
395
392
|
|
396
393
|
if key in data:
|
397
394
|
return data[key]
|
398
395
|
|
399
396
|
if self.verbose:
|
400
|
-
log
|
397
|
+
if self.log is not None:
|
398
|
+
self.log.warning('Key %s not found in %s.', key, self.path)
|
401
399
|
|
402
400
|
return None
|
403
401
|
|
@@ -405,25 +403,32 @@ class DotEnv:
|
|
405
403
|
##
|
406
404
|
|
407
405
|
|
408
|
-
def
|
409
|
-
path:
|
406
|
+
def dotenv_get_key(
|
407
|
+
path: ta.Union[str, 'os.PathLike[str]'],
|
410
408
|
key_to_get: str,
|
411
409
|
*,
|
412
|
-
encoding: str
|
413
|
-
|
410
|
+
encoding: ta.Optional[str] = 'utf-8',
|
411
|
+
log: ta.Optional[logging.Logger] = None,
|
412
|
+
) -> ta.Optional[str]:
|
414
413
|
"""
|
415
414
|
Get the value of a given key from the given .env.
|
416
415
|
|
417
416
|
Returns `None` if the key isn't found or doesn't have a value.
|
418
417
|
"""
|
419
|
-
|
418
|
+
|
419
|
+
return Dotenv(
|
420
|
+
path,
|
421
|
+
verbose=True,
|
422
|
+
encoding=encoding,
|
423
|
+
log=log,
|
424
|
+
).get(key_to_get)
|
420
425
|
|
421
426
|
|
422
427
|
@contextlib.contextmanager
|
423
|
-
def
|
424
|
-
path:
|
425
|
-
encoding: str
|
426
|
-
) -> ta.Iterator[
|
428
|
+
def _dotenv_rewrite(
|
429
|
+
path: ta.Union[str, 'os.PathLike[str]'],
|
430
|
+
encoding: ta.Optional[str],
|
431
|
+
) -> ta.Iterator[ta.Tuple[ta.IO[str], ta.IO[str]]]:
|
427
432
|
pathlib.Path(path).touch()
|
428
433
|
|
429
434
|
with tempfile.NamedTemporaryFile(mode='w', encoding=encoding, delete=False) as dest:
|
@@ -441,21 +446,23 @@ def _rewrite(
|
|
441
446
|
raise error from None
|
442
447
|
|
443
448
|
|
444
|
-
def
|
445
|
-
path:
|
449
|
+
def dotenv_set_key(
|
450
|
+
path: ta.Union[str, 'os.PathLike[str]'],
|
446
451
|
key_to_set: str,
|
447
452
|
value_to_set: str,
|
448
453
|
*,
|
449
454
|
quote_mode: str = 'always',
|
450
455
|
export: bool = False,
|
451
|
-
encoding: str
|
452
|
-
|
456
|
+
encoding: ta.Optional[str] = 'utf-8',
|
457
|
+
log: ta.Optional[logging.Logger] = None,
|
458
|
+
) -> ta.Tuple[ta.Optional[bool], str, str]:
|
453
459
|
"""
|
454
460
|
Adds or Updates a key/value to the given .env
|
455
461
|
|
456
462
|
If the .env path given doesn't exist, fails instead of risking creating
|
457
463
|
an orphan .env somewhere in the filesystem
|
458
464
|
"""
|
465
|
+
|
459
466
|
if quote_mode not in ('always', 'auto', 'never'):
|
460
467
|
raise ValueError(f'Unknown quote_mode: {quote_mode}')
|
461
468
|
|
@@ -473,10 +480,10 @@ def set_key(
|
|
473
480
|
else:
|
474
481
|
line_out = f'{key_to_set}={value_out}\n'
|
475
482
|
|
476
|
-
with
|
483
|
+
with _dotenv_rewrite(path, encoding=encoding) as (source, dest):
|
477
484
|
replaced = False
|
478
485
|
missing_newline = False
|
479
|
-
for mapping in
|
486
|
+
for mapping in _dotenv_with_warn_for_invalid_lines(parse_dotenv_stream(source), log):
|
480
487
|
if mapping.key == key_to_set:
|
481
488
|
dest.write(line_out)
|
482
489
|
replaced = True
|
@@ -491,51 +498,55 @@ def set_key(
|
|
491
498
|
return True, key_to_set, value_to_set
|
492
499
|
|
493
500
|
|
494
|
-
def
|
495
|
-
path:
|
501
|
+
def dotenv_unset_key(
|
502
|
+
path: ta.Union[str, 'os.PathLike[str]'],
|
496
503
|
key_to_unset: str,
|
497
504
|
*,
|
498
505
|
quote_mode: str = 'always',
|
499
|
-
encoding: str
|
500
|
-
|
506
|
+
encoding: ta.Optional[str] = 'utf-8',
|
507
|
+
log: ta.Optional[logging.Logger] = None,
|
508
|
+
) -> ta.Tuple[ta.Optional[bool], str]:
|
501
509
|
"""
|
502
510
|
Removes a given key from the given `.env` file.
|
503
511
|
|
504
512
|
If the .env path given doesn't exist, fails.
|
505
513
|
If the given key doesn't exist in the .env, fails.
|
506
514
|
"""
|
515
|
+
|
507
516
|
if not os.path.exists(path):
|
508
|
-
log
|
517
|
+
if log is not None:
|
518
|
+
log.warning("Can't delete from %s - it doesn't exist.", path)
|
509
519
|
return None, key_to_unset
|
510
520
|
|
511
521
|
removed = False
|
512
|
-
with
|
513
|
-
for mapping in
|
522
|
+
with _dotenv_rewrite(path, encoding=encoding) as (source, dest):
|
523
|
+
for mapping in _dotenv_with_warn_for_invalid_lines(parse_dotenv_stream(source), log):
|
514
524
|
if mapping.key == key_to_unset:
|
515
525
|
removed = True
|
516
526
|
else:
|
517
527
|
dest.write(mapping.original.string)
|
518
528
|
|
519
529
|
if not removed:
|
520
|
-
log
|
530
|
+
if log is not None:
|
531
|
+
log.warning("Key %s not removed from %s - key doesn't exist.", key_to_unset, path)
|
521
532
|
return None, key_to_unset
|
522
533
|
|
523
534
|
return removed, key_to_unset
|
524
535
|
|
525
536
|
|
526
|
-
def
|
527
|
-
values: ta.Iterable[
|
537
|
+
def dotenv_resolve_variables(
|
538
|
+
values: ta.Iterable[ta.Tuple[str, ta.Optional[str]]],
|
528
539
|
override: bool,
|
529
540
|
env: ta.Mapping[str, str],
|
530
|
-
) ->
|
531
|
-
new_values:
|
541
|
+
) -> ta.Dict[str, ta.Optional[str]]:
|
542
|
+
new_values: ta.Dict[str, ta.Optional[str]] = {}
|
532
543
|
|
533
544
|
for (name, value) in values:
|
534
545
|
if value is None:
|
535
546
|
result = None
|
536
547
|
else:
|
537
|
-
atoms =
|
538
|
-
aenv:
|
548
|
+
atoms = parse_dotenv_variables(value)
|
549
|
+
aenv: ta.Dict[str, ta.Optional[str]] = {}
|
539
550
|
if override:
|
540
551
|
aenv.update(env)
|
541
552
|
aenv.update(new_values)
|
@@ -550,14 +561,15 @@ def resolve_variables(
|
|
550
561
|
|
551
562
|
|
552
563
|
def dotenv_values(
|
553
|
-
path:
|
554
|
-
stream: ta.IO[str]
|
564
|
+
path: ta.Union[str, 'os.PathLike[str]', None] = None,
|
565
|
+
stream: ta.Optional[ta.IO[str]] = None,
|
555
566
|
*,
|
556
567
|
verbose: bool = False,
|
557
568
|
interpolate: bool = True,
|
558
|
-
encoding: str
|
559
|
-
env: ta.Mapping[str, str]
|
560
|
-
|
569
|
+
encoding: ta.Optional[str] = 'utf-8',
|
570
|
+
env: ta.Optional[ta.Mapping[str, str]] = None,
|
571
|
+
log: ta.Optional[logging.Logger] = None,
|
572
|
+
) -> ta.Dict[str, ta.Optional[str]]:
|
561
573
|
"""
|
562
574
|
Parse a .env file and return its content as a dict.
|
563
575
|
|
@@ -574,10 +586,11 @@ def dotenv_values(
|
|
574
586
|
If both `path` and `stream` are `None`, `find_dotenv()` is used to find the
|
575
587
|
.env file.
|
576
588
|
"""
|
589
|
+
|
577
590
|
if path is None and stream is None:
|
578
591
|
raise ValueError('must set path or stream')
|
579
592
|
|
580
|
-
return
|
593
|
+
return Dotenv(
|
581
594
|
path=path,
|
582
595
|
stream=stream,
|
583
596
|
verbose=verbose,
|
@@ -585,4 +598,5 @@ def dotenv_values(
|
|
585
598
|
override=True,
|
586
599
|
encoding=encoding,
|
587
600
|
env=env,
|
601
|
+
log=log,
|
588
602
|
).dict()
|