ominfra 0.0.0.dev438__py3-none-any.whl → 0.0.0.dev483__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.

Potentially problematic release.


This version of ominfra might be problematic. Click here for more details.

@@ -65,6 +65,7 @@ import io
65
65
  import itertools
66
66
  import json
67
67
  import logging
68
+ import operator
68
69
  import os
69
70
  import os.path
70
71
  import pwd
@@ -98,6 +99,91 @@ if sys.version_info < (3, 8):
98
99
  raise OSError(f'Requires python (3, 8), got {sys.version_info} from {sys.executable}') # noqa
99
100
 
100
101
 
102
+ def __omlish_amalg__(): # noqa
103
+ return dict(
104
+ src_files=[
105
+ dict(path='errors.py', sha1='eed49133c64621fb5f081ba7f249e8c4c8745025'),
106
+ dict(path='privileges.py', sha1='80fffb4966565e70b6300381800ff849616a1daa'),
107
+ dict(path='states.py', sha1='7e80da5abde756a47bb01fff1967e35ee9f754e5'),
108
+ dict(path='utils/diag.py', sha1='65f6491a57b3b8ff6dc166c4136c39e49e008c8d'),
109
+ dict(path='utils/fs.py', sha1='f18fd3d60c863e05d91c8e4735b86629334f5181'),
110
+ dict(path='utils/ostypes.py', sha1='81aa9dc830189ae7095c2b8c823e28ce4a808e8d'),
111
+ dict(path='utils/signals.py', sha1='445bab01dcd0144194f330e55accee1277992626'),
112
+ dict(path='utils/strings.py', sha1='c4ced4877e366a64b7d366353ab9e5691c587f38'),
113
+ dict(path='../../omlish/configs/types.py', sha1='f7a5584cd6eccb77d18d729796072a162e9a8790'),
114
+ dict(path='../../omlish/formats/ini/sections.py', sha1='731c92cce82e183d1d4bdc23fc781fad62187394'),
115
+ dict(path='../../omlish/formats/toml/parser.py', sha1='73dac82289350ab951c4bcdbfe61167fa221f26f'),
116
+ dict(path='../../omlish/formats/toml/writer.py', sha1='6ea41d7e724bb1dcf6bd84b88993ff4e8798e021'),
117
+ dict(path='../../omlish/http/versions.py', sha1='197685ffbb62a457a0e8d4047a9df26aebd7dae4'),
118
+ dict(path='../../omlish/io/readers.py', sha1='4b19ab4a87f2fa2a6f6c3cad7e1f3892b7cbd3a4'),
119
+ dict(path='../../omlish/lite/abstract.py', sha1='a2fc3f3697fa8de5247761e9d554e70176f37aac'),
120
+ dict(path='../../omlish/lite/attrops.py', sha1='c1ebfb8573d766d34593c452a2377208d02726dc'),
121
+ dict(path='../../omlish/lite/cached.py', sha1='0c33cf961ac8f0727284303c7a30c5ea98f714f2'),
122
+ dict(path='../../omlish/lite/check.py', sha1='bb6b6b63333699b84462951a854d99ae83195b94'),
123
+ dict(path='../../omlish/lite/json.py', sha1='57eeddc4d23a17931e00284ffa5cb6e3ce089486'),
124
+ dict(path='../../omlish/lite/objects.py', sha1='9566bbf3530fd71fcc56321485216b592fae21e9'),
125
+ dict(path='../../omlish/lite/reflect.py', sha1='c4fec44bf144e9d93293c996af06f6c65fc5e63d'),
126
+ dict(path='../../omlish/lite/strings.py', sha1='89831ecbc34ad80e118a865eceb390ed399dc4d6'),
127
+ dict(path='../../omlish/lite/typing.py', sha1='deaaa560b63d9a0e40991ec0006451f5f0df04c1'),
128
+ dict(path='../../omlish/logs/levels.py', sha1='91405563d082a5eba874da82aac89d83ce7b6152'),
129
+ dict(path='../../omlish/logs/std/filters.py', sha1='f36aab646d84d31e295b33aaaaa6f8b67ff38b3d'),
130
+ dict(path='../../omlish/logs/std/proxy.py', sha1='3e7301a2aa351127f9c85f61b2f85dcc3f15aafb'),
131
+ dict(path='../../omlish/logs/warnings.py', sha1='c4eb694b24773351107fcc058f3620f1dbfb6799'),
132
+ dict(path='../../omlish/sockets/addresses.py', sha1='26533e88a8073f89646c0f77f1fbe0869282ab0e'),
133
+ dict(path='events.py', sha1='d30d903b7d664f76e738ed939b7ec0e6e6861a0a'),
134
+ dict(path='utils/collections.py', sha1='f9c3c8a52e6057e938730746eaa28e48a5b757c6'),
135
+ dict(path='utils/fds.py', sha1='cf9b2a52cc74b2aaebed656ba16888e4322746ec'),
136
+ dict(path='utils/users.py', sha1='d440d9deb2f03b4611bc0eb0ad186f9a994d84f7'),
137
+ dict(path='../../omlish/configs/formats.py', sha1='9bc4f953b4b8700f6f109e6f49e2d70f8e48ce7c'),
138
+ dict(path='../../omlish/configs/processing/names.py', sha1='3ae4c9e921929eb64cee6150cc86f35fee0f2070'),
139
+ dict(path='../../omlish/http/coro/io.py', sha1='2cdf6529c37a37cc0c1db2e02032157cf906d5d6'),
140
+ dict(path='../../omlish/http/parsing.py', sha1='3fea28dc6341908ba7c8fad42bf7bbe711f21b82'),
141
+ dict(path='../../omlish/io/buffers.py', sha1='45a5f79c6d71f02ab82082a48d63ebbd10959031'),
142
+ dict(path='../../omlish/io/fdio/handlers.py', sha1='e81356d4d73a670c35a972476a6338d0b737662b'),
143
+ dict(path='../../omlish/io/fdio/pollers.py', sha1='022d5a8a24412764864ca95186a167698b739baf'),
144
+ dict(path='../../omlish/lite/marshal.py', sha1='96348f5f2a26dc27d842d33cc3927e9da163436b'),
145
+ dict(path='../../omlish/lite/maybes.py', sha1='bdf5136654ccd14b6a072588cad228925bdfbabd'),
146
+ dict(path='../../omlish/lite/runtime.py', sha1='2e752a27ae2bf89b1bb79b4a2da522a3ec360c70'),
147
+ dict(path='../../omlish/logs/infos.py', sha1='4dd104bd468a8c438601dd0bbda619b47d2f1620'),
148
+ dict(path='../../omlish/logs/protocols.py', sha1='05ca4d1d7feb50c4e3b9f22ee371aa7bf4b3dbd1'),
149
+ dict(path='../../omlish/logs/std/json.py', sha1='2a75553131e4d5331bb0cedde42aa183f403fc3b'),
150
+ dict(path='../../omlish/os/journald.py', sha1='7485cad562f8b9b4f71efd41a6177660f7d62e55'),
151
+ dict(path='configs.py', sha1='61f986fc5c9591194f72c3b4ffa4b018770710ed'),
152
+ dict(path='pipes.py', sha1='ad9315c50bffe81ee204227163d85ab366ce5320'),
153
+ dict(path='setup.py', sha1='4be12354bb45cf7773fd98ad9695aa330ae07fe6'),
154
+ dict(path='utils/os.py', sha1='9f7314f1c0c34a8154e9acf38a5b916b2e310b4d'),
155
+ dict(path='../../omlish/http/handlers.py', sha1='40629060bac22ea5e94b720b57001861a4ec9031'),
156
+ dict(path='../../omlish/io/fdio/kqueue.py', sha1='c90ba13e9e5ee795b6af752a6f25f8bcfd7f88a0'),
157
+ dict(path='../../omlish/lite/configs.py', sha1='c8602e0e197ef1133e7e8e248935ac745bfd46cb'),
158
+ dict(path='../../omlish/lite/inject.py', sha1='6f097e3170019a34ff6834d36fcc9cbeed3a7ab4'),
159
+ dict(path='../../omlish/logs/contexts.py', sha1='7456964ade9ac66460e9ade4e242dbdc24b39501'),
160
+ dict(path='../../omlish/logs/standard.py', sha1='818b674f7d15012f25b79f52f6e8e7368b633038'),
161
+ dict(path='types.py', sha1='7ef67f710fb54c3af067aa596cb593f33eafe380'),
162
+ dict(path='../../omlish/http/coro/server/server.py', sha1='c0a980afa8346dbc20570acddb2b3b579bfc1ce0'),
163
+ dict(path='../../omlish/logs/base.py', sha1='a376460b11b9dc0555fd4ead5437af62c2109a4b'),
164
+ dict(path='../../omlish/logs/std/records.py', sha1='8bbf6ef9eccb3a012c6ca416ddf3969450fd8fc9'),
165
+ dict(path='dispatchers.py', sha1='33fe5ae77e33b3cfabb97b1a1c0f06dd0cc54703'),
166
+ dict(path='groupsimpl.py', sha1='4fe587a6eaff7dd874b54450be62f9689283d230'),
167
+ dict(path='process.py', sha1='ec0903adbde7552ba8a6aad9030716ef57fc4a6c'),
168
+ dict(path='../../omlish/http/coro/server/fdio.py', sha1='3f1b4865e589a336f942a763dc11ce42fa5c8857'),
169
+ dict(path='../../omlish/logs/std/loggers.py', sha1='daa35bdc4adea5006e442688017f0de3392579b7'),
170
+ dict(path='groups.py', sha1='a02a602d28793e5c84fbe7bfbcfa6ccce2ee0788'),
171
+ dict(path='spawning.py', sha1='9e65e562395ad04e3f3a314f946b7a4e58a601da'),
172
+ dict(path='../../omlish/logs/modules.py', sha1='99e73cde6872fd5eda6af3dbf0fc9322bdeb641a'),
173
+ dict(path='dispatchersimpl.py', sha1='701947899daef9f68c4277495594031cf73d9a62'),
174
+ dict(path='http.py', sha1='6a144e4c93abefc5f9cdba207e807ea75f8f2d5d'),
175
+ dict(path='io.py', sha1='6ba708a8396c212afdd1d314c9b5804c2d66646e'),
176
+ dict(path='processimpl.py', sha1='7edbbcd39a8ed1fd195c760da894620617a9d969'),
177
+ dict(path='setupimpl.py', sha1='b4b8b8c3e1d71a0e6794fb0a845181f3662a6bfd'),
178
+ dict(path='signals.py', sha1='645361d922557b5cedddbd261b3f1485b96555dd'),
179
+ dict(path='spawningimpl.py', sha1='c770e0017c2388fe59897d12fe67c3b6b7b2ca5a'),
180
+ dict(path='supervisor.py', sha1='a97a13ec71deaf6eacabb1527f373b21b89209af'),
181
+ dict(path='inject.py', sha1='6ad254bcf1c78e0b8a1d7bb3940628857e3bb60c'),
182
+ dict(path='main.py', sha1='f2b43d282aa8b3505636829b0082b16345d2bbfb'),
183
+ ],
184
+ )
185
+
186
+
101
187
  ########################################
