ominfra 0.0.0.dev149__py3-none-any.whl → 0.0.0.dev150__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.
@@ -34,6 +34,7 @@
34
34
  # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35
35
  import abc
36
36
  import base64
37
+ import collections
37
38
  import collections.abc
38
39
  import contextlib
39
40
  import ctypes as ct
@@ -105,6 +106,11 @@ CallableT = ta.TypeVar('CallableT', bound=ta.Callable)
105
106
 
106
107
  # ../../omlish/lite/check.py
107
108
  SizedT = ta.TypeVar('SizedT', bound=ta.Sized)
109
+ CheckMessage = ta.Union[str, ta.Callable[..., ta.Optional[str]], None] # ta.TypeAlias
110
+ CheckLateConfigureFn = ta.Callable[['Checks'], None]
111
+ CheckOnRaiseFn = ta.Callable[[Exception], None] # ta.TypeAlias
112
+ CheckExceptionFactory = ta.Callable[..., Exception] # ta.TypeAlias
113
+ CheckArgsRenderer = ta.Callable[..., ta.Optional[str]] # ta.TypeAlias
108
114
 
109
115
  # ../../omlish/lite/socket.py
110
116
  SocketAddress = ta.Any
@@ -119,12 +125,12 @@ A2 = ta.TypeVar('A2')
119
125
  EventCallback = ta.Callable[['Event'], None]
120
126
  ProcessOutputChannel = ta.Literal['stdout', 'stderr'] # ta.TypeAlias
121
127
 
128
+ # ../../omlish/http/parsing.py
129
+ HttpHeaders = http.client.HTTPMessage # ta.TypeAlias
130
+
122
131
  # ../../omlish/lite/contextmanagers.py
123
132
  ExitStackedT = ta.TypeVar('ExitStackedT', bound='ExitStacked')
124
133
 
125
- # ../../omlish/lite/http/parsing.py
126
- HttpHeaders = http.client.HTTPMessage # ta.TypeAlias
127
-
128
134
  # ../../omlish/lite/inject.py
129
135
  U = ta.TypeVar('U')
130
136
  InjectorKeyCls = ta.Union[type, ta.NewType]
@@ -135,10 +141,10 @@ InjectorBindingOrBindings = ta.Union['InjectorBinding', 'InjectorBindings']
135
141
  # ../configs.py
136
142
  ConfigMapping = ta.Mapping[str, ta.Any]
137
143
 
138
- # ../../omlish/lite/http/handlers.py
144
+ # ../../omlish/http/handlers.py
139
145
  HttpHandler = ta.Callable[['HttpHandlerRequest'], 'HttpHandlerResponse']
140
146
 
141
- # ../../omlish/lite/http/coroserver.py
147
+ # ../../omlish/http/coroserver.py
142
148
  CoroHttpServerFactory = ta.Callable[[SocketAddress], 'CoroHttpServer']
143
149
 
144
150
 
@@ -1430,6 +1436,25 @@ def parse_octal(arg: ta.Union[str, int]) -> int:
1430
1436
  raise ValueError(f'{arg} can not be converted to an octal type') # noqa
1431
1437
 
1432
1438
 
1439
+ ########################################
1440
+ # ../../../omlish/http/versions.py
1441
+
1442
+
1443
+ class HttpProtocolVersion(ta.NamedTuple):
1444
+ major: int
1445
+ minor: int
1446
+
1447
+ def __str__(self) -> str:
1448
+ return f'HTTP/{self.major}.{self.minor}'
1449
+
1450
+
1451
+ class HttpProtocolVersions:
1452
+ HTTP_0_9 = HttpProtocolVersion(0, 9)
1453
+ HTTP_1_0 = HttpProtocolVersion(1, 0)
1454
+ HTTP_1_1 = HttpProtocolVersion(1, 1)
1455
+ HTTP_2_0 = HttpProtocolVersion(2, 0)
1456
+
1457
+
1433
1458
  ########################################
1434
1459
  # ../../../omlish/io/fdio/pollers.py
1435
1460
 
@@ -1698,123 +1723,454 @@ def async_cached_nullary(fn): # ta.Callable[..., T]) -> ta.Callable[..., T]:
1698
1723
 
1699
1724
  ########################################
1700
1725
  # ../../../omlish/lite/check.py
1726
+ """
1727
+ TODO:
1728
+ - def maybe(v: lang.Maybe[T])
1729
+ - patch / override lite.check ?
1730
+ - checker interface?
1731
+ """
1732
+
1733
+
1734
+ ##
1701
1735
 
1702
1736
 
1703
- def check_isinstance(v: ta.Any, spec: ta.Union[ta.Type[T], tuple]) -> T:
1704
- if not isinstance(v, spec):
1705
- raise TypeError(v)
1706
- return v
1737
+ class Checks:
1738
+ def __init__(self) -> None:
1739
+ super().__init__()
1707
1740
 
1741
+ self._config_lock = threading.RLock()
1742
+ self._on_raise_fns: ta.Sequence[CheckOnRaiseFn] = []
1743
+ self._exception_factory: CheckExceptionFactory = Checks.default_exception_factory
1744
+ self._args_renderer: ta.Optional[CheckArgsRenderer] = None
1745
+ self._late_configure_fns: ta.Sequence[CheckLateConfigureFn] = []
1746
+
1747
+ @staticmethod
1748
+ def default_exception_factory(exc_cls: ta.Type[Exception], *args, **kwargs) -> Exception:
1749
+ return exc_cls(*args, **kwargs) # noqa
1708
1750
 
1709
- def check_not_isinstance(v: T, spec: ta.Union[type, tuple]) -> T:
1710
- if isinstance(v, spec):
1711
- raise TypeError(v)
1712
- return v
1751
+ #
1713
1752
 
1753
+ def register_on_raise(self, fn: CheckOnRaiseFn) -> None:
1754
+ with self._config_lock:
1755
+ self._on_raise_fns = [*self._on_raise_fns, fn]
1714
1756
 
1715
- def check_none(v: T) -> None:
1716
- if v is not None:
1717
- raise ValueError(v)
1757
+ def unregister_on_raise(self, fn: CheckOnRaiseFn) -> None:
1758
+ with self._config_lock:
1759
+ self._on_raise_fns = [e for e in self._on_raise_fns if e != fn]
1718
1760
 
1761
+ #
1719
1762
 
1720
- def check_not_none(v: ta.Optional[T]) -> T:
1721
- if v is None:
1722
- raise ValueError
1723
- return v
1763
+ def set_exception_factory(self, factory: CheckExceptionFactory) -> None:
1764
+ self._exception_factory = factory
1724
1765
 
1766
+ def set_args_renderer(self, renderer: ta.Optional[CheckArgsRenderer]) -> None:
1767
+ self._args_renderer = renderer
1725
1768
 
1726
- def check_not(v: ta.Any) -> None:
1727
- if v:
1728
- raise ValueError(v)
1729
- return v
1769
+ #
1730
1770
 
1771
+ def register_late_configure(self, fn: CheckLateConfigureFn) -> None:
1772
+ with self._config_lock:
1773
+ self._late_configure_fns = [*self._late_configure_fns, fn]
1731
1774
 
1732
- def check_non_empty_str(v: ta.Optional[str]) -> str:
1733
- if not v:
1734
- raise ValueError
1735
- return v
1775
+ def _late_configure(self) -> None:
1776
+ if not self._late_configure_fns:
1777
+ return
1736
1778
 
1779
+ with self._config_lock:
1780
+ if not (lc := self._late_configure_fns):
1781
+ return
1737
1782
 
1738
- def check_arg(v: bool, msg: str = 'Illegal argument') -> None:
1739
- if not v:
1740
- raise ValueError(msg)
1783
+ for fn in lc:
1784
+ fn(self)
1741
1785
 
1786
+ self._late_configure_fns = []
1742
1787
 
1743
- def check_state(v: bool, msg: str = 'Illegal state') -> None:
1744
- if not v:
1745
- raise ValueError(msg)
1788
+ #
1746
1789
 
1790
+ class _ArgsKwargs:
1791
+ def __init__(self, *args, **kwargs):
1792
+ self.args = args
1793
+ self.kwargs = kwargs
1747
1794
 
1748
- def check_equal(l: T, r: T) -> T:
1749
- if l != r:
1750
- raise ValueError(l, r)
1751
- return l
1795
+ def _raise(
1796
+ self,
1797
+ exception_type: ta.Type[Exception],
1798
+ default_message: str,
1799
+ message: CheckMessage,
1800
+ ak: _ArgsKwargs = _ArgsKwargs(),
1801
+ *,
1802
+ render_fmt: ta.Optional[str] = None,
1803
+ ) -> ta.NoReturn:
1804
+ exc_args = ()
1805
+ if callable(message):
1806
+ message = ta.cast(ta.Callable, message)(*ak.args, **ak.kwargs)
1807
+ if isinstance(message, tuple):
1808
+ message, *exc_args = message # type: ignore
1752
1809
 
1810
+ if message is None:
1811
+ message = default_message
1753
1812
 
1754
- def check_not_equal(l: T, r: T) -> T:
1755
- if l == r:
1756
- raise ValueError(l, r)
1757
- return l
1813
+ self._late_configure()
1758
1814
 
1815
+ if render_fmt is not None and (af := self._args_renderer) is not None:
1816
+ rendered_args = af(render_fmt, *ak.args)
1817
+ if rendered_args is not None:
1818
+ message = f'{message} : {rendered_args}'
1759
1819
 
1760
- def check_is(l: T, r: T) -> T:
1761
- if l is not r:
1762
- raise ValueError(l, r)
1763
- return l
1820
+ exc = self._exception_factory(
1821
+ exception_type,
1822
+ message,
1823
+ *exc_args,
1824
+ *ak.args,
1825
+ **ak.kwargs,
1826
+ )
1764
1827
 
1828
+ for fn in self._on_raise_fns:
1829
+ fn(exc)
1765
1830
 
1766
- def check_is_not(l: T, r: ta.Any) -> T:
1767
- if l is r:
1768
- raise ValueError(l, r)
1769
- return l
1831
+ raise exc
1770
1832
 
1833
+ #
1771
1834
 
1772
- def check_in(v: T, c: ta.Container[T]) -> T:
1773
- if v not in c:
1774
- raise ValueError(v, c)
1775
- return v
1835
+ def _unpack_isinstance_spec(self, spec: ta.Any) -> tuple:
1836
+ if isinstance(spec, type):
1837
+ return (spec,)
1838
+ if not isinstance(spec, tuple):
1839
+ spec = (spec,)
1840
+ if None in spec:
1841
+ spec = tuple(filter(None, spec)) + (None.__class__,) # noqa
1842
+ if ta.Any in spec:
1843
+ spec = (object,)
1844
+ return spec
1845
+
1846
+ def isinstance(self, v: ta.Any, spec: ta.Union[ta.Type[T], tuple], msg: CheckMessage = None) -> T: # noqa
1847
+ if not isinstance(v, self._unpack_isinstance_spec(spec)):
1848
+ self._raise(
1849
+ TypeError,
1850
+ 'Must be instance',
1851
+ msg,
1852
+ Checks._ArgsKwargs(v, spec),
1853
+ render_fmt='not isinstance(%s, %s)',
1854
+ )
1776
1855
 
1856
+ return v
1777
1857
 
1778
- def check_not_in(v: T, c: ta.Container[T]) -> T:
1779
- if v in c:
1780
- raise ValueError(v, c)
1781
- return v
1858
+ def of_isinstance(self, spec: ta.Union[ta.Type[T], tuple], msg: CheckMessage = None) -> ta.Callable[[ta.Any], T]:
1859
+ def inner(v):
1860
+ return self.isinstance(v, self._unpack_isinstance_spec(spec), msg)
1782
1861
 
1862
+ return inner
1783
1863
 
1784
- def check_single(vs: ta.Iterable[T]) -> T:
1785
- [v] = vs
1786
- return v
1864
+ def cast(self, v: ta.Any, cls: ta.Type[T], msg: CheckMessage = None) -> T: # noqa
1865
+ if not isinstance(v, cls):
1866
+ self._raise(
1867
+ TypeError,
1868
+ 'Must be instance',
1869
+ msg,
1870
+ Checks._ArgsKwargs(v, cls),
1871
+ )
1787
1872
 
1873
+ return v
1788
1874
 
1789
- def check_empty(v: SizedT) -> SizedT:
1790
- if len(v):
1791
- raise ValueError(v)
1792
- return v
1875
+ def of_cast(self, cls: ta.Type[T], msg: CheckMessage = None) -> ta.Callable[[T], T]:
1876
+ def inner(v):
1877
+ return self.cast(v, cls, msg)
1793
1878
 
