omlish 0.0.0.dev130__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 CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev130'
2
- __revision__ = '04fd1bb83aa7700c60be8dc97b6530ddf15b6dd7'
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.46',
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
- log = logging.getLogger(__name__)
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 Atom(metaclass=abc.ABCMeta):
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 | None]) -> str: ...
63
+ def resolve(self, env: ta.Mapping[str, ta.Optional[str]]) -> str: ...
68
64
 
69
65
 
70
- class Literal(Atom):
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'Literal(value={self.value})'
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 | None]) -> str:
82
+ def resolve(self, env: ta.Mapping[str, ta.Optional[str]]) -> str:
87
83
  return self.value
88
84
 
89
85
 
90
- class Variable(Atom):
91
- def __init__(self, name: str, default: str | None) -> None:
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'Variable(name={self.name}, default={self.default})'
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 | None]) -> 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 parse_variables(value: str) -> ta.Iterator[Atom]:
109
+ def parse_dotenv_variables(value: str) -> ta.Iterator[DotenvAtom]:
114
110
  cursor = 0
115
111
 
116
- for match in _posix_variable.finditer(value):
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 Literal(value=value[cursor:start])
118
+ yield DotenvLiteral(value=value[cursor:start])
123
119
 
124
- yield Variable(name=name, default=default)
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 Literal(value=value[cursor:length])
125
+ yield DotenvLiteral(value=value[cursor:length])
130
126
 
131
127
 
132
128
  ##
133
129
 
134
130
 
135
- def _make_regex(string: str, extra_flags: int = 0) -> ta.Pattern[str]:
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
- _newline = _make_regex(r'(\r\n|\n|\r)')
140
- _multiline_whitespace = _make_regex(r'\s*', extra_flags=re.MULTILINE)
141
- _whitespace = _make_regex(r'[^\S\r\n]*')
142
- _export = _make_regex(r'(?:export[^\S\r\n]+)?')
143
- _single_quoted_key = _make_regex(r"'([^']+)'")
144
- _unquoted_key = _make_regex(r'([^=\#\s]+)')
145
- _equal_sign = _make_regex(r'(=[^\S\r\n]*)')
146
- _single_quoted_value = _make_regex(r"'((?:\\'|[^'])*)'")
147
- _double_quoted_value = _make_regex(r'"((?:\\"|[^"])*)"')
148
- _unquoted_value = _make_regex(r'([^\r\n]*)')
149
- _comment = _make_regex(r'(?:[^\S\r\n]*#[^\r\n]*)?')
150
- _end_of_line = _make_regex(r'[^\S\r\n]*(?:\r\n|\n|\r|$)')
151
- _rest_of_line = _make_regex(r'[^\r\n]*(?:\r|\n|\r\n)?')
152
- _double_quote_escapes = _make_regex(r"\\[\\'\"abfnrtv]")
153
- _single_quote_escapes = _make_regex(r"\\[\\']")
154
-
155
-
156
- class Original(ta.NamedTuple):
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 Binding(ta.NamedTuple):
162
- key: str | None
163
- value: str | None
164
- original: 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 _Position:
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) -> '_Position':
171
+ def start(cls) -> '_DotenvPosition':
176
172
  return cls(chars=0, line=1)
177
173
 
178
- def set(self, other: '_Position') -> None:
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(_newline, string))
180
+ self.line += len(re.findall(_dotenv_newline_pat, string))
185
181
 
186
182
 
187
- class Error(Exception):
183
+ class DotenvError(Exception):
188
184
  pass
189
185
 
190
186
 