102
188
 
103
189
 
@@ -112,7 +198,7 @@ TomlParseFloat = ta.Callable[[str], ta.Any] # ta.TypeAlias
112
198
  TomlKey = ta.Tuple[str, ...] # ta.TypeAlias
113
199
  TomlPos = int # ta.TypeAlias
114
200
 
115
- # ../../omlish/lite/attrops.py
201
+ # ../../omlish/lite/abstract.py
116
202
  T = ta.TypeVar('T')
117
203
 
118
204
  # ../../omlish/lite/cached.py
@@ -1725,6 +1811,36 @@ class HttpProtocolVersions:
1725
1811
  HTTP_2_0 = HttpProtocolVersion(2, 0)
1726
1812
 
1727
1813
 
1814
+ ########################################
1815
+ # ../../../omlish/io/readers.py
1816
+
1817
+
1818
+ ##
1819
+
1820
+
1821
+ class RawBytesReader(ta.Protocol):
1822
+ def read1(self, n: int = -1, /) -> bytes: ...
1823
+
1824
+
1825
+ class BufferedBytesReader(RawBytesReader, ta.Protocol):
1826
+ def read(self, n: int = -1, /) -> bytes: ...
1827
+
1828
+ def readall(self) -> bytes: ...
1829
+
1830
+
1831
+ #
1832
+
1833
+
1834
+ class AsyncRawBytesReader(ta.Protocol):
1835
+ def read1(self, n: int = -1, /) -> ta.Awaitable[bytes]: ...
1836
+
1837
+
1838
+ class AsyncBufferedBytesReader(AsyncRawBytesReader, ta.Protocol):
1839
+ def read(self, n: int = -1, /) -> ta.Awaitable[bytes]: ...
1840
+
1841
+ def readall(self) -> ta.Awaitable[bytes]: ...
1842
+
1843
+
1728
1844
  ########################################
