Skip to content

Commit

Permalink
Minor cleanup of new readuntil regex support
Browse files Browse the repository at this point in the history
  • Loading branch information
ronf committed Oct 11, 2023
1 parent 057d908 commit 4bf893e
Showing 1 changed file with 29 additions and 17 deletions.
46 changes: 29 additions & 17 deletions asyncssh/stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
from typing import TYPE_CHECKING, Any, AnyStr, AsyncIterator
from typing import Callable, Dict, Generic, Iterable
from typing import List, Optional, Set, Tuple, Union, cast
from typing.re import Pattern

from .constants import EXTENDED_DATA_STDERR
from .logging import SSHLogger
Expand Down Expand Up @@ -181,24 +180,35 @@ async def readline(self) -> AnyStr:
except asyncio.IncompleteReadError as exc:
return cast(AnyStr, exc.partial)

async def readuntil(self, separator: object, max_separator_len: int = None) -> AnyStr:
async def readuntil(self, separator: object,
max_separator_len = 0) -> AnyStr:
"""Read data from the stream until `separator` is seen
This method is a coroutine which reads from the stream until
the requested separator is seen. If a match is found, the
returned data will include the separator at the end.
The separator argument can be a single `bytes` or
`str` value or a sequence of multiple values to match
against or a compiled regex (typing.re.Pattern),
returning data as soon as any of the separators
are found in the stream.
The separator-length argument may only be set when providing
a compiled regex as a separator.
Otherwise, the separator's length would be used.
Note that if compiled regex is provided and the length is not set,
0 would be used. (regex match on the whole buffer)
The `separator` argument can be a single `bytes` or `str`
value, a sequence of multiple `bytes` or `str` values,
or a compiled regex (`re.Pattern`) to match against,
returning data as soon as a matching separator is found
in the stream.
When passing a regex pattern as the separator, the
`max_separator_len` argument should be set to the
maximum length of an expected separator match. This
can greatly improve performance, by minimizing how far
back into the stream must be searched for a match.
When passing literal separators to match against, the
max separator length will be set automatically.
.. note:: For best results, a separator regex should
both begin and end with data which is as
unique as possible, and should not start or
end with optional or repeated elements.
Otherwise, you run the risk of failing to
match parts of a separator when it is split
across multiple reads.
If EOF or a signal is received before a match occurs, an
:exc:`IncompleteReadError <asyncio.IncompleteReadError>`
Expand All @@ -210,7 +220,8 @@ async def readuntil(self, separator: object, max_separator_len: int = None) -> A
"""

return await self._session.readuntil(separator, self._datatype, max_separator_len)
return await self._session.readuntil(separator, self._datatype,
max_separator_len)

async def readexactly(self, n: int) -> AnyStr:
"""Read an exact amount of data from the stream
Expand Down Expand Up @@ -566,7 +577,8 @@ async def read(self, n: int, datatype: DataType, exact: bool) -> AnyStr:

return result

async def readuntil(self, separator: object, datatype: DataType, max_separator_len: int) -> AnyStr:
async def readuntil(self, separator: object, datatype: DataType,
max_separator_len: int) -> AnyStr:
"""Read data from the channel until a separator is seen"""

if not separator:
Expand All @@ -581,7 +593,7 @@ async def readuntil(self, separator: object, datatype: DataType, max_separator_l
elif isinstance(separator, (bytes, str)):
seplen = len(separator)
separators = re.escape(cast(AnyStr, separator))
elif isinstance(separator, Pattern):
elif isinstance(separator, re.Pattern):
seplen = max_separator_len
separators = separator
else:
Expand Down Expand Up @@ -613,7 +625,7 @@ async def readuntil(self, separator: object, datatype: DataType, max_separator_l

newbuf = cast(AnyStr, recv_buf[curbuf])
buf += newbuf
start = 0 if seplen is None else max(buflen + 1 - seplen, 0)
start = 0 if seplen == 0 else max(buflen + 1 - seplen, 0)

match = pat.search(buf, start)
if match:
Expand Down

0 comments on commit 4bf893e

Please sign in to comment.