async def open_uds_stream( self, path: str, hostname: bytes, ssl_context: Optional[SSLContext], timeout: TimeoutDict, ) -> AsyncSocketStream: connect_timeout = timeout.get("connect") unicode_host = hostname.decode("utf-8") exc_map = { TimeoutError: ConnectTimeout, OSError: ConnectError, BrokenResourceError: ConnectError, } with map_exceptions(exc_map): async with maybe_async_cm(anyio.fail_after(connect_timeout)): stream: anyio.abc.ByteStream = await anyio.connect_unix(path) if ssl_context: stream = await TLSStream.wrap( stream, hostname=unicode_host, ssl_context=ssl_context, standard_compatible=False, ) return SocketStream(stream=stream)
async def read(self, n: int, timeout: TimeoutDict) -> bytes: read_timeout = timeout.get("read") async with self.read_lock: try: async with maybe_async_cm(anyio.fail_after(read_timeout)): return await self.stream.receive(n) except TimeoutError: raise ReadTimeout from None except BrokenResourceError as exc: raise ReadError from exc except EndOfStream: raise ReadError("Server disconnected while attempting read") from None
async def write(self, data: bytes, timeout: TimeoutDict) -> None: if not data: return write_timeout = timeout.get("write") async with self.write_lock: try: async with maybe_async_cm(anyio.fail_after(write_timeout)): return await self.stream.send(data) except TimeoutError: raise WriteTimeout from None except BrokenResourceError as exc: raise WriteError from exc
async def _flush_output(self) -> None: if not self._stream: raise SMTPException('Not connected') data = self._protocol.get_outgoing_data() if data: logger.debug('Sent: %s', data) try: async with maybe_async_cm(fail_after(self.timeout)): await self._stream.send(data) except (BrokenResourceError, TimeoutError): await aclose_forcefully(self._stream) self._stream = None raise
async def start_tls( self, hostname: bytes, ssl_context: SSLContext, timeout: TimeoutDict, ) -> "SocketStream": connect_timeout = timeout.get("connect") try: async with maybe_async_cm(anyio.fail_after(connect_timeout)): ssl_stream = await TLSStream.wrap( self.stream, ssl_context=ssl_context, hostname=hostname.decode("ascii"), ) except TimeoutError: raise ConnectTimeout from None except BrokenResourceError as exc: raise ConnectError from exc return SocketStream(ssl_stream)
async def connect(self) -> None: """Connect to the SMTP server.""" if not self._stream: async with maybe_async_cm(fail_after(self.connect_timeout)): self._stream = await connect_tcp(self.host, self.port) try: await self._wait_response() await self._send_command(self._protocol.send_greeting, self.domain) # Do the TLS handshake if supported by the server if 'STARTTLS' in self._protocol.extensions: await self._send_command(self._protocol.start_tls) self._stream = await TLSStream.wrap(self._stream, hostname=self.host, ssl_context=self.ssl_context, standard_compatible=False) # Send a new EHLO command to determine new capabilities await self._send_command(self._protocol.send_greeting, self.domain) # Use the authenticator if one was provided if self.authenticator: auth_gen = self.authenticator.authenticate() try: auth_data = await auth_gen.__anext__() response = await self._send_command( self._protocol.authenticate, self.authenticator.mechanism, auth_data) while self._protocol.state is ClientState.authenticating: auth_data = await auth_gen.asend(response.message) self._protocol.send_authentication_data(auth_data) await self._flush_output() except StopAsyncIteration: pass finally: await auth_gen.aclose() except BaseException: await aclose_forcefully(self) self._stream = None raise
async def _wait_response(self) -> SMTPResponse: while True: if not self._stream: raise SMTPException('Not connected') if self._protocol.needs_incoming_data: try: async with maybe_async_cm(fail_after(self.timeout)): data = await self._stream.receive() except (BrokenResourceError, TimeoutError): await aclose_forcefully(self._stream) self._stream = None raise logger.debug('Received: %s', data) response = self._protocol.feed_bytes(data) if response: if response.is_error(): response.raise_as_exception() else: return response await self._flush_output()
async def test_maybe_async_cm() -> None: async with maybe_async_cm(CancelScope()): pass
async def acquire(self, timeout: float = None) -> None: async with maybe_async_cm(anyio.move_on_after(timeout)): await self.semaphore.acquire() return raise self.exc_class()
async def test_maybe_async_cm(): async with maybe_async_cm(open_cancel_scope()): pass