1729
1845
  # ../../../omlish/lite/abstract.py
1730
1846
 
@@ -1740,25 +1856,49 @@ def is_abstract_method(obj: ta.Any) -> bool:
1740
1856
  return bool(getattr(obj, _IS_ABSTRACT_METHOD_ATTR, False))
1741
1857
 
1742
1858
 
1743
- def update_abstracts(cls, *, force=False):
1859
+ def compute_abstract_methods(cls: type) -> ta.FrozenSet[str]:
1860
+ # ~> https://github.com/python/cpython/blob/f3476c6507381ca860eec0989f53647b13517423/Modules/_abc.c#L358
1861
+
1862
+ # Stage 1: direct abstract methods
1863
+
1864
+ abstracts = {
1865
+ a
1866
+ # Get items as a list to avoid mutation issues during iteration
1867
+ for a, v in list(cls.__dict__.items())
1868
+ if is_abstract_method(v)
1869
+ }
1870
+
1871
+ # Stage 2: inherited abstract methods
1872
+
1873
+ for base in cls.__bases__:
1874
+ # Get __abstractmethods__ from base if it exists
1875
+ if (base_abstracts := getattr(base, _ABSTRACT_METHODS_ATTR, None)) is None:
1876
+ continue
1877
+
1878
+ # Iterate over abstract methods in base
1879
+ for key in base_abstracts:
1880
+ # Check if this class has an attribute with this name
1881
+ try:
1882
+ value = getattr(cls, key)
1883
+ except AttributeError:
1884
+ # Attribute not found in this class, skip
1885
+ continue
1886
+
1887
+ # Check if it's still abstract
1888
+ if is_abstract_method(value):
1889
+ abstracts.add(key)
1890
+
1891
+ return frozenset(abstracts)
1892
+
1893
+
1894
+ def update_abstracts(cls: ta.Type[T], *, force: bool = False) -> ta.Type[T]:
1744
1895
  if not force and not hasattr(cls, _ABSTRACT_METHODS_ATTR):
1745
1896
  # Per stdlib: We check for __abstractmethods__ here because cls might by a C implementation or a python
1746
1897
  # implementation (especially during testing), and we want to handle both cases.
1747
1898
  return cls
1748
1899
 
1749
- abstracts: ta.Set[str] = set()
1750
-
1751
- for scls in cls.__bases__:
1752
- for name in getattr(scls, _ABSTRACT_METHODS_ATTR, ()):
1753
- value = getattr(cls, name, None)
1754
- if getattr(value, _IS_ABSTRACT_METHOD_ATTR, False):
1755
- abstracts.add(name)
1756
-
1757
- for name, value in cls.__dict__.items():
1758
- if getattr(value, _IS_ABSTRACT_METHOD_ATTR, False):
1759
- abstracts.add(name)
1760
-
1761
- setattr(cls, _ABSTRACT_METHODS_ATTR, frozenset(abstracts))
1900
+ abstracts = compute_abstract_methods(cls)
1901
+ setattr(cls, _ABSTRACT_METHODS_ATTR, abstracts)
1762
1902
  return cls
1763
1903
 
1764
1904
 
@@ -1812,23 +1952,26 @@ class Abstract:
1812
1952
  super().__init_subclass__(**kwargs)
1813
1953
 
1814
1954
  if not (Abstract in cls.__bases__ or abc.ABC in cls.__bases__):
1815
- ams = {a: cls for a, o in cls.__dict__.items() if is_abstract_method(o)}
1816
-
1817
- seen = set(cls.__dict__)
1818
- for b in cls.__bases__:
1819
- ams.update({a: b for a in set(getattr(b, _ABSTRACT_METHODS_ATTR, [])) - seen}) # noqa
1820
- seen.update(dir(b))
1955
+ if ams := compute_abstract_methods(cls):
1956
+ amd = {
1957
+ a: mcls
1958
+ for mcls in cls.__mro__[::-1]
1959
+ for a in ams
1960
+ if a in mcls.__dict__
1961
+ }
1821
1962
 
1822
- if ams:
1823
1963
  raise AbstractTypeError(
1824
1964
  f'Cannot subclass abstract class {cls.__name__} with abstract methods: ' +
1825
1965
  ', '.join(sorted([
1826
1966
  '.'.join([
1827
- *([m] if (m := getattr(c, '__module__')) else []),
1828
- getattr(c, '__qualname__', getattr(c, '__name__')),
1967
+ *([
1968
+ *([m] if (m := getattr(c, '__module__')) else []),
1969
+ getattr(c, '__qualname__', getattr(c, '__name__')),
1970
+ ] if c is not None else '?'),
1829
1971
  a,
1830
1972
  ])
1831
- for a, c in ams.items()
1973
+ for a in ams
1974
+ for c in [amd.get(a)]
1832
1975
  ])),
1833
1976
  )
1834
1977
 
@@ -1853,6 +1996,8 @@ TODO:
1853
1996
  - per-attr repr transform / filter
1854
1997
  - __ne__ ? cases where it still matters
1855
1998
  - ordering ?
