omlish 0.0.0.dev123__py3-none-any.whl → 0.0.0.dev125__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
omlish/lite/inject.py CHANGED
@@ -9,10 +9,12 @@ import weakref
9
9
 
10
10
  from .check import check_isinstance
11
11
  from .check import check_not_isinstance
12
+ from .check import check_not_none
12
13
  from .maybes import Maybe
13
14
  from .reflect import get_optional_alias_arg
14
15
  from .reflect import is_new_type
15
16
  from .reflect import is_optional_alias
17
+ from .typing import Func
16
18
 
17
19
 
18
20
  T = ta.TypeVar('T')
@@ -30,12 +32,28 @@ InjectorBindingOrBindings = ta.Union['InjectorBinding', 'InjectorBindings']
30
32
 
31
33
 
32
34
  @dc.dataclass(frozen=True)
33
- class InjectorKey:
34
- cls: InjectorKeyCls
35
+ class InjectorKey(ta.Generic[T]):
36
+ # Before PEP-560 typing.Generic was a metaclass with a __new__ that takes a 'cls' arg, so instantiating a dataclass
37
+ # with kwargs (such as through dc.replace) causes `TypeError: __new__() got multiple values for argument 'cls'`.
38
+ # See:
39
+ # - https://github.com/python/cpython/commit/d911e40e788fb679723d78b6ea11cabf46caed5a
40
+ # - https://gist.github.com/wrmsr/4468b86efe9f373b6b114bfe85b98fd3
41
+ cls_: InjectorKeyCls
42
+
35
43
  tag: ta.Any = None
36
44
  array: bool = False
37
45
 
38
46
 
47
+ def is_valid_injector_key_cls(cls: ta.Any) -> bool:
48
+ return isinstance(cls, type) or is_new_type(cls)
49
+
50
+
51
+ def check_valid_injector_key_cls(cls: T) -> T:
52
+ if not is_valid_injector_key_cls(cls):
53
+ raise TypeError(cls)
54
+ return cls
55
+
56
+
39
57
  ##
40
58
 
41
59
 
@@ -79,6 +97,12 @@ class Injector(abc.ABC):
79
97
  def inject(self, obj: ta.Any) -> ta.Any:
80
98
  raise NotImplementedError
81
99
 
100
+ def __getitem__(
101
+ self,
102
+ target: ta.Union[InjectorKey[T], ta.Type[T]],
103
+ ) -> T:
104
+ return self.provide(target)
105
+
82
106
 
83
107
  ###
84
108
  # exceptions
@@ -111,7 +135,7 @@ def as_injector_key(o: ta.Any) -> InjectorKey:
111
135
  raise TypeError(o)
112
136
  if isinstance(o, InjectorKey):
113
137
  return o
114
- if isinstance(o, type) or is_new_type(o):
138
+ if is_valid_injector_key_cls(o):
115
139
  return InjectorKey(o)
116
140
  raise TypeError(o)
117
141
 
@@ -136,14 +160,14 @@ class FnInjectorProvider(InjectorProvider):
136
160
 
137
161
  @dc.dataclass(frozen=True)
138
162
  class CtorInjectorProvider(InjectorProvider):
139
- cls: type
163
+ cls_: type
140
164
 
141
165
  def __post_init__(self) -> None:
142
- check_isinstance(self.cls, type)
166
+ check_isinstance(self.cls_, type)
143
167
 
144
168
  def provider_fn(self) -> InjectorProviderFn:
145
169
  def pfn(i: Injector) -> ta.Any:
146
- return i.inject(self.cls)
170
+ return i.inject(self.cls_)
147
171
 
148
172
  return pfn
149
173
 