1879
+ return inner
1794
1880
 
1795
- def check_not_empty(v: SizedT) -> SizedT:
1796
- if not len(v):
1797
- raise ValueError(v)
1798
- return v
1881
+ def not_isinstance(self, v: T, spec: ta.Any, msg: CheckMessage = None) -> T: # noqa
1882
+ if isinstance(v, self._unpack_isinstance_spec(spec)):
1883
+ self._raise(
1884
+ TypeError,
1885
+ 'Must not be instance',
1886
+ msg,
1887
+ Checks._ArgsKwargs(v, spec),
1888
+ render_fmt='isinstance(%s, %s)',
1889
+ )
1799
1890
 
1891
+ return v
1800
1892
 
1801
- ########################################
1802
- # ../../../omlish/lite/http/versions.py
1893
+ def of_not_isinstance(self, spec: ta.Any, msg: CheckMessage = None) -> ta.Callable[[T], T]:
1894
+ def inner(v):
1895
+ return self.not_isinstance(v, self._unpack_isinstance_spec(spec), msg)
1803
1896
 
1897
+ return inner
1804
1898
 
1805
- class HttpProtocolVersion(ta.NamedTuple):
1806
- major: int
1807
- minor: int
1899
+ ##
1808
1900
 
1809
- def __str__(self) -> str:
1810
- return f'HTTP/{self.major}.{self.minor}'
1901
+ def issubclass(self, v: ta.Type[T], spec: ta.Any, msg: CheckMessage = None) -> ta.Type[T]: # noqa
1902
+ if not issubclass(v, spec):
1903
+ self._raise(
1904
+ TypeError,
1905
+ 'Must be subclass',
1906
+ msg,
1907
+ Checks._ArgsKwargs(v, spec),
1908
+ render_fmt='not issubclass(%s, %s)',
1909
+ )
1811
1910
 
1911
+ return v
1812
1912
 
1813
- class HttpProtocolVersions:
1814
- HTTP_0_9 = HttpProtocolVersion(0, 9)
1815
- HTTP_1_0 = HttpProtocolVersion(1, 0)
1816
- HTTP_1_1 = HttpProtocolVersion(1, 1)
1817
- HTTP_2_0 = HttpProtocolVersion(2, 0)
1913
+ def not_issubclass(self, v: ta.Type[T], spec: ta.Any, msg: CheckMessage = None) -> ta.Type[T]: # noqa
1914
+ if issubclass(v, spec):
1915
+ self._raise(
1916
+ TypeError,
1917
+ 'Must not be subclass',
1918
+ msg,
1919
+ Checks._ArgsKwargs(v, spec),
1920
+ render_fmt='issubclass(%s, %s)',
1921
+ )
1922
+
1923
+ return v
1924
+
1925
+ #
1926
+
1927
+ def in_(self, v: T, c: ta.Container[T], msg: CheckMessage = None) -> T:
1928
+ if v not in c:
1929
+ self._raise(
1930
+ ValueError,
1931
+ 'Must be in',
1932
+ msg,
1933
+ Checks._ArgsKwargs(v, c),
1934
+ render_fmt='%s not in %s',
1935
+ )
1936
+
1937
+ return v
1938
+
1939
+ def not_in(self, v: T, c: ta.Container[T], msg: CheckMessage = None) -> T:
1940
+ if v in c:
1941
+ self._raise(
1942
+ ValueError,
1943
+ 'Must not be in',
1944
+ msg,
1945
+ Checks._ArgsKwargs(v, c),
1946
+ render_fmt='%s in %s',
1947
+ )
1948
+
1949
+ return v
1950
+
1951
+ def empty(self, v: SizedT, msg: CheckMessage = None) -> SizedT:
1952
+ if len(v) != 0:
1953
+ self._raise(
1954
+ ValueError,
1955
+ 'Must be empty',
1956
+ msg,
1957
+ Checks._ArgsKwargs(v),
1958
+ render_fmt='%s',
1959
+ )
1960
+
1961
+ return v
1962
+
1963
+ def iterempty(self, v: ta.Iterable[T], msg: CheckMessage = None) -> ta.Iterable[T]:
1964
+ it = iter(v)
1965
+ try:
1966
+ next(it)
1967
+ except StopIteration:
1968
+ pass
1969
+ else:
1970
+ self._raise(
1971
+ ValueError,
1972
+ 'Must be empty',
1973
+ msg,
1974
+ Checks._ArgsKwargs(v),
1975
+ render_fmt='%s',
1976
+ )
1977
+
1978
+ return v
1979
+
1980
+ def not_empty(self, v: SizedT, msg: CheckMessage = None) -> SizedT:
1981
+ if len(v) == 0:
1982
+ self._raise(
1983
+ ValueError,
1984
+ 'Must not be empty',
1985
+ msg,
1986
+ Checks._ArgsKwargs(v),
1987
+ render_fmt='%s',
1988
+ )
1989
+
1990
+ return v
1991
+
1992
+ def unique(self, it: ta.Iterable[T], msg: CheckMessage = None) -> ta.Iterable[T]:
1993
+ dupes = [e for e, c in collections.Counter(it).items() if c > 1]
1994
+ if dupes:
1995
+ self._raise(
1996
+ ValueError,
1997
+ 'Must be unique',
1998
+ msg,
1999
+ Checks._ArgsKwargs(it, dupes),
2000
+ )
2001
+
2002
+ return it
2003
+
2004
+ def single(self, obj: ta.Iterable[T], message: CheckMessage = None) -> T:
2005
+ try:
2006
+ [value] = obj
2007
+ except ValueError:
2008
+ self._raise(
2009
+ ValueError,
2010
+ 'Must be single',
2011
+ message,
2012
+ Checks._ArgsKwargs(obj),
2013
+ render_fmt='%s',
2014
+ )
2015
+
2016
+ return value
2017
+
2018
+ def opt_single(self, obj: ta.Iterable[T], message: CheckMessage = None) -> ta.Optional[T]:
2019
+ it = iter(obj)
2020
+ try:
2021
+ value = next(it)
2022
+ except StopIteration:
2023
+ return None
2024
+
2025
+ try:
2026
+ next(it)
2027
+ except StopIteration:
2028
+ return value # noqa
2029
+
2030
+ self._raise(
2031
+ ValueError,
2032
+ 'Must be empty or single',
2033
+ message,
2034
+ Checks._ArgsKwargs(obj),
2035
+ render_fmt='%s',
2036
+ )
2037
+
2038
+ raise RuntimeError # noqa
2039
+
2040
+ #
2041
+
2042
+ def none(self, v: ta.Any, msg: CheckMessage = None) -> None:
2043
+ if v is not None:
2044
+ self._raise(
2045
+ ValueError,
2046
+ 'Must be None',
2047
+ msg,
2048
+ Checks._ArgsKwargs(v),
2049
+ render_fmt='%s',
2050
+ )
2051
+
2052
+ def not_none(self, v: ta.Optional[T], msg: CheckMessage = None) -> T:
2053
+ if v is None:
2054
+ self._raise(
2055
+ ValueError,
2056
+ 'Must not be None',
2057
+ msg,
2058
+ Checks._ArgsKwargs(v),
2059
+ render_fmt='%s',
2060
+ )
2061
+
2062
+ return v
2063
+
2064
+ #
2065
+
2066
+ def equal(self, v: T, o: ta.Any, msg: CheckMessage = None) -> T:
2067
+ if o != v:
2068
+ self._raise(
2069
+ ValueError,
2070
+ 'Must be equal',
2071
+ msg,
2072
+ Checks._ArgsKwargs(v, o),
2073
+ render_fmt='%s != %s',
2074
+ )
2075
+
2076
+ return v
2077
+
2078
+ def is_(self, v: T, o: ta.Any, msg: CheckMessage = None) -> T:
2079
+ if o is not v:
2080
+ self._raise(
2081
+ ValueError,
2082
+ 'Must be the same',
2083
+ msg,
2084
+ Checks._ArgsKwargs(v, o),
2085
+ render_fmt='%s is not %s',
2086
+ )
2087
+
2088
+ return v
2089
+
2090
+ def is_not(self, v: T, o: ta.Any, msg: CheckMessage = None) -> T:
2091
+ if o is v:
2092
+ self._raise(
2093
+ ValueError,
2094
+ 'Must not be the same',
2095
+ msg,
2096
+ Checks._ArgsKwargs(v, o),
2097
+ render_fmt='%s is %s',
2098
+ )
2099
+
2100
+ return v
2101
+
2102
+ def callable(self, v: T, msg: CheckMessage = None) -> T: # noqa
2103
+ if not callable(v):
2104
+ self._raise(
2105
+ TypeError,
2106
+ 'Must be callable',
2107
+ msg,
2108
+ Checks._ArgsKwargs(v),
2109
+ render_fmt='%s',
2110
+ )
2111
+
2112
+ return v # type: ignore
2113
+
2114
+ def non_empty_str(self, v: ta.Optional[str], msg: CheckMessage = None) -> str:
2115
+ if not isinstance(v, str) or not v:
2116
+ self._raise(
2117
+ ValueError,
2118
+ 'Must be non-empty str',
2119
+ msg,
2120
+ Checks._ArgsKwargs(v),
2121
+ render_fmt='%s',
2122
+ )
2123
+
2124
+ return v
2125
+
2126
+ def replacing(self, expected: ta.Any, old: ta.Any, new: T, msg: CheckMessage = None) -> T:
2127
+ if old != expected:
2128
+ self._raise(
2129
+ ValueError,
2130
+ 'Must be replacing',
2131
+ msg,
2132
+ Checks._ArgsKwargs(expected, old, new),
2133
+ render_fmt='%s -> %s -> %s',
2134
+ )
2135
+
2136
+ return new
2137
+
2138
+ def replacing_none(self, old: ta.Any, new: T, msg: CheckMessage = None) -> T:
2139
+ if old is not None:
2140
+ self._raise(
2141
+ ValueError,
2142
+ 'Must be replacing None',
2143
+ msg,
2144
+ Checks._ArgsKwargs(old, new),
2145
+ render_fmt='%s -> %s',
2146
+ )
2147
+
2148
+ return new
2149
+
2150
+ #
2151
+
2152
+ def arg(self, v: bool, msg: CheckMessage = None) -> None:
2153
+ if not v:
2154
+ self._raise(
2155
+ RuntimeError,
2156
+ 'Argument condition not met',
2157
+ msg,
2158
+ Checks._ArgsKwargs(v),
2159
+ render_fmt='%s',
2160
+ )
2161
+
2162
+ def state(self, v: bool, msg: CheckMessage = None) -> None:
2163
+ if not v:
2164
+ self._raise(
2165
+ RuntimeError,
2166
+ 'State condition not met',
2167
+ msg,
2168
+ Checks._ArgsKwargs(v),
2169
+ render_fmt='%s',
2170
+ )
2171
+
2172
+
2173
+ check = Checks()
1818
2174
 
1819
2175
 
1820
2176
  ########################################
@@ -2569,868 +2925,868 @@ def get_user(name: str) -> User:
2569
2925
 
2570
2926
 
2571
2927
  ########################################
2572
- # ../../../omlish/io/buffers.py
2573
-
2574
-
2575
- class DelimitingBuffer:
2576
- """
2577
- https://github.com/python-trio/trio/issues/796 :|
2578
- """
2579
-
2580
- #
2581
-
2582
- class Error(Exception):
2583
- def __init__(self, buffer: 'DelimitingBuffer') -> None:
2584
- super().__init__(buffer)
2585
- self.buffer = buffer
2928
+ # ../../../omlish/http/parsing.py
2929
+ # PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
2930
+ # --------------------------------------------
2931
+ #
2932
+ # 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and the Individual or Organization
2933
+ # ("Licensee") accessing and otherwise using this software ("Python") in source or binary form and its associated
2934
+ # documentation.
2935
+ #
2936
+ # 2. Subject to the terms and conditions of this License Agreement, PSF hereby grants Licensee a nonexclusive,
2937
+ # royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative
2938
+ # works, distribute, and otherwise use Python alone or in any derivative version, provided, however, that PSF's License
2939
+ # Agreement and PSF's notice of copyright, i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
2940
+ # 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 Python Software Foundation; All Rights Reserved" are retained in Python
2941
+ # alone or in any derivative version prepared by Licensee.
2942
+ #
2943
+ # 3. In the event Licensee prepares a derivative work that is based on or incorporates Python or any part thereof, and
2944
+ # wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in
2945
+ # any such work a brief summary of the changes made to Python.
2946
+ #
2947
+ # 4. PSF is making Python available to Licensee on an "AS IS" basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES,
2948
+ # EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY
2949
+ # OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT INFRINGE ANY THIRD PARTY
2950
+ # RIGHTS.
2951
+ #
2952
+ # 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL
2953
+ # DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, OR ANY DERIVATIVE THEREOF, EVEN IF
2954
+ # ADVISED OF THE POSSIBILITY THEREOF.
2955
+ #
2956
+ # 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions.
2957
+ #
2958
+ # 7. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint
2959
+ # venture between PSF and Licensee. This License Agreement does not grant permission to use PSF trademarks or trade
2960
+ # name in a trademark sense to endorse or promote products or services of Licensee, or any third party.
2961
+ #
2962
+ # 8. By copying, installing or otherwise using Python, Licensee agrees to be bound by the terms and conditions of this
2963
+ # License Agreement.
2586
2964
 