1999
+ - repr_filter: ta.Union[ta.Callable[[ta.Any], ta.Optional[str]], ta.Literal['not_none', 'truthy']]] ?
2000
+ - unify repr/repr_fn/repr_filter
1856
2001
  """
1857
2002
 
1858
2003
 
@@ -1870,6 +2015,8 @@ class AttrOps(ta.Generic[T]):
1870
2015
  display: ta.Optional[str] = None,
1871
2016
 
1872
2017
  repr: bool = True, # noqa
2018
+ repr_fn: ta.Optional[ta.Callable[[ta.Any], ta.Optional[str]]] = None,
2019
+
1873
2020
  hash: bool = True, # noqa
1874
2021
  eq: bool = True,
1875
2022
  ) -> None:
@@ -1884,6 +2031,8 @@ class AttrOps(ta.Generic[T]):
1884
2031
  self._display = display
1885
2032
 
1886
2033
  self._repr = repr
2034
+ self._repr_fn = repr_fn
2035
+
1887
2036
  self._hash = hash
1888
2037
  self._eq = eq
1889
2038
 
@@ -1891,21 +2040,30 @@ class AttrOps(ta.Generic[T]):
1891
2040
  def of(
1892
2041
  cls,
1893
2042
  o: ta.Union[
1894
- str,
1895
- ta.Tuple[str, str],
1896
2043
  'AttrOps.Attr',
2044
+ str,
2045
+ ta.Tuple[str, ta.Union[str, ta.Mapping[str, ta.Any]]],
2046
+ ta.Mapping[str, ta.Any],
1897
2047
  ],
1898
2048
  ) -> 'AttrOps.Attr':
1899
2049
  if isinstance(o, AttrOps.Attr):
1900
2050
  return o
1901
2051
  elif isinstance(o, str):
1902
2052
  return cls(o)
2053
+ elif isinstance(o, tuple):
2054
+ name, x = o
2055
+ kw: ta.Mapping[str, ta.Any]
2056
+ if isinstance(x, str):
2057
+ kw = dict(display=x)
2058
+ elif isinstance(x, ta.Mapping):
2059
+ kw = x
2060
+ else:
2061
+ raise TypeError(x)
2062
+ return cls(name, **kw)
2063
+ elif isinstance(o, ta.Mapping):
2064
+ return cls(**o)
1903
2065
  else:
1904
- name, disp = o
1905
- return cls(
1906
- name,
1907
- display=disp,
1908
- )
2066
+ raise TypeError(o)
1909
2067
 
1910
2068
  @property
1911
2069
  def name(self) -> str:
@@ -1923,19 +2081,34 @@ class AttrOps(ta.Generic[T]):
1923
2081
  def eq(self) -> bool:
1924
2082
  return self._eq
1925
2083
 
2084
+ @staticmethod
2085
+ def opt_repr(o: ta.Any) -> ta.Optional[str]:
2086
+ return repr(o) if o is not None else None
2087
+
2088
+ @staticmethod
2089
+ def truthy_repr(o: ta.Any) -> ta.Optional[str]:
2090
+ return repr(o) if o else None
2091
+
2092
+ #
2093
+
1926
2094
  @ta.overload
1927
2095
  def __init__(
1928
2096
  self,
1929
2097
  *attrs: ta.Sequence[ta.Union[
1930
2098
  str,
1931
- ta.Tuple[str, str],
2099
+ ta.Tuple[str, ta.Union[str, ta.Mapping[str, ta.Any]]],
2100
+ ta.Mapping[str, ta.Any],
1932
2101
  Attr,
1933
2102
  ]],
2103
+
1934
2104
  with_module: bool = False,
1935
2105
  use_qualname: bool = False,
1936
2106
  with_id: bool = False,
2107
+ terse: bool = False,
1937
2108
  repr_filter: ta.Optional[ta.Callable[[ta.Any], bool]] = None,
1938
2109
  recursive: bool = False,
2110
+
2111
+ cache_hash: ta.Union[bool, str] = False,
1939
2112
  subtypes_eq: bool = False,
1940
2113
  ) -> None:
1941
2114
  ...
@@ -1945,16 +2118,20 @@ class AttrOps(ta.Generic[T]):
1945
2118
  self,
1946
2119
  attrs_fn: ta.Callable[[T], ta.Tuple[ta.Union[
1947
2120
  ta.Any,
1948
- ta.Tuple[str, ta.Any],
2121
+ ta.Tuple[ta.Any, ta.Union[str, ta.Mapping[str, ta.Any]]],
1949
2122
  Attr,
1950
2123
  ], ...]],
1951
2124
  /,
1952
2125
  *,
2126
+
1953
2127
  with_module: bool = False,
1954
2128
  use_qualname: bool = False,
1955
2129
  with_id: bool = False,
2130
+ terse: bool = False,
1956
2131
  repr_filter: ta.Optional[ta.Callable[[ta.Any], bool]] = None,
1957
2132
  recursive: bool = False,
2133
+
2134
+ cache_hash: ta.Union[bool, str] = False,
1958
2135
  subtypes_eq: bool = False,
1959
2136
  ) -> None:
1960
2137
  ...
@@ -1962,11 +2139,15 @@ class AttrOps(ta.Generic[T]):
1962
2139
  def __init__(
1963
2140
  self,
1964
2141
  *args,
2142
+
1965
2143
  with_module=False,
1966
2144
  use_qualname=False,
1967
2145
  with_id=False,
2146
+ terse=False,
1968
2147
  repr_filter=None,
1969
2148
  recursive=False,
2149
+
2150
+ cache_hash=False,
1970
2151
  subtypes_eq=False,
1971
2152
  ) -> None:
1972
2153
  if args and len(args) == 1 and callable(args[0]):
@@ -1977,8 +2158,11 @@ class AttrOps(ta.Generic[T]):
1977
2158
  self._with_module: bool = with_module
1978
2159
  self._use_qualname: bool = use_qualname
1979
2160
  self._with_id: bool = with_id
2161
+ self._terse: bool = terse
1980
2162
  self._repr_filter: ta.Optional[ta.Callable[[ta.Any], bool]] = repr_filter
1981
2163
  self._recursive: bool = recursive
2164
+
2165
+ self._cache_hash: ta.Union[bool, str] = cache_hash
1982
2166
  self._subtypes_eq: bool = subtypes_eq
1983
2167
 
1984
2168
  @property
@@ -2013,20 +2197,27 @@ class AttrOps(ta.Generic[T]):
2013
2197
 
2014
2198
  attrs: ta.List[AttrOps.Attr] = []
2015
2199
  for o in raw:
2016
- if isinstance(o, AttrOps.Attr):
2017
- attrs.append(o)
2200
+ if isinstance(o, (AttrOps.Attr, ta.Mapping)):
2201
+ attrs.append(AttrOps.Attr.of(o))
2018
2202
  continue
2019
2203
 
2204
+ kw: ta.Mapping[str, ta.Any]
2020
2205
  if isinstance(o, tuple):
2021
- disp, cap, = o
2206
+ cap, x = o
2207
+ if isinstance(x, str):
2208
+ kw = dict(display=x)
2209
+ elif isinstance(x, ta.Mapping):
2210
+ kw = x
2211
+ else:
2212
+ raise TypeError(x)
2022
2213
  else:
2023
- disp, cap = None, o
2214
+ cap, kw = o, {}
2024
2215
 
2025
2216
  path = tuple(rec(cap))
2026
2217
 
2027
2218
  attrs.append(AttrOps.Attr(
2028
2219
  '.'.join(path),
2029
- display=disp,
2220
+ **kw,
2030
2221
  ))
2031
2222
 
2032
2223
  return attrs
@@ -2043,19 +2234,27 @@ class AttrOps(ta.Generic[T]):
2043
2234
  pass
2044
2235
 
2045
2236
  def _repr(o: T) -> str:
2046
- vs = ', '.join(
2047
- f'{a._display}={v!r}' # noqa
2048
- for a in self._attrs
2049
- if a._repr # noqa
2050
- for v in [getattr(o, a._name)] # noqa
2051
- if self._repr_filter is None or self._repr_filter(v)
2052
- )
2237
+ vs: ta.List[str] = []
2238
+ for a in self._attrs:
2239
+ if not a._repr: # noqa
2240
+ continue
2241
+ v = getattr(o, a._name) # noqa
2242
+ if self._repr_filter is not None and not self._repr_filter(v):
2243
+ continue
2244
+ if (rfn := a._repr_fn) is None: # noqa
2245
+ rfn = repr
2246
+ if (vr := rfn(v)) is None:
2247
+ continue
2248
+ if self._terse:
2249
+ vs.append(vr)
2250
+ else:
2251
+ vs.append(f'{a._display}={vr}') # noqa
2053
2252
 
2054
2253
  return (
2055
2254
  f'{o.__class__.__module__ + "." if self._with_module else ""}'
2056
2255
  f'{o.__class__.__qualname__ if self._use_qualname else o.__class__.__name__}'
2057
2256
  f'{("@" + hex(id(o))[2:]) if self._with_id else ""}' # noqa
2058
- f'({vs})'
2257
+ f'({", ".join(vs)})'
2059
2258
  )
2060
2259
 
2061
2260
  if self._recursive:
@@ -2080,6 +2279,8 @@ class AttrOps(ta.Generic[T]):
2080
2279
 
2081
2280
  #
2082
2281
 
2282
+ _DEFAULT_CACHED_HASH_ATTR: ta.ClassVar[str] = '__cached_hash__'
2283
+
2083
2284
  _hash: ta.Callable[[T], int]
2084
2285
 
2085
2286
  @property
@@ -2089,13 +2290,33 @@ class AttrOps(ta.Generic[T]):
2089
2290
  except AttributeError:
2090
2291
  pass
2091
2292
 
2092
- def _hash(o: T) -> int:
2293
+ def _calc_hash(o: T) -> int:
2093
2294
  return hash(tuple(
2094
2295
  getattr(o, a._name) # noqa
2095
2296
  for a in self._attrs
2096
2297
  if a._hash # noqa
2097
2298
  ))
2098
2299
 
2300
+ if (ch := self._cache_hash) is not False:
2301
+ if ch is True:
2302
+ cha = self._DEFAULT_CACHED_HASH_ATTR
2303
+ elif isinstance(ch, str):
2304
+ cha = ch
2305
+ else:
2306
+ raise TypeError(ch)
2307
+
2308
+ def _cached_hash(o: T) -> int:
2309
+ try:
2310
+ return object.__getattribute__(o, cha)
2311
+ except AttributeError:
2312
+ object.__setattr__(o, cha, h := _calc_hash(o))
2313
+ return h
2314
+
2315
+ _hash = _cached_hash
2316
+
2317
+ else:
2318
+ _hash = _calc_hash
2319
+
2099
2320
  self._hash = _hash
2100
2321
  return _hash
2101
2322
 
@@ -2236,6 +2457,62 @@ def async_cached_nullary(fn): # ta.Callable[..., T]) -> ta.Callable[..., T]:
2236
2457
  return _AsyncCachedNullary(fn)
2237
2458
 
2238
2459
 
2460
+ ##
2461
+
2462
+
2463
+ cached_property = functools.cached_property
2464
+
2465
+
2466
+ class _cached_property: # noqa
2467
+ """Backported to pick up https://github.com/python/cpython/commit/056dfc71dce15f81887f0bd6da09d6099d71f979 ."""
2468
+
2469
+ def __init__(self, func):
2470
+ self.func = func
2471
+ self.attrname = None # noqa
2472
+ self.__doc__ = func.__doc__
2473
+ self.__module__ = func.__module__
2474
+
2475
+ _NOT_FOUND = object()
2476
+
2477
+ def __set_name__(self, owner, name):
2478
+ if self.attrname is None:
2479
+ self.attrname = name # noqa
2480
+ elif name != self.attrname:
2481
+ raise TypeError(
2482
+ f'Cannot assign the same cached_property to two different names ({self.attrname!r} and {name!r}).',
2483
+ )
2484
+
2485
+ def __get__(self, instance, owner=None):
2486
+ if instance is None:
2487
+ return self
2488
+ if self.attrname is None:
2489
+ raise TypeError('Cannot use cached_property instance without calling __set_name__ on it.')
2490
+
2491
+ try:
2492
+ cache = instance.__dict__
2493
+ except AttributeError: # not all objects have __dict__ (e.g. class defines slots)
2494
+ raise TypeError(
2495
+ f"No '__dict__' attribute on {type(instance).__name__!r} instance to cache {self.attrname!r} property.",
2496
+ ) from None
2497
+
2498
+ val = cache.get(self.attrname, self._NOT_FOUND)
2499
+
2500
+ if val is self._NOT_FOUND:
2501
+ val = self.func(instance)
2502
+ try:
2503
+ cache[self.attrname] = val
2504
+ except TypeError:
2505
+ raise TypeError(
2506
+ f"The '__dict__' attribute on {type(instance).__name__!r} instance does not support item "
2507
+ f"assignment for caching {self.attrname!r} property.",
2508
+ ) from None
2509
+
2510
+ return val
2511
+
2512
+
2513
+ globals()['cached_property'] = _cached_property
2514
+
2515
+
2239
2516
  ########################################
