Skip to content

Commit 2c779b6

Browse files
authored
Merge branch 'main' into fix/paginator-options
2 parents 170fa3a + 7347f59 commit 2c779b6

File tree

9 files changed

+259
-232
lines changed

9 files changed

+259
-232
lines changed

.codecov.yml

Lines changed: 0 additions & 57 deletions
This file was deleted.

.github/workflows/lint_test.yml

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,14 @@ jobs:
9292
# black and flake8 action. As pre-commit does not support user installs,
9393
# we set PIP_USER=0 to not do a user install.
9494
- name: Run pre-commit hooks
95+
id: pre-commit
9596
run: export PIP_USER=0; SKIP="no-commit-to-branch,black,flake8" pre-commit run --all-files
9697

9798
# Run black seperately as we don't want to reformat the files
9899
# just error if something isn't formatted correctly.
99100
- name: Check files with black
101+
id: black
102+
if: always() && (steps.pre-commit.outcome == 'success' || steps.pre-commit.outcome == 'failure')
100103
run: black . --check --diff --color
101104

102105
# Run flake8 and have it format the linting errors in the format of
@@ -108,6 +111,8 @@ jobs:
108111
# Format used:
109112
# ::error file={filename},line={line},col={col}::{message}
110113
- name: Run flake8
114+
id: flake8
115+
if: always() && (steps.pre-commit.outcome == 'success' || steps.pre-commit.outcome == 'failure')
111116
run: "flake8 \
112117
--format='::error file=%(path)s,line=%(row)d,col=%(col)d::\
113118
[flake8] %(code)s: %(text)s'"
@@ -162,12 +167,50 @@ jobs:
162167
# This is saved to ./.coverage to be used by codecov to link a
163168
# coverage report to github.
164169
- name: Run tests and generate coverage report
165-
run: python -m pytest -n auto --dist loadfile --cov --disable-warnings -q
170+
id: run_tests
171+
run: python -m pytest tests -n auto --dist loadfile --cov --disable-warnings -q
166172

167-
# This step will publish the coverage reports to codecov.io and
173+
# This step will publish the coverage reports to coveralls.io and
168174
# print a "job" link in the output of the GitHub Action
169-
- name: Publish coverage report to codecov.io
170-
run: python -m codecov
175+
- name: Publish coverage report to coveralls.io
176+
# upload coverage even if a test run failed
177+
# this is a test, and may be removed in the future
178+
if: always() && (steps.run_tests.outcome == 'success' || steps.run_tests.outcome == 'failure')
179+
# important that we don't fail the workflow when coveralls is down
180+
continue-on-error: true
181+
env:
182+
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
183+
COVERALLS_FLAG_NAME: coverage-${{ runner.os }}-python-${{ env.PYTHON_VERSION }}
184+
COVERALLS_PARALLEL: true
185+
COVERALLS_SERVICE_NAME: github
186+
run: python -m coveralls
187+
188+
coveralls-finish:
189+
name: Indicate completion to coveralls.io
190+
runs-on: ubuntu-latest
191+
needs: test
192+
# we don't want to fail the workflow when coveralls is down
193+
continue-on-error: true
194+
# we always want to ensure we attempt to send a finish to coveralls
195+
if: always()
196+
steps:
197+
# Set up a consistent version of Python
198+
- name: Set up Python 3.9
199+
id: python
200+
uses: actions/setup-python@v2
201+
with:
202+
python-version: '3.9'
203+
- name: Coveralls Finished
204+
continue-on-error: true
205+
env:
206+
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
207+
COVERALLS_SERVICE_NAME: github
208+
# NOTE: this has a small thing where this will not always be the same with the poetry.lock file
209+
# given how this is installed for one api request, its not worth pinning to me.
210+
# any bugs caused by this can be solved when they occur
211+
run: |
212+
python3 -m pip install --upgrade coveralls
213+
python3 -m coveralls --finish
171214
172215
artifact:
173216
name: Generate Artifact

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
# Discord Modmail
22

