omlish 0.0.0.dev218__py3-none-any.whl → 0.0.0.dev220__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. omlish/__about__.py +3 -3
  2. omlish/antlr/dot.py +13 -6
  3. omlish/dataclasses/__init__.py +1 -0
  4. omlish/dataclasses/impl/api.py +10 -0
  5. omlish/dataclasses/impl/fields.py +8 -1
  6. omlish/dataclasses/impl/init.py +1 -1
  7. omlish/dataclasses/impl/main.py +1 -1
  8. omlish/dataclasses/impl/metaclass.py +112 -29
  9. omlish/dataclasses/impl/overrides.py +53 -0
  10. omlish/dataclasses/impl/params.py +3 -0
  11. omlish/dataclasses/impl/reflect.py +17 -5
  12. omlish/dataclasses/impl/simple.py +0 -42
  13. omlish/http/coro/server.py +60 -40
  14. omlish/http/handlers.py +15 -0
  15. omlish/http/sessions.py +1 -1
  16. omlish/http/simple.py +101 -0
  17. omlish/io/fileno.py +11 -0
  18. omlish/lang/__init__.py +4 -1
  19. omlish/lang/cached.py +0 -1
  20. omlish/lang/classes/__init__.py +3 -1
  21. omlish/lang/classes/abstract.py +14 -1
  22. omlish/lang/classes/restrict.py +5 -5
  23. omlish/lang/classes/virtual.py +0 -1
  24. omlish/lang/clsdct.py +0 -1
  25. omlish/lang/contextmanagers.py +0 -8
  26. omlish/lang/descriptors.py +0 -1
  27. omlish/lang/maybes.py +0 -1
  28. omlish/lang/objects.py +0 -2
  29. omlish/secrets/__init__.py +0 -24
  30. omlish/secrets/all.py +16 -0
  31. omlish/secrets/secrets.py +6 -0
  32. omlish/secrets/ssl.py +9 -0
  33. omlish/secrets/tempssl.py +50 -0
  34. omlish/sockets/addresses.py +22 -10
  35. omlish/sockets/bind.py +26 -20
  36. omlish/sockets/handlers.py +7 -0
  37. omlish/sockets/server/handlers.py +39 -4
  38. omlish/sockets/server/server.py +25 -4
  39. omlish/sql/alchemy/secrets.py +1 -1
  40. omlish/sql/dbs.py +1 -1
  41. {omlish-0.0.0.dev218.dist-info → omlish-0.0.0.dev220.dist-info}/METADATA +3 -3
  42. {omlish-0.0.0.dev218.dist-info → omlish-0.0.0.dev220.dist-info}/RECORD +46 -40
  43. {omlish-0.0.0.dev218.dist-info → omlish-0.0.0.dev220.dist-info}/LICENSE +0 -0
  44. {omlish-0.0.0.dev218.dist-info → omlish-0.0.0.dev220.dist-info}/WHEEL +0 -0
  45. {omlish-0.0.0.dev218.dist-info → omlish-0.0.0.dev220.dist-info}/entry_points.txt +0 -0
  46. {omlish-0.0.0.dev218.dist-info → omlish-0.0.0.dev220.dist-info}/top_level.txt +0 -0
@@ -65,7 +65,7 @@ import typing as ta
65
65
 
66
66
  from ...lite.check import check
67
67
  from ...sockets.addresses import SocketAddress
68
- from ...sockets.handlers import SocketHandler # noqa
68
+ from ...sockets.handlers import SocketHandler_
69
69
  from ...sockets.io import SocketIoPair
70
70
  from ..handlers import HttpHandler
71
71
  from ..handlers import HttpHandlerRequest
@@ -205,6 +205,10 @@ class CoroHttpServer:
205
205
  return h
206
206
  return None
207
207
 
208
+ def close(self) -> None:
209
+ if isinstance(d := self.data, HttpHandlerResponseStreamedData):
210
+ d.close()
211
+
208
212
  #
209
213
 