2587
- def __repr__(self) -> str:
2588
- return attr_repr(self, 'buffer')
2589
2965
 
2590
- class ClosedError(Error):
2591
- pass
2966
+ ##
2592
2967
 
2593
- #
2594
2968
 
2595
- DEFAULT_DELIMITERS: bytes = b'\n'
2969
+ class ParseHttpRequestResult(abc.ABC): # noqa
2970
+ __slots__ = (
2971
+ 'server_version',
2972
+ 'request_line',
2973
+ 'request_version',
2974
+ 'version',
2975
+ 'headers',
2976
+ 'close_connection',
2977
+ )
2596
2978
 
2597
2979
  def __init__(
2598
2980
  self,
2599
- delimiters: ta.Iterable[int] = DEFAULT_DELIMITERS,
2600
2981
  *,
2601
- keep_ends: bool = False,
2602
- max_size: ta.Optional[int] = None,
2982
+ server_version: HttpProtocolVersion,
2983
+ request_line: str,
2984
+ request_version: HttpProtocolVersion,
2985
+ version: HttpProtocolVersion,
2986
+ headers: ta.Optional[HttpHeaders],
2987
+ close_connection: bool,
2603
2988
  ) -> None:
2604
2989
  super().__init__()
2605
2990
 
2606
- self._delimiters = frozenset(check_isinstance(d, int) for d in delimiters)
2607
- self._keep_ends = keep_ends
2608
- self._max_size = max_size
2991
+ self.server_version = server_version
2992
+ self.request_line = request_line
2993
+ self.request_version = request_version
2994
+ self.version = version
2995
+ self.headers = headers
2996
+ self.close_connection = close_connection
2609
2997
 
2610
- self._buf: ta.Optional[io.BytesIO] = io.BytesIO()
2998
+ def __repr__(self) -> str:
2999
+ return f'{self.__class__.__name__}({", ".join(f"{a}={getattr(self, a)!r}" for a in self.__slots__)})'
2611
3000
 
2612
- #
2613
3001
 
2614
- @property
2615
- def is_closed(self) -> bool:
2616
- return self._buf is None
3002
+ class EmptyParsedHttpResult(ParseHttpRequestResult):
3003
+ pass
2617
3004
 
2618
- def tell(self) -> int:
2619
- if (buf := self._buf) is None:
2620
- raise self.ClosedError(self)
2621
- return buf.tell()
2622
3005
 
2623
- def peek(self) -> bytes:
2624
- if (buf := self._buf) is None:
2625
- raise self.ClosedError(self)
2626
- return buf.getvalue()
3006
+ class ParseHttpRequestError(ParseHttpRequestResult):
3007
+ __slots__ = (
3008
+ 'code',
3009
+ 'message',
3010
+ *ParseHttpRequestResult.__slots__,
3011
+ )
2627
3012
 
2628
- def _find_delim(self, data: ta.Union[bytes, bytearray], i: int) -> ta.Optional[int]:
2629
- r = None # type: int | None
2630
- for d in self._delimiters:
2631
- if (p := data.find(d, i)) >= 0:
2632
- if r is None or p < r:
2633
- r = p
2634
- return r
3013
+ def __init__(
3014
+ self,
3015
+ *,
3016
+ code: http.HTTPStatus,
3017
+ message: ta.Union[str, ta.Tuple[str, str]],
2635
3018
 
2636
- def _append_and_reset(self, chunk: bytes) -> bytes:
2637
- buf = check_not_none(self._buf)
2638
- if not buf.tell():
2639
- return chunk
3019
+ **kwargs: ta.Any,
3020
+ ) -> None:
3021
+ super().__init__(**kwargs)
2640
3022
 
2641
- buf.write(chunk)
2642
- ret = buf.getvalue()
2643
- buf.seek(0)
2644
- buf.truncate()
2645
- return ret
3023
+ self.code = code
3024
+ self.message = message
2646
3025
 
2647
- class Incomplete(ta.NamedTuple):
2648
- b: bytes
2649
3026
 
2650
- def feed(self, data: ta.Union[bytes, bytearray]) -> ta.Generator[ta.Union[bytes, Incomplete], None, None]:
2651
- if (buf := self._buf) is None:
2652
- raise self.ClosedError(self)
3027
+ class ParsedHttpRequest(ParseHttpRequestResult):
3028
+ __slots__ = (
3029
+ 'method',
3030
+ 'path',
3031
+ 'headers',
3032
+ 'expects_continue',
3033
+ *[a for a in ParseHttpRequestResult.__slots__ if a != 'headers'],
3034
+ )
2653
3035
 
2654
- if not data:
2655
- self._buf = None
3036
+ def __init__(
3037
+ self,
3038
+ *,
3039
+ method: str,
3040
+ path: str,
3041
+ headers: HttpHeaders,
3042
+ expects_continue: bool,
2656
3043
 
2657
- if buf.tell():
2658
- yield self.Incomplete(buf.getvalue())
3044
+ **kwargs: ta.Any,
3045
+ ) -> None:
3046
+ super().__init__(
3047
+ headers=headers,
3048
+ **kwargs,
3049
+ )
2659
3050
 
2660
- return
3051
+ self.method = method
3052
+ self.path = path
3053
+ self.expects_continue = expects_continue
2661
3054
 
2662
- l = len(data)
2663
- i = 0
2664
- while i < l:
2665
- if (p := self._find_delim(data, i)) is None:
2666
- break
3055
+ headers: HttpHeaders
2667
3056
 
2668
- n = p + 1
2669
- if self._keep_ends:
2670
- p = n
2671
3057
 
2672
- yield self._append_and_reset(data[i:p])
3058
+ #
2673
3059
 
2674
- i = n
2675
3060
 
2676
- if i >= l:
2677
- return
3061
+ class HttpRequestParser:
3062
+ DEFAULT_SERVER_VERSION = HttpProtocolVersions.HTTP_1_0
2678
3063
 
2679
- if self._max_size is None:
2680
- buf.write(data[i:])
2681
- return
3064
+ # The default request version. This only affects responses up until the point where the request line is parsed, so
3065
+ # it mainly decides what the client gets back when sending a malformed request line.
3066
+ # Most web servers default to HTTP 0.9, i.e. don't send a status line.
3067
+ DEFAULT_REQUEST_VERSION = HttpProtocolVersions.HTTP_0_9
2682
3068
 
2683
- while i < l:
2684
- remaining_data_len = l - i
2685
- remaining_buf_capacity = self._max_size - buf.tell()
3069
+ #
2686
3070
 
2687
- if remaining_data_len < remaining_buf_capacity:
2688
- buf.write(data[i:])
2689
- return
3071
+ DEFAULT_MAX_LINE: int = 0x10000
3072
+ DEFAULT_MAX_HEADERS: int = 100
2690
3073
 
2691
- p = i + remaining_buf_capacity
2692
- yield self.Incomplete(self._append_and_reset(data[i:p]))
2693
- i = p
3074
+ #
2694
3075
 
3076
+ def __init__(
3077
+ self,
3078
+ *,
3079
+ server_version: HttpProtocolVersion = DEFAULT_SERVER_VERSION,
2695
3080
 
2696
- class ReadableListBuffer:
2697
- def __init__(self) -> None:
3081
+ max_line: int = DEFAULT_MAX_LINE,
3082
+ max_headers: int = DEFAULT_MAX_HEADERS,
3083
+ ) -> None:
2698
3084
  super().__init__()
2699
- self._lst: list[bytes] = []
2700
3085
 
2701
- def feed(self, d: bytes) -> None:
2702
- if d:
2703
- self._lst.append(d)
3086
+ if server_version >= HttpProtocolVersions.HTTP_2_0:
3087
+ raise ValueError(f'Unsupported protocol version: {server_version}')
3088
+ self._server_version = server_version
2704
3089
 
2705
- def _chop(self, i: int, e: int) -> bytes:
2706
- lst = self._lst
2707
- d = lst[i]
3090
+ self._max_line = max_line
3091
+ self._max_headers = max_headers
2708
3092
 
2709
- o = b''.join([
2710
- *lst[:i],
2711
- d[:e],
2712
- ])
3093
+ #
2713
3094
 
2714
- self._lst = [
2715
- *([d[e:]] if e < len(d) else []),
2716
- *lst[i + 1:],
2717
- ]
3095
+ @property
3096
+ def server_version(self) -> HttpProtocolVersion:
3097
+ return self._server_version
2718
3098
 
2719
- return o
3099
+ #
2720
3100
 
2721
- def read(self, n: ta.Optional[int] = None) -> ta.Optional[bytes]:
2722
- if n is None:
2723
- o = b''.join(self._lst)
2724
- self._lst = []
2725
- return o
3101
+ def _run_read_line_coro(
3102
+ self,
3103
+ gen: ta.Generator[int, bytes, T],
3104
+ read_line: ta.Callable[[int], bytes],
3105
+ ) -> T:
3106
+ sz = next(gen)
3107
+ while True:
3108
+ try:
3109
+ sz = gen.send(read_line(sz))
3110
+ except StopIteration as e:
3111
+ return e.value
2726
3112
 
2727
- if not (lst := self._lst):
2728
- return None
3113
+ #
2729
3114
 
2730
- c = 0
2731
- for i, d in enumerate(lst):
2732
- r = n - c
2733
- if (l := len(d)) >= r:
2734
- return self._chop(i, r)
2735
- c += l
3115
+ def parse_request_version(self, version_str: str) -> HttpProtocolVersion:
3116
+ if not version_str.startswith('HTTP/'):
3117
+ raise ValueError(version_str) # noqa
2736
3118
 
2737
- return None
3119
+ base_version_number = version_str.split('/', 1)[1]
3120
+ version_number_parts = base_version_number.split('.')
2738
3121
 
2739
- def read_until(self, delim: bytes = b'\n') -> ta.Optional[bytes]:
2740
- if not (lst := self._lst):
2741
- return None
3122
+ # RFC 2145 section 3.1 says there can be only one "." and
3123
+ # - major and minor numbers MUST be treated as separate integers;
3124
+ # - HTTP/2.4 is a lower version than HTTP/2.13, which in turn is lower than HTTP/12.3;
3125
+ # - Leading zeros MUST be ignored by recipients.
3126
+ if len(version_number_parts) != 2:
3127
+ raise ValueError(version_number_parts) # noqa
3128
+ if any(not component.isdigit() for component in version_number_parts):
3129
+ raise ValueError('non digit in http version') # noqa
3130
+ if any(len(component) > 10 for component in version_number_parts):
3131
+ raise ValueError('unreasonable length http version') # noqa
2742
3132
 
2743
- for i, d in enumerate(lst):
2744
- if (p := d.find(delim)) >= 0:
2745
- return self._chop(i, p + len(delim))
3133
+ return HttpProtocolVersion(
3134
+ int(version_number_parts[0]),
3135
+ int(version_number_parts[1]),
3136
+ )
2746
3137
 
2747
- return None
3138
+ #
2748
3139
 
3140
+ def coro_read_raw_headers(self) -> ta.Generator[int, bytes, ta.List[bytes]]:
3141
+ raw_headers: ta.List[bytes] = []
3142
+ while True:
3143
+ line = yield self._max_line + 1
3144
+ if len(line) > self._max_line:
3145
+ raise http.client.LineTooLong('header line')
3146
+ raw_headers.append(line)
3147
+ if len(raw_headers) > self._max_headers:
3148
+ raise http.client.HTTPException(f'got more than {self._max_headers} headers')
3149
+ if line in (b'\r\n', b'\n', b''):
3150
+ break
3151
+ return raw_headers
2749
3152
 
2750
- class IncrementalWriteBuffer:
2751
- def __init__(
2752
- self,
2753
- data: bytes,
2754
- *,
2755
- write_size: int = 0x10000,
2756
- ) -> None:
2757
- super().__init__()
3153
+ def read_raw_headers(self, read_line: ta.Callable[[int], bytes]) -> ta.List[bytes]:
3154
+ return self._run_read_line_coro(self.coro_read_raw_headers(), read_line)
2758
3155
 
2759
- check_not_empty(data)
2760
- self._len = len(data)
2761
- self._write_size = write_size
3156
+ def parse_raw_headers(self, raw_headers: ta.Sequence[bytes]) -> HttpHeaders:
3157
+ return http.client.parse_headers(io.BytesIO(b''.join(raw_headers)))
2762
3158
 
2763
- self._lst = [
2764
- data[i:i + write_size]
2765
- for i in range(0, len(data), write_size)
2766
- ]
2767
- self._pos = 0
3159
+ #
2768
3160
 
2769
- @property
2770
- def rem(self) -> int:
2771
- return self._len - self._pos
3161
+ def coro_parse(self) -> ta.Generator[int, bytes, ParseHttpRequestResult]:
3162
+ raw_request_line = yield self._max_line + 1
2772
3163
 
2773
- def write(self, fn: ta.Callable[[bytes], int]) -> int:
2774
- lst = check_not_empty(self._lst)
3164
+ # Common result kwargs
2775
3165
 
2776
- t = 0
2777
- for i, d in enumerate(lst): # noqa
2778
- n = fn(check_not_empty(d))
2779
- if not n:
2780
- break
2781
- t += n
3166
+ request_line = '-'
3167
+ request_version = self.DEFAULT_REQUEST_VERSION
2782
3168
 
2783
- if t:
2784
- self._lst = [
2785
- *([d[n:]] if n < len(d) else []),
2786
- *lst[i + 1:],
2787
- ]
2788
- self._pos += t
3169
+ # Set to min(server, request) when it gets that far, but if it fails before that the server authoritatively
3170
+ # responds with its own version.
3171
+ version = self._server_version
2789
3172
 
2790
- return t
3173
+ headers: HttpHeaders | None = None
2791
3174
 
3175
+ close_connection = True
2792
3176
 
2793
- ########################################
2794
- # ../../../omlish/io/fdio/handlers.py
3177
+ def result_kwargs():
3178
+ return dict(
3179
+ server_version=self._server_version,
3180
+ request_line=request_line,
3181
+ request_version=request_version,
3182
+ version=version,
3183
+ headers=headers,
3184
+ close_connection=close_connection,
3185
+ )
2795
3186
 
3187
+ # Decode line
2796
3188
 
2797
- class FdioHandler(abc.ABC):
2798
- @abc.abstractmethod
2799
- def fd(self) -> int:
2800
- raise NotImplementedError
3189
+ if len(raw_request_line) > self._max_line:
3190
+ return ParseHttpRequestError(
3191
+ code=http.HTTPStatus.REQUEST_URI_TOO_LONG,
3192
+ message='Request line too long',
3193
+ **result_kwargs(),
3194
+ )
2801
3195
 
2802
- #
3196
+ if not raw_request_line:
3197
+ return EmptyParsedHttpResult(**result_kwargs())
2803
3198
 
2804
- @property
2805
- @abc.abstractmethod
2806
- def closed(self) -> bool:
2807
- raise NotImplementedError
3199
+ request_line = raw_request_line.decode('iso-8859-1').rstrip('\r\n')
2808
3200
 
2809
- @abc.abstractmethod
2810
- def close(self) -> None:
2811
- raise NotImplementedError
3201
+ # Split words
2812
3202
 
2813
- #
3203
+ words = request_line.split()
3204
+ if len(words) == 0:
3205
+ return EmptyParsedHttpResult(**result_kwargs())
2814
3206
 
2815
- def readable(self) -> bool:
2816
- return False
3207
+ # Parse and set version
2817
3208
 
2818
- def writable(self) -> bool:
2819
- return False
3209
+ if len(words) >= 3: # Enough to determine protocol version
3210
+ version_str = words[-1]
3211
+ try:
3212
+ request_version = self.parse_request_version(version_str)
2820
3213
 
2821
- #
3214
+ except (ValueError, IndexError):
3215
+ return ParseHttpRequestError(
3216
+ code=http.HTTPStatus.BAD_REQUEST,
3217
+ message=f'Bad request version ({version_str!r})',
3218
+ **result_kwargs(),
3219
+ )
2822
3220
 
2823
- def on_readable(self) -> None:
2824
- raise TypeError
3221
+ if (
3222
+ request_version < HttpProtocolVersions.HTTP_0_9 or
3223
+ request_version >= HttpProtocolVersions.HTTP_2_0
3224
+ ):
3225
+ return ParseHttpRequestError(
3226
+ code=http.HTTPStatus.HTTP_VERSION_NOT_SUPPORTED,
3227
+ message=f'Invalid HTTP version ({version_str})',
3228
+ **result_kwargs(),
3229
+ )
2825
3230
 
2826
- def on_writable(self) -> None:
2827
- raise TypeError
3231
+ version = min([self._server_version, request_version])
2828
3232
 
2829
- def on_error(self, exc: ta.Optional[BaseException] = None) -> None: # noqa
2830
- pass
3233
+ if version >= HttpProtocolVersions.HTTP_1_1:
3234
+ close_connection = False
2831
3235
 
3236
+ # Verify word count
2832
3237
 
2833
- class SocketFdioHandler(FdioHandler, abc.ABC):
2834
- def __init__(
2835
- self,
2836
- addr: SocketAddress,
2837
- sock: socket.socket,
2838
- ) -> None:
2839
- super().__init__()
3238
+ if not 2 <= len(words) <= 3:
3239
+ return ParseHttpRequestError(
3240
+ code=http.HTTPStatus.BAD_REQUEST,
3241
+ message=f'Bad request syntax ({request_line!r})',
3242
+ **result_kwargs(),
3243
+ )
2840
3244
 
2841
- self._addr = addr
2842
- self._sock: ta.Optional[socket.socket] = sock
3245
+ # Parse method and path
2843
3246
 
2844
- def fd(self) -> int:
2845
- return check_not_none(self._sock).fileno()
3247
+ method, path = words[:2]
3248
+ if len(words) == 2:
3249
+ close_connection = True
3250
+ if method != 'GET':
3251
+ return ParseHttpRequestError(
3252
+ code=http.HTTPStatus.BAD_REQUEST,
3253
+ message=f'Bad HTTP/0.9 request type ({method!r})',
3254
+ **result_kwargs(),
3255
+ )
2846
3256
 
2847
- @property
2848
- def closed(self) -> bool:
2849
- return self._sock is None
3257
+ # gh-87389: The purpose of replacing '//' with '/' is to protect against open redirect attacks possibly
3258
+ # triggered if the path starts with '//' because http clients treat //path as an absolute URI without scheme
3259
+ # (similar to http://path) rather than a path.
3260
+ if path.startswith('//'):
3261
+ path = '/' + path.lstrip('/') # Reduce to a single /
2850
3262
 
2851
- def close(self) -> None:
2852
- if self._sock is not None:
2853
- self._sock.close()
2854
- self._sock = None
3263
+ # Parse headers
2855
3264
 
3265
+ try:
3266
+ raw_gen = self.coro_read_raw_headers()
3267
+ raw_sz = next(raw_gen)
3268
+ while True:
3269
+ buf = yield raw_sz
3270
+ try:
3271
+ raw_sz = raw_gen.send(buf)
3272
+ except StopIteration as e:
3273
+ raw_headers = e.value
3274
+ break
2856
3275
 
2857
- ########################################
2858
- # ../../../omlish/io/fdio/kqueue.py
3276
+ headers = self.parse_raw_headers(raw_headers)
2859
3277
 
3278
+ except http.client.LineTooLong as err:
3279
+ return ParseHttpRequestError(
3280
+ code=http.HTTPStatus.REQUEST_HEADER_FIELDS_TOO_LARGE,
3281
+ message=('Line too long', str(err)),
3282
+ **result_kwargs(),
3283
+ )
2860
3284
 
2861
- KqueueFdioPoller: ta.Optional[ta.Type[FdioPoller]]
2862
- if sys.platform == 'darwin' or sys.platform.startswith('freebsd'):
3285
+ except http.client.HTTPException as err:
3286
+ return ParseHttpRequestError(
3287
+ code=http.HTTPStatus.REQUEST_HEADER_FIELDS_TOO_LARGE,
3288
+ message=('Too many headers', str(err)),
3289
+ **result_kwargs(),
3290
+ )
2863
3291
 
2864
- class _KqueueFdioPoller(FdioPoller):
2865
- DEFAULT_MAX_EVENTS = 1000
3292
+ # Check for connection directive
2866
3293
 
2867
- def __init__(
2868
- self,
2869
- *,
2870
- max_events: int = DEFAULT_MAX_EVENTS,
2871
- ) -> None:
2872
- super().__init__()
3294
+ conn_type = headers.get('Connection', '')
3295
+ if conn_type.lower() == 'close':
3296
+ close_connection = True
3297
+ elif (
3298
+ conn_type.lower() == 'keep-alive' and
3299
+ version >= HttpProtocolVersions.HTTP_1_1
3300
+ ):
3301
+ close_connection = False
2873
3302
 
2874
- self._max_events = max_events
3303
+ # Check for expect directive
2875
3304
 
2876
- self._kqueue: ta.Optional[ta.Any] = None
3305
+ expect = headers.get('Expect', '')
3306
+ if (
3307
+ expect.lower() == '100-continue' and
3308
+ version >= HttpProtocolVersions.HTTP_1_1
3309
+ ):
3310
+ expects_continue = True
3311
+ else:
3312
+ expects_continue = False
2877
3313
 
2878
- #
3314
+ # Return
2879
3315
 
2880
- def _get_kqueue(self) -> 'select.kqueue':
2881
- if (kq := self._kqueue) is not None:
2882
- return kq
2883
- kq = select.kqueue()
2884
- self._kqueue = kq
2885
- return kq
3316
+ return ParsedHttpRequest(
3317
+ method=method,
3318
+ path=path,
3319
+ expects_continue=expects_continue,
3320
+ **result_kwargs(),
3321
+ )
2886
3322
 
2887
- def close(self) -> None:
2888
- if self._kqueue is not None:
2889
- self._kqueue.close()
2890
- self._kqueue = None
3323
+ def parse(self, read_line: ta.Callable[[int], bytes]) -> ParseHttpRequestResult:
3324
+ return self._run_read_line_coro(self.coro_parse(), read_line)
2891
3325
 
2892
- def reopen(self) -> None:
2893
- for fd in self._readable:
2894
- self._register_readable(fd)
2895
- for fd in self._writable:
2896
- self._register_writable(fd)
2897
3326
 
2898
- #
3327
+ ########################################
3328
+ # ../../../omlish/io/buffers.py
2899
3329
 
2900
- def _register_readable(self, fd: int) -> None:
2901
- self._update_registration(fd, 'read', 'add')
2902
3330
 
2903
- def _register_writable(self, fd: int) -> None:
2904
- self._update_registration(fd, 'write', 'add')
3331
+ class DelimitingBuffer:
3332
+ """
3333
+ https://github.com/python-trio/trio/issues/796 :|
3334
+ """
2905
3335
 
2906
- def _unregister_readable(self, fd: int) -> None:
2907
- self._update_registration(fd, 'read', 'del')
3336
+ #
2908
3337
 
2909
- def _unregister_writable(self, fd: int) -> None:
2910
- self._update_registration(fd, 'write', 'del')
3338
+ class Error(Exception):
3339
+ def __init__(self, buffer: 'DelimitingBuffer') -> None:
3340
+ super().__init__(buffer)
3341
+ self.buffer = buffer
2911
3342
 
2912
- #
3343
+ def __repr__(self) -> str:
3344
+ return attr_repr(self, 'buffer')
2913
3345
 
2914
- _CONTROL_FILTER_BY_READ_OR_WRITE: ta.ClassVar[ta.Mapping[ta.Literal['read', 'write'], int]] = {
2915
- 'read': select.KQ_FILTER_READ,
2916
- 'write': select.KQ_FILTER_WRITE,
2917
- }
3346
+ class ClosedError(Error):
3347
+ pass
2918
3348
 
2919
- _CONTROL_FLAGS_BY_ADD_OR_DEL: ta.ClassVar[ta.Mapping[ta.Literal['add', 'del'], int]] = {
2920
- 'add': select.KQ_EV_ADD,
2921
- 'del': select.KQ_EV_DELETE,
2922
- }
3349
+ #
2923
3350
 
2924
- def _update_registration(
2925
- self,
2926
- fd: int,
2927
- read_or_write: ta.Literal['read', 'write'],
2928
- add_or_del: ta.Literal['add', 'del'],
2929
- ) -> None: # noqa
2930
- ke = select.kevent(
2931
- fd,
2932
- filter=self._CONTROL_FILTER_BY_READ_OR_WRITE[read_or_write],
2933
- flags=self._CONTROL_FLAGS_BY_ADD_OR_DEL[add_or_del],
2934
- )
2935
- kq = self._get_kqueue()
2936
- try:
2937
- kq.control([ke], 0)
3351
+ DEFAULT_DELIMITERS: bytes = b'\n'
2938
3352
 
