From f7553f5c8f0706cb1f3cf04da76aa0edc4f6bafd Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 10 Aug 2022 22:21:50 -0600 Subject: [PATCH 1/4] Add tag support --- jarvis/cogs/tags.py | 276 ++++++++++++++++++++++++++++++++++++++++++++ poetry.lock | 30 ++++- pyproject.toml | 1 + 3 files changed, 304 insertions(+), 3 deletions(-) create mode 100644 jarvis/cogs/tags.py diff --git a/jarvis/cogs/tags.py b/jarvis/cogs/tags.py new file mode 100644 index 0000000..5034cdf --- /dev/null +++ b/jarvis/cogs/tags.py @@ -0,0 +1,276 @@ +"""JARVIS Tags Cog.""" +import asyncio +import re + +from jarvis_core.db import q +from jarvis_core.db.models import Setting, Tag +from naff import AutocompleteContext, Client, Extension, InteractionContext +from naff.models.discord.embed import EmbedField +from naff.models.discord.enums import Permissions +from naff.models.discord.modal import InputText, Modal, TextStyles +from naff.models.naff.application_commands import ( + OptionTypes, + SlashCommand, + slash_option, +) +from thefuzz import process + +from jarvis.utils import build_embed + +invites = re.compile( + r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501 + flags=re.IGNORECASE, +) + + +class TagCog(Extension): + + tag = SlashCommand(name="tag", description="Create and manage custom tags") + + @tag.subcommand(sub_cmd_name="get", sub_cmd_description="Get a tag") + @slash_option( + name="name", + description="Tag to get", + autocomplete=True, + opt_type=OptionTypes.STRING, + required=True, + ) + async def _get(self, ctx: InteractionContext, name: str) -> None: + tag = await Tag.find_one(q(guild=ctx.guild.id, name=name)) + if not tag: + await ctx.send( + "Well this is awkward, looks like the tag was deleted just now", ephemeral=True + ) + return + + await ctx.send(tag.content) + + @tag.subcommand(sub_cmd_name="create", sub_cmd_description="Create a tag") + async def _create(self, ctx: InteractionContext) -> None: + modal = Modal( + title="Create a new tag!", + components=[ + InputText( + label="Tag name", + placeholder="name", + style=TextStyles.SHORT, + custom_id="name", + max_length=40, + ), + InputText( + label="Content", + placeholder="Content to send here", + style=TextStyles.PARAGRAPH, + custom_id="content", + max_length=1000, + ), + ], + ) + + await ctx.send_modal(modal) + try: + response = await self.bot.wait_for_modal(modal, author=ctx.author.id, timeout=60 * 5) + name = response.responses.get("name") + content = response.responses.get("content") + except asyncio.TimeoutError: + return + + noinvite = await Setting.find_one(q(guild=ctx.guild.id, setting="noinvite")) + + if (invites.search(content) or invites.search(name)) and noinvite.value: + await response.send( + "Listen, don't use this to try and bypass the rules", ephemeral=True + ) + return + elif not content.strip() or not name.strip(): + await response.send("Content and name required", ephemeral=True) + return + + tag = await Tag.find_one(q(guild=ctx.guild.id, name=name)) + if tag: + await response.send("That tag already exists", ephemeral=True) + return + + content = re.sub(r"\\?([@<])", r"\\\g<1>", content) + + tag = Tag( + creator=ctx.author.id, + name=name, + content=content, + guild=ctx.guild.id, + ) + await tag.commit() + + embed = build_embed( + title="Tag Created", + description=f"{ctx.author.mention} created a new tag", + fields=[EmbedField(name="Name", value=name), EmbedField(name="Content", value=content)], + ) + + embed.set_author( + name=ctx.author.username + "#" + ctx.author.discriminator, + icon_url=ctx.author.display_avatar.url, + ) + + await response.send(embeds=embed) + + @tag.subcommand(sub_cmd_name="edit", sub_cmd_description="Edit a tag") + @slash_option( + name="name", + description="Tag name", + opt_type=OptionTypes.STRING, + autocomplete=True, + required=True, + ) + async def _edit(self, ctx: InteractionContext, name: str) -> None: + tag = await Tag.find_one(q(guild=ctx.guild.id, name=name)) + if not tag: + await ctx.send("Tag not found", ephemeral=True) + return + elif tag.creator != ctx.author.id and not ( + ctx.author.has_permission(Permissions.ADMINISTRATOR) + or ctx.author.has_permission(Permissions.MANAGE_MESSAGES) + ): + await ctx.send("You didn't create this tag, ask the creator to edit it", ephemeral=True) + return + + modal = Modal( + title="Edit a tag!", + components=[ + InputText( + label="Tag name", + value=tag.name, + style=TextStyles.SHORT, + custom_id="name", + max_length=40, + ), + InputText( + label="Content", + value=tag.content, + style=TextStyles.PARAGRAPH, + custom_id="content", + max_length=1000, + ), + ], + ) + + await ctx.send_modal(modal) + try: + response = await self.bot.wait_for_modal(modal, author=ctx.author.id, timeout=60 * 5) + name = response.responses.get("name") + content = response.responses.get("content") + except asyncio.TimeoutError: + return + + noinvite = await Setting.find_one(q(guild=ctx.guild.id, setting="noinvite")) + + if (invites.search(content) or invites.search(name)) and noinvite.value: + await response.send( + "Listen, don't use this to try and bypass the rules", ephemeral=True + ) + return + elif not content.strip() or not name.strip(): + await response.send("Content and name required", ephemeral=True) + return + + tag.content = re.sub(r"\\?([@<])", r"\\\g<1>", content) + tag.name = name + + await tag.commit() + + embed = build_embed( + title="Tag Updated", + description=f"{ctx.author.mention} updated a tag", + fields=[ + EmbedField(name="Name", value=name), + EmbedField(name="Content", value=tag.content), + ], + ) + + embed.set_author( + name=ctx.author.username + "#" + ctx.author.discriminator, + icon_url=ctx.author.display_avatar.url, + ) + + await response.send(embeds=embed) + + @tag.subcommand(sub_cmd_name="delete", sub_cmd_description="Delete a tag") + @slash_option( + name="name", + description="Tag name", + opt_type=OptionTypes.STRING, + required=True, + autocomplete=True, + ) + async def _delete(self, ctx: InteractionContext, name: str) -> None: + tag = await Tag.find_one(q(guild=ctx.guild.id, name=name)) + if not tag: + await ctx.send("Tag not found", ephemeral=True) + return + elif tag.creator != ctx.author.id and not ( + ctx.author.has_permission(Permissions.ADMINISTRATOR) + or ctx.author.has_permission(Permissions.MANAGE_MESSAGES) + ): + await ctx.send( + "You didn't create this tag, ask the creator to delete it", ephemeral=True + ) + return + + await tag.delete() + await ctx.send(f"Tag `{name}` deleted") + + @tag.subcommand(sub_cmd_name="info", sub_cmd_description="Get info on a tag") + @slash_option( + name="name", + description="Tag name", + opt_type=OptionTypes.STRING, + required=True, + autocomplete=True, + ) + async def _info(self, ctx: InteractionContext, name: str) -> None: + tag = await Tag.find_one(q(guild=ctx.guild.id, name=name)) + if not tag: + await ctx.send("Tag not found", ephemeral=True) + return + + username, discrim, url = None, None, None + author = await self.bot.fetch_user(tag.creator) + if author: + username = author.username + discrim = author.discriminator + url = author.display_avatar.url + + ts = int(tag.created_at.timestamp()) + + embed = build_embed( + title="Tag Info", + description=f"Here's the info on the tag `{name}`", + fields=[ + EmbedField(name="Name", value=name), + EmbedField(name="Content", value=tag.content), + EmbedField(name="Created At", value=f""), + ], + ) + + embed.set_author( + name=f"{username}#{discrim}" if username else "Unknown Author", + icon_url=url, + ) + + await ctx.send(embeds=embed) + + @_get.autocomplete("name") + @_edit.autocomplete("name") + @_delete.autocomplete("name") + @_info.autocomplete("name") + async def _autocomplete(self, ctx: AutocompleteContext, name: str) -> None: + tags = await Tag.find(q(guild=ctx.guild.id)).to_list(None) + names = [tag.name for tag in tags] + results = process.extract(name, names, limit=25) + choices = [{"name": r[0], "value": r[0]} for r in results] + await ctx.send(choices=choices) + + +def setup(bot: Client) -> None: + """Add TagCog to JARVIS""" + TagCog(bot) diff --git a/poetry.lock b/poetry.lock index f9748f6..c56cab1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -414,7 +414,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [[package]] name = "jarvis-core" -version = "0.11.0" +version = "0.12.0" description = "JARVIS core" category = "main" optional = false @@ -435,7 +435,7 @@ umongo = "^3.1.0" type = "git" url = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git" reference = "main" -resolved_reference = "fce3b829a30583abd48b3221825c3ed303610de8" +resolved_reference = "4cece14cd9cd1604bf12845339fdb5f66b6c0719" [[package]] name = "jinxed" @@ -825,6 +825,14 @@ requests-toolbelt = ">=0.9.1" autocompletion = ["argcomplete (>=1.10.0,<3)"] yaml = ["PyYaml (>=5.2)"] +[[package]] +name = "python-levenshtein" +version = "0.12.2" +description = "Python extension for computing string edit distances and similarities." +category = "main" +optional = false +python-versions = "*" + [[package]] name = "pytz" version = "2022.1" @@ -955,6 +963,20 @@ category = "main" optional = false python-versions = ">=3.6" +[[package]] +name = "thefuzz" +version = "0.19.0" +description = "Fuzzy string matching in python" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +python-levenshtein = {version = ">=0.12", optional = true, markers = "extra == \"speedup\""} + +[package.extras] +speedup = ["python-levenshtein (>=0.12)"] + [[package]] name = "tomli" version = "2.0.1" @@ -1168,7 +1190,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "549486089ef65c69b0932e799efdbb0d22d6631d925de845ec4b6ba98d57c527" +content-hash = "d4a3ccd2f79fe0c323784bfba2c5950817257639bbdcdb57a6e71682a8846504" [metadata.files] aiofile = [ @@ -1929,6 +1951,7 @@ python-gitlab = [ {file = "python-gitlab-3.5.0.tar.gz", hash = "sha256:29ae7fb9b8c9aeb2e6e19bd2fd04867e93ecd7af719978ce68fac0cf116ab30d"}, {file = "python_gitlab-3.5.0-py3-none-any.whl", hash = "sha256:73b5aa6502efa557ee1a51227cceb0243fac5529627da34f08c5f265bf50417c"}, ] +python-levenshtein = [] pytz = [ {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"}, {file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"}, @@ -2094,6 +2117,7 @@ smmap = [ {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, ] +thefuzz = [] tomli = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, diff --git a/pyproject.toml b/pyproject.toml index 9c455c1..5417210 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ naff = { version = "^1.2.0", extras = ["orjson"] } nafftrack = {git = "https://github.com/artem30801/nafftrack.git", rev = "master"} ansitoimg = "^2022.1" nest-asyncio = "^1.5.5" +thefuzz = {extras = ["speedup"], version = "^0.19.0"} [tool.poetry.dev-dependencies] black = {version = "^22.3.0", allow-prereleases = true} From 85bce061a17fbb900383933038c61a25a0ea76d0 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 11 Aug 2022 14:42:36 -0600 Subject: [PATCH 2/4] Improve functionality of tags --- jarvis/cogs/tags.py | 69 +++++++++++++++++++++++++++++++++++++-------- poetry.lock | 2 +- 2 files changed, 59 insertions(+), 12 deletions(-) diff --git a/jarvis/cogs/tags.py b/jarvis/cogs/tags.py index 5034cdf..512e302 100644 --- a/jarvis/cogs/tags.py +++ b/jarvis/cogs/tags.py @@ -1,6 +1,8 @@ """JARVIS Tags Cog.""" import asyncio import re +from datetime import datetime, timezone +from typing import Dict, List from jarvis_core.db import q from jarvis_core.db.models import Setting, Tag @@ -24,6 +26,9 @@ invites = re.compile( class TagCog(Extension): + def __init__(self, bot: Client): + self.bot = bot + self.cache: Dict[int, List[int]] = {} tag = SlashCommand(name="tag", description="Create and manage custom tags") @@ -62,7 +67,7 @@ class TagCog(Extension): placeholder="Content to send here", style=TextStyles.PARAGRAPH, custom_id="content", - max_length=1000, + max_length=512, ), ], ) @@ -70,14 +75,21 @@ class TagCog(Extension): await ctx.send_modal(modal) try: response = await self.bot.wait_for_modal(modal, author=ctx.author.id, timeout=60 * 5) - name = response.responses.get("name") + name = response.responses.get("name").replace("`", "") content = response.responses.get("content") except asyncio.TimeoutError: return noinvite = await Setting.find_one(q(guild=ctx.guild.id, setting="noinvite")) - if (invites.search(content) or invites.search(name)) and noinvite.value: + if ( + (invites.search(content) or invites.search(name)) + and noinvite.value + and not ( + ctx.author.has_permission(Permissions.ADMINISTRATOR) + or ctx.author.has_permission(Permissions.MANAGE_MESSAGES) + ) + ): await response.send( "Listen, don't use this to try and bypass the rules", ephemeral=True ) @@ -113,6 +125,9 @@ class TagCog(Extension): ) await response.send(embeds=embed) + if ctx.guild.id not in self.cache: + self.cache[ctx.guild.id] = [] + self.cache[ctx.guild.id].append(tag.name) @tag.subcommand(sub_cmd_name="edit", sub_cmd_description="Edit a tag") @slash_option( @@ -123,6 +138,7 @@ class TagCog(Extension): required=True, ) async def _edit(self, ctx: InteractionContext, name: str) -> None: + old_name = name tag = await Tag.find_one(q(guild=ctx.guild.id, name=name)) if not tag: await ctx.send("Tag not found", ephemeral=True) @@ -149,7 +165,7 @@ class TagCog(Extension): value=tag.content, style=TextStyles.PARAGRAPH, custom_id="content", - max_length=1000, + max_length=512, ), ], ) @@ -157,14 +173,28 @@ class TagCog(Extension): await ctx.send_modal(modal) try: response = await self.bot.wait_for_modal(modal, author=ctx.author.id, timeout=60 * 5) - name = response.responses.get("name") + name = response.responses.get("name").replace("`", "") content = response.responses.get("content") except asyncio.TimeoutError: return + new_tag = await Tag.find_one(q(guild=ctx.guild.id, name=name)) + if new_tag and new_tag.id != tag.id: + await ctx.send( + "That tag name is used by another tag, choose another name", ephemeral=True + ) + return + noinvite = await Setting.find_one(q(guild=ctx.guild.id, setting="noinvite")) - if (invites.search(content) or invites.search(name)) and noinvite.value: + if ( + (invites.search(content) or invites.search(name)) + and noinvite.value + and not ( + ctx.author.has_permission(Permissions.ADMINISTRATOR) + or ctx.author.has_permission(Permissions.MANAGE_MESSAGES) + ) + ): await response.send( "Listen, don't use this to try and bypass the rules", ephemeral=True ) @@ -175,6 +205,8 @@ class TagCog(Extension): tag.content = re.sub(r"\\?([@<])", r"\\\g<1>", content) tag.name = name + tag.edited_at = datetime.now(tz=timezone.utc) + tag.editor = ctx.author.id await tag.commit() @@ -193,6 +225,9 @@ class TagCog(Extension): ) await response.send(embeds=embed) + if tag.name not in self.cache[ctx.guild.id]: + self.cache[ctx.guild.id].remove(old_name) + self.cache[ctx.guild.id].append(tag.name) @tag.subcommand(sub_cmd_name="delete", sub_cmd_description="Delete a tag") @slash_option( @@ -218,6 +253,7 @@ class TagCog(Extension): await tag.delete() await ctx.send(f"Tag `{name}` deleted") + self.cache[ctx.guild.id].remove(tag.name) @tag.subcommand(sub_cmd_name="info", sub_cmd_description="Get info on a tag") @slash_option( @@ -233,12 +269,13 @@ class TagCog(Extension): await ctx.send("Tag not found", ephemeral=True) return - username, discrim, url = None, None, None + username, discrim, url, mention = None, None, None, "Unknown User" author = await self.bot.fetch_user(tag.creator) if author: username = author.username discrim = author.discriminator url = author.display_avatar.url + mention = author.mention ts = int(tag.created_at.timestamp()) @@ -249,11 +286,20 @@ class TagCog(Extension): EmbedField(name="Name", value=name), EmbedField(name="Content", value=tag.content), EmbedField(name="Created At", value=f""), + EmbedField(name="Created By", value=mention), ], ) + if tag.edited_at: + ets = int(tag.edited_at.timestamp()) + editor = await self.bot.fetch_user(tag.editor) + emention = "Unknown User" + if editor: + emention = editor.mention + embed.add_field(name="Edited At", value=f"") + embed.add_field(name="Edited By", value=emention) embed.set_author( - name=f"{username}#{discrim}" if username else "Unknown Author", + name=f"{username}#{discrim}" if username else "Unknown User", icon_url=url, ) @@ -264,9 +310,10 @@ class TagCog(Extension): @_delete.autocomplete("name") @_info.autocomplete("name") async def _autocomplete(self, ctx: AutocompleteContext, name: str) -> None: - tags = await Tag.find(q(guild=ctx.guild.id)).to_list(None) - names = [tag.name for tag in tags] - results = process.extract(name, names, limit=25) + if not self.cache.get(ctx.guild.id): + tags = await Tag.find(q(guild=ctx.guild.id)).to_list(None) + self.cache[ctx.guild.id] = [tag.name for tag in tags] + results = process.extract(name, self.cache.get(ctx.guild.id), limit=25) choices = [{"name": r[0], "value": r[0]} for r in results] await ctx.send(choices=choices) diff --git a/poetry.lock b/poetry.lock index c56cab1..6b3616f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -435,7 +435,7 @@ umongo = "^3.1.0" type = "git" url = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git" reference = "main" -resolved_reference = "4cece14cd9cd1604bf12845339fdb5f66b6c0719" +resolved_reference = "fe24fce330cfd23a7af3834ef11b675780e6325d" [[package]] name = "jinxed" From 1863ad6c25ad637396ac5077f3dc09d2f2f21137 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 11 Aug 2022 14:43:57 -0600 Subject: [PATCH 3/4] Add stop command, fix logging --- jarvis/__init__.py | 7 +++++++ jarvis/cogs/botutil.py | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/jarvis/__init__.py b/jarvis/__init__.py index 71f3859..acb5cd0 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -19,6 +19,7 @@ __version__ = const.__version__ async def run() -> None: """Run JARVIS""" + # Configure logger jconfig = JarvisConfig.from_yaml() logger = get_logger("jarvis", show_locals=jconfig.log_level == "DEBUG") logger.setLevel(jconfig.log_level) @@ -28,6 +29,7 @@ async def run() -> None: ) logger.addHandler(file_handler) + # Configure client intents = ( Intents.DEFAULT | Intents.MESSAGES | Intents.GUILD_MEMBERS | Intents.GUILD_MESSAGE_CONTENT ) @@ -42,18 +44,23 @@ async def run() -> None: delete_unused_application_cmds=True, send_command_tracebacks=False, redis=redis, + logger=logger, ) + # External modules if jconfig.log_level == "DEBUG": jurigged.watch(pattern="jarvis/*.py") if jconfig.rook_token: rook.start(token=jconfig.rook_token, labels={"env": "dev"}) + + # Initialize bot logger.info("Starting JARVIS") logger.debug("Connecting to database") connect(**jconfig.mongo["connect"], testing=jconfig.mongo["database"] != "jarvis") logger.debug("Loading configuration from database") # jconfig.get_db_config() + # Load extensions logger.debug("Loading extensions") for extension in get_extensions(cogs_path): jarvis.load_extension(extension) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 61a49f5..8cd9d26 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -1,4 +1,5 @@ """JARVIS bot utility commands.""" +import asyncio import logging import platform from io import BytesIO @@ -26,6 +27,12 @@ class BotutilCog(Extension): """Checks if author is bot owner.""" return ctx.author.id == self.bot.owner.id + @prefixed_command(name="stop") + async def _stop(self, ctx: PrefixedContext) -> None: + await ctx.send("Shutting down now") + loop = asyncio.get_running_loop() + loop.stop() + @prefixed_command(name="tail") async def _tail(self, ctx: PrefixedContext, count: int = 10) -> None: lines = [] From 080fba0fb086ebe979d8d448d796afff2ace128f Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 11 Aug 2022 22:24:18 -0600 Subject: [PATCH 4/4] Update dependencies --- poetry.lock | 30 +++++++----------------------- 1 file changed, 7 insertions(+), 23 deletions(-) diff --git a/poetry.lock b/poetry.lock index 6b3616f..4f99613 100644 --- a/poetry.lock +++ b/poetry.lock @@ -106,17 +106,6 @@ category = "main" optional = false python-versions = "*" -[[package]] -name = "asgiref" -version = "3.5.2" -description = "ASGI specs, helper code, and adapters" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] - [[package]] name = "async-generator" version = "1.10" @@ -553,15 +542,15 @@ python-versions = "^3.10" develop = false [package.dependencies] -naff = {version = "^1.2.0", extras = ["orjson"]} +naff = {version = "^1.7.1", extras = ["orjson"]} prometheus-client = "^0.14.1" -uvicorn = "^0.17.6" +uvicorn = "^0.18.2" [package.source] type = "git" url = "https://github.com/artem30801/nafftrack.git" reference = "master" -resolved_reference = "e3b6f102d6784731d90c52e84b401c86117583f2" +resolved_reference = "eae6ffd93a1a7854347eb0e147b894bf307c0003" [[package]] name = "nanoid" @@ -1109,19 +1098,18 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "uvicorn" -version = "0.17.6" +version = "0.18.2" description = "The lightning-fast ASGI server." category = "main" optional = false python-versions = ">=3.7" [package.dependencies] -asgiref = ">=3.4.0" click = ">=7.0" h11 = ">=0.8" [package.extras] -standard = ["websockets (>=10.0)", "httptools (>=0.4.0)", "watchgod (>=0.6)", "python-dotenv (>=0.13)", "PyYAML (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "colorama (>=0.4)"] +standard = ["websockets (>=10.0)", "httptools (>=0.4.0)", "watchfiles (>=0.13)", "python-dotenv (>=0.13)", "PyYAML (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "colorama (>=0.4)"] [[package]] name = "watchdog" @@ -1299,10 +1287,6 @@ appdirs = [ {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, ] -asgiref = [ - {file = "asgiref-3.5.2-py3-none-any.whl", hash = "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4"}, - {file = "asgiref-3.5.2.tar.gz", hash = "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"}, -] async-generator = [ {file = "async_generator-1.10-py3-none-any.whl", hash = "sha256:01c7bf666359b4967d2cda0000cc2e4af16a0ae098cbffcb8472fb9e8ad6585b"}, {file = "async_generator-1.10.tar.gz", hash = "sha256:6ebb3d106c12920aaae42ccb6f787ef5eefdcdd166ea3d628fa8476abe712144"}, @@ -2159,8 +2143,8 @@ urllib3 = [ {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, ] uvicorn = [ - {file = "uvicorn-0.17.6-py3-none-any.whl", hash = "sha256:19e2a0e96c9ac5581c01eb1a79a7d2f72bb479691acd2b8921fce48ed5b961a6"}, - {file = "uvicorn-0.17.6.tar.gz", hash = "sha256:5180f9d059611747d841a4a4c4ab675edf54c8489e97f96d0583ee90ac3bfc23"}, + {file = "uvicorn-0.18.2-py3-none-any.whl", hash = "sha256:c19a057deb1c5bb060946e2e5c262fc01590c6529c0af2c3d9ce941e89bc30e0"}, + {file = "uvicorn-0.18.2.tar.gz", hash = "sha256:cade07c403c397f9fe275492a48c1b869efd175d5d8a692df649e6e7e2ed8f4e"}, ] watchdog = [ {file = "watchdog-2.1.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:676263bee67b165f16b05abc52acc7a94feac5b5ab2449b491f1a97638a79277"},