From fba165037943fda90039ec9cadf0649cfae0e781 Mon Sep 17 00:00:00 2001 From: Sebastiaan Zeeff <33516116+SebastiaanZ@users.noreply.github.com> Date: Mon, 23 Sep 2019 21:13:47 +0200 Subject: Fix failing duration conversion https://github.com/python-discord/bot/issues/446 The current ExpirationDate converter does not convert duration strings to `datetime.datetime` objects correctly. To remedy the problem, I've written a new Duration converter that uses regex matching to extract the relevant duration units and `dateutil.relativedelta.relativedelta` to compute a `datetime.datetime` that's the given duration in the future. I've left the old `ExpirationDate` converter in place for now, since the new Duration converter may not be the most optimal method. However, given the importance of being able to convert durations for moderation tasks, I think it's better to implement Duration now and rethink the approach later. This commit closes #446 --- bot/cogs/antispam.py | 4 +- bot/cogs/moderation.py | 12 +++--- bot/cogs/reminders.py | 8 ++-- bot/cogs/superstarify/__init__.py | 4 +- bot/converters.py | 35 +++++++++++++++++ tests/test_converters.py | 82 +++++++++++++++++++++++++++++++++++++-- 6 files changed, 127 insertions(+), 18 deletions(-) diff --git a/bot/cogs/antispam.py b/bot/cogs/antispam.py index 7a3360436..8dfa0ad05 100644 --- a/bot/cogs/antispam.py +++ b/bot/cogs/antispam.py @@ -17,7 +17,7 @@ from bot.constants import ( Guild as GuildConfig, Icons, STAFF_ROLES, ) -from bot.converters import ExpirationDate +from bot.converters import Duration log = logging.getLogger(__name__) @@ -102,7 +102,7 @@ class AntiSpam(Cog): self.validation_errors = validation_errors role_id = AntiSpamConfig.punishment['role_id'] self.muted_role = Object(role_id) - self.expiration_date_converter = ExpirationDate() + self.expiration_date_converter = Duration() self.message_deletion_queue = dict() self.queue_consumption_tasks = dict() diff --git a/bot/cogs/moderation.py b/bot/cogs/moderation.py index 81b3864a7..4d651bef7 100644 --- a/bot/cogs/moderation.py +++ b/bot/cogs/moderation.py @@ -14,7 +14,7 @@ from discord.ext.commands import ( from bot import constants from bot.cogs.modlog import ModLog from bot.constants import Colours, Event, Icons, MODERATION_ROLES -from bot.converters import ExpirationDate, InfractionSearchQuery +from bot.converters import Duration, InfractionSearchQuery from bot.decorators import with_role from bot.pagination import LinePaginator from bot.utils.moderation import already_has_active_infraction, post_infraction @@ -279,7 +279,7 @@ class Moderation(Scheduler, Cog): @with_role(*MODERATION_ROLES) @command() - async def tempmute(self, ctx: Context, user: Member, duration: ExpirationDate, *, reason: str = None) -> None: + async def tempmute(self, ctx: Context, user: Member, duration: Duration, *, reason: str = None) -> None: """ Create a temporary mute infraction for a user with the provided expiration and reason. @@ -345,7 +345,7 @@ class Moderation(Scheduler, Cog): @with_role(*MODERATION_ROLES) @command() - async def tempban(self, ctx: Context, user: UserTypes, duration: ExpirationDate, *, reason: str = None) -> None: + async def tempban(self, ctx: Context, user: UserTypes, duration: Duration, *, reason: str = None) -> None: """ Create a temporary ban infraction for a user with the provided expiration and reason. @@ -600,7 +600,7 @@ class Moderation(Scheduler, Cog): @with_role(*MODERATION_ROLES) @command(hidden=True, aliases=["shadowtempmute, stempmute"]) async def shadow_tempmute( - self, ctx: Context, user: Member, duration: ExpirationDate, *, reason: str = None + self, ctx: Context, user: Member, duration: Duration, *, reason: str = None ) -> None: """ Create a temporary mute infraction for a user with the provided reason. @@ -653,7 +653,7 @@ class Moderation(Scheduler, Cog): @with_role(*MODERATION_ROLES) @command(hidden=True, aliases=["shadowtempban, stempban"]) async def shadow_tempban( - self, ctx: Context, user: UserTypes, duration: ExpirationDate, *, reason: str = None + self, ctx: Context, user: UserTypes, duration: Duration, *, reason: str = None ) -> None: """ Create a temporary ban infraction for a user with the provided reason. @@ -884,7 +884,7 @@ class Moderation(Scheduler, Cog): @infraction_edit_group.command(name="duration") async def edit_duration( self, ctx: Context, - infraction_id: int, expires_at: Union[ExpirationDate, str] + infraction_id: int, expires_at: Union[Duration, str] ) -> None: """ Sets the duration of the given infraction, relative to the time of updating. diff --git a/bot/cogs/reminders.py b/bot/cogs/reminders.py index 8460de91f..c37abf21e 100644 --- a/bot/cogs/reminders.py +++ b/bot/cogs/reminders.py @@ -11,7 +11,7 @@ from discord import Colour, Embed, Message from discord.ext.commands import Bot, Cog, Context, group from bot.constants import Channels, Icons, NEGATIVE_REPLIES, POSITIVE_REPLIES, STAFF_ROLES -from bot.converters import ExpirationDate +from bot.converters import Duration from bot.pagination import LinePaginator from bot.utils.checks import without_role_check from bot.utils.scheduling import Scheduler @@ -118,12 +118,12 @@ class Reminders(Scheduler, Cog): await self._delete_reminder(reminder["id"]) @group(name="remind", aliases=("reminder", "reminders"), invoke_without_command=True) - async def remind_group(self, ctx: Context, expiration: ExpirationDate, *, content: str) -> None: + async def remind_group(self, ctx: Context, expiration: Duration, *, content: str) -> None: """Commands for managing your reminders.""" await ctx.invoke(self.new_reminder, expiration=expiration, content=content) @remind_group.command(name="new", aliases=("add", "create")) - async def new_reminder(self, ctx: Context, expiration: ExpirationDate, *, content: str) -> Optional[Message]: + async def new_reminder(self, ctx: Context, expiration: Duration, *, content: str) -> Optional[Message]: """ Set yourself a simple reminder. @@ -237,7 +237,7 @@ class Reminders(Scheduler, Cog): await ctx.invoke(self.bot.get_command("help"), "reminders", "edit") @edit_reminder_group.command(name="duration", aliases=("time",)) - async def edit_reminder_duration(self, ctx: Context, id_: int, expiration: ExpirationDate) -> None: + async def edit_reminder_duration(self, ctx: Context, id_: int, expiration: Duration) -> None: """ Edit one of your reminder's expiration. diff --git a/bot/cogs/superstarify/__init__.py b/bot/cogs/superstarify/__init__.py index f7d6a269d..b1936ef3a 100644 --- a/bot/cogs/superstarify/__init__.py +++ b/bot/cogs/superstarify/__init__.py @@ -10,7 +10,7 @@ from bot.cogs.moderation import Moderation from bot.cogs.modlog import ModLog from bot.cogs.superstarify.stars import get_nick from bot.constants import Icons, MODERATION_ROLES, POSITIVE_REPLIES -from bot.converters import ExpirationDate +from bot.converters import Duration from bot.decorators import with_role from bot.utils.moderation import post_infraction @@ -153,7 +153,7 @@ class Superstarify(Cog): @command(name='superstarify', aliases=('force_nick', 'star')) @with_role(*MODERATION_ROLES) async def superstarify( - self, ctx: Context, member: Member, expiration: ExpirationDate, reason: str = None + self, ctx: Context, member: Member, expiration: Duration, reason: str = None ) -> None: """ Force a random superstar name (like Taylor Swift) to be the user's nickname for a specified duration. diff --git a/bot/converters.py b/bot/converters.py index 7386187ab..b7340982b 100644 --- a/bot/converters.py +++ b/bot/converters.py @@ -1,4 +1,5 @@ import logging +import re from datetime import datetime from ssl import CertificateError from typing import Union @@ -6,6 +7,7 @@ from typing import Union import dateparser import discord from aiohttp import ClientConnectorError +from dateutil.relativedelta import relativedelta from discord.ext.commands import BadArgument, Context, Converter @@ -197,3 +199,36 @@ class ExpirationDate(Converter): expiry = now + (now - expiry) return expiry + + +class Duration(Converter): + """Convert duration strings into UTC datetime.datetime objects.""" + + duration_parser = re.compile( + r"((?P\d+?)(years|year|Y|y))?" + r"((?P\d+?)(months|month|m))?" + r"((?P\d+?)(weeks|week|W|w))?" + r"((?P\d+?)(days|day|D|d))?" + r"((?P\d+?)(hours|hour|H|h))?" + r"((?P\d+?)(minutes|minute|M))?" + r"((?P\d+?)(seconds|second|S|s))?" + ) + + async def convert(self, ctx: Context, duration: str) -> datetime: + """ + Converts a `duration` string to a datetime object that's `duration` in the future. + + The converter supports years (symbols: `years`, `year, `Y`, `y`), months (`months`, `month`, + `m`), weeks (`weeks`, `week`, `W`, `w`), days (`days`, `day`, `D`, `d`), hours (`hours`, + `hour`, `H`, `h`), minutes (`minutes`, `minute`, `M`), and seconds (`seconds`, `second`, + `S`, `s`), The units must be provided in descending order of magnitude. + """ + match = self.duration_parser.fullmatch(duration) + if not match: + raise BadArgument(f"`{duration}` is not a valid duration string.") + + duration_dict = {unit: int(amount) for unit, amount in match.groupdict().items() if amount} + delta = relativedelta(**duration_dict) + now = datetime.utcnow() + + return now + delta diff --git a/tests/test_converters.py b/tests/test_converters.py index 3cf774c80..3cf00035f 100644 --- a/tests/test_converters.py +++ b/tests/test_converters.py @@ -1,11 +1,12 @@ import asyncio -from datetime import datetime -from unittest.mock import MagicMock +import datetime +from unittest.mock import MagicMock, patch import pytest from discord.ext.commands import BadArgument from bot.converters import ( + Duration, ExpirationDate, TagContentConverter, TagNameConverter, @@ -17,10 +18,10 @@ from bot.converters import ( ('value', 'expected'), ( # sorry aliens - ('2199-01-01T00:00:00', datetime(2199, 1, 1)), + ('2199-01-01T00:00:00', datetime.datetime(2199, 1, 1)), ) ) -def test_expiration_date_converter_for_valid(value: str, expected: datetime): +def test_expiration_date_converter_for_valid(value: str, expected: datetime.datetime): converter = ExpirationDate() assert asyncio.run(converter.convert(None, value)) == expected @@ -91,3 +92,76 @@ def test_valid_python_identifier_for_valid(value: str): def test_valid_python_identifier_for_invalid(value: str): with pytest.raises(BadArgument, match=f'`{value}` is not a valid Python identifier'): asyncio.run(ValidPythonIdentifier.convert(None, value)) + + +FIXED_UTC_NOW = datetime.datetime.fromisoformat('2019-01-01T00:00:00') + + +@pytest.mark.parametrize( + ('duration', 'expected'), + ( + # Simple duration strings + ('1Y', datetime.datetime.fromisoformat('2020-01-01T00:00:00')), + ('1y', datetime.datetime.fromisoformat('2020-01-01T00:00:00')), + ('1year', datetime.datetime.fromisoformat('2020-01-01T00:00:00')), + ('1years', datetime.datetime.fromisoformat('2020-01-01T00:00:00')), + ('1m', datetime.datetime.fromisoformat('2019-02-01T00:00:00')), + ('1month', datetime.datetime.fromisoformat('2019-02-01T00:00:00')), + ('1months', datetime.datetime.fromisoformat('2019-02-01T00:00:00')), + ('1w', datetime.datetime.fromisoformat('2019-01-08T00:00:00')), + ('1W', datetime.datetime.fromisoformat('2019-01-08T00:00:00')), + ('1week', datetime.datetime.fromisoformat('2019-01-08T00:00:00')), + ('1weeks', datetime.datetime.fromisoformat('2019-01-08T00:00:00')), + ('1d', datetime.datetime.fromisoformat('2019-01-02T00:00:00')), + ('1D', datetime.datetime.fromisoformat('2019-01-02T00:00:00')), + ('1day', datetime.datetime.fromisoformat('2019-01-02T00:00:00')), + ('1days', datetime.datetime.fromisoformat('2019-01-02T00:00:00')), + ('1h', datetime.datetime.fromisoformat('2019-01-01T01:00:00')), + ('1H', datetime.datetime.fromisoformat('2019-01-01T01:00:00')), + ('1hour', datetime.datetime.fromisoformat('2019-01-01T01:00:00')), + ('1hours', datetime.datetime.fromisoformat('2019-01-01T01:00:00')), + ('1M', datetime.datetime.fromisoformat('2019-01-01T00:01:00')), + ('1minute', datetime.datetime.fromisoformat('2019-01-01T00:01:00')), + ('1minutes', datetime.datetime.fromisoformat('2019-01-01T00:01:00')), + ('1s', datetime.datetime.fromisoformat('2019-01-01T00:00:01')), + ('1S', datetime.datetime.fromisoformat('2019-01-01T00:00:01')), + ('1second', datetime.datetime.fromisoformat('2019-01-01T00:00:01')), + ('1seconds', datetime.datetime.fromisoformat('2019-01-01T00:00:01')), + + # Complex duration strings + ('1y1m1w1d1H1M1S', datetime.datetime.fromisoformat('2020-02-09T01:01:01')), + ('5y100S', datetime.datetime.fromisoformat('2024-01-01T00:01:40')), + ('2w28H', datetime.datetime.fromisoformat('2019-01-16T04:00:00')), + ) +) +def test_duration_converter_for_valid(duration: str, expected: datetime): + converter = Duration() + + with patch('bot.converters.datetime') as mock_datetime: + mock_datetime.utcnow.return_value = FIXED_UTC_NOW + assert asyncio.run(converter.convert(None, duration)) == expected + + +@pytest.mark.parametrize( + ('duration'), + ( + # Units in wrong order + ('1d1w'), + ('1s1y'), + + # Unknown substrings + ('1MVes'), + ('1y3breads'), + + # Missing amount + ('ym'), + + # Garbage + ('Guido van Rossum'), + ('lemon lemon lemon lemon lemon lemon lemon'), + ) +) +def test_duration_converter_for_invalid(duration: str): + converter = Duration() + with pytest.raises(BadArgument, match=f'`{duration}` is not a valid duration string.'): + asyncio.run(converter.convert(None, duration)) -- cgit v1.2.3 From 56a365b24e7f6e145718a13edc410848ab169a06 Mon Sep 17 00:00:00 2001 From: Sebastiaan Zeeff <33516116+SebastiaanZ@users.noreply.github.com> Date: Mon, 23 Sep 2019 23:03:58 +0200 Subject: Allow whitespace in duration strings and update tests https://github.com/python-discord/bot/issues/446 After review feedback and a discussion in the dev-core team, I've changed a couple of things: - Allow a space between amount and unit in the duration string; - Allow a space between different units in the duration string; - Remove the old ExpirationDate converter completely; - Remove the dependency `dateparser` from the Pipfile; - Update tests for the two types of optional spaces; - Change the test for valid cases to a more readable format; This PR closes #446 --- Pipfile | 1 - Pipfile.lock | 57 ++++++------------------ bot/converters.py | 39 ++++------------ tests/test_converters.py | 113 +++++++++++++++++++++++++++-------------------- 4 files changed, 87 insertions(+), 123 deletions(-) diff --git a/Pipfile b/Pipfile index 6a58054c1..33f44e9a6 100644 --- a/Pipfile +++ b/Pipfile @@ -17,7 +17,6 @@ aio-pika = "*" python-dateutil = "*" deepdiff = "*" requests = "*" -dateparser = "*" more_itertools = "~=7.2" urllib3 = ">=1.24.2,<1.25" diff --git a/Pipfile.lock b/Pipfile.lock index 9bdcff923..7674acb26 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "29aaaa90a070d544e5b39fb6033410daa9bb7f658077205e44099f3175f6822b" + "sha256": "d582b1e226b1ce675817161d9059352d8f303c1bc1646034a9e73673f6581d12" }, "pipfile-spec": 6, "requires": { @@ -62,10 +62,10 @@ }, "aiormq": { "hashes": [ - "sha256:0b755b748d87d5ec63b4b7f162102333bf0901caf1f8a2bf29467bbdd54e637d", - "sha256:f8eef1f98bc331a266404d925745fac589dab30412688564d740754d6d643863" + "sha256:c3e4dd01a2948a75f739fb637334dbb8c6f1a4cecf74d5ed662dc3bab7f39973", + "sha256:e220d3f9477bb2959b729b79bec815148ddb8a7686fc6c3d05d41c88ebd7c59e" ], - "version": "==2.7.5" + "version": "==2.8.0" }, "alabaster": { "hashes": [ @@ -150,14 +150,6 @@ ], "version": "==3.0.4" }, - "dateparser": { - "hashes": [ - "sha256:983d84b5e3861cb0aa240cad07f12899bb10b62328aae188b9007e04ce37d665", - "sha256:e1eac8ef28de69a554d5fcdb60b172d526d61924b1a40afbbb08df459a36006b" - ], - "index": "pypi", - "version": "==0.7.2" - }, "deepdiff": { "hashes": [ "sha256:1123762580af0904621136d117c8397392a244d3ff0fa0a50de57a7939582476", @@ -342,10 +334,10 @@ }, "packaging": { "hashes": [ - "sha256:a7ac867b97fdc07ee80a8058fe4435ccd274ecc3b0ed61d852d7d53055528cf9", - "sha256:c491ca87294da7cc01902edbe30a5bc6c4c28172b5138ab4e4aa1b9d7bfaeafe" + "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", + "sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108" ], - "version": "==19.1" + "version": "==19.2" }, "pamqp": { "hashes": [ @@ -432,22 +424,6 @@ "index": "pypi", "version": "==5.1.2" }, - "regex": { - "hashes": [ - "sha256:1e9f9bc44ca195baf0040b1938e6801d2f3409661c15fe57f8164c678cfc663f", - "sha256:587b62d48ca359d2d4f02d486f1f0aa9a20fbaf23a9d4198c4bed72ab2f6c849", - "sha256:835ccdcdc612821edf132c20aef3eaaecfb884c9454fdc480d5887562594ac61", - "sha256:93f6c9da57e704e128d90736430c5c59dd733327882b371b0cae8833106c2a21", - "sha256:a46f27d267665016acb3ec8c6046ec5eae8cf80befe85ba47f43c6f5ec636dcd", - "sha256:c5c8999b3a341b21ac2c6ec704cfcccbc50f1fedd61b6a8ee915ca7fd4b0a557", - "sha256:d4d1829cf97632673aa49f378b0a2c3925acd795148c5ace8ef854217abbee89", - "sha256:d96479257e8e4d1d7800adb26bf9c5ca5bab1648a1eddcac84d107b73dc68327", - "sha256:f20f4912daf443220436759858f96fefbfc6c6ba9e67835fd6e4e9b73582791a", - "sha256:f2b37b5b2c2a9d56d9e88efef200ec09c36c7f323f9d58d0b985a90923df386d", - "sha256:fe765b809a1f7ce642c2edeee351e7ebd84391640031ba4b60af8d91a9045890" - ], - "version": "==2019.8.19" - }, "requests": { "hashes": [ "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", @@ -526,13 +502,6 @@ ], "version": "==1.1.3" }, - "tzlocal": { - "hashes": [ - "sha256:11c9f16e0a633b4b60e1eede97d8a46340d042e67b670b290ca526576e039048", - "sha256:949b9dd5ba4be17190a80c0268167d7e6c92c62b30026cf9764caf3e308e5590" - ], - "version": "==2.0.0" - }, "urllib3": { "hashes": [ "sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4", @@ -800,10 +769,10 @@ }, "packaging": { "hashes": [ - "sha256:a7ac867b97fdc07ee80a8058fe4435ccd274ecc3b0ed61d852d7d53055528cf9", - "sha256:c491ca87294da7cc01902edbe30a5bc6c4c28172b5138ab4e4aa1b9d7bfaeafe" + "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", + "sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108" ], - "version": "==19.1" + "version": "==19.2" }, "pluggy": { "hashes": [ @@ -857,11 +826,11 @@ }, "pytest": { "hashes": [ - "sha256:95d13143cc14174ca1a01ec68e84d76ba5d9d493ac02716fd9706c949a505210", - "sha256:b78fe2881323bd44fd9bd76e5317173d4316577e7b1cddebae9136a4495ec865" + "sha256:813b99704b22c7d377bbd756ebe56c35252bb710937b46f207100e843440b3c2", + "sha256:cc6620b96bc667a0c8d4fa592a8c9c94178a1bd6cc799dbb057dfd9286d31a31" ], "index": "pypi", - "version": "==5.1.2" + "version": "==5.1.3" }, "pytest-cov": { "hashes": [ diff --git a/bot/converters.py b/bot/converters.py index b7340982b..db3e2a426 100644 --- a/bot/converters.py +++ b/bot/converters.py @@ -4,7 +4,6 @@ from datetime import datetime from ssl import CertificateError from typing import Union -import dateparser import discord from aiohttp import ClientConnectorError from dateutil.relativedelta import relativedelta @@ -179,39 +178,17 @@ class TagContentConverter(Converter): return tag_content -class ExpirationDate(Converter): - """Convert relative expiration date into UTC datetime using dateparser.""" - - DATEPARSER_SETTINGS = { - 'PREFER_DATES_FROM': 'future', - 'TIMEZONE': 'UTC', - 'TO_TIMEZONE': 'UTC' - } - - async def convert(self, ctx: Context, expiration_string: str) -> datetime: - """Convert relative expiration date into UTC datetime.""" - expiry = dateparser.parse(expiration_string, settings=self.DATEPARSER_SETTINGS) - if expiry is None: - raise BadArgument(f"Failed to parse expiration date from `{expiration_string}`") - - now = datetime.utcnow() - if expiry < now: - expiry = now + (now - expiry) - - return expiry - - class Duration(Converter): """Convert duration strings into UTC datetime.datetime objects.""" duration_parser = re.compile( - r"((?P\d+?)(years|year|Y|y))?" - r"((?P\d+?)(months|month|m))?" - r"((?P\d+?)(weeks|week|W|w))?" - r"((?P\d+?)(days|day|D|d))?" - r"((?P\d+?)(hours|hour|H|h))?" - r"((?P\d+?)(minutes|minute|M))?" - r"((?P\d+?)(seconds|second|S|s))?" + r"((?P\d+?) ?(years|year|Y|y) ?)?" + r"((?P\d+?) ?(months|month|m) ?)?" + r"((?P\d+?) ?(weeks|week|W|w) ?)?" + r"((?P\d+?) ?(days|day|D|d) ?)?" + r"((?P\d+?) ?(hours|hour|H|h) ?)?" + r"((?P\d+?) ?(minutes|minute|M) ?)?" + r"((?P\d+?) ?(seconds|second|S|s))?" ) async def convert(self, ctx: Context, duration: str) -> datetime: @@ -227,7 +204,7 @@ class Duration(Converter): if not match: raise BadArgument(f"`{duration}` is not a valid duration string.") - duration_dict = {unit: int(amount) for unit, amount in match.groupdict().items() if amount} + duration_dict = {unit: int(amount) for unit, amount in match.groupdict(default=0).items()} delta = relativedelta(**duration_dict) now = datetime.utcnow() diff --git a/tests/test_converters.py b/tests/test_converters.py index 3cf00035f..35fc5d88e 100644 --- a/tests/test_converters.py +++ b/tests/test_converters.py @@ -3,29 +3,17 @@ import datetime from unittest.mock import MagicMock, patch import pytest +from dateutil.relativedelta import relativedelta from discord.ext.commands import BadArgument from bot.converters import ( Duration, - ExpirationDate, TagContentConverter, TagNameConverter, ValidPythonIdentifier, ) -@pytest.mark.parametrize( - ('value', 'expected'), - ( - # sorry aliens - ('2199-01-01T00:00:00', datetime.datetime(2199, 1, 1)), - ) -) -def test_expiration_date_converter_for_valid(value: str, expected: datetime.datetime): - converter = ExpirationDate() - assert asyncio.run(converter.convert(None, value)) == expected - - @pytest.mark.parametrize( ('value', 'expected'), ( @@ -97,46 +85,68 @@ def test_valid_python_identifier_for_invalid(value: str): FIXED_UTC_NOW = datetime.datetime.fromisoformat('2019-01-01T00:00:00') -@pytest.mark.parametrize( - ('duration', 'expected'), - ( +@pytest.fixture( + params=( # Simple duration strings - ('1Y', datetime.datetime.fromisoformat('2020-01-01T00:00:00')), - ('1y', datetime.datetime.fromisoformat('2020-01-01T00:00:00')), - ('1year', datetime.datetime.fromisoformat('2020-01-01T00:00:00')), - ('1years', datetime.datetime.fromisoformat('2020-01-01T00:00:00')), - ('1m', datetime.datetime.fromisoformat('2019-02-01T00:00:00')), - ('1month', datetime.datetime.fromisoformat('2019-02-01T00:00:00')), - ('1months', datetime.datetime.fromisoformat('2019-02-01T00:00:00')), - ('1w', datetime.datetime.fromisoformat('2019-01-08T00:00:00')), - ('1W', datetime.datetime.fromisoformat('2019-01-08T00:00:00')), - ('1week', datetime.datetime.fromisoformat('2019-01-08T00:00:00')), - ('1weeks', datetime.datetime.fromisoformat('2019-01-08T00:00:00')), - ('1d', datetime.datetime.fromisoformat('2019-01-02T00:00:00')), - ('1D', datetime.datetime.fromisoformat('2019-01-02T00:00:00')), - ('1day', datetime.datetime.fromisoformat('2019-01-02T00:00:00')), - ('1days', datetime.datetime.fromisoformat('2019-01-02T00:00:00')), - ('1h', datetime.datetime.fromisoformat('2019-01-01T01:00:00')), - ('1H', datetime.datetime.fromisoformat('2019-01-01T01:00:00')), - ('1hour', datetime.datetime.fromisoformat('2019-01-01T01:00:00')), - ('1hours', datetime.datetime.fromisoformat('2019-01-01T01:00:00')), - ('1M', datetime.datetime.fromisoformat('2019-01-01T00:01:00')), - ('1minute', datetime.datetime.fromisoformat('2019-01-01T00:01:00')), - ('1minutes', datetime.datetime.fromisoformat('2019-01-01T00:01:00')), - ('1s', datetime.datetime.fromisoformat('2019-01-01T00:00:01')), - ('1S', datetime.datetime.fromisoformat('2019-01-01T00:00:01')), - ('1second', datetime.datetime.fromisoformat('2019-01-01T00:00:01')), - ('1seconds', datetime.datetime.fromisoformat('2019-01-01T00:00:01')), + ('1Y', {"years": 1}), + ('1y', {"years": 1}), + ('1year', {"years": 1}), + ('1years', {"years": 1}), + ('1m', {"months": 1}), + ('1month', {"months": 1}), + ('1months', {"months": 1}), + ('1w', {"weeks": 1}), + ('1W', {"weeks": 1}), + ('1week', {"weeks": 1}), + ('1weeks', {"weeks": 1}), + ('1d', {"days": 1}), + ('1D', {"days": 1}), + ('1day', {"days": 1}), + ('1days', {"days": 1}), + ('1h', {"hours": 1}), + ('1H', {"hours": 1}), + ('1hour', {"hours": 1}), + ('1hours', {"hours": 1}), + ('1M', {"minutes": 1}), + ('1minute', {"minutes": 1}), + ('1minutes', {"minutes": 1}), + ('1s', {"seconds": 1}), + ('1S', {"seconds": 1}), + ('1second', {"seconds": 1}), + ('1seconds', {"seconds": 1}), # Complex duration strings - ('1y1m1w1d1H1M1S', datetime.datetime.fromisoformat('2020-02-09T01:01:01')), - ('5y100S', datetime.datetime.fromisoformat('2024-01-01T00:01:40')), - ('2w28H', datetime.datetime.fromisoformat('2019-01-16T04:00:00')), + ( + '1y1m1w1d1H1M1S', + { + "years": 1, + "months": 1, + "weeks": 1, + "days": 1, + "hours": 1, + "minutes": 1, + "seconds": 1 + } + ), + ('5y100S', {"years": 5, "seconds": 100}), + ('2w28H', {"weeks": 2, "hours": 28}), + + # Duration strings with spaces + ('1 year 2 months', {"years": 1, "months": 2}), + ('1d 2H', {"days": 1, "hours": 2}), + ('1 week2 days', {"weeks": 1, "days": 2}), ) ) -def test_duration_converter_for_valid(duration: str, expected: datetime): - converter = Duration() +def create_future_datetime(request): + """Yields duration string and target datetime.datetime object.""" + duration, duration_dict = request.param + future_datetime = FIXED_UTC_NOW + relativedelta(**duration_dict) + yield duration, future_datetime + +def test_duration_converter_for_valid(create_future_datetime: tuple): + converter = Duration() + duration, expected = create_future_datetime with patch('bot.converters.datetime') as mock_datetime: mock_datetime.utcnow.return_value = FIXED_UTC_NOW assert asyncio.run(converter.convert(None, duration)) == expected @@ -149,6 +159,10 @@ def test_duration_converter_for_valid(duration: str, expected: datetime): ('1d1w'), ('1s1y'), + # Duplicated units + ('1 year 2 years'), + ('1 M 10 minutes'), + # Unknown substrings ('1MVes'), ('1y3breads'), @@ -156,6 +170,11 @@ def test_duration_converter_for_valid(duration: str, expected: datetime): # Missing amount ('ym'), + # Incorrect whitespace + (" 1y"), + ("1S "), + ("1y 1m"), + # Garbage ('Guido van Rossum'), ('lemon lemon lemon lemon lemon lemon lemon'), -- cgit v1.2.3 From 3691657184304e2b1e5e06aea75cf9bbaf858bc6 Mon Sep 17 00:00:00 2001 From: Sebastiaan Zeeff <33516116+SebastiaanZ@users.noreply.github.com> Date: Mon, 23 Sep 2019 23:19:05 +0200 Subject: Restructure Duration converter docstring In this commit, I've restructured the Duration converter's docstring to a more readable format: a bullet point list. --- bot/converters.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/bot/converters.py b/bot/converters.py index db3e2a426..339da7b60 100644 --- a/bot/converters.py +++ b/bot/converters.py @@ -195,10 +195,16 @@ class Duration(Converter): """ Converts a `duration` string to a datetime object that's `duration` in the future. - The converter supports years (symbols: `years`, `year, `Y`, `y`), months (`months`, `month`, - `m`), weeks (`weeks`, `week`, `W`, `w`), days (`days`, `day`, `D`, `d`), hours (`hours`, - `hour`, `H`, `h`), minutes (`minutes`, `minute`, `M`), and seconds (`seconds`, `second`, - `S`, `s`), The units must be provided in descending order of magnitude. + The converter supports the following symbols for each unit of time: + - years: `Y`, `y`, `year`, `years` + - months: `m`, `month`, `months` + - weeks: `w`, `W`, `week`, `weeks` + - days: `d`, `D`, `day`, `days` + - hours: `H`, `h`, `hour`, `hours` + - minutes: `M`, `minute`, `minutes` + - seconds: `S`, `s`, `second`, `seconds` + + The units need to be provided in descending order of magnitude. """ match = self.duration_parser.fullmatch(duration) if not match: -- cgit v1.2.3 From fe9efe6fc845380705b4c99ae861bf0c6bb1cd3a Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Mon, 23 Sep 2019 17:01:21 +1000 Subject: Reformat indents, use Docker@2 task for azure instead of sh script. --- azure-pipelines.yml | 129 +++++++++++++++++++++++------------------------- scripts/deploy-azure.sh | 12 ----- 2 files changed, 63 insertions(+), 78 deletions(-) delete mode 100644 scripts/deploy-azure.sh diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 4dcad685c..0c47d2544 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -6,69 +6,66 @@ variables: PIPENV_NOSPIN: 1 jobs: -- job: test - displayName: 'Lint & Test' - - pool: - vmImage: ubuntu-16.04 - - variables: - PIPENV_CACHE_DIR: ".cache/pipenv" - PIP_CACHE_DIR: ".cache/pip" - PIP_SRC: ".cache/src" - - steps: - - script: | - sudo apt-get update - sudo apt-get install build-essential curl docker libffi-dev libfreetype6-dev libxml2 libxml2-dev libxslt1-dev zlib1g zlib1g-dev - displayName: 'Install base dependencies' - - - task: UsePythonVersion@0 - displayName: 'Set Python version' - inputs: - versionSpec: '3.7.x' - addToPath: true - - - script: sudo pip install pipenv - displayName: 'Install pipenv' - - - script: pipenv install --dev --deploy --system - displayName: 'Install project using pipenv' - - - script: python -m flake8 - displayName: 'Run linter' - - - script: BOT_API_KEY=foo BOT_TOKEN=bar WOLFRAM_API_KEY=baz python -m pytest --junitxml=junit.xml --cov=bot --cov-branch --cov-report=term --cov-report=xml tests - displayName: Run tests - - - task: PublishCodeCoverageResults@1 - displayName: 'Publish Coverage Results' - condition: succeededOrFailed() - inputs: - codeCoverageTool: Cobertura - summaryFileLocation: coverage.xml - - - task: PublishTestResults@2 - displayName: 'Publish Test Results' - condition: succeededOrFailed() - inputs: - testResultsFiles: junit.xml - testRunTitle: 'Bot Test results' - -- job: build - displayName: 'Build Containers' - dependsOn: 'test' - - steps: - - task: Docker@1 - displayName: 'Login: Docker Hub' - - inputs: - containerregistrytype: 'Container Registry' - dockerRegistryEndpoint: 'DockerHub' - command: 'login' - - - task: ShellScript@2 - displayName: 'Build and deploy containers' - inputs: - scriptPath: scripts/deploy-azure.sh + - job: test + displayName: 'Lint & Test' + + pool: + vmImage: ubuntu-16.04 + + variables: + PIPENV_CACHE_DIR: ".cache/pipenv" + PIP_CACHE_DIR: ".cache/pip" + PIP_SRC: ".cache/src" + + steps: + - script: | + sudo apt-get update + sudo apt-get install build-essential curl docker libffi-dev libfreetype6-dev libxml2 libxml2-dev libxslt1-dev zlib1g zlib1g-dev + displayName: 'Install base dependencies' + + - task: UsePythonVersion@0 + displayName: 'Set Python version' + inputs: + versionSpec: '3.7.x' + addToPath: true + + - script: sudo pip install pipenv + displayName: 'Install pipenv' + + - script: pipenv install --dev --deploy --system + displayName: 'Install project using pipenv' + + - script: python -m flake8 + displayName: 'Run linter' + + - script: BOT_API_KEY=foo BOT_TOKEN=bar WOLFRAM_API_KEY=baz python -m pytest --junitxml=junit.xml --cov=bot --cov-branch --cov-report=term --cov-report=xml tests + displayName: Run tests + + - task: PublishCodeCoverageResults@1 + displayName: 'Publish Coverage Results' + condition: succeededOrFailed() + inputs: + codeCoverageTool: Cobertura + summaryFileLocation: coverage.xml + + - task: PublishTestResults@2 + displayName: 'Publish Test Results' + condition: succeededOrFailed() + inputs: + testResultsFiles: junit.xml + testRunTitle: 'Bot Test results' + + - job: build + displayName: 'Build & Push Container' + dependsOn: 'test' + + steps: + - task: Docker@2 + displayName: 'Build & Push Container' + inputs: + containerRegistry: 'DockerHub' + repository: 'pythondiscord/bot' + command: 'buildAndPush' + Dockerfile: 'Dockerfile' + buildContext: '.' + tags: 'latest' diff --git a/scripts/deploy-azure.sh b/scripts/deploy-azure.sh deleted file mode 100644 index ed4b719e2..000000000 --- a/scripts/deploy-azure.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -cd .. - -# Build and deploy on master branch, only if not a pull request -if [[ ($BUILD_SOURCEBRANCHNAME == 'master') && ($SYSTEM_PULLREQUEST_PULLREQUESTID == '') ]]; then - echo "Building image" - docker build -t pythondiscord/bot:latest . - - echo "Pushing image" - docker push pythondiscord/bot:latest -fi -- cgit v1.2.3 From 6a2d6b973bd3c4db3f6cca5972dfc05c32b18be4 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Mon, 23 Sep 2019 18:57:28 +1000 Subject: Pin dependencies, pre-build `regex` pkg. --- .gitignore | 1 - Pipfile | 34 ++++++++++----------- Pipfile.lock | 2 +- wheels/regex-2019.8.19-cp37-cp37m-linux_x86_64.whl | Bin 0 -> 688982 bytes 4 files changed, 17 insertions(+), 20 deletions(-) create mode 100644 wheels/regex-2019.8.19-cp37-cp37m-linux_x86_64.whl diff --git a/.gitignore b/.gitignore index cda3aeb9f..261fa179f 100644 --- a/.gitignore +++ b/.gitignore @@ -20,7 +20,6 @@ lib64/ parts/ sdist/ var/ -wheels/ *.egg-info/ .installed.cfg *.egg diff --git a/Pipfile b/Pipfile index 33f44e9a6..990653f92 100644 --- a/Pipfile +++ b/Pipfile @@ -5,18 +5,18 @@ name = "pypi" [packages] discord-py = "~=1.2" -aiodns = "*" -logmatic-python = "*" -aiohttp = "*" -sphinx = "*" -markdownify = "*" -lxml = "*" -pyyaml = "*" -fuzzywuzzy = "*" -aio-pika = "*" -python-dateutil = "*" -deepdiff = "*" -requests = "*" +aiodns = "~=2.0" +logmatic-python = "~=0.1" +aiohttp = "~=3.5" +sphinx = "~=2.2" +markdownify = "~=0.4" +lxml = "~=4.4" +pyyaml = "~=5.1" +fuzzywuzzy = "~=0.17" +aio-pika = "~=6.1" +python-dateutil = "~=2.8" +deepdiff = "~=4.0" +requests = "~=2.22" more_itertools = "~=7.2" urllib3 = ">=1.24.2,<1.25" @@ -30,10 +30,10 @@ flake8-string-format = "~=0.2" flake8-tidy-imports = "~=2.0" flake8-todo = "~=0.7" pre-commit = "~=1.18" -safety = "*" -dodgy = "*" -pytest = "*" -pytest-cov = "*" +safety = "~=1.8" +dodgy = "~=0.1" +pytest = "~=5.1" +pytest-cov = "~=2.7" [requires] python_version = "3.7" @@ -46,5 +46,3 @@ build = "docker build -t pythondiscord/bot:latest -f docker/bot.Dockerfile ." push = "docker push pythondiscord/bot:latest" buildbase = "docker build -t pythondiscord/bot-base:latest -f docker/base.Dockerfile ." pushbase = "docker push pythondiscord/bot-base:latest" -buildci = "docker build -t pythondiscord/bot-ci:latest -f docker/ci.Dockerfile ." -pushci = "docker push pythondiscord/bot-ci:latest" diff --git a/Pipfile.lock b/Pipfile.lock index 7674acb26..58489c60e 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d582b1e226b1ce675817161d9059352d8f303c1bc1646034a9e73673f6581d12" + "sha256": "6c2d9ea980f1dbe954237de6d173ffa9ba480aa5cf0a03c4d7840b0739d4e2fa" }, "pipfile-spec": 6, "requires": { diff --git a/wheels/regex-2019.8.19-cp37-cp37m-linux_x86_64.whl b/wheels/regex-2019.8.19-cp37-cp37m-linux_x86_64.whl new file mode 100644 index 000000000..0be4519fe Binary files /dev/null and b/wheels/regex-2019.8.19-cp37-cp37m-linux_x86_64.whl differ -- cgit v1.2.3 From 8755a93a2866f7a4b94e3e0493f07141f55d03b1 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Mon, 23 Sep 2019 20:07:15 +1000 Subject: Add a dev-only compose for new devs who don't have `site`. --- docker-compose.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 docker-compose.yml diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..1c55482dc --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,28 @@ +# This docker compose is used for quick setups of the site and database which +# the bot project relies on for testing. Use it if you haven't got a +# ready-to-use site environment already setup. + +version: "3.7" + +services: + postgres: + image: postgres:11-alpine + ports: + - "127.0.0.1:7777:5432" + environment: + POSTGRES_DB: pysite + POSTGRES_PASSWORD: pysite + POSTGRES_USER: pysite + + web: + image: pythondiscord/site:latest + command: "./manage.py runserver 0.0.0.0:8000" + ports: + - "127.0.0.1:8000:8000" + depends_on: + - postgres + environment: + DATABASE_URL: postgres://pysite:pysite@postgres:5432/pysite + DEBUG: "true" + SECRET_KEY: suitable-for-development-only + STATIC_ROOT: /var/www/static -- cgit v1.2.3 From 5fdacedccb70b2f549439aaa709beb24e84737c1 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Mon, 23 Sep 2019 20:16:59 +1000 Subject: Remove unneeded Pipfile scripts and azure stages/vars. --- Dockerfile | 33 +++++++++++++-------------------- Pipfile | 4 +--- azure-pipelines.yml | 8 -------- docker/ci.Dockerfile | 20 -------------------- 4 files changed, 14 insertions(+), 51 deletions(-) delete mode 100644 docker/ci.Dockerfile diff --git a/Dockerfile b/Dockerfile index aa6333380..21a938c4f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,27 +1,20 @@ -FROM python:3.7-alpine3.7 +FROM python:3.7-slim -RUN apk add --no-cache \ - build-base \ - freetype-dev \ - git \ - jpeg-dev \ - libffi-dev \ - libxml2 \ - libxml2-dev \ - libxslt-dev \ - tini \ - zlib \ - zlib-dev - -ENV \ - LIBRARY_PATH=/lib:/usr/lib +# Set pip to have cleaner logs and no saved cache +ENV PIP_NO_CACHE_DIR=false \ + PIPENV_HIDE_EMOJIS=1 \ + PIPENV_IGNORE_VIRTUALENVS=1 \ + PIPENV_NOSPIN=1 +# Install pipenv RUN pip install -U pipenv +# Copy project files into working directory WORKDIR /bot -COPY . . +COPY docker . -RUN pipenv install --deploy --system +# Install project dependencies +RUN pipenv install --system --deploy -ENTRYPOINT ["/sbin/tini", "--"] -CMD ["python3", "-m", "bot"] +ENTRYPOINT ["python3"] +CMD ["-m", "bot"] diff --git a/Pipfile b/Pipfile index 990653f92..da46a536d 100644 --- a/Pipfile +++ b/Pipfile @@ -42,7 +42,5 @@ python_version = "3.7" start = "python -m bot" lint = "python -m flake8" precommit = "pre-commit install" -build = "docker build -t pythondiscord/bot:latest -f docker/bot.Dockerfile ." +build = "docker build -t pythondiscord/bot:latest -f Dockerfile ." push = "docker push pythondiscord/bot:latest" -buildbase = "docker build -t pythondiscord/bot-base:latest -f docker/base.Dockerfile ." -pushbase = "docker push pythondiscord/bot-base:latest" diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 0c47d2544..1adbd12ea 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -8,21 +8,13 @@ variables: jobs: - job: test displayName: 'Lint & Test' - pool: vmImage: ubuntu-16.04 variables: - PIPENV_CACHE_DIR: ".cache/pipenv" PIP_CACHE_DIR: ".cache/pip" - PIP_SRC: ".cache/src" steps: - - script: | - sudo apt-get update - sudo apt-get install build-essential curl docker libffi-dev libfreetype6-dev libxml2 libxml2-dev libxslt1-dev zlib1g zlib1g-dev - displayName: 'Install base dependencies' - - task: UsePythonVersion@0 displayName: 'Set Python version' inputs: diff --git a/docker/ci.Dockerfile b/docker/ci.Dockerfile deleted file mode 100644 index fd7e25239..000000000 --- a/docker/ci.Dockerfile +++ /dev/null @@ -1,20 +0,0 @@ -FROM python:3.6-alpine3.7 - -RUN apk add --update docker \ - curl \ - tini \ - build-base \ - libffi-dev \ - zlib \ - jpeg-dev \ - libxml2 libxml2-dev libxslt-dev \ - zlib-dev \ - freetype-dev - -RUN pip install pipenv - -ENV LIBRARY_PATH=/lib:/usr/lib -ENV PIPENV_VENV_IN_PROJECT=1 -ENV PIPENV_IGNORE_VIRTUALENVS=1 -ENV PIPENV_NOSPIN=1 -ENV PIPENV_HIDE_EMOJIS=1 -- cgit v1.2.3 From 4f11cbb706640fb459df9f3d7a737ea2a5b81e73 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Mon, 23 Sep 2019 22:19:22 +1000 Subject: Add docker build condition for CI. --- azure-pipelines.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 1adbd12ea..de454e5c4 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -50,6 +50,7 @@ jobs: - job: build displayName: 'Build & Push Container' dependsOn: 'test' + condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) steps: - task: Docker@2 -- cgit v1.2.3 From 0232db30bcd247a6218f853c5e1e6f7c19b5fbd5 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Mon, 23 Sep 2019 22:24:34 +1000 Subject: Fix random text mistake in Dockerfile. --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 21a938c4f..271c25050 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,7 @@ RUN pip install -U pipenv # Copy project files into working directory WORKDIR /bot -COPY docker . +COPY . . # Install project dependencies RUN pipenv install --system --deploy -- cgit v1.2.3 From 83ce5184a5f4edf40abeeb42214261450095aeff Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Tue, 24 Sep 2019 14:45:24 +1000 Subject: Create default django admin user. --- docker-compose.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 1c55482dc..4b0dcff35 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,7 +16,9 @@ services: web: image: pythondiscord/site:latest - command: "./manage.py runserver 0.0.0.0:8000" + command: > + bash -c "echo \"from django.contrib.auth import get_user_model; User = get_user_model(); User.objects.create_superuser('admin', 'admin', 'admin') if not User.objects.filter(username='admin').exists() else print('Admin user already exists')\" | python manage.py shell + && ./manage.py runserver 0.0.0.0:8000" ports: - "127.0.0.1:8000:8000" depends_on: -- cgit v1.2.3 From cdcfabfb12ea6709b49c8509e6fa6627fb4e0220 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Wed, 25 Sep 2019 02:27:51 +1000 Subject: Remove regex wheel as dateparser is no longer used. --- wheels/regex-2019.8.19-cp37-cp37m-linux_x86_64.whl | Bin 688982 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 wheels/regex-2019.8.19-cp37-cp37m-linux_x86_64.whl diff --git a/wheels/regex-2019.8.19-cp37-cp37m-linux_x86_64.whl b/wheels/regex-2019.8.19-cp37-cp37m-linux_x86_64.whl deleted file mode 100644 index 0be4519fe..000000000 Binary files a/wheels/regex-2019.8.19-cp37-cp37m-linux_x86_64.whl and /dev/null differ -- cgit v1.2.3 From 5ab0f0d8690af02fbd0f014cad5227a350e2483f Mon Sep 17 00:00:00 2001 From: "S. Co1" Date: Tue, 24 Sep 2019 12:35:10 -0400 Subject: Fix reminders filter query Closes #445 --- bot/cogs/reminders.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/cogs/reminders.py b/bot/cogs/reminders.py index c37abf21e..6e91d2c06 100644 --- a/bot/cogs/reminders.py +++ b/bot/cogs/reminders.py @@ -146,7 +146,7 @@ class Reminders(Scheduler, Cog): active_reminders = await self.bot.api_client.get( 'bot/reminders', params={ - 'user__id': str(ctx.author.id) + 'author__id': str(ctx.author.id) } ) @@ -184,7 +184,7 @@ class Reminders(Scheduler, Cog): # Get all the user's reminders from the database. data = await self.bot.api_client.get( 'bot/reminders', - params={'user__id': str(ctx.author.id)} + params={'author__id': str(ctx.author.id)} ) now = datetime.utcnow() -- cgit v1.2.3 From d91beca91d2e58b1ca01ea9f5c9ac92185b0c193 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Wed, 25 Sep 2019 03:04:45 +1000 Subject: Remove unnecessary sudo for pipenv install. --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index de454e5c4..b5ecab83c 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -21,7 +21,7 @@ jobs: versionSpec: '3.7.x' addToPath: true - - script: sudo pip install pipenv + - script: pip install pipenv displayName: 'Install pipenv' - script: pipenv install --dev --deploy --system -- cgit v1.2.3