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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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"