2939
- except OSError as exc:
2940
- if exc.errno == errno.EBADF:
2941
- # log.debug('EBADF encountered in kqueue. Invalid file descriptor %s', ke.ident)
2942
- pass
2943
- elif exc.errno == errno.ENOENT:
2944
- # Can happen when trying to remove an already closed socket
2945
- if add_or_del == 'add':
2946
- raise
2947
- else:
2948
- raise
3353
+ def __init__(
3354
+ self,
3355
+ delimiters: ta.Iterable[int] = DEFAULT_DELIMITERS,
3356
+ *,
3357
+ keep_ends: bool = False,
3358
+ max_size: ta.Optional[int] = None,
3359
+ ) -> None:
3360
+ super().__init__()
2949
3361
 
2950
- #
3362
+ self._delimiters = frozenset(check.isinstance(d, int) for d in delimiters)
3363
+ self._keep_ends = keep_ends
3364
+ self._max_size = max_size
2951
3365
 
2952
- def poll(self, timeout: ta.Optional[float]) -> FdioPoller.PollResult:
2953
- kq = self._get_kqueue()
2954
- try:
2955
- kes = kq.control(None, self._max_events, timeout)
3366
+ self._buf: ta.Optional[io.BytesIO] = io.BytesIO()
2956
3367
 
2957
- except OSError as exc:
2958
- if exc.errno == errno.EINTR:
2959
- return FdioPoller.PollResult(msg='EINTR encountered in poll', exc=exc)
2960
- else:
2961
- raise
3368
+ #
2962
3369
 
2963
- r: ta.List[int] = []
2964
- w: ta.List[int] = []
2965
- for ke in kes:
2966
- if ke.filter == select.KQ_FILTER_READ:
2967
- r.append(ke.ident)
2968
- if ke.filter == select.KQ_FILTER_WRITE:
2969
- w.append(ke.ident)
2970
-
2971
- return FdioPoller.PollResult(r, w)
2972
-
2973
- KqueueFdioPoller = _KqueueFdioPoller
2974
- else:
2975
- KqueueFdioPoller = None
3370
+ @property
3371
+ def is_closed(self) -> bool:
3372
+ return self._buf is None
2976
3373
 
3374
+ def tell(self) -> int:
3375
+ if (buf := self._buf) is None:
3376
+ raise self.ClosedError(self)
3377
+ return buf.tell()
2977
3378
 
2978
- ########################################
2979
- # ../../../omlish/lite/contextmanagers.py
3379
+ def peek(self) -> bytes:
3380
+ if (buf := self._buf) is None:
3381
+ raise self.ClosedError(self)
3382
+ return buf.getvalue()
2980
3383
 
3384
+ def _find_delim(self, data: ta.Union[bytes, bytearray], i: int) -> ta.Optional[int]:
3385
+ r = None # type: int | None
3386
+ for d in self._delimiters:
3387
+ if (p := data.find(d, i)) >= 0:
3388
+ if r is None or p < r:
3389
+ r = p
3390
+ return r
2981
3391
 
2982
- ##
3392
+ def _append_and_reset(self, chunk: bytes) -> bytes:
3393
+ buf = check.not_none(self._buf)
3394
+ if not buf.tell():
3395
+ return chunk
2983
3396
 
3397
+ buf.write(chunk)
3398
+ ret = buf.getvalue()
3399
+ buf.seek(0)
3400
+ buf.truncate()
3401
+ return ret
2984
3402
 
2985
- class ExitStacked:
2986
- _exit_stack: ta.Optional[contextlib.ExitStack] = None
3403
+ class Incomplete(ta.NamedTuple):
3404
+ b: bytes
2987
3405
 
2988
- def __enter__(self: ExitStackedT) -> ExitStackedT:
2989
- check_state(self._exit_stack is None)
2990
- es = self._exit_stack = contextlib.ExitStack()
2991
- es.__enter__()
2992
- return self
3406
+ def feed(self, data: ta.Union[bytes, bytearray]) -> ta.Generator[ta.Union[bytes, Incomplete], None, None]:
3407
+ if (buf := self._buf) is None:
3408
+ raise self.ClosedError(self)
2993
3409
 
2994
- def __exit__(self, exc_type, exc_val, exc_tb):
2995
- if (es := self._exit_stack) is None:
2996
- return None
2997
- self._exit_contexts()
2998
- return es.__exit__(exc_type, exc_val, exc_tb)
3410
+ if not data:
3411
+ self._buf = None
2999
3412
 
3000
- def _exit_contexts(self) -> None:
3001
- pass
3413
+ if buf.tell():
3414
+ yield self.Incomplete(buf.getvalue())
3002
3415
 
3003
- def _enter_context(self, cm: ta.ContextManager[T]) -> T:
3004
- es = check_not_none(self._exit_stack)
3005
- return es.enter_context(cm)
3416
+ return
3006
3417
 
3418
+ l = len(data)
3419
+ i = 0
3420
+ while i < l:
3421
+ if (p := self._find_delim(data, i)) is None:
3422
+ break
3007
3423
 
3008
- ##
3424
+ n = p + 1
3425
+ if self._keep_ends:
3426
+ p = n
3009
3427
 
3428
+ yield self._append_and_reset(data[i:p])
3010
3429
 
3011
- @contextlib.contextmanager
3012
- def defer(fn: ta.Callable) -> ta.Generator[ta.Callable, None, None]:
3013
- try:
3014
- yield fn
3015
- finally:
3016
- fn()
3430
+ i = n
3017
3431
 
3432
+ if i >= l:
3433
+ return
3018
3434
 
3019
- @contextlib.contextmanager
3020
- def attr_setting(obj, attr, val, *, default=None): # noqa
3021
- not_set = object()
3022
- orig = getattr(obj, attr, not_set)
3023
- try:
3024
- setattr(obj, attr, val)
3025
- if orig is not not_set:
3026
- yield orig
3027
- else:
3028
- yield default
3029
- finally:
3030
- if orig is not_set:
3031
- delattr(obj, attr)
3032
- else:
3033
- setattr(obj, attr, orig)
3435
+ if self._max_size is None:
3436
+ buf.write(data[i:])
3437
+ return
3034
3438
 
3439
+ while i < l:
3440
+ remaining_data_len = l - i
3441
+ remaining_buf_capacity = self._max_size - buf.tell()
3035
3442
 
3036
- ########################################
3037
- # ../../../omlish/lite/http/parsing.py
3038
- # PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
3039
- # --------------------------------------------
3040
- #
3041
- # 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and the Individual or Organization
3042
- # ("Licensee") accessing and otherwise using this software ("Python") in source or binary form and its associated
3043
- # documentation.
3044
- #
3045
- # 2. Subject to the terms and conditions of this License Agreement, PSF hereby grants Licensee a nonexclusive,
3046
- # royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative
3047
- # works, distribute, and otherwise use Python alone or in any derivative version, provided, however, that PSF's License
3048
- # Agreement and PSF's notice of copyright, i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
3049
- # 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 Python Software Foundation; All Rights Reserved" are retained in Python
3050
- # alone or in any derivative version prepared by Licensee.
3051
- #
3052
- # 3. In the event Licensee prepares a derivative work that is based on or incorporates Python or any part thereof, and
3053
- # wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in
3054
- # any such work a brief summary of the changes made to Python.
3055
- #
3056
- # 4. PSF is making Python available to Licensee on an "AS IS" basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES,
3057
- # EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY
3058
- # OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT INFRINGE ANY THIRD PARTY
3059
- # RIGHTS.
3060
- #
3061
- # 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL
3062
- # DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, OR ANY DERIVATIVE THEREOF, EVEN IF
3063
- # ADVISED OF THE POSSIBILITY THEREOF.
3064
- #
3065
- # 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions.
3066
- #
3067
- # 7. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint
3068
- # venture between PSF and Licensee. This License Agreement does not grant permission to use PSF trademarks or trade
3069
- # name in a trademark sense to endorse or promote products or services of Licensee, or any third party.
3070
- #
3071
- # 8. By copying, installing or otherwise using Python, Licensee agrees to be bound by the terms and conditions of this
3072
- # License Agreement.
3443
+ if remaining_data_len < remaining_buf_capacity:
3444
+ buf.write(data[i:])
3445
+ return
3073
3446
 
3447
+ p = i + remaining_buf_capacity
3448
+ yield self.Incomplete(self._append_and_reset(data[i:p]))
3449
+ i = p
3074
3450
 
3075
- ##
3076
3451
 
3452
+ class ReadableListBuffer:
3453
+ def __init__(self) -> None:
3454
+ super().__init__()
3455
+ self._lst: list[bytes] = []
3077
3456
 
3078
- class ParseHttpRequestResult(abc.ABC): # noqa
3079
- __slots__ = (
3080
- 'server_version',
3081
- 'request_line',
3082
- 'request_version',
3083
- 'version',
3084
- 'headers',
3085
- 'close_connection',
3086
- )
3457
+ def feed(self, d: bytes) -> None:
3458
+ if d:
3459
+ self._lst.append(d)
3087
3460
 
3088
- def __init__(
3089
- self,
3090
- *,
3091
- server_version: HttpProtocolVersion,
3092
- request_line: str,
3093
- request_version: HttpProtocolVersion,
3094
- version: HttpProtocolVersion,
3095
- headers: ta.Optional[HttpHeaders],
3096
- close_connection: bool,
3097
- ) -> None:
3098
- super().__init__()
3461
+ def _chop(self, i: int, e: int) -> bytes:
3462
+ lst = self._lst
3463
+ d = lst[i]
3099
3464
 
3100
- self.server_version = server_version
3101
- self.request_line = request_line
3102
- self.request_version = request_version
3103
- self.version = version
3104
- self.headers = headers
3105
- self.close_connection = close_connection
3465
+ o = b''.join([
3466
+ *lst[:i],
3467
+ d[:e],
3468
+ ])
3106
3469
 
3107
- def __repr__(self) -> str:
3108
- return f'{self.__class__.__name__}({", ".join(f"{a}={getattr(self, a)!r}" for a in self.__slots__)})'
3470
+ self._lst = [
3471
+ *([d[e:]] if e < len(d) else []),
3472
+ *lst[i + 1:],
3473
+ ]
3109
3474
 
3475
+ return o
3110
3476
 
3111
- class EmptyParsedHttpResult(ParseHttpRequestResult):
3112
- pass
3477
+ def read(self, n: ta.Optional[int] = None) -> ta.Optional[bytes]:
3478
+ if n is None:
3479
+ o = b''.join(self._lst)
3480
+ self._lst = []
3481
+ return o
3113
3482
 
3483
+ if not (lst := self._lst):
3484
+ return None
3114
3485
 
3115
- class ParseHttpRequestError(ParseHttpRequestResult):
3116
- __slots__ = (
3117
- 'code',
3118
- 'message',
3119
- *ParseHttpRequestResult.__slots__,
3120
- )
3486
+ c = 0
3487
+ for i, d in enumerate(lst):
3488
+ r = n - c
3489
+ if (l := len(d)) >= r:
3490
+ return self._chop(i, r)
3491
+ c += l
3121
3492
 
3122
- def __init__(
3123
- self,
3124
- *,
3125
- code: http.HTTPStatus,
3126
- message: ta.Union[str, ta.Tuple[str, str]],
3493
+ return None
3127
3494
 
3128
- **kwargs: ta.Any,
3129
- ) -> None:
3130
- super().__init__(**kwargs)
3495
+ def read_until(self, delim: bytes = b'\n') -> ta.Optional[bytes]:
3496
+ if not (lst := self._lst):
3497
+ return None
3131
3498
 
3132
- self.code = code
3133
- self.message = message
3499
+ for i, d in enumerate(lst):
3500
+ if (p := d.find(delim)) >= 0:
3501
+ return self._chop(i, p + len(delim))
3134
3502
 
3503
+ return None
3135
3504
 
3136
- class ParsedHttpRequest(ParseHttpRequestResult):
3137
- __slots__ = (
3138
- 'method',
3139
- 'path',
3140
- 'headers',
3141
- 'expects_continue',
3142
- *[a for a in ParseHttpRequestResult.__slots__ if a != 'headers'],
3143
- )
3144
3505
 
