ominfra 0.0.0.dev149__py3-none-any.whl → 0.0.0.dev151__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -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] # ta.TypeAlias
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