2240
2517
  # ../../../omlish/lite/check.py
2241
2518
  """
@@ -3024,6 +3301,12 @@ def format_num_bytes(num_bytes: int) -> str:
3024
3301
 
3025
3302
  ##
3026
3303
  # A workaround for typing deficiencies (like `Argument 2 to NewType(...) must be subclassable`).
3304
+ #
3305
+ # Note that this problem doesn't happen at runtime - it happens in mypy:
3306
+ #
3307
+ # mypy <(echo "import typing as ta; MyCallback = ta.NewType('MyCallback', ta.Callable[[], None])")
3308
+ # /dev/fd/11:1:22: error: Argument 2 to NewType(...) must be subclassable (got "Callable[[], None]") [valid-newtype]
3309
+ #
3027
3310
 
3028
3311
 
3029
3312
  @dc.dataclass(frozen=True)
@@ -4114,6 +4397,72 @@ def build_config_named_children(
4114
4397
  return lst
4115
4398
 
4116
4399
 
4400
+ ########################################
4401
+ # ../../../omlish/http/coro/io.py
4402
+
4403
+
4404
+ ##
4405
+
4406
+
4407
+ class CoroHttpIo:
4408
+ def __new__(cls, *args, **kwargs): # noqa
4409
+ raise TypeError
4410
+
4411
+ def __init_subclass__(cls, **kwargs): # noqa
4412
+ raise TypeError
4413
+
4414
+ #
4415
+
4416
+ MAX_LINE: ta.ClassVar[int] = 65536
4417
+
4418
+ #
4419
+
4420
+ class Io(Abstract):
4421
+ pass
4422
+
4423
+ #
4424
+
4425
+ class AnyLogIo(Io, Abstract):
4426
+ pass
4427
+
4428
+ #
4429
+
4430
+ @dc.dataclass(frozen=True)
4431
+ class ConnectIo(Io):
4432
+ args: ta.Tuple[ta.Any, ...]
4433
+ kwargs: ta.Optional[ta.Dict[str, ta.Any]] = None
4434
+
4435
+ server_hostname: ta.Optional[str] = None
4436
+
4437
+ #
4438
+
4439
+ class CloseIo(Io):
4440
+ pass
4441
+
4442
+ #
4443
+
4444
+ class AnyReadIo(Io): # noqa
4445
+ pass
4446
+
4447
+ @dc.dataclass(frozen=True)
4448
+ class ReadIo(AnyReadIo):
4449
+ sz: ta.Optional[int]
4450
+
4451
+ @dc.dataclass(frozen=True)
4452
+ class ReadLineIo(AnyReadIo):
4453
+ sz: int
4454
+
4455
+ @dc.dataclass(frozen=True)
4456
+ class PeekIo(AnyReadIo):
4457
+ sz: int
4458
+
4459
+ #
4460
+
4461
+ @dc.dataclass(frozen=True)
4462
+ class WriteIo(Io):
4463
+ data: bytes
4464
+
4465
+
4117
4466
  ########################################
4118
4467
  # ../../../omlish/http/parsing.py
4119
4468
  # PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
@@ -4529,6 +4878,10 @@ class HttpRequestParser:
4529
4878
 
4530
4879
  ########################################
4531
4880
  # ../../../omlish/io/buffers.py
4881
+ """
4882
+ TODO:
4883
+ - overhaul and just coro-ify pyio?
4884
+ """
4532
4885
 
4533
4886
 
4534
4887
  ##
@@ -4707,6 +5060,9 @@ class ReadableListBuffer:
4707
5060
 
4708
5061
  self._lst: list[bytes] = []
4709
5062
 
5063
+ def __bool__(self) -> ta.NoReturn:
5064
+ raise TypeError("Use 'buf is not None' or 'len(buf)'.")
5065
+
4710
5066
  def __len__(self) -> int:
4711
5067
  return sum(map(len, self._lst))
4712
5068
 
@@ -4732,6 +5088,9 @@ class ReadableListBuffer:
4732
5088
 
4733
5089
  def read(self, n: ta.Optional[int] = None) -> ta.Optional[bytes]:
4734
5090
  if n is None:
5091
+ if not self._lst:
5092
+ return b''
5093
+
4735
5094
  o = b''.join(self._lst)
4736
5095
  self._lst = []
4737
5096
  return o
@@ -4770,6 +5129,110 @@ class ReadableListBuffer:
4770
5129
  r = self.read_until_(delim)
4771
5130
  return r if isinstance(r, bytes) else None
4772
5131
 
5132
+ #
5133
+
5134
+ DEFAULT_BUFFERED_READER_CHUNK_SIZE: ta.ClassVar[int] = -1
5135
+
5136
+ @ta.final
5137
+ class _BufferedBytesReader(BufferedBytesReader):
5138
+ def __init__(
5139
+ self,
5140
+ raw: RawBytesReader,
5141
+ buf: 'ReadableListBuffer',
5142
+ *,
5143
+ chunk_size: ta.Optional[int] = None,
5144
+ ) -> None:
5145
+ self._raw = raw
5146
+ self._buf = buf
5147
+ self._chunk_size = chunk_size or ReadableListBuffer.DEFAULT_BUFFERED_READER_CHUNK_SIZE
5148
+
5149
+ def read1(self, n: int = -1, /) -> bytes:
5150
+ if n < 0:
5151
+ n = self._chunk_size
5152
+ if not n:
5153
+ return b''
5154
+ if 0 < n <= len(self._buf):
5155
+ return self._buf.read(n) or b''
5156
+ return self._raw.read1(n)
5157
+
5158
+ def read(self, /, n: int = -1) -> bytes:
5159
+ if n < 0:
5160
+ return self.readall()
5161
+ while len(self._buf) < n:
5162
+ if not (b := self._raw.read1(n)):
5163
+ break
5164
+ self._buf.feed(b)
5165
+ return self._buf.read(n) or b''
5166
+
5167
+ def readall(self) -> bytes:
5168
+ buf = io.BytesIO()
5169
+ buf.write(self._buf.read() or b'')
5170
+ while (b := self._raw.read1(self._chunk_size)):
5171
+ buf.write(b)
5172
+ return buf.getvalue()
5173
+
5174
+ def new_buffered_reader(
5175
+ self,
5176
+ raw: RawBytesReader,
5177
+ *,
5178
+ chunk_size: ta.Optional[int] = None,
5179
+ ) -> BufferedBytesReader:
5180
+ return self._BufferedBytesReader(
5181
+ raw,
5182
+ self,
5183
+ chunk_size=chunk_size,
5184
+ )
5185
+
5186
+ @ta.final
5187
+ class _AsyncBufferedBytesReader(AsyncBufferedBytesReader):
5188
+ def __init__(
5189
+ self,
5190
+ raw: AsyncRawBytesReader,
5191
+ buf: 'ReadableListBuffer',
5192
+ *,
5193
+ chunk_size: ta.Optional[int] = None,
5194
+ ) -> None:
5195
+ self._raw = raw
5196
+ self._buf = buf
5197
+ self._chunk_size = chunk_size or ReadableListBuffer.DEFAULT_BUFFERED_READER_CHUNK_SIZE
5198
+
5199
+ async def read1(self, n: int = -1, /) -> bytes:
5200
+ if n < 0:
5201
+ n = self._chunk_size
5202
+ if not n:
5203
+ return b''
5204
+ if 0 < n <= len(self._buf):
5205
+ return self._buf.read(n) or b''
5206
+ return await self._raw.read1(n)
5207
+
5208
+ async def read(self, /, n: int = -1) -> bytes:
5209
+ if n < 0:
5210
+ return await self.readall()
5211
+ while len(self._buf) < n:
5212
+ if not (b := await self._raw.read1(n)):
5213
+ break
5214
+ self._buf.feed(b)
5215
+ return self._buf.read(n) or b''
5216
+
5217
+ async def readall(self) -> bytes:
5218
+ buf = io.BytesIO()
5219
+ buf.write(self._buf.read() or b'')
5220
+ while b := await self._raw.read1(self._chunk_size):
5221
+ buf.write(b)
5222
+ return buf.getvalue()
5223
+
5224
+ def new_async_buffered_reader(
5225
+ self,
5226
+ raw: AsyncRawBytesReader,
5227
+ *,
5228
+ chunk_size: ta.Optional[int] = None,
5229
+ ) -> AsyncBufferedBytesReader:
5230
+ return self._AsyncBufferedBytesReader(
5231
+ raw,
5232
+ self,
5233
+ chunk_size=chunk_size,
5234
+ )
5235
+
4773
5236
 
4774
5237
  ##
4775
5238
 
@@ -6014,8 +6477,6 @@ class _JustMaybe(_Maybe[T]):
6014
6477
  __slots__ = ('_v', '_hash')
6015
6478
 
6016
6479
  def __init__(self, v: T) -> None:
6017
- super().__init__()
6018
-
6019
6480
  self._v = v
6020
6481
 
6021
6482
  @property
@@ -6073,6 +6534,13 @@ class _EmptyMaybe(_Maybe[T]):
6073
6534
  Maybe._empty = _EmptyMaybe() # noqa
6074
6535
 
6075
6536
 
6537
+ ##
6538
+
6539
+
6540
+ setattr(Maybe, 'just', _JustMaybe) # noqa
6541
+ setattr(Maybe, 'empty', functools.partial(operator.attrgetter('_empty'), Maybe))
6542
+
6543
+
6076
6544
  ########################################
6077
6545
  # ../../../omlish/lite/runtime.py
6078
6546
 
@@ -9392,48 +9860,21 @@ class CoroHttpServer:
9392
9860
 
9393
9861
  #
9394
9862
 
9395
- class Io(Abstract):
9396
- pass
9397
-
9398
- #
9399
-
9400
- class AnyLogIo(Io):
9401
- pass
9402
-
9403
9863
  @dc.dataclass(frozen=True)
9404
- class ParsedRequestLogIo(AnyLogIo):
9864
+ class ParsedRequestLogIo(CoroHttpIo.AnyLogIo):
9405
9865
  request: ParsedHttpRequest
9406
9866
 
9407
9867
  @dc.dataclass(frozen=True)
9408
- class ErrorLogIo(AnyLogIo):
9868
+ class ErrorLogIo(CoroHttpIo.AnyLogIo):
9409
9869
  error: 'CoroHttpServer.Error'
9410
9870
 
9411
9871
  #
9412
9872
 
9413
- class AnyReadIo(Io): # noqa
9414
- pass
9415
-
9416
- @dc.dataclass(frozen=True)
9417
- class ReadIo(AnyReadIo):
9418
- sz: int
9419
-
9420
- @dc.dataclass(frozen=True)
9421
- class ReadLineIo(AnyReadIo):
9422
- sz: int
9423
-
9424
- #
9425
-
9426
- @dc.dataclass(frozen=True)
9427
- class WriteIo(Io):
9428
- data: bytes
9429
-
9430
- #
9431
-
9432
9873
  @dc.dataclass(frozen=True)
9433
9874
  class CoroHandleResult:
9434
9875
  close_reason: ta.Literal['response', 'internal', None] = None
9435
9876
 
9436
- def coro_handle(self) -> ta.Generator[Io, ta.Optional[bytes], CoroHandleResult]:
9877
+ def coro_handle(self) -> ta.Generator[CoroHttpIo.Io, ta.Optional[bytes], CoroHandleResult]:
9437
9878
  return self._coro_run_handler(self._coro_handle_one())
9438
9879
 
9439
9880
  class Close(Exception): # noqa
@@ -9442,20 +9883,20 @@ class CoroHttpServer:
9442
9883
  def _coro_run_handler(
9443
9884
  self,
9444
9885
  gen: ta.Generator[
9445
- ta.Union[AnyLogIo, AnyReadIo, _Response],
9886
+ ta.Union[CoroHttpIo.AnyLogIo, CoroHttpIo.AnyReadIo, _Response],
9446
9887
  ta.Optional[bytes],
9447
9888
  None,
9448
9889
  ],
9449
- ) -> ta.Generator[Io, ta.Optional[bytes], CoroHandleResult]:
9890
+ ) -> ta.Generator[CoroHttpIo.Io, ta.Optional[bytes], CoroHandleResult]:
9450
9891
  i: ta.Optional[bytes]
9451
9892
  o: ta.Any = next(gen)
9452
9893
  while True:
9453
9894
  try:
9454
- if isinstance(o, self.AnyLogIo):
9895
+ if isinstance(o, CoroHttpIo.AnyLogIo):
9455
9896
  i = None
9456
9897
  yield o
9457
9898
 
9458
- elif isinstance(o, self.AnyReadIo):
9899
+ elif isinstance(o, CoroHttpIo.AnyReadIo):
9459
9900
  i = check.isinstance((yield o), bytes)
9460
9901
 
9461
9902
  elif isinstance(o, self._Response):
@@ -9463,10 +9904,10 @@ class CoroHttpServer:
9463
9904
 
9464
9905
  r = self._preprocess_response(o)
9465
9906
  hb = self._build_response_head_bytes(r)
9466
- check.none((yield self.WriteIo(hb)))
9907
+ check.none((yield CoroHttpIo.WriteIo(hb)))
9467
9908
 
9468
9909
  for b in self._yield_response_data(r):
9469
- yield self.WriteIo(b)
9910
+ yield CoroHttpIo.WriteIo(b)
9470
9911
 
9471
9912
  o.close()
9472
9913
  if o.close_connection:
@@ -9494,7 +9935,7 @@ class CoroHttpServer:
9494
9935
  raise
9495
9936
 
9496
9937
  def _coro_handle_one(self) -> ta.Generator[
9497
- ta.Union[AnyLogIo, AnyReadIo, _Response],
9938
+ ta.Union[CoroHttpIo.AnyLogIo, CoroHttpIo.AnyReadIo, _Response],
9498
9939
  ta.Optional[bytes],
9499
9940
  None,
9500
9941
  ]:
@@ -9504,7 +9945,7 @@ class CoroHttpServer:
9504
9945
  sz = next(gen)
9505
9946
  while True:
9506
9947
  try:
9507
- line = check.isinstance((yield self.ReadLineIo(sz)), bytes)
9948
+ line = check.isinstance((yield CoroHttpIo.ReadLineIo(sz)), bytes)
9508
9949
  sz = gen.send(line)
9509
9950
  except StopIteration as e:
9510
9951
  parsed = e.value
@@ -9543,7 +9984,7 @@ class CoroHttpServer:
9543
9984
 
9544
9985
  request_data: ta.Optional[bytes]
9545
9986
  if (cl := parsed.headers.get('Content-Length')) is not None:
9546
- request_data = check.isinstance((yield self.ReadIo(int(cl))), bytes)
9987
+ request_data = check.isinstance((yield CoroHttpIo.ReadIo(int(cl))), bytes)
9547
9988
  else:
9548
9989
  request_data = None
9549
9990
 
@@ -10603,7 +11044,7 @@ class CoroHttpServerConnectionFdioHandler(SocketFdioHandler):
10603
11044
  *,
10604
11045
  read_size: int = 0x10000,
10605
11046
  write_size: int = 0x10000,
10606
- log_handler: ta.Optional[ta.Callable[[CoroHttpServer, CoroHttpServer.AnyLogIo], None]] = None,
11047
+ log_handler: ta.Optional[ta.Callable[[CoroHttpServer, CoroHttpIo.AnyLogIo], None]] = None,
10607
11048
  ) -> None:
10608
11049
  check.state(not sock.getblocking())
10609
11050
 
@@ -10623,13 +11064,13 @@ class CoroHttpServerConnectionFdioHandler(SocketFdioHandler):
10623
11064
  )
10624
11065
  self._srv_coro: ta.Optional[
10625
11066
  ta.Generator[
10626
- CoroHttpServer.Io,
11067
+ CoroHttpIo.Io,
10627
11068
  ta.Optional[bytes],
10628
11069
  CoroHttpServer.CoroHandleResult,
10629
11070
  ],
10630
11071
  ] = self._coro_srv.coro_handle()
10631
11072
 
10632
- self._cur_io: ta.Optional[CoroHttpServer.Io] = None
11073
+ self._cur_io: ta.Optional[CoroHttpIo.Io] = None
10633
11074
  self._next_io()
10634
11075
 
10635
11076
  #
@@ -10652,22 +11093,22 @@ class CoroHttpServerConnectionFdioHandler(SocketFdioHandler):
10652
11093
  o = None
10653
11094
  break
10654
11095
 
10655
- if isinstance(o, CoroHttpServer.AnyLogIo):
11096
+ if isinstance(o, CoroHttpIo.AnyLogIo):
10656
11097
  if self._log_handler is not None:
10657
11098
  self._log_handler(self._coro_srv, o)
10658
11099
  o = None
10659
11100
 
10660
- elif isinstance(o, CoroHttpServer.ReadIo):
11101
+ elif isinstance(o, CoroHttpIo.ReadIo):
10661
11102
  if (d := self._read_buf.read(o.sz)) is None:
10662
11103
  break
10663
11104
  o = None
10664
11105
 
10665
- elif isinstance(o, CoroHttpServer.ReadLineIo):
11106
+ elif isinstance(o, CoroHttpIo.ReadLineIo):
10666
11107
  if (d := self._read_buf.read_until(b'\n')) is None:
10667
11108
  break
10668
11109
  o = None
10669
11110
 
10670
- elif isinstance(o, CoroHttpServer.WriteIo):
11111
+ elif isinstance(o, CoroHttpIo.WriteIo):
10671
11112
  check.none(self._write_buf)
10672
11113
  self._write_buf = IncrementalWriteBuffer(o.data, write_size=self._write_size)
10673
11114
  break
@@ -10701,11 +11142,11 @@ class CoroHttpServerConnectionFdioHandler(SocketFdioHandler):
10701
11142
 
10702
11143
  self._read_buf.feed(buf)
10703
11144
 
10704
- if isinstance(self._cur_io, CoroHttpServer.AnyReadIo):
11145
+ if isinstance(self._cur_io, CoroHttpIo.AnyReadIo):
10705
11146
  self._next_io()
10706
11147
 
10707
11148
  def on_writable(self) -> None:
10708
- check.isinstance(self._cur_io, CoroHttpServer.WriteIo)
11149
+ check.isinstance(self._cur_io, CoroHttpIo.WriteIo)
10709
11150
  wb = check.not_none(self._write_buf)
10710
11151
  while wb.rem > 0:
10711
11152
  def send(d: bytes) -> int: