optimise mavlink SS packet size (#3029)
[ExpressLRS.git] / src / python / external / streamexpect.py
blob75ed7972b6267478eeb15c89926ac0c46a951c1b
1 # -*- coding: utf-8 -*-
2 # This Source Code Form is subject to the terms of the Mozilla Public
3 # License, v. 2.0. If a copy of the MPL was not distributed with this
4 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 # Copyright (c) 2015 Digi International Inc. All Rights Reserved.
8 # Downloaded from https://github.com/digidotcom/python-streamexpect
10 import collections
11 import re
12 from . import six
13 import socket
14 import sys
15 import time
16 import unicodedata
19 __version__ = '0.2.1'
22 class SequenceMatch(object):
23 """Information about a match that has a concept of ordering."""
25 def __init__(self, searcher, match, start, end):
26 """
27 :param Searcher searcher: The :class:`Searcher` that found the match
28 :param match: Portion of sequence that triggered the match
29 :param int start: Index of start of match
30 :param int end: Index of item directly after match
31 """
32 self.searcher = searcher
33 self.match = match
34 self.start = int(start)
35 self.end = int(end)
37 def __repr__(self):
38 return '{}({!r}, match={!r}, start={}, end={})'.format(
39 self.__class__.__name__, self.searcher, self.match, self.start,
40 self.end)
43 class RegexMatch(SequenceMatch):
44 """Information about a match from a regex."""
46 def __init__(self, searcher, match, start, end, groups):
47 """
48 :param Searcher searcher: The :class:`Searcher` that found the match
49 :param match: Portion of sequence that triggered the match
50 :param int start: Index of start of match
51 :param int end: Index of item directly after match
52 :param tuple groups: Contains the matched subgroups if the regex
53 contained groups, otherwise ``None``
54 """
55 super(RegexMatch, self).__init__(searcher, match, start, end)
56 self.groups = groups
58 def __repr__(self):
59 return '{}({!r}, match={!r}, start={}), end={}, groups={!r}'.format(
60 self.__class__.__name__, self.searcher, self.match, self.start,
61 self.end, self.groups)
64 class ExpectTimeout(Exception):
65 """Exception raised when *expect* call exceeds a timeout."""
68 class Searcher(object):
69 """Base class for searching buffers.
71 Implements the base class for *Searcher* types, which are used by the
72 library to determine whether or not a particular buffer contains a *match*.
73 The type of the match is determined by the *Searcher* implementation: it
74 may be bytes, text, or something else entirely.
76 To conform to the *Searcher* interface, a class must implement one method
77 *search* and one read-only property *match_type*. The buffer passed to
78 the *search* method must match the type returned by the *match_type*
79 property, and *search* must raise a `TypeError` if it does not. The
80 member function :func:`_check_type` exists to provide this functionality
81 for subclass implementations.
82 """
84 def __repr__(self):
85 return '{}()'.format(self.__class__.__name__)
87 def search(self, buf):
88 """Search the provided buffer for a *match*.
90 Search the provided buffer for a *match*. What exactly a *match* means
91 is defined by the *Searcher* implementation. If the *match* is found,
92 returns an `SequenceMatch` object, otherwise returns ``None``.
94 :param buf: Buffer to search for a match.
95 """
96 raise NotImplementedError('search function must be provided')
98 @property
99 def match_type(self):
100 """Read-only property that returns type matched by this *Searcher*"""
101 raise NotImplementedError('match_type must be provided')
103 def _check_type(self, value):
104 """Checks that *value* matches the type of this *Searcher*.
106 Checks that *value* matches the type of this *Searcher*, returning the
107 value if it does and raising a `TypeError` if it does not.
109 :return: *value* if type of *value* matches type of this *Searcher*.
110 :raises TypeError: if type of *value* does not match the type of this
111 *Searcher*
113 if not isinstance(value, self.match_type):
114 raise TypeError('Type ' + str(type(value)) + ' does not match '
115 'expected type ' + str(self.match_type))
116 else:
117 return value
120 class BytesSearcher(Searcher):
121 """Binary/ASCII searcher.
123 A binary/ASCII searcher. Matches when the pattern passed to the
124 constructor is found in the input buffer.
126 Note that this class only operates on binary types. That means that in
127 Python 3, it will fail on strings, as strings are Unicode by default. In
128 Python 2 this class will fail on the Unicode type, as strings are ASCII by
129 default.
132 def __init__(self, b):
134 :param b: Bytes to search for. Must be a binary type (i.e. bytes)
136 self._bytes = self._check_type(b)
138 def __repr__(self):
139 return '{}({!r})'.format(self.__class__.__name__, self._bytes)
141 @property
142 def match_type(self):
143 return six.binary_type
145 def search(self, buf):
146 """Search the provided buffer for matching bytes.
148 Search the provided buffer for matching bytes. If the *match* is found,
149 returns a :class:`SequenceMatch` object, otherwise returns ``None``.
151 :param buf: Buffer to search for a match.
152 :return: :class:`SequenceMatch` if matched, None if no match was found.
154 idx = self._check_type(buf).find(self._bytes)
155 if idx < 0:
156 return None
157 else:
158 start = idx
159 end = idx + len(self._bytes)
160 return SequenceMatch(self, buf[start:end], start, end)
163 class TextSearcher(Searcher):
164 """Plain text searcher.
166 A plain-text searcher. Matches when the text passed to the constructor is
167 found in the input buffer.
169 Note that this class operates only on text types (i.e. Unicode) and raises
170 a TypeError if used with binary data. Use the :class:`BytesSearcher` type
171 to search binary or ASCII text.
173 To make sure that modified (accented, grave, etc.) characters are matched
174 accurately, the input text is converted to the Unicode canonical composed
175 form before being used to match.
178 FORM = 'NFKC'
180 def __init__(self, text):
182 :param text: Text to search for. Must be a text type (i.e. Unicode)
184 super(TextSearcher, self).__init__()
185 self._check_type(text)
186 self._text = unicodedata.normalize(self.FORM, text)
188 def __repr__(self):
189 return '{}({!r})'.format(self.__class__.__name__, self._text)
191 @property
192 def match_type(self):
193 return six.text_type
195 def search(self, buf):
196 """Search the provided buffer for matching text.
198 Search the provided buffer for matching text. If the *match* is found,
199 returns a :class:`SequenceMatch` object, otherwise returns ``None``.
201 :param buf: Buffer to search for a match.
202 :return: :class:`SequenceMatch` if matched, None if no match was found.
204 self._check_type(buf)
205 normalized = unicodedata.normalize(self.FORM, buf)
206 idx = normalized.find(self._text)
207 if idx < 0:
208 return None
209 start = idx
210 end = idx + len(self._text)
211 return SequenceMatch(self, normalized[start:end], start, end)
214 class RegexSearcher(Searcher):
215 """Regular expression searcher.
217 Searches for a match in the stream that matches the provided regular
218 expression.
220 This class follows the Python 3 model for dealing with binary versus text
221 patterns, raising a `TypeError` if mixed binary/text is used. This means
222 that a *RegexSearcher* that is instantiated with binary data will raise a
223 `TypeError` if used on text, and a *RegexSearcher* instantiated with text
224 will raise a `TypeError` on binary data.
227 def __init__(self, pattern, regex_options=0):
229 :param pattern: The regex to search for, as a single compiled regex
230 or a string that will be processed as a regex.
231 :param regex_options: Options passed to the regex engine.
233 super(RegexSearcher, self).__init__()
234 self._regex = re.compile(pattern, regex_options)
236 def __repr__(self):
237 return '{}(re.compile({!r}))'.format(self.__class__.__name__,
238 self._regex.pattern)
240 @property
241 def match_type(self):
242 return type(self._regex.pattern)
244 def search(self, buf):
245 """Search the provided buffer for a match to the object's regex.
247 Search the provided buffer for a match to the object's regex. If the
248 *match* is found, returns a :class:`RegexMatch` object, otherwise
249 returns ``None``.
251 :param buf: Buffer to search for a match.
252 :return: :class:`RegexMatch` if matched, None if no match was found.
254 match = self._regex.search(self._check_type(buf))
255 if match is not None:
256 start = match.start()
257 end = match.end()
258 return RegexMatch(self, buf[start:end], start, end, match.groups())
261 def _flatten(n):
262 """Recursively flatten a mixed sequence of sub-sequences and items"""
263 if isinstance(n, collections.Sequence):
264 for x in n:
265 for y in _flatten(x):
266 yield y
267 else:
268 yield n
271 class SearcherCollection(Searcher, list):
272 """Collect multiple `Searcher` objects into one.
274 Collect multiple `Searcher` instances into a single `Searcher` instance.
275 This is different than simply looping over a list of searchers, as this
276 class will always find the earliest match from any of its sub-searchers
277 (i.e. the match with the smallest index).
279 Note that this class requires that all of its sub-searchers have the same
280 *match_type*.
283 def __init__(self, *searchers):
285 :param searchers: One or more :class:`Searcher` implementations.
287 super(SearcherCollection, self).__init__()
288 self.extend(_flatten(searchers))
289 if not self:
290 raise ValueError(self.__class__.__name__ + ' requires at least '
291 'one sub-searcher to be specified')
293 # Check that all searchers are valid
294 for searcher in self:
295 try:
296 getattr(searcher, 'search')
297 except AttributeError:
298 raise TypeError('missing required attribute "search"')
299 try:
300 getattr(searcher, 'match_type')
301 except AttributeError:
302 raise TypeError('missing required attribute "match_type"')
304 # Check that all searchers are the same match type
305 match_type = self[0].match_type
306 if not all(map(lambda x: x.match_type == match_type, self)):
307 raise ValueError(self.__class__.__name__ + ' requires that all '
308 'sub-searchers implement the same match_type')
309 self._match_type = match_type
311 def __repr__(self):
312 return '{}({!r})'.format(self.__class__.__name__, list(self))
314 @property
315 def match_type(self):
316 return self._match_type
318 def search(self, buf):
319 """Search the provided buffer for a match to any sub-searchers.
321 Search the provided buffer for a match to any of this collection's
322 sub-searchers. If a single matching sub-searcher is found, returns that
323 sub-searcher's *match* object. If multiple matches are found, the match
324 with the smallest index is returned. If no matches are found, returns
325 ``None``.
327 :param buf: Buffer to search for a match.
328 :return: :class:`RegexMatch` if matched, None if no match was found.
330 self._check_type(buf)
331 best_match = None
332 best_index = sys.maxsize
333 for searcher in self:
334 match = searcher.search(buf)
335 if match and match.start < best_index:
336 best_match = match
337 best_index = match.start
338 return best_match
341 class StreamAdapter(object):
342 """Adapter to match varying stream objects to a single interface.
344 Despite the existence of the Python stream interface and file-like objects,
345 there are actually a number of subtly different implementations of streams
346 within Python. In addition, there are stream-like constructs like sockets
347 that use a different interface entirely (*send*/*recv* versus
348 *read*/*write*).
350 This class provides a base adapter that can be used to convert anything
351 even remotely stream-like into a form that can consistently be used by
352 implementations of `Expecter`. The key method is :func:`poll`, which must
353 *always* provide a blocking interface to the underlying stream, and must
354 *also* provide a reliable timeout mechanism. The exact method to achieve
355 these two goals is implementation dependent, and a particular
356 implementation may be used to meet the need at hand.
358 This class also automatically delegates any non-existent attributes to the
359 underlying stream object. This allows the adapter to be used identically to
360 the stream.
362 def __init__(self, stream):
363 """:param stream: Stream object to wrap over."""
364 self.stream = stream
366 def __getattr__(self, attr):
367 return getattr(self.stream, attr)
369 def __repr__(self):
370 return '{}({!r})'.format(self.__class__.__name__, self.stream)
372 def poll(self, timeout):
373 """Unified blocking read access to the underlying stream.
375 All subclasses of :class:`StreamAdapter` must implement this method.
376 Once called, the method must either:
378 - Return new read data whenever it becomes available, or
379 - Raise an `ExpectTimeout` exception if timeout is exceeded.
381 The amount of data to return from each call is implementation
382 dependent, but it is important that either all data is returned from
383 the function, or that the data be somehow returned to the stream. In
384 other words, any data not returned must still be available the next
385 time the `poll` method is called.
387 Note that there is no "wait forever" functionality: either some new
388 data must be returned or an exception must occur in a finite amount of
389 time. It is also important that, if there is a timeout, the method
390 raise the exception as soon after the timeout occurred as is reasonably
391 possible.
393 raise NotImplementedError(self.__class__.__name__ +
394 '.poll must be implemented')
397 class PollingStreamAdapterMixin(object):
398 """Add *poll_period* and *max_read* properties to a `StreamAdapter`"""
400 @property
401 def poll_period(self):
402 return self._poll_period
404 @poll_period.setter
405 def poll_period(self, value):
406 value = float(value)
407 if value <= 0:
408 raise ValueError('poll_period must be greater than 0')
409 self._poll_period = value
411 @property
412 def max_read(self):
413 return self._max_read
415 @max_read.setter
416 def max_read(self, value):
417 value = int(value)
418 if value < 0:
419 raise ValueError('max_read must be greater than or equal to 0')
420 self._max_read = value
423 class PollingStreamAdapter(StreamAdapter, PollingStreamAdapterMixin):
424 """A :class:`StreamAdapter` that polls a non-blocking stream.
426 Polls a non-blocking stream of data until new data is available or a
427 timeout is exceeded. It is *VERY IMPORTANT* that the underlying stream be
428 non-blocking.
431 def __init__(self, stream, poll_period=0.1, max_read=1024):
433 :param stream: Stream to poll for data.
434 :param float poll_period: Time (in seconds) between polls of the
435 stream.
436 :param int max_read: The maximum number of bytes/characters to read
437 from the stream at one time.
439 super(PollingStreamAdapter, self).__init__(stream)
440 self.poll_period = poll_period
441 self.max_read = max_read
443 def poll(self, timeout):
445 :param float timeout: Timeout in seconds.
447 timeout = float(timeout)
448 end_time = time.time() + timeout
449 while True:
450 # Keep reading until data is received or timeout
451 incoming = self.stream.read(self._max_read)
452 if incoming:
453 return incoming
454 if (end_time - time.time()) < 0:
455 raise ExpectTimeout()
456 time.sleep(self._poll_period)
459 class PollingSocketStreamAdapter(StreamAdapter, PollingStreamAdapterMixin):
460 """A :class:`StreamAdapter` that polls a non-blocking socket.
462 Polls a non-blocking socket for data until new data is available or a
463 timeout is exceeded.
466 def __init__(self, sock, poll_period=0.1, max_read=1024):
468 :param sock: Socket to poll for data.
469 :param float poll_period: Time (in seconds) between poll of the socket.
470 :param int max_read: The maximum number of bytes/characters to read
471 from the socket at one time.
473 super(PollingSocketStreamAdapter, self).__init__(sock)
474 self.poll_period = poll_period
475 self.max_read = max_read
477 def poll(self, timeout):
479 :param float timeout: Timeout in seconds. A timeout that is less than
480 the poll_period will still cause a single read that may take up to
481 poll_period seconds.
483 now = time.time()
484 end_time = now + float(timeout)
485 prev_timeout = self.stream.gettimeout()
486 self.stream.settimeout(self._poll_period)
487 incoming = None
488 try:
489 while (end_time - now) >= 0:
490 try:
491 incoming = self.stream.recv(self._max_read)
492 except socket.timeout:
493 pass
494 if incoming:
495 return incoming
496 now = time.time()
497 raise ExpectTimeout()
498 finally:
499 self.stream.settimeout(prev_timeout)
502 class ExpectBytesMixin(object):
504 def expect_bytes(self, b, timeout=3):
505 """Wait for a match to the bytes in *b* to appear on the stream.
507 Waits for input matching the bytes *b* for up to *timeout* seconds.
508 If a match is found, a :class:`SequenceMatch` result is returned. If
509 no match is found within *timeout* seconds, raise an
510 :class:`ExpectTimeout` exception.
512 :param b: The byte pattern to search for.
513 :param float timeout: Timeout in seconds.
514 :return: :class:`SequenceMatch` if matched, None if no match was found.
516 return self.expect(BytesSearcher(b), timeout)
519 class ExpectTextMixin(object):
521 def expect_text(self, text, timeout=3):
522 """Wait for a match to the text in *text* to appear on the stream.
524 Waits for input matching the text *text* for up to *timeout*
525 seconds. If a match is found, a :class:`SequenceMatch` result is
526 returned. If no match is found within *timeout* seconds, raise an
527 :class:`ExpectTimeout` exception.
529 :param text: The plain-text pattern to search for.
530 :param float timeout: Timeout in seconds.
531 :return: :class:`SequenceMatch` if matched, None if no match was found.
533 return self.expect(TextSearcher(text), timeout)
536 class ExpectRegexMixin(object):
538 def expect_regex(self, pattern, timeout=3, regex_options=0):
539 """Wait for a match to the regex in *pattern* to appear on the stream.
541 Waits for input matching the regex *pattern* for up to *timeout*
542 seconds. If a match is found, a :class:`RegexMatch` result is returned.
543 If no match is found within *timeout* seconds, raise an
544 :class:`ExpectTimeout` exception.
546 :param pattern: The pattern to search for, as a single compiled regex
547 or a string that will be processed as a regex.
548 :param float timeout: Timeout in seconds.
549 :param regex_options: Options passed to the regex engine.
550 :return: :class:`RegexMatch` if matched, None if no match was found.
552 return self.expect(RegexSearcher(pattern, regex_options), timeout)
555 class Expecter(object):
556 """Base class for consuming input and waiting for a pattern to appear.
558 Implements the base class for *Expecter* types, which wrap over a
559 :class:`StreamAdapter` type and provide methods for applying a
560 :class:`Searcher` to the received data. Any attributes not part of this
561 class are delegated to the underlying :class:`StreamAdapter` type.
564 def __init__(self, stream_adapter, input_callback, window, close_adapter):
566 :param StreamAdapter stream_adapter: The :class:`StreamAdapter` object
567 to receive data from.
568 :param function input_callback: Callback function with one parameter
569 that is called each time new data is read from the
570 *stream_adapter*.
571 :param int window: Number of historical objects (bytes, characters,
572 etc.) to buffer.
573 :param bool close_adapter: If ``True``, and the Expecter is used as a
574 context manager, closes the adapter at the end of the context
575 manager.
577 self.stream_adapter = stream_adapter
578 if not input_callback:
579 self.input_callback = lambda _: None
580 else:
581 self.input_callback = input_callback
582 self.window = window
583 self.close_adapter = close_adapter
585 # Delegate undefined methods to underlying stream
586 def __getattr__(self, attr):
587 return getattr(self._stream_adapter, attr)
589 def __enter__(self):
590 return self
592 def __exit__(self, type_, value, traceback):
593 if self.close_adapter:
594 self._stream_adapter.close()
595 return False
597 @property
598 def stream_adapter(self):
599 return self._stream_adapter
601 @stream_adapter.setter
602 def stream_adapter(self, value):
603 try:
604 getattr(value, 'poll')
605 except AttributeError:
606 raise TypeError('stream_adapter must define "poll" method')
607 self._stream_adapter = value
609 @property
610 def window(self):
611 return self._window
613 @window.setter
614 def window(self, value):
615 value = int(value)
616 if value < 1:
617 raise ValueError('window must be at least 1')
618 self._window = value
620 def expect(self, searcher, timeout):
621 """Apply *searcher* to underlying :class:`StreamAdapter`
623 :param Searcher searcher: :class:`Searcher` to apply to underlying
624 stream.
625 :param float timeout: Timeout in seconds.
627 raise NotImplementedError('Expecter must implement "expect"')
630 class BytesExpecter(Expecter, ExpectBytesMixin, ExpectRegexMixin):
631 """:class:`Expecter` interface for searching a byte-oriented stream."""
633 def __init__(self, stream_adapter, input_callback=None, window=1024,
634 close_adapter=True):
636 :param StreamAdapter stream_adapter: The :class:`StreamAdapter` object
637 to receive data from.
638 :param function input_callback: Callback function with one parameter
639 that is called each time new data is read from the
640 *stream_adapter*.
641 :param int window: Number of historical bytes to buffer.
643 super(BytesExpecter, self).__init__(stream_adapter, input_callback,
644 window, close_adapter)
645 self._history = six.binary_type()
646 self._start = 0
648 def expect(self, searcher, timeout=3):
649 """Wait for input matching *searcher*
651 Waits for input matching *searcher* for up to *timeout* seconds. If
652 a match is found, the match result is returned (the specific type of
653 returned result depends on the :class:`Searcher` type). If no match is
654 found within *timeout* seconds, raise an :class:`ExpectTimeout`
655 exception.
657 :param Searcher searcher: :class:`Searcher` to apply to underlying
658 stream.
659 :param float timeout: Timeout in seconds.
661 timeout = float(timeout)
662 end = time.time() + timeout
663 match = searcher.search(self._history[self._start:])
664 while not match:
665 # poll() will raise ExpectTimeout if time is exceeded
666 incoming = self._stream_adapter.poll(end - time.time())
667 self.input_callback(incoming)
668 self._history += incoming
669 match = searcher.search(self._history[self._start:])
670 trimlength = len(self._history) - self._window
671 if trimlength > 0:
672 self._start -= trimlength
673 self._history = self._history[trimlength:]
675 self._start += match.end
676 if (self._start < 0):
677 self._start = 0
679 return match
682 class TextExpecter(Expecter, ExpectTextMixin, ExpectRegexMixin):
683 """:class:`Expecter` interface for searching a text-oriented stream."""
685 def __init__(self, stream_adapter, input_callback=None, window=1024,
686 close_adapter=True):
688 :param StreamAdapter stream_adapter: The :class:`StreamAdapter` object
689 to receive data from.
690 :param function input_callback: Callback function with one parameter
691 that is called each time new data is read from the
692 *stream_adapter*.
693 :param int window: Number of historical characters to buffer.
695 super(TextExpecter, self).__init__(stream_adapter, input_callback,
696 window, close_adapter)
697 self._history = six.text_type()
698 self._start = 0
700 def expect(self, searcher, timeout=3):
701 """Wait for input matching *searcher*.
703 Waits for input matching *searcher* for up to *timeout* seconds. If
704 a match is found, the match result is returned (the specific type of
705 returned result depends on the :class:`Searcher` type). If no match is
706 found within *timeout* seconds, raise an :class:`ExpectTimeout`
707 exception.
709 :param Searcher searcher: :class:`Searcher` to apply to underlying
710 stream.
711 :param float timeout: Timeout in seconds.
713 timeout = float(timeout)
714 end = time.time() + timeout
715 match = searcher.search(self._history[self._start:])
716 while not match:
717 # poll() will raise ExpectTimeout if time is exceeded
718 incoming = self._stream_adapter.poll(end - time.time())
719 self.input_callback(incoming)
720 self._history += incoming
721 match = searcher.search(self._history[self._start:])
722 trimlength = len(self._history) - self._window
723 if trimlength > 0:
724 self._start -= trimlength
725 self._history = self._history[trimlength:]
727 self._start += match.end
728 if (self._start < 0):
729 self._start = 0
731 return match
734 def _echo_text(value):
735 sys.stdout.write(value)
738 def _echo_bytes(value):
739 sys.stdout.write(value.decode('ascii', errors='backslashreplace'))
742 def wrap(stream, unicode=False, window=1024, echo=False, close_stream=True):
743 """Wrap a stream to implement expect functionality.
745 This function provides a convenient way to wrap any Python stream (a
746 file-like object) or socket with an appropriate :class:`Expecter` class for
747 the stream type. The returned object adds an :func:`Expect.expect` method
748 to the stream, while passing normal stream functions like *read*/*recv*
749 and *write*/*send* through to the underlying stream.
751 Here's an example of opening and wrapping a pair of network sockets::
753 import socket
754 import streamexpect
756 source, drain = socket.socketpair()
757 expecter = streamexpect.wrap(drain)
758 source.sendall(b'this is a test')
759 match = expecter.expect_bytes(b'test', timeout=5)
761 assert match is not None
763 :param stream: The stream/socket to wrap.
764 :param bool unicode: If ``True``, the wrapper will be configured for
765 Unicode matching, otherwise matching will be done on binary.
766 :param int window: Historical characters to buffer.
767 :param bool echo: If ``True``, echoes received characters to stdout.
768 :param bool close_stream: If ``True``, and the wrapper is used as a context
769 manager, closes the stream at the end of the context manager.
771 if hasattr(stream, 'read'):
772 proxy = PollingStreamAdapter(stream)
773 elif hasattr(stream, 'recv'):
774 proxy = PollingSocketStreamAdapter(stream)
775 else:
776 raise TypeError('stream must have either read or recv method')
778 if echo and unicode:
779 callback = _echo_text
780 elif echo and not unicode:
781 callback = _echo_bytes
782 else:
783 callback = None
785 if unicode:
786 expecter = TextExpecter(proxy, input_callback=callback, window=window,
787 close_adapter=close_stream)
788 else:
789 expecter = BytesExpecter(proxy, input_callback=callback, window=window,
790 close_adapter=close_stream)
792 return expecter
795 __all__ = [
796 # Functions
797 'wrap',
799 # Expecter types
800 'Expecter',
801 'BytesExpecter',
802 'TextExpecter',
804 # Searcher types
805 'Searcher',
806 'BytesSearcher',
807 'TextSearcher',
808 'RegexSearcher',
809 'SearcherCollection',
811 # Match types
812 'SequenceMatch',
813 'RegexMatch',
815 # StreamAdapter types
816 'StreamAdapter',
817 'PollingStreamAdapter',
818 'PollingSocketStreamAdapter',
819 'PollingStreamAdapterMixin',
821 # Exceptions
822 'ExpectTimeout',