33
[![Lint & Test](https://img.shields.io/github/workflow/status/discord-modmail/modmail/Lint%20&%20Test/main?label=Lint+%26+Test&logo=github&style=flat)](https://github.com/discord-modmail/modmail/actions/workflows/lint_test.yml "Lint and Test")
4-
[![Code Coverage](https://img.shields.io/codecov/c/gh/discord-modmail/modmail/main?logo=codecov&style=flat&label=Code+Coverage)](https://app.codecov.io/gh/discord-modmail/modmail "Code Coverage")
5-
[![Codacy Grade](https://img.shields.io/codacy/grade/78be21a49835484595aea556d5920638?logo=codacy&style=flat&label=Code+Quality)](https://www.codacy.com/gh/discord-modmail/modmail/dashboard "Codacy Grade")
4+
[![Code Coverage](https://img.shields.io/coveralls/github/discord-modmail/modmail?logo=coveralls&style=flat&label=Code+Coverage)](https://coveralls.io/github/discord-modmail/modmail)
65
[![Python](https://img.shields.io/static/v1?label=Python&message=3.8+%7C+3.9&color=blue&logo=Python&style=flat)](https://www.python.org/downloads/ "Python 3.8 | 3.9")
76
[![License](https://img.shields.io/github/license/discord-modmail/modmail?style=flat&label=License)](./LICENSE "License file")
87
[![Code Style](https://img.shields.io/static/v1?label=Code%20Style&message=black&color=000000&style=flat)](https://github.com/psf/black "The uncompromising python formatter")

modmail/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
1+
import asyncio
12
import logging
23
import logging.handlers
4+
import os
35
from pathlib import Path
46

57
import coloredlogs
68

79
from modmail.log import ModmailLogger
810

911

12+
# On windows aiodns's asyncio support relies on APIs like add_reader (which aiodns uses)
13+
# are not guaranteed to be available, and in particular are not available when using the
14+
# ProactorEventLoop on Windows, this method is only supported with Windows SelectorEventLoop
15+
if os.name == "nt":
16+
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
17+
1018
logging.TRACE = 5
1119
logging.NOTICE = 25
1220
logging.addLevelName(logging.TRACE, "TRACE")

modmail/bot.py

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import asyncio
22
import logging
33
import signal
4+
import socket
45
import typing as t
5-
from typing import Any
66

7+
import aiohttp
78
import arrow
89
import discord
9-
from aiohttp import ClientSession
1010
from discord import Activity, AllowedMentions, Intents
1111
from discord.client import _cleanup_loop
1212
from discord.ext import commands
@@ -41,9 +41,12 @@ class ModmailBot(commands.Bot):
4141
def __init__(self, **kwargs):
4242
self.config = CONFIG
4343
self.start_time: t.Optional[arrow.Arrow] = None # arrow.utcnow()
44-
self.http_session: t.Optional[ClientSession] = None
44+
self.http_session: t.Optional[aiohttp.ClientSession] = None
4545
self.dispatcher = Dispatcher()
4646

47+
self._connector = None
48+
self._resolver = None
49+
4750
status = discord.Status.online
4851
activity = Activity(type=discord.ActivityType.listening, name="users dming me!")
4952
# listen to messages mentioning the bot or matching the prefix
@@ -65,6 +68,24 @@ def __init__(self, **kwargs):
6568
**kwargs,
6669
)
6770

71+
async def create_connectors(self, *args, **kwargs) -> None:
72+
"""Re-create the connector and set up sessions before logging into Discord."""
73+
# Use asyncio for DNS resolution instead of threads so threads aren't spammed.
74+
self._resolver = aiohttp.AsyncResolver()
75+
76+
# Use AF_INET as its socket family to prevent HTTPS related problems both locally
77+
# and in production.
78+
self._connector = aiohttp.TCPConnector(
79+
resolver=self._resolver,
80+
family=socket.AF_INET,
81+
)
82+
83+
# Client.login() will call HTTPClient.static_login() which will create a session using
84+
# this connector attribute.
85+
self.http.connector = self._connector
86+
87+
self.http_session = aiohttp.ClientSession(connector=self._connector)
88+
6889
async def start(self, token: str, reconnect: bool = True) -> None:
6990
"""
7091
Start the bot.
@@ -74,8 +95,8 @@ async def start(self, token: str, reconnect: bool = True) -> None:
7495
"""
7596
try:
7697
# create the aiohttp session
77-
self.http_session = ClientSession(loop=self.loop)
78-
self.logger.trace("Created ClientSession.")
98+
await self.create_connectors()
99+
self.logger.trace("Created aiohttp.ClientSession.")
79100
# set start time to when we started the bot.
80101
# This is now, since we're about to connect to the gateway.
81102
# This should also be before we load any extensions, since if they have a load time, it should
@@ -122,7 +143,7 @@ def run(self, *args, **kwargs) -> None:
122143
except NotImplementedError:
123144
pass
124145

125-
def stop_loop_on_completion(f: Any) -> None:
146+
def stop_loop_on_completion(f: t.Any) -> None:
126147
loop.stop()
127148

128149
future = asyncio.ensure_future(self.start(*args, **kwargs), loop=loop)
@@ -164,10 +185,16 @@ async def close(self) -> None:
164185
except Exception:
165186
self.logger.error(f"Exception occured while removing cog {cog.name}", exc_info=True)
166187

188+
await super().close()
189+
167190
if self.http_session:
168191
await self.http_session.close()
169192

170-
await super().close()
193+
if self._connector:
194+
await self._connector.close()
195+
196+
if self._resolver:
197+
await self._resolver.close()
171198

172199
def load_extensions(self) -> None:
173200
"""Load all enabled extensions."""

0 commit comments

Comments
 (0)