@@ -292,19 +316,42 @@ def build_injector_provider_map(bs: InjectorBindings) -> ta.Mapping[InjectorKey,
292
316
  # inspection
293
317
 
294
318
 
295
- _INJECTION_SIGNATURE_CACHE: ta.MutableMapping[ta.Any, inspect.Signature] = weakref.WeakKeyDictionary()
319
+ # inspect.signature(eval_str=True) was added in 3.10 and we have to support 3.8, so we have to get_type_hints to eval
320
+ # str annotations *in addition to* getting the signature for parameter information.
321
+ class _InjectionInspection(ta.NamedTuple):
322
+ signature: inspect.Signature
323
+ type_hints: ta.Mapping[str, ta.Any]
324
+
325
+
326
+ _INJECTION_INSPECTION_CACHE: ta.MutableMapping[ta.Any, _InjectionInspection] = weakref.WeakKeyDictionary()
327
+
296
328
 
329
+ def _do_injection_inspect(obj: ta.Any) -> _InjectionInspection:
330
+ uw = obj
331
+ while True:
332
+ if isinstance(uw, functools.partial):
333
+ uw = uw.func
334
+ else:
335
+ if (uw2 := inspect.unwrap(uw)) is uw:
336
+ break
337
+ uw = uw2
338
+
339
+ return _InjectionInspection(
340
+ inspect.signature(obj),
341
+ ta.get_type_hints(uw),
342
+ )
297
343
 
298
- def _injection_signature(obj: ta.Any) -> inspect.Signature:
344
+
345
+ def _injection_inspect(obj: ta.Any) -> _InjectionInspection:
299
346
  try:
300
- return _INJECTION_SIGNATURE_CACHE[obj]
347
+ return _INJECTION_INSPECTION_CACHE[obj]
301
348
  except TypeError:
302
- return inspect.signature(obj)
349
+ return _do_injection_inspect(obj)
303
350
  except KeyError:
304
351
  pass
305
- sig = inspect.signature(obj)
306
- _INJECTION_SIGNATURE_CACHE[obj] = sig
307
- return sig
352
+ insp = _do_injection_inspect(obj)
353
+ _INJECTION_INSPECTION_CACHE[obj] = insp
354
+ return insp
308
355
 
309
356
 
310
357
  class InjectionKwarg(ta.NamedTuple):
@@ -325,20 +372,20 @@ def build_injection_kwargs_target(
325
372
  skip_kwargs: ta.Optional[ta.Iterable[ta.Any]] = None,
326
373
  raw_optional: bool = False,
327
374
  ) -> InjectionKwargsTarget:
328
- sig = _injection_signature(obj)
375
+ insp = _injection_inspect(obj)
329
376
 
330
377
  seen: ta.Set[InjectorKey] = set(map(as_injector_key, skip_kwargs)) if skip_kwargs is not None else set()
331
378
  kws: ta.List[InjectionKwarg] = []
332
- for p in list(sig.parameters.values())[skip_args:]:
379
+ for p in list(insp.signature.parameters.values())[skip_args:]:
333
380
  if p.annotation is inspect.Signature.empty:
334
381
  if p.default is not inspect.Parameter.empty:
335
382
  raise KeyError(f'{obj}, {p.name}')
336
383
  continue
337
384
 
338
385
  if p.kind not in (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.KEYWORD_ONLY):
339
- raise TypeError(sig)
386
+ raise TypeError(insp)
340
387
 
341
- ann = p.annotation
388
+ ann = insp.type_hints.get(p.name, p.annotation)
342
389
  if (
343
390
  not raw_optional and
344
391
  is_optional_alias(ann)
@@ -363,6 +410,66 @@ def build_injection_kwargs_target(
363
410
  )
364
411
 
365
412
 
413
+ ###
414
+ # injector
415
+
416
+
417
+ _INJECTOR_INJECTOR_KEY: InjectorKey[Injector] = InjectorKey(Injector)
418
+
419
+
420
+ class _Injector(Injector):
421
+ def __init__(self, bs: InjectorBindings, p: ta.Optional[Injector] = None) -> None:
422
+ super().__init__()
423
+
424
+ self._bs = check_isinstance(bs, InjectorBindings)
425
+ self._p: ta.Optional[Injector] = check_isinstance(p, (Injector, type(None)))
426
+
427
+ self._pfm = {k: v.provider_fn() for k, v in build_injector_provider_map(bs).items()}
428
+
429
+ if _INJECTOR_INJECTOR_KEY in self._pfm:
430
+ raise DuplicateInjectorKeyError(_INJECTOR_INJECTOR_KEY)
431
+
432
+ def try_provide(self, key: ta.Any) -> Maybe[ta.Any]:
433
+ key = as_injector_key(key)
434
+
435
+ if key == _INJECTOR_INJECTOR_KEY:
436
+ return Maybe.just(self)
437
+
438
+ fn = self._pfm.get(key)
439
+ if fn is not None:
440
+ return Maybe.just(fn(self))
441
+
442
+ if self._p is not None:
443
+ pv = self._p.try_provide(key)
444
+ if pv is not None:
445
+ return Maybe.empty()
446
+
447
+ return Maybe.empty()
448
+
449
+ def provide(self, key: ta.Any) -> ta.Any:
450
+ v = self.try_provide(key)
451
+ if v.present:
452
+ return v.must()
453
+ raise UnboundInjectorKeyError(key)
454
+
455
+ def provide_kwargs(self, obj: ta.Any) -> ta.Mapping[str, ta.Any]:
456
+ kt = build_injection_kwargs_target(obj)
457
+ ret: ta.Dict[str, ta.Any] = {}
458
+ for kw in kt.kwargs:
459
+ if kw.has_default:
460
+ if not (mv := self.try_provide(kw.key)).present:
461
+ continue
462
+ v = mv.must()
463
+ else:
464
+ v = self.provide(kw.key)
465
+ ret[kw.name] = v
466
+ return ret
467
+
468
+ def inject(self, obj: ta.Any) -> ta.Any:
469
+ kws = self.provide_kwargs(obj)
470
+ return obj(**kws)
471
+
472
+
366
473
  ###
367
474
  # binder
368
475
 
@@ -412,7 +519,7 @@ class InjectorBinder:
412
519
  to_key: ta.Any = None,
413
520
 
414
521
  singleton: bool = False,
415
- ) -> InjectorBinding:
522
+ ) -> InjectorBindingOrBindings:
416
523
  if obj is None or obj is inspect.Parameter.empty:
417
524
  raise TypeError(obj)
418
525
  if isinstance(obj, cls._BANNED_BIND_TYPES):
@@ -442,9 +549,9 @@ class InjectorBinder:
442
549
  elif cls._is_fn(obj) and not has_to:
443
550
  to_fn = obj
444
551
  if key is None:
445
- sig = _injection_signature(obj)
446
- ty = check_isinstance(sig.return_annotation, type)
447
- key = InjectorKey(ty)
552
+ insp = _injection_inspect(obj)
553
+ key_cls: ta.Any = check_valid_injector_key_cls(check_not_none(insp.type_hints.get('return')))
554
+ key = InjectorKey(key_cls)
448
555
  else:
449
556
  if to_const is not None:
450
557
  raise TypeError('Cannot bind instance with to_const')
@@ -495,67 +602,21 @@ class InjectorBinder:
495
602
 
496
603
 
497
604
  ###
498
- # injector
499
-
500
-
501
- _INJECTOR_INJECTOR_KEY = InjectorKey(Injector)
502
-
503
-
504
- class _Injector(Injector):
505
- def __init__(self, bs: InjectorBindings, p: ta.Optional[Injector] = None) -> None:
506
- super().__init__()
507
-
508
- self._bs = check_isinstance(bs, InjectorBindings)
509
- self._p: ta.Optional[Injector] = check_isinstance(p, (Injector, type(None)))
510
-
511
- self._pfm = {k: v.provider_fn() for k, v in build_injector_provider_map(bs).items()}
512
-
513
- if _INJECTOR_INJECTOR_KEY in self._pfm:
514
- raise DuplicateInjectorKeyError(_INJECTOR_INJECTOR_KEY)
515
-
516
- def try_provide(self, key: ta.Any) -> Maybe[ta.Any]:
517
- key = as_injector_key(key)
518
-
519
- if key == _INJECTOR_INJECTOR_KEY:
520
- return Maybe.just(self)
521
-
522
- fn = self._pfm.get(key)
523
- if fn is not None:
524
- return Maybe.just(fn(self))
525
-
526
- if self._p is not None:
527
- pv = self._p.try_provide(key)
528
- if pv is not None:
529
- return Maybe.empty()
530
-
531
- return Maybe.empty()
605
+ # injection helpers
532
606
 
