Ejemplo n.º 1
0
async def check_for_updates(http_settings: config.HTTPSettings,
                            proxy_settings: config.ProxySettings) -> None:
    """Perform a check for newer versions of the library, logging any found."""
    if about.__git_sha1__.casefold() == "head":
        # We are not in a PyPI release, return
        return

    try:
        async with net.create_client_session(
                connector=net.create_tcp_connector(
                    dns_cache=False, limit=1, http_settings=http_settings),
                connector_owner=True,
                http_settings=http_settings,
                raise_for_status=True,
                trust_env=proxy_settings.trust_env,
        ) as cs:
            async with cs.get(
                    "https://pypi.org/pypi/hikari/json",
                    allow_redirects=http_settings.max_redirects is not None,
                    max_redirects=http_settings.max_redirects
                    if http_settings.max_redirects is not None else 10,
                    proxy=proxy_settings.url,
                    proxy_headers=proxy_settings.all_headers,
            ) as resp:
                data = await resp.json()

        this_version = HikariVersion(about.__version__)
        is_dev = this_version.prerelease is not None
        newer_releases: typing.List[HikariVersion] = []

        for release_string, artifacts in data["releases"].items():
            if not all(artifact["yanked"] for artifact in artifacts):
                v = HikariVersion(release_string)
                if v.prerelease is not None and not is_dev:
                    # Don't encourage the user to upgrade from a stable to a dev release...
                    continue

                if v == this_version:
                    continue

                if v > this_version:
                    newer_releases.append(v)
        if newer_releases:
            newest = max(newer_releases)
            _LOGGER.info(
                "A newer version of hikari is available, consider upgrading to %s",
                newest)
    except Exception as ex:
        _LOGGER.debug("Failed to fetch hikari version details", exc_info=ex)
Ejemplo n.º 2
0
    async def connect(
        cls,
        *,
        http_settings: config.HTTPSettings,
        logger: logging.Logger,
        proxy_settings: config.ProxySettings,
        log_filterer: typing.Callable[[str], str],
        url: str,
    ) -> typing.AsyncGenerator[_GatewayTransport, None]:
        """Generate a single-use websocket connection.

        This uses a single connection in a TCP connector pool, with a one-use
        aiohttp client session.

        This also handles waiting for transports to be closed properly first,
        and keeps all of the nested boilerplate out of the way of the
        rest of the code, for the most part anyway.
        """
        exit_stack = contextlib.AsyncExitStack()

        try:
            connector = net.create_tcp_connector(http_settings,
                                                 dns_cache=False,
                                                 limit=1)
            client_session = await exit_stack.enter_async_context(
                net.create_client_session(connector, True, http_settings, True,
                                          proxy_settings.trust_env, cls))

            web_socket = await exit_stack.enter_async_context(
                client_session.ws_connect(
                    max_msg_size=0,
                    proxy=proxy_settings.url,
                    proxy_headers=proxy_settings.headers,
                    url=url,
                ))

            assert isinstance(web_socket, cls)

            raised = False
            try:
                web_socket.logger = logger
                # We store this so we can remove it from debug logs
                # which enables people to send me logs in issues safely.
                # Also MyPy raises a false positive about this...
                web_socket.log_filterer = log_filterer  # type: ignore

                yield web_socket
            except errors.GatewayError:
                raised = True
                raise
            except Exception as ex:
                raised = True
                raise errors.GatewayError(
                    f"Unexpected {type(ex).__name__}: {ex}") from ex
            finally:
                if web_socket.closed:
                    logger.log(ux.TRACE, "ws was already closed")

                elif raised:
                    await web_socket.send_close(
                        code=errors.ShardCloseCode.UNEXPECTED_CONDITION,
                        message=b"unexpected fatal client error :-(",
                    )

                elif not web_socket._closing:
                    # We use a special close code here that prevents Discord
                    # randomly invalidating our session. Undocumented behaviour is
                    # nice like that...
                    await web_socket.send_close(
                        code=_RESUME_CLOSE_CODE,
                        message=b"client is shutting down",
                    )

        except (aiohttp.ClientOSError, aiohttp.ClientConnectionError,
                aiohttp.WSServerHandshakeError) as ex:
            # Windows will sometimes raise an aiohttp.ClientOSError
            # If we cannot do DNS lookup, this will fail with a ClientConnectionError
            # usually.
            raise errors.GatewayConnectionError(
                f"Failed to connect to Discord: {ex!r}") from ex

        finally:
            await exit_stack.aclose()

            # We have to sleep to allow aiohttp time to close SSL transports...
            # https://github.com/aio-libs/aiohttp/issues/1925
            # https://docs.aiohttp.org/en/stable/client_advanced.html#graceful-shutdown
            await asyncio.sleep(0.25)