3506
+ class IncrementalWriteBuffer:
3145
3507
  def __init__(
3146
3508
  self,
3509
+ data: bytes,
3147
3510
  *,
3148
- method: str,
3149
- path: str,
3150
- headers: HttpHeaders,
3151
- expects_continue: bool,
3152
-
3153
- **kwargs: ta.Any,
3511
+ write_size: int = 0x10000,
3154
3512
  ) -> None:
3155
- super().__init__(
3156
- headers=headers,
3157
- **kwargs,
3158
- )
3159
-
3160
- self.method = method
3161
- self.path = path
3162
- self.expects_continue = expects_continue
3163
-
3164
- headers: HttpHeaders
3513
+ super().__init__()
3165
3514
 
3515
+ check.not_empty(data)
3516
+ self._len = len(data)
3517
+ self._write_size = write_size
3166
3518
 
3167
- #
3168
-
3519
+ self._lst = [
3520
+ data[i:i + write_size]
3521
+ for i in range(0, len(data), write_size)
3522
+ ]
3523
+ self._pos = 0
3169
3524
 
3170
- class HttpRequestParser:
3171
- DEFAULT_SERVER_VERSION = HttpProtocolVersions.HTTP_1_0
3525
+ @property
3526
+ def rem(self) -> int:
3527
+ return self._len - self._pos
3172
3528
 
3173
- # The default request version. This only affects responses up until the point where the request line is parsed, so
3174
- # it mainly decides what the client gets back when sending a malformed request line.
3175
- # Most web servers default to HTTP 0.9, i.e. don't send a status line.
3176
- DEFAULT_REQUEST_VERSION = HttpProtocolVersions.HTTP_0_9
3529
+ def write(self, fn: ta.Callable[[bytes], int]) -> int:
3530
+ lst = check.not_empty(self._lst)
3177
3531
 
3178
- #
3532
+ t = 0
3533
+ for i, d in enumerate(lst): # noqa
3534
+ n = fn(check.not_empty(d))
3535
+ if not n:
3536
+ break
3537
+ t += n
3179
3538
 
3180
- DEFAULT_MAX_LINE: int = 0x10000
3181
- DEFAULT_MAX_HEADERS: int = 100
3539
+ if t:
3540
+ self._lst = [
3541
+ *([d[n:]] if n < len(d) else []),
3542
+ *lst[i + 1:],
3543
+ ]
3544
+ self._pos += t
3182
3545
 
3183
- #
3546
+ return t
3184
3547
 
3185
- def __init__(
3186
- self,
3187
- *,
3188
- server_version: HttpProtocolVersion = DEFAULT_SERVER_VERSION,
3189
3548
 
3190
- max_line: int = DEFAULT_MAX_LINE,
3191
- max_headers: int = DEFAULT_MAX_HEADERS,
3192
- ) -> None:
3193
- super().__init__()
3549
+ ########################################
3550
+ # ../../../omlish/io/fdio/handlers.py
3194
3551
 
3195
- if server_version >= HttpProtocolVersions.HTTP_2_0:
3196
- raise ValueError(f'Unsupported protocol version: {server_version}')
3197
- self._server_version = server_version
3198
3552
 
3199
- self._max_line = max_line
3200
- self._max_headers = max_headers
3553
+ class FdioHandler(abc.ABC):
3554
+ @abc.abstractmethod
3555
+ def fd(self) -> int:
3556
+ raise NotImplementedError
3201
3557
 
3202
3558
  #
3203
3559
 
3204
3560
  @property
3205
- def server_version(self) -> HttpProtocolVersion:
3206
- return self._server_version
3561
+ @abc.abstractmethod
3562
+ def closed(self) -> bool:
3563
+ raise NotImplementedError
3564
+
3565
+ @abc.abstractmethod
3566
+ def close(self) -> None:
3567
+ raise NotImplementedError
3207
3568
 
3208
3569
  #
3209
3570
 
3210
- def _run_read_line_coro(
3211
- self,
3212
- gen: ta.Generator[int, bytes, T],
3213
- read_line: ta.Callable[[int], bytes],
3214
- ) -> T:
3215
- sz = next(gen)
3216
- while True:
3217
- try:
3218
- sz = gen.send(read_line(sz))
3219
- except StopIteration as e:
3220
- return e.value
3571
+ def readable(self) -> bool:
3572
+ return False
3573
+
3574
+ def writable(self) -> bool:
3575
+ return False
3221
3576
 
3222
3577
  #
3223
3578
 
3224
- def parse_request_version(self, version_str: str) -> HttpProtocolVersion:
3225
- if not version_str.startswith('HTTP/'):
3226
- raise ValueError(version_str) # noqa
3579
+ def on_readable(self) -> None:
3580
+ raise TypeError
3227
3581
 
3228
- base_version_number = version_str.split('/', 1)[1]
3229
- version_number_parts = base_version_number.split('.')
3582
+ def on_writable(self) -> None:
3583
+ raise TypeError
3230
3584
 
3231
- # RFC 2145 section 3.1 says there can be only one "." and
3232
- # - major and minor numbers MUST be treated as separate integers;
3233
- # - HTTP/2.4 is a lower version than HTTP/2.13, which in turn is lower than HTTP/12.3;
3234
- # - Leading zeros MUST be ignored by recipients.
3235
- if len(version_number_parts) != 2:
3236
- raise ValueError(version_number_parts) # noqa
3237
- if any(not component.isdigit() for component in version_number_parts):
3238
- raise ValueError('non digit in http version') # noqa
3239
- if any(len(component) > 10 for component in version_number_parts):
3240
- raise ValueError('unreasonable length http version') # noqa
3585
+ def on_error(self, exc: ta.Optional[BaseException] = None) -> None: # noqa
3586
+ pass
3241
3587
 
3242
- return HttpProtocolVersion(
3243
- int(version_number_parts[0]),
3244
- int(version_number_parts[1]),
3245
- )
3246
3588
 
3247
- #
3589
+ class SocketFdioHandler(FdioHandler, abc.ABC):
3590
+ def __init__(
3591
+ self,
3592
+ addr: SocketAddress,
3593
+ sock: socket.socket,
3594
+ ) -> None:
3595
+ super().__init__()
3248
3596
 
3249
- def coro_read_raw_headers(self) -> ta.Generator[int, bytes, ta.List[bytes]]:
3250
- raw_headers: ta.List[bytes] = []
3251
- while True:
3252
- line = yield self._max_line + 1
3253
- if len(line) > self._max_line:
3254
- raise http.client.LineTooLong('header line')
3255
- raw_headers.append(line)
3256
- if len(raw_headers) > self._max_headers:
3257
- raise http.client.HTTPException(f'got more than {self._max_headers} headers')
3258
- if line in (b'\r\n', b'\n', b''):
3259
- break
3260
- return raw_headers
3597
+ self._addr = addr
3598
+ self._sock: ta.Optional[socket.socket] = sock
3261
3599
 
3262
- def read_raw_headers(self, read_line: ta.Callable[[int], bytes]) -> ta.List[bytes]:
3263
- return self._run_read_line_coro(self.coro_read_raw_headers(), read_line)
3600
+ def fd(self) -> int:
3601
+ return check.not_none(self._sock).fileno()
3264
3602
 
3265
- def parse_raw_headers(self, raw_headers: ta.Sequence[bytes]) -> HttpHeaders:
3266
- return http.client.parse_headers(io.BytesIO(b''.join(raw_headers)))
3603
+ @property
3604
+ def closed(self) -> bool:
3605
+ return self._sock is None
3267
3606
 
3268
- #
3607
+ def close(self) -> None:
3608
+ if self._sock is not None:
3609
+ self._sock.close()
3610
+ self._sock = None
3269
3611
 
3270
- def coro_parse(self) -> ta.Generator[int, bytes, ParseHttpRequestResult]:
3271
- raw_request_line = yield self._max_line + 1
3272
3612
 
3273
- # Common result kwargs
3613
+ ########################################
3614
+ # ../../../omlish/io/fdio/kqueue.py
3274
3615
 
3275
- request_line = '-'
3276
- request_version = self.DEFAULT_REQUEST_VERSION
3277
3616
 
3278
- # Set to min(server, request) when it gets that far, but if it fails before that the server authoritatively
3279
- # responds with its own version.
3280
- version = self._server_version
3617
+ KqueueFdioPoller: ta.Optional[ta.Type[FdioPoller]]
3618
+ if sys.platform == 'darwin' or sys.platform.startswith('freebsd'):
3281
3619
 
3282
- headers: HttpHeaders | None = None
3620
+ class _KqueueFdioPoller(FdioPoller):
3621
+ DEFAULT_MAX_EVENTS = 1000
3283
3622
 
3284
- close_connection = True
3623
+ def __init__(
3624
+ self,
3625
+ *,
3626
+ max_events: int = DEFAULT_MAX_EVENTS,
3627
+ ) -> None:
3628
+ super().__init__()
3285
3629
 
3286
- def result_kwargs():
3287
- return dict(
3288
- server_version=self._server_version,
3289
- request_line=request_line,
3290
- request_version=request_version,
3291
- version=version,
3292
- headers=headers,
3293
- close_connection=close_connection,
3294
- )
3630
+ self._max_events = max_events
3295
3631
 
3296
- # Decode line
3632
+ self._kqueue: ta.Optional[ta.Any] = None
3297
3633
 
3298
- if len(raw_request_line) > self._max_line:
3299
- return ParseHttpRequestError(
3300
- code=http.HTTPStatus.REQUEST_URI_TOO_LONG,
3301
- message='Request line too long',
3302
- **result_kwargs(),
3303
- )
3634
+ #
3304
3635
 
3305
- if not raw_request_line:
3306
- return EmptyParsedHttpResult(**result_kwargs())
3636
+ def _get_kqueue(self) -> 'select.kqueue':
3637
+ if (kq := self._kqueue) is not None:
3638
+ return kq
3639
+ kq = select.kqueue()
3640
+ self._kqueue = kq
3641
+ return kq
3307
3642
 
3308
- request_line = raw_request_line.decode('iso-8859-1').rstrip('\r\n')
3643
+ def close(self) -> None:
3644
+ if self._kqueue is not None:
3645
+ self._kqueue.close()
3646
+ self._kqueue = None
3309
3647
 
3310
- # Split words
3648
+ def reopen(self) -> None:
3649
+ for fd in self._readable:
3650
+ self._register_readable(fd)
3651
+ for fd in self._writable:
3652
+ self._register_writable(fd)
3311
3653
 
3312
- words = request_line.split()
3313
- if len(words) == 0:
3314
- return EmptyParsedHttpResult(**result_kwargs())
3654
+ #
3315
3655
 
3316
- # Parse and set version
3656
+ def _register_readable(self, fd: int) -> None:
3657
+ self._update_registration(fd, 'read', 'add')
3317
3658
 
3318
- if len(words) >= 3: # Enough to determine protocol version
3319
- version_str = words[-1]
3320
- try:
3321
- request_version = self.parse_request_version(version_str)
3659
+ def _register_writable(self, fd: int) -> None:
3660
+ self._update_registration(fd, 'write', 'add')
3322
3661
 
3323
- except (ValueError, IndexError):
3324
- return ParseHttpRequestError(
3325
- code=http.HTTPStatus.BAD_REQUEST,
3326
- message=f'Bad request version ({version_str!r})',
3327
- **result_kwargs(),
3328
- )
3662
+ def _unregister_readable(self, fd: int) -> None:
3663
+ self._update_registration(fd, 'read', 'del')
3329
3664
 
3330
- if (
3331
- request_version < HttpProtocolVersions.HTTP_0_9 or
3332
- request_version >= HttpProtocolVersions.HTTP_2_0
3333
- ):
3334
- return ParseHttpRequestError(
3335
- code=http.HTTPStatus.HTTP_VERSION_NOT_SUPPORTED,
3336
- message=f'Invalid HTTP version ({version_str})',
3337
- **result_kwargs(),
3338
- )
3665
+ def _unregister_writable(self, fd: int) -> None:
3666
+ self._update_registration(fd, 'write', 'del')
3339
3667
 
3340
- version = min([self._server_version, request_version])
3668
+ #
3341
3669
 
3342
- if version >= HttpProtocolVersions.HTTP_1_1:
3343
- close_connection = False
3670
+ _CONTROL_FILTER_BY_READ_OR_WRITE: ta.ClassVar[ta.Mapping[ta.Literal['read', 'write'], int]] = {
3671
+ 'read': select.KQ_FILTER_READ,
3672
+ 'write': select.KQ_FILTER_WRITE,
3673
+ }
3344
3674
 
3345
- # Verify word count
3675
+ _CONTROL_FLAGS_BY_ADD_OR_DEL: ta.ClassVar[ta.Mapping[ta.Literal['add', 'del'], int]] = {
3676
+ 'add': select.KQ_EV_ADD,
3677
+ 'del': select.KQ_EV_DELETE,
3678
+ }
3346
3679
 