533
- def provide(self, key: ta.Any) -> ta.Any:
534
- v = self.try_provide(key)
535
- if v.present:
536
- return v.must()
537
- raise UnboundInjectorKeyError(key)
538
607
 
539
- def provide_kwargs(self, obj: ta.Any) -> ta.Mapping[str, ta.Any]:
540
- kt = build_injection_kwargs_target(obj)
541
- ret: ta.Dict[str, ta.Any] = {}
542
- for kw in kt.kwargs:
543
- if kw.has_default:
544
- if not (mv := self.try_provide(kw.key)).present:
545
- continue
546
- v = mv.must()
547
- else:
548
- v = self.provide(kw.key)
549
- ret[kw.name] = v
550
- return ret
608
+ def make_injector_factory(
609
+ factory_cls: ta.Any,
610
+ factory_fn: ta.Callable[..., T],
611
+ ) -> ta.Callable[..., Func[T]]:
612
+ def outer(injector: Injector) -> factory_cls:
613
+ def inner(*args, **kwargs):
614
+ return injector.inject(functools.partial(factory_fn, *args, **kwargs))
615
+ return Func(inner)
616
+ return outer
551
617
 
552
- def inject(self, obj: ta.Any) -> ta.Any:
553
- kws = self.provide_kwargs(obj)
554
- return obj(**kws)
555
618
 
556
-
557
- ###
558
- # injection helpers
619
+ ##
559
620
 
560
621
 
561
622
  class Injection:
@@ -586,6 +647,12 @@ class Injection:
586
647
  def override(cls, p: InjectorBindings, *args: InjectorBindingOrBindings) -> InjectorBindings:
587
648
  return injector_override(p, *args)
588
649
 
650
+ # injector
651
+
652
+ @classmethod
653
+ def create_injector(cls, *args: InjectorBindingOrBindings, p: ta.Optional[Injector] = None) -> Injector:
654
+ return _Injector(as_injector_bindings(*args), p)
655
+
589
656
  # binder
590
657
 
591
658
  @classmethod
@@ -603,7 +670,7 @@ class Injection:
603
670
  to_key: ta.Any = None,
604
671
 
605
672
  singleton: bool = False,
606
- ) -> InjectorBinding:
673
+ ) -> InjectorBindingOrBindings:
607
674
  return InjectorBinder.bind(
608
675
  obj,
609
676
 
@@ -619,11 +686,15 @@ class Injection:
619
686
  singleton=singleton,
620
687
  )
621
688
 
622
- # injector
689
+ # helpers
623
690
 
624
691
  @classmethod
625
- def create_injector(cls, *args: InjectorBindingOrBindings, p: ta.Optional[Injector] = None) -> Injector:
626
- return _Injector(as_injector_bindings(*args), p)
692
+ def bind_factory(
693
+ cls,
694
+ factory_cls: ta.Any,
695
+ factory_fn: ta.Callable[..., T],
696
+ ) -> InjectorBindingOrBindings:
697
+ return cls.bind(make_injector_factory(factory_cls, factory_fn))
627
698
 
628
699
 
629
700
  inj = Injection