210
214
  def _build_response_head_bytes(self, a: _Response) -> bytes:
@@ -420,35 +424,45 @@ class CoroHttpServer:
420
424
  while True:
421
425
  gen = self.coro_handle_one()
422
426
 
423
- o = next(gen)
424
427
  i: ta.Optional[bytes]
428
+ o: ta.Any = next(gen)
425
429
  while True:
426
- if isinstance(o, self.AnyLogIo):
427
- i = None
428
- yield o
430
+ try:
431
+ if isinstance(o, self.AnyLogIo):
432
+ i = None
433
+ yield o
429
434
 
430
- elif isinstance(o, self.AnyReadIo):
431
- i = check.isinstance((yield o), bytes)
435
+ elif isinstance(o, self.AnyReadIo):
436
+ i = check.isinstance((yield o), bytes)
432
437
 
433
- elif isinstance(o, self._Response):
434
- i = None
438
+ elif isinstance(o, self._Response):
439
+ i = None
435
440
 
436
- r = self._preprocess_response(o)
437
- hb = self._build_response_head_bytes(r)
438
- check.none((yield self.WriteIo(hb)))
441
+ r = self._preprocess_response(o)
442
+ hb = self._build_response_head_bytes(r)
443
+ check.none((yield self.WriteIo(hb)))
439
444
 
440
- for b in self._yield_response_data(r):
441
- yield self.WriteIo(b)
445
+ for b in self._yield_response_data(r):
446
+ yield self.WriteIo(b)
442
447
 
443
- else:
444
- raise TypeError(o)
448
+ o.close()
449
+ o = None
445
450
 
446
- try:
447
- o = gen.send(i)
448
- except EOFError:
449
- return
450
- except StopIteration:
451
- break
451
+ else:
452
+ raise TypeError(o) # noqa
453
+
454
+ try:
455
+ o = gen.send(i)
456
+ except EOFError:
457
+ return
458
+ except StopIteration:
459
+ break
460
+
461
+ except Exception: # noqa
462
+ if hasattr(o, 'close'):
463
+ o.close()
464
+
465
+ raise
452
466
 