191
- class _Reader:
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 = _Position.start()
196
- self.mark = _Position.start()
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) -> Original:
205
- return Original(
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 Error('read: End of string')
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 Error('read_regex: Pattern not found')
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 _decode_escapes(regex: ta.Pattern[str], string: str) -> str:
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 _parse_key(reader: _Reader) -> str | None:
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(_single_quoted_key)
236
+ (key,) = reader.read_regex(_dotenv_single_quoted_key_pat)
241
237
  else:
242
- (key,) = reader.read_regex(_unquoted_key)
238
+ (key,) = reader.read_regex(_dotenv_unquoted_key_pat)
243
239
  return key
244
240
 
245
241
 
246
- def _parse_unquoted_value(reader: _Reader) -> str:
247
- (part,) = reader.read_regex(_unquoted_value)
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 _parse_value(reader: _Reader) -> str:
247
+ def _parse_dotenv_value(reader: _DotenvReader) -> str:
252
248
  char = reader.peek(1)
253
249
  if char == "'":
254
- (value,) = reader.read_regex(_single_quoted_value)
255
- return _decode_escapes(_single_quote_escapes, value)
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(_double_quoted_value)
258
- return _decode_escapes(_double_quote_escapes, value)
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 _parse_unquoted_value(reader)
258
+ return _parse_dotenv_unquoted_value(reader)
263
259
 
264
260
 
265
- def _parse_binding(reader: _Reader) -> Binding:
261
+ def _parse_dotenv_binding(reader: _DotenvReader) -> DotenvBinding:
266
262
  reader.set_mark()
267
263
  try:
268
- reader.read_regex(_multiline_whitespace)
264
+ reader.read_regex(_dotenv_multiline_whitespace_pat)
269
265
  if not reader.has_next():
270
- return Binding(
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(_export)
277
- key = _parse_key(reader)
278
- reader.read_regex(_whitespace)
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(_equal_sign)
281
- value: str | None = _parse_value(reader)
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(_comment)
285
- reader.read_regex(_end_of_line)
286
- return Binding(
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 Error:
293
- reader.read_regex(_rest_of_line)
294
- return Binding(
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 parse_stream(stream: ta.IO[str]) -> ta.Iterator[Binding]:
303
- reader = _Reader(stream)
298
+ def parse_dotenv_stream(stream: ta.IO[str]) -> ta.Iterator[DotenvBinding]:
299
+ reader = _DotenvReader(stream)
304
300
  while reader.has_next():
305
- yield _parse_binding(reader)
301
+ yield _parse_dotenv_binding(reader)
306
302
 
307
303
 
308
304
  ##
309
305
 
310
306
 
311
- # A type alias for a string path to be used for the paths in this file. These paths may flow to `open()` and
312
- # `shutil.move()`; `shutil.move()` only accepts string paths, not byte paths or file descriptors. See
313
- # https://github.com/python/typeshed/pull/6832.
314
- StrPath: ta.TypeAlias = ta.Union[str, 'os.PathLike[str]']
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.warning(
321
- 'dotenv could not parse statement starting at line %s',
322
- mapping.original.line,
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
- StrMutableMappingT = ta.TypeVar('StrMutableMappingT', bound=ta.MutableMapping[str, str])
321
+ StrStrMutableMappingT = ta.TypeVar('StrStrMutableMappingT', bound=ta.MutableMapping[str, str])
328
322
 
329
323
 
330
- class DotEnv:
324
+ class Dotenv:
331
325
  def __init__(
332
326
  self,
333
- path: StrPath | None = None,
334
- stream: ta.IO[str] | None = None,
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 | None = None,
330
+ encoding: ta.Optional[str] = None,
337
331
  interpolate: bool = True,
338
332
  override: bool = True,
339
- env: ta.Mapping[str, str] | None = None,
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: StrPath | None = path
343
- self.stream: ta.IO[str] | None = stream
344
- self._dict: dict[str, str | None] | None = None
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 | None = encoding
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.info(
361
- 'dotenv could not find configuration file %s.',
362
- self.path or '.env',
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) -> dict[str, str | None]:
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 = resolve_variables(raw_values, override=self.override, env=self.env)
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: StrMutableMappingT) -> StrMutableMappingT:
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[tuple[str, str | None]]:
384
+ def parse(self) -> ta.Iterator[ta.Tuple[str, ta.Optional[str]]]:
388
385
  with self._get_stream() as stream:
389
- for mapping in _with_warn_for_invalid_lines(parse_stream(stream)):
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 | None:
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.warning('Key %s not found in %s.', key, self.path)
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 get_key(
409
- path: StrPath,
406
+ def dotenv_get_key(
407
+ path: ta.Union[str, 'os.PathLike[str]'],
410
408
  key_to_get: str,
411
409
  *,
412
- encoding: str | None = 'utf-8',
413
- ) -> str | None:
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
- return DotEnv(path, verbose=True, encoding=encoding).get(key_to_get)
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 _rewrite(
424
- path: StrPath,
425
- encoding: str | None,
426
- ) -> ta.Iterator[tuple[ta.IO[str], ta.IO[str]]]:
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 set_key(
445
- path: StrPath,
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 | None = 'utf-8',
452
- ) -> tuple[bool | None, str, str]:
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 _rewrite(path, encoding=encoding) as (source, dest):
483
+ with _dotenv_rewrite(path, encoding=encoding) as (source, dest):
477
484
  replaced = False
478
485
  missing_newline = False
479
- for mapping in _with_warn_for_invalid_lines(parse_stream(source)):
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 unset_key(
495
- path: StrPath,
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 | None = 'utf-8',
500
- ) -> tuple[bool | None, str]:
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.warning("Can't delete from %s - it doesn't exist.", path)
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 _rewrite(path, encoding=encoding) as (source, dest):
513
- for mapping in _with_warn_for_invalid_lines(parse_stream(source)):
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.warning("Key %s not removed from %s - key doesn't exist.", key_to_unset, path)
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 resolve_variables(
527
- values: ta.Iterable[tuple[str, str | None]],
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
- ) -> dict[str, str | None]:
531
- new_values: dict[str, str | None] = {}
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 = parse_variables(value)
538
- aenv: dict[str, str | None] = {}
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: StrPath | None = None,
554
- stream: ta.IO[str] | None = None,
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 | None = 'utf-8',
559
- env: ta.Mapping[str, str] | None = None,
560
- ) -> dict[str, str | None]:
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 DotEnv(
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()
omlish/inject/__init__.py CHANGED
@@ -106,7 +106,7 @@ from .scopes import ( # noqa
106
106
  ScopeSeededProvider,
107
107
  SeededScope,
108
108
  Singleton,
109
- Thread,
109
+ ThreadScope,
110
110
  bind_scope,
111
111
  bind_scope_seed,
112
112
  enter_seeded_scope,
@@ -33,7 +33,7 @@ from ..listeners import ProvisionListener
33
33
  from ..listeners import ProvisionListenerBinding
34
34
  from ..scopes import ScopeBinding
35
35
  from ..scopes import Singleton
36
- from ..scopes import Thread
36
+ from ..scopes import ThreadScope
37
37
  from ..types import Scope
38
38
  from ..types import Unscoped
39
39
  from .elements import ElementCollection
@@ -48,7 +48,7 @@ log = logging.getLogger(__name__)
48
48
  DEFAULT_SCOPES: list[Scope] = [
49
49
  Unscoped(),
50
50
  Singleton(),
51
- Thread(),
51
+ ThreadScope(),
52
52
  ]
53
53
 
54
54
 
@@ -24,7 +24,7 @@ from ..providers import Provider
24
24
  from ..scopes import ScopeSeededProvider
25
25
  from ..scopes import SeededScope
26
26
  from ..scopes import Singleton
27
- from ..scopes import Thread
27
+ from ..scopes import ThreadScope
28
28
  from ..types import Scope
29
29
  from ..types import Unscoped
30
30
  from .bindings import BindingImpl
@@ -86,8 +86,8 @@ class ThreadScopeImpl(ScopeImpl, lang.Final):
86
86
  self._local = threading.local()
87
87
 
88
88
  @property
89
- def scope(self) -> Thread:
90
- return Thread()
89
+ def scope(self) -> ThreadScope:
90
+ return ThreadScope()
91
91
 
92
92
  def provide(self, binding: BindingImpl, injector: Injector) -> ta.Any:
93
93
  dct: dict[BindingImpl, ta.Any]
@@ -190,7 +190,7 @@ class SeededScopeImpl(ScopeImpl):
190
190
  SCOPE_IMPLS_BY_SCOPE: dict[type[Scope], ta.Callable[..., ScopeImpl]] = {
191
191
  Unscoped: lambda _: UnscopedScopeImpl(),
192
192
  Singleton: lambda _: SingletonScopeImpl(),
193
- Thread: lambda _: ThreadScopeImpl(),
193
+ ThreadScope: lambda _: ThreadScopeImpl(),
194
194
  SeededScope: lambda s: SeededScopeImpl(s),
195
195
  }
196
196
 
omlish/inject/scopes.py CHANGED
@@ -49,11 +49,11 @@ SCOPE_ALIASES['singleton'] = Singleton()
49
49
  ##
50
50
 
51
51
 
52
- class Thread(Scope, lang.Singleton, lang.Final):
52
+ class ThreadScope(Scope, lang.Singleton, lang.Final):
53
53
  pass
54
54
 
55
55
 
56
- SCOPE_ALIASES['thread'] = Thread()
56
+ SCOPE_ALIASES['thread'] = ThreadScope()
57
57
 
58
58
 
59
59
  ##
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: omlish
3
- Version: 0.0.0.dev130
3
+ Version: 0.0.0.dev131
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -39,7 +39,7 @@ Requires-Dist: pymysql~=1.1; extra == "all"
39
39
  Requires-Dist: aiomysql~=0.2; extra == "all"
40
40
  Requires-Dist: aiosqlite~=0.20; extra == "all"
41
41
  Requires-Dist: asyncpg~=0.30; extra == "all"
42
- Requires-Dist: apsw~=3.46; extra == "all"
42
+ Requires-Dist: apsw~=3.47; extra == "all"
43
43
  Requires-Dist: sqlean.py~=3.45; extra == "all"
44
44
  Requires-Dist: duckdb~=1.1; extra == "all"
45
45
  Requires-Dist: pytest~=8.0; extra == "all"
@@ -85,7 +85,7 @@ Requires-Dist: pymysql~=1.1; extra == "sqldrivers"
85
85
  Requires-Dist: aiomysql~=0.2; extra == "sqldrivers"
86
86
  Requires-Dist: aiosqlite~=0.20; extra == "sqldrivers"
87
87
  Requires-Dist: asyncpg~=0.30; extra == "sqldrivers"
88
- Requires-Dist: apsw~=3.46; extra == "sqldrivers"
88
+ Requires-Dist: apsw~=3.47; extra == "sqldrivers"
89
89
  Requires-Dist: sqlean.py~=3.45; extra == "sqldrivers"
90
90
  Requires-Dist: duckdb~=1.1; extra == "sqldrivers"
91
91
  Provides-Extra: testing
@@ -1,5 +1,5 @@
1
1
  omlish/.manifests.json,sha256=CxGnj-UiRPlZgmgWoovDWrOnqpSEmBy_kqA7cdfSA3w,1431
2
- omlish/__about__.py,sha256=PvbOOiIxO1ksGKnYclis2IJG4h8EtRZ-Qy1gtTkvgI4,3379
2
+ omlish/__about__.py,sha256=d7BMVkW2N3aBZ6Tdsm0Fd2b0CcKLA6TPpoZPJHMAGDM,3379
3
3
  omlish/__init__.py,sha256=SsyiITTuK0v74XpKV8dqNaCmjOlan1JZKrHQv5rWKPA,253
4
4
  omlish/argparse.py,sha256=cqKGAqcxuxv_s62z0gq29L9KAvg_3-_rFvXKjVpRJjo,8126
5
5
  omlish/c3.py,sha256=ubu7lHwss5V4UznbejAI0qXhXahrU01MysuHOZI9C4U,8116
@@ -178,7 +178,7 @@ omlish/docker/helpers.py,sha256=9uyHpPVbsB2jqTzvU7jiLzTkDN1omqofse1w4B4GH5E,612
178
178
  omlish/docker/hub.py,sha256=7LIuJGdA-N1Y1dmo50ynKM1KUTcnQM_5XbtPbdT_QLU,3940
179
179
  omlish/docker/manifests.py,sha256=LR4FpOGNUT3bZQ-gTjB6r_-1C3YiG30QvevZjrsVUQM,7068
180
180
  omlish/formats/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
181
- omlish/formats/dotenv.py,sha256=orf37G11RM6Z6MxjBWbFAntwT8fi-ptkjN4EWoTeAsA,17689
181
+ omlish/formats/dotenv.py,sha256=qoDG4Ayu7B-8LjBBhcmNiLZW0_9LgCi3Ri2aPo9DEQ8,19314
182
182
  omlish/formats/props.py,sha256=cek3JLFLIrpE76gvs8rs_B8yF4SpY8ooDH8apWsquwE,18953
183
183
  omlish/formats/xml.py,sha256=ggiOwSERt4d9XmZwLZiDIh5qnFJS4jdmow9m9_9USps,1491
184
184
  omlish/formats/yaml.py,sha256=wTW8ECG9jyA7qIFUqKZUro4KAKpN4IvcW_qhlrKveXM,6836
@@ -232,7 +232,7 @@ omlish/http/multipart.py,sha256=R9ycpHsXRcmh0uoc43aYb7BdWL-8kSQHe7J-M81aQZM,2240
232
232
  omlish/http/sessions.py,sha256=VZ_WS5uiQG5y7i3u8oKuQMqf8dPKUOjFm_qk_0OvI8c,4793
233
233
  omlish/http/sse.py,sha256=MDs9RvxQXoQliImcc6qK1ERajEYM7Q1l8xmr-9ceNBc,2315
234
234
  omlish/http/wsgi.py,sha256=czZsVUX-l2YTlMrUjKN49wRoP4rVpS0qpeBn4O5BoMY,948
235
- omlish/inject/__init__.py,sha256=JQ7x8l9MjU-kJ5ap7cPVq7SY7zbbCIrjyJAF0UeE5-s,1886
235
+ omlish/inject/__init__.py,sha256=n0RC9UDGsBQQ39cST39-XJqJPq2M0tnnh9yJubW9azo,1891
236
236
  omlish/inject/binder.py,sha256=DAbc8TZi5w8Mna0TUtq0mT4jeDVA7i7SlBtOFrh2swc,4185
237
237
  omlish/inject/bindings.py,sha256=pLXn2U3kvmAS-68IOG-tr77DbiI-wp9hGyy4lhG6_H8,525
238
238
  omlish/inject/eagers.py,sha256=5AkGYuwijG0ihsH9NSaZotggalJ5_xWXhHE9mkn6IBA,329
@@ -248,20 +248,20 @@ omlish/inject/origins.py,sha256=OVQkiuRxx6ZtE8ZliufdndtFexcfpj-wZSDkUeGUCYM,534
248
248
  omlish/inject/overrides.py,sha256=hrm243slCw_DDRbn3dK5QK1jfHezVokG-WYO2JaQOV8,535
249
249
  omlish/inject/privates.py,sha256=hZOa_keY3KlXAzyiZ-sfN697UKXpkfXXNUIEmGT5TAA,641
250
250
  omlish/inject/providers.py,sha256=Z6UzNCwRhKHHR0L5CyBMo4F-1M_xElLkPA6EKQWcqlw,754
251
- omlish/inject/scopes.py,sha256=0b8Y0VidkpEk7tyxKSbv_1fE-2LomKTYlhFeoEemkwg,1979
251
+ omlish/inject/scopes.py,sha256=bxbpEPqRs9N61GDKD-4ZWXkB6xiLDrILLjcE2IvZEtM,1989
252
252
  omlish/inject/types.py,sha256=11WVEPkZ-_8cv1BeTDRU-soIYxB_6x7dyWtsa2Iej9U,251
253
253
  omlish/inject/utils.py,sha256=_UOZqA8IcLWPqf4Mcg9iIusQ5yxP_6Txg6PWtUYl23o,408
254
254
  omlish/inject/impl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
255
255
  omlish/inject/impl/bindings.py,sha256=8H586RCgmvwq53XBL9WMbb-1-Tdw_hh9zxIDCwcHA1c,414
256
256
  omlish/inject/impl/elements.py,sha256=bJBbHce_eZyIua2wbcejMwd9Uv-QeYcQ-c5N1qOXSmU,5950
257
- omlish/inject/impl/injector.py,sha256=WxIVOF6zvipb44302_j-xQZt8vnNCY0gdLX6UWzslXI,7550
257
+ omlish/inject/impl/injector.py,sha256=8Pm2TbI-ySfibpHG2kFeF1nBHkh5v-NEynykljkQ8ew,7560
258
258
  omlish/inject/impl/inspect.py,sha256=J0d2HJ-Z2-cHD4mJ0Kf5oJCOa2bMVG68Oh0Mhe3Cay4,3120
259
259
  omlish/inject/impl/multis.py,sha256=rRIWNCiTGaSWQUz_jxEy8LUmzdJDAlG94sLHYDS-ncg,2048
260
260
  omlish/inject/impl/origins.py,sha256=-cdcwz3BWb5LuC9Yn5ynYOwyPsKH06-kCc-3U0PxZ5w,1640
261
261
  omlish/inject/impl/privates.py,sha256=alpCYyk5VJ9lJknbRH2nLVNFYVvFhkj-VC1Vco3zCFQ,2613
262
262
  omlish/inject/impl/providers.py,sha256=QnwhsujJFIHC0JTgd2Wlo1kP53i3CWTrj1nKU2DNxwg,2375
263
263
  omlish/inject/impl/proxy.py,sha256=1ko0VaKqzu9UG8bIldp9xtUrAVUOFTKWKTjOCqIGr4s,1636
264
- omlish/inject/impl/scopes.py,sha256=ASfULXgP_ETlsAqFJfrZmyEaZt64Zr8tNn5ScA-EoXk,5900
264
+ omlish/inject/impl/scopes.py,sha256=hKnzNieB-fJSFEXDP_QG1mCfIKoVFIfFlf9LiIt5tk4,5920
265
265
  omlish/io/__init__.py,sha256=aaIEsXTSfytW-oEkUWczdUJ_ifFY7ihIpyidIbfjkwY,56
266
266
  omlish/io/_abc.py,sha256=Cxs8KB1B_69rxpUYxI-MTsilAmNooJJn3w07DKqYKkE,1255
267
267
  omlish/io/pyio.py,sha256=YB3g6yg64MzcFwbzKBo4adnbsbZ3FZMlOZfjNtWmYoc,95316
@@ -488,9 +488,9 @@ omlish/text/glyphsplit.py,sha256=Ug-dPRO7x-OrNNr8g1y6DotSZ2KH0S-VcOmUobwa4B0,329
488
488
  omlish/text/indent.py,sha256=6Jj6TFY9unaPa4xPzrnZemJ-fHsV53IamP93XGjSUHs,1274
489
489
  omlish/text/parts.py,sha256=7vPF1aTZdvLVYJ4EwBZVzRSy8XB3YqPd7JwEnNGGAOo,6495
490
490
  omlish/text/random.py,sha256=jNWpqiaKjKyTdMXC-pWAsSC10AAP-cmRRPVhm59ZWLk,194
491
- omlish-0.0.0.dev130.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
492
- omlish-0.0.0.dev130.dist-info/METADATA,sha256=1UOC8pu3kIMCWi1vb1n83h4VZjHWkmZpcmwpEzxERlk,4173
493
- omlish-0.0.0.dev130.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
494
- omlish-0.0.0.dev130.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
495
- omlish-0.0.0.dev130.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
496
- omlish-0.0.0.dev130.dist-info/RECORD,,
491
+ omlish-0.0.0.dev131.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
492
+ omlish-0.0.0.dev131.dist-info/METADATA,sha256=V7J6J12IhBiLRFrCaAkvQgvB1VPvsZSzu32VsYQGSdE,4173
493
+ omlish-0.0.0.dev131.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
494
+ omlish-0.0.0.dev131.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
495
+ omlish-0.0.0.dev131.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
496
+ omlish-0.0.0.dev131.dist-info/RECORD,,