omlish/lite/journald.py CHANGED
@@ -29,7 +29,6 @@ sd_iovec._fields_ = [
29
29
  def sd_libsystemd() -> ta.Any:
30
30
  lib = ct.CDLL('libsystemd.so.0')
31
31
 
32
- lib.sd_journal_sendv = lib['sd_journal_sendv'] # type: ignore
33
32
  lib.sd_journal_sendv.restype = ct.c_int
34
33
  lib.sd_journal_sendv.argtypes = [ct.POINTER(sd_iovec), ct.c_int]
35
34
 
omlish/lite/runtime.py CHANGED
@@ -14,5 +14,4 @@ REQUIRED_PYTHON_VERSION = (3, 8)
14
14
 
15
15
  def check_runtime_version() -> None:
16
16
  if sys.version_info < REQUIRED_PYTHON_VERSION:
17
- raise OSError(
18
- f'Requires python {REQUIRED_PYTHON_VERSION}, got {sys.version_info} from {sys.executable}') # noqa
17
+ raise OSError(f'Requires python {REQUIRED_PYTHON_VERSION}, got {sys.version_info} from {sys.executable}') # noqa
omlish/lite/socket.py ADDED
@@ -0,0 +1,77 @@
1
+ # ruff: noqa: UP006 UP007
2
+ """
3
+ TODO:
4
+ - SocketClientAddress family / tuple pairs
5
+ + codification of https://docs.python.org/3/library/socket.html#socket-families
6
+ """
7
+ import abc
8
+ import dataclasses as dc
9
+ import socket
10
+ import typing as ta
11
+
12
+
13
+ SocketAddress = ta.Any
14
+
15
+
16
+ SocketHandlerFactory = ta.Callable[[SocketAddress, ta.BinaryIO, ta.BinaryIO], 'SocketHandler']
17
+
18
+
19
+ ##
20
+
21
+
22
+ @dc.dataclass(frozen=True)
23
+ class SocketAddressInfoArgs:
24
+ host: ta.Optional[str]
25
+ port: ta.Union[str, int, None]
26
+ family: socket.AddressFamily = socket.AddressFamily.AF_UNSPEC
27
+ type: int = 0
28
+ proto: int = 0
29
+ flags: socket.AddressInfo = socket.AddressInfo(0)
30
+
31
+
32
+ @dc.dataclass(frozen=True)
33
+ class SocketAddressInfo:
34
+ family: socket.AddressFamily
35
+ type: int
36
+ proto: int
37
+ canonname: ta.Optional[str]
38
+ sockaddr: SocketAddress
39
+
40
+
41
+ def get_best_socket_family(
42
+ host: ta.Optional[str],
43
+ port: ta.Union[str, int, None],
44
+ family: ta.Union[int, socket.AddressFamily] = socket.AddressFamily.AF_UNSPEC,
45
+ ) -> ta.Tuple[socket.AddressFamily, SocketAddress]:
46
+ """https://github.com/python/cpython/commit/f289084c83190cc72db4a70c58f007ec62e75247"""
47
+
48
+ infos = socket.getaddrinfo(
49
+ host,
50
+ port,
51
+ family,
52
+ type=socket.SOCK_STREAM,
53
+ flags=socket.AI_PASSIVE,
54
+ )
55
+ ai = SocketAddressInfo(*next(iter(infos)))
56
+ return ai.family, ai.sockaddr
57
+
58
+
59
+ ##
60
+
61
+
62
+ class SocketHandler(abc.ABC):
63
+ def __init__(
64
+ self,
65
+ client_address: SocketAddress,
66
+ rfile: ta.BinaryIO,
67
+ wfile: ta.BinaryIO,
68
+ ) -> None:
69
+ super().__init__()
70
+
71
+ self._client_address = client_address
72
+ self._rfile = rfile
73
+ self._wfile = wfile
74
+
75
+ @abc.abstractmethod
76
+ def handle(self) -> None:
77
+ raise NotImplementedError
@@ -0,0 +1,66 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import socket
3
+ import socketserver
4
+ import typing as ta
5
+
6
+ from omlish.lite.check import check_not_none
7
+
8
+ from .socket import SocketAddress
9
+ from .socket import SocketHandlerFactory
10
+
11
+
12
+ ##
13
+
14
+
15
+ class SocketServerBaseRequestHandler_: # noqa
16
+ request: socket.socket
17
+ client_address: SocketAddress
18
+ server: socketserver.TCPServer
19
+
20
+
21
+ class SocketServerStreamRequestHandler_(SocketServerBaseRequestHandler_): # noqa
22
+ rbufsize: int
23
+ wbufsize: int
24
+
25
+ timeout: ta.Optional[float]
26
+
27
+ disable_nagle_algorithm: bool
28
+
29
+ connection: socket.socket
30
+ rfile: ta.BinaryIO
31
+ wfile: ta.BinaryIO
32
+
33
+
34
+ ##
35
+
36
+
37
+ class SocketHandlerSocketServerStreamRequestHandler( # type: ignore[misc]
38
+ socketserver.StreamRequestHandler,
39
+ SocketServerStreamRequestHandler_,
40
+ ):
41
+ socket_handler_factory: ta.Optional[SocketHandlerFactory] = None
42
+
43
+ def __init__(
44
+ self,
45
+ request: socket.socket,
46
+ client_address: SocketAddress,
47
+ server: socketserver.TCPServer,
48
+ *,
49
+ socket_handler_factory: ta.Optional[SocketHandlerFactory] = None,
50
+ ) -> None:
51
+ if socket_handler_factory is not None:
52
+ self.socket_handler_factory = socket_handler_factory
53
+
54
+ super().__init__(
55
+ request,
56
+ client_address,
57
+ server,
58
+ )
59
+
60
+ def handle(self) -> None:
61
+ target = check_not_none(self.socket_handler_factory)(
62
+ self.client_address,
63
+ self.rfile, # type: ignore[arg-type]
64
+ self.wfile, # type: ignore[arg-type]
65
+ )
66
+ target.handle()
omlish/lite/typing.py ADDED
@@ -0,0 +1,13 @@
1
+ import dataclasses as dc
2
+ import typing as ta
3
+
4
+
5
+ T = ta.TypeVar('T')
6
+
7
+
8
+ @dc.dataclass(frozen=True)
9
+ class Func(ta.Generic[T]):
10
+ fn: ta.Callable[..., T]
11
+
12
+ def __call__(self, *args: ta.Any, **kwargs: ta.Any) -> T:
13
+ return self.fn(*args, **kwargs)
@@ -0,0 +1,100 @@
1
+ Metadata-Version: 2.1
2
+ Name: omlish
3
+ Version: 0.0.0.dev125
4
+ Summary: omlish
5
+ Author: wrmsr
6
+ License: BSD-3-Clause
7
+ Project-URL: source, https://github.com/wrmsr/omlish
8
+ Classifier: License :: OSI Approved :: BSD License
9
+ Classifier: Development Status :: 2 - Pre-Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Operating System :: POSIX
13
+ Requires-Python: >=3.12
14
+ License-File: LICENSE
15
+ Provides-Extra: all
16
+ Requires-Dist: anyio~=4.6; extra == "all"
17
+ Requires-Dist: sniffio~=1.3; extra == "all"
18
+ Requires-Dist: greenlet~=3.1; extra == "all"
19
+ Requires-Dist: trio~=0.27; extra == "all"
20
+ Requires-Dist: trio-asyncio~=0.15; extra == "all"
21
+ Requires-Dist: lz4~=4.3; extra == "all"
22
+ Requires-Dist: python-snappy~=0.7; extra == "all"
23
+ Requires-Dist: zstandard~=0.23; extra == "all"
24
+ Requires-Dist: asttokens~=2.4; extra == "all"
25
+ Requires-Dist: executing~=2.1; extra == "all"
26
+ Requires-Dist: psutil~=6.0; extra == "all"
27
+ Requires-Dist: orjson~=3.10; extra == "all"
28
+ Requires-Dist: ujson~=5.10; extra == "all"
29
+ Requires-Dist: json5~=0.9; extra == "all"
30
+ Requires-Dist: pyyaml~=6.0; extra == "all"
31
+ Requires-Dist: cbor2~=5.6; extra == "all"
32
+ Requires-Dist: cloudpickle~=3.1; extra == "all"
33
+ Requires-Dist: httpx[http2]~=0.27; extra == "all"
34
+ Requires-Dist: wrapt~=1.14; extra == "all"
35
+ Requires-Dist: cryptography~=43.0; extra == "all"
36
+ Requires-Dist: sqlalchemy[asyncio]~=2.0; extra == "all"
37
+ Requires-Dist: pg8000~=1.31; extra == "all"
38
+ Requires-Dist: pymysql~=1.1; extra == "all"
39
+ Requires-Dist: aiomysql~=0.2; extra == "all"
40
+ Requires-Dist: aiosqlite~=0.20; extra == "all"
41
+ Requires-Dist: asyncpg~=0.30; extra == "all"
42
+ Requires-Dist: apsw~=3.46; extra == "all"
43
+ Requires-Dist: sqlean.py~=3.45; extra == "all"
44
+ Requires-Dist: duckdb~=1.1; extra == "all"
45
+ Requires-Dist: pytest~=8.0; extra == "all"
46
+ Requires-Dist: anyio~=4.6; extra == "all"
47
+ Requires-Dist: sniffio~=1.3; extra == "all"
48
+ Requires-Dist: asttokens~=2.4; extra == "all"
49
+ Requires-Dist: executing~=2.1; extra == "all"
50
+ Requires-Dist: orjson~=3.10; extra == "all"
51
+ Requires-Dist: pyyaml~=6.0; extra == "all"
52
+ Requires-Dist: wrapt~=1.14; extra == "all"
53
+ Provides-Extra: async
54
+ Requires-Dist: anyio~=4.6; extra == "async"
55
+ Requires-Dist: sniffio~=1.3; extra == "async"
56
+ Requires-Dist: greenlet~=3.1; extra == "async"
57
+ Requires-Dist: trio~=0.27; extra == "async"
58
+ Requires-Dist: trio-asyncio~=0.15; extra == "async"
59
+ Provides-Extra: compress
60
+ Requires-Dist: lz4~=4.3; extra == "compress"
61
+ Requires-Dist: python-snappy~=0.7; extra == "compress"
62
+ Requires-Dist: zstandard~=0.23; extra == "compress"
63
+ Provides-Extra: diag
64
+ Requires-Dist: asttokens~=2.4; extra == "diag"
65
+ Requires-Dist: executing~=2.1; extra == "diag"
66
+ Requires-Dist: psutil~=6.0; extra == "diag"
67
+ Provides-Extra: formats
68
+ Requires-Dist: orjson~=3.10; extra == "formats"
69
+ Requires-Dist: ujson~=5.10; extra == "formats"
70
+ Requires-Dist: json5~=0.9; extra == "formats"
71
+ Requires-Dist: pyyaml~=6.0; extra == "formats"
72
+ Requires-Dist: cbor2~=5.6; extra == "formats"
73
+ Requires-Dist: cloudpickle~=3.1; extra == "formats"
74
+ Provides-Extra: http
75
+ Requires-Dist: httpx[http2]~=0.27; extra == "http"
76
+ Provides-Extra: misc
77
+ Requires-Dist: wrapt~=1.14; extra == "misc"
78
+ Provides-Extra: secrets
79
+ Requires-Dist: cryptography~=43.0; extra == "secrets"
80
+ Provides-Extra: sqlalchemy
81
+ Requires-Dist: sqlalchemy[asyncio]~=2.0; extra == "sqlalchemy"
82
+ Provides-Extra: sqldrivers
83
+ Requires-Dist: pg8000~=1.31; extra == "sqldrivers"
84
+ Requires-Dist: pymysql~=1.1; extra == "sqldrivers"
85
+ Requires-Dist: aiomysql~=0.2; extra == "sqldrivers"
86
+ Requires-Dist: aiosqlite~=0.20; extra == "sqldrivers"
87
+ Requires-Dist: asyncpg~=0.30; extra == "sqldrivers"
88
+ Requires-Dist: apsw~=3.46; extra == "sqldrivers"
89
+ Requires-Dist: sqlean.py~=3.45; extra == "sqldrivers"
90
+ Requires-Dist: duckdb~=1.1; extra == "sqldrivers"
91
+ Provides-Extra: testing
92
+ Requires-Dist: pytest~=8.0; extra == "testing"
93
+ Provides-Extra: plus
94
+ Requires-Dist: anyio~=4.6; extra == "plus"
95
+ Requires-Dist: sniffio~=1.3; extra == "plus"
96
+ Requires-Dist: asttokens~=2.4; extra == "plus"
97
+ Requires-Dist: executing~=2.1; extra == "plus"
98
+ Requires-Dist: orjson~=3.10; extra == "plus"
99
+ Requires-Dist: pyyaml~=6.0; extra == "plus"
100
+ Requires-Dist: wrapt~=1.14; extra == "plus"