453
467
  def coro_handle_one(self) -> ta.Generator[
454
468
  ta.Union[AnyLogIo, AnyReadIo, _Response],
@@ -530,34 +544,40 @@ class CoroHttpServer:
530
544
  yield self._build_error_response(err)
531
545
  return
532
546
 
533
- # Build internal response
547
+ try:
548
+ # Build internal response
534
549
 
535
- response_headers = handler_response.headers or {}
536
- response_data = handler_response.data
550
+ response_headers = handler_response.headers or {}
551
+ response_data = handler_response.data
537
552
 
538
- headers: ta.List[CoroHttpServer._Header] = [
539
- *self._make_default_headers(),
540
- ]
553
+ headers: ta.List[CoroHttpServer._Header] = [
554
+ *self._make_default_headers(),
555
+ ]
541
556
 
542
- for k, v in response_headers.items():
543
- headers.append(self._Header(k, v))
557
+ for k, v in response_headers.items():
558
+ headers.append(self._Header(k, v))
544
559
 
545
- if handler_response.close_connection and 'Connection' not in headers:
546
- headers.append(self._Header('Connection', 'close'))
560
+ if handler_response.close_connection and 'Connection' not in headers:
561
+ headers.append(self._Header('Connection', 'close'))
547
562
 
548
- yield self._Response(
549
- version=parsed.version,
550
- code=http.HTTPStatus(handler_response.status),
551
- headers=headers,
552
- data=response_data,
553
- close_connection=handler_response.close_connection,
554
- )
563
+ yield self._Response(
564
+ version=parsed.version,
565
+ code=http.HTTPStatus(handler_response.status),
566
+ headers=headers,
567
+ data=response_data,
568
+ close_connection=handler_response.close_connection,
569
+ )
570
+
571
+ except Exception: # noqa
572
+ handler_response.close()
573
+
574
+ raise
555
575
 
556
576
 
557
577
  ##
558
578
 
559
579
 
560
- class CoroHttpServerSocketHandler: # SocketHandler
580
+ class CoroHttpServerSocketHandler(SocketHandler_):
561
581
  def __init__(
562
582
  self,
563
583
  server_factory: CoroHttpServerFactory,
omlish/http/handlers.py CHANGED
@@ -1,5 +1,6 @@
1
1
  # ruff: noqa: UP006 UP007
2
2
  # @omlish-lite
3
+ import abc
3
4
  import dataclasses as dc
4
5
  import http.server
5
6
  import typing as ta
@@ -32,12 +33,20 @@ class HttpHandlerResponse:
32
33
  data: ta.Optional[HttpHandlerResponseData] = None
33
34
  close_connection: ta.Optional[bool] = None
34
35
 
36
+ def close(self) -> None:
37
+ if isinstance(d := self.data, HttpHandlerResponseStreamedData):
38
+ d.close()
39
+
35
40
 
36
41
  @dc.dataclass(frozen=True)
37
42
  class HttpHandlerResponseStreamedData:
38
43
  iter: ta.Iterable[bytes]
39
44
  length: ta.Optional[int] = None
40
45
 
46
+ def close(self) -> None:
47
+ if hasattr(d := self.iter, 'close'):
48
+ d.close() # noqa
49
+
41
50
 
42
51
  class HttpHandlerError(Exception):
43
52
  pass
@@ -45,3 +54,9 @@ class HttpHandlerError(Exception):
45
54
 
46
55
  class UnsupportedMethodHttpHandlerError(Exception):
47
56
  pass
57
+
58
+
59
+ class HttpHandler_(abc.ABC): # noqa
60
+ @abc.abstractmethod
61
+ def __call__(self, req: HttpHandlerRequest) -> HttpHandlerResponse:
62
+ raise NotImplementedError
omlish/http/sessions.py CHANGED
@@ -9,8 +9,8 @@ import typing as ta
9
9
  import zlib
10
10
 
11
11
  from .. import lang
12
- from .. import secrets as sec
13
12
  from ..funcs import pairs as fpa
13
+ from ..secrets import all as sec
14
14
  from .cookies import dump_cookie
15
15
  from .cookies import parse_cookie
16
16
  from .json import JSON_TAGGER
omlish/http/simple.py ADDED
@@ -0,0 +1,101 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ import concurrent.futures as cf
4
+ import contextlib
5
+ import functools
6
+ import typing as ta
7
+
8
+ from ..lite.check import check
9
+ from ..sockets.addresses import SocketAndAddress
10
+ from ..sockets.bind import CanSocketBinder
11
+ from ..sockets.bind import SocketBinder
12
+ from ..sockets.server.handlers import ExecutorSocketServerHandler
13
+ from ..sockets.server.handlers import SocketHandlerSocketServerHandler
14
+ from ..sockets.server.handlers import SocketServerHandler
15
+ from ..sockets.server.handlers import SocketWrappingSocketServerHandler
16
+ from ..sockets.server.handlers import StandardSocketServerHandler
17
+ from ..sockets.server.server import SocketServer
18
+ from ..sockets.server.threading import ThreadingSocketServerHandler
19
+ from .coro.server import CoroHttpServer
20
+ from .coro.server import CoroHttpServerSocketHandler
21
+ from .handlers import HttpHandler
22
+ from .parsing import HttpRequestParser
23
+ from .versions import HttpProtocolVersion
24
+ from .versions import HttpProtocolVersions
25
+
26
+
27
+ if ta.TYPE_CHECKING:
28
+ import ssl
29
+
30
+
31
+ @contextlib.contextmanager
32
+ def make_simple_http_server(
33
+ bind: CanSocketBinder,
34
+ handler: HttpHandler,
35
+ *,
36
+ server_version: HttpProtocolVersion = HttpProtocolVersions.HTTP_1_1,
37
+ ssl_context: ta.Optional['ssl.SSLContext'] = None,
38
+ executor: ta.Optional[cf.Executor] = None,
39
+ use_threads: bool = False,
40
+ ) -> ta.Iterator[SocketServer]:
41
+ check.arg(not (executor is not None and use_threads))
42
+
43
+ #
44
+
45
+ with contextlib.ExitStack() as es:
46
+ server_factory = functools.partial(
47
+ CoroHttpServer,
48
+ handler=handler,
49
+ parser=HttpRequestParser(
50
+ server_version=server_version,
51
+ ),
52
+ )
53
+
54
+ socket_handler = CoroHttpServerSocketHandler(
55
+ server_factory,
56
+ )
57
+
58
+ #
59
+
60
+ server_handler: SocketServerHandler = SocketHandlerSocketServerHandler(
61
+ socket_handler,
62
+ )
63
+
64
+ #
65
+
66
+ if ssl_context is not None:
67
+ server_handler = SocketWrappingSocketServerHandler(
68
+ server_handler,
69
+ SocketAndAddress.socket_wrapper(functools.partial(
70
+ ssl_context.wrap_socket,
71
+ server_side=True,
72
+ )),
73
+ )
74
+
75
+ #
76
+
77
+ server_handler = StandardSocketServerHandler(
78
+ server_handler,
79
+ )
80
+
81
+ #
82
+
83
+ if executor is not None:
84
+ server_handler = ExecutorSocketServerHandler(
85
+ server_handler,
86
+ executor,
87
+ )
88
+
89
+ elif use_threads:
90
+ server_handler = es.enter_context(ThreadingSocketServerHandler(
91
+ server_handler,
92
+ ))
93
+
94
+ #
95
+
96
+ server = es.enter_context(SocketServer(
97
+ SocketBinder.of(bind),
98
+ server_handler,
99
+ ))
100
+
101
+ yield server
omlish/io/fileno.py ADDED
@@ -0,0 +1,11 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ import dataclasses as dc
4
+
5
+
6
+ @dc.dataclass(frozen=True)
7
+ class Fileno:
8
+ fd: int
9
+
10
+ def fileno(self) -> int:
11
+ return self.fd
omlish/lang/__init__.py CHANGED
@@ -6,11 +6,12 @@ from .cached import ( # noqa
6
6
 
7
7
  from .classes import ( # noqa
8
8
  Abstract,
9
+ AbstractTypeError,
9
10
  AnySensitive,
10
11
  Callable,
11
12
  Descriptor,
12
13
  Final,
13
- FinalError,
14
+ FinalTypeError,
14
15
  LazySingleton,
15
16
  Marker,
16
17
  Namespace,
@@ -26,6 +27,7 @@ from .classes import ( # noqa
26
27
  SimpleMetaDict,
27
28
  Singleton,
28
29
  Virtual,
30
+ get_abstract_methods,
29
31
  is_abstract,
30
32
  is_abstract_class,
31
33
  is_abstract_method,
@@ -52,6 +54,7 @@ from .cmp import ( # noqa
52
54
 
53
55
  from .contextmanagers import ( # noqa
54
56
  AsyncContextManager,
57
+ AsyncExitStacked,
55
58
  ContextManaged,
56
59
  ContextManager,
57
60
  ContextWrapped,
omlish/lang/cached.py CHANGED
@@ -158,7 +158,6 @@ class _CachedFunction(ta.Generic[T]):
158
158
 
159
159
 
160
160
  class _CachedFunctionDescriptor(_CachedFunction[T]):
161
-
162
161
  def __init__(
163
162
  self,
164
163
  fn: ta.Callable[P, T],
@@ -1,5 +1,7 @@
1
1
  from .abstract import ( # noqa
2
2
  Abstract,
3
+ AbstractTypeError,
4
+ get_abstract_methods,
3
5
  is_abstract,
4
6
  is_abstract_class,
5
7
  is_abstract_method,
@@ -10,7 +12,7 @@ from .abstract import ( # noqa
10
12
  from .restrict import ( # noqa
11
13
  AnySensitive,
12
14
  Final,
13
- FinalError,
15
+ FinalTypeError,
14
16
  NoBool,
15
17
  NotInstantiable,
16
18
  NotPicklable,
@@ -11,6 +11,8 @@ _ABSTRACT_METHODS_ATTR = '__abstractmethods__'
11
11
  _IS_ABSTRACT_METHOD_ATTR = '__isabstractmethod__'
12
12
  _FORCE_ABSTRACT_ATTR = '__forceabstract__'
13
13
 
14
+ _INTERNAL_ABSTRACT_ATTRS = frozenset([_FORCE_ABSTRACT_ATTR])
15
+
14
16
 
15
17
  def make_abstract(obj: T) -> T:
16
18
  if callable(obj):
@@ -27,6 +29,10 @@ def make_abstract(obj: T) -> T:
27
29
  return obj
28
30
 
29
31
 
32
+ class AbstractTypeError(TypeError):
33
+ pass
34
+
35
+
30
36
  class Abstract(abc.ABC): # noqa
31
37
  __slots__ = ()
32
38
 
@@ -50,7 +56,7 @@ class Abstract(abc.ABC): # noqa
50
56
  ams.update(set(getattr(b, _ABSTRACT_METHODS_ATTR, [])) - seen)
51
57
  seen.update(dir(b))
52
58
  if ams:
53
- raise TypeError(
59
+ raise AbstractTypeError(
54
60
  f'Cannot subclass abstract class {cls.__name__} with abstract methods: '
55
61
  f'{", ".join(map(str, sorted(ams)))}',
56
62
  )
@@ -78,6 +84,13 @@ def is_abstract(obj: ta.Any) -> bool:
78
84
  return is_abstract_method(obj) or is_abstract_class(obj)
79
85
 
80
86
 
87
+ def get_abstract_methods(cls: type, *, include_internal: bool = False) -> frozenset[str]:
88
+ ms = frozenset(getattr(cls, _ABSTRACT_METHODS_ATTR))
89
+ if not include_internal:
90
+ ms -= _INTERNAL_ABSTRACT_ATTRS
91
+ return ms
92
+
93
+
81
94
  def unabstract_class(
82
95
  members: ta.Iterable[str | tuple[str, ta.Any]],
83
96
  ): # -> ta.Callable[[type[T]], type[T]]:
@@ -9,7 +9,7 @@ from .abstract import is_abstract
9
9
  ##
10
10
 
11
11
 
12
- class FinalError(TypeError):
12
+ class FinalTypeError(TypeError):
13
13
  def __init__(self, _type: type) -> None:
14
14
  super().__init__()
15
15
 
@@ -28,11 +28,11 @@ class Final(Abstract):
28
28
  abstracts: set[ta.Any] = set()
29
29
  for base in cls.__bases__:
30
30
  if base is Abstract:
31
- raise FinalError(base)
31
+ raise FinalTypeError(base)
32
32
  elif base is Final:
33
33
  continue
34
34
  elif Final in base.__mro__:
35
- raise FinalError(base)
35
+ raise FinalTypeError(base)
36
36
  else:
37
37
  abstracts.update(getattr(base, '__abstractmethods__', []))
38
38
 
@@ -40,9 +40,9 @@ class Final(Abstract):
40
40
  try:
41
41
  v = cls.__dict__[a]
42
42
  except KeyError:
43
- raise FinalError(a) from None
43
+ raise FinalTypeError(a) from None
44
44
  if is_abstract(v):
45
- raise FinalError(a)
45
+ raise FinalTypeError(a)
46
46
 
47
47
 
48
48
  ##
@@ -28,7 +28,6 @@ def _make_not_instantiable():
28
28
 
29
29
 
30
30
  class _VirtualMeta(abc.ABCMeta):
31
-
32
31
  def __new__(mcls, name, bases, namespace):
33
32
  if 'Virtual' not in globals():
34
33
  return super().__new__(mcls, name, bases, namespace)
omlish/lang/clsdct.py CHANGED
@@ -36,7 +36,6 @@ def get_caller_cls_dct(offset: int = 0) -> ta.MutableMapping[str, ta.Any]:
36
36
 
37
37
 
38
38
  class ClassDctFn:
39
-
40
39
  def __init__(self, fn: ta.Callable, offset: int | None = None, *, wrap=True) -> None:
41
40
  super().__init__()
42
41
 
@@ -19,7 +19,6 @@ T = ta.TypeVar('T')
19
19
 
20
20
 
21
21
  class ContextManaged:
22
-
23
22
  def __enter__(self) -> ta.Self:
24
23
  return self
25
24
 
@@ -33,7 +32,6 @@ class ContextManaged:
33
32
 
34
33
 
35
34
  class NopContextManaged(ContextManaged):
36
-
37
35
  def __init_subclass__(cls, **kwargs: ta.Any) -> None:
38
36
  raise TypeError
39
37
 
@@ -42,7 +40,6 @@ NOP_CONTEXT_MANAGED = NopContextManaged()
42
40
 
43
41
 
44
42
  class NopContextManager:
45
-
46
43
  def __init_subclass__(cls, **kwargs: ta.Any) -> None:
47
44
  raise TypeError
48
45
 
@@ -57,7 +54,6 @@ NOP_CONTEXT_MANAGER = NopContextManager()
57
54
 
58
55
 
59
56
  class ContextManager(abc.ABC, ta.Generic[T]):
60
-
61
57
  def __init_subclass__(cls, **kwargs: ta.Any) -> None:
62
58
  super().__init_subclass__(**kwargs)
63
59
 
@@ -87,7 +83,6 @@ class ContextManager(abc.ABC, ta.Generic[T]):
87
83
 
88
84
 
89
85
  class AsyncContextManager(abc.ABC, ta.Generic[T]):
90
-
91
86
  def __init_subclass__(cls, **kwargs: ta.Any) -> None:
92
87
  super().__init_subclass__(**kwargs)
93
88
 
@@ -191,7 +186,6 @@ def attr_setting(obj, attr, val, *, default=None): # noqa
191
186
 
192
187
 
193
188
  class ExitStacked:
194
-
195
189
  @property
196
190
  def _exit_stack(self) -> contextlib.ExitStack:
197
191
  try:
@@ -229,7 +223,6 @@ class ExitStacked:
229
223
 
230
224
 
231
225
  class AsyncExitStacked:
232
-
233
226
  @property
234
227
  def _exit_stack(self) -> contextlib.AsyncExitStack:
235
228
  try:
@@ -276,7 +269,6 @@ ContextWrappable: ta.TypeAlias = ta.ContextManager | str | ta.Callable[..., ta.C
276
269
 
277
270
 
278
271
  class ContextWrapped:
279
-
280
272
  def __init__(self, fn: ta.Callable, cm: str | ContextWrappable) -> None:
281
273
  super().__init__()
282
274
 
@@ -197,7 +197,6 @@ decorator = _decorator
197
197
 
198
198
 
199
199
  class AccessForbiddenError(Exception):
200
-
201
200
  def __init__(self, name: str | None = None, *args: ta.Any, **kwargs: ta.Any) -> None:
202
201
  super().__init__(*((name,) if name is not None else ()), *args, **kwargs) # noqa
203
202
  self.name = name
omlish/lang/maybes.py CHANGED
@@ -11,7 +11,6 @@ class ValueNotPresentException(BaseException):
11
11
 
12
12
 
13
13
  class Maybe(abc.ABC, ta.Generic[T]):
14
-
15
14
  @property
16
15
  @abc.abstractmethod
17
16
  def present(self) -> bool:
omlish/lang/objects.py CHANGED
@@ -140,9 +140,7 @@ def dir_dict(o: ta.Any) -> dict[str, ta.Any]:
140
140
 
141
141
 
142
142
  class SimpleProxy(ta.Generic[T]):
143
-
144
143
  class Descriptor:
145
-
146
144
  def __init__(self, attr: str) -> None:
147
145
  super().__init__()
148
146
  self._attr = attr
@@ -1,24 +0,0 @@
1
- from .secrets import ( # noqa
2
- CachingSecrets,
3
- CompositeSecrets,
4
- EMPTY_SECRETS,
5
- EmptySecrets,
6
- EnvVarSecrets,
7
- FnSecrets,
8
- LoggingSecrets,
9
- MappingSecrets,
10
- Secret,
11
- SecretRef,
12
- SecretRefOrStr,
13
- Secrets,
14
- secret_field,
15
- secret_repr,
16
- )
17
-
18
-
19
- ##
20
-
21
-
22
- from ..lang.imports import _register_conditional_import # noqa
23
-
24
- _register_conditional_import('..marshal', '.marshal', __package__)
omlish/secrets/all.py ADDED
@@ -0,0 +1,16 @@
1
+ from .secrets import ( # noqa
2
+ CachingSecrets,
3
+ CompositeSecrets,
4
+ EMPTY_SECRETS,
5
+ EmptySecrets,
6
+ EnvVarSecrets,
7
+ FnSecrets,
8
+ LoggingSecrets,
9
+ MappingSecrets,
10
+ Secret,
11
+ SecretRef,
12
+ SecretRefOrStr,
13
+ Secrets,
14
+ secret_field,
15
+ secret_repr,
16
+ )
omlish/secrets/secrets.py CHANGED
@@ -335,3 +335,9 @@ class EnvVarSecrets(Secrets):
335
335
  return dct.pop(ekey)
336
336
  else:
337
337
  return dct[ekey]
338
+
339
+
340
+ ##
341
+
342
+
343
+ lang.imports._register_conditional_import('..marshal', '.marshal', __package__) # noqa
omlish/secrets/ssl.py ADDED
@@ -0,0 +1,9 @@
1
+ # @omlish-lite
2
+ # ruff: noqa: UP006 UP007
3
+ import dataclasses as dc
4
+
5
+
6
+ @dc.dataclass(frozen=True)
7
+ class SslCert:
8
+ key_file: str
9
+ cert_file: str
@@ -0,0 +1,50 @@
1
+ # @omlish-lite
2
+ # ruff: noqa: UP006 UP007
3
+ import os.path
4
+ import subprocess
5
+ import tempfile
6
+ import typing as ta
7
+
8
+ from .ssl import SslCert
9
+
10
+
11
+ class TempSslCert(ta.NamedTuple):
12
+ cert: SslCert
13
+ temp_dir: str
14
+
15
+
16
+ def generate_temp_localhost_ssl_cert() -> TempSslCert:
17
+ temp_dir = tempfile.mkdtemp()
18
+
19
+ proc = subprocess.run(
20
+ [
21
+ 'openssl',
22
+ 'req',
23
+ '-x509',
24
+ '-newkey', 'rsa:2048',
25
+
26
+ '-keyout', 'key.pem',
27
+ '-out', 'cert.pem',
28
+
29
+ '-days', '365',
30
+
31
+ '-nodes',
32
+
33
+ '-subj', '/CN=localhost',
34
+ '-addext', 'subjectAltName = DNS:localhost,IP:127.0.0.1',
35
+ ],
36
+ cwd=temp_dir,
37
+ capture_output=True,
38
+ check=False,
39
+ )
40
+
41
+ if proc.returncode:
42
+ raise RuntimeError(f'Failed to generate temp ssl cert: {proc.stderr=}')
43
+
44
+ return TempSslCert(
45
+ SslCert(
46
+ key_file=os.path.join(temp_dir, 'key.pem'),
47
+ cert_file=os.path.join(temp_dir, 'cert.pem'),
48
+ ),
49
+ temp_dir,
50
+ )