3347
- if not 2 <= len(words) <= 3:
3348
- return ParseHttpRequestError(
3349
- code=http.HTTPStatus.BAD_REQUEST,
3350
- message=f'Bad request syntax ({request_line!r})',
3351
- **result_kwargs(),
3680
+ def _update_registration(
3681
+ self,
3682
+ fd: int,
3683
+ read_or_write: ta.Literal['read', 'write'],
3684
+ add_or_del: ta.Literal['add', 'del'],
3685
+ ) -> None: # noqa
3686
+ ke = select.kevent(
3687
+ fd,
3688
+ filter=self._CONTROL_FILTER_BY_READ_OR_WRITE[read_or_write],
3689
+ flags=self._CONTROL_FLAGS_BY_ADD_OR_DEL[add_or_del],
3352
3690
  )
3691
+ kq = self._get_kqueue()
3692
+ try:
3693
+ kq.control([ke], 0)
3353
3694
 
3354
- # Parse method and path
3695
+ except OSError as exc:
3696
+ if exc.errno == errno.EBADF:
3697
+ # log.debug('EBADF encountered in kqueue. Invalid file descriptor %s', ke.ident)
3698
+ pass
3699
+ elif exc.errno == errno.ENOENT:
3700
+ # Can happen when trying to remove an already closed socket
3701
+ if add_or_del == 'add':
3702
+ raise
3703
+ else:
3704
+ raise
3355
3705
 
3356
- method, path = words[:2]
3357
- if len(words) == 2:
3358
- close_connection = True
3359
- if method != 'GET':
3360
- return ParseHttpRequestError(
3361
- code=http.HTTPStatus.BAD_REQUEST,
3362
- message=f'Bad HTTP/0.9 request type ({method!r})',
3363
- **result_kwargs(),
3364
- )
3706
+ #
3365
3707
 
3366
- # gh-87389: The purpose of replacing '//' with '/' is to protect against open redirect attacks possibly
3367
- # triggered if the path starts with '//' because http clients treat //path as an absolute URI without scheme
3368
- # (similar to http://path) rather than a path.
3369
- if path.startswith('//'):
3370
- path = '/' + path.lstrip('/') # Reduce to a single /
3708
+ def poll(self, timeout: ta.Optional[float]) -> FdioPoller.PollResult:
3709
+ kq = self._get_kqueue()
3710
+ try:
3711
+ kes = kq.control(None, self._max_events, timeout)
3371
3712
 
3372
- # Parse headers
3713
+ except OSError as exc:
3714
+ if exc.errno == errno.EINTR:
3715
+ return FdioPoller.PollResult(msg='EINTR encountered in poll', exc=exc)
3716
+ else:
3717
+ raise
3373
3718
 
3374
- try:
3375
- raw_gen = self.coro_read_raw_headers()
3376
- raw_sz = next(raw_gen)
3377
- while True:
3378
- buf = yield raw_sz
3379
- try:
3380
- raw_sz = raw_gen.send(buf)
3381
- except StopIteration as e:
3382
- raw_headers = e.value
3383
- break
3719
+ r: ta.List[int] = []
3720
+ w: ta.List[int] = []
3721
+ for ke in kes:
3722
+ if ke.filter == select.KQ_FILTER_READ:
3723
+ r.append(ke.ident)
3724
+ if ke.filter == select.KQ_FILTER_WRITE:
3725
+ w.append(ke.ident)
3384
3726
 
3385
- headers = self.parse_raw_headers(raw_headers)
3727
+ return FdioPoller.PollResult(r, w)
3386
3728
 
3387
- except http.client.LineTooLong as err:
3388
- return ParseHttpRequestError(
3389
- code=http.HTTPStatus.REQUEST_HEADER_FIELDS_TOO_LARGE,
3390
- message=('Line too long', str(err)),
3391
- **result_kwargs(),
3392
- )
3729
+ KqueueFdioPoller = _KqueueFdioPoller
3730
+ else:
3731
+ KqueueFdioPoller = None
3393
3732
 
3394
- except http.client.HTTPException as err:
3395
- return ParseHttpRequestError(
3396
- code=http.HTTPStatus.REQUEST_HEADER_FIELDS_TOO_LARGE,
3397
- message=('Too many headers', str(err)),
3398
- **result_kwargs(),
3399
- )
3400
3733
 
3401
- # Check for connection directive
3734
+ ########################################
3735
+ # ../../../omlish/lite/contextmanagers.py
3402
3736
 
3403
- conn_type = headers.get('Connection', '')
3404
- if conn_type.lower() == 'close':
3405
- close_connection = True
3406
- elif (
3407
- conn_type.lower() == 'keep-alive' and
3408
- version >= HttpProtocolVersions.HTTP_1_1
3409
- ):
3410
- close_connection = False
3411
3737
 
3412
- # Check for expect directive
3738
+ ##
3413
3739
 
3414
- expect = headers.get('Expect', '')
3415
- if (
3416
- expect.lower() == '100-continue' and
3417
- version >= HttpProtocolVersions.HTTP_1_1
3418
- ):
3419
- expects_continue = True
3420
- else:
3421
- expects_continue = False
3422
3740
 
3423
- # Return
3741
+ class ExitStacked:
3742
+ _exit_stack: ta.Optional[contextlib.ExitStack] = None
3424
3743
 
3425
- return ParsedHttpRequest(
3426
- method=method,
3427
- path=path,
3428
- expects_continue=expects_continue,
3429
- **result_kwargs(),
3430
- )
3744
+ def __enter__(self: ExitStackedT) -> ExitStackedT:
3745
+ check.state(self._exit_stack is None)
3746
+ es = self._exit_stack = contextlib.ExitStack()
3747
+ es.__enter__()
3748
+ return self
3431
3749
 
3432
- def parse(self, read_line: ta.Callable[[int], bytes]) -> ParseHttpRequestResult:
3433
- return self._run_read_line_coro(self.coro_parse(), read_line)
3750
+ def __exit__(self, exc_type, exc_val, exc_tb):
3751
+ if (es := self._exit_stack) is None:
3752
+ return None
3753
+ self._exit_contexts()
3754
+ return es.__exit__(exc_type, exc_val, exc_tb)
3755
+
3756
+ def _exit_contexts(self) -> None:
3757
+ pass
3758
+
3759
+ def _enter_context(self, cm: ta.ContextManager[T]) -> T:
3760
+ es = check.not_none(self._exit_stack)
3761
+ return es.enter_context(cm)
3762
+
3763
+
3764
+ ##
3765
+
3766
+
3767
+ @contextlib.contextmanager
3768
+ def defer(fn: ta.Callable) -> ta.Generator[ta.Callable, None, None]:
3769
+ try:
3770
+ yield fn
3771
+ finally:
3772
+ fn()
3773
+
3774
+
3775
+ @contextlib.contextmanager
3776
+ def attr_setting(obj, attr, val, *, default=None): # noqa
3777
+ not_set = object()
3778
+ orig = getattr(obj, attr, not_set)
3779
+ try:
3780
+ setattr(obj, attr, val)
3781
+ if orig is not not_set:
3782
+ yield orig
3783
+ else:
3784
+ yield default
3785
+ finally:
3786
+ if orig is not_set:
3787
+ delattr(obj, attr)
3788
+ else:
3789
+ setattr(obj, attr, orig)
3434
3790
 
3435
3791
 
3436
3792
  ########################################
@@ -3577,7 +3933,7 @@ class FnInjectorProvider(InjectorProvider):
3577
3933
  fn: ta.Any
3578
3934
 
3579
3935
  def __post_init__(self) -> None:
3580
- check_not_isinstance(self.fn, type)
3936
+ check.not_isinstance(self.fn, type)
3581
3937
 
3582
3938
  def provider_fn(self) -> InjectorProviderFn:
3583
3939
  def pfn(i: Injector) -> ta.Any:
@@ -3591,7 +3947,7 @@ class CtorInjectorProvider(InjectorProvider):
3591
3947
  cls_: type
3592
3948
 
3593
3949
  def __post_init__(self) -> None:
3594
- check_isinstance(self.cls_, type)
3950
+ check.isinstance(self.cls_, type)
3595
3951
 
3596
3952
  def provider_fn(self) -> InjectorProviderFn:
3597
3953
  def pfn(i: Injector) -> ta.Any:
@@ -3613,7 +3969,7 @@ class SingletonInjectorProvider(InjectorProvider):
3613
3969
  p: InjectorProvider
3614
3970
 
3615
3971
  def __post_init__(self) -> None:
3616
- check_isinstance(self.p, InjectorProvider)
3972
+ check.isinstance(self.p, InjectorProvider)
3617
3973
 
3618
3974
  def provider_fn(self) -> InjectorProviderFn:
3619
3975
  v = not_set = object()
@@ -3633,7 +3989,7 @@ class LinkInjectorProvider(InjectorProvider):
3633
3989
  k: InjectorKey
3634
3990
 
3635
3991
  def __post_init__(self) -> None:
3636
- check_isinstance(self.k, InjectorKey)
3992
+ check.isinstance(self.k, InjectorKey)
3637
3993
 
3638
3994
  def provider_fn(self) -> InjectorProviderFn:
3639
3995
  def pfn(i: Injector) -> ta.Any:
@@ -3830,7 +4186,7 @@ def build_injection_kwargs_target(
3830
4186
 
3831
4187
  skip_names: ta.Set[str] = set()
3832
4188
  if skip_kwargs is not None:
3833
- skip_names.update(check_not_isinstance(skip_kwargs, str))
4189
+ skip_names.update(check.not_isinstance(skip_kwargs, str))
3834
4190
 
3835
4191
  seen: ta.Set[InjectorKey] = set()
3836
4192
  kws: ta.List[InjectionKwarg] = []
@@ -3891,8 +4247,8 @@ class _Injector(Injector):
3891
4247
  def __init__(self, bs: InjectorBindings, p: ta.Optional[Injector] = None) -> None:
3892
4248
  super().__init__()
3893
4249
 
3894
- self._bs = check_isinstance(bs, InjectorBindings)
3895
- self._p: ta.Optional[Injector] = check_isinstance(p, (Injector, type(None)))
4250
+ self._bs = check.isinstance(bs, InjectorBindings)
4251
+ self._p: ta.Optional[Injector] = check.isinstance(p, (Injector, type(None)))
3896
4252
 
3897
4253
  self._pfm = {k: v.provider_fn() for k, v in build_injector_provider_map(bs).items()}
3898
4254
 
@@ -3923,8 +4279,8 @@ class _Injector(Injector):
3923
4279
  return Maybe.empty()
3924
4280
 
3925
4281
  def handle_provision(self, key: InjectorKey, mv: Maybe) -> Maybe:
3926
- check_in(key, self._seen_keys)
3927
- check_not_in(key, self._provisions)
4282
+ check.in_(key, self._seen_keys)
4283
+ check.not_in(key, self._provisions)
3928
4284
  self._provisions[key] = mv
3929
4285
  return mv
3930
4286
 
@@ -4038,7 +4394,7 @@ class InjectorBinder:
4038
4394
 
4039
4395
  @classmethod
4040
4396
  def bind_as_fn(cls, icls: ta.Type[T]) -> ta.Type[T]:
4041
- check_isinstance(icls, type)
4397
+ check.isinstance(icls, type)
4042
4398
  if icls not in cls._FN_TYPES:
4043
4399
  cls._FN_TYPES = (*cls._FN_TYPES, icls)
4044
4400
  return icls
@@ -4095,7 +4451,7 @@ class InjectorBinder:
4095
4451
  to_fn = obj
4096
4452
  if key is None:
4097
4453
  insp = _injection_inspect(obj)
4098
- key_cls: ta.Any = check_valid_injector_key_cls(check_not_none(insp.type_hints.get('return')))
4454
+ key_cls: ta.Any = check_valid_injector_key_cls(check.not_none(insp.type_hints.get('return')))
4099
4455
  key = InjectorKey(key_cls)
4100
4456
  else:
4101
4457
  if to_const is not None:
@@ -4794,10 +5150,10 @@ class ProxyObjMarshaler(ObjMarshaler):
4794
5150
  m: ta.Optional[ObjMarshaler] = None
4795
5151
 
4796
5152
  def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
4797
- return check_not_none(self.m).marshal(o, ctx)
5153
+ return check.not_none(self.m).marshal(o, ctx)
4798
5154
 
4799
5155
  def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
4800
- return check_not_none(self.m).unmarshal(o, ctx)
5156
+ return check.not_none(self.m).unmarshal(o, ctx)
4801
5157
 
4802
5158
 
4803
5159
  @dc.dataclass(frozen=True)
@@ -4956,19 +5312,19 @@ class DatetimeObjMarshaler(ObjMarshaler):
4956
5312
 
4957
5313
  class DecimalObjMarshaler(ObjMarshaler):
4958
5314
  def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
4959
- return str(check_isinstance(o, decimal.Decimal))
5315
+ return str(check.isinstance(o, decimal.Decimal))
4960
5316
 
4961
5317
  def unmarshal(self, v: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
4962
- return decimal.Decimal(check_isinstance(v, str))
5318
+ return decimal.Decimal(check.isinstance(v, str))
4963
5319
 
4964
5320
 
4965
5321
  class FractionObjMarshaler(ObjMarshaler):
4966
5322
  def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
4967
- fr = check_isinstance(o, fractions.Fraction)
5323
+ fr = check.isinstance(o, fractions.Fraction)
4968
5324
  return [fr.numerator, fr.denominator]
4969
5325
 
4970
5326
  def unmarshal(self, v: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
4971
- num, denom = check_isinstance(v, list)
5327
+ num, denom = check.isinstance(v, list)
4972
5328
  return fractions.Fraction(num, denom)
4973
5329
 
4974
5330
 
@@ -5270,7 +5626,7 @@ def build_config_named_children(
5270
5626
  lst: ta.List[ConfigMapping] = []
5271
5627
  if isinstance(o, ta.Mapping):
5272
5628
  for k, v in o.items():
5273
- check_isinstance(v, ta.Mapping)
5629
+ check.isinstance(v, ta.Mapping)
5274
5630
  if name_key in v:
5275
5631
  n = v[name_key]
5276
5632
  if k != n:
@@ -5280,7 +5636,7 @@ def build_config_named_children(
5280
5636
  lst.append({name_key: k, **v})
5281
5637
 
5282
5638
  else:
5283
- check_not_isinstance(o, str)
5639
+ check.not_isinstance(o, str)
5284
5640
  lst.extend(o)
5285
5641
 
5286
5642
  seen = set()
@@ -5411,7 +5767,7 @@ class SupervisorSetup(abc.ABC):
5411
5767
 
5412
5768
 
5413
5769
  ########################################
5414
- # ../../../omlish/lite/http/handlers.py
5770
+ # ../../../omlish/http/handlers.py
5415
5771
 
5416
5772
 
5417
5773
  @dc.dataclass(frozen=True)
@@ -5586,7 +5942,7 @@ def parse_logging_level(value: ta.Union[str, int]) -> int:
5586
5942
 
5587
5943
 
5588
5944
  ########################################
5589
- # ../../../omlish/lite/http/coroserver.py
5945
+ # ../../../omlish/http/coroserver.py
5590
5946
  # PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
5591
5947
  # --------------------------------------------
5592
5948
  #
@@ -5966,13 +6322,13 @@ class CoroHttpServer:
5966
6322
  yield o
5967
6323
 
5968
6324
  elif isinstance(o, self.AnyReadIo):
5969
- i = check_isinstance((yield o), bytes)
6325
+ i = check.isinstance((yield o), bytes)
5970
6326
 
5971
6327
  elif isinstance(o, self._Response):
5972
6328
  i = None
5973
6329
  r = self._preprocess_response(o)
5974
6330
  b = self._build_response_bytes(r)
5975
- check_none((yield self.WriteIo(b)))
6331
+ check.none((yield self.WriteIo(b)))
5976
6332
 
5977
6333
  else:
5978
6334
  raise TypeError(o)
@@ -5995,7 +6351,7 @@ class CoroHttpServer:
5995
6351
  sz = next(gen)
5996
6352
  while True:
5997
6353
  try:
5998
- line = check_isinstance((yield self.ReadLineIo(sz)), bytes)
6354
+ line = check.isinstance((yield self.ReadLineIo(sz)), bytes)
5999
6355
  sz = gen.send(line)
6000
6356
  except StopIteration as e:
6001
6357
  parsed = e.value
@@ -6014,11 +6370,11 @@ class CoroHttpServer:
6014
6370
  yield self._build_error_response(err)
6015
6371
  return
6016
6372
 
6017
- parsed = check_isinstance(parsed, ParsedHttpRequest)
6373
+ parsed = check.isinstance(parsed, ParsedHttpRequest)
6018
6374
 
6019
6375
  # Log
6020
6376
 
6021
- check_none((yield self.ParsedRequestLogIo(parsed)))
6377
+ check.none((yield self.ParsedRequestLogIo(parsed)))
6022
6378
 
6023
6379
  # Handle CONTINUE
6024
6380
 
@@ -6034,7 +6390,7 @@ class CoroHttpServer:
6034
6390
 
6035
6391
  request_data: ta.Optional[bytes]
6036
6392
  if (cl := parsed.headers.get('Content-Length')) is not None:
6037
- request_data = check_isinstance((yield self.ReadIo(int(cl))), bytes)
6393
+ request_data = check.isinstance((yield self.ReadIo(int(cl))), bytes)
6038
6394
  else:
6039
6395
  request_data = None
6040
6396
 
@@ -6042,7 +6398,7 @@ class CoroHttpServer:
6042
6398
 
6043
6399
  handler_request = HttpHandlerRequest(
6044
6400
  client_address=self._client_address,
6045
- method=check_not_none(parsed.method),
6401
+ method=check.not_none(parsed.method),
6046
6402
  path=parsed.path,
6047
6403
  headers=parsed.headers,
6048
6404
  data=request_data,
@@ -6340,7 +6696,7 @@ class CoroHttpServerConnectionFdioHandler(SocketFdioHandler):
6340
6696
  write_size: int = 0x10000,
6341
6697
  log_handler: ta.Optional[ta.Callable[[CoroHttpServer, CoroHttpServer.AnyLogIo], None]] = None,
6342
6698
  ) -> None:
6343
- check_state(not sock.getblocking())
6699
+ check.state(not sock.getblocking())
6344
6700
 
6345
6701
  super().__init__(addr, sock)
6346
6702
 
@@ -6364,7 +6720,7 @@ class CoroHttpServerConnectionFdioHandler(SocketFdioHandler):
6364
6720
  #
6365
6721
 
6366
6722
  def _next_io(self) -> None: # noqa
6367
- coro = check_not_none(self._srv_coro)
6723
+ coro = check.not_none(self._srv_coro)
6368
6724
 
6369
6725
  d: bytes | None = None
6370
6726
  o = self._cur_io
@@ -6397,7 +6753,7 @@ class CoroHttpServerConnectionFdioHandler(SocketFdioHandler):
6397
6753
  o = None
6398
6754
 
6399
6755
  elif isinstance(o, CoroHttpServer.WriteIo):
6400
- check_none(self._write_buf)
6756
+ check.none(self._write_buf)
6401
6757
  self._write_buf = IncrementalWriteBuffer(o.data, write_size=self._write_size)
6402
6758
  break
6403
6759
 
@@ -6418,7 +6774,7 @@ class CoroHttpServerConnectionFdioHandler(SocketFdioHandler):
6418
6774
 
6419
6775
  def on_readable(self) -> None:
6420
6776
  try:
6421
- buf = check_not_none(self._sock).recv(self._read_size)
6777
+ buf = check.not_none(self._sock).recv(self._read_size)
6422
6778
  except BlockingIOError:
6423
6779
  return
6424
6780
  except ConnectionResetError:
@@ -6434,12 +6790,12 @@ class CoroHttpServerConnectionFdioHandler(SocketFdioHandler):
6434
6790
  self._next_io()
6435
6791
 
6436
6792
  def on_writable(self) -> None:
6437
- check_isinstance(self._cur_io, CoroHttpServer.WriteIo)
6438
- wb = check_not_none(self._write_buf)
6793
+ check.isinstance(self._cur_io, CoroHttpServer.WriteIo)
6794
+ wb = check.not_none(self._write_buf)
6439
6795
  while wb.rem > 0:
6440
6796
  def send(d: bytes) -> int:
6441
6797
  try:
6442
- return check_not_none(self._sock).send(d)
6798
+ return check.not_none(self._sock).send(d)
6443
6799
  except ConnectionResetError:
6444
6800
  self.close()
6445
6801
  return 0
@@ -6832,7 +7188,7 @@ class ProcessGroupImpl(ProcessGroup):
6832
7188
 
6833
7189
  by_name: ta.Dict[str, Process] = {}
6834
7190
  for pconfig in self._config.processes or []:
6835
- p = check_isinstance(self._process_factory(pconfig, self), Process)
7191
+ p = check.isinstance(self._process_factory(pconfig, self), Process)
6836
7192
  if p.name in by_name:
6837
7193
  raise KeyError(f'name {p.name} of process {p} already registered by {by_name[p.name]}')
6838
7194
  by_name[pconfig.name] = p
@@ -7385,7 +7741,7 @@ class SocketServerFdioHandler(SocketFdioHandler):
7385
7741
  return True
7386
7742
 
7387
7743
  def on_readable(self) -> None:
7388
- cli_sock, cli_addr = check_not_none(self._sock).accept()
7744
+ cli_sock, cli_addr = check.not_none(self._sock).accept()
7389
7745
  cli_sock.setblocking(False)
7390
7746
 
7391
7747
  self._on_connect(cli_sock, cli_addr)
@@ -7624,7 +7980,7 @@ class ProcessImpl(Process):
7624
7980
  if stdin_fd is None:
7625
7981
  raise OSError(errno.EPIPE, 'Process has no stdin channel')
7626
7982
 
7627
- dispatcher = check_isinstance(self._dispatchers[stdin_fd], ProcessInputDispatcher)
7983
+ dispatcher = check.isinstance(self._dispatchers[stdin_fd], ProcessInputDispatcher)
7628
7984
  if dispatcher.closed:
7629
7985
  raise OSError(errno.EPIPE, "Process' stdin channel is closed")
7630
7986
 
@@ -8183,21 +8539,21 @@ class ProcessSpawningImpl(ProcessSpawning):
8183
8539
  dispatchers: ta.List[FdioHandler] = []
8184
8540
 
8185
8541
  if pipes.stdout is not None:
8186
- dispatchers.append(check_isinstance(self._output_dispatcher_factory(
8542
+ dispatchers.append(check.isinstance(self._output_dispatcher_factory(
8187
8543
  self.process,
8188
8544
  ProcessCommunicationStdoutEvent,
8189
8545
  pipes.stdout,
8190
8546
  ), ProcessOutputDispatcher))
8191
8547
 
8192
8548
  if pipes.stderr is not None:
8193
- dispatchers.append(check_isinstance(self._output_dispatcher_factory(
8549
+ dispatchers.append(check.isinstance(self._output_dispatcher_factory(
8194
8550
  self.process,
8195
8551
  ProcessCommunicationStderrEvent,
8196
8552
  pipes.stderr,
8197
8553
  ), ProcessOutputDispatcher))
8198
8554
 
8199
8555
  if pipes.stdin is not None:
8200
- dispatchers.append(check_isinstance(self._input_dispatcher_factory(
8556
+ dispatchers.append(check.isinstance(self._input_dispatcher_factory(
8201
8557
  self.process,
8202
8558
  'stdin',
8203
8559
  pipes.stdin,
@@ -8287,14 +8643,14 @@ class ProcessSpawningImpl(ProcessSpawning):
8287
8643
  raise RuntimeError('Unreachable')
8288
8644
 
8289
8645
  def _prepare_child_fds(self, pipes: ProcessPipes) -> None:
8290
- os.dup2(check_not_none(pipes.child_stdin), 0)
8646
+ os.dup2(check.not_none(pipes.child_stdin), 0)
8291
8647
 
8292
- os.dup2(check_not_none(pipes.child_stdout), 1)
8648
+ os.dup2(check.not_none(pipes.child_stdout), 1)
8293
8649
 
8294
8650
  if self.config.redirect_stderr:
8295
- os.dup2(check_not_none(pipes.child_stdout), 2)
8651
+ os.dup2(check.not_none(pipes.child_stdout), 2)
8296
8652
  else:
8297
- os.dup2(check_not_none(pipes.child_stderr), 2)
8653
+ os.dup2(check.not_none(pipes.child_stderr), 2)
8298
8654
 
8299
8655
  for i in range(3, self._server_config.min_fds):
8300
8656
  if i in self._inherited_fds:
@@ -8410,7 +8766,7 @@ class Supervisor:
8410
8766
  if self._process_groups.get(config.name) is not None:
8411
8767
  return False
8412
8768
 
8413
- group = check_isinstance(self._process_group_factory(config), ProcessGroup)
8769
+ group = check.isinstance(self._process_group_factory(config), ProcessGroup)
8414
8770
  for process in group:
8415
8771
  process.after_setuid()
8416
8772