From 9a753859b36c574bbb986dd9cc22a588c99c3970 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Sat, 25 Jan 2025 04:26:21 -0500 Subject: [PATCH 001/133] Improve autocorrection speed, make `version.txt` redundant, and update checks and dependencies --- .github/workflows/ruff.yml | 2 +- dexscript.py | 41 +- poetry.lock | 953 +++++++++++++++---------------------- pyproject.toml | 4 +- version.txt | 2 +- 5 files changed, 408 insertions(+), 594 deletions(-) diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index b732972..11a2ade 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -5,4 +5,4 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: astral-sh/ruff-action@v2 \ No newline at end of file + - uses: astral-sh/ruff-action@v3 \ No newline at end of file diff --git a/dexscript.py b/dexscript.py index 7527b01..17d17a4 100644 --- a/dexscript.py +++ b/dexscript.py @@ -1,4 +1,5 @@ import base64 +import inspect import logging import os import re @@ -18,29 +19,25 @@ dir_type = "ballsdex" if os.path.isdir("ballsdex") else "carfigures" if dir_type == "ballsdex": - from ballsdex.core.models import Ball, Economy, GuildConfig, Regime, Special, User + from ballsdex.core.models import Ball, Economy, Regime, Special from ballsdex.settings import settings else: - from carfigures.core.models import Admin as User from carfigures.core.models import Car as Ball from carfigures.core.models import CarType as Regime from carfigures.core.models import Country as Economy from carfigures.core.models import Event as Special - from carfigures.core.models import GuildConfig from carfigures.settings import settings log = logging.getLogger(f"{dir_type}.core.dexscript") -__version__ = "0.4.3.2" +__version__ = "0.4.3.3" START_CODE_BLOCK_RE = re.compile(r"^((```sql?)(?=\s)|(```))") FILENAME_RE = re.compile(r"^(.+)(\.\S+)$") MODELS = { - "user": [User, "USERNAME"], - "guildconfig": [GuildConfig, "ID"], "ball": [Ball, "COUNTRY"], "regime": [Regime, "NAME"], "economy": [Economy, "NAME"], @@ -95,6 +92,7 @@ async def save_file(attachment: discord.Attachment) -> Path: await attachment.save(path) return path + def in_list(list_attempt, index): try: list_attempt[index] @@ -340,10 +338,10 @@ async def create_model(self, model, identifier, yield_creation): fields[key] = 1 - if key in ["country", "full_name", "catch_names", "name", "username"]: + if key in ["country", "full_name", "catch_names", "name"]: fields[key] = identifier elif key == "emoji_id": - fields[key] = 100**8 + fields[key] = 100 ** 8 elif key == "regime_id": first_regime = await Regime.first() fields[key] = first_regime.pk @@ -354,16 +352,21 @@ async def create_model(self, model, identifier, yield_creation): await model.create(**fields) async def get_model(self, model, identifier): + attribute = re.search( + r"return\s+self\.(\w+)", inspect.getsource(model.name.__str__) + ).group(1) + + correction_list = await model.name.all().values_list(attribute, flat=True) + translated_identifier = self.translate(model.extra_data[0].lower()) + try: returned_model = await model.name.filter( **{ - self.translate(model.extra_data[0].lower()): self.autocorrect( - identifier, [str(x) for x in await model.name.all()] - ) + translated_identifier: self.autocorrect(identifier, correction_list) } ) except AttributeError: - raise DexScriptError(f"{model} is not a valid model.") + raise DexScriptError(f"'{model}' is not a valid model.") return returned_model[0] @@ -395,7 +398,6 @@ def translate(string: str, item=None): string: str The string you want to translate. """ - if dir_type == "ballsdex": return getattr(item, string) if item else string @@ -467,7 +469,7 @@ async def execute(self, code: str): except IndexError: # TODO: Remove `error` duplicates. - error = f"Argument is missing when calling {value.name}." + error = f"Argument missing when calling {value.name}." return ((error, error), CodeStatus.FAILURE) except Exception as error: return ((error, traceback.format_exc()), CodeStatus.FAILURE) @@ -488,7 +490,6 @@ def cleanup_code(content): """ Automatically removes code blocks from the code. """ - if content.startswith("```") and content.endswith("```"): return START_CODE_BLOCK_RE.sub("", content)[:-3] @@ -500,14 +501,15 @@ def check_version(): return None r = requests.get( - "https://api.github.com/repos/Dotsian/DexScript/contents/version.txt", + "https://api.github.com/repos/Dotsian/DexScript/contents/pyproject.toml", {"ref": SETTINGS["REFERENCE"]}, ) if r.status_code != requests.codes.ok: return - new_version = base64.b64decode(r.json()["content"]).decode("UTF-8").rstrip() + toml_content = base64.b64decode(r.json()["content"]).decode("UTF-8") + new_version = re.search(r'version\s*=\s*"(.*?)"', toml_content).group(1) if new_version != __version__: return ( @@ -529,7 +531,6 @@ async def run(self, ctx: commands.Context, *, code: str): code: str The code you'd like to execute. """ - body = self.cleanup_code(code) version_check = self.check_version() @@ -551,7 +552,6 @@ async def about(self, ctx: commands.Context): """ Displays information about DexScript. """ - embed = discord.Embed( title="DexScript - BETA", description=( @@ -579,7 +579,6 @@ async def update_ds(self, ctx: commands.Context): """ Updates DexScript to the latest version. """ - r = requests.get( "https://api.github.com/repos/Dotsian/DexScript/contents/installer.py", {"ref": SETTINGS["REFERENCE"]}, @@ -600,7 +599,6 @@ async def reload_ds(self, ctx: commands.Context): """ Reloads DexScript. """ - await self.bot.reload_extension(f"{dir_type}.core.dexscript") await ctx.send("Reloaded DexScript") @@ -617,7 +615,6 @@ async def setting(self, ctx: commands.Context, setting: str, value: str): value: str The value you want to set the setting to. """ - response = f"`{setting}` is not a valid setting." if setting in SETTINGS: diff --git a/poetry.lock b/poetry.lock index 1f7c3ae..1a27b3c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,114 +1,101 @@ -# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" -version = "2.4.3" +version = "2.4.4" description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ - {file = "aiohappyeyeballs-2.4.3-py3-none-any.whl", hash = "sha256:8a7a83727b2756f394ab2895ea0765a0a8c475e3c71e98d43d76f22b4b435572"}, - {file = "aiohappyeyeballs-2.4.3.tar.gz", hash = "sha256:75cf88a15106a5002a8eb1dab212525c00d1f4c0fa96e551c9fbe6f09a621586"}, + {file = "aiohappyeyeballs-2.4.4-py3-none-any.whl", hash = "sha256:a980909d50efcd44795c4afeca523296716d50cd756ddca6af8c65b996e27de8"}, + {file = "aiohappyeyeballs-2.4.4.tar.gz", hash = "sha256:5fdd7d87889c63183afc18ce9271f9b0a7d32c2303e394468dd45d514a757745"}, ] [[package]] name = "aiohttp" -version = "3.10.10" +version = "3.11.11" description = "Async http client/server framework (asyncio)" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "aiohttp-3.10.10-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:be7443669ae9c016b71f402e43208e13ddf00912f47f623ee5994e12fc7d4b3f"}, - {file = "aiohttp-3.10.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7b06b7843929e41a94ea09eb1ce3927865387e3e23ebe108e0d0d09b08d25be9"}, - {file = "aiohttp-3.10.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:333cf6cf8e65f6a1e06e9eb3e643a0c515bb850d470902274239fea02033e9a8"}, - {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:274cfa632350225ce3fdeb318c23b4a10ec25c0e2c880eff951a3842cf358ac1"}, - {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9e5e4a85bdb56d224f412d9c98ae4cbd032cc4f3161818f692cd81766eee65a"}, - {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b606353da03edcc71130b52388d25f9a30a126e04caef1fd637e31683033abd"}, - {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab5a5a0c7a7991d90446a198689c0535be89bbd6b410a1f9a66688f0880ec026"}, - {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:578a4b875af3e0daaf1ac6fa983d93e0bbfec3ead753b6d6f33d467100cdc67b"}, - {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8105fd8a890df77b76dd3054cddf01a879fc13e8af576805d667e0fa0224c35d"}, - {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3bcd391d083f636c06a68715e69467963d1f9600f85ef556ea82e9ef25f043f7"}, - {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fbc6264158392bad9df19537e872d476f7c57adf718944cc1e4495cbabf38e2a"}, - {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e48d5021a84d341bcaf95c8460b152cfbad770d28e5fe14a768988c461b821bc"}, - {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2609e9ab08474702cc67b7702dbb8a80e392c54613ebe80db7e8dbdb79837c68"}, - {file = "aiohttp-3.10.10-cp310-cp310-win32.whl", hash = "sha256:84afcdea18eda514c25bc68b9af2a2b1adea7c08899175a51fe7c4fb6d551257"}, - {file = "aiohttp-3.10.10-cp310-cp310-win_amd64.whl", hash = "sha256:9c72109213eb9d3874f7ac8c0c5fa90e072d678e117d9061c06e30c85b4cf0e6"}, - {file = "aiohttp-3.10.10-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c30a0eafc89d28e7f959281b58198a9fa5e99405f716c0289b7892ca345fe45f"}, - {file = "aiohttp-3.10.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:258c5dd01afc10015866114e210fb7365f0d02d9d059c3c3415382ab633fcbcb"}, - {file = "aiohttp-3.10.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:15ecd889a709b0080f02721255b3f80bb261c2293d3c748151274dfea93ac871"}, - {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3935f82f6f4a3820270842e90456ebad3af15810cf65932bd24da4463bc0a4c"}, - {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:413251f6fcf552a33c981c4709a6bba37b12710982fec8e558ae944bfb2abd38"}, - {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1720b4f14c78a3089562b8875b53e36b51c97c51adc53325a69b79b4b48ebcb"}, - {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:679abe5d3858b33c2cf74faec299fda60ea9de62916e8b67e625d65bf069a3b7"}, - {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79019094f87c9fb44f8d769e41dbb664d6e8fcfd62f665ccce36762deaa0e911"}, - {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe2fb38c2ed905a2582948e2de560675e9dfbee94c6d5ccdb1301c6d0a5bf092"}, - {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a3f00003de6eba42d6e94fabb4125600d6e484846dbf90ea8e48a800430cc142"}, - {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1bbb122c557a16fafc10354b9d99ebf2f2808a660d78202f10ba9d50786384b9"}, - {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:30ca7c3b94708a9d7ae76ff281b2f47d8eaf2579cd05971b5dc681db8caac6e1"}, - {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:df9270660711670e68803107d55c2b5949c2e0f2e4896da176e1ecfc068b974a"}, - {file = "aiohttp-3.10.10-cp311-cp311-win32.whl", hash = "sha256:aafc8ee9b742ce75044ae9a4d3e60e3d918d15a4c2e08a6c3c3e38fa59b92d94"}, - {file = "aiohttp-3.10.10-cp311-cp311-win_amd64.whl", hash = "sha256:362f641f9071e5f3ee6f8e7d37d5ed0d95aae656adf4ef578313ee585b585959"}, - {file = "aiohttp-3.10.10-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9294bbb581f92770e6ed5c19559e1e99255e4ca604a22c5c6397b2f9dd3ee42c"}, - {file = "aiohttp-3.10.10-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a8fa23fe62c436ccf23ff930149c047f060c7126eae3ccea005f0483f27b2e28"}, - {file = "aiohttp-3.10.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5c6a5b8c7926ba5d8545c7dd22961a107526562da31a7a32fa2456baf040939f"}, - {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:007ec22fbc573e5eb2fb7dec4198ef8f6bf2fe4ce20020798b2eb5d0abda6138"}, - {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9627cc1a10c8c409b5822a92d57a77f383b554463d1884008e051c32ab1b3742"}, - {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:50edbcad60d8f0e3eccc68da67f37268b5144ecc34d59f27a02f9611c1d4eec7"}, - {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a45d85cf20b5e0d0aa5a8dca27cce8eddef3292bc29d72dcad1641f4ed50aa16"}, - {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b00807e2605f16e1e198f33a53ce3c4523114059b0c09c337209ae55e3823a8"}, - {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f2d4324a98062be0525d16f768a03e0bbb3b9fe301ceee99611dc9a7953124e6"}, - {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:438cd072f75bb6612f2aca29f8bd7cdf6e35e8f160bc312e49fbecab77c99e3a"}, - {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:baa42524a82f75303f714108fea528ccacf0386af429b69fff141ffef1c534f9"}, - {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a7d8d14fe962153fc681f6366bdec33d4356f98a3e3567782aac1b6e0e40109a"}, - {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c1277cd707c465cd09572a774559a3cc7c7a28802eb3a2a9472588f062097205"}, - {file = "aiohttp-3.10.10-cp312-cp312-win32.whl", hash = "sha256:59bb3c54aa420521dc4ce3cc2c3fe2ad82adf7b09403fa1f48ae45c0cbde6628"}, - {file = "aiohttp-3.10.10-cp312-cp312-win_amd64.whl", hash = "sha256:0e1b370d8007c4ae31ee6db7f9a2fe801a42b146cec80a86766e7ad5c4a259cf"}, - {file = "aiohttp-3.10.10-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ad7593bb24b2ab09e65e8a1d385606f0f47c65b5a2ae6c551db67d6653e78c28"}, - {file = "aiohttp-3.10.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1eb89d3d29adaf533588f209768a9c02e44e4baf832b08118749c5fad191781d"}, - {file = "aiohttp-3.10.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3fe407bf93533a6fa82dece0e74dbcaaf5d684e5a51862887f9eaebe6372cd79"}, - {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50aed5155f819873d23520919e16703fc8925e509abbb1a1491b0087d1cd969e"}, - {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f05e9727ce409358baa615dbeb9b969db94324a79b5a5cea45d39bdb01d82e6"}, - {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dffb610a30d643983aeb185ce134f97f290f8935f0abccdd32c77bed9388b42"}, - {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa6658732517ddabe22c9036479eabce6036655ba87a0224c612e1ae6af2087e"}, - {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:741a46d58677d8c733175d7e5aa618d277cd9d880301a380fd296975a9cdd7bc"}, - {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e00e3505cd80440f6c98c6d69269dcc2a119f86ad0a9fd70bccc59504bebd68a"}, - {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ffe595f10566f8276b76dc3a11ae4bb7eba1aac8ddd75811736a15b0d5311414"}, - {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdfcf6443637c148c4e1a20c48c566aa694fa5e288d34b20fcdc58507882fed3"}, - {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d183cf9c797a5291e8301790ed6d053480ed94070637bfaad914dd38b0981f67"}, - {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:77abf6665ae54000b98b3c742bc6ea1d1fb31c394bcabf8b5d2c1ac3ebfe7f3b"}, - {file = "aiohttp-3.10.10-cp313-cp313-win32.whl", hash = "sha256:4470c73c12cd9109db8277287d11f9dd98f77fc54155fc71a7738a83ffcc8ea8"}, - {file = "aiohttp-3.10.10-cp313-cp313-win_amd64.whl", hash = "sha256:486f7aabfa292719a2753c016cc3a8f8172965cabb3ea2e7f7436c7f5a22a151"}, - {file = "aiohttp-3.10.10-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:1b66ccafef7336a1e1f0e389901f60c1d920102315a56df85e49552308fc0486"}, - {file = "aiohttp-3.10.10-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:acd48d5b80ee80f9432a165c0ac8cbf9253eaddb6113269a5e18699b33958dbb"}, - {file = "aiohttp-3.10.10-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3455522392fb15ff549d92fbf4b73b559d5e43dc522588f7eb3e54c3f38beee7"}, - {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45c3b868724137f713a38376fef8120c166d1eadd50da1855c112fe97954aed8"}, - {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:da1dee8948d2137bb51fbb8a53cce6b1bcc86003c6b42565f008438b806cccd8"}, - {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c5ce2ce7c997e1971b7184ee37deb6ea9922ef5163c6ee5aa3c274b05f9e12fa"}, - {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28529e08fde6f12eba8677f5a8608500ed33c086f974de68cc65ab218713a59d"}, - {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f7db54c7914cc99d901d93a34704833568d86c20925b2762f9fa779f9cd2e70f"}, - {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:03a42ac7895406220124c88911ebee31ba8b2d24c98507f4a8bf826b2937c7f2"}, - {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:7e338c0523d024fad378b376a79faff37fafb3c001872a618cde1d322400a572"}, - {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:038f514fe39e235e9fef6717fbf944057bfa24f9b3db9ee551a7ecf584b5b480"}, - {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:64f6c17757251e2b8d885d728b6433d9d970573586a78b78ba8929b0f41d045a"}, - {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:93429602396f3383a797a2a70e5f1de5df8e35535d7806c9f91df06f297e109b"}, - {file = "aiohttp-3.10.10-cp38-cp38-win32.whl", hash = "sha256:c823bc3971c44ab93e611ab1a46b1eafeae474c0c844aff4b7474287b75fe49c"}, - {file = "aiohttp-3.10.10-cp38-cp38-win_amd64.whl", hash = "sha256:54ca74df1be3c7ca1cf7f4c971c79c2daf48d9aa65dea1a662ae18926f5bc8ce"}, - {file = "aiohttp-3.10.10-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:01948b1d570f83ee7bbf5a60ea2375a89dfb09fd419170e7f5af029510033d24"}, - {file = "aiohttp-3.10.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9fc1500fd2a952c5c8e3b29aaf7e3cc6e27e9cfc0a8819b3bce48cc1b849e4cc"}, - {file = "aiohttp-3.10.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f614ab0c76397661b90b6851a030004dac502e48260ea10f2441abd2207fbcc7"}, - {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00819de9e45d42584bed046314c40ea7e9aea95411b38971082cad449392b08c"}, - {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05646ebe6b94cc93407b3bf34b9eb26c20722384d068eb7339de802154d61bc5"}, - {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:998f3bd3cfc95e9424a6acd7840cbdd39e45bc09ef87533c006f94ac47296090"}, - {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9010c31cd6fa59438da4e58a7f19e4753f7f264300cd152e7f90d4602449762"}, - {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ea7ffc6d6d6f8a11e6f40091a1040995cdff02cfc9ba4c2f30a516cb2633554"}, - {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ef9c33cc5cbca35808f6c74be11eb7f5f6b14d2311be84a15b594bd3e58b5527"}, - {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ce0cdc074d540265bfeb31336e678b4e37316849d13b308607efa527e981f5c2"}, - {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:597a079284b7ee65ee102bc3a6ea226a37d2b96d0418cc9047490f231dc09fe8"}, - {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:7789050d9e5d0c309c706953e5e8876e38662d57d45f936902e176d19f1c58ab"}, - {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e7f8b04d83483577fd9200461b057c9f14ced334dcb053090cea1da9c8321a91"}, - {file = "aiohttp-3.10.10-cp39-cp39-win32.whl", hash = "sha256:c02a30b904282777d872266b87b20ed8cc0d1501855e27f831320f471d54d983"}, - {file = "aiohttp-3.10.10-cp39-cp39-win_amd64.whl", hash = "sha256:edfe3341033a6b53a5c522c802deb2079eee5cbfbb0af032a55064bd65c73a23"}, - {file = "aiohttp-3.10.10.tar.gz", hash = "sha256:0631dd7c9f0822cc61c88586ca76d5b5ada26538097d0f1df510b082bad3411a"}, + {file = "aiohttp-3.11.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a60804bff28662cbcf340a4d61598891f12eea3a66af48ecfdc975ceec21e3c8"}, + {file = "aiohttp-3.11.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b4fa1cb5f270fb3eab079536b764ad740bb749ce69a94d4ec30ceee1b5940d5"}, + {file = "aiohttp-3.11.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:731468f555656767cda219ab42e033355fe48c85fbe3ba83a349631541715ba2"}, + {file = "aiohttp-3.11.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb23d8bb86282b342481cad4370ea0853a39e4a32a0042bb52ca6bdde132df43"}, + {file = "aiohttp-3.11.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f047569d655f81cb70ea5be942ee5d4421b6219c3f05d131f64088c73bb0917f"}, + {file = "aiohttp-3.11.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd7659baae9ccf94ae5fe8bfaa2c7bc2e94d24611528395ce88d009107e00c6d"}, + {file = "aiohttp-3.11.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af01e42ad87ae24932138f154105e88da13ce7d202a6de93fafdafb2883a00ef"}, + {file = "aiohttp-3.11.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5854be2f3e5a729800bac57a8d76af464e160f19676ab6aea74bde18ad19d438"}, + {file = "aiohttp-3.11.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6526e5fb4e14f4bbf30411216780c9967c20c5a55f2f51d3abd6de68320cc2f3"}, + {file = "aiohttp-3.11.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:85992ee30a31835fc482468637b3e5bd085fa8fe9392ba0bdcbdc1ef5e9e3c55"}, + {file = "aiohttp-3.11.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:88a12ad8ccf325a8a5ed80e6d7c3bdc247d66175afedbe104ee2aaca72960d8e"}, + {file = "aiohttp-3.11.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0a6d3fbf2232e3a08c41eca81ae4f1dff3d8f1a30bae415ebe0af2d2458b8a33"}, + {file = "aiohttp-3.11.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:84a585799c58b795573c7fa9b84c455adf3e1d72f19a2bf498b54a95ae0d194c"}, + {file = "aiohttp-3.11.11-cp310-cp310-win32.whl", hash = "sha256:bfde76a8f430cf5c5584553adf9926534352251d379dcb266ad2b93c54a29745"}, + {file = "aiohttp-3.11.11-cp310-cp310-win_amd64.whl", hash = "sha256:0fd82b8e9c383af11d2b26f27a478640b6b83d669440c0a71481f7c865a51da9"}, + {file = "aiohttp-3.11.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ba74ec819177af1ef7f59063c6d35a214a8fde6f987f7661f4f0eecc468a8f76"}, + {file = "aiohttp-3.11.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4af57160800b7a815f3fe0eba9b46bf28aafc195555f1824555fa2cfab6c1538"}, + {file = "aiohttp-3.11.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ffa336210cf9cd8ed117011085817d00abe4c08f99968deef0013ea283547204"}, + {file = "aiohttp-3.11.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81b8fe282183e4a3c7a1b72f5ade1094ed1c6345a8f153506d114af5bf8accd9"}, + {file = "aiohttp-3.11.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3af41686ccec6a0f2bdc66686dc0f403c41ac2089f80e2214a0f82d001052c03"}, + {file = "aiohttp-3.11.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70d1f9dde0e5dd9e292a6d4d00058737052b01f3532f69c0c65818dac26dc287"}, + {file = "aiohttp-3.11.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:249cc6912405917344192b9f9ea5cd5b139d49e0d2f5c7f70bdfaf6b4dbf3a2e"}, + {file = "aiohttp-3.11.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0eb98d90b6690827dcc84c246811feeb4e1eea683c0eac6caed7549be9c84665"}, + {file = "aiohttp-3.11.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec82bf1fda6cecce7f7b915f9196601a1bd1a3079796b76d16ae4cce6d0ef89b"}, + {file = "aiohttp-3.11.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9fd46ce0845cfe28f108888b3ab17abff84ff695e01e73657eec3f96d72eef34"}, + {file = "aiohttp-3.11.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bd176afcf8f5d2aed50c3647d4925d0db0579d96f75a31e77cbaf67d8a87742d"}, + {file = "aiohttp-3.11.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ec2aa89305006fba9ffb98970db6c8221541be7bee4c1d027421d6f6df7d1ce2"}, + {file = "aiohttp-3.11.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:92cde43018a2e17d48bb09c79e4d4cb0e236de5063ce897a5e40ac7cb4878773"}, + {file = "aiohttp-3.11.11-cp311-cp311-win32.whl", hash = "sha256:aba807f9569455cba566882c8938f1a549f205ee43c27b126e5450dc9f83cc62"}, + {file = "aiohttp-3.11.11-cp311-cp311-win_amd64.whl", hash = "sha256:ae545f31489548c87b0cced5755cfe5a5308d00407000e72c4fa30b19c3220ac"}, + {file = "aiohttp-3.11.11-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e595c591a48bbc295ebf47cb91aebf9bd32f3ff76749ecf282ea7f9f6bb73886"}, + {file = "aiohttp-3.11.11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3ea1b59dc06396b0b424740a10a0a63974c725b1c64736ff788a3689d36c02d2"}, + {file = "aiohttp-3.11.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8811f3f098a78ffa16e0ea36dffd577eb031aea797cbdba81be039a4169e242c"}, + {file = "aiohttp-3.11.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7227b87a355ce1f4bf83bfae4399b1f5bb42e0259cb9405824bd03d2f4336a"}, + {file = "aiohttp-3.11.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d40f9da8cabbf295d3a9dae1295c69975b86d941bc20f0a087f0477fa0a66231"}, + {file = "aiohttp-3.11.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffb3dc385f6bb1568aa974fe65da84723210e5d9707e360e9ecb51f59406cd2e"}, + {file = "aiohttp-3.11.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8f5f7515f3552d899c61202d99dcb17d6e3b0de777900405611cd747cecd1b8"}, + {file = "aiohttp-3.11.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3499c7ffbfd9c6a3d8d6a2b01c26639da7e43d47c7b4f788016226b1e711caa8"}, + {file = "aiohttp-3.11.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8e2bf8029dbf0810c7bfbc3e594b51c4cc9101fbffb583a3923aea184724203c"}, + {file = "aiohttp-3.11.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b6212a60e5c482ef90f2d788835387070a88d52cf6241d3916733c9176d39eab"}, + {file = "aiohttp-3.11.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d119fafe7b634dbfa25a8c597718e69a930e4847f0b88e172744be24515140da"}, + {file = "aiohttp-3.11.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:6fba278063559acc730abf49845d0e9a9e1ba74f85f0ee6efd5803f08b285853"}, + {file = "aiohttp-3.11.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:92fc484e34b733704ad77210c7957679c5c3877bd1e6b6d74b185e9320cc716e"}, + {file = "aiohttp-3.11.11-cp312-cp312-win32.whl", hash = "sha256:9f5b3c1ed63c8fa937a920b6c1bec78b74ee09593b3f5b979ab2ae5ef60d7600"}, + {file = "aiohttp-3.11.11-cp312-cp312-win_amd64.whl", hash = "sha256:1e69966ea6ef0c14ee53ef7a3d68b564cc408121ea56c0caa2dc918c1b2f553d"}, + {file = "aiohttp-3.11.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:541d823548ab69d13d23730a06f97460f4238ad2e5ed966aaf850d7c369782d9"}, + {file = "aiohttp-3.11.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:929f3ed33743a49ab127c58c3e0a827de0664bfcda566108989a14068f820194"}, + {file = "aiohttp-3.11.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0882c2820fd0132240edbb4a51eb8ceb6eef8181db9ad5291ab3332e0d71df5f"}, + {file = "aiohttp-3.11.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b63de12e44935d5aca7ed7ed98a255a11e5cb47f83a9fded7a5e41c40277d104"}, + {file = "aiohttp-3.11.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa54f8ef31d23c506910c21163f22b124facb573bff73930735cf9fe38bf7dff"}, + {file = "aiohttp-3.11.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a344d5dc18074e3872777b62f5f7d584ae4344cd6006c17ba12103759d407af3"}, + {file = "aiohttp-3.11.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7fb429ab1aafa1f48578eb315ca45bd46e9c37de11fe45c7f5f4138091e2f1"}, + {file = "aiohttp-3.11.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c341c7d868750e31961d6d8e60ff040fb9d3d3a46d77fd85e1ab8e76c3e9a5c4"}, + {file = "aiohttp-3.11.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ed9ee95614a71e87f1a70bc81603f6c6760128b140bc4030abe6abaa988f1c3d"}, + {file = "aiohttp-3.11.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:de8d38f1c2810fa2a4f1d995a2e9c70bb8737b18da04ac2afbf3971f65781d87"}, + {file = "aiohttp-3.11.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a9b7371665d4f00deb8f32208c7c5e652059b0fda41cf6dbcac6114a041f1cc2"}, + {file = "aiohttp-3.11.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:620598717fce1b3bd14dd09947ea53e1ad510317c85dda2c9c65b622edc96b12"}, + {file = "aiohttp-3.11.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bf8d9bfee991d8acc72d060d53860f356e07a50f0e0d09a8dfedea1c554dd0d5"}, + {file = "aiohttp-3.11.11-cp313-cp313-win32.whl", hash = "sha256:9d73ee3725b7a737ad86c2eac5c57a4a97793d9f442599bea5ec67ac9f4bdc3d"}, + {file = "aiohttp-3.11.11-cp313-cp313-win_amd64.whl", hash = "sha256:c7a06301c2fb096bdb0bd25fe2011531c1453b9f2c163c8031600ec73af1cc99"}, + {file = "aiohttp-3.11.11-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3e23419d832d969f659c208557de4a123e30a10d26e1e14b73431d3c13444c2e"}, + {file = "aiohttp-3.11.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:21fef42317cf02e05d3b09c028712e1d73a9606f02467fd803f7c1f39cc59add"}, + {file = "aiohttp-3.11.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1f21bb8d0235fc10c09ce1d11ffbd40fc50d3f08a89e4cf3a0c503dc2562247a"}, + {file = "aiohttp-3.11.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1642eceeaa5ab6c9b6dfeaaa626ae314d808188ab23ae196a34c9d97efb68350"}, + {file = "aiohttp-3.11.11-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2170816e34e10f2fd120f603e951630f8a112e1be3b60963a1f159f5699059a6"}, + {file = "aiohttp-3.11.11-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8be8508d110d93061197fd2d6a74f7401f73b6d12f8822bbcd6d74f2b55d71b1"}, + {file = "aiohttp-3.11.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4eed954b161e6b9b65f6be446ed448ed3921763cc432053ceb606f89d793927e"}, + {file = "aiohttp-3.11.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6c9af134da4bc9b3bd3e6a70072509f295d10ee60c697826225b60b9959acdd"}, + {file = "aiohttp-3.11.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:44167fc6a763d534a6908bdb2592269b4bf30a03239bcb1654781adf5e49caf1"}, + {file = "aiohttp-3.11.11-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:479b8c6ebd12aedfe64563b85920525d05d394b85f166b7873c8bde6da612f9c"}, + {file = "aiohttp-3.11.11-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:10b4ff0ad793d98605958089fabfa350e8e62bd5d40aa65cdc69d6785859f94e"}, + {file = "aiohttp-3.11.11-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:b540bd67cfb54e6f0865ceccd9979687210d7ed1a1cc8c01f8e67e2f1e883d28"}, + {file = "aiohttp-3.11.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1dac54e8ce2ed83b1f6b1a54005c87dfed139cf3f777fdc8afc76e7841101226"}, + {file = "aiohttp-3.11.11-cp39-cp39-win32.whl", hash = "sha256:568c1236b2fde93b7720f95a890741854c1200fba4a3471ff48b2934d2d93fd3"}, + {file = "aiohttp-3.11.11-cp39-cp39-win_amd64.whl", hash = "sha256:943a8b052e54dfd6439fd7989f67fc6a7f2138d0a2cf0a7de5f18aa4fe7eb3b1"}, + {file = "aiohttp-3.11.11.tar.gz", hash = "sha256:bb49c7f1e6ebf3821a42d81d494f538107610c3a705987f53068546b0e90303e"}, ] [package.dependencies] @@ -117,20 +104,22 @@ aiosignal = ">=1.1.2" attrs = ">=17.3.0" frozenlist = ">=1.1.1" multidict = ">=4.5,<7.0" -yarl = ">=1.12.0,<2.0" +propcache = ">=0.2.0" +yarl = ">=1.17.0,<2.0" [package.extras] speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] [[package]] name = "aiosignal" -version = "1.3.1" +version = "1.3.2" description = "aiosignal: a list of registered asynchronous callbacks" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, - {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, + {file = "aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5"}, + {file = "aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54"}, ] [package.dependencies] @@ -138,40 +127,31 @@ frozenlist = ">=1.1.0" [[package]] name = "attrs" -version = "24.2.0" +version = "24.3.0" description = "Classes Without Boilerplate" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["main"] files = [ - {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, - {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, + {file = "attrs-24.3.0-py3-none-any.whl", hash = "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308"}, + {file = "attrs-24.3.0.tar.gz", hash = "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff"}, ] [package.extras] benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] -[[package]] -name = "cfgv" -version = "3.4.0" -description = "Validate configuration and produce human readable error messages." -optional = false -python-versions = ">=3.8" -files = [ - {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, - {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, -] - [[package]] name = "discord-py" version = "2.4.0" description = "A Python wrapper for the Discord API" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "discord.py-2.4.0-py3-none-any.whl", hash = "sha256:b8af6711c70f7e62160bfbecb55be699b5cb69d007426759ab8ab06b1bd77d1d"}, {file = "discord_py-2.4.0.tar.gz", hash = "sha256:d07cb2a223a185873a1d0ee78b9faa9597e45b3f6186df21a95cec1e9bcdc9a5"}, @@ -186,139 +166,115 @@ speed = ["Brotli", "aiodns (>=1.1)", "cchardet (==2.1.7)", "orjson (>=3.5.4)"] test = ["coverage[toml]", "pytest", "pytest-asyncio", "pytest-cov", "pytest-mock", "typing-extensions (>=4.3,<5)", "tzdata"] voice = ["PyNaCl (>=1.3.0,<1.6)"] -[[package]] -name = "distlib" -version = "0.3.9" -description = "Distribution utilities" -optional = false -python-versions = "*" -files = [ - {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, - {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, -] - -[[package]] -name = "filelock" -version = "3.16.1" -description = "A platform independent file lock." -optional = false -python-versions = ">=3.8" -files = [ - {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, - {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, -] - -[package.extras] -docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] -typing = ["typing-extensions (>=4.12.2)"] - [[package]] name = "frozenlist" -version = "1.4.1" +version = "1.5.0" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ - {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, - {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, - {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, - {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, - {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, - {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, - {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, - {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, - {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, - {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, - {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, - {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, - {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, - {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, - {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, -] - -[[package]] -name = "identify" -version = "2.6.1" -description = "File identification library for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0"}, - {file = "identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98"}, + {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a"}, + {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb"}, + {file = "frozenlist-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15538c0cbf0e4fa11d1e3a71f823524b0c46299aed6e10ebb4c2089abd8c3bec"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e79225373c317ff1e35f210dd5f1344ff31066ba8067c307ab60254cd3a78ad5"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9272fa73ca71266702c4c3e2d4a28553ea03418e591e377a03b8e3659d94fa76"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:498524025a5b8ba81695761d78c8dd7382ac0b052f34e66939c42df860b8ff17"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b5278ed9d50fe610185ecd23c55d8b307d75ca18e94c0e7de328089ac5dcba"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f3c8c1dacd037df16e85227bac13cca58c30da836c6f936ba1df0c05d046d8d"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2ac49a9bedb996086057b75bf93538240538c6d9b38e57c82d51f75a73409d2"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e66cc454f97053b79c2ab09c17fbe3c825ea6b4de20baf1be28919460dd7877f"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3ba5f9a0dfed20337d3e966dc359784c9f96503674c2faf015f7fe8e96798c"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6321899477db90bdeb9299ac3627a6a53c7399c8cd58d25da094007402b039ab"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76e4753701248476e6286f2ef492af900ea67d9706a0155335a40ea21bf3b2f5"}, + {file = "frozenlist-1.5.0-cp310-cp310-win32.whl", hash = "sha256:977701c081c0241d0955c9586ffdd9ce44f7a7795df39b9151cd9a6fd0ce4cfb"}, + {file = "frozenlist-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:189f03b53e64144f90990d29a27ec4f7997d91ed3d01b51fa39d2dbe77540fd4"}, + {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30"}, + {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5"}, + {file = "frozenlist-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf"}, + {file = "frozenlist-1.5.0-cp311-cp311-win32.whl", hash = "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942"}, + {file = "frozenlist-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d"}, + {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21"}, + {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d"}, + {file = "frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f"}, + {file = "frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8"}, + {file = "frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f"}, + {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953"}, + {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0"}, + {file = "frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03"}, + {file = "frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c"}, + {file = "frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28"}, + {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:dd94994fc91a6177bfaafd7d9fd951bc8689b0a98168aa26b5f543868548d3ca"}, + {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0da8bbec082bf6bf18345b180958775363588678f64998c2b7609e34719b10"}, + {file = "frozenlist-1.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:73f2e31ea8dd7df61a359b731716018c2be196e5bb3b74ddba107f694fbd7604"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:828afae9f17e6de596825cf4228ff28fbdf6065974e5ac1410cecc22f699d2b3"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1577515d35ed5649d52ab4319db757bb881ce3b2b796d7283e6634d99ace307"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2150cc6305a2c2ab33299453e2968611dacb970d2283a14955923062c8d00b10"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a72b7a6e3cd2725eff67cd64c8f13335ee18fc3c7befc05aed043d24c7b9ccb9"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c16d2fa63e0800723139137d667e1056bee1a1cf7965153d2d104b62855e9b99"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:17dcc32fc7bda7ce5875435003220a457bcfa34ab7924a49a1c19f55b6ee185c"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:97160e245ea33d8609cd2b8fd997c850b56db147a304a262abc2b3be021a9171"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f1e6540b7fa044eee0bb5111ada694cf3dc15f2b0347ca125ee9ca984d5e9e6e"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:91d6c171862df0a6c61479d9724f22efb6109111017c87567cfeb7b5d1449fdf"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c1fac3e2ace2eb1052e9f7c7db480818371134410e1f5c55d65e8f3ac6d1407e"}, + {file = "frozenlist-1.5.0-cp38-cp38-win32.whl", hash = "sha256:b97f7b575ab4a8af9b7bc1d2ef7f29d3afee2226bd03ca3875c16451ad5a7723"}, + {file = "frozenlist-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:374ca2dabdccad8e2a76d40b1d037f5bd16824933bf7bcea3e59c891fd4a0923"}, + {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9bbcdfaf4af7ce002694a4e10a0159d5a8d20056a12b05b45cea944a4953f972"}, + {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1893f948bf6681733aaccf36c5232c231e3b5166d607c5fa77773611df6dc336"}, + {file = "frozenlist-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2b5e23253bb709ef57a8e95e6ae48daa9ac5f265637529e4ce6b003a37b2621f"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f253985bb515ecd89629db13cb58d702035ecd8cfbca7d7a7e29a0e6d39af5f"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04a5c6babd5e8fb7d3c871dc8b321166b80e41b637c31a995ed844a6139942b6"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9fe0f1c29ba24ba6ff6abf688cb0b7cf1efab6b6aa6adc55441773c252f7411"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:226d72559fa19babe2ccd920273e767c96a49b9d3d38badd7c91a0fdeda8ea08"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b731db116ab3aedec558573c1a5eec78822b32292fe4f2f0345b7f697745c2"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:366d8f93e3edfe5a918c874702f78faac300209a4d5bf38352b2c1bdc07a766d"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1b96af8c582b94d381a1c1f51ffaedeb77c821c690ea5f01da3d70a487dd0a9b"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c03eff4a41bd4e38415cbed054bbaff4a075b093e2394b6915dca34a40d1e38b"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:50cf5e7ee9b98f22bdecbabf3800ae78ddcc26e4a435515fc72d97903e8488e0"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1e76bfbc72353269c44e0bc2cfe171900fbf7f722ad74c9a7b638052afe6a00c"}, + {file = "frozenlist-1.5.0-cp39-cp39-win32.whl", hash = "sha256:666534d15ba8f0fda3f53969117383d5dc021266b3c1a42c9ec4855e4b58b9d3"}, + {file = "frozenlist-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:5c28f4b5dbef8a0d8aad0d4de24d1e9e981728628afaf4ea0792f5d0939372f0"}, + {file = "frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3"}, + {file = "frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817"}, ] -[package.extras] -license = ["ukkonen"] - [[package]] name = "idna" version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -333,6 +289,7 @@ version = "6.1.0" description = "multidict implementation" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"}, @@ -428,356 +385,216 @@ files = [ {file = "multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a"}, ] -[[package]] -name = "nodeenv" -version = "1.9.1" -description = "Node.js virtual environment builder" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, - {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, -] - -[[package]] -name = "platformdirs" -version = "4.3.6" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." -optional = false -python-versions = ">=3.8" -files = [ - {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, - {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, -] - -[package.extras] -docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] -type = ["mypy (>=1.11.2)"] - -[[package]] -name = "pre-commit" -version = "3.8.0" -description = "A framework for managing and maintaining multi-language pre-commit hooks." -optional = false -python-versions = ">=3.9" -files = [ - {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, - {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, -] - -[package.dependencies] -cfgv = ">=2.0.0" -identify = ">=1.0.0" -nodeenv = ">=0.11.1" -pyyaml = ">=5.1" -virtualenv = ">=20.10.0" - [[package]] name = "propcache" -version = "0.2.0" +version = "0.2.1" description = "Accelerated property cache" optional = false -python-versions = ">=3.8" -files = [ - {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5869b8fd70b81835a6f187c5fdbe67917a04d7e52b6e7cc4e5fe39d55c39d58"}, - {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:952e0d9d07609d9c5be361f33b0d6d650cd2bae393aabb11d9b719364521984b"}, - {file = "propcache-0.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:33ac8f098df0585c0b53009f039dfd913b38c1d2edafed0cedcc0c32a05aa110"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e48e8875e6c13909c800fa344cd54cc4b2b0db1d5f911f840458a500fde2c2"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:388f3217649d6d59292b722d940d4d2e1e6a7003259eb835724092a1cca0203a"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f571aea50ba5623c308aa146eb650eebf7dbe0fd8c5d946e28343cb3b5aad577"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3dfafb44f7bb35c0c06eda6b2ab4bfd58f02729e7c4045e179f9a861b07c9850"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3ebe9a75be7ab0b7da2464a77bb27febcb4fab46a34f9288f39d74833db7f61"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d2f0d0f976985f85dfb5f3d685697ef769faa6b71993b46b295cdbbd6be8cc37"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a3dc1a4b165283bd865e8f8cb5f0c64c05001e0718ed06250d8cac9bec115b48"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9e0f07b42d2a50c7dd2d8675d50f7343d998c64008f1da5fef888396b7f84630"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e63e3e1e0271f374ed489ff5ee73d4b6e7c60710e1f76af5f0e1a6117cd26394"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:56bb5c98f058a41bb58eead194b4db8c05b088c93d94d5161728515bd52b052b"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7665f04d0c7f26ff8bb534e1c65068409bf4687aa2534faf7104d7182debb336"}, - {file = "propcache-0.2.0-cp310-cp310-win32.whl", hash = "sha256:7cf18abf9764746b9c8704774d8b06714bcb0a63641518a3a89c7f85cc02c2ad"}, - {file = "propcache-0.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:cfac69017ef97db2438efb854edf24f5a29fd09a536ff3a992b75990720cdc99"}, - {file = "propcache-0.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:63f13bf09cc3336eb04a837490b8f332e0db41da66995c9fd1ba04552e516354"}, - {file = "propcache-0.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608cce1da6f2672a56b24a015b42db4ac612ee709f3d29f27a00c943d9e851de"}, - {file = "propcache-0.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:466c219deee4536fbc83c08d09115249db301550625c7fef1c5563a584c9bc87"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc2db02409338bf36590aa985a461b2c96fce91f8e7e0f14c50c5fcc4f229016"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6ed8db0a556343d566a5c124ee483ae113acc9a557a807d439bcecc44e7dfbb"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91997d9cb4a325b60d4e3f20967f8eb08dfcb32b22554d5ef78e6fd1dda743a2"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c7dde9e533c0a49d802b4f3f218fa9ad0a1ce21f2c2eb80d5216565202acab4"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffcad6c564fe6b9b8916c1aefbb37a362deebf9394bd2974e9d84232e3e08504"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:97a58a28bcf63284e8b4d7b460cbee1edaab24634e82059c7b8c09e65284f178"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:945db8ee295d3af9dbdbb698cce9bbc5c59b5c3fe328bbc4387f59a8a35f998d"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39e104da444a34830751715f45ef9fc537475ba21b7f1f5b0f4d71a3b60d7fe2"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c5ecca8f9bab618340c8e848d340baf68bcd8ad90a8ecd7a4524a81c1764b3db"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c436130cc779806bdf5d5fae0d848713105472b8566b75ff70048c47d3961c5b"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:191db28dc6dcd29d1a3e063c3be0b40688ed76434622c53a284e5427565bbd9b"}, - {file = "propcache-0.2.0-cp311-cp311-win32.whl", hash = "sha256:5f2564ec89058ee7c7989a7b719115bdfe2a2fb8e7a4543b8d1c0cc4cf6478c1"}, - {file = "propcache-0.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e2e54267980349b723cff366d1e29b138b9a60fa376664a157a342689553f71"}, - {file = "propcache-0.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ee7606193fb267be4b2e3b32714f2d58cad27217638db98a60f9efb5efeccc2"}, - {file = "propcache-0.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:91ee8fc02ca52e24bcb77b234f22afc03288e1dafbb1f88fe24db308910c4ac7"}, - {file = "propcache-0.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e900bad2a8456d00a113cad8c13343f3b1f327534e3589acc2219729237a2e8"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f52a68c21363c45297aca15561812d542f8fc683c85201df0bebe209e349f793"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e41d67757ff4fbc8ef2af99b338bfb955010444b92929e9e55a6d4dcc3c4f09"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a64e32f8bd94c105cc27f42d3b658902b5bcc947ece3c8fe7bc1b05982f60e89"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55346705687dbd7ef0d77883ab4f6fabc48232f587925bdaf95219bae072491e"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00181262b17e517df2cd85656fcd6b4e70946fe62cd625b9d74ac9977b64d8d9"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6994984550eaf25dd7fc7bd1b700ff45c894149341725bb4edc67f0ffa94efa4"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:56295eb1e5f3aecd516d91b00cfd8bf3a13991de5a479df9e27dd569ea23959c"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:439e76255daa0f8151d3cb325f6dd4a3e93043e6403e6491813bcaaaa8733887"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f6475a1b2ecb310c98c28d271a30df74f9dd436ee46d09236a6b750a7599ce57"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3444cdba6628accf384e349014084b1cacd866fbb88433cd9d279d90a54e0b23"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4a9d9b4d0a9b38d1c391bb4ad24aa65f306c6f01b512e10a8a34a2dc5675d348"}, - {file = "propcache-0.2.0-cp312-cp312-win32.whl", hash = "sha256:69d3a98eebae99a420d4b28756c8ce6ea5a29291baf2dc9ff9414b42676f61d5"}, - {file = "propcache-0.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ad9c9b99b05f163109466638bd30ada1722abb01bbb85c739c50b6dc11f92dc3"}, - {file = "propcache-0.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ecddc221a077a8132cf7c747d5352a15ed763b674c0448d811f408bf803d9ad7"}, - {file = "propcache-0.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0e53cb83fdd61cbd67202735e6a6687a7b491c8742dfc39c9e01e80354956763"}, - {file = "propcache-0.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92fe151145a990c22cbccf9ae15cae8ae9eddabfc949a219c9f667877e40853d"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a21ef516d36909931a2967621eecb256018aeb11fc48656e3257e73e2e247a"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f88a4095e913f98988f5b338c1d4d5d07dbb0b6bad19892fd447484e483ba6b"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a5b3bb545ead161be780ee85a2b54fdf7092815995661947812dde94a40f6fb"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67aeb72e0f482709991aa91345a831d0b707d16b0257e8ef88a2ad246a7280bf"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c997f8c44ec9b9b0bcbf2d422cc00a1d9b9c681f56efa6ca149a941e5560da2"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a66df3d4992bc1d725b9aa803e8c5a66c010c65c741ad901e260ece77f58d2f"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3ebbcf2a07621f29638799828b8d8668c421bfb94c6cb04269130d8de4fb7136"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1235c01ddaa80da8235741e80815ce381c5267f96cc49b1477fdcf8c047ef325"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3947483a381259c06921612550867b37d22e1df6d6d7e8361264b6d037595f44"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d5bed7f9805cc29c780f3aee05de3262ee7ce1f47083cfe9f77471e9d6777e83"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4a91d44379f45f5e540971d41e4626dacd7f01004826a18cb048e7da7e96544"}, - {file = "propcache-0.2.0-cp313-cp313-win32.whl", hash = "sha256:f902804113e032e2cdf8c71015651c97af6418363bea8d78dc0911d56c335032"}, - {file = "propcache-0.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:8f188cfcc64fb1266f4684206c9de0e80f54622c3f22a910cbd200478aeae61e"}, - {file = "propcache-0.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:53d1bd3f979ed529f0805dd35ddaca330f80a9a6d90bc0121d2ff398f8ed8861"}, - {file = "propcache-0.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:83928404adf8fb3d26793665633ea79b7361efa0287dfbd372a7e74311d51ee6"}, - {file = "propcache-0.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:77a86c261679ea5f3896ec060be9dc8e365788248cc1e049632a1be682442063"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:218db2a3c297a3768c11a34812e63b3ac1c3234c3a086def9c0fee50d35add1f"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7735e82e3498c27bcb2d17cb65d62c14f1100b71723b68362872bca7d0913d90"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:20a617c776f520c3875cf4511e0d1db847a076d720714ae35ffe0df3e440be68"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67b69535c870670c9f9b14a75d28baa32221d06f6b6fa6f77a0a13c5a7b0a5b9"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4569158070180c3855e9c0791c56be3ceeb192defa2cdf6a3f39e54319e56b89"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:db47514ffdbd91ccdc7e6f8407aac4ee94cc871b15b577c1c324236b013ddd04"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:2a60ad3e2553a74168d275a0ef35e8c0a965448ffbc3b300ab3a5bb9956c2162"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:662dd62358bdeaca0aee5761de8727cfd6861432e3bb828dc2a693aa0471a563"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:25a1f88b471b3bc911d18b935ecb7115dff3a192b6fef46f0bfaf71ff4f12418"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:f60f0ac7005b9f5a6091009b09a419ace1610e163fa5deaba5ce3484341840e7"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:74acd6e291f885678631b7ebc85d2d4aec458dd849b8c841b57ef04047833bed"}, - {file = "propcache-0.2.0-cp38-cp38-win32.whl", hash = "sha256:d9b6ddac6408194e934002a69bcaadbc88c10b5f38fb9307779d1c629181815d"}, - {file = "propcache-0.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:676135dcf3262c9c5081cc8f19ad55c8a64e3f7282a21266d05544450bffc3a5"}, - {file = "propcache-0.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:25c8d773a62ce0451b020c7b29a35cfbc05de8b291163a7a0f3b7904f27253e6"}, - {file = "propcache-0.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:375a12d7556d462dc64d70475a9ee5982465fbb3d2b364f16b86ba9135793638"}, - {file = "propcache-0.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1ec43d76b9677637a89d6ab86e1fef70d739217fefa208c65352ecf0282be957"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f45eec587dafd4b2d41ac189c2156461ebd0c1082d2fe7013571598abb8505d1"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc092ba439d91df90aea38168e11f75c655880c12782facf5cf9c00f3d42b562"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa1076244f54bb76e65e22cb6910365779d5c3d71d1f18b275f1dfc7b0d71b4d"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:682a7c79a2fbf40f5dbb1eb6bfe2cd865376deeac65acf9beb607505dced9e12"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e40876731f99b6f3c897b66b803c9e1c07a989b366c6b5b475fafd1f7ba3fb8"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:363ea8cd3c5cb6679f1c2f5f1f9669587361c062e4899fce56758efa928728f8"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:140fbf08ab3588b3468932974a9331aff43c0ab8a2ec2c608b6d7d1756dbb6cb"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e70fac33e8b4ac63dfc4c956fd7d85a0b1139adcfc0d964ce288b7c527537fea"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b33d7a286c0dc1a15f5fc864cc48ae92a846df287ceac2dd499926c3801054a6"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f6d5749fdd33d90e34c2efb174c7e236829147a2713334d708746e94c4bde40d"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22aa8f2272d81d9317ff5756bb108021a056805ce63dd3630e27d042c8092798"}, - {file = "propcache-0.2.0-cp39-cp39-win32.whl", hash = "sha256:73e4b40ea0eda421b115248d7e79b59214411109a5bc47d0d48e4c73e3b8fcf9"}, - {file = "propcache-0.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:9517d5e9e0731957468c29dbfd0f976736a0e55afaea843726e887f36fe017df"}, - {file = "propcache-0.2.0-py3-none-any.whl", hash = "sha256:2ccc28197af5313706511fab3a8b66dcd6da067a1331372c82ea1cb74285e036"}, - {file = "propcache-0.2.0.tar.gz", hash = "sha256:df81779732feb9d01e5d513fad0122efb3d53bbc75f61b2a4f29a020bc985e70"}, -] - -[[package]] -name = "pyyaml" -version = "6.0.2" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, - {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, - {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, - {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, - {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, - {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, - {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, - {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, - {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, - {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, - {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, - {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, - {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, - {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, - {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, - {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, - {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, + {file = "propcache-0.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6b3f39a85d671436ee3d12c017f8fdea38509e4f25b28eb25877293c98c243f6"}, + {file = "propcache-0.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d51fbe4285d5db5d92a929e3e21536ea3dd43732c5b177c7ef03f918dff9f2"}, + {file = "propcache-0.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6445804cf4ec763dc70de65a3b0d9954e868609e83850a47ca4f0cb64bd79fea"}, + {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9479aa06a793c5aeba49ce5c5692ffb51fcd9a7016e017d555d5e2b0045d212"}, + {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9631c5e8b5b3a0fda99cb0d29c18133bca1e18aea9effe55adb3da1adef80d3"}, + {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3156628250f46a0895f1f36e1d4fbe062a1af8718ec3ebeb746f1d23f0c5dc4d"}, + {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b6fb63ae352e13748289f04f37868099e69dba4c2b3e271c46061e82c745634"}, + {file = "propcache-0.2.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:887d9b0a65404929641a9fabb6452b07fe4572b269d901d622d8a34a4e9043b2"}, + {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a96dc1fa45bd8c407a0af03b2d5218392729e1822b0c32e62c5bf7eeb5fb3958"}, + {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a7e65eb5c003a303b94aa2c3852ef130230ec79e349632d030e9571b87c4698c"}, + {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:999779addc413181912e984b942fbcc951be1f5b3663cd80b2687758f434c583"}, + {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:19a0f89a7bb9d8048d9c4370c9c543c396e894c76be5525f5e1ad287f1750ddf"}, + {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1ac2f5fe02fa75f56e1ad473f1175e11f475606ec9bd0be2e78e4734ad575034"}, + {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:574faa3b79e8ebac7cb1d7930f51184ba1ccf69adfdec53a12f319a06030a68b"}, + {file = "propcache-0.2.1-cp310-cp310-win32.whl", hash = "sha256:03ff9d3f665769b2a85e6157ac8b439644f2d7fd17615a82fa55739bc97863f4"}, + {file = "propcache-0.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:2d3af2e79991102678f53e0dbf4c35de99b6b8b58f29a27ca0325816364caaba"}, + {file = "propcache-0.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ffc3cca89bb438fb9c95c13fc874012f7b9466b89328c3c8b1aa93cdcfadd16"}, + {file = "propcache-0.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f174bbd484294ed9fdf09437f889f95807e5f229d5d93588d34e92106fbf6717"}, + {file = "propcache-0.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:70693319e0b8fd35dd863e3e29513875eb15c51945bf32519ef52927ca883bc3"}, + {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b480c6a4e1138e1aa137c0079b9b6305ec6dcc1098a8ca5196283e8a49df95a9"}, + {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d27b84d5880f6d8aa9ae3edb253c59d9f6642ffbb2c889b78b60361eed449787"}, + {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:857112b22acd417c40fa4595db2fe28ab900c8c5fe4670c7989b1c0230955465"}, + {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf6c4150f8c0e32d241436526f3c3f9cbd34429492abddbada2ffcff506c51af"}, + {file = "propcache-0.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66d4cfda1d8ed687daa4bc0274fcfd5267873db9a5bc0418c2da19273040eeb7"}, + {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2f992c07c0fca81655066705beae35fc95a2fa7366467366db627d9f2ee097f"}, + {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:4a571d97dbe66ef38e472703067021b1467025ec85707d57e78711c085984e54"}, + {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bb6178c241278d5fe853b3de743087be7f5f4c6f7d6d22a3b524d323eecec505"}, + {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ad1af54a62ffe39cf34db1aa6ed1a1873bd548f6401db39d8e7cd060b9211f82"}, + {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e7048abd75fe40712005bcfc06bb44b9dfcd8e101dda2ecf2f5aa46115ad07ca"}, + {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:160291c60081f23ee43d44b08a7e5fb76681221a8e10b3139618c5a9a291b84e"}, + {file = "propcache-0.2.1-cp311-cp311-win32.whl", hash = "sha256:819ce3b883b7576ca28da3861c7e1a88afd08cc8c96908e08a3f4dd64a228034"}, + {file = "propcache-0.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:edc9fc7051e3350643ad929df55c451899bb9ae6d24998a949d2e4c87fb596d3"}, + {file = "propcache-0.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:081a430aa8d5e8876c6909b67bd2d937bfd531b0382d3fdedb82612c618bc41a"}, + {file = "propcache-0.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2ccec9ac47cf4e04897619c0e0c1a48c54a71bdf045117d3a26f80d38ab1fb0"}, + {file = "propcache-0.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:14d86fe14b7e04fa306e0c43cdbeebe6b2c2156a0c9ce56b815faacc193e320d"}, + {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:049324ee97bb67285b49632132db351b41e77833678432be52bdd0289c0e05e4"}, + {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cd9a1d071158de1cc1c71a26014dcdfa7dd3d5f4f88c298c7f90ad6f27bb46d"}, + {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98110aa363f1bb4c073e8dcfaefd3a5cea0f0834c2aab23dda657e4dab2f53b5"}, + {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:647894f5ae99c4cf6bb82a1bb3a796f6e06af3caa3d32e26d2350d0e3e3faf24"}, + {file = "propcache-0.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfd3223c15bebe26518d58ccf9a39b93948d3dcb3e57a20480dfdd315356baff"}, + {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d71264a80f3fcf512eb4f18f59423fe82d6e346ee97b90625f283df56aee103f"}, + {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e73091191e4280403bde6c9a52a6999d69cdfde498f1fdf629105247599b57ec"}, + {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3935bfa5fede35fb202c4b569bb9c042f337ca4ff7bd540a0aa5e37131659348"}, + {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f508b0491767bb1f2b87fdfacaba5f7eddc2f867740ec69ece6d1946d29029a6"}, + {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1672137af7c46662a1c2be1e8dc78cb6d224319aaa40271c9257d886be4363a6"}, + {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b74c261802d3d2b85c9df2dfb2fa81b6f90deeef63c2db9f0e029a3cac50b518"}, + {file = "propcache-0.2.1-cp312-cp312-win32.whl", hash = "sha256:d09c333d36c1409d56a9d29b3a1b800a42c76a57a5a8907eacdbce3f18768246"}, + {file = "propcache-0.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:c214999039d4f2a5b2073ac506bba279945233da8c786e490d411dfc30f855c1"}, + {file = "propcache-0.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aca405706e0b0a44cc6bfd41fbe89919a6a56999157f6de7e182a990c36e37bc"}, + {file = "propcache-0.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:12d1083f001ace206fe34b6bdc2cb94be66d57a850866f0b908972f90996b3e9"}, + {file = "propcache-0.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d93f3307ad32a27bda2e88ec81134b823c240aa3abb55821a8da553eed8d9439"}, + {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba278acf14471d36316159c94a802933d10b6a1e117b8554fe0d0d9b75c9d536"}, + {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4e6281aedfca15301c41f74d7005e6e3f4ca143584ba696ac69df4f02f40d629"}, + {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b750a8e5a1262434fb1517ddf64b5de58327f1adc3524a5e44c2ca43305eb0b"}, + {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf72af5e0fb40e9babf594308911436c8efde3cb5e75b6f206c34ad18be5c052"}, + {file = "propcache-0.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2d0a12018b04f4cb820781ec0dffb5f7c7c1d2a5cd22bff7fb055a2cb19ebce"}, + {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e800776a79a5aabdb17dcc2346a7d66d0777e942e4cd251defeb084762ecd17d"}, + {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:4160d9283bd382fa6c0c2b5e017acc95bc183570cd70968b9202ad6d8fc48dce"}, + {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:30b43e74f1359353341a7adb783c8f1b1c676367b011709f466f42fda2045e95"}, + {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:58791550b27d5488b1bb52bc96328456095d96206a250d28d874fafe11b3dfaf"}, + {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0f022d381747f0dfe27e99d928e31bc51a18b65bb9e481ae0af1380a6725dd1f"}, + {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:297878dc9d0a334358f9b608b56d02e72899f3b8499fc6044133f0d319e2ec30"}, + {file = "propcache-0.2.1-cp313-cp313-win32.whl", hash = "sha256:ddfab44e4489bd79bda09d84c430677fc7f0a4939a73d2bba3073036f487a0a6"}, + {file = "propcache-0.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:556fc6c10989f19a179e4321e5d678db8eb2924131e64652a51fe83e4c3db0e1"}, + {file = "propcache-0.2.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6a9a8c34fb7bb609419a211e59da8887eeca40d300b5ea8e56af98f6fbbb1541"}, + {file = "propcache-0.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ae1aa1cd222c6d205853b3013c69cd04515f9d6ab6de4b0603e2e1c33221303e"}, + {file = "propcache-0.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:accb6150ce61c9c4b7738d45550806aa2b71c7668c6942f17b0ac182b6142fd4"}, + {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5eee736daafa7af6d0a2dc15cc75e05c64f37fc37bafef2e00d77c14171c2097"}, + {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7a31fc1e1bd362874863fdeed71aed92d348f5336fd84f2197ba40c59f061bd"}, + {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba4cfa1052819d16699e1d55d18c92b6e094d4517c41dd231a8b9f87b6fa681"}, + {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f089118d584e859c62b3da0892b88a83d611c2033ac410e929cb6754eec0ed16"}, + {file = "propcache-0.2.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:781e65134efaf88feb447e8c97a51772aa75e48b794352f94cb7ea717dedda0d"}, + {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31f5af773530fd3c658b32b6bdc2d0838543de70eb9a2156c03e410f7b0d3aae"}, + {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:a7a078f5d37bee6690959c813977da5291b24286e7b962e62a94cec31aa5188b"}, + {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cea7daf9fc7ae6687cf1e2c049752f19f146fdc37c2cc376e7d0032cf4f25347"}, + {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:8b3489ff1ed1e8315674d0775dc7d2195fb13ca17b3808721b54dbe9fd020faf"}, + {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9403db39be1393618dd80c746cb22ccda168efce239c73af13c3763ef56ffc04"}, + {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5d97151bc92d2b2578ff7ce779cdb9174337390a535953cbb9452fb65164c587"}, + {file = "propcache-0.2.1-cp39-cp39-win32.whl", hash = "sha256:9caac6b54914bdf41bcc91e7eb9147d331d29235a7c967c150ef5df6464fd1bb"}, + {file = "propcache-0.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:92fc4500fcb33899b05ba73276dfb684a20d31caa567b7cb5252d48f896a91b1"}, + {file = "propcache-0.2.1-py3-none-any.whl", hash = "sha256:52277518d6aae65536e9cea52d4e7fd2f7a66f4aa2d30ed3f2fcea620ace3c54"}, + {file = "propcache-0.2.1.tar.gz", hash = "sha256:3f77ce728b19cb537714499928fe800c3dda29e8d9428778fc7c186da4c09a64"}, ] [[package]] name = "ruff" -version = "0.6.9" +version = "0.9.3" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ - {file = "ruff-0.6.9-py3-none-linux_armv6l.whl", hash = "sha256:064df58d84ccc0ac0fcd63bc3090b251d90e2a372558c0f057c3f75ed73e1ccd"}, - {file = "ruff-0.6.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:140d4b5c9f5fc7a7b074908a78ab8d384dd7f6510402267bc76c37195c02a7ec"}, - {file = "ruff-0.6.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53fd8ca5e82bdee8da7f506d7b03a261f24cd43d090ea9db9a1dc59d9313914c"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645d7d8761f915e48a00d4ecc3686969761df69fb561dd914a773c1a8266e14e"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eae02b700763e3847595b9d2891488989cac00214da7f845f4bcf2989007d577"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d5ccc9e58112441de8ad4b29dcb7a86dc25c5f770e3c06a9d57e0e5eba48829"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:417b81aa1c9b60b2f8edc463c58363075412866ae4e2b9ab0f690dc1e87ac1b5"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c866b631f5fbce896a74a6e4383407ba7507b815ccc52bcedabb6810fdb3ef7"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b118afbb3202f5911486ad52da86d1d52305b59e7ef2031cea3425142b97d6f"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67267654edc23c97335586774790cde402fb6bbdb3c2314f1fc087dee320bfa"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3ef0cc774b00fec123f635ce5c547dac263f6ee9fb9cc83437c5904183b55ceb"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:12edd2af0c60fa61ff31cefb90aef4288ac4d372b4962c2864aeea3a1a2460c0"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:55bb01caeaf3a60b2b2bba07308a02fca6ab56233302406ed5245180a05c5625"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:925d26471fa24b0ce5a6cdfab1bb526fb4159952385f386bdcc643813d472039"}, - {file = "ruff-0.6.9-py3-none-win32.whl", hash = "sha256:eb61ec9bdb2506cffd492e05ac40e5bc6284873aceb605503d8494180d6fc84d"}, - {file = "ruff-0.6.9-py3-none-win_amd64.whl", hash = "sha256:785d31851c1ae91f45b3d8fe23b8ae4b5170089021fbb42402d811135f0b7117"}, - {file = "ruff-0.6.9-py3-none-win_arm64.whl", hash = "sha256:a9641e31476d601f83cd602608739a0840e348bda93fec9f1ee816f8b6798b93"}, - {file = "ruff-0.6.9.tar.gz", hash = "sha256:b076ef717a8e5bc819514ee1d602bbdca5b4420ae13a9cf61a0c0a4f53a2baa2"}, -] - -[[package]] -name = "virtualenv" -version = "20.26.6" -description = "Virtual Python Environment builder" -optional = false -python-versions = ">=3.7" -files = [ - {file = "virtualenv-20.26.6-py3-none-any.whl", hash = "sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2"}, - {file = "virtualenv-20.26.6.tar.gz", hash = "sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48"}, + {file = "ruff-0.9.3-py3-none-linux_armv6l.whl", hash = "sha256:7f39b879064c7d9670197d91124a75d118d00b0990586549949aae80cdc16624"}, + {file = "ruff-0.9.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a187171e7c09efa4b4cc30ee5d0d55a8d6c5311b3e1b74ac5cb96cc89bafc43c"}, + {file = "ruff-0.9.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c59ab92f8e92d6725b7ded9d4a31be3ef42688a115c6d3da9457a5bda140e2b4"}, + {file = "ruff-0.9.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dc153c25e715be41bb228bc651c1e9b1a88d5c6e5ed0194fa0dfea02b026439"}, + {file = "ruff-0.9.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:646909a1e25e0dc28fbc529eab8eb7bb583079628e8cbe738192853dbbe43af5"}, + {file = "ruff-0.9.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a5a46e09355695fbdbb30ed9889d6cf1c61b77b700a9fafc21b41f097bfbba4"}, + {file = "ruff-0.9.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c4bb09d2bbb394e3730d0918c00276e79b2de70ec2a5231cd4ebb51a57df9ba1"}, + {file = "ruff-0.9.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96a87ec31dc1044d8c2da2ebbed1c456d9b561e7d087734336518181b26b3aa5"}, + {file = "ruff-0.9.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb7554aca6f842645022fe2d301c264e6925baa708b392867b7a62645304df4"}, + {file = "ruff-0.9.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cabc332b7075a914ecea912cd1f3d4370489c8018f2c945a30bcc934e3bc06a6"}, + {file = "ruff-0.9.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:33866c3cc2a575cbd546f2cd02bdd466fed65118e4365ee538a3deffd6fcb730"}, + {file = "ruff-0.9.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:006e5de2621304c8810bcd2ee101587712fa93b4f955ed0985907a36c427e0c2"}, + {file = "ruff-0.9.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ba6eea4459dbd6b1be4e6bfc766079fb9b8dd2e5a35aff6baee4d9b1514ea519"}, + {file = "ruff-0.9.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:90230a6b8055ad47d3325e9ee8f8a9ae7e273078a66401ac66df68943ced029b"}, + {file = "ruff-0.9.3-py3-none-win32.whl", hash = "sha256:eabe5eb2c19a42f4808c03b82bd313fc84d4e395133fb3fc1b1516170a31213c"}, + {file = "ruff-0.9.3-py3-none-win_amd64.whl", hash = "sha256:040ceb7f20791dfa0e78b4230ee9dce23da3b64dd5848e40e3bf3ab76468dcf4"}, + {file = "ruff-0.9.3-py3-none-win_arm64.whl", hash = "sha256:800d773f6d4d33b0a3c60e2c6ae8f4c202ea2de056365acfa519aa48acf28e0b"}, + {file = "ruff-0.9.3.tar.gz", hash = "sha256:8293f89985a090ebc3ed1064df31f3b4b56320cdfcec8b60d3295bddb955c22a"}, ] -[package.dependencies] -distlib = ">=0.3.7,<1" -filelock = ">=3.12.2,<4" -platformdirs = ">=3.9.1,<5" - -[package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] - [[package]] name = "yarl" -version = "1.15.3" +version = "1.18.3" description = "Yet another URL library" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "yarl-1.15.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:14d6f07b7b4b3b8fba521904db58442281730b44318d6abb9908de79e2a4e4f4"}, - {file = "yarl-1.15.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eacd9de9b5b8262818a2e1f88efbd8d523abc8453de238c5d2f6a91fa85032dd"}, - {file = "yarl-1.15.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5a63ed17af784da3de39b82adfd4f8404ad5ee2ec8f616b063f37da3e64e0521"}, - {file = "yarl-1.15.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b55cc82ba92c07af6ba619dcf70cc89f7b9626adefb87d251f80f2e77419f1da"}, - {file = "yarl-1.15.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:63ba82841ce315e4b5dc8b9345062638c74b1864d38172d0a0403e5a083b0950"}, - {file = "yarl-1.15.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59dce412b2515de05ab2eb6aef19ad7f70857ad436cd65fc4276df007106fb42"}, - {file = "yarl-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e337737b8c9d837e5b4d9e906cc57ed7a639e16e515c8094509b17f556fdb642"}, - {file = "yarl-1.15.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2128315cdc517a45ceb72ec17b256a7940eeb4843c66834c203e7d6580c83405"}, - {file = "yarl-1.15.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:69c2d111e67a818e702ba957da8c8e62de916f5c1b3da043f744084c63f12d46"}, - {file = "yarl-1.15.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:d2a70e8bec768be7423d8d465858a3646b34257a20cc02fd92612f1b14931f50"}, - {file = "yarl-1.15.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:efe758958a7bffce68d91ade238df72667e1f18966ed7b1d3d390eead51a8903"}, - {file = "yarl-1.15.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:b765f19e23c29b68e4f8bbadd36f1da2333ba983d8da2d6518e5f0a7eb2579c2"}, - {file = "yarl-1.15.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:df494e5a79f2ef8f81f966f787e515760e639c6319a321c16198b379c256a157"}, - {file = "yarl-1.15.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:68b27a7d9fb0f145de608da2e45e37fd2397b00266f10487e557f769afa2842d"}, - {file = "yarl-1.15.3-cp310-cp310-win32.whl", hash = "sha256:6d1aba1f644d6e5e16edada31938c11b6c9c97e3bf065742a2c7740d38af0c19"}, - {file = "yarl-1.15.3-cp310-cp310-win_amd64.whl", hash = "sha256:925e72fc7a4222a5bf6d288876d5afacc8f833b49c4cca85f65089131ba25afa"}, - {file = "yarl-1.15.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dbd4808a209b175b5ebbac24c4798dd7511c5ee522a16f2f0eac78c717dfcdfc"}, - {file = "yarl-1.15.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:20f8bdaf667386cea1a8f49cb69a85f90346656d750d3c1278be1dbc76601065"}, - {file = "yarl-1.15.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:adeac55335669a189189373c93d131ebfc2de3ec04f0d3aa7dff6661f83b89b6"}, - {file = "yarl-1.15.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:690d8f702945506b58c9c5834d586e8fd819b845fe6239ab16ebc64a92a6fd3d"}, - {file = "yarl-1.15.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:df7784a29b9689341c17d06d826e3b52ee59d6b6916177e4db0477be7aad5f72"}, - {file = "yarl-1.15.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12c80ec2af97ff3e433699bcabc787ef34e7c08ec038a6e6a25fb81d7bb83607"}, - {file = "yarl-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39533b927c665bcff7da80bf299218e4af12f3e2be27e9c456e29547bcefd631"}, - {file = "yarl-1.15.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db32a5c2912db45e73f80107d178e30f5c48cf596762b3c60ddfebdd655385f0"}, - {file = "yarl-1.15.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bde319602111e9acca3c4f87f4205b38ba6166004bf108de47553633f9a580fc"}, - {file = "yarl-1.15.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:493760c4ced954582db83c4760166992c016e1777ebc0f3ef1bb5eb60b2b5924"}, - {file = "yarl-1.15.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d9cd73f7bff5079d87c2622aa418a75d5d3cdc944d3edb905c5dfc3235466eb0"}, - {file = "yarl-1.15.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e924040582499f7514ec64691031504e6224b5ae7224216208fc2c94f8b13c89"}, - {file = "yarl-1.15.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1c3e9ae98719fe180751b093d02dbcc33b78a37e861d0f2c9571720bd31555db"}, - {file = "yarl-1.15.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6f2911cae6dd012adaaf51494dad4cafb4284ad1f3b588df6ea3e3017e053750"}, - {file = "yarl-1.15.3-cp311-cp311-win32.whl", hash = "sha256:acdfe626607a245aedca35b211f9305a9e7a33349da525bf4ef3caaec8ef51cd"}, - {file = "yarl-1.15.3-cp311-cp311-win_amd64.whl", hash = "sha256:0ace3927502a9f90a868d62c66623703cf5096dcb586187266e9b964d8dd6c81"}, - {file = "yarl-1.15.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:decf9d76191bfe34835f1abd3fa8ebe8a9cd7e16300a5c7e82b18c0812bb22a2"}, - {file = "yarl-1.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ce65ed7ad7b6cbca06b0c011b170bd2b0bc56b0a740540e2713e5ac12d7b9b2e"}, - {file = "yarl-1.15.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3cf2b50352df8775591869aaa22c52b64d60376ba99c0802b42778fedc90b775"}, - {file = "yarl-1.15.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32e8ebf0080ddd38ec05f8be940a3719e5fe1ab8bb6d2b3f6f8b89c9e34149aa"}, - {file = "yarl-1.15.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05183fd49244517cb11c208d0ae128f2e8a85ddb7caf22ad8b0ffcdf5481fcb6"}, - {file = "yarl-1.15.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:46653b5fd29e63ffe63335da343829a2b00bb43b0bd9bb21240d3b42629629e2"}, - {file = "yarl-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6316af233610b9868eda92cf68c016750cbf50085ac6c51faa17905ddd25605"}, - {file = "yarl-1.15.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5685ebc333c95b75be3a0a83a81b82b6411beee9585eaeb9e2e588ae8df23848"}, - {file = "yarl-1.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6da6f6c6ee5595658f21bb9d1ecd702f7a7f22f224ac063dfb595624aec4a2e0"}, - {file = "yarl-1.15.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:45c05b87a8494d9820ea1ac82118fd2f1d795d868e94766fe8ff670377bf6280"}, - {file = "yarl-1.15.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:04f930fcc940f96b8b29110c56882bcff8703f87a7b9354d3acf60ffded5a23d"}, - {file = "yarl-1.15.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8df77742b403e71c5d62d22d150e6e35efd6096a15f2c7419815911c62225100"}, - {file = "yarl-1.15.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f785d83ece0998e4ce4fadda22fa6c1ecc40e10f41617013a8726d2e9af0d98f"}, - {file = "yarl-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7794aade99be0d48b69bd5942acddfeff0de3d09c724d9abe4f19736708ef18f"}, - {file = "yarl-1.15.3-cp312-cp312-win32.whl", hash = "sha256:a3a98d70c667c957c7cd0b153d4cb5e45d43f5e2e23de73be6f7b5c883c01f72"}, - {file = "yarl-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:90257bc627897a2c1d562efcd6a6b18887e9dacae795cad2367e8e16df47d966"}, - {file = "yarl-1.15.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f94d8adfdec402ff97cecc243b310c01d571362ca87bcf8def8e15cb3aaac3ee"}, - {file = "yarl-1.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d0328f798052a33803a77d0868c7f802e952127092c1738fc9e7bfcaac7207c5"}, - {file = "yarl-1.15.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f5f0a0691e39c2e7b5c0f23e6765fa6cb162dce99d9ab1897fdd0f7a4a38b6fb"}, - {file = "yarl-1.15.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370f646d3654e196ddbf772a2d737fe4e1dd738267015b73ff6267ca592fd9d6"}, - {file = "yarl-1.15.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3487c57bc8f17f2586ae7fd0e77f65cd298d45b64d15f604bbb29f4cce0e7961"}, - {file = "yarl-1.15.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef67989d480358482830dc3bc232709804f46a61e7e9841d3f0b1c13a4735b3b"}, - {file = "yarl-1.15.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5ab6c64921802176f56c36aa67c5e6a8baf9557ec1662cb41ecdb5580b67eb9"}, - {file = "yarl-1.15.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb474a06023d01ead9c072b2580c22b2691aa1cabdcc19c3171ab1fa6d8496e3"}, - {file = "yarl-1.15.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92f9a45230d3aa8568c1d692ab27bf505a32dfe3b404721458fc374f411e8bd2"}, - {file = "yarl-1.15.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:24cad94cf2f46cc8e4b9cd44e4e8a84483536a6c54554960b02b10b5724ab122"}, - {file = "yarl-1.15.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:380f30073cbd9b740891bb56f44ee31f870e8721269b618ccc9913400936d9f6"}, - {file = "yarl-1.15.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:353306ba6f0218af1aefe4b9c8b3a0b81b209bc75d79357dac6aca70a7b09d6a"}, - {file = "yarl-1.15.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fe03cea925d884b8f1157a7037df2f5b6a6478a64b78ee600832d8a9f044c83e"}, - {file = "yarl-1.15.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5c4cc1a438ac52562427330e33891f50a78ffd38d335abc64f93f201c83bdc82"}, - {file = "yarl-1.15.3-cp313-cp313-win32.whl", hash = "sha256:956975a3a1ce1f4537be22278d6a283b8bc74d77671f7f6469ab1e800f4e9b02"}, - {file = "yarl-1.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:2e61b72cf15922a7a665299a6b6825bd9901d67ec3b9d3cf9b256dc1667c9bb1"}, - {file = "yarl-1.15.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:270fef2b335e60c91ee835c524445e2248af841c8b72f48769ed6c02fbff5873"}, - {file = "yarl-1.15.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:59b77f0682e1917be197fc8229530f0c6fb3ef8e242d8256ba091a3a1c0ef7e6"}, - {file = "yarl-1.15.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cc4b999718287073dccd3acb0ef1593961bd7923af08991cb3c94080db503935"}, - {file = "yarl-1.15.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9b251d3f90e125ff0d1f76257329a9190fa1bfd2157344c875580bff6dedc62"}, - {file = "yarl-1.15.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ccb4667e0c0a25815efbfe251d24b56624449a319d4bb497074dd49444fb306"}, - {file = "yarl-1.15.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ac26e43b56dbafb30256906bc763cc1f22e05825ae1ced4c6afbd0e6584f18de"}, - {file = "yarl-1.15.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2207491555af5dbbee4c3179a76766f7bc1ecff858f420ea96f2e105ca42c4dd"}, - {file = "yarl-1.15.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14effa29db6113be065a594e13a0f45afb9c1e374fd22b4bc3a4eff0725184b2"}, - {file = "yarl-1.15.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:19077525cd36c797cae19262e15f2881da33c602fb35d075ff0e4263b51b8b88"}, - {file = "yarl-1.15.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:d80c019083506886df098b7bb0d844e19db7e226736829ef49f892ed0a070fa5"}, - {file = "yarl-1.15.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c24debeec87908a864a2b4cb700f863db9441cabacdb22dc448c5d38b55c6f62"}, - {file = "yarl-1.15.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:1c49fe426c45520b4b8a48544d3a9a58194f39c1b57d92451883f847c299a137"}, - {file = "yarl-1.15.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:66ddcd7ee3264bc937860f4780290d60f6472ca0484c214fe805116a831121e8"}, - {file = "yarl-1.15.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2a5cbbb06559757f091f9e71d3f76c27d4dfe0652cc3f17ccce398b8377bfda4"}, - {file = "yarl-1.15.3-cp39-cp39-win32.whl", hash = "sha256:d798de0b50efb66583fc096bcdaa852ed6ea3485a4eb610d6a634f8010d932f4"}, - {file = "yarl-1.15.3-cp39-cp39-win_amd64.whl", hash = "sha256:8f0b33fd088e93ba5f7f6dd55226630e7b78212752479c8fcc6abbd143b9c1ce"}, - {file = "yarl-1.15.3-py3-none-any.whl", hash = "sha256:a1d49ed6f4b812dde88e937d4c2bd3f13d72c23ef7de1e17a63b7cacef4b5691"}, - {file = "yarl-1.15.3.tar.gz", hash = "sha256:fbcff47f8ba82467f203037f7a30decf5c724211b224682f7236edb0dcbb5b95"}, + {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34"}, + {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7"}, + {file = "yarl-1.18.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:602d98f2c2d929f8e697ed274fbadc09902c4025c5a9963bf4e9edfc3ab6f7ed"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c654d5207c78e0bd6d749f6dae1dcbbfde3403ad3a4b11f3c5544d9906969dde"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5094d9206c64181d0f6e76ebd8fb2f8fe274950a63890ee9e0ebfd58bf9d787b"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35098b24e0327fc4ebdc8ffe336cee0a87a700c24ffed13161af80124b7dc8e5"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3236da9272872443f81fedc389bace88408f64f89f75d1bdb2256069a8730ccc"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2c08cc9b16f4f4bc522771d96734c7901e7ebef70c6c5c35dd0f10845270bcd"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80316a8bd5109320d38eef8833ccf5f89608c9107d02d2a7f985f98ed6876990"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c1e1cc06da1491e6734f0ea1e6294ce00792193c463350626571c287c9a704db"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fea09ca13323376a2fdfb353a5fa2e59f90cd18d7ca4eaa1fd31f0a8b4f91e62"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e3b9fd71836999aad54084906f8663dffcd2a7fb5cdafd6c37713b2e72be1760"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:757e81cae69244257d125ff31663249b3013b5dc0a8520d73694aed497fb195b"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b1771de9944d875f1b98a745bc547e684b863abf8f8287da8466cf470ef52690"}, + {file = "yarl-1.18.3-cp310-cp310-win32.whl", hash = "sha256:8874027a53e3aea659a6d62751800cf6e63314c160fd607489ba5c2edd753cf6"}, + {file = "yarl-1.18.3-cp310-cp310-win_amd64.whl", hash = "sha256:93b2e109287f93db79210f86deb6b9bbb81ac32fc97236b16f7433db7fc437d8"}, + {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8503ad47387b8ebd39cbbbdf0bf113e17330ffd339ba1144074da24c545f0069"}, + {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02ddb6756f8f4517a2d5e99d8b2f272488e18dd0bfbc802f31c16c6c20f22193"}, + {file = "yarl-1.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a283dd2882ac98cc6318384f565bffc751ab564605959df4752d42483ad889"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d980e0325b6eddc81331d3f4551e2a333999fb176fd153e075c6d1c2530aa8a8"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b643562c12680b01e17239be267bc306bbc6aac1f34f6444d1bded0c5ce438ca"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c017a3b6df3a1bd45b9fa49a0f54005e53fbcad16633870104b66fa1a30a29d8"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75674776d96d7b851b6498f17824ba17849d790a44d282929c42dbb77d4f17ae"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccaa3a4b521b780a7e771cc336a2dba389a0861592bbce09a476190bb0c8b4b3"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d06d3005e668744e11ed80812e61efd77d70bb7f03e33c1598c301eea20efbb"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9d41beda9dc97ca9ab0b9888cb71f7539124bc05df02c0cff6e5acc5a19dcc6e"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ba23302c0c61a9999784e73809427c9dbedd79f66a13d84ad1b1943802eaaf59"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6748dbf9bfa5ba1afcc7556b71cda0d7ce5f24768043a02a58846e4a443d808d"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0b0cad37311123211dc91eadcb322ef4d4a66008d3e1bdc404808992260e1a0e"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fb2171a4486bb075316ee754c6d8382ea6eb8b399d4ec62fde2b591f879778a"}, + {file = "yarl-1.18.3-cp311-cp311-win32.whl", hash = "sha256:61b1a825a13bef4a5f10b1885245377d3cd0bf87cba068e1d9a88c2ae36880e1"}, + {file = "yarl-1.18.3-cp311-cp311-win_amd64.whl", hash = "sha256:b9d60031cf568c627d028239693fd718025719c02c9f55df0a53e587aab951b5"}, + {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1dd4bdd05407ced96fed3d7f25dbbf88d2ffb045a0db60dbc247f5b3c5c25d50"}, + {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7c33dd1931a95e5d9a772d0ac5e44cac8957eaf58e3c8da8c1414de7dd27c576"}, + {file = "yarl-1.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b411eddcfd56a2f0cd6a384e9f4f7aa3efee14b188de13048c25b5e91f1640"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436c4fc0a4d66b2badc6c5fc5ef4e47bb10e4fd9bf0c79524ac719a01f3607c2"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e35ef8683211db69ffe129a25d5634319a677570ab6b2eba4afa860f54eeaf75"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84b2deecba4a3f1a398df819151eb72d29bfeb3b69abb145a00ddc8d30094512"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e5a1fea0fd4f5bfa7440a47eff01d9822a65b4488f7cff83155a0f31a2ecba"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0e883008013c0e4aef84dcfe2a0b172c4d23c2669412cf5b3371003941f72bb"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a3f356548e34a70b0172d8890006c37be92995f62d95a07b4a42e90fba54272"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ccd17349166b1bee6e529b4add61727d3f55edb7babbe4069b5764c9587a8cc6"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b958ddd075ddba5b09bb0be8a6d9906d2ce933aee81100db289badbeb966f54e"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c7d79f7d9aabd6011004e33b22bc13056a3e3fb54794d138af57f5ee9d9032cb"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4891ed92157e5430874dad17b15eb1fda57627710756c27422200c52d8a4e393"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ce1af883b94304f493698b00d0f006d56aea98aeb49d75ec7d98cd4a777e9285"}, + {file = "yarl-1.18.3-cp312-cp312-win32.whl", hash = "sha256:f91c4803173928a25e1a55b943c81f55b8872f0018be83e3ad4938adffb77dd2"}, + {file = "yarl-1.18.3-cp312-cp312-win_amd64.whl", hash = "sha256:7e2ee16578af3b52ac2f334c3b1f92262f47e02cc6193c598502bd46f5cd1477"}, + {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90adb47ad432332d4f0bc28f83a5963f426ce9a1a8809f5e584e704b82685dcb"}, + {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:913829534200eb0f789d45349e55203a091f45c37a2674678744ae52fae23efa"}, + {file = "yarl-1.18.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9f7768395923c3039055c14334ba4d926f3baf7b776c923c93d80195624782"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a19f62ff30117e706ebc9090b8ecc79aeb77d0b1f5ec10d2d27a12bc9f66d0"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e17c9361d46a4d5addf777c6dd5eab0715a7684c2f11b88c67ac37edfba6c482"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a74a13a4c857a84a845505fd2d68e54826a2cd01935a96efb1e9d86c728e186"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41f7ce59d6ee7741af71d82020346af364949314ed3d87553763a2df1829cc58"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f52a265001d830bc425f82ca9eabda94a64a4d753b07d623a9f2863fde532b53"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:82123d0c954dc58db301f5021a01854a85bf1f3bb7d12ae0c01afc414a882ca2"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2ec9bbba33b2d00999af4631a3397d1fd78290c48e2a3e52d8dd72db3a067ac8"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbd6748e8ab9b41171bb95c6142faf068f5ef1511935a0aa07025438dd9a9bc1"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:877d209b6aebeb5b16c42cbb377f5f94d9e556626b1bfff66d7b0d115be88d0a"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b464c4ab4bfcb41e3bfd3f1c26600d038376c2de3297760dfe064d2cb7ea8e10"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d39d351e7faf01483cc7ff7c0213c412e38e5a340238826be7e0e4da450fdc8"}, + {file = "yarl-1.18.3-cp313-cp313-win32.whl", hash = "sha256:61ee62ead9b68b9123ec24bc866cbef297dd266175d53296e2db5e7f797f902d"}, + {file = "yarl-1.18.3-cp313-cp313-win_amd64.whl", hash = "sha256:578e281c393af575879990861823ef19d66e2b1d0098414855dd367e234f5b3c"}, + {file = "yarl-1.18.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:61e5e68cb65ac8f547f6b5ef933f510134a6bf31bb178be428994b0cb46c2a04"}, + {file = "yarl-1.18.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe57328fbc1bfd0bd0514470ac692630f3901c0ee39052ae47acd1d90a436719"}, + {file = "yarl-1.18.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a440a2a624683108a1b454705ecd7afc1c3438a08e890a1513d468671d90a04e"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09c7907c8548bcd6ab860e5f513e727c53b4a714f459b084f6580b49fa1b9cee"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4f6450109834af88cb4cc5ecddfc5380ebb9c228695afc11915a0bf82116789"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9ca04806f3be0ac6d558fffc2fdf8fcef767e0489d2684a21912cc4ed0cd1b8"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77a6e85b90a7641d2e07184df5557132a337f136250caafc9ccaa4a2a998ca2c"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6333c5a377c8e2f5fae35e7b8f145c617b02c939d04110c76f29ee3676b5f9a5"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0b3c92fa08759dbf12b3a59579a4096ba9af8dd344d9a813fc7f5070d86bbab1"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4ac515b860c36becb81bb84b667466885096b5fc85596948548b667da3bf9f24"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:045b8482ce9483ada4f3f23b3774f4e1bf4f23a2d5c912ed5170f68efb053318"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:a4bb030cf46a434ec0225bddbebd4b89e6471814ca851abb8696170adb163985"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:54d6921f07555713b9300bee9c50fb46e57e2e639027089b1d795ecd9f7fa910"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1d407181cfa6e70077df3377938c08012d18893f9f20e92f7d2f314a437c30b1"}, + {file = "yarl-1.18.3-cp39-cp39-win32.whl", hash = "sha256:ac36703a585e0929b032fbaab0707b75dc12703766d0b53486eabd5139ebadd5"}, + {file = "yarl-1.18.3-cp39-cp39-win_amd64.whl", hash = "sha256:ba87babd629f8af77f557b61e49e7c7cac36f22f871156b91e10a6e9d4f829e9"}, + {file = "yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b"}, + {file = "yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1"}, ] [package.dependencies] @@ -786,6 +603,6 @@ multidict = ">=4.0" propcache = ">=0.2.0" [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = "^3.12" -content-hash = "fb7cd890426d005a0d7e08b6104fabc7a423689bafee69a8690445da08a2cab9" +content-hash = "533677253d4b6098e7b76cf439d6b1cc09a59b2e0b7744481bace360757f0cb7" diff --git a/pyproject.toml b/pyproject.toml index fe824dc..3795833 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "dexscript" -version = "0.4.3.2" +version = "0.4.3.3" description = "" authors = ["dotzz "] license = "MIT" @@ -8,7 +8,7 @@ license = "MIT" [tool.poetry.dependencies] python = "^3.12" "discord.py" = "^2.4.0" -ruff = "^0.8.4" +ruff = "^0.9.3" [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/version.txt b/version.txt index 291ccee..929329f 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.4.3.2 +0.4.3.3 \ No newline at end of file From de1c6ead8284f475c14ceafa9c224b3f900be814 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Sat, 25 Jan 2025 04:32:28 -0500 Subject: [PATCH 002/133] Fix file delete method --- dexscript.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dexscript.py b/dexscript.py index 17d17a4..8f2c356 100644 --- a/dexscript.py +++ b/dexscript.py @@ -276,9 +276,9 @@ async def file(self): await self.ctx.send(file=discord.File(self.args[2].name)) case "delete": - os.remove(self.args[1].name) + os.remove(self.args[2].name) - await self.ctx.send(f"Deleted `{self.args[1]}`") + await self.ctx.send(f"Deleted `{self.args[2]}`") case _: raise DexScriptError( From d72c9977b3b380da60167907edfe15e761123c14 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Sat, 25 Jan 2025 18:39:43 -0500 Subject: [PATCH 003/133] Add FILTER commands, add new file operation, optimize code, etc. --- .github/ISSUE_TEMPLATE/bug_report.yml | 18 +-- dexscript.py | 202 +++++++++++--------------- 2 files changed, 84 insertions(+), 136 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index cf3ea84..9a1c7ca 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -22,24 +22,12 @@ body: placeholder: Bug explanation... validations: required: true - - type: dropdown + - type: textarea id: version attributes: label: Version - description: What version of DexScript is your bot using? - options: - - 0.1 - - 0.2 - - 0.2.1 - - 0.2.2 - - 0.3 - - 0.3.1 - - 0.4 - - 0.4.1 - - 0.4.2 - - 0.4.3 - - 0.4.3.1 - default: 10 + description: Add your version number here. You can fetch it using the `.about` command. + placeholder: Version number... validations: required: true - type: dropdown diff --git a/dexscript.py b/dexscript.py index 8f2c356..e3ddc23 100644 --- a/dexscript.py +++ b/dexscript.py @@ -3,6 +3,7 @@ import logging import os import re +import shutil import traceback from dataclasses import dataclass from dataclasses import field as datafield @@ -38,10 +39,10 @@ FILENAME_RE = re.compile(r"^(.+)(\.\S+)$") MODELS = { - "ball": [Ball, "COUNTRY"], - "regime": [Regime, "NAME"], - "economy": [Economy, "NAME"], - "special": [Special, "NAME"], + "ball": Ball, + "regime": Regime, + "economy": Economy, + "special": Special, } SETTINGS = { @@ -50,8 +51,6 @@ "REFERENCE": "main", } -dex_yields = [] - class Types(Enum): METHOD = 0 @@ -62,10 +61,6 @@ class Types(Enum): DATETIME = 5 -class YieldType(Enum): - CREATE_MODEL = 0 - - class CodeStatus(Enum): SUCCESS = 0 FAILURE = 1 @@ -111,100 +106,41 @@ def __str__(self): return str(self.name) -@dataclass -class Yield: - model: Any - identifier: Any - value: dict - type: YieldType - - @staticmethod - def get(model, identifier): - return next( - (x for x in dex_yields if (x.model, x.identifier.name) == (model, identifier)), None - ) - - class Methods: - def __init__(self, parser, ctx, args: list[Value]): - self.ctx = ctx + def __init__(self, parser, args: list[Value]): self.args = args - self.parser = parser - async def push(self): - global dex_yields - - if in_list(self.args, 1) and self.args[1].name.lower() == "-clear": - dex_yields = [] - - await self.ctx.send("Cleared yield cache.") - return - - for index, yield_object in enumerate(dex_yields, start=1): - if in_list(self.args, 1) and int(self.args[1].name) >= index: - break - - match yield_object.type: - case YieldType.CREATE_MODEL: - await yield_object.model.create(**yield_object.value) - - plural = "" if len(dex_yields) == 1 else "s" - number = self.args[1].name if in_list(self.args, 1) else len(dex_yields) - - await self.ctx.send(f"Pushed `{number}` yield{plural}.") - - dex_yields = [] - - async def create(self): - result = await self.parser.create_model( - self.args[1].name, self.args[2], self.args[3] if in_list(self.args, 3) else False - ) - - suffix = "" - if result is not None: - suffix = " and yielded it until `push`" - dex_yields.append(result) + async def create(self, ctx): + await self.parser.create_model(self.args[1].name, self.args[2]) - await self.ctx.send(f"Created `{self.args[2]}`{suffix}") + await ctx.send(f"Created `{self.args[2]}`") - async def delete(self): - returned_model = await self.parser.get_model(self.args[1], self.args[2].name) - await returned_model.delete() + async def delete(self, ctx): + await self.parser.get_model(self.args[1], self.args[2].name).delete() - await self.ctx.send(f"Deleted `{self.args[2]}`") + await ctx.send(f"Deleted `{self.args[2]}`") - async def update(self): - found_yield = Yield.get(self.args[1].name, self.args[2].name) + async def update(self, ctx): new_attribute = None - if self.ctx.message.attachments != []: - image_path = await save_file(self.ctx.message.attachments[0]) + if ctx.message.attachments != []: + image_path = await save_file(ctx.message.attachments[0]) new_attribute = Value(f"/{image_path}", Types.STRING) else: new_attribute = self.args[4] - update_message = f"`{self.args[2]}'s` {self.args[3]} to `{new_attribute.name}`" - - if found_yield is None: - returned_model = await self.parser.get_model(self.args[1], self.args[2].name) - - setattr(returned_model, self.args[3].name.lower(), new_attribute.name) - - await returned_model.save() - - await self.ctx.send(f"Updated {update_message}") - - return + await self.parser.get_model(self.args[1], self.args[2].name).update( + **{self.args[3].name.lower(): new_attribute.name} + ) - found_yield.value[self.args[3].name.lower()] = new_attribute.name + await ctx.send(f"Updated `{self.args[2]}'s` {self.args[3]} to `{new_attribute.name}`") - await self.ctx.send(f"Updated yielded {update_message}") - async def view(self): + async def view(self, ctx): returned_model = await self.parser.get_model(self.args[1], self.args[2].name) if not in_list(self.args, 3): @@ -223,72 +159,93 @@ async def view(self): fields["content"] += "```" - await self.ctx.send(**fields) + await ctx.send(**fields) return attribute = getattr(returned_model, self.args[3].name.lower()) if isinstance(attribute, str) and os.path.isfile(attribute[1:]): - await self.ctx.send(f"```{attribute}```", file=discord.File(attribute[1:])) + await ctx.send(f"```{attribute}```", file=discord.File(attribute[1:])) return - await self.ctx.send(f"```{attribute}```") + await ctx.send(f"```{attribute}```") - async def list(self): - model = self.args[1].name - parameters = "GLOBAL YIELDS:\n\n" + async def filter_update(self, ctx): + attribute = self.args[2].name.lower() + + await self.args[1].name.filter(**{attribute: self.args[3].name}).update(**{attribute: self.args[4].name}) + + await ctx.send( + f"Updated all `{self.args[1]}` instances from a " + f"`{self.args[2]}` value of `{self.args[3]}` to `{self.args[4]}`" + ) + + + async def filter_remove(self, ctx): + await self.args[1].name.filter(**{self.args[2].name.lower(): self.args[3].name}).delete() + + await ctx.send( + f"Deleted all `{self.args[1]}` instances with a `{self.args[2]}` value of `{self.args[3]}`" + ) + + + async def filter_view(self, ctx): + pass + + + async def attributes(self, ctx): + model = self.args[1].name model_name = model if isinstance(model, str) else model.__name__ - if model_name.lower() != "-yields": - parameters = f"{model_name.upper()} FIELDS:\n\n" + parameters = f"{model_name.upper()} ATTRIBUTES:\n\n" - for field in vars(model()): # type: ignore - if field[:1] == "_": - continue + for field in vars(model()): # type: ignore + if field[:1] == "_": + continue - parameters += f"- {field.replace(' ', '_').upper()}\n" - else: - for index, dex_yield in enumerate(dex_yields, start=1): - parameters += f"{index}. {dex_yield.identifier.name.upper()}\n" + parameters += f"- {field.replace(' ', '_').upper()}\n" - await self.ctx.send(f"```\n{parameters}\n```") + await ctx.send(f"```\n{parameters}\n```") - async def file(self): + + async def dev(self, ctx): match self.args[1].name.lower(): case "write": - new_file = self.ctx.message.attachments[0] + new_file = ctx.message.attachments[0] with open(self.args[2].name, "w") as opened_file: contents = await new_file.read() opened_file.write(contents.decode("utf-8")) - await self.ctx.send(f"Wrote to `{self.args[2]}`") + await ctx.send(f"Wrote to `{self.args[2]}`") case "clear": with open(self.args[2].name, "w") as _: pass - await self.ctx.send(f"Cleared `{self.args[2]}`") + await ctx.send(f"Cleared `{self.args[2]}`") case "read": - await self.ctx.send(file=discord.File(self.args[2].name)) + await ctx.send(file=discord.File(self.args[2].name)) case "delete": os.remove(self.args[2].name) - await self.ctx.send(f"Deleted `{self.args[2]}`") + await ctx.send(f"Deleted `{self.args[2]}`") + + case "rmdir": + shutil.rmtree(self.args[2].name) + + await ctx.send(f"Deleted `{self.args[2]}` directory") case _: raise DexScriptError( f"'{self.args[0]}' is not a valid file operation. " - "(READ, WRITE, CLEAR, or DELETE)" + "(READ, WRITE, CLEAR, RMDIR, or DELETE)" ) - async def show(self): - await self.ctx.send(f"```\n{self.args[1]}\n```") - class DexScriptParser: """ @@ -325,8 +282,14 @@ def autocorrect(string, correction_list, error="does not exist."): raise DexScriptError(f"'{string}' {error}{suggestion}") return autocorrection[0] + + @staticmethod + def extract_str_attr(object): + expression = r"return\s+self\.(\w+)" - async def create_model(self, model, identifier, yield_creation): + return re.search(expression, inspect.getsource(object.__str__)).group(1) + + async def create_model(self, model, identifier): fields = {} for key, field in vars(model()).items(): @@ -346,15 +309,10 @@ async def create_model(self, model, identifier, yield_creation): first_regime = await Regime.first() fields[key] = first_regime.pk - if yield_creation: - return Yield(model, identifier, fields, YieldType.CREATE_MODEL) - await model.create(**fields) async def get_model(self, model, identifier): - attribute = re.search( - r"return\s+self\.(\w+)", inspect.getsource(model.name.__str__) - ).group(1) + attribute = self.extract_str_attr(model.name) correction_list = await model.name.all().values_list(attribute, flat=True) translated_identifier = self.translate(model.extra_data[0].lower()) @@ -377,8 +335,10 @@ def var(self, value): case Types.MODEL: current_model = MODELS[value.name.lower()] - value.name = current_model[0] - value.extra_data.append(current_model[1]) + string_key = self.extract_str_attr(current_model) + + value.name = current_model + value.extra_data.append(string_key) case Types.BOOLEAN: value.name = value.name.lower() == "true" @@ -462,10 +422,10 @@ async def execute(self, code: str): if value.type != Types.METHOD: continue - new_method = Methods(self, self.ctx, line2) + new_method = Methods(self, line2) try: - await getattr(new_method, value.name.lower())() + await getattr(new_method, value.name.lower())(self.ctx) except IndexError: # TODO: Remove `error` duplicates. From dce81445aebb4e8cb687d4ef3765d2e13c845936 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Sat, 25 Jan 2025 18:41:32 -0500 Subject: [PATCH 004/133] Fix ruff line length --- dexscript.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/dexscript.py b/dexscript.py index e3ddc23..dac4de1 100644 --- a/dexscript.py +++ b/dexscript.py @@ -174,7 +174,9 @@ async def view(self, ctx): async def filter_update(self, ctx): attribute = self.args[2].name.lower() - await self.args[1].name.filter(**{attribute: self.args[3].name}).update(**{attribute: self.args[4].name}) + await self.args[1].name.filter(**{attribute: self.args[3].name}).update( + **{attribute: self.args[4].name} + ) await ctx.send( f"Updated all `{self.args[1]}` instances from a " @@ -186,7 +188,8 @@ async def filter_remove(self, ctx): await self.args[1].name.filter(**{self.args[2].name.lower(): self.args[3].name}).delete() await ctx.send( - f"Deleted all `{self.args[1]}` instances with a `{self.args[2]}` value of `{self.args[3]}`" + f"Deleted all `{self.args[1]}` instances with a " + f"`{self.args[2]}` value of `{self.args[3]}`" ) From c0a192211f21d55dd30dab0cd9dc0fbcdd271873 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Sun, 26 Jan 2025 00:56:18 -0500 Subject: [PATCH 005/133] Add `listdir` dev method and merge `rmdir` with `delete` --- dexscript.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/dexscript.py b/dexscript.py index dac4de1..6850d79 100644 --- a/dexscript.py +++ b/dexscript.py @@ -193,10 +193,6 @@ async def filter_remove(self, ctx): ) - async def filter_view(self, ctx): - pass - - async def attributes(self, ctx): model = self.args[1].name @@ -233,20 +229,27 @@ async def dev(self, ctx): case "read": await ctx.send(file=discord.File(self.args[2].name)) + case "listdir": + value = self.args[2].name if in_list(self.args, 2) else None + + await ctx.send(f"```{'\n'.join(os.listdir(value))}```") + case "delete": - os.remove(self.args[2].name) + is_dir = os.path.isdir(self.args[2].name) - await ctx.send(f"Deleted `{self.args[2]}`") + file_type = "directory" if is_dir else "file" - case "rmdir": - shutil.rmtree(self.args[2].name) + if is_dir: + shutil.rmtree(self.args[2].name) + else: + os.remove(self.args[2].name) - await ctx.send(f"Deleted `{self.args[2]}` directory") + await ctx.send(f"Deleted `{self.args[2]}` {file_type}") case _: raise DexScriptError( f"'{self.args[0]}' is not a valid file operation. " - "(READ, WRITE, CLEAR, RMDIR, or DELETE)" + "(READ, WRITE, CLEAR, LISTDIR, or DELETE)" ) From 04b2dda14127d49b3b1cea13c4aa75495266826f Mon Sep 17 00:00:00 2001 From: Dotsian Date: Sun, 26 Jan 2025 00:57:48 -0500 Subject: [PATCH 006/133] Move to `dev` branch --- installer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/installer.py b/installer.py index c63f695..5fae499 100644 --- a/installer.py +++ b/installer.py @@ -32,7 +32,7 @@ class InstallerConfig: """ Configuration class for the installer. """ - github = ["Dotsian/DexScript", "main"] + github = ["Dotsian/DexScript", "dev"] migrations = [ ( "¶¶await self.add_cog(Core(self))", From bff2af980ae6e159f657c65a92e81d6a679011fe Mon Sep 17 00:00:00 2001 From: Dotsian Date: Sun, 26 Jan 2025 21:53:51 -0500 Subject: [PATCH 007/133] Add command docstring, overhaul argument system, improve error handling [TESTING] --- dexscript.py | 235 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 158 insertions(+), 77 deletions(-) diff --git a/dexscript.py b/dexscript.py index 6850d79..077e076 100644 --- a/dexscript.py +++ b/dexscript.py @@ -61,11 +61,6 @@ class Types(Enum): DATETIME = 5 -class CodeStatus(Enum): - SUCCESS = 0 - FAILURE = 1 - - class DexScriptError(Exception): pass @@ -107,43 +102,83 @@ def __str__(self): class Methods: - def __init__(self, parser, args: list[Value]): - self.args = args + def __init__(self, parser): self.parser = parser - async def create(self, ctx): - await self.parser.create_model(self.args[1].name, self.args[2]) + async def help(self, ctx): + """ + Lists all DexScript commands and provides documentation for them. + + Documentation + ------------- + HELP + """ + # getattr(Methods, command).__doc__.replace("\n", "").split("Documentation-------------") + pass + + + async def create(self, ctx, model, identifier): + """ + Creates a model instance. + + Documentation + ------------- + CREATE > MODEL > IDENTIFIER + """ + await self.parser.create_model(model.name, identifier) + + await ctx.send(f"Created `{identifier}`") - await ctx.send(f"Created `{self.args[2]}`") + async def delete(self, ctx, model, identifier): + """ + Deletes a model instance. - async def delete(self, ctx): - await self.parser.get_model(self.args[1], self.args[2].name).delete() + Documentation + ------------- + DELETE > MODEL > IDENTIFIER + """ + await self.parser.get_model(model, identifier.name).delete() - await ctx.send(f"Deleted `{self.args[2]}`") + await ctx.send(f"Deleted `{identifier}`") - async def update(self, ctx): + async def update(self, ctx, model, identifier, attribute, value): + """ + Updates a model instance's attribute. + + Documentation + ------------- + UPDATE > MODEL > IDENTIFIER > ATTRIBUTE > VALUE + """ new_attribute = None if ctx.message.attachments != []: image_path = await save_file(ctx.message.attachments[0]) new_attribute = Value(f"/{image_path}", Types.STRING) else: - new_attribute = self.args[4] + new_attribute = value - await self.parser.get_model(self.args[1], self.args[2].name).update( - **{self.args[3].name.lower(): new_attribute.name} + await self.parser.get_model(model, identifier.name).update( + **{attribute.name.lower(): new_attribute.name} ) - await ctx.send(f"Updated `{self.args[2]}'s` {self.args[3]} to `{new_attribute.name}`") + await ctx.send(f"Updated `{identifier}'s` {attribute} to `{new_attribute.name}`") - async def view(self, ctx): - returned_model = await self.parser.get_model(self.args[1], self.args[2].name) + async def view(self, ctx, model, identifier, attribute=None): + """ + Displays an attribute of a model instance. If `ATTRIBUTE` is left blank, + it will display every attribute of that model instance. - if not in_list(self.args, 3): + Documentation + ------------- + VIEW > MODEL > IDENTIFIER > ATTRIBUTE(?) + """ + returned_model = await self.parser.get_model(model, identifier.name) + + if attribute is None: fields = {"content": "```"} for key, value in vars(returned_model).items(): @@ -155,6 +190,7 @@ async def view(self, ctx): if isinstance(value, str) and value.startswith("/static"): if fields.get("files") is None: fields["files"] = [] + fields["files"].append(discord.File(value[1:])) fields["content"] += "```" @@ -162,93 +198,137 @@ async def view(self, ctx): await ctx.send(**fields) return - attribute = getattr(returned_model, self.args[3].name.lower()) + new_attribute = getattr(returned_model, attribute.name.lower()) - if isinstance(attribute, str) and os.path.isfile(attribute[1:]): - await ctx.send(f"```{attribute}```", file=discord.File(attribute[1:])) + if isinstance(new_attribute, str) and os.path.isfile(new_attribute[1:]): + await ctx.send(f"```{new_attribute}```", file=discord.File(new_attribute[1:])) return - await ctx.send(f"```{attribute}```") + await ctx.send(f"```{new_attribute}```") - async def filter_update(self, ctx): - attribute = self.args[2].name.lower() + async def filter_update( + self, ctx, model, attribute, old_value, new_value, tortoise_operator=None + ): + """ + Updates all instances of a model to the specified value where the specified attribute meets + the condition defined by the optional `TORTOISE_OPERATOR` argument + (e.g., greater than, equal to, etc.). - await self.args[1].name.filter(**{attribute: self.args[3].name}).update( - **{attribute: self.args[4].name} + Documentation + ------------- + FILTER_UPDATE > MODEL > ATTRIBUTE > OLD_VALUE > NEW_VALUE > TORTOISE_OPERATOR(?) + """ + lower_name = attribute.name.lower() + + if tortoise_operator is not None: + lower_name += f"__{tortoise_operator.name.lower()}" + + await model.name.filter(**{lower_name: old_value.name}).update( + **{lower_name: new_value.name} ) await ctx.send( - f"Updated all `{self.args[1]}` instances from a " - f"`{self.args[2]}` value of `{self.args[3]}` to `{self.args[4]}`" + f"Updated all `{model}` instances from a " + f"`{attribute}` value of `{old_value}` to `{new_value}`" ) - async def filter_remove(self, ctx): - await self.args[1].name.filter(**{self.args[2].name.lower(): self.args[3].name}).delete() + async def filter_delete( + self, ctx, model, attribute, value, tortoise_operator=None + ): + """ + Deletes all instances of a model where the specified attribute meets the condition + defined by the optional `TORTOISE_OPERATOR` argument (e.g., greater than, equal to, etc.). + + Documentation + ------------- + FILTER_DELETE > MODEL > ATTRIBUTE > VALUE > TORTOISE_OPERATOR(?) + """ + lower_name = attribute.name.lower() + + if tortoise_operator is not None: + lower_name += f"__{tortoise_operator.name.lower()}" + + await model.name.filter(**{lower_name: value.name}).delete() await ctx.send( - f"Deleted all `{self.args[1]}` instances with a " - f"`{self.args[2]}` value of `{self.args[3]}`" + f"Deleted all `{model}` instances with a " + f"`{attribute}` value of `{value}`" ) - async def attributes(self, ctx): - model = self.args[1].name + async def attributes(self, ctx, model): + """ + Lists all changeable attributes of a model. - model_name = model if isinstance(model, str) else model.__name__ + Documentation + ------------- + ATTRIBUTES > MODEL + """ + model_name = ( + model.name if isinstance(model.name, str) else model.name.__name__ + ) parameters = f"{model_name.upper()} ATTRIBUTES:\n\n" - for field in vars(model()): # type: ignore + for field in vars(model.name()): # type: ignore if field[:1] == "_": continue parameters += f"- {field.replace(' ', '_').upper()}\n" - await ctx.send(f"```\n{parameters}\n```") + await ctx.send(f"```{parameters}```") - async def dev(self, ctx): - match self.args[1].name.lower(): + async def dev(self, ctx, operation, file_name=None): + """ + Developer commands for managing and modifying the bot's internal filesystem. + + Documentation + ------------- + DEV > OPERATION > FILE_NAME(?) + """ + match operation.name.lower(): case "write": + if file_name is None: + raise DexScriptError("`File name is None") + new_file = ctx.message.attachments[0] - with open(self.args[2].name, "w") as opened_file: + with open(file_name.name, "w") as opened_file: contents = await new_file.read() opened_file.write(contents.decode("utf-8")) - await ctx.send(f"Wrote to `{self.args[2]}`") + await ctx.send(f"Wrote to `{file_name}`") case "clear": - with open(self.args[2].name, "w") as _: + with open(file_name.name, "w") as _: pass - await ctx.send(f"Cleared `{self.args[2]}`") + await ctx.send(f"Cleared `{file_name}`") case "read": - await ctx.send(file=discord.File(self.args[2].name)) + await ctx.send(file=discord.File(file_name.name)) case "listdir": - value = self.args[2].name if in_list(self.args, 2) else None - - await ctx.send(f"```{'\n'.join(os.listdir(value))}```") + await ctx.send(f"```{'\n'.join(os.listdir(file_name))}```") case "delete": - is_dir = os.path.isdir(self.args[2].name) + is_dir = os.path.isdir(file_name.name) file_type = "directory" if is_dir else "file" if is_dir: - shutil.rmtree(self.args[2].name) + shutil.rmtree(file_name.name) else: - os.remove(self.args[2].name) + os.remove(file_name.name) - await ctx.send(f"Deleted `{self.args[2]}` {file_type}") + await ctx.send(f"Deleted `{file_name}` {file_type}") case _: raise DexScriptError( - f"'{self.args[0]}' is not a valid file operation. " + f"'{operation}' is not a valid dev operation. " "(READ, WRITE, CLEAR, LISTDIR, or DELETE)" ) @@ -379,7 +459,9 @@ def create_value(self, line): value = Value(line, type) lower = line.lower() - if lower in vars(Methods): + method_functions = [x for x in dir(Methods) if not x.startswith("__")] + + if lower in method_functions: type = Types.METHOD elif lower in MODELS: type = Types.MODEL @@ -394,7 +476,7 @@ def create_value(self, line): return self.var(value) - async def execute(self, code: str): + async def execute(self, code: str) -> str | None: try: seperator = "\n" if "\n" in code else ";'" @@ -424,23 +506,21 @@ async def execute(self, code: str): parsed_code.append(line_code) for line2 in parsed_code: - for value in line2: - if value.type != Types.METHOD: - continue + method = line2[0] - new_method = Methods(self, line2) + if method.type != Types.METHOD: + return f"'{method.name}' is not a valid command." + + method_function = getattr(Methods(self), method.name.lower()) - try: - await getattr(new_method, value.name.lower())(self.ctx) - except IndexError: - # TODO: Remove `error` duplicates. - - error = f"Argument missing when calling {value.name}." - return ((error, error), CodeStatus.FAILURE) + try: + await method_function(self.ctx, *line2.pop(0)) + except TypeError: + return f"Argument missing when calling {method.name}." except Exception as error: - return ((error, traceback.format_exc()), CodeStatus.FAILURE) + return traceback.format_exc() if SETTINGS["DEBUG"] else error - return (None, CodeStatus.SUCCESS) + return None class DexScript(commands.Cog): @@ -495,7 +575,7 @@ async def run(self, ctx: commands.Context, *, code: str): Parameters ---------- code: str - The code you'd like to execute. + The code you want to execute. """ body = self.cleanup_code(code) @@ -505,12 +585,13 @@ async def run(self, ctx: commands.Context, *, code: str): await ctx.send(f"-# {version_check}") dexscript_instance = DexScriptParser(ctx) - result, status = await dexscript_instance.execute(body) + result = await dexscript_instance.execute(body) - if status == CodeStatus.FAILURE and result is not None: - await ctx.send(f"```ERROR: {result[SETTINGS['DEBUG']]}\n```") - else: - await ctx.message.add_reaction("✅") + if result is not None: + await ctx.send(f"```ERROR: {result}```") + return + + await ctx.message.add_reaction("✅") @commands.command() @commands.is_owner() @@ -556,7 +637,7 @@ async def update_ds(self, ctx: commands.Context): else: await ctx.send( "Failed to update DexScript. Report this issue to `dot_zz` on Discord.\n" - f"```\nERROR CODE: {r.status_code}\n```" + f"```ERROR CODE: {r.status_code}```" ) @commands.command(name="reload-ds") From f4b0a8ba1bdee97e5aae389fc24b1e0f98130fe7 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Sun, 26 Jan 2025 21:55:26 -0500 Subject: [PATCH 008/133] Fix ruff line length --- dexscript.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dexscript.py b/dexscript.py index 077e076..0b4e273 100644 --- a/dexscript.py +++ b/dexscript.py @@ -211,8 +211,8 @@ async def filter_update( self, ctx, model, attribute, old_value, new_value, tortoise_operator=None ): """ - Updates all instances of a model to the specified value where the specified attribute meets - the condition defined by the optional `TORTOISE_OPERATOR` argument + Updates all instances of a model to the specified value where the specified attribute + meets the condition defined by the optional `TORTOISE_OPERATOR` argument (e.g., greater than, equal to, etc.). Documentation From 60380fdf107a27b3993b42fdbd4a3e4f80a9892d Mon Sep 17 00:00:00 2001 From: Dotsian Date: Sun, 26 Jan 2025 22:00:25 -0500 Subject: [PATCH 009/133] Fix debug error when missing argument --- dexscript.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/dexscript.py b/dexscript.py index 0b4e273..d8f4021 100644 --- a/dexscript.py +++ b/dexscript.py @@ -515,8 +515,13 @@ async def execute(self, code: str) -> str | None: try: await method_function(self.ctx, *line2.pop(0)) - except TypeError: - return f"Argument missing when calling {method.name}." + except TypeError as error: + final = error + + if not SETTINGS["DEBUG"]: + final = f"Argument missing when calling {method.name}." + + return final except Exception as error: return traceback.format_exc() if SETTINGS["DEBUG"] else error From 2293cb0414267f528882a8ca4610238a900496a9 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Sun, 26 Jan 2025 22:03:37 -0500 Subject: [PATCH 010/133] move to traceback and fix parsing error --- dexscript.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/dexscript.py b/dexscript.py index d8f4021..e9df926 100644 --- a/dexscript.py +++ b/dexscript.py @@ -513,10 +513,12 @@ async def execute(self, code: str) -> str | None: method_function = getattr(Methods(self), method.name.lower()) + line2.pop(0) + try: - await method_function(self.ctx, *line2.pop(0)) - except TypeError as error: - final = error + await method_function(self.ctx, *line2) + except TypeError: + final = traceback.format_exc() if not SETTINGS["DEBUG"]: final = f"Argument missing when calling {method.name}." From 68183aaaa7298e6a42e17d4606646423f9dde597 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Mon, 27 Jan 2025 13:18:57 -0500 Subject: [PATCH 011/133] Overhaul model creation, add more CF translation support for camelCase, optimize code. [TESTING] --- dexscript.py | 130 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 79 insertions(+), 51 deletions(-) diff --git a/dexscript.py b/dexscript.py index e9df926..7939e4a 100644 --- a/dexscript.py +++ b/dexscript.py @@ -91,6 +91,22 @@ def in_list(list_attempt, index): return False +def is_number(string): + try: + float(string) + return True + except ValueError: + return False + + +def is_date(string): + try: + parse_date(string) + return True + except Exception: + return False + + @dataclass class Value: name: Any @@ -342,22 +358,6 @@ def __init__(self, ctx): self.ctx = ctx self.values = [] - @staticmethod - def is_number(string): - try: - float(string) - return True - except ValueError: - return False - - @staticmethod - def is_date(string): - try: - parse_date(string) - return True - except Exception: - return False - @staticmethod def autocorrect(string, correction_list, error="does not exist."): autocorrection = get_close_matches(string, correction_list) @@ -378,22 +378,41 @@ def extract_str_attr(object): async def create_model(self, model, identifier): fields = {} - for key, field in vars(model()).items(): - if field is not None: + for field, field_type in model._meta.fields_map.items(): + field_data = vars(model()).get(field) + + special_list = { + "Identifiers": ["country", "catch_names", "name"], + "Ignore": ["id", "short_name"] + } + + for key, value in special_list.items(): + special_list[key] = self.translate(value) + + if field_data is not None or field in special_list["Ignore"]: continue - if key in ["id", "short_name"]: + if key in special_list["Identifiers"]: + fields[key] = identifier continue - fields[key] = 1 + match field_type: + case "ForeignKeyFieldInstance": + first_model = await MODELS[field].first() - if key in ["country", "full_name", "catch_names", "name"]: - fields[key] = identifier - elif key == "emoji_id": - fields[key] = 100 ** 8 - elif key == "regime_id": - first_regime = await Regime.first() - fields[key] = first_regime.pk + if first_model is None: + raise DexScriptError(f"Could not find default {field}") + + fields[field] = first_model.pk + + case "BigIntField": + fields[field] = 100 ** 8 + + case "BackwardFKRelation" | "JSONField": + continue + + case _: + fields[field] = 1 await model.create(**fields) @@ -435,44 +454,53 @@ def var(self, value): return return_value @staticmethod - def translate(string: str, item=None): + def translate(original: str | list[str]): """ Translates model and field names into a format for both Ballsdex and CarFigures. Parameters ---------- - string: str - The string you want to translate. + original: str | list[str] + The original string or list of strings you want to translate. """ if dir_type == "ballsdex": - return getattr(item, string) if item else string - - translation = {"BALL": "ENTITY", "COUNTRY": "full_name"} - - translated_string = translation.get(string.upper(), string) + return original + + translation = { + "BALL": "ENTITY", + "COUNTRY": "fullName", + "SHORT_NAME": "shortName", + "CATCH_NAMES": "catchNames", + "ICON": "image", + } + + if isinstance(original, list): + translated_copy = [translation.get(x.upper(), x) for x in original] + else: + translated_copy = translation.get(original.upper(), original) - return getattr(item, translated_string) if item else translated_string + return translated_copy def create_value(self, line): - type = Types.STRING - - value = Value(line, type) + value = Value(line, TYpes.STRING) lower = line.lower() method_functions = [x for x in dir(Methods) if not x.startswith("__")] - if lower in method_functions: - type = Types.METHOD - elif lower in MODELS: - type = Types.MODEL - elif self.is_date(lower) and lower.count("-") >= 2: - type = Types.DATETIME - elif self.is_number(lower): - type = Types.NUMBER - elif lower in ["true", "false"]: - type = Types.BOOLEAN - - value.type = type + type_dict = { + Types.METHOD: lower in method_functions, + Types.MODEL: lower in MODELS, + Types.DATETIME: is_date(lower) and lower.count("-") >= 2, + Types.NUMBER: is_number(lower) + Types.BOOLEAN: lower in ["true", "false"] + } + + for key, value in type_dict.items(): + if value is False: + continue + + value.type = key + break return self.var(value) From cd63d9f071db793bad5ef1d646784541f0f8010f Mon Sep 17 00:00:00 2001 From: Dotsian Date: Mon, 27 Jan 2025 13:20:32 -0500 Subject: [PATCH 012/133] Fix SyntaxError --- dexscript.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dexscript.py b/dexscript.py index 7939e4a..95f9ac7 100644 --- a/dexscript.py +++ b/dexscript.py @@ -491,7 +491,7 @@ def create_value(self, line): Types.METHOD: lower in method_functions, Types.MODEL: lower in MODELS, Types.DATETIME: is_date(lower) and lower.count("-") >= 2, - Types.NUMBER: is_number(lower) + Types.NUMBER: is_number(lower), Types.BOOLEAN: lower in ["true", "false"] } From 255739b5e73ce8f55bb548a889aa0247ca09039b Mon Sep 17 00:00:00 2001 From: Dotsian Date: Mon, 27 Jan 2025 22:36:22 -0500 Subject: [PATCH 013/133] Fix typo --- dexscript.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dexscript.py b/dexscript.py index 95f9ac7..c27d39b 100644 --- a/dexscript.py +++ b/dexscript.py @@ -482,7 +482,7 @@ def translate(original: str | list[str]): return translated_copy def create_value(self, line): - value = Value(line, TYpes.STRING) + value = Value(line, Types.STRING) lower = line.lower() method_functions = [x for x in dir(Methods) if not x.startswith("__")] From 41314bf6f57d6eb8ff849080441fc0d1f217b619 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Mon, 27 Jan 2025 22:38:44 -0500 Subject: [PATCH 014/133] Fix naming scheme --- dexscript.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dexscript.py b/dexscript.py index c27d39b..63d2d68 100644 --- a/dexscript.py +++ b/dexscript.py @@ -495,8 +495,8 @@ def create_value(self, line): Types.BOOLEAN: lower in ["true", "false"] } - for key, value in type_dict.items(): - if value is False: + for key, operation in type_dict.items(): + if operation is False: continue value.type = key @@ -546,10 +546,10 @@ async def execute(self, code: str) -> str | None: try: await method_function(self.ctx, *line2) except TypeError: - final = traceback.format_exc() + final = f"Argument missing when calling {method.name}." - if not SETTINGS["DEBUG"]: - final = f"Argument missing when calling {method.name}." + if SETTINGS["DEBUG"]: + final = traceback.format_exc() return final except Exception as error: From 46475d50a4afdd7cd55aac80edcb2f1202166ea3 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Mon, 27 Jan 2025 22:46:33 -0500 Subject: [PATCH 015/133] Fix `listdir` raising error, rename `file_name` to `file_path`, and softcode `valid_operations` list. --- dexscript.py | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/dexscript.py b/dexscript.py index 63d2d68..3c5039d 100644 --- a/dexscript.py +++ b/dexscript.py @@ -297,55 +297,63 @@ async def attributes(self, ctx, model): await ctx.send(f"```{parameters}```") - async def dev(self, ctx, operation, file_name=None): + async def dev(self, ctx, operation, file_path=None): """ Developer commands for managing and modifying the bot's internal filesystem. Documentation ------------- - DEV > OPERATION > FILE_NAME(?) + DEV > OPERATION > FILE_PATH(?) """ - match operation.name.lower(): + lower = operation.name.lower() + valid_operations = ["write", "clear", "read", "listdir", "delete"] + + if lower != "listdir" and lower in valid_operations and file_path is None: + raise DexScriptError("`file_path` is None") + + match lower: case "write": - if file_name is None: - raise DexScriptError("`File name is None") + if file_path is None: + raise DexScriptError("`file_path` is None") new_file = ctx.message.attachments[0] - with open(file_name.name, "w") as opened_file: + with open(file_path.name, "w") as opened_file: contents = await new_file.read() opened_file.write(contents.decode("utf-8")) - await ctx.send(f"Wrote to `{file_name}`") + await ctx.send(f"Wrote to `{file_path}`") case "clear": - with open(file_name.name, "w") as _: + with open(file_path.name, "w") as _: pass - await ctx.send(f"Cleared `{file_name}`") + await ctx.send(f"Cleared `{file_path}`") case "read": - await ctx.send(file=discord.File(file_name.name)) + await ctx.send(file=discord.File(file_path.name)) case "listdir": - await ctx.send(f"```{'\n'.join(os.listdir(file_name))}```") + path = file_path.name if file_path is not None else None + + await ctx.send(f"```{'\n'.join(os.listdir(path))}```") case "delete": - is_dir = os.path.isdir(file_name.name) + is_dir = os.path.isdir(file_path.name) file_type = "directory" if is_dir else "file" if is_dir: - shutil.rmtree(file_name.name) + shutil.rmtree(file_path.name) else: - os.remove(file_name.name) + os.remove(file_path.name) - await ctx.send(f"Deleted `{file_name}` {file_type}") + await ctx.send(f"Deleted `{file_path}` {file_type}") case _: raise DexScriptError( - f"'{operation}' is not a valid dev operation. " - "(READ, WRITE, CLEAR, LISTDIR, or DELETE)" + f"'{operation}' is not a valid dev operation.\n" + f"({", ".join([x.upper() for x in valid_operations])})" ) From 728258782e342eaff35dfba42823bdfd47dfdd7a Mon Sep 17 00:00:00 2001 From: Dotsian Date: Mon, 27 Jan 2025 22:51:15 -0500 Subject: [PATCH 016/133] Remove redundant check --- dexscript.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/dexscript.py b/dexscript.py index 3c5039d..118c464 100644 --- a/dexscript.py +++ b/dexscript.py @@ -313,9 +313,6 @@ async def dev(self, ctx, operation, file_path=None): match lower: case "write": - if file_path is None: - raise DexScriptError("`file_path` is None") - new_file = ctx.message.attachments[0] with open(file_path.name, "w") as opened_file: From 22e63607caf9664dafd6f3f093d88114f1fe8907 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 28 Jan 2025 14:59:15 -0500 Subject: [PATCH 017/133] WIP --- dexscript.py | 250 +++++++++++++++++++++++++++++++------------------ installer.py | 19 ++-- uninstaller.py | 3 + 3 files changed, 170 insertions(+), 102 deletions(-) diff --git a/dexscript.py b/dexscript.py index 118c464..c7c6549 100644 --- a/dexscript.py +++ b/dexscript.py @@ -17,20 +17,17 @@ from dateutil.parser import parse as parse_date from discord.ext import commands -dir_type = "ballsdex" if os.path.isdir("ballsdex") else "carfigures" +DIR = "ballsdex" if os.path.isdir("ballsdex") else "carfigures" -if dir_type == "ballsdex": +if DIR == "ballsdex": from ballsdex.core.models import Ball, Economy, Regime, Special from ballsdex.settings import settings else: - from carfigures.core.models import Car as Ball - from carfigures.core.models import CarType as Regime - from carfigures.core.models import Country as Economy - from carfigures.core.models import Event as Special + from carfigures.core.models import Car, CarType, Country, Event from carfigures.settings import settings -log = logging.getLogger(f"{dir_type}.core.dexscript") +log = logging.getLogger(f"{DIR}.core.dexscript") __version__ = "0.4.3.3" @@ -39,16 +36,19 @@ FILENAME_RE = re.compile(r"^(.+)(\.\S+)$") MODELS = { - "ball": Ball, - "regime": Regime, - "economy": Economy, - "special": Special, -} - -SETTINGS = { - "DEBUG": False, - "OUTDATED-WARNING": True, - "REFERENCE": "main", + "ballsdex": [ + Ball, + Regime, + Economy, + Special + ], + "carfigures": [ + Car, + CarType, + Country, + Event + FontPack + ] } @@ -83,30 +83,6 @@ async def save_file(attachment: discord.Attachment) -> Path: return path -def in_list(list_attempt, index): - try: - list_attempt[index] - return True - except Exception: - return False - - -def is_number(string): - try: - float(string) - return True - except ValueError: - return False - - -def is_date(string): - try: - parse_date(string) - return True - except Exception: - return False - - @dataclass class Value: name: Any @@ -117,6 +93,119 @@ def __str__(self): return str(self.name) +@dataclass +class Settings: + """ + Settings class for DexScript. + """ + + debug = False + versioncheck = False + reference = "main" + + +config = Settings() + + +@dataclass +class Models: + """ + Model functions. + """ + + @staticmethod + def fetch_model(field): + return next( + (x for x in self.all() if x.__name__ == field), None + ) + + @staticmethod + def all(names=False): + allowed_list = { + "ballsdex": [ + Ball, + Regime, + Economy, + Special + ], + "carfigures": [ + Car, + CarType, + Country, + Event + FontPack + ] + } + + return_list = allowed_list[DIR] + + if names: + return_list = [x.__name__ for x in return_list] + + return return_list + + + + @staticmethod + def port(original: str | list[str]): + """ + Translates model and field names into a format for both Ballsdex and CarFigures. + + Parameters + ---------- + original: str | list[str] + The original string or list of strings you want to translate. + """ + if DIR == "ballsdex": + return original + + translation = { + "BALL": "ENTITY", + "COUNTRY": "fullName", + "SHORT_NAME": "shortName", + "CATCH_NAMES": "catchNames", + "ICON": "image", + } + + if isinstance(original, list): + translated_copy = [translation.get(x.upper(), x) for x in original] + else: + translated_copy = translation.get(original.upper(), original) + + return translated_copy + + +@dataclass +class Utils: + """ + DexScript utility functions. + """ + + @staticmethod + def in_list(list_attempt, index): + try: + list_attempt[index] + return True + except Exception: + return False + + @staticmethod + def is_number(string): + try: + float(string) + return True + except ValueError: + return False + + @staticmethod + def is_date(string): + try: + parse_date(string) + return True + except Exception: + return False + + class Methods: def __init__(self, parser): self.parser = parser @@ -176,6 +265,8 @@ async def update(self, ctx, model, identifier, attribute, value): else: new_attribute = value + models_list = Models.all(True) + await self.parser.get_model(model, identifier.name).update( **{attribute.name.lower(): new_attribute.name} ) @@ -392,7 +483,7 @@ async def create_model(self, model, identifier): } for key, value in special_list.items(): - special_list[key] = self.translate(value) + special_list[key] = Models.port(value) if field_data is not None or field in special_list["Ignore"]: continue @@ -403,12 +494,13 @@ async def create_model(self, model, identifier): match field_type: case "ForeignKeyFieldInstance": - first_model = await MODELS[field].first() + model = Utils.fetch_model(field) + instance = await model.first() - if first_model is None: + if instance is None: raise DexScriptError(f"Could not find default {field}") - fields[field] = first_model.pk + fields[field] = instance.pk case "BigIntField": fields[field] = 100 ** 8 @@ -425,7 +517,7 @@ async def get_model(self, model, identifier): attribute = self.extract_str_attr(model.name) correction_list = await model.name.all().values_list(attribute, flat=True) - translated_identifier = self.translate(model.extra_data[0].lower()) + translated_identifier = Models.port(model.extra_data[0].lower()) try: returned_model = await model.name.filter( @@ -443,11 +535,11 @@ def var(self, value): match value.type: case Types.MODEL: - current_model = MODELS[value.name.lower()] + model = Utils.fetch_model(value.name.lower()) - string_key = self.extract_str_attr(current_model) + string_key = self.extract_str_attr(model) - value.name = current_model + value.name = model value.extra_data.append(string_key) case Types.BOOLEAN: @@ -458,34 +550,6 @@ def var(self, value): return return_value - @staticmethod - def translate(original: str | list[str]): - """ - Translates model and field names into a format for both Ballsdex and CarFigures. - - Parameters - ---------- - original: str | list[str] - The original string or list of strings you want to translate. - """ - if dir_type == "ballsdex": - return original - - translation = { - "BALL": "ENTITY", - "COUNTRY": "fullName", - "SHORT_NAME": "shortName", - "CATCH_NAMES": "catchNames", - "ICON": "image", - } - - if isinstance(original, list): - translated_copy = [translation.get(x.upper(), x) for x in original] - else: - translated_copy = translation.get(original.upper(), original) - - return translated_copy - def create_value(self, line): value = Value(line, Types.STRING) lower = line.lower() @@ -494,7 +558,7 @@ def create_value(self, line): type_dict = { Types.METHOD: lower in method_functions, - Types.MODEL: lower in MODELS, + Types.MODEL: lower in MODELS[DIR], Types.DATETIME: is_date(lower) and lower.count("-") >= 2, Types.NUMBER: is_number(lower), Types.BOOLEAN: lower in ["true", "false"] @@ -553,12 +617,12 @@ async def execute(self, code: str) -> str | None: except TypeError: final = f"Argument missing when calling {method.name}." - if SETTINGS["DEBUG"]: + if config.debug final = traceback.format_exc() return final except Exception as error: - return traceback.format_exc() if SETTINGS["DEBUG"] else error + return traceback.format_exc() if config.debug else error return None @@ -583,12 +647,12 @@ def cleanup_code(content): @staticmethod def check_version(): - if not SETTINGS["OUTDATED-WARNING"]: + if not config.versioncheck: return None r = requests.get( "https://api.github.com/repos/Dotsian/DexScript/contents/pyproject.toml", - {"ref": SETTINGS["REFERENCE"]}, + {"ref": config.branch}, ) if r.status_code != requests.codes.ok: @@ -668,7 +732,7 @@ async def update_ds(self, ctx: commands.Context): """ r = requests.get( "https://api.github.com/repos/Dotsian/DexScript/contents/installer.py", - {"ref": SETTINGS["REFERENCE"]}, + {"ref": config.branch}, ) if r.status_code == requests.codes.ok: @@ -686,12 +750,14 @@ async def reload_ds(self, ctx: commands.Context): """ Reloads DexScript. """ - await self.bot.reload_extension(f"{dir_type}.core.dexscript") + await self.bot.reload_extension(f"{DIR}.core.dexscript") await ctx.send("Reloaded DexScript") @commands.command() @commands.is_owner() - async def setting(self, ctx: commands.Context, setting: str, value: str): + async def setting( + self, ctx: commands.Context, setting: str, value: str | None = None + ): """ Changes a setting based on the value provided. @@ -699,20 +765,18 @@ async def setting(self, ctx: commands.Context, setting: str, value: str): ---------- setting: str The setting you want to toggle. - value: str + value: str | None The value you want to set the setting to. """ response = f"`{setting}` is not a valid setting." - if setting in SETTINGS: - selected_setting = SETTINGS[setting] + if name, setting_value in vars(config): + new_value = value - if isinstance(selected_setting, bool): - SETTINGS[setting] = bool(value) - elif isinstance(selected_setting, str): - SETTINGS[setting] = value + if isinstance(setting_value, bool): + new_value = bool(value) if value else not setting_value - response = f"`{setting}` has been set to `{value}`" + response = f"`{name}` has been set to `{value}`" await ctx.send(response) diff --git a/installer.py b/installer.py index 5fae499..1dcc7db 100644 --- a/installer.py +++ b/installer.py @@ -19,9 +19,9 @@ from requests import codes, get -dir_type = "ballsdex" if path.isdir("ballsdex") else "carfigures" +DIR = "ballsdex" if path.isdir("ballsdex") else "carfigures" -if dir_type == "ballsdex": +if DIR == "ballsdex": from ballsdex.settings import settings else: from carfigures.settings import settings @@ -32,6 +32,7 @@ class InstallerConfig: """ Configuration class for the installer. """ + github = ["Dotsian/DexScript", "dev"] migrations = [ ( @@ -48,7 +49,7 @@ def __init__(self): self.managed_time = None self.keywords = ["Installed", "Installing", "Install"] - self.updating = path.isfile(f"{dir_type}/core/dexscript.py") + self.updating = path.isfile(f"{DIR}/core/dexscript.py") if self.updating: self.keywords = ["Updated", "Updating", "Update"] @@ -71,7 +72,7 @@ def format_migration(line): line.replace(" ", "") .replace("¶", " ") .replace("/n", "\n") - .replace("$DIR", dir_type) + .replace("$DIR", DIR) ) async def error(self, error, exception=False): @@ -126,10 +127,10 @@ async def run(self, ctx): request = request.json() content = b64decode(request["content"]) - with open(f"{dir_type}/core/dexscript.py", "w") as opened_file: + with open(f"{DIR}/core/dexscript.py", "w") as opened_file: opened_file.write(content.decode("UTF-8")) - with open(f"{dir_type}/core/bot.py", "r") as read_file: + with open(f"{DIR}/core/bot.py", "r") as read_file: lines = read_file.readlines() stripped_lines = [x.rstrip() for x in lines] @@ -144,13 +145,13 @@ async def run(self, ctx): lines.insert(stripped_lines.index(original) + 1, new) - with open(f"{dir_type}/core/bot.py", "w") as write_file: + with open(f"{DIR}/core/bot.py", "w") as write_file: write_file.writelines(lines) try: - await bot.load_extension(f"{dir_type}.core.dexscript") + await bot.load_extension(f"{DIR}.core.dexscript") except commands.ExtensionAlreadyLoaded: - await bot.reload_extension(f"{dir_type}.core.dexscript") + await bot.reload_extension(f"{DIR}.core.dexscript") if self.updating: request = get(f"{link}/version.txt", {"ref": config.github[1]}) diff --git a/uninstaller.py b/uninstaller.py index 8a336d1..4fe722c 100644 --- a/uninstaller.py +++ b/uninstaller.py @@ -1,3 +1,6 @@ +# OLD UNINSTALLER +# SUBJECT TO CHANGE + import datetime import os import time From b8c1edfea8721408813eae37fda643bea3497fe9 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 28 Jan 2025 23:45:30 -0500 Subject: [PATCH 018/133] Finish WIP --- dexscript.py | 66 +++++++++++++++++--------------------------------- pyproject.toml | 2 +- version.txt | 2 +- 3 files changed, 24 insertions(+), 46 deletions(-) diff --git a/dexscript.py b/dexscript.py index c7c6549..b52526a 100644 --- a/dexscript.py +++ b/dexscript.py @@ -23,34 +23,18 @@ from ballsdex.core.models import Ball, Economy, Regime, Special from ballsdex.settings import settings else: - from carfigures.core.models import Car, CarType, Country, Event + from carfigures.core.models import Car, CarType, Country, Event, FontPack from carfigures.settings import settings log = logging.getLogger(f"{DIR}.core.dexscript") -__version__ = "0.4.3.3" +__version__ = "0.5" START_CODE_BLOCK_RE = re.compile(r"^((```sql?)(?=\s)|(```))") FILENAME_RE = re.compile(r"^(.+)(\.\S+)$") -MODELS = { - "ballsdex": [ - Ball, - Regime, - Economy, - Special - ], - "carfigures": [ - Car, - CarType, - Country, - Event - FontPack - ] -} - class Types(Enum): METHOD = 0 @@ -99,9 +83,9 @@ class Settings: Settings class for DexScript. """ - debug = False - versioncheck = False - reference = "main" + debug: bool = False + versioncheck: bool = False + reference: str = "main" config = Settings() @@ -116,7 +100,7 @@ class Models: @staticmethod def fetch_model(field): return next( - (x for x in self.all() if x.__name__ == field), None + (x for x in Models.all() if x.__name__ == field), None ) @staticmethod @@ -132,7 +116,7 @@ def all(names=False): Car, CarType, Country, - Event + Event, FontPack ] } @@ -181,14 +165,6 @@ class Utils: DexScript utility functions. """ - @staticmethod - def in_list(list_attempt, index): - try: - list_attempt[index] - return True - except Exception: - return False - @staticmethod def is_number(string): try: @@ -265,7 +241,7 @@ async def update(self, ctx, model, identifier, attribute, value): else: new_attribute = value - models_list = Models.all(True) + # models_list = Models.all(True) await self.parser.get_model(model, identifier.name).update( **{attribute.name.lower(): new_attribute.name} @@ -558,9 +534,9 @@ def create_value(self, line): type_dict = { Types.METHOD: lower in method_functions, - Types.MODEL: lower in MODELS[DIR], - Types.DATETIME: is_date(lower) and lower.count("-") >= 2, - Types.NUMBER: is_number(lower), + Types.MODEL: lower in Models.all(True), + Types.DATETIME: Utils.is_date(lower) and lower.count("-") >= 2, + Types.NUMBER: Utils.is_number(lower), Types.BOOLEAN: lower in ["true", "false"] } @@ -617,7 +593,7 @@ async def execute(self, code: str) -> str | None: except TypeError: final = f"Argument missing when calling {method.name}." - if config.debug + if config.debug: final = traceback.format_exc() return final @@ -768,17 +744,19 @@ async def setting( value: str | None The value you want to set the setting to. """ - response = f"`{setting}` is not a valid setting." - - if name, setting_value in vars(config): - new_value = value + if setting not in vars(config): + await ctx.send(f"`{setting}` is not a valid setting.") + return + + setting_value = vars(config)[setting] + new_value = value - if isinstance(setting_value, bool): - new_value = bool(value) if value else not setting_value + if isinstance(setting_value, bool): + new_value = bool(value) if value else not setting_value - response = f"`{name}` has been set to `{value}`" + setattr(config, setting, new_value) - await ctx.send(response) + await ctx.send(f"`{setting}` has been set to `{new_value}`") async def setup(bot): diff --git a/pyproject.toml b/pyproject.toml index 3795833..54c4171 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "dexscript" -version = "0.4.3.3" +version = "0.5" description = "" authors = ["dotzz "] license = "MIT" diff --git a/version.txt b/version.txt index 929329f..ea2303b 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.4.3.3 \ No newline at end of file +0.5 \ No newline at end of file From 02c1aed02d728f55b6ece777fa63b8f89e4cce18 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 28 Jan 2025 23:48:09 -0500 Subject: [PATCH 019/133] Fix variable --- installer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/installer.py b/installer.py index 1dcc7db..37a1926 100644 --- a/installer.py +++ b/installer.py @@ -80,7 +80,7 @@ async def error(self, error, exception=False): description = ( f"Please submit a [bug report]" - f"() to the GitHub page" + f"() to the GitHub page" ) if exception: From e6351a80d8b39fc6e48eab92b963087bdd934296 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 28 Jan 2025 23:50:19 -0500 Subject: [PATCH 020/133] Fix uknown field error --- installer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/installer.py b/installer.py index 37a1926..a582a14 100644 --- a/installer.py +++ b/installer.py @@ -94,7 +94,7 @@ async def error(self, error, exception=False): fields = {"embed": self.embed} if exception: - fields["file"] = discord.File(StringIO(error), filename="DexScript.log") + fields["attachments"] = [discord.File(StringIO(error), filename="DexScript.log")] await self.message.edit(**fields) From c6c856a6a06331b39f4be8ea75a2ec9178aff188 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Wed, 29 Jan 2025 00:01:16 -0500 Subject: [PATCH 021/133] Fix import errors --- dexscript.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/dexscript.py b/dexscript.py index b52526a..eb0571f 100644 --- a/dexscript.py +++ b/dexscript.py @@ -20,10 +20,10 @@ DIR = "ballsdex" if os.path.isdir("ballsdex") else "carfigures" if DIR == "ballsdex": - from ballsdex.core.models import Ball, Economy, Regime, Special + from ballsdex.core.models import Ball, Economy, Regime, Special # noqa: F401 from ballsdex.settings import settings else: - from carfigures.core.models import Car, CarType, Country, Event, FontPack + from carfigures.core.models import Car, CarType, Country, Event, FontsPack # noqa: F401 from carfigures.settings import settings @@ -107,24 +107,24 @@ def fetch_model(field): def all(names=False): allowed_list = { "ballsdex": [ - Ball, - Regime, - Economy, - Special + "Ball", + "Regime", + "Economy", + "Special" ], "carfigures": [ - Car, - CarType, - Country, - Event, - FontPack + "Car", + "CarType", + "Country", + "Event", + "FontsPack" ] } return_list = allowed_list[DIR] - if names: - return_list = [x.__name__ for x in return_list] + if not names: + return_list = [globals().get(x) for x in return_list if globals().get(x) is not None] return return_list From 55b4653b7cf9670a8ffcbe120d371035b059e430 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Wed, 29 Jan 2025 09:59:01 -0500 Subject: [PATCH 022/133] Remake parser --- dexscript.py | 145 ++++++++++++++++++++++++--------------------------- 1 file changed, 67 insertions(+), 78 deletions(-) diff --git a/dexscript.py b/dexscript.py index eb0571f..5971cff 100644 --- a/dexscript.py +++ b/dexscript.py @@ -99,9 +99,7 @@ class Models: @staticmethod def fetch_model(field): - return next( - (x for x in Models.all() if x.__name__ == field), None - ) + return globals().get(field) @staticmethod def all(names=False): @@ -225,21 +223,22 @@ async def delete(self, ctx, model, identifier): await ctx.send(f"Deleted `{identifier}`") - async def update(self, ctx, model, identifier, attribute, value): + async def update(self, ctx, model, identifier, attribute, value=None): """ - Updates a model instance's attribute. + Updates a model instance's attribute. If value is None, it will check + for any attachments. Documentation ------------- - UPDATE > MODEL > IDENTIFIER > ATTRIBUTE > VALUE + UPDATE > MODEL > IDENTIFIER > ATTRIBUTE > VALUE(?) """ - new_attribute = None + new_attribute = value - if ctx.message.attachments != []: - image_path = await save_file(ctx.message.attachments[0]) + if value is None and self.parser.attachments != []: + image_path = await save_file(self.parser.attachments[0]) new_attribute = Value(f"/{image_path}", Types.STRING) - else: - new_attribute = value + + self.parser.attachments.pop(0) # models_list = Models.all(True) @@ -290,6 +289,7 @@ async def view(self, ctx, model, identifier, attribute=None): await ctx.send(f"```{new_attribute}```") + # TODO: Add attachment support. async def filter_update( self, ctx, model, attribute, old_value, new_value, tortoise_operator=None ): @@ -428,6 +428,7 @@ class DexScriptParser: def __init__(self, ctx): self.ctx = ctx + self.attachments = ctx.message.attachments self.values = [] @staticmethod @@ -470,8 +471,8 @@ async def create_model(self, model, identifier): match field_type: case "ForeignKeyFieldInstance": - model = Utils.fetch_model(field) - instance = await model.first() + fetched_model = Models.fetch_model(field) + instance = await fetched_model.first() if instance is None: raise DexScriptError(f"Could not find default {field}") @@ -506,26 +507,6 @@ async def get_model(self, model, identifier): return returned_model[0] - def var(self, value): - return_value = value - - match value.type: - case Types.MODEL: - model = Utils.fetch_model(value.name.lower()) - - string_key = self.extract_str_attr(model) - - value.name = model - value.extra_data.append(string_key) - - case Types.BOOLEAN: - value.name = value.name.lower() == "true" - - case Types.DATETIME: - value.name = parse_date(value.name) - - return return_value - def create_value(self, line): value = Value(line, Types.STRING) lower = line.lower() @@ -547,60 +528,61 @@ def create_value(self, line): value.type = key break - return self.var(value) - - async def execute(self, code: str) -> str | None: - try: - seperator = "\n" if "\n" in code else ";'" - - split_code = [x for x in code.split(seperator) if x.strip() != ""] - - parsed_code: list[list[Value]] = [] + return_value = value - for line in split_code: - line_code: list[Value] = [] - full_line = "" + match value.type: + case Types.MODEL: + model = Models.fetch_model(value.name.lower()) - for index2, char in enumerate(line): - if char == "": - continue + string_key = self.extract_str_attr(model) - full_line += char + value.name = model + value.extra_data.append(string_key) - if full_line == "--": - break + case Types.BOOLEAN: + value.name = value.name.lower() == "true" - if char in [">"] or index2 == len(line) - 1: - line_code.append(self.create_value(full_line.replace(">", "").strip())) + case Types.DATETIME: + value.name = parse_date(value.name) - full_line = "" + return value - if len(line_code) == len(line.split(">")): - parsed_code.append(line_code) + def error(message, log): + return (message, log)[config.debug] - for line2 in parsed_code: - method = line2[0] + async def execute(self, code: str): + loaded_methods = Methods(self) - if method.type != Types.METHOD: - return f"'{method.name}' is not a valid command." - - method_function = getattr(Methods(self), method.name.lower()) + split_code = [x for x in code.split("\n") if x.strip() != ""] - line2.pop(0) + parsed_code = [ + [self.create_value(s.strip()) for s in re.findall(r"[^>]+", line)] + for line in split_code if not line.strip().startswith("--") + ] - try: - await method_function(self.ctx, *line2) - except TypeError: - final = f"Argument missing when calling {method.name}." + for line2 in parsed_code: + if line2 == []: + continue + + method = line2[0] - if config.debug: - final = traceback.format_exc() + if method.type != Types.METHOD: + return self.error( + f"'{method.name}' is not a valid command.", + traceback.format_exc() + ) + + method_function = getattr(loaded_methods, method.name.lower()) - return final - except Exception as error: - return traceback.format_exc() if config.debug else error + line2.pop(0) - return None + try: + await method_function(self.ctx, *line2) + except TypeError: + return self.error( + f"Argument missing when calling '{method.name}'.", + traceback.format_exc() + ) class DexScript(commands.Cog): @@ -665,13 +647,20 @@ async def run(self, ctx: commands.Context, *, code: str): await ctx.send(f"-# {version_check}") dexscript_instance = DexScriptParser(ctx) - result = await dexscript_instance.execute(body) - if result is not None: - await ctx.send(f"```ERROR: {result}```") + try: + result = await dexscript_instance.execute(body) + except Exception as error: + full_error = traceback.format_exc() if config.debug else error + + await ctx.send(f"```ERROR: {full_error}```") return - - await ctx.message.add_reaction("✅") + else: + if result is not None: + await ctx.send(f"```ERROR: {result}```") + return + + await ctx.message.add_reaction("✅") @commands.command() @commands.is_owner() From 58d4d2612d9b1a14fc30584dfe9199901148bcb6 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Wed, 29 Jan 2025 10:03:13 -0500 Subject: [PATCH 023/133] Remove case sensitivity from settings --- dexscript.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dexscript.py b/dexscript.py index 5971cff..ab3e204 100644 --- a/dexscript.py +++ b/dexscript.py @@ -528,8 +528,6 @@ def create_value(self, line): value.type = key break - return_value = value - match value.type: case Types.MODEL: model = Models.fetch_model(value.name.lower()) @@ -733,6 +731,8 @@ async def setting( value: str | None The value you want to set the setting to. """ + setting = setting.lower() + if setting not in vars(config): await ctx.send(f"`{setting}` is not a valid setting.") return From 041ae367bed1d9d57d9c93f09bb0ce718d3964c7 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Wed, 29 Jan 2025 10:05:17 -0500 Subject: [PATCH 024/133] Fix ruff checks --- dexscript.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dexscript.py b/dexscript.py index ab3e204..c4cefb5 100644 --- a/dexscript.py +++ b/dexscript.py @@ -20,10 +20,14 @@ DIR = "ballsdex" if os.path.isdir("ballsdex") else "carfigures" if DIR == "ballsdex": - from ballsdex.core.models import Ball, Economy, Regime, Special # noqa: F401 + from ballsdex.core.models import ( + Ball, Economy, Regime, Special # noqa: F401 # noqa: I001 + ) from ballsdex.settings import settings else: - from carfigures.core.models import Car, CarType, Country, Event, FontsPack # noqa: F401 + from carfigures.core.models import ( + Car, CarType, Country, Event, FontsPack # noqa: F401 # noqa: I001 + ) from carfigures.settings import settings From ae8016b43174604fe23ca10cf1c81ad675850a8c Mon Sep 17 00:00:00 2001 From: Dotsian Date: Wed, 29 Jan 2025 10:07:31 -0500 Subject: [PATCH 025/133] Fix ruff checks #2 --- dexscript.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/dexscript.py b/dexscript.py index c4cefb5..12459d4 100644 --- a/dexscript.py +++ b/dexscript.py @@ -20,14 +20,10 @@ DIR = "ballsdex" if os.path.isdir("ballsdex") else "carfigures" if DIR == "ballsdex": - from ballsdex.core.models import ( - Ball, Economy, Regime, Special # noqa: F401 # noqa: I001 - ) + from ballsdex.core.models import Ball, Economy, Regime, Special # noqa: F401, I001 from ballsdex.settings import settings else: - from carfigures.core.models import ( - Car, CarType, Country, Event, FontsPack # noqa: F401 # noqa: I001 - ) + from carfigures.core.models import Car, CarType, Country, Event, FontsPack # noqa: F401, I001 from carfigures.settings import settings From 9b540cd2975ec3434af4c218d3950ea35e17a83a Mon Sep 17 00:00:00 2001 From: Dotsian Date: Wed, 29 Jan 2025 10:27:52 -0500 Subject: [PATCH 026/133] Fix parsing error --- dexscript.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dexscript.py b/dexscript.py index 12459d4..7cd900a 100644 --- a/dexscript.py +++ b/dexscript.py @@ -545,7 +545,7 @@ def create_value(self, line): return value - def error(message, log): + def error(self, message, log): return (message, log)[config.debug] async def execute(self, code: str): From 410cf8b8ea87f62439d7db2bdca43c3b18ef1411 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Wed, 29 Jan 2025 19:46:17 -0500 Subject: [PATCH 027/133] Fix model type --- dexscript.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/dexscript.py b/dexscript.py index 7cd900a..d178e3e 100644 --- a/dexscript.py +++ b/dexscript.py @@ -102,7 +102,7 @@ def fetch_model(field): return globals().get(field) @staticmethod - def all(names=False): + def all(names=False, key=None): allowed_list = { "ballsdex": [ "Ball", @@ -124,6 +124,9 @@ def all(names=False): if not names: return_list = [globals().get(x) for x in return_list if globals().get(x) is not None] + if key is not None: + return_list = [key(x) for x in return_list] + return return_list @@ -515,7 +518,7 @@ def create_value(self, line): type_dict = { Types.METHOD: lower in method_functions, - Types.MODEL: lower in Models.all(True), + Types.MODEL: lower in Models.all(True, key=str.lower), Types.DATETIME: Utils.is_date(lower) and lower.count("-") >= 2, Types.NUMBER: Utils.is_number(lower), Types.BOOLEAN: lower in ["true", "false"] From 15c7dd50510bc506aed3ce7311b9abd381db24e3 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Wed, 29 Jan 2025 23:49:26 -0500 Subject: [PATCH 028/133] WIP --- dexscript.py => DexScript/dexscript.py | 93 +++++++++++-------- installer.py => DexScript/github/installer.py | 0 DexScript/github/migration.py | 36 +++++++ .../github/uninstaller.py | 0 version.txt | 1 - 5 files changed, 91 insertions(+), 39 deletions(-) rename dexscript.py => DexScript/dexscript.py (89%) rename installer.py => DexScript/github/installer.py (100%) create mode 100644 DexScript/github/migration.py rename uninstaller.py => DexScript/github/uninstaller.py (100%) delete mode 100644 version.txt diff --git a/dexscript.py b/DexScript/dexscript.py similarity index 89% rename from dexscript.py rename to DexScript/dexscript.py index d178e3e..91fd2db 100644 --- a/dexscript.py +++ b/DexScript/dexscript.py @@ -367,61 +367,78 @@ async def attributes(self, ctx, model): await ctx.send(f"```{parameters}```") - async def dev(self, ctx, operation, file_path=None): + async def dev(self, ctx, operation, arg1): + """ + Developer commands for executing evals. + + Documentation + ------------- + DEV > OPERATION > ARG1(?) + """ + match operation: + case "exec_git": + link = arg1.name.replace("https://github.com/", "") + "" + https://github.com/Dotsian/DexScript/blob/dev/installer.py + r = requests.get( + "https://api.github.com/repos/Dotsian/DexScript/contents/DexScript/github/installer.py", + {"ref": "dev"} + ) + + if r.status_code == requests.codes.ok: + content = base64.b64decode(r.json()["content"]) + await ctx.invoke(bot.get_command("eval"), body=content.decode("UTF-8")) + else: + await ctx.send("Failed to install DexScript BETA.\nReport this issue to `dot_zz` on Discord.") + print(f"ERROR CODE: {r.status_code}") + + class File: """ Developer commands for managing and modifying the bot's internal filesystem. Documentation ------------- - DEV > OPERATION > FILE_PATH(?) + REFER TO WIKI. """ - lower = operation.name.lower() - valid_operations = ["write", "clear", "read", "listdir", "delete"] - if lower != "listdir" and lower in valid_operations and file_path is None: - raise DexScriptError("`file_path` is None") - - match lower: - case "write": - new_file = ctx.message.attachments[0] + async def read(self, ctx, file_path): + await ctx.send(file=discord.File(file_path.name)) + + + async def write(self, ctx, file_path): + new_file = ctx.message.attachments[0] - with open(file_path.name, "w") as opened_file: - contents = await new_file.read() - opened_file.write(contents.decode("utf-8")) + with open(file_path.name, "w") as opened_file: + contents = await new_file.read() + opened_file.write(contents.decode("utf-8")) - await ctx.send(f"Wrote to `{file_path}`") + await ctx.send(f"Wrote to `{file_path}`") - case "clear": - with open(file_path.name, "w") as _: - pass - await ctx.send(f"Cleared `{file_path}`") + async def clear(self, ctx, file_path): + with open(file_path.name, "w") as _: + pass - case "read": - await ctx.send(file=discord.File(file_path.name)) + await ctx.send(f"Cleared `{file_path}`") - case "listdir": - path = file_path.name if file_path is not None else None - await ctx.send(f"```{'\n'.join(os.listdir(path))}```") + async def listdir(self, ctx, file_path=None): + path = file_path.name if file_path is not None else None - case "delete": - is_dir = os.path.isdir(file_path.name) + await ctx.send(f"```{'\n'.join(os.listdir(path))}```") + - file_type = "directory" if is_dir else "file" + async def delete(self, ctx, file_path): + is_dir = os.path.isdir(file_path.name) - if is_dir: - shutil.rmtree(file_path.name) - else: - os.remove(file_path.name) + file_type = "directory" if is_dir else "file" - await ctx.send(f"Deleted `{file_path}` {file_type}") + if is_dir: + shutil.rmtree(file_path.name) + else: + os.remove(file_path.name) - case _: - raise DexScriptError( - f"'{operation}' is not a valid dev operation.\n" - f"({", ".join([x.upper() for x in valid_operations])})" - ) + await ctx.send(f"Deleted `{file_path}` {file_type}") class DexScriptParser: @@ -533,7 +550,7 @@ def create_value(self, line): match value.type: case Types.MODEL: - model = Models.fetch_model(value.name.lower()) + model = Models.fetch_model(line) string_key = self.extract_str_attr(model) @@ -541,7 +558,7 @@ def create_value(self, line): value.extra_data.append(string_key) case Types.BOOLEAN: - value.name = value.name.lower() == "true" + value.name = lower == "true" case Types.DATETIME: value.name = parse_date(value.name) diff --git a/installer.py b/DexScript/github/installer.py similarity index 100% rename from installer.py rename to DexScript/github/installer.py diff --git a/DexScript/github/migration.py b/DexScript/github/migration.py new file mode 100644 index 0000000..a11365f --- /dev/null +++ b/DexScript/github/migration.py @@ -0,0 +1,36 @@ +from os import path + +DIR = "ballsdex" if path.isdir("ballsdex") else "carfigures" + + +#|-----------------------------------------------------------------------------------------|# + + +def repair_bot_file(): + """ + Repairs the `bot.py` file and removes extra newlines caused by an old DexScript installer. + """ + with open(f"{DIR}/core/bot.py", "r") as file: + content = file.read() + + if "import asyncio\n\n" not in content: + return + + new_lines = [] + last_was_newline = False + + for line in content.splitlines(keepends=True): + if last_was_newline and line == "\n": + continue + + last_was_newline = line == "\n" + new_lines.append(line) + + with open(f"{DIR}/core/bot.py", "w") as file: + file.writelines(new_lines) + + +#|-----------------------------------------------------------------------------------------|# + + +repair_bot_file() diff --git a/uninstaller.py b/DexScript/github/uninstaller.py similarity index 100% rename from uninstaller.py rename to DexScript/github/uninstaller.py diff --git a/version.txt b/version.txt deleted file mode 100644 index ea2303b..0000000 --- a/version.txt +++ /dev/null @@ -1 +0,0 @@ -0.5 \ No newline at end of file From 0804c252b35fd28650d9240b02cf61845522da45 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Thu, 30 Jan 2025 11:34:03 -0500 Subject: [PATCH 029/133] New readme WIP --- README.md | 48 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index bc9c7f0..8ccfe01 100644 --- a/README.md +++ b/README.md @@ -5,31 +5,47 @@ # DexScript - BETA -DexScript is a set of commands created by DotZZ. The commands simplify editing, adding, and deleting models such as balls, regimes, specials, etc. +[![Ruff](https://github.com/Dotsian/DexScript/actions/workflows/ruff.yml/badge.svg)](https://github.com/Dotsian/DexScript/actions/workflows/ruff.yml) +[![Issues](https://img.shields.io/github/issues/Dotsian/DexScript)](https://github.com/Dotsian/DexScript/issues) +[![discord.py](https://img.shields.io/badge/discord-py-blue.svg)](https://github.com/Rapptz/discord.py) -## Supported forks +DexScript is a set of commands created by DotZZ. The commands simplify editing, adding, and deleting models such as balls, regimes, specials, etc. -DexScript can be used for BallsDex and CarFigures instances; each has its own respective guide on how to use DexScript with it. +DexScript supports both BallsDex and CarFigures. Each fork has its own respective guide on how to use DexScript with it. ## Installation -To install DexScript, follow the guide below and read it carefully. If you need assistance, join the official [DexScript Discord Server](https://discord.gg/pkKvMdP74Z). +### Requirements -The guide is in the [wiki](https://github.com/Dotsian/DexScript/wiki/Installing,-Updating,-and-Uninstalling) and you should read the first one that is for installing. If you ever want to uninstall it you can always follow the guide that is in below the install part. +To install DexScript, you must have the following: -## Updating +* Ballsdex or CarFigures v2.2.0+ +* Eval access + +### Installing + +DexScript has two branches: Main and Dev. + +The main branch contains the most stable features, while the dev branch contains unreleased features, a plenthora of bugs, and many changes. -You've probably been using this for a while and if a new version is released you can always go find the code for updating in the wiki. +To install DexScript, run the following eval command: -## Beta preview +#### Main -Yes, you can test beta things, and if you are wondering how to do that, follow the guide in the [wiki](https://github.com/Dotsian/DexScript/wiki/Installing,-Updating,-and-Uninstalling) below the uninstall one. If there are any bugs please report them at [bug report](https://github.com/Dotsian/DexScript/issues/new/choose). +```py +import base64, requests -## Information +await ctx.invoke(bot.get_command("eval"), body=base64.b64decode(requests.get("https://api.github.com/repos/Dotsian/DexScript/contents/installer.py").json()["content"]).decode()) +``` + +### Dev + +```py +import base64, requests + +await ctx.invoke(bot.get_command("eval"), body=base64.b64decode(requests.get("https://api.github.com/repos/Dotsian/DexScript/contents/DexScript/github/installer.py", {"ref": "dev"}).json()["content"]).decode()) +``` + +## Updating -> ``Made by dot_zz`` -> ``Version 0.4.3.2`` -> ``MIT License`` -> ``Made on September 14, 2024`` -> ``Last updated on January 1, 2025`` -> [``DexScript Discord server``](https://discord.gg/pkKvMdP74Z) +The command above will automatically update DexScript, however, if you already have DexScript, you can run `DEV > EXEC_GIT > Dotsian/DexScript/installer.py` to update DexScript. From f732b37fa80bad9ef0f6b11020e1fb2b1fb57261 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Thu, 30 Jan 2025 12:00:26 -0500 Subject: [PATCH 030/133] Update README.md --- README.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 8ccfe01..c6c40dc 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,7 @@ [![Issues](https://img.shields.io/github/issues/Dotsian/DexScript)](https://github.com/Dotsian/DexScript/issues) [![discord.py](https://img.shields.io/badge/discord-py-blue.svg)](https://github.com/Rapptz/discord.py) -DexScript is a set of commands created by DotZZ. The commands simplify editing, adding, and deleting models such as balls, regimes, specials, etc. - -DexScript supports both BallsDex and CarFigures. Each fork has its own respective guide on how to use DexScript with it. +DexScript is a set of commands for Ballsdex and CarFigures created by DotZZ that expands on the standalone admin commands and substitutes for the admin panel. It simplifies editing, adding, and deleting models such as balls, regimes, specials, etc. ## Installation @@ -24,27 +22,31 @@ To install DexScript, you must have the following: ### Installing -DexScript has two branches: Main and Dev. +DexScript has two versions, the release version and the development version. -The main branch contains the most stable features, while the dev branch contains unreleased features, a plenthora of bugs, and many changes. +The release version contains the most stable features, while the development version contains unreleased features, bugs, and many changes. To install DexScript, run the following eval command: -#### Main +
+Release Version ```py import base64, requests await ctx.invoke(bot.get_command("eval"), body=base64.b64decode(requests.get("https://api.github.com/repos/Dotsian/DexScript/contents/installer.py").json()["content"]).decode()) ``` +
-### Dev +
+Development Version ```py import base64, requests await ctx.invoke(bot.get_command("eval"), body=base64.b64decode(requests.get("https://api.github.com/repos/Dotsian/DexScript/contents/DexScript/github/installer.py", {"ref": "dev"}).json()["content"]).decode()) ``` +
## Updating From da3c186d47c718966e61aaf7c541bda36d788837 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Thu, 30 Jan 2025 14:59:14 -0500 Subject: [PATCH 031/133] WIP #2 --- DexScript/dexscript.py | 154 ++++++++++++++++++++++++++++++----------- README.md | 18 +++-- 2 files changed, 125 insertions(+), 47 deletions(-) diff --git a/DexScript/dexscript.py b/DexScript/dexscript.py index 91fd2db..35378a2 100644 --- a/DexScript/dexscript.py +++ b/DexScript/dexscript.py @@ -1,3 +1,4 @@ +import asyncio import base64 import inspect import logging @@ -166,6 +167,16 @@ class Utils: DexScript utility functions. """ + @staticmethod + def cleanup_code(content): + """ + Automatically removes code blocks from the code. + """ + if content.startswith("```") and content.endswith("```"): + return START_CODE_BLOCK_RE.sub("", content)[:-3] + + return content.strip("` \n") + @staticmethod def is_number(string): try: @@ -367,6 +378,84 @@ async def attributes(self, ctx, model): await ctx.send(f"```{parameters}```") + class Eval: + """ + Developer commands for executing evals. + + Documentation + ------------- + REFER TO WIKI. + """ + + def __loaded__(self): + os.makedirs("eval_presets", exist_ok=True) + + + async def exec_git(self, ctx, link): + link = link.split("/") + api = f"https://api.github.com/repos/{link[0]}/{link[1]}/contents/{link[2]}" + + request = requests.get(api) + + if request.status_code != requests.codes.ok: + raise DexScriptError(f"Request Error Code {request.status_code}") + + content = base64.b64decode(r.json()["content"]) + + try: + await ctx.invoke(bot.get_command("eval"), body=content.decode("UTF-8")) + except Exception as error: + raise DexScriptError(error) + else: + await ctx.message.add_reaction("✅") + + + async def save(self, ctx, name): + if len(name) > 25: + raise DexScriptError(f"`{name}` is above the 25 character limit.") + + if os.path.isfile(f"eval_presets/{name}.py"): + raise DexScriptError(f"`{name}` aleady exists.") + + save_content = "" + + await ctx.send("Please paste the eval command below...") + + try: + message = await bot.wait_for( + "message", timeout=15, check=lambda m: m.author == ctx.message.author + ) + except asyncio.TimeoutError: + await channel.send("Preset saving has timed out.") + return + with open(f"eval_presets/{name}.py", "w") as file: + file.write(Utils.cleanup_code(message.content)) + + await ctx.send(f"`{name}` eval preset has been saved!") + + + async def remove(self, ctx, name): + if not os.path.isfile(f"eval_presets/{name}.py"): + raise DexScriptError(f"`{name}` does not exists.") + + os.remove(f"eval_presets/{name}.py") + + await ctx.send(f"Removed `{name}` preset.") + + + async def run(self, ctx, name): # TODO: Allow args to be passed through `run`. + if not os.path.isfile(f"eval_presets/{name}.py"): + raise DexScriptError(f"`{name}` does not exists.") + + with open(f"eval_presets/{name}.py", "r") as file: + try: + await ctx.invoke(bot.get_command("eval"), body=file.read()) + except Exception as error: + raise DexScriptError(error) + else: + await ctx.message.add_reaction("✅") + + async def dev(self, ctx, operation, arg1): """ Developer commands for executing evals. @@ -611,16 +700,6 @@ class DexScript(commands.Cog): def __init__(self, bot): self.bot = bot - @staticmethod - def cleanup_code(content): - """ - Automatically removes code blocks from the code. - """ - if content.startswith("```") and content.endswith("```"): - return START_CODE_BLOCK_RE.sub("", content)[:-3] - - return content.strip("` \n") - @staticmethod def check_version(): if not config.versioncheck: @@ -657,7 +736,7 @@ async def run(self, ctx: commands.Context, *, code: str): code: str The code you want to execute. """ - body = self.cleanup_code(code) + body = Utils.cleanup_code(code) version_check = self.check_version() @@ -686,20 +765,35 @@ async def about(self, ctx: commands.Context): """ Displays information about DexScript. """ + guide_link = "https://github.com/Dotsian/DexScript/wiki/Commands" + discord_link = "https://discord.gg/EhCxuNQfzt" + + description = ( + "DexScript is a set of commands for Ballsdex and CarFigures created by DotZZ " + "that expands on the standalone admin commands and substitutes for the admin panel. " + "It simplifies editing, adding, deleting, and displaying data for models such as " + "balls, regimes, specials, etc.\n\n" + f"Refer to the official [DexScript guide](<{guide_link}>) for information " + f"about DexScript's functionality or use the `{settings.prefix}.run HELP` to display " + "a list of all commands and what they do.\n\n" + "If you want to follow DexScript or require assistance, join the official " + f"[DexScript Discord server](<{discord_link}>)." + ) + embed = discord.Embed( title="DexScript - BETA", - description=( - "DexScript is a set of commands created by DotZZ " - "that allows you to easily " - "modify, delete, and display data for models.\n\n" - "For a guide on how to use DexScript, " - "refer to the official [DexScript guide]().\n\n" - "If you want to follow DexScript, " - "join the official [DexScript Discord]() server." - ), + description=description, color=discord.Color.from_str("#03BAFC"), ) + embed.add_field( + name="Updating DexScript", + value=( + "To update DexScript, run " + f"`{settings.prefix}.run EVAL > EXEC_GIT > Dotsian/DexScript/installer.py`" + ) + ) + version_check = "OUTDATED" if self.check_version() is not None else "LATEST" embed.set_thumbnail(url="https://i.imgur.com/uKfx0qO.png") @@ -707,26 +801,6 @@ async def about(self, ctx: commands.Context): await ctx.send(embed=embed) - @commands.command(name="update-ds") - @commands.is_owner() - async def update_ds(self, ctx: commands.Context): - """ - Updates DexScript to the latest version. - """ - r = requests.get( - "https://api.github.com/repos/Dotsian/DexScript/contents/installer.py", - {"ref": config.branch}, - ) - - if r.status_code == requests.codes.ok: - content = base64.b64decode(r.json()["content"]) - await ctx.invoke(self.bot.get_command("eval"), body=content.decode("UTF-8")) - else: - await ctx.send( - "Failed to update DexScript. Report this issue to `dot_zz` on Discord.\n" - f"```ERROR CODE: {r.status_code}```" - ) - @commands.command(name="reload-ds") @commands.is_owner() async def reload_ds(self, ctx: commands.Context): diff --git a/README.md b/README.md index c6c40dc..94d81cc 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,15 @@ [![Issues](https://img.shields.io/github/issues/Dotsian/DexScript)](https://github.com/Dotsian/DexScript/issues) [![discord.py](https://img.shields.io/badge/discord-py-blue.svg)](https://github.com/Rapptz/discord.py) -DexScript is a set of commands for Ballsdex and CarFigures created by DotZZ that expands on the standalone admin commands and substitutes for the admin panel. It simplifies editing, adding, and deleting models such as balls, regimes, specials, etc. +## What is DexScript? + +DexScript is a set of commands for Ballsdex and CarFigures created by DotZZ that expands on the standalone admin commands and substitutes for the admin panel. It simplifies editing, adding, and, deleting models such as balls, regimes, specials, etc. + +Let's say you wanted to update a ball's rarity to 2. You could run `UPDATE > BALL > Kingdom of Cyprus > RARITY > 2.0`. + +_**INSERT SCREENSHOT HERE**_ + +See how simple that is? Using DexScript is way more efficient than eval commands and the admin panel. ## Installation @@ -28,25 +36,21 @@ The release version contains the most stable features, while the development ver To install DexScript, run the following eval command: -
-Release Version +#### Release Version ```py import base64, requests await ctx.invoke(bot.get_command("eval"), body=base64.b64decode(requests.get("https://api.github.com/repos/Dotsian/DexScript/contents/installer.py").json()["content"]).decode()) ``` -
-
-Development Version +#### Development Version ```py import base64, requests await ctx.invoke(bot.get_command("eval"), body=base64.b64decode(requests.get("https://api.github.com/repos/Dotsian/DexScript/contents/DexScript/github/installer.py", {"ref": "dev"}).json()["content"]).decode()) ``` -
## Updating From 10a31bc548247f90e93fbce4e39b661c27cc90b4 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Thu, 30 Jan 2025 23:59:42 -0500 Subject: [PATCH 032/133] Add DexScript promo art --- README.md | 7 ++----- assets/DexScriptPromo.png | Bin 0 -> 72609 bytes 2 files changed, 2 insertions(+), 5 deletions(-) create mode 100644 assets/DexScriptPromo.png diff --git a/README.md b/README.md index 94d81cc..02a15b4 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,7 @@ - -
-DexScript logo -
- # DexScript - BETA +![DexScript Banner](assets/DexScriptPromo.png) + [![Ruff](https://github.com/Dotsian/DexScript/actions/workflows/ruff.yml/badge.svg)](https://github.com/Dotsian/DexScript/actions/workflows/ruff.yml) [![Issues](https://img.shields.io/github/issues/Dotsian/DexScript)](https://github.com/Dotsian/DexScript/issues) [![discord.py](https://img.shields.io/badge/discord-py-blue.svg)](https://github.com/Rapptz/discord.py) diff --git a/assets/DexScriptPromo.png b/assets/DexScriptPromo.png new file mode 100644 index 0000000000000000000000000000000000000000..6d15e70f4f15d0aa26d0e3a36440cb04ffaa7b4e GIT binary patch literal 72609 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 Date: Tue, 4 Feb 2025 17:41:35 -0500 Subject: [PATCH 092/133] Fix path --- DexScript/package/utils.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/DexScript/package/utils.py b/DexScript/package/utils.py index cb6632b..730805e 100644 --- a/DexScript/package/utils.py +++ b/DexScript/package/utils.py @@ -182,10 +182,7 @@ def remove_code_markdown(content) -> str: @staticmethod def image_path(path) -> bool: - if path.startswith("/static/uploads/"): - path.replace("/static/uploads/", "") - - return f"{MEDIA_PATH}/{path}" + return f"{MEDIA_PATH}/{path.replace("/static/uploads/", "")}" @staticmethod def is_image(path) -> bool: From e9e2b168d5e59ded730bf0502ac6af0ec29a9bbb Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 18:44:30 -0500 Subject: [PATCH 093/133] Fix checks --- DexScript/package/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 404c87e..227bc01 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -6,7 +6,7 @@ import discord import requests -from .utils import DIR, MEDIA_PATH, Types, Utils +from .utils import DIR, Types, Utils class DexCommand: From 0f3eecc8e03153124ec35054d0da612efb7b75f5 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 18:46:57 -0500 Subject: [PATCH 094/133] Fix path (again) --- DexScript/package/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DexScript/package/utils.py b/DexScript/package/utils.py index 730805e..6ce13de 100644 --- a/DexScript/package/utils.py +++ b/DexScript/package/utils.py @@ -165,7 +165,7 @@ async def save_file(attachment: discord.Attachment) -> Path: if MEDIA_PATH == "./admin_panel/media": return path.relative_to("./admin_panel/media/") - return path.relative_to("/static/uploads") + return path.relative_to("static/uploads") @staticmethod def extract_str_attr(object): From 3948fbb3d154d7d473c06a74f224c40dd9b7c08c Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 19:59:57 -0500 Subject: [PATCH 095/133] Fix path #3 --- DexScript/package/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DexScript/package/utils.py b/DexScript/package/utils.py index 6ce13de..c3296b1 100644 --- a/DexScript/package/utils.py +++ b/DexScript/package/utils.py @@ -182,7 +182,7 @@ def remove_code_markdown(content) -> str: @staticmethod def image_path(path) -> bool: - return f"{MEDIA_PATH}/{path.replace("/static/uploads/", "")}" + return f"{MEDIA_PATH}/{path.replace('static/uploads/', '')}" @staticmethod def is_image(path) -> bool: From 19e60ac7d7f4ef694b684456a48cfa7f97fe2357 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 20:09:50 -0500 Subject: [PATCH 096/133] Final path fix #4 --- DexScript/package/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 227bc01..71ec4a5 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -134,7 +134,7 @@ async def update(self, ctx, model, identifier, attribute, value=None): if value is None: image_path = await Utils.save_file(ctx.message.attachments[0]) - new_value = Utils.image_path(image_path) + new_value = Utils.image_path(str(image_path.absolute())) if attribute.type == Types.MODEL: new_value = await self.get_model(attribute, new_value) From fa2d72e4070ab208e2185aff91500599ced70f7c Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 20:11:47 -0500 Subject: [PATCH 097/133] bruh --- DexScript/package/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 71ec4a5..76b8850 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -134,7 +134,7 @@ async def update(self, ctx, model, identifier, attribute, value=None): if value is None: image_path = await Utils.save_file(ctx.message.attachments[0]) - new_value = Utils.image_path(str(image_path.absolute())) + new_value = Utils.image_path(f"{image_path.parent}/{image_path.name}") if attribute.type == Types.MODEL: new_value = await self.get_model(attribute, new_value) From aeb7f05579984d5e04e4c9b26649ab1ab1dfb5ac Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 20:13:38 -0500 Subject: [PATCH 098/133] Remove prefix --- DexScript/package/commands.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 76b8850..7578132 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -134,7 +134,9 @@ async def update(self, ctx, model, identifier, attribute, value=None): if value is None: image_path = await Utils.save_file(ctx.message.attachments[0]) - new_value = Utils.image_path(f"{image_path.parent}/{image_path.name}") + new_value = Utils.image_path( + f"{image_path.parent}/{image_path.name.replace('./', '')}" + ) if attribute.type == Types.MODEL: new_value = await self.get_model(attribute, new_value) From 46b93e0be419bf51a027a3c09717a8539ed2afd8 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 20:35:45 -0500 Subject: [PATCH 099/133] Remove `exec_git`, change eval timeout to 20, change name character limit to 100, and FIX. THE. PATH. --- DexScript/package/commands.py | 44 ++++++----------------------------- DexScript/package/utils.py | 7 ++---- 2 files changed, 9 insertions(+), 42 deletions(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 7578132..81e993d 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -134,9 +134,7 @@ async def update(self, ctx, model, identifier, attribute, value=None): if value is None: image_path = await Utils.save_file(ctx.message.attachments[0]) - new_value = Utils.image_path( - f"{image_path.parent}/{image_path.name.replace('./', '')}" - ) + new_value = Utils.image_path(str(image_path)) if attribute.type == Types.MODEL: new_value = await self.get_model(attribute, new_value) @@ -292,42 +290,12 @@ async def delete(self, ctx, model, attribute, value, tortoise_operator=None): class Eval(DexCommand): """ - Commands for executing evals and managing eval presets. + Commands for managing eval presets. """ def __loaded__(self): os.makedirs("eval_presets", exist_ok=True) - async def exec_git(self, ctx, link): - """ - Executes an eval command based on a GitHub file link. - - Documentation - ------------- - EVAL > EXEC_GIT > LINK - """ - link = link.name.split("/") - start = f"{link[0]}/{link[1]}" - - link.pop(0) - link.pop(0) - - api = f"https://api.github.com/repos/{start}/contents/{'/'.join(link)}" - - request = requests.get(api) - - if request.status_code != requests.codes.ok: - raise Exception(f"Request Error Code {request.status_code}") - - content = b64decode(request.json()["content"]) - - try: - await ctx.invoke(self.bot.get_command("eval"), body=content.decode("UTF-8")) - except Exception as error: - raise Exception(error) - else: - await ctx.message.add_reaction("✅") - async def save(self, ctx, name): """ Saves an eval preset. @@ -336,8 +304,10 @@ async def save(self, ctx, name): ------------- EVAL > SAVE > NAME """ - if len(name.name) > 25: - raise Exception(f"`{name}` is above the 25 character limit ({len(name)} > 25)") + NAME_LIMIT = 100 + + if len(name.name) > NAME_LIMIT: + raise Exception(f"`{name}` is above the {NAME_LIMIT} character limit ({len(name)} > {NAME_LIMIT})") if os.path.isfile(f"eval_presets/{name}.py"): raise Exception(f"`{name}` aleady exists.") @@ -346,7 +316,7 @@ async def save(self, ctx, name): try: message = await self.bot.wait_for( - "message", timeout=15, check=lambda m: m.author == ctx.message.author + "message", timeout=20, check=lambda m: m.author == ctx.message.author ) except asyncio.TimeoutError: await ctx.send("Preset saving has timed out.") diff --git a/DexScript/package/utils.py b/DexScript/package/utils.py index c3296b1..eef87ea 100644 --- a/DexScript/package/utils.py +++ b/DexScript/package/utils.py @@ -162,10 +162,7 @@ async def save_file(attachment: discord.Attachment) -> Path: await attachment.save(path) - if MEDIA_PATH == "./admin_panel/media": - return path.relative_to("./admin_panel/media/") - - return path.relative_to("static/uploads") + return path.relative_to(MEDIA_PATH) @staticmethod def extract_str_attr(object): @@ -182,7 +179,7 @@ def remove_code_markdown(content) -> str: @staticmethod def image_path(path) -> bool: - return f"{MEDIA_PATH}/{path.replace('static/uploads/', '')}" + return f"{MEDIA_PATH}/{path}" @staticmethod def is_image(path) -> bool: From 2702779cfea4d81d8d396b6ce2aeaec1ba9101a7 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 20:47:03 -0500 Subject: [PATCH 100/133] Fix readme grammar and P.A.T.H... --- DexScript/package/utils.py | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DexScript/package/utils.py b/DexScript/package/utils.py index eef87ea..8974e6d 100644 --- a/DexScript/package/utils.py +++ b/DexScript/package/utils.py @@ -179,7 +179,7 @@ def remove_code_markdown(content) -> str: @staticmethod def image_path(path) -> bool: - return f"{MEDIA_PATH}/{path}" + return f"{MEDIA_PATH}/{path.replace('/static/uploads/', '')}" @staticmethod def is_image(path) -> bool: diff --git a/README.md b/README.md index 336a161..bc09abe 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,6 @@ If you are installing DexScript for the first time, you will see a button called If you already have DexScript, you will see a button called "Update". When you click that button, DexScript will update to the latest version. This will instantly update DexScript, which means you don't have to restart your bot. -#### Uninstall +#### Uninstalling If you already have DexScript, you will see a button called "Uninstall". Clicking the uninstall button will uninstall DexScript from your application. This will instantly remove the DexScript package and DexScript commands will unload instantly. From 8537045566ee649d551cf030acea65f78b0e7592 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 20:50:54 -0500 Subject: [PATCH 101/133] Fix checks --- DexScript/package/commands.py | 11 ++++++----- DexScript/package/utils.py | 7 ++++++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 81e993d..63798d6 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -1,10 +1,8 @@ import asyncio import os import shutil -from base64 import b64decode import discord -import requests from .utils import DIR, Types, Utils @@ -307,19 +305,22 @@ async def save(self, ctx, name): NAME_LIMIT = 100 if len(name.name) > NAME_LIMIT: - raise Exception(f"`{name}` is above the {NAME_LIMIT} character limit ({len(name)} > {NAME_LIMIT})") + raise Exception( + f"`{name}` is above the {NAME_LIMIT} character limit " + f"({len(name)} > {NAME_LIMIT})" + ) if os.path.isfile(f"eval_presets/{name}.py"): raise Exception(f"`{name}` aleady exists.") - await ctx.send("Please paste the eval command below...") + await ctx.send("Please send the eval command below...") try: message = await self.bot.wait_for( "message", timeout=20, check=lambda m: m.author == ctx.message.author ) except asyncio.TimeoutError: - await ctx.send("Preset saving has timed out.") + await ctx.send("Eval preset saving has timed out.") return with open(f"eval_presets/{name}.py", "w") as file: file.write(Utils.remove_code_markdown(message.content)) diff --git a/DexScript/package/utils.py b/DexScript/package/utils.py index 8974e6d..b998813 100644 --- a/DexScript/package/utils.py +++ b/DexScript/package/utils.py @@ -179,7 +179,12 @@ def remove_code_markdown(content) -> str: @staticmethod def image_path(path) -> bool: - return f"{MEDIA_PATH}/{path.replace('/static/uploads/', '')}" + full_path = path.replace('/static/uploads/', '') + + if MEDIA_PATH == "./static/uploads" and full_path[1] == ".": + full_path = full_path[1:] + + return f"{MEDIA_PATH}/{full_path}" @staticmethod def is_image(path) -> bool: From 970bc939e35f7e6db149ca55c2041b4fabc47a6b Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 20:52:23 -0500 Subject: [PATCH 102/133] Fix index --- DexScript/package/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DexScript/package/utils.py b/DexScript/package/utils.py index b998813..4caa5e9 100644 --- a/DexScript/package/utils.py +++ b/DexScript/package/utils.py @@ -181,7 +181,7 @@ def remove_code_markdown(content) -> str: def image_path(path) -> bool: full_path = path.replace('/static/uploads/', '') - if MEDIA_PATH == "./static/uploads" and full_path[1] == ".": + if MEDIA_PATH == "./static/uploads" and full_path[0] == ".": full_path = full_path[1:] return f"{MEDIA_PATH}/{full_path}" From 74164094b9f7feefc7cd87e68b3bdac849ef1f68 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 23:47:32 -0500 Subject: [PATCH 103/133] Use full value name --- DexScript/package/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 63798d6..c5663c5 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -135,7 +135,7 @@ async def update(self, ctx, model, identifier, attribute, value=None): new_value = Utils.image_path(str(image_path)) if attribute.type == Types.MODEL: - new_value = await self.get_model(attribute, new_value) + new_value = await self.get_model(attribute, value.name) setattr(returned_model, attribute_name, new_value) From 6a0c47b6e32193050005c3e87808686c71789a08 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 23:49:03 -0500 Subject: [PATCH 104/133] Fix class name --- DexScript/package/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index c5663c5..5a88de7 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -122,7 +122,7 @@ async def update(self, ctx, model, identifier, attribute, value=None): """ _attr_name = attribute.name.__name__ if attribute.type == Types.MODEL else attribute.name - attribute_name = Utils.casing(_attr_name.lower()) + attribute_name = Utils.casing(_attr_name.lower(), attribute.type == Types.MODEL) new_value = Utils.casing(value.name) if value is not None else None returned_model = await self.get_model(model, identifier.name) From 58464dadbe35e28498d52621eff8c39d15b1a1f2 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 23:52:41 -0500 Subject: [PATCH 105/133] Switch to PK methods --- DexScript/package/commands.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 5a88de7..f84e7c2 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -122,7 +122,7 @@ async def update(self, ctx, model, identifier, attribute, value=None): """ _attr_name = attribute.name.__name__ if attribute.type == Types.MODEL else attribute.name - attribute_name = Utils.casing(_attr_name.lower(), attribute.type == Types.MODEL) + attribute_name = Utils.casing(_attr_name.lower()) new_value = Utils.casing(value.name) if value is not None else None returned_model = await self.get_model(model, identifier.name) @@ -135,7 +135,8 @@ async def update(self, ctx, model, identifier, attribute, value=None): new_value = Utils.image_path(str(image_path)) if attribute.type == Types.MODEL: - new_value = await self.get_model(attribute, value.name) + attribute_name = Utils.to_snake_case(_attr_name.lower()) + "_id" + new_value = await self.get_model(attribute, value.name).pk setattr(returned_model, attribute_name, new_value) From c7dfd73e8ae4388e1e093298d4294b6c4bb57d3e Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 23:53:13 -0500 Subject: [PATCH 106/133] Fix error --- DexScript/package/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index f84e7c2..7847785 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -136,7 +136,7 @@ async def update(self, ctx, model, identifier, attribute, value=None): if attribute.type == Types.MODEL: attribute_name = Utils.to_snake_case(_attr_name.lower()) + "_id" - new_value = await self.get_model(attribute, value.name).pk + new_value = (await self.get_model(attribute, value.name)).pk setattr(returned_model, attribute_name, new_value) From de3406da4b33b9ef629ba4a24488f488f00a7a34 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 23:55:25 -0500 Subject: [PATCH 107/133] Fix naming --- DexScript/package/commands.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 7847785..81ac4e2 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -134,15 +134,20 @@ async def update(self, ctx, model, identifier, attribute, value=None): image_path = await Utils.save_file(ctx.message.attachments[0]) new_value = Utils.image_path(str(image_path)) + text = f"Updated `{identifier}'s` {attribute} to `{new_value}`" + if attribute.type == Types.MODEL: attribute_name = Utils.to_snake_case(_attr_name.lower()) + "_id" - new_value = (await self.get_model(attribute, value.name)).pk + + attribute_model = await self.get_model(attribute, value.name) + new_value = attribute_model.pk + + text = f"Updated `{identifier}'s` {attribute_model.__name__} to `{value.name}`" setattr(returned_model, attribute_name, new_value) await returned_model.save(update_fields=[attribute_name]) - - await ctx.send(f"Updated `{identifier}'s` {attribute} to `{new_value}`") + await ctx.send(text) async def view(self, ctx, model, identifier, attribute=None): """ From 433650ae1604f445c3a8373174430cd960c9d9a9 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 23:56:58 -0500 Subject: [PATCH 108/133] Fully fix name --- DexScript/package/commands.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 81ac4e2..c415972 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -138,11 +138,11 @@ async def update(self, ctx, model, identifier, attribute, value=None): if attribute.type == Types.MODEL: attribute_name = Utils.to_snake_case(_attr_name.lower()) + "_id" - + attribute_model = await self.get_model(attribute, value.name) new_value = attribute_model.pk - text = f"Updated `{identifier}'s` {attribute_model.__name__} to `{value.name}`" + text = f"Updated `{identifier}'s` {attribute_model.__class__.__name__} to `{value.name}`" setattr(returned_model, attribute_name, new_value) From 8658a60edb6b9266e7e1f4e51bf6831270f58a94 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Wed, 5 Feb 2025 00:04:23 -0500 Subject: [PATCH 109/133] Fix ruff checks (testing phase begins tomorrow) --- DexScript/package/commands.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index c415972..3025d06 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -142,7 +142,10 @@ async def update(self, ctx, model, identifier, attribute, value=None): attribute_model = await self.get_model(attribute, value.name) new_value = attribute_model.pk - text = f"Updated `{identifier}'s` {attribute_model.__class__.__name__} to `{value.name}`" + text = ( + f"Updated `{identifier}'s` {attribute_model.__class__.__name__} " + f"to `{value.name}`" + ) setattr(returned_model, attribute_name, new_value) From 5eacf87bb4fd97c7e15f42b703a59b6374cece74 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Thu, 6 Feb 2025 14:58:26 -0500 Subject: [PATCH 110/133] Add shared variables, add `FILTER > VIEW` command, fix message length error, move functions to utils, add `value` to `Value` class, etc. [WIP] --- DexScript/package/commands.py | 161 +++++++++----------- DexScript/package/parser.py | 15 +- DexScript/package/utils.py | 278 +++++++++++++++++++++++++++------- 3 files changed, 306 insertions(+), 148 deletions(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 3025d06..61df6bb 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -1,88 +1,42 @@ import asyncio import os import shutil +from dataclasses import dataclass +from dataclasses import field as datafield import discord from .utils import DIR, Types, Utils +@dataclass +class Shared: + """ + Values that will be retained throughout the entire code execution. + """ + + attachments: list = datafield(default_factory=list) + + class DexCommand: - def __init__(self, bot): + """ + Default class for all dex commands. + """ + + def __init__(self, bot, shared): self.bot = bot + self.shared = shared def __loaded__(self): pass def attribute_error(self, model, attribute): raise Exception( - f"'{attribute}' is not a valid {model.name.__name__} attribute\n" - f"Run `ATTRIBUTES > {model.name.__name__}` to see a list of " + f"'{attribute}' is not a valid {model.name} attribute\n" + f"Run `ATTRIBUTES > {model.name}` to see a list of " "all attributes for that model" ) - async def create_model(self, model, identifier, fields_only=False): - fields = {} - - special_list = { - "Identifiers": ["country", "catch_names", "name"], - "Ignore": ["id", "short_name"] - } - - if DIR == "carfigures": - special_list = { - "Identifiers": ["fullName", "catchNames", "name"], - "Ignore": ["id", "shortName"] - } - - for field, field_type in model._meta.fields_map.items(): - if field_type.null or field in special_list["Ignore"]: - continue - - if field in special_list["Identifiers"]: - fields[field] = identifier - continue - - match field_type.__class__.__name__: - case "ForeignKeyFieldInstance": - if field == "cartype": - field = "car_type" - - casing_field = Utils.casing(field, True) - - instance = await Utils.fetch_model(casing_field).first() - - if instance is None: - raise Exception(f"Could not find default {casing_field}") - - fields[field] = instance.pk - - case "BigIntField": - fields[field] = 100**8 - - case "BackwardFKRelation" | "JSONField": - continue - - case _: - fields[field] = 1 - - if fields_only: - return fields - - await model.create(**fields) - - async def get_model(self, model, identifier): - correction_list = await model.name.all().values_list(model.extra_data[0], flat=True) - - try: - returned_model = await model.name.filter( - **{model.extra_data[0]: Utils.autocorrect(identifier, correction_list)} - ) - except AttributeError: - raise Exception(f"'{model}' is not a valid model.") - - return returned_model[0] - class Global(DexCommand): """ @@ -97,7 +51,7 @@ async def create(self, ctx, model, identifier): ------------- CREATE > MODEL > IDENTIFIER """ - await self.create_model(model.name, identifier.name) + await Utils.create_model(model, identifier) await ctx.send(f"Created `{identifier}`") async def delete(self, ctx, model, identifier): @@ -108,7 +62,7 @@ async def delete(self, ctx, model, identifier): ------------- DELETE > MODEL > IDENTIFIER """ - await self.get_model(model, identifier.name).delete() + await Utils.get_model(model, identifier).delete() await ctx.send(f"Deleted `{identifier}`") async def update(self, ctx, model, identifier, attribute, value=None): @@ -120,18 +74,18 @@ async def update(self, ctx, model, identifier, attribute, value=None): ------------- UPDATE > MODEL > IDENTIFIER > ATTRIBUTE > VALUE(?) """ - _attr_name = attribute.name.__name__ if attribute.type == Types.MODEL else attribute.name - - attribute_name = Utils.casing(_attr_name.lower()) + attribute_name = Utils.casing(attribute.name.lower()) new_value = Utils.casing(value.name) if value is not None else None - returned_model = await self.get_model(model, identifier.name) + returned_model = await Utils.get_model(model, identifier) - if not hasattr(model.name(), attribute_name): + if model.value is not None and not hasattr(model.value(), attribute_name): self.attribute_error(model, attribute_name) - if value is None: - image_path = await Utils.save_file(ctx.message.attachments[0]) + model_field = Utils.get_field(model.value, attribute_name) + + if value is None and self.shared.attachments != [] and model_field.: + image_path = await Utils.save_file(self.shared.attachments[0]) new_value = Utils.image_path(str(image_path)) text = f"Updated `{identifier}'s` {attribute} to `{new_value}`" @@ -139,7 +93,7 @@ async def update(self, ctx, model, identifier, attribute, value=None): if attribute.type == Types.MODEL: attribute_name = Utils.to_snake_case(_attr_name.lower()) + "_id" - attribute_model = await self.get_model(attribute, value.name) + attribute_model = await Utils.get_model(attribute, value.name) new_value = attribute_model.pk text = ( @@ -161,7 +115,7 @@ async def view(self, ctx, model, identifier, attribute=None): ------------- VIEW > MODEL > IDENTIFIER > ATTRIBUTE(?) """ - returned_model = await self.get_model(model, identifier.name) + returned_model = await Utils.get_model(model, identifier.name) if attribute is None: fields = {"content": "```"} @@ -209,17 +163,15 @@ async def attributes(self, ctx, model): ------------- ATTRIBUTES > MODEL """ - model_name = model.name if isinstance(model.name, str) else model.name.__name__ + fields = [f"{model.name.upper()} ATTRIBUTES:\n\n"] - parameters = f"{model_name.upper()} ATTRIBUTES:\n\n" - - for field in vars(model.name()): # type: ignore + for field in vars(model.value()): # type: ignore if field[:1] == "_": continue - parameters += f"- {Utils.to_snake_case(field).replace(' ', '_').upper()}\n" + fields.append(f"- {Utils.to_snake_case(field).replace(' ', '_').upper()}\n") - await ctx.send(f"```{parameters}```") + await Utils.message_list(ctx, fields) class Filter(DexCommand): @@ -250,8 +202,8 @@ async def update(self, ctx, model, attribute, old_value, new_value, tortoise_ope value_new = new_value.name if attribute.type == Types.MODEL: - value_old = await self.get_model(attribute, value_old) - value_new = await self.get_model(attribute, value_new) + value_old = await Utils.get_model(attribute, value_old) + value_new = await Utils.get_model(attribute, value_new) await model.name.filter(**{casing_name: value_old}).update( **{casing_name: value_new} @@ -285,7 +237,7 @@ async def delete(self, ctx, model, attribute, value, tortoise_operator=None): new_value = value.name if attribute.type == Types.MODEL: - new_value = await self.get_model(attribute, new_value) + new_value = await Utils.get_model(attribute, new_value) await model.name.filter(**{casing_name: new_value}).delete() @@ -294,6 +246,37 @@ async def delete(self, ctx, model, attribute, value, tortoise_operator=None): f"`{attribute}` value of `{value}`" ) + async def view(self, ctx, model, attribute, value, tortoise_operator=None): + """ + Displays all instances of a model where the specified attribute meets the condition + defined by the optional `TORTOISE_OPERATOR` argument + (e.g., greater than, equal to, etc.). + + Documentation + ------------- + FILTER > VIEW > MODEL > ATTRIBUTE > VALUE > TORTOISE_OPERATOR(?) + """ + _attr_name = attribute.name.__name__ if attribute.type == Types.MODEL else attribute.name + + casing_name = Utils.casing(_attr_name.lower()) + + if not hasattr(model.name(), casing_name): + self.attribute_error(model, casing_name) + + if tortoise_operator is not None: + casing_name += f"__{tortoise_operator.name.lower()}" + + new_value = value.name + + if attribute.type == Types.MODEL: + new_value = await Utils.get_model(attribute, new_value) + + instances = await model.name.filter(**{casing_name: new_value}).values_list( + model.extra_data[0], flat=True + ) + + await Utils.message_list(ctx, instances) + class Eval(DexCommand): """ @@ -326,7 +309,7 @@ async def save(self, ctx, name): try: message = await self.bot.wait_for( - "message", timeout=20, check=lambda m: m.author == ctx.message.author + "message", check=lambda m: m.author == ctx.message.author, timeout=20 ) except asyncio.TimeoutError: await ctx.send("Eval preset saving has timed out.") @@ -355,8 +338,8 @@ async def list(self, ctx): if os.listdir("eval_presets") == []: await ctx.send("You have no eval presets saved.") return - - await ctx.send(f"```{'\n'.join(os.listdir("eval_presets"))}```") + + await Utils.message_list(ctx, os.listdir("eval_presets")) async def run(self, ctx, name): # TODO: Allow args to be passed through `run`. """ @@ -435,7 +418,7 @@ async def listdir(self, ctx, file_path=None): """ path = file_path.name if file_path is not None else None - await ctx.send(f"```{'\n'.join(os.listdir(path))}```") + await Utils.message_list(ctx, os.listdir(path)) async def delete(self, ctx, file_path): """ diff --git a/DexScript/package/parser.py b/DexScript/package/parser.py index b82902c..294e3de 100644 --- a/DexScript/package/parser.py +++ b/DexScript/package/parser.py @@ -13,8 +13,10 @@ @dataclass class Value: - name: Any + name: str type: Types = Types.DEFAULT + value: Any = None + extra_data: list = datafield(default_factory=list) def __str__(self): @@ -79,14 +81,15 @@ def create_value(self, line): string_key = Utils.extract_str_attr(model) - value.name = model + value.name = model.__name__ + value.value = model value.extra_data.append(string_key) case Types.BOOLEAN: - value.name = lower == "true" + value.value = lower == "true" case Types.DATETIME: - value.name = parse_date(value.name) + value.value = parse_date(line) return value @@ -94,6 +97,8 @@ def error(self, message, log): return (message, log)[config.debug] async def execute(self, code: str, run_commands=True): + shared_instance = commands.Shared(self.ctx.message.attachments) + split_code = [x for x in code.split("\n") if x.strip() != ""] parsed_code = [ @@ -125,7 +130,7 @@ async def execute(self, code: str, run_commands=True): line2.pop(0) class_loaded = commands.Global if method[0] == commands.Global else method[0] - class_loaded = class_loaded(self.bot) + class_loaded = class_loaded(self.bot, shared_instance) class_loaded.__loaded__() method_call = getattr(class_loaded, method[1].name.lower()) diff --git a/DexScript/package/utils.py b/DexScript/package/utils.py index 4caa5e9..eeba5e6 100644 --- a/DexScript/package/utils.py +++ b/DexScript/package/utils.py @@ -1,9 +1,11 @@ +import contextlib import inspect import os import re from dataclasses import dataclass from difflib import get_close_matches from enum import Enum +from io import StringIO from pathlib import Path import discord @@ -51,45 +53,33 @@ class Utils: """ @staticmethod - def fetch_model(model): - return globals().get(model) + def image_path(path) -> bool: + full_path = path.replace('/static/uploads/', '') - @staticmethod - def models(names=False, key=None): - model_list = { - "ballsdex": [ - "Ball", - "Regime", - "Economy", - "Special", - ], - "carfigures": [ - "Car", - "CarType", - "Country", - "Event", - "FontsPack", - "Exclusive", - ], - } + if MEDIA_PATH == "./static/uploads" and full_path[0] == ".": + full_path = full_path[1:] - return_list = model_list[DIR] + return f"{MEDIA_PATH}/{full_path}" - if not names: - return_list = [globals().get(x) for x in return_list if globals().get(x) is not None] + @staticmethod + def is_image(path) -> bool: + return os.path.isfile(Utils.image_path(path)) - if key is not None: - return_list = [key(x) for x in return_list] + @staticmethod + def is_date(string) -> bool: + try: + parse_date(string) + return True + except Exception: + return False - return return_list - @staticmethod def _common_format(string_or_list: str | list[str], func): if isinstance(string_or_list, str): return func(string_or_list) return [func(x) for x in string_or_list] - + @staticmethod def to_camel_case(item): """ @@ -136,15 +126,71 @@ def casing(item, pascal=False): return main_casing @staticmethod - def autocorrect(string, correction_list, error="does not exist."): - autocorrection = get_close_matches(string, correction_list) + async def message_list(ctx, messages: list[str]): + """ + Creates an interactive message limit that allows you to display a list of messages + without suprassing the Discord message character limit. + + Parameters + ---------- + ctx: discord.Context + The context object that will be used. + messages: list[str] + The list of messages you want to add to the interaction. + """ + pages = [[]] + page_length = 0 - if not autocorrection or autocorrection[0] != string: - suggestion = f"\nDid you mean '{autocorrection[0]}'?" if autocorrection else "" + def check(message): + return ( + message.author.id == ctx.message.author.id and + message.content.lower() in ("more", "file") + ) + + for message in messages: + if page_length + len(messages) >= 750: + page_length = 0 + pages.append([]) - raise Exception(f"'{string}' {error}{suggestion}") + page_length += len(message) + pages[-1].append(message) - return autocorrection[0] + for index, page in enumerate(pages, start=1): + await ctx.send(f"```\n{'\n'.join(page)}\n```") + + if index == len(pages): + break + + remaining = len(pages) - index + + text = f"There are `{remaining}` pages remaining." + + if remaining == 1: + text = "There is `1` page remaining." + + message = await ctx.send( + f"{text} Type `more` to continue or `file` to send all messages in a file" + ) + + try: + response = await ctx.bot.wait_for("message", check=check, timeout=15) + except asyncio.TimeoutError: + with contextlib.suppress(discord.HTTPException): + await message.delete() + + break + + with contextlib.suppress(discord.HTTPException): + await ctx.channel.delete_messages((message, response)) + + if response.content.lower() == "more": + continue + + await ctx.send( + file=discord.File(StringIO("\n".join(messages)), filename="output.txt") + ) + + break @staticmethod async def save_file(attachment: discord.Attachment) -> Path: @@ -165,35 +211,159 @@ async def save_file(attachment: discord.Attachment) -> Path: return path.relative_to(MEDIA_PATH) @staticmethod - def extract_str_attr(object): - expression = r"return\s+self\.(\w+)" + def fetch_model(model): + return globals().get(model) - return re.search(expression, inspect.getsource(object.__str__)).group(1) + @staticmethod + def models(names=False, key=None): + model_list = { + "ballsdex": [ + "Ball", + "Regime", + "Economy", + "Special", + ], + "carfigures": [ + "Car", + "CarType", + "Country", + "Event", + "FontsPack", + "Exclusive", + ], + } + + return_list = model_list[DIR] + + if not names: + return_list = [globals().get(x) for x in return_list if globals().get(x) is not None] + + if key is not None: + return_list = [key(x) for x in return_list] + + return return_list @staticmethod - def remove_code_markdown(content) -> str: - if content.startswith("```") and content.endswith("```"): - return START_CODE_BLOCK_RE.sub("", content)[:-3] + async def create_model(model, identifier, fields_only=False): + """ + Creates a model instance while providing default values for all. + + Parameters + ---------- + model: Model + The tortoise model you want to use. + identifier: str + The name of the model instance. + fields_only: bool + Whether you want to return the fields created only or not (debugging). + """ + fields = {} - return content.strip("` \n") + special_list = { + "Identifiers": ["country", "catch_names", "name"], + "Ignore": ["id", "short_name"] + } + + if DIR == "carfigures": + special_list = { + "Identifiers": ["fullName", "catchNames", "name"], + "Ignore": ["id", "shortName"] + } + + for field, field_type in model._meta.fields_map.items(): + if field_type.null or field in special_list["Ignore"]: + continue + + if field in special_list["Identifiers"]: + fields[field] = identifier + continue + + match field_type.__class__.__name__: + case "ForeignKeyFieldInstance": + if field == "cartype": + field = "car_type" + + casing_field = Utils.casing(field, True) + + instance = await Utils.fetch_model(casing_field).first() + + if instance is None: + raise Exception(f"Could not find default {casing_field}") + + fields[field] = instance.pk + + case "BigIntField": + fields[field] = 100**8 + + case "BackwardFKRelation" | "JSONField": + continue + + case _: + fields[field] = 1 + if fields_only: + return fields + + await model.create(**fields) + @staticmethod - def image_path(path) -> bool: - full_path = path.replace('/static/uploads/', '') + async def get_model(model, identifier): + """ + Returns a model instance, providing autocorrection. + + Parameters + ---------- + model: Value + The model you want to use. + identifier: str + The identifier of the model instance you are trying to return. + """ + correction_list = await model.value.all().values_list(model.extra_data[0], flat=True) - if MEDIA_PATH == "./static/uploads" and full_path[0] == ".": - full_path = full_path[1:] + try: + returned_model = await model.value.filter( + **{model.extra_data[0]: Utils.autocorrect(str(identifier), correction_list)} + ) + except AttributeError: + raise Exception(f"'{model}' is not a valid model.") - return f"{MEDIA_PATH}/{full_path}" + return returned_model[0] @staticmethod - def is_image(path) -> bool: - return os.path.isfile(Utils.image_path(path)) + def get_field(model, field): + """ + Returns a field from a model. + + Parameters + ---------- + model: Model + The tortoise model you want to use. + field: str + The field you want to fetch. + """ + return model._meta.fields_map.get(field) @staticmethod - def is_date(string) -> bool: - try: - parse_date(string) - return True - except Exception: - return False + def autocorrect(string, correction_list, error="does not exist."): + autocorrection = get_close_matches(string, correction_list) + + if not autocorrection or autocorrection[0] != string: + suggestion = f"\nDid you mean '{autocorrection[0]}'?" if autocorrection else "" + + raise Exception(f"'{string}' {error}{suggestion}") + + return autocorrection[0] + + @staticmethod + def extract_str_attr(object): + expression = r"return\s+self\.(\w+)" # TODO: Add `return str()` + + return re.search(expression, inspect.getsource(object.__str__)).group(1) + + @staticmethod + def remove_code_markdown(content) -> str: + if content.startswith("```") and content.endswith("```"): + return START_CODE_BLOCK_RE.sub("", content)[:-3] + + return content.strip("` \n") + From a253ea0a6e7beba6f5bba9d2153e28401dfff20c Mon Sep 17 00:00:00 2001 From: Dotsian Date: Fri, 7 Feb 2025 00:09:57 -0500 Subject: [PATCH 111/133] WIP 2 --- DexScript/package/commands.py | 119 +++++++++++++++------------------- DexScript/package/parser.py | 6 +- DexScript/package/utils.py | 15 ++++- 3 files changed, 70 insertions(+), 70 deletions(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 61df6bb..e5a1128 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -31,6 +31,9 @@ def __loaded__(self): pass def attribute_error(self, model, attribute): + if model.value is None or hasattr(model.value(), attribute): + return + raise Exception( f"'{attribute}' is not a valid {model.name} attribute\n" f"Run `ATTRIBUTES > {model.name}` to see a list of " @@ -51,7 +54,7 @@ async def create(self, ctx, model, identifier): ------------- CREATE > MODEL > IDENTIFIER """ - await Utils.create_model(model, identifier) + await Utils.create_model(model.value, identifier) await ctx.send(f"Created `{identifier}`") async def delete(self, ctx, model, identifier): @@ -74,37 +77,37 @@ async def update(self, ctx, model, identifier, attribute, value=None): ------------- UPDATE > MODEL > IDENTIFIER > ATTRIBUTE > VALUE(?) """ - attribute_name = Utils.casing(attribute.name.lower()) - new_value = Utils.casing(value.name) if value is not None else None + attribute_name = attribute.case.lower() + new_value = None - returned_model = await Utils.get_model(model, identifier) + if value is not None: + new_value = value.casing - if model.value is not None and not hasattr(model.value(), attribute_name): - self.attribute_error(model, attribute_name) + returned_model = await Utils.get_model(model, identifier) + self.attribute_error(model, attribute_name) model_field = Utils.get_field(model.value, attribute_name) - if value is None and self.shared.attachments != [] and model_field.: - image_path = await Utils.save_file(self.shared.attachments[0]) - new_value = Utils.image_path(str(image_path)) + image_fields = Utils.fetch_fields( + model.value, + lambda _, field_type: ( + field_type.__class__.__name__ == "CharField" and field_type.max_length == 200 + ), + ) - text = f"Updated `{identifier}'s` {attribute} to `{new_value}`" + if value is None and self.shared.attachments and model_field in image_fields: + image_path = await Utils.save_file(self.shared.attachments.pop(0)) + new_value = Utils.image_path(str(image_path)) if attribute.type == Types.MODEL: - attribute_name = Utils.to_snake_case(_attr_name.lower()) + "_id" - + attribute_name = Utils.to_snake_case(attribute.name.lower()) + "_id" attribute_model = await Utils.get_model(attribute, value.name) new_value = attribute_model.pk - text = ( - f"Updated `{identifier}'s` {attribute_model.__class__.__name__} " - f"to `{value.name}`" - ) - setattr(returned_model, attribute_name, new_value) - await returned_model.save(update_fields=[attribute_name]) - await ctx.send(text) + + await ctx.send(f"Updated `{identifier}'s` {attribute} to `{value.name}`") async def view(self, ctx, model, identifier, attribute=None): """ @@ -127,22 +130,14 @@ async def view(self, ctx, model, identifier, attribute=None): fields["content"] += f"{Utils.to_snake_case(key)}: {value}\n" if isinstance(value, str) and Utils.is_image(value): - if fields.get("files") is None: - fields["files"] = [] - - fields["files"].append(discord.File(Utils.image_path(value))) + fields.setdefault("files", []).append(discord.File(Utils.image_path(value))) fields["content"] += "```" - await ctx.send(**fields) return - _attr_name = attribute.name.__name__ if attribute.type == Types.MODEL else attribute.name - - attribute_name = Utils.casing(_attr_name.lower()) - - if not hasattr(model.name(), attribute_name): - self.attribute_error(model, attribute_name) + attribute_name = attribute.case.lower() + self.attribute_error(model, attribute_name) new_attribute = getattr(returned_model, attribute_name) @@ -163,13 +158,14 @@ async def attributes(self, ctx, model): ------------- ATTRIBUTES > MODEL """ - fields = [f"{model.name.upper()} ATTRIBUTES:\n\n"] - - for field in vars(model.value()): # type: ignore - if field[:1] == "_": - continue + fields = [ + f"- {Utils.to_snake_case(x).upper()}" + for x in Utils.fetch_fields( + model.value, lambda _, field_type: field_type != "BackwardFKRelation" + ) + ] - fields.append(f"- {Utils.to_snake_case(field).replace(' ', '_').upper()}\n") + fields.insert(0, f"{model.name.upper()} ATTRIBUTES:\n") await Utils.message_list(ctx, fields) @@ -189,29 +185,23 @@ async def update(self, ctx, model, attribute, old_value, new_value, tortoise_ope ------------- FILTER > UPDATE > MODEL > ATTRIBUTE > OLD_VALUE > NEW_VALUE > TORTOISE_OPERATOR(?) """ - _attr_name = attribute.name.__name__ if attribute.type == Types.MODEL else attribute.name - casing_name = Utils.casing(_attr_name.lower()) - - if not hasattr(model.name(), casing_name): - self.attribute_error(model, casing_name) + casing_name = attribute.case.lower() + self.attribute_error(model, casing_name) if tortoise_operator is not None: casing_name += f"__{tortoise_operator.name.lower()}" - value_old = old_value.name - value_new = new_value.name + value_old, value_new = old_value.value, new_value.value if attribute.type == Types.MODEL: value_old = await Utils.get_model(attribute, value_old) value_new = await Utils.get_model(attribute, value_new) - await model.name.filter(**{casing_name: value_old}).update( - **{casing_name: value_new} - ) + await model.value.filter(**{casing_name: value_old}).update(**{casing_name: value_new}) await ctx.send( - f"Updated all `{model.name.__name__}` instances from a " - f"`{attribute}` value of `{old_value}` to `{new_value}`" + f"Updated all `{model.name}` instances from a `{attribute}` " + f"value of `{old_value}` to `{new_value}`" ) async def delete(self, ctx, model, attribute, value, tortoise_operator=None): @@ -224,17 +214,13 @@ async def delete(self, ctx, model, attribute, value, tortoise_operator=None): ------------- FILTER > DELETE > MODEL > ATTRIBUTE > VALUE > TORTOISE_OPERATOR(?) """ - _attr_name = attribute.name.__name__ if attribute.type == Types.MODEL else attribute.name - - casing_name = Utils.casing(_attr_name.lower()) - - if not hasattr(model.name(), casing_name): - self.attribute_error(model, casing_name) + casing_name = attribute.case.lower() + self.attribute_error(model, casing_name) if tortoise_operator is not None: casing_name += f"__{tortoise_operator.name.lower()}" - new_value = value.name + new_value = value.value if attribute.type == Types.MODEL: new_value = await Utils.get_model(attribute, new_value) @@ -242,8 +228,7 @@ async def delete(self, ctx, model, attribute, value, tortoise_operator=None): await model.name.filter(**{casing_name: new_value}).delete() await ctx.send( - f"Deleted all `{model.name.__name__}` instances with a " - f"`{attribute}` value of `{value}`" + f"Deleted all `{model.name.__name__}` instances with a `{attribute}` value of `{value}`" ) async def view(self, ctx, model, attribute, value, tortoise_operator=None): @@ -256,17 +241,13 @@ async def view(self, ctx, model, attribute, value, tortoise_operator=None): ------------- FILTER > VIEW > MODEL > ATTRIBUTE > VALUE > TORTOISE_OPERATOR(?) """ - _attr_name = attribute.name.__name__ if attribute.type == Types.MODEL else attribute.name - - casing_name = Utils.casing(_attr_name.lower()) - - if not hasattr(model.name(), casing_name): - self.attribute_error(model, casing_name) + casing_name = attribute.case.lower() + self.attribute_error(model, casing_name) if tortoise_operator is not None: casing_name += f"__{tortoise_operator.name.lower()}" - new_value = value.name + new_value = value.value if attribute.type == Types.MODEL: new_value = await Utils.get_model(attribute, new_value) @@ -298,22 +279,24 @@ async def save(self, ctx, name): if len(name.name) > NAME_LIMIT: raise Exception( - f"`{name}` is above the {NAME_LIMIT} character limit " - f"({len(name)} > {NAME_LIMIT})" + f"`{name}` exceeds the {NAME_LIMIT}-character limit ({len(name)} > {NAME_LIMIT})" ) if os.path.isfile(f"eval_presets/{name}.py"): - raise Exception(f"`{name}` aleady exists.") + raise Exception(f"`{name}` already exists.") await ctx.send("Please send the eval command below...") try: message = await self.bot.wait_for( - "message", check=lambda m: m.author == ctx.message.author, timeout=20 + "message", + check=lambda m: m.author == ctx.message.author, + timeout=20, ) except asyncio.TimeoutError: await ctx.send("Eval preset saving has timed out.") return + with open(f"eval_presets/{name}.py", "w") as file: file.write(Utils.remove_code_markdown(message.content)) diff --git a/DexScript/package/parser.py b/DexScript/package/parser.py index 294e3de..b6b3de2 100644 --- a/DexScript/package/parser.py +++ b/DexScript/package/parser.py @@ -19,8 +19,12 @@ class Value: extra_data: list = datafield(default_factory=list) + @property + def case(self): + return Utils.casing(self.name) + def __str__(self): - return str(self.name) + return self.name class DexScriptParser: diff --git a/DexScript/package/utils.py b/DexScript/package/utils.py index eeba5e6..a6934a9 100644 --- a/DexScript/package/utils.py +++ b/DexScript/package/utils.py @@ -1,3 +1,4 @@ +import asyncio import contextlib import inspect import os @@ -275,7 +276,7 @@ async def create_model(model, identifier, fields_only=False): continue if field in special_list["Identifiers"]: - fields[field] = identifier + fields[field] = str(identifier) continue match field_type.__class__.__name__: @@ -328,6 +329,18 @@ async def get_model(model, identifier): raise Exception(f"'{model}' is not a valid model.") return returned_model[0] + + @staticmethod + def fetch_fields(model, field_filter=None): + fetched_list = [] + + for field, field_type in model._meta.fields_map.items(): + if field_filter is not None and not field_filter(field, field_type): + continue + + fetched_list.append(field) + + return fetched_list @staticmethod def get_field(model, field): From a0f58943c6c94ab72bbd64eb454aed489060ceea Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 11 Feb 2025 12:21:07 -0500 Subject: [PATCH 112/133] Fix checks --- DexScript/package/commands.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index e5a1128..ad939e6 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -6,7 +6,7 @@ import discord -from .utils import DIR, Types, Utils +from .utils import Types, Utils @dataclass @@ -228,7 +228,8 @@ async def delete(self, ctx, model, attribute, value, tortoise_operator=None): await model.name.filter(**{casing_name: new_value}).delete() await ctx.send( - f"Deleted all `{model.name.__name__}` instances with a `{attribute}` value of `{value}`" + f"Deleted all `{model.name.__name__}` instances with a `{attribute}` " + f"value of `{value}`" ) async def view(self, ctx, model, attribute, value, tortoise_operator=None): From de286dacc2d2e5f62f81d995a74ab05a8b057bfd Mon Sep 17 00:00:00 2001 From: Dotsian Date: Thu, 20 Feb 2025 22:15:45 -0500 Subject: [PATCH 113/133] Bump Ruff --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 52e57e6..ea5bc68 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ license = "MIT" [tool.poetry.dependencies] python = ">=3.12" "discord.py" = "^2.4.0" -ruff = "^0.9.4" +ruff = "^0.9.7" [build-system] requires = ["poetry-core>=1.0.0"] From da2a09b570357abb3ef632fb1f27160e4622bdac Mon Sep 17 00:00:00 2001 From: Dotsian Date: Thu, 6 Mar 2025 19:59:05 -0500 Subject: [PATCH 114/133] [WIP] drop CF support --- DATAPOLICY.md | 5 +- DexScript/github/installer.py | 45 ++++++++---------- DexScript/github/migration.py | 8 ++-- DexScript/package/cog.py | 9 ++-- DexScript/package/commands.py | 55 ++++++++++++++++------ DexScript/package/parser.py | 14 ++---- DexScript/package/utils.py | 87 +++++------------------------------ README.md | 4 +- 8 files changed, 85 insertions(+), 142 deletions(-) diff --git a/DATAPOLICY.md b/DATAPOLICY.md index c55aafb..5b98041 100644 --- a/DATAPOLICY.md +++ b/DATAPOLICY.md @@ -8,10 +8,7 @@ We do not collect, store, or access any data from your application. DexScript on ## File Modifications -During the DexScript installation process, the following changes will be made to your application's files: - -- If you are using **BallsDex**, DexScript will modify your `ballsdex/core/bot.py` file. -- If you are using **CarFigures**, DexScript will modify your `carfigures/core/bot.py` file. +During the DexScript installation process, DexScript will modify your `ballsdex/core/bot.py` file. DexScript will add a single line of code to allow the DexScript extension to load when your application starts. This modification is solely for the purpose of running DexScript. You can view the code added by checking the `DexScript/github/installer.py` file in the official DexScript GitHub repository. diff --git a/DexScript/github/installer.py b/DexScript/github/installer.py index 4fdffdd..4bca14e 100644 --- a/DexScript/github/installer.py +++ b/DexScript/github/installer.py @@ -23,14 +23,9 @@ import requests from discord.ext import commands -DIR = "ballsdex" if os.path.isdir("ballsdex") else "carfigures" +from ballsdex.settings import settings -if DIR == "ballsdex": - from ballsdex.settings import settings -else: - from carfigures.settings import settings - -UPDATING = os.path.isdir(f"{DIR}/packages/dexscript") +UPDATING = os.path.isdir("ballsdex/packages/dexscript") class MigrationType(Enum): @@ -54,20 +49,20 @@ class InstallerConfig: install_migrations = [ ( "||await self.add_cog(Core(self))", - '||await self.load_extension("$DIR.packages.dexscript")\n', + '||await self.load_extension("ballsdex.packages.dexscript")\n', MigrationType.APPEND, ), ( - '||await self.load_extension("$DIR.core.dexscript")\n', - '||await self.load_extension("$DIR.packages.dexscript")\n', + '||await self.load_extension("ballsdex.core.dexscript")\n', + '||await self.load_extension("ballsdex.packages.dexscript")\n', MigrationType.REPLACE, ) ] uninstall_migrations = [ - '||await self.load_extension("$DIR.core.dexscript")\n', - '||await self.load_extension("$DIR.packages.dexscript")\n' + '||await self.load_extension("ballsdex.core.dexscript")\n', + '||await self.load_extension("ballsdex.packages.dexscript")\n' ] - path = f"{DIR}/packages/dexscript" + path = "ballsdex/packages/dexscript" @dataclass class Logger: @@ -105,7 +100,7 @@ def __init__(self, installer, embed_type="setup"): def setup(self): self.title = "DexScript Installation" self.description = "Welcome to the DexScript installer!" - self.color = discord.Color.from_str("#FFF" if DIR == "carfigures" else "#03BAFC") + self.color = discord.Color.from_str("#03BAFC") self.timestamp = datetime.now() latest_version = self.installer.latest_version @@ -142,7 +137,7 @@ def installed(self): "DexScript has been succesfully installed to your bot.\n" f"Run the `{settings.prefix}about` command to view details about DexScript." ) - self.color = discord.Color.from_str("#FFF" if DIR == "carfigures" else "#03BAFC") + self.color = discord.Color.from_str("#03BAFC") self.timestamp = datetime.now() self.set_thumbnail(url=config.appearance["logo"]) @@ -150,7 +145,7 @@ def installed(self): def uninstalled(self): self.title = "DexScript Uninstalled!" self.description = "DexScript has been succesfully uninstalled from your bot." - self.color = discord.Color.from_str("#FFF" if DIR == "carfigures" else "#03BAFC") + self.color = discord.Color.from_str("#03BAFC") self.timestamp = datetime.now() self.set_thumbnail(url=config.appearance["logo"]) @@ -239,7 +234,7 @@ def __init__(self): self.interface = InstallerGUI(self) def uninstall_migrate(self): - with open(f"{DIR}/core/bot.py", "r") as read_file: + with open("ballsdex/core/bot.py", "r") as read_file: lines = read_file.readlines() for index, line in enumerate(lines): @@ -251,11 +246,11 @@ def uninstall_migrate(self): lines.pop(index) - with open(f"{DIR}/core/bot.py", "w") as write_file: + with open("ballsdex/core/bot.py", "w") as write_file: write_file.writelines(lines) def install_migrate(self): - with open(f"{DIR}/core/bot.py", "r") as read_file: + with open("ballsdex/core/bot.py", "r") as read_file: lines = read_file.readlines() for index, line in enumerate(lines): @@ -275,12 +270,12 @@ def install_migrate(self): lines.insert(index + 1, new) - with open(f"{DIR}/core/bot.py", "w") as write_file: + with open("ballsdex/core/bot.py", "w") as write_file: write_file.writelines(lines) async def install(self): - if os.path.isfile(f"{DIR}/core/dexscript.py"): - os.remove(f"{DIR}/core/dexscript.py") + if os.path.isfile("ballsdex/core/dexscript.py"): + os.remove("ballsdex/core/dexscript.py") link = f"https://api.github.com/repos/{config.github[0]}/contents/" @@ -319,8 +314,8 @@ async def install(self): logger.log("DexScript installation finished", "INFO") async def uninstall(self): - if os.path.isfile(f"{DIR}/core/dexscript.py"): - os.remove(f"{DIR}/core/dexscript.py") + if os.path.isfile("ballsdex/core/dexscript.py"): + os.remove("ballsdex/core/dexscript.py") shutil.rmtree(config.path) @@ -331,7 +326,7 @@ async def uninstall(self): @staticmethod def format_migration(line): return ( - line.replace(" ", "").replace("|", " ").replace("/n", "\n").replace("$DIR", DIR) + line.replace(" ", "").replace("|", " ").replace("/n", "\n") ) @property diff --git a/DexScript/github/migration.py b/DexScript/github/migration.py index 1047287..554b407 100644 --- a/DexScript/github/migration.py +++ b/DexScript/github/migration.py @@ -1,7 +1,5 @@ from os import path -DIR = "ballsdex" if path.isdir("ballsdex") else "carfigures" - # |-----------------------------------------------------------------------------------------|# @@ -12,11 +10,11 @@ def repair_bot_file(): """ new_lines = [] - with open(f"{DIR}/core/bot.py", "r") as file: + with open("ballsdex/core/bot.py", "r") as file: if "import asyncio\n\n" not in file.read(): return - with open(f"{DIR}/core/bot.py", "r") as file: + with open("ballsdex/core/bot.py", "r") as file: last_was_newline = False for line in file.readlines(): @@ -29,7 +27,7 @@ def repair_bot_file(): new_lines.append(line) - with open(f"{DIR}/core/bot.py", "w") as file: + with open("ballsdex/core/bot.py", "w") as file: file.writelines(new_lines) diff --git a/DexScript/package/cog.py b/DexScript/package/cog.py index 6e68261..bfbae4d 100644 --- a/DexScript/package/cog.py +++ b/DexScript/package/cog.py @@ -7,12 +7,9 @@ from discord.ext import commands from .parser import DexScriptParser -from .utils import DIR, Utils, config +from .utils import Utils, config -if DIR == "ballsdex": - from ballsdex.settings import settings -else: - from carfigures.settings import settings +from ballsdex.settings import settings __version__ = "0.5" @@ -95,7 +92,7 @@ async def about(self, ctx: commands.Context): discord_link = "https://discord.gg/EhCxuNQfzt" description = ( - "DexScript is a set of commands for Ballsdex and CarFigures created by DotZZ " + "DexScript is a set of commands for Ballsdex created by DotZZ " "that expands on the standalone admin commands and substitutes for the admin panel. " "It simplifies editing, adding, deleting, and displaying data for models such as " "balls, regimes, specials, etc.\n\n" diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index ad939e6..51392bb 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -77,11 +77,8 @@ async def update(self, ctx, model, identifier, attribute, value=None): ------------- UPDATE > MODEL > IDENTIFIER > ATTRIBUTE > VALUE(?) """ - attribute_name = attribute.case.lower() - new_value = None - - if value is not None: - new_value = value.casing + attribute_name = attribute.name.lower() + new_value = None if value is None else value.value returned_model = await Utils.get_model(model, identifier) self.attribute_error(model, attribute_name) @@ -100,7 +97,7 @@ async def update(self, ctx, model, identifier, attribute, value=None): new_value = Utils.image_path(str(image_path)) if attribute.type == Types.MODEL: - attribute_name = Utils.to_snake_case(attribute.name.lower()) + "_id" + attribute_name = f"{attribute.name.lower()}_id" attribute_model = await Utils.get_model(attribute, value.name) new_value = attribute_model.pk @@ -118,7 +115,7 @@ async def view(self, ctx, model, identifier, attribute=None): ------------- VIEW > MODEL > IDENTIFIER > ATTRIBUTE(?) """ - returned_model = await Utils.get_model(model, identifier.name) + returned_model = await Utils.get_model(model, identifier) if attribute is None: fields = {"content": "```"} @@ -127,7 +124,7 @@ async def view(self, ctx, model, identifier, attribute=None): if key.startswith("_"): continue - fields["content"] += f"{Utils.to_snake_case(key)}: {value}\n" + fields["content"] += f"{key}: {value}\n" if isinstance(value, str) and Utils.is_image(value): fields.setdefault("files", []).append(discord.File(Utils.image_path(value))) @@ -136,7 +133,7 @@ async def view(self, ctx, model, identifier, attribute=None): await ctx.send(**fields) return - attribute_name = attribute.case.lower() + attribute_name = attribute.name.lower() self.attribute_error(model, attribute_name) new_attribute = getattr(returned_model, attribute_name) @@ -159,7 +156,7 @@ async def attributes(self, ctx, model): ATTRIBUTES > MODEL """ fields = [ - f"- {Utils.to_snake_case(x).upper()}" + f"- {x.upper()}" for x in Utils.fetch_fields( model.value, lambda _, field_type: field_type != "BackwardFKRelation" ) @@ -185,7 +182,7 @@ async def update(self, ctx, model, attribute, old_value, new_value, tortoise_ope ------------- FILTER > UPDATE > MODEL > ATTRIBUTE > OLD_VALUE > NEW_VALUE > TORTOISE_OPERATOR(?) """ - casing_name = attribute.case.lower() + casing_name = attribute.name.lower() self.attribute_error(model, casing_name) if tortoise_operator is not None: @@ -214,7 +211,7 @@ async def delete(self, ctx, model, attribute, value, tortoise_operator=None): ------------- FILTER > DELETE > MODEL > ATTRIBUTE > VALUE > TORTOISE_OPERATOR(?) """ - casing_name = attribute.case.lower() + casing_name = attribute.name.lower() self.attribute_error(model, casing_name) if tortoise_operator is not None: @@ -228,7 +225,7 @@ async def delete(self, ctx, model, attribute, value, tortoise_operator=None): await model.name.filter(**{casing_name: new_value}).delete() await ctx.send( - f"Deleted all `{model.name.__name__}` instances with a `{attribute}` " + f"Deleted all `{model.name}` instances with a `{attribute}` " f"value of `{value}`" ) @@ -242,7 +239,7 @@ async def view(self, ctx, model, attribute, value, tortoise_operator=None): ------------- FILTER > VIEW > MODEL > ATTRIBUTE > VALUE > TORTOISE_OPERATOR(?) """ - casing_name = attribute.case.lower() + casing_name = attribute.name.lower() self.attribute_error(model, casing_name) if tortoise_operator is not None: @@ -422,3 +419,33 @@ async def delete(self, ctx, file_path): os.remove(file_path.name) await ctx.send(f"Deleted `{file_path}` {file_type}") + +class Template(DexCommand): + """ + Template commands used to assist with DexScript commands. + """ + + # TODO: Softcode model creation template. + async def create(self, ctx, model, argument=None): + """ + Sends the `create` template for a model. + + Documentation + ------------- + TEMPLATE > CREATE > BALL > ARGUMENT(?) + """ + match model.name.lower(): + case "ball": + template_commands = [ + f"CREATE > BALL > {argument}", + f"UPDATE > BALL > {argument} > REGIME > ...", + f"UPDATE > BALL > {argument} > HEALTH > ...", + f"UPDATE > BALL > {argument} > ATTACK > ...", + f"UPDATE > BALL > {argument} > RARITY > ...", + f"UPDATE > BALL > {argument} > EMOJI_ID > ...", + f"UPDATE > BALL > {argument} > CREDITS > ...", + f"UPDATE > BALL > {argument} > CAPACITY_NAME > ...", + f"UPDATE > BALL > {argument} > CAPACITY_DESCRIPTION > ..." + ] + + await ctx.send(f"```sql\n{'\n'.join(template_commands)}\n```") diff --git a/DexScript/package/parser.py b/DexScript/package/parser.py index b6b3de2..a44a4cc 100644 --- a/DexScript/package/parser.py +++ b/DexScript/package/parser.py @@ -8,7 +8,7 @@ from dateutil.parser import parse as parse_date from . import commands -from .utils import DIR, Types, Utils, config +from .utils import Types, Utils, config @dataclass @@ -19,10 +19,6 @@ class Value: extra_data: list = datafield(default_factory=list) - @property - def case(self): - return Utils.casing(self.name) - def __str__(self): return self.name @@ -73,20 +69,16 @@ def create_value(self, line): model = Utils.fetch_model(line) if model is None: - examples = "Ball, Regime, Special" - - if DIR == "carfigures": - examples = "Car, CarType, Event" - raise Exception( f"'{line}' is not a valid model\n" - f"Make sure you check your capitalization (e.g. {examples})" + f"Make sure you check your capitalization (e.g. Ball, Regime, Special)" ) string_key = Utils.extract_str_attr(model) value.name = model.__name__ value.value = model + value.extra_data.append(string_key) case Types.BOOLEAN: diff --git a/DexScript/package/utils.py b/DexScript/package/utils.py index a6934a9..2150b10 100644 --- a/DexScript/package/utils.py +++ b/DexScript/package/utils.py @@ -12,12 +12,7 @@ import discord from dateutil.parser import parse as parse_date -DIR = "ballsdex" if os.path.isdir("ballsdex") else "carfigures" - -if DIR == "ballsdex": - from ballsdex.core.models import Ball, Economy, Regime, Special # noqa: F401, I001 -else: - from carfigures.core.models import Car, CarType, Country, Event, FontsPack # noqa: F401, I001 +from ballsdex.core.models import Ball, Economy, Regime, Special # noqa: F401, I001 START_CODE_BLOCK_RE = re.compile(r"^((```sql?)(?=\s)|(```))") FILENAME_RE = re.compile(r"^(.+)(\.\S+)$") @@ -82,49 +77,12 @@ def _common_format(string_or_list: str | list[str], func): return [func(x) for x in string_or_list] @staticmethod - def to_camel_case(item): - """ - Formats a string or list from snake_case into camelCase for CarFigure support. - """ - return Utils._common_format( - item, func=lambda s: re.sub( r"(_[a-z])", lambda m: m.group(1)[1].upper(), s) - ) - - @staticmethod - def to_pascal_case(item): - """ - Formats a string or list from snake or camel case to pascal case for class support. - """ + def pascal_case(string) -> str: return Utils._common_format( item, func=lambda s: re.sub( r"(_[a-z])", lambda m: m.group(1)[1].upper(), s[:1].upper() + s[1:] ) ) - - @staticmethod - def to_snake_case(item): - """ - Formats a string or list from camelCase into snake_case for CarFigure support. - """ - return Utils._common_format( - item, func=lambda s: re.sub(r"(? Ball > Mongolia > RARITY > 2.0`. @@ -26,7 +26,7 @@ DexScript is currently in beta. However, the latest version is a release candida To install DexScript, you must have the following: -* Ballsdex or CarFigures v2.2.0+ +* Ballsdex * Eval access ## DexScript Setup From 479f4c240e2d7f706ae8bc2ca2694d0413d7857e Mon Sep 17 00:00:00 2001 From: Dotsian Date: Thu, 6 Mar 2025 20:07:04 -0500 Subject: [PATCH 115/133] Fix checks --- DexScript/github/migration.py | 6 ------ DexScript/package/cog.py | 1 - 2 files changed, 7 deletions(-) diff --git a/DexScript/github/migration.py b/DexScript/github/migration.py index 554b407..10ad6a9 100644 --- a/DexScript/github/migration.py +++ b/DexScript/github/migration.py @@ -1,9 +1,3 @@ -from os import path - - -# |-----------------------------------------------------------------------------------------|# - - def repair_bot_file(): """ Repairs the `bot.py` file and removes extra newlines caused by an old DexScript installer. diff --git a/DexScript/package/cog.py b/DexScript/package/cog.py index bfbae4d..bef8b59 100644 --- a/DexScript/package/cog.py +++ b/DexScript/package/cog.py @@ -11,7 +11,6 @@ from ballsdex.settings import settings - __version__ = "0.5" From abad3789993ae7561dcb91f9a7f210a96ffe78e4 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Thu, 6 Mar 2025 20:13:46 -0500 Subject: [PATCH 116/133] Fix checks #2 --- DexScript/github/installer.py | 28 ++++++++++------------- DexScript/package/commands.py | 10 ++++----- DexScript/package/parser.py | 2 +- DexScript/package/utils.py | 42 +++++++++++++++++------------------ 4 files changed, 38 insertions(+), 44 deletions(-) diff --git a/DexScript/github/installer.py b/DexScript/github/installer.py index 4bca14e..548402a 100644 --- a/DexScript/github/installer.py +++ b/DexScript/github/installer.py @@ -21,9 +21,8 @@ import discord import requests -from discord.ext import commands - from ballsdex.settings import settings +from discord.ext import commands UPDATING = os.path.isdir("ballsdex/packages/dexscript") @@ -44,7 +43,7 @@ class InstallerConfig: appearance = { "logo": "https://raw.githubusercontent.com/Dotsian/DexScript/refs/heads/dev/assets/DexScriptLogo.png", "logo_error": "https://raw.githubusercontent.com/Dotsian/DexScript/refs/heads/dev/assets/DexScriptLogoError.png", - "banner": "https://raw.githubusercontent.com/Dotsian/DexScript/refs/heads/dev/assets/DexScriptPromo.png" + "banner": "https://raw.githubusercontent.com/Dotsian/DexScript/refs/heads/dev/assets/DexScriptPromo.png", } install_migrations = [ ( @@ -56,14 +55,15 @@ class InstallerConfig: '||await self.load_extension("ballsdex.core.dexscript")\n', '||await self.load_extension("ballsdex.packages.dexscript")\n', MigrationType.REPLACE, - ) + ), ] uninstall_migrations = [ '||await self.load_extension("ballsdex.core.dexscript")\n', - '||await self.load_extension("ballsdex.packages.dexscript")\n' + '||await self.load_extension("ballsdex.packages.dexscript")\n', ] path = "ballsdex/packages/dexscript" + @dataclass class Logger: name: str @@ -128,7 +128,7 @@ def error(self): self.description += f"\n```{logger.output[-1]}```" self.installer.interface.attachments = [logger.file("DexScript.log")] - + self.set_thumbnail(url=config.appearance["logo_error"]) def installed(self): @@ -172,21 +172,19 @@ async def install_button(self, interaction: discord.Interaction, _: discord.ui.B self.installer.interface.embed = InstallerEmbed(self.installer, "error") else: self.installer.interface.embed = InstallerEmbed(self.installer, "installed") - + self.installer.interface.view = None await interaction.message.edit(**self.installer.interface.fields) await interaction.response.defer() - @discord.ui.button( - style=discord.ButtonStyle.primary, label="Uninstall", disabled=not UPDATING - ) + @discord.ui.button(style=discord.ButtonStyle.primary, label="Uninstall", disabled=not UPDATING) async def uninstall_button(self, interaction: discord.Interaction, _: discord.ui.Button): await self.installer.uninstall() self.installer.interface.embed = InstallerEmbed(self.installer, "uninstalled") self.installer.interface.view = None - + await interaction.message.edit(**self.installer.interface.fields) await interaction.response.defer() @@ -240,7 +238,7 @@ def uninstall_migrate(self): for index, line in enumerate(lines): for migration in config.uninstall_migrations: original = self.format_migration(migration) - + if line != original: continue @@ -316,7 +314,7 @@ async def install(self): async def uninstall(self): if os.path.isfile("ballsdex/core/dexscript.py"): os.remove("ballsdex/core/dexscript.py") - + shutil.rmtree(config.path) self.uninstall_migrate() @@ -325,9 +323,7 @@ async def uninstall(self): @staticmethod def format_migration(line): - return ( - line.replace(" ", "").replace("|", " ").replace("/n", "\n") - ) + return line.replace(" ", "").replace("|", " ").replace("/n", "\n") @property def latest_version(self): diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 51392bb..5612b53 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -33,7 +33,7 @@ def __loaded__(self): def attribute_error(self, model, attribute): if model.value is None or hasattr(model.value(), attribute): return - + raise Exception( f"'{attribute}' is not a valid {model.name} attribute\n" f"Run `ATTRIBUTES > {model.name}` to see a list of " @@ -171,7 +171,7 @@ class Filter(DexCommand): """ Filter commands used for mass updating, deleting, and viewing models. """ - + async def update(self, ctx, model, attribute, old_value, new_value, tortoise_operator=None): """ Updates all instances of a model to the specified value where the specified attribute @@ -225,8 +225,7 @@ async def delete(self, ctx, model, attribute, value, tortoise_operator=None): await model.name.filter(**{casing_name: new_value}).delete() await ctx.send( - f"Deleted all `{model.name}` instances with a `{attribute}` " - f"value of `{value}`" + f"Deleted all `{model.name}` instances with a `{attribute}` " f"value of `{value}`" ) async def view(self, ctx, model, attribute, value, tortoise_operator=None): @@ -420,6 +419,7 @@ async def delete(self, ctx, file_path): await ctx.send(f"Deleted `{file_path}` {file_type}") + class Template(DexCommand): """ Template commands used to assist with DexScript commands. @@ -445,7 +445,7 @@ async def create(self, ctx, model, argument=None): f"UPDATE > BALL > {argument} > EMOJI_ID > ...", f"UPDATE > BALL > {argument} > CREDITS > ...", f"UPDATE > BALL > {argument} > CAPACITY_NAME > ...", - f"UPDATE > BALL > {argument} > CAPACITY_DESCRIPTION > ..." + f"UPDATE > BALL > {argument} > CAPACITY_DESCRIPTION > ...", ] await ctx.send(f"```sql\n{'\n'.join(template_commands)}\n```") diff --git a/DexScript/package/parser.py b/DexScript/package/parser.py index a44a4cc..4a781d3 100644 --- a/DexScript/package/parser.py +++ b/DexScript/package/parser.py @@ -78,7 +78,7 @@ def create_value(self, line): value.name = model.__name__ value.value = model - + value.extra_data.append(string_key) case Types.BOOLEAN: diff --git a/DexScript/package/utils.py b/DexScript/package/utils.py index 2150b10..4b6d733 100644 --- a/DexScript/package/utils.py +++ b/DexScript/package/utils.py @@ -10,9 +10,8 @@ from pathlib import Path import discord -from dateutil.parser import parse as parse_date - from ballsdex.core.models import Ball, Economy, Regime, Special # noqa: F401, I001 +from dateutil.parser import parse as parse_date START_CODE_BLOCK_RE = re.compile(r"^((```sql?)(?=\s)|(```))") FILENAME_RE = re.compile(r"^(.+)(\.\S+)$") @@ -28,6 +27,7 @@ class Types(Enum): MODEL = 4 DATETIME = 5 + @dataclass class Settings: """ @@ -50,7 +50,7 @@ class Utils: @staticmethod def image_path(path) -> bool: - full_path = path.replace('/static/uploads/', '') + full_path = path.replace("/static/uploads/", "") if MEDIA_PATH == "./static/uploads" and full_path[0] == ".": full_path = full_path[1:] @@ -73,21 +73,22 @@ def is_date(string) -> bool: def _common_format(string_or_list: str | list[str], func): if isinstance(string_or_list, str): return func(string_or_list) - + return [func(x) for x in string_or_list] @staticmethod def pascal_case(string) -> str: return Utils._common_format( - item, func=lambda s: re.sub( + item, + func=lambda s: re.sub( r"(_[a-z])", lambda m: m.group(1)[1].upper(), s[:1].upper() + s[1:] - ) + ), ) @staticmethod async def message_list(ctx, messages: list[str]): """ - Creates an interactive message limit that allows you to display a list of messages + Creates an interactive message limit that allows you to display a list of messages without suprassing the Discord message character limit. Parameters @@ -101,11 +102,11 @@ async def message_list(ctx, messages: list[str]): page_length = 0 def check(message): - return ( - message.author.id == ctx.message.author.id and - message.content.lower() in ("more", "file") + return message.author.id == ctx.message.author.id and message.content.lower() in ( + "more", + "file", ) - + for message in messages: if page_length + len(messages) >= 750: page_length = 0 @@ -138,16 +139,14 @@ def check(message): await message.delete() break - + with contextlib.suppress(discord.HTTPException): await ctx.channel.delete_messages((message, response)) if response.content.lower() == "more": continue - await ctx.send( - file=discord.File(StringIO("\n".join(messages)), filename="output.txt") - ) + await ctx.send(file=discord.File(StringIO("\n".join(messages)), filename="output.txt")) break @@ -208,7 +207,7 @@ async def create_model(model, identifier, fields_only=False): special_list = { "Identifiers": ["country", "catch_names", "name"], - "Ignore": ["id", "short_name"] + "Ignore": ["id", "short_name"], } for field, field_type in model._meta.fields_map.items(): @@ -222,7 +221,7 @@ async def create_model(model, identifier, fields_only=False): match field_type.__class__.__name__: case "ForeignKeyFieldInstance": casing_field = Utils.pascal_case(field) - + instance = await Utils.fetch_model(casing_field).first() if instance is None: @@ -238,7 +237,7 @@ async def create_model(model, identifier, fields_only=False): case _: fields[field] = 1 - + if fields_only: return fields @@ -266,7 +265,7 @@ async def get_model(model, identifier): raise Exception(f"'{model}' is not a valid model.") return returned_model[0] - + @staticmethod def fetch_fields(model, field_filter=None): fetched_list = [] @@ -274,7 +273,7 @@ def fetch_fields(model, field_filter=None): for field, field_type in model._meta.fields_map.items(): if field_filter is not None and not field_filter(field, field_type): continue - + fetched_list.append(field) return fetched_list @@ -306,7 +305,7 @@ def autocorrect(string, correction_list, error="does not exist."): @staticmethod def extract_str_attr(object): - expression = r"return\s+self\.(\w+)" # TODO: Add `return str()` + expression = r"return\s+self\.(\w+)" # TODO: Add `return str()` return re.search(expression, inspect.getsource(object.__str__)).group(1) @@ -316,4 +315,3 @@ def remove_code_markdown(content) -> str: return START_CODE_BLOCK_RE.sub("", content)[:-3] return content.strip("` \n") - From 911129db7024b22111459fff3b7e2424d23457f7 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Thu, 6 Mar 2025 20:16:29 -0500 Subject: [PATCH 117/133] Fix `cog` file checks --- DexScript/package/cog.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/DexScript/package/cog.py b/DexScript/package/cog.py index bef8b59..044a265 100644 --- a/DexScript/package/cog.py +++ b/DexScript/package/cog.py @@ -4,13 +4,12 @@ import discord import requests +from ballsdex.settings import settings from discord.ext import commands from .parser import DexScriptParser from .utils import Utils, config -from ballsdex.settings import settings - __version__ = "0.5" @@ -120,20 +119,22 @@ async def about(self, ctx: commands.Context): @commands.command() @commands.is_owner() async def installer(self, ctx: commands.Context): - link = "https://api.github.com/repos/Dotsian/DexScript/contents/DexScript/github/installer.py" + link = ( + "https://api.github.com/repos/Dotsian/DexScript/contents/DexScript/github/installer.py" + ) request = requests.get(link, {"ref": config.reference}) match request.status_code: case requests.codes.not_found: await ctx.send(f"Could not find installer for the {config.reference} branch.") - + case requests.codes.ok: content = requests.get(link, {"ref": config.reference}).json()["content"] - + await ctx.invoke( self.bot.get_command("eval"), body=base64.b64decode(content).decode() ) - + case _: await ctx.send(f"Request raised error code `{request.status_code}`.") From 5ab525153f2cf1652c82926e10d8de6dc3f78a03 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Thu, 6 Mar 2025 22:37:55 -0500 Subject: [PATCH 118/133] Fix incorrect variable --- DexScript/package/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DexScript/package/utils.py b/DexScript/package/utils.py index 4b6d733..65d89a2 100644 --- a/DexScript/package/utils.py +++ b/DexScript/package/utils.py @@ -79,7 +79,7 @@ def _common_format(string_or_list: str | list[str], func): @staticmethod def pascal_case(string) -> str: return Utils._common_format( - item, + string, func=lambda s: re.sub( r"(_[a-z])", lambda m: m.group(1)[1].upper(), s[:1].upper() + s[1:] ), From db5c0e7497e08d986b6623538ff9aec1e35930ef Mon Sep 17 00:00:00 2001 From: Dotsian Date: Thu, 6 Mar 2025 22:50:10 -0500 Subject: [PATCH 119/133] Remove model name case sensitivity and improve `ATTRIBUTE` command --- DexScript/package/commands.py | 8 ++++---- DexScript/package/parser.py | 5 +---- DexScript/package/utils.py | 37 ++++++++++++++++++++++++++--------- 3 files changed, 33 insertions(+), 17 deletions(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 5612b53..eaa4c7a 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -147,18 +147,18 @@ async def view(self, ctx, model, identifier, attribute=None): await ctx.send(f"```{new_attribute}```") - async def attributes(self, ctx, model): + async def attributes(self, ctx, model, required=False): """ Lists all changeable attributes of a model. Documentation ------------- - ATTRIBUTES > MODEL + ATTRIBUTES > MODEL > REQUIRED(?) """ fields = [ f"- {x.upper()}" for x in Utils.fetch_fields( - model.value, lambda _, field_type: field_type != "BackwardFKRelation" + model.value, required, lambda _, field_type: field_type != "BackwardFKRelation" ) ] @@ -426,7 +426,7 @@ class Template(DexCommand): """ # TODO: Softcode model creation template. - async def create(self, ctx, model, argument=None): + async def create(self, ctx, model, argument="..."): """ Sends the `create` template for a model. diff --git a/DexScript/package/parser.py b/DexScript/package/parser.py index 4a781d3..7ddbfec 100644 --- a/DexScript/package/parser.py +++ b/DexScript/package/parser.py @@ -69,10 +69,7 @@ def create_value(self, line): model = Utils.fetch_model(line) if model is None: - raise Exception( - f"'{line}' is not a valid model\n" - f"Make sure you check your capitalization (e.g. Ball, Regime, Special)" - ) + raise Exception(f"'{line}' is not a valid model") string_key = Utils.extract_str_attr(model) diff --git a/DexScript/package/utils.py b/DexScript/package/utils.py index 65d89a2..64c1f71 100644 --- a/DexScript/package/utils.py +++ b/DexScript/package/utils.py @@ -18,6 +18,13 @@ MEDIA_PATH = "./admin_panel/media" if os.path.isdir("./admin_panel/media") else "./static/uploads" +MODELS = [ + "Ball", + "Regime", + "Economy", + "Special", +] + class Types(Enum): DEFAULT = 0 @@ -170,19 +177,16 @@ async def save_file(attachment: discord.Attachment) -> Path: @staticmethod def fetch_model(model): - return globals().get(model) + return globals().get(Utils.pascal_case(model)) @staticmethod def models(names=False, key=None): - model_list = [ - "Ball", - "Regime", - "Economy", - "Special", - ] + model_list = MODELS if not names: - model_list = [globals().get(x) for x in model_list if globals().get(x) is not None] + model_list = [ + Utils.fetch_model(x) for x in model_list if Utils.fetch_model(x) is not None + ] if key is not None: model_list = [key(x) for x in model_list] @@ -267,13 +271,28 @@ async def get_model(model, identifier): return returned_model[0] @staticmethod - def fetch_fields(model, field_filter=None): + def fetch_fields(model, null=False, field_filter=None): + """ + Returns a list of a model's fields. + + Parameters + ---------- + model: Model + The model you want to fetch fields from. + null: bool + Whether it should return fields that are null. + field_filter: Callable | None + If this callable returns False, that specific field won't be included. + """ fetched_list = [] for field, field_type in model._meta.fields_map.items(): if field_filter is not None and not field_filter(field, field_type): continue + if null and not field_type.null: + continue + fetched_list.append(field) return fetched_list From 5c2eb684a3197728af351a7f8b5c6ea072907fe7 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Thu, 6 Mar 2025 23:05:35 -0500 Subject: [PATCH 120/133] Bug fixes #1 --- DexScript/package/commands.py | 6 +++--- DexScript/package/utils.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index eaa4c7a..3b04f3f 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -98,7 +98,7 @@ async def update(self, ctx, model, identifier, attribute, value=None): if attribute.type == Types.MODEL: attribute_name = f"{attribute.name.lower()}_id" - attribute_model = await Utils.get_model(attribute, value.name) + attribute_model = await Utils.get_model(attribute, value) new_value = attribute_model.pk setattr(returned_model, attribute_name, new_value) @@ -222,7 +222,7 @@ async def delete(self, ctx, model, attribute, value, tortoise_operator=None): if attribute.type == Types.MODEL: new_value = await Utils.get_model(attribute, new_value) - await model.name.filter(**{casing_name: new_value}).delete() + await model.value.filter(**{casing_name: new_value}).delete() await ctx.send( f"Deleted all `{model.name}` instances with a `{attribute}` " f"value of `{value}`" @@ -249,7 +249,7 @@ async def view(self, ctx, model, attribute, value, tortoise_operator=None): if attribute.type == Types.MODEL: new_value = await Utils.get_model(attribute, new_value) - instances = await model.name.filter(**{casing_name: new_value}).values_list( + instances = await model.value.filter(**{casing_name: new_value}).values_list( model.extra_data[0], flat=True ) diff --git a/DexScript/package/utils.py b/DexScript/package/utils.py index 64c1f71..f10cf53 100644 --- a/DexScript/package/utils.py +++ b/DexScript/package/utils.py @@ -86,7 +86,7 @@ def _common_format(string_or_list: str | list[str], func): @staticmethod def pascal_case(string) -> str: return Utils._common_format( - string, + string.lower(), func=lambda s: re.sub( r"(_[a-z])", lambda m: m.group(1)[1].upper(), s[:1].upper() + s[1:] ), @@ -115,7 +115,7 @@ def check(message): ) for message in messages: - if page_length + len(messages) >= 750: + if page_length >= 750: page_length = 0 pages.append([]) From 0b646b3ad3a2f94b5c0963a4145fe17e522ff25c Mon Sep 17 00:00:00 2001 From: Dotsian Date: Thu, 6 Mar 2025 23:27:35 -0500 Subject: [PATCH 121/133] Overhaul `ATTRIBUTES` and fully document `utils.py` --- DexScript/package/commands.py | 31 +++++--- DexScript/package/utils.py | 130 ++++++++++++++++++++++++++-------- 2 files changed, 122 insertions(+), 39 deletions(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 3b04f3f..00909ee 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -147,21 +147,30 @@ async def view(self, ctx, model, identifier, attribute=None): await ctx.send(f"```{new_attribute}```") - async def attributes(self, ctx, model, required=False): + async def attributes(self, ctx, model, filter=None): """ Lists all changeable attributes of a model. Documentation ------------- - ATTRIBUTES > MODEL > REQUIRED(?) - """ - fields = [ - f"- {x.upper()}" - for x in Utils.fetch_fields( - model.value, required, lambda _, field_type: field_type != "BackwardFKRelation" - ) - ] - + ATTRIBUTES > MODEL > FILTER(?) + """ + def filter_function(_, field_type): + if field_type == "BackwardFKRelation": + return False + + if filter is None: + return True + + match filter.value.lower(): + case "null": + return field_type.null + case "valid": + return not field_type.null + + return True + + fields = [f"- {x.upper()}" for x in Utils.fetch_fields(model.value, filter_function)] fields.insert(0, f"{model.name.upper()} ATTRIBUTES:\n") await Utils.message_list(ctx, fields) @@ -432,7 +441,7 @@ async def create(self, ctx, model, argument="..."): Documentation ------------- - TEMPLATE > CREATE > BALL > ARGUMENT(?) + TEMPLATE > CREATE > MODEL > ARGUMENT(?) """ match model.name.lower(): case "ball": diff --git a/DexScript/package/utils.py b/DexScript/package/utils.py index f10cf53..2f337ca 100644 --- a/DexScript/package/utils.py +++ b/DexScript/package/utils.py @@ -8,6 +8,7 @@ from enum import Enum from io import StringIO from pathlib import Path +from typing import Any, Callable import discord from ballsdex.core.models import Ball, Economy, Regime, Special # noqa: F401, I001 @@ -56,7 +57,15 @@ class Utils: """ @staticmethod - def image_path(path) -> bool: + def image_path(path: str) -> bool: + """ + Formats an image path correctly. + + Parameters + ---------- + path: str + The path you want to format. + """ full_path = path.replace("/static/uploads/", "") if MEDIA_PATH == "./static/uploads" and full_path[0] == ".": @@ -65,11 +74,27 @@ def image_path(path) -> bool: return f"{MEDIA_PATH}/{full_path}" @staticmethod - def is_image(path) -> bool: + def is_image(path: str) -> bool: + """ + Determines if a file is an image if it is found within the correct image directory. + + Parameters + ---------- + path: str + The path of the file. + """ return os.path.isfile(Utils.image_path(path)) @staticmethod - def is_date(string) -> bool: + def is_date(string: str) -> bool: + """ + Determines if a string can be parsed into a date. + + Parameters + ---------- + string: str + The string you want to check. + """ try: parse_date(string) return True @@ -77,19 +102,18 @@ def is_date(string) -> bool: return False @staticmethod - def _common_format(string_or_list: str | list[str], func): - if isinstance(string_or_list, str): - return func(string_or_list) - - return [func(x) for x in string_or_list] + def pascal_case(string: str) -> str: + """ + Converts a string from whatever case it's in to PascalCase. - @staticmethod - def pascal_case(string) -> str: - return Utils._common_format( - string.lower(), - func=lambda s: re.sub( - r"(_[a-z])", lambda m: m.group(1)[1].upper(), s[:1].upper() + s[1:] - ), + Parameters + ---------- + string: str + The string you want to convert. + """ + string = string.lower() + return re.sub( + r"(_[a-z])", lambda m: m.group(1)[1].upper(), string[:1].upper() + string[1:] ) @staticmethod @@ -159,6 +183,14 @@ def check(message): @staticmethod async def save_file(attachment: discord.Attachment) -> Path: + """ + Saves a `discord.Attachment` object into a directory. + + Parameters + ---------- + attachment: discord.Attachment + The attachment you want to save. + """ path = Path(f"{MEDIA_PATH}/{attachment.filename}") match = FILENAME_RE.match(attachment.filename) @@ -176,11 +208,29 @@ async def save_file(attachment: discord.Attachment) -> Path: return path.relative_to(MEDIA_PATH) @staticmethod - def fetch_model(model): + def fetch_model(model: str): + """ + Fetches a model's class based on the model name provided. + + Parameters + ---------- + model: str + The name of the model you want to fetch. + """ return globals().get(Utils.pascal_case(model)) @staticmethod - def models(names=False, key=None): + def models(names=False, key: Callable | None = None): + """ + Returns a list of models. + + Parameters + ---------- + names: bool + Whether or not a list of the model's names should be returned instead. + key: Callable | None + The model instance of name will be passed through this callable per model. + """ model_list = MODELS if not names: @@ -248,7 +298,7 @@ async def create_model(model, identifier, fields_only=False): await model.create(**fields) @staticmethod - async def get_model(model, identifier): + async def get_model(model, identifier: str): """ Returns a model instance, providing autocorrection. @@ -271,7 +321,7 @@ async def get_model(model, identifier): return returned_model[0] @staticmethod - def fetch_fields(model, null=False, field_filter=None): + def fetch_fields(model, field_filter: Callable | None = None) -> list[str]: """ Returns a list of a model's fields. @@ -279,8 +329,6 @@ def fetch_fields(model, null=False, field_filter=None): ---------- model: Model The model you want to fetch fields from. - null: bool - Whether it should return fields that are null. field_filter: Callable | None If this callable returns False, that specific field won't be included. """ @@ -290,15 +338,12 @@ def fetch_fields(model, null=False, field_filter=None): if field_filter is not None and not field_filter(field, field_type): continue - if null and not field_type.null: - continue - fetched_list.append(field) return fetched_list @staticmethod - def get_field(model, field): + def get_field(model, field: str): """ Returns a field from a model. @@ -312,7 +357,20 @@ def get_field(model, field): return model._meta.fields_map.get(field) @staticmethod - def autocorrect(string, correction_list, error="does not exist."): + def autocorrect(string: str, correction_list: list[str], error="does not exist."): + """ + Autocorrects a string based on the specified `correction_list` + and raises an error if there are no strings similiar to the string provided. + + Parameters + ---------- + string: str + The base string that will be used for autocorrection. + correction_list: list[str] + A list of strings that will be referenced when autocorrecting. + error: str + The error message that will be raised when there are no similarities. + """ autocorrection = get_close_matches(string, correction_list) if not autocorrection or autocorrection[0] != string: @@ -323,13 +381,29 @@ def autocorrect(string, correction_list, error="does not exist."): return autocorrection[0] @staticmethod - def extract_str_attr(object): + def extract_str_attr(object: Any): + """ + Extracts the attribute used in the `__str__` method of a class. + + Parameters + ---------- + object: Any + The class you want to fetch the `__str__` attribute from. + """ expression = r"return\s+self\.(\w+)" # TODO: Add `return str()` return re.search(expression, inspect.getsource(object.__str__)).group(1) @staticmethod - def remove_code_markdown(content) -> str: + def remove_code_markdown(content: str) -> str: + """ + Removes code markdown from a message. + + Parameters + ---------- + content: str + The content you want to remove the code markdown from. + """ if content.startswith("```") and content.endswith("```"): return START_CODE_BLOCK_RE.sub("", content)[:-3] From bdb8a116a5d07a03404003f3bbcaf9b69cc4bdbe Mon Sep 17 00:00:00 2001 From: Dotsian Date: Thu, 6 Mar 2025 23:30:25 -0500 Subject: [PATCH 122/133] add default value --- DexScript/package/parser.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DexScript/package/parser.py b/DexScript/package/parser.py index 7ddbfec..b5798dc 100644 --- a/DexScript/package/parser.py +++ b/DexScript/package/parser.py @@ -47,6 +47,8 @@ def __init__(self, ctx, bot): def create_value(self, line): value = Value(line) + value.value = line + lower = line.lower() type_dict = { From 49d3f392caa7a49774dc067af181a3bd5f0be437 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Fri, 7 Mar 2025 17:04:01 -0500 Subject: [PATCH 123/133] Type checking --- DexScript/github/installer.py | 4 ++-- DexScript/package/__init__.py | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/DexScript/github/installer.py b/DexScript/github/installer.py index 548402a..207e907 100644 --- a/DexScript/github/installer.py +++ b/DexScript/github/installer.py @@ -305,9 +305,9 @@ async def install(self): logger.log("Loading DexScript extension", "INFO") try: - await bot.load_extension(config.path.replace("/", ".")) # type: ignore - except commands.ExtensionAlreadyLoaded: await bot.reload_extension(config.path.replace("/", ".")) # type: ignore + except commands.ExtensionNotLoaded: + await bot.load_extension(config.path.replace("/", ".")) # type: ignore logger.log("DexScript installation finished", "INFO") diff --git a/DexScript/package/__init__.py b/DexScript/package/__init__.py index 6b27689..e5f0369 100644 --- a/DexScript/package/__init__.py +++ b/DexScript/package/__init__.py @@ -1,5 +1,10 @@ +from typing import TYPE_CHECKING + from .cog import DexScript +if TYPE_CHECKING: + from ballsdex.core.bot import BallsDexBot + -async def setup(bot): +async def setup(bot: "BallsDexBot"): await bot.add_cog(DexScript(bot)) From 9630359cc35ee34384f07c72fa1b83af3ff7b1c8 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Thu, 10 Apr 2025 14:50:32 -0400 Subject: [PATCH 124/133] Small changes --- DexScript/github/installer.py | 6 +++--- DexScript/package/commands.py | 2 +- DexScript/package/utils.py | 13 ++++++++----- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/DexScript/github/installer.py b/DexScript/github/installer.py index 207e907..bb5c7ea 100644 --- a/DexScript/github/installer.py +++ b/DexScript/github/installer.py @@ -176,7 +176,7 @@ async def install_button(self, interaction: discord.Interaction, _: discord.ui.B self.installer.interface.view = None await interaction.message.edit(**self.installer.interface.fields) - await interaction.response.defer() + await interaction.response.defer(thinking=True) @discord.ui.button(style=discord.ButtonStyle.primary, label="Uninstall", disabled=not UPDATING) async def uninstall_button(self, interaction: discord.Interaction, _: discord.ui.Button): @@ -186,7 +186,7 @@ async def uninstall_button(self, interaction: discord.Interaction, _: discord.ui self.installer.interface.view = None await interaction.message.edit(**self.installer.interface.fields) - await interaction.response.defer() + await interaction.response.defer(thinking=True) @discord.ui.button(style=discord.ButtonStyle.red, label="Exit") async def quit_button(self, interaction: discord.Interaction, _: discord.ui.Button): @@ -194,7 +194,7 @@ async def quit_button(self, interaction: discord.Interaction, _: discord.ui.Butt self.quit_button.disabled = True await interaction.message.edit(**self.installer.interface.fields) - await interaction.response.defer() + await interaction.response.defer(thinking=True) class InstallerGUI: diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 00909ee..1729f12 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -94,7 +94,7 @@ async def update(self, ctx, model, identifier, attribute, value=None): if value is None and self.shared.attachments and model_field in image_fields: image_path = await Utils.save_file(self.shared.attachments.pop(0)) - new_value = Utils.image_path(str(image_path)) + new_value = f"/{image_path}" if attribute.type == Types.MODEL: attribute_name = f"{attribute.name.lower()}_id" diff --git a/DexScript/package/utils.py b/DexScript/package/utils.py index 2f337ca..dc16c9a 100644 --- a/DexScript/package/utils.py +++ b/DexScript/package/utils.py @@ -17,7 +17,8 @@ START_CODE_BLOCK_RE = re.compile(r"^((```sql?)(?=\s)|(```))") FILENAME_RE = re.compile(r"^(.+)(\.\S+)$") -MEDIA_PATH = "./admin_panel/media" if os.path.isdir("./admin_panel/media") else "./static/uploads" +STATIC = os.path.isdir("static") +MEDIA_PATH = "./static/uploads" if STATIC else "./admin_panel/media" MODELS = [ "Ball", @@ -68,7 +69,7 @@ def image_path(path: str) -> bool: """ full_path = path.replace("/static/uploads/", "") - if MEDIA_PATH == "./static/uploads" and full_path[0] == ".": + if STATIC and full_path[0] == ".": full_path = full_path[1:] return f"{MEDIA_PATH}/{full_path}" @@ -133,9 +134,11 @@ async def message_list(ctx, messages: list[str]): page_length = 0 def check(message): - return message.author.id == ctx.message.author.id and message.content.lower() in ( - "more", - "file", + valid_choice = message.content.lower() in ("more", "file") + + return ( + message.author == ctx.message.author and + message.channel == ctx.channel and valid_choice ) for message in messages: From 462a34ddb47bdb1eeb6b97640d34f4e1fa1f6b10 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Sat, 19 Apr 2025 16:56:52 -0400 Subject: [PATCH 125/133] Use config file --- DexScript/github/installer.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/DexScript/github/installer.py b/DexScript/github/installer.py index bb5c7ea..0f5341c 100644 --- a/DexScript/github/installer.py +++ b/DexScript/github/installer.py @@ -231,6 +231,25 @@ class Installer: def __init__(self): self.interface = InstallerGUI(self) + def add_package(self, package: str) -> bool: + with open("config.yml", "r") as file: + lines = file.readlines() + + item = f" - {package}\n" + + if "packages:\n" not in lines or item in lines: + return False + + for i, line in enumerate(lines): + if line.rstrip().startswith("packages:"): + lines.insert(i + 1, item) + break + + with open("config.yml", "w") as file: + file.writelines(lines) + + return True + def uninstall_migrate(self): with open("ballsdex/core/bot.py", "r") as read_file: lines = read_file.readlines() @@ -300,7 +319,8 @@ async def install(self): logger.log("Applying bot.py migrations", "INFO") - self.install_migrate() + if self.add_package(config.path.replace("/", ".")) is False: + self.install_migrate() logger.log("Loading DexScript extension", "INFO") From 52f747768ce3c793f9ad9d3b1b37d17de50db42d Mon Sep 17 00:00:00 2001 From: Dotsian Date: Sat, 19 Apr 2025 16:58:50 -0400 Subject: [PATCH 126/133] Minor fix --- DexScript/github/installer.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/DexScript/github/installer.py b/DexScript/github/installer.py index 0f5341c..6f02e2f 100644 --- a/DexScript/github/installer.py +++ b/DexScript/github/installer.py @@ -176,7 +176,7 @@ async def install_button(self, interaction: discord.Interaction, _: discord.ui.B self.installer.interface.view = None await interaction.message.edit(**self.installer.interface.fields) - await interaction.response.defer(thinking=True) + await interaction.response.defer() @discord.ui.button(style=discord.ButtonStyle.primary, label="Uninstall", disabled=not UPDATING) async def uninstall_button(self, interaction: discord.Interaction, _: discord.ui.Button): @@ -186,15 +186,16 @@ async def uninstall_button(self, interaction: discord.Interaction, _: discord.ui self.installer.interface.view = None await interaction.message.edit(**self.installer.interface.fields) - await interaction.response.defer(thinking=True) + await interaction.response.defer() @discord.ui.button(style=discord.ButtonStyle.red, label="Exit") async def quit_button(self, interaction: discord.Interaction, _: discord.ui.Button): self.install_button.disabled = True + self.uninstall_button.disabled = True self.quit_button.disabled = True await interaction.message.edit(**self.installer.interface.fields) - await interaction.response.defer(thinking=True) + await interaction.response.defer() class InstallerGUI: From 5eca894f48b957fc61b247a71be1f2d4239a75ac Mon Sep 17 00:00:00 2001 From: Dotsian Date: Sat, 19 Apr 2025 23:47:47 -0400 Subject: [PATCH 127/133] Fix delete command --- DexScript/package/commands.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 1729f12..715683f 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -65,7 +65,10 @@ async def delete(self, ctx, model, identifier): ------------- DELETE > MODEL > IDENTIFIER """ - await Utils.get_model(model, identifier).delete() + model = await Utils.get_model(model, identifier) + + await model.delete() + await ctx.send(f"Deleted `{identifier}`") async def update(self, ctx, model, identifier, attribute, value=None): From e761b91500c1408311f3dbd08b5160085453432e Mon Sep 17 00:00:00 2001 From: Dotsian Date: Sun, 20 Apr 2025 00:05:04 -0400 Subject: [PATCH 128/133] Fix create command, specify model, and update DATAPOLICY.md --- DATAPOLICY.md | 5 ++++- DexScript/package/commands.py | 6 +++--- DexScript/package/utils.py | 6 ++++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/DATAPOLICY.md b/DATAPOLICY.md index 5b98041..1ff136e 100644 --- a/DATAPOLICY.md +++ b/DATAPOLICY.md @@ -8,7 +8,10 @@ We do not collect, store, or access any data from your application. DexScript on ## File Modifications -During the DexScript installation process, DexScript will modify your `ballsdex/core/bot.py` file. +During the DexScript installation process, DexScript will modify the following based on the Ballsdex version: + +- **2.22.0-** - `ballsdex/core/bot.py`. +- **2.22.0+** - `config.yml` DexScript will add a single line of code to allow the DexScript extension to load when your application starts. This modification is solely for the purpose of running DexScript. You can view the code added by checking the `DexScript/github/installer.py` file in the official DexScript GitHub repository. diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 715683f..d02efb5 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -55,7 +55,7 @@ async def create(self, ctx, model, identifier): CREATE > MODEL > IDENTIFIER """ await Utils.create_model(model.value, identifier) - await ctx.send(f"Created `{identifier}`") + await ctx.send(f"Created `{identifier}` {model.name.lower()}") async def delete(self, ctx, model, identifier): """ @@ -68,8 +68,8 @@ async def delete(self, ctx, model, identifier): model = await Utils.get_model(model, identifier) await model.delete() - - await ctx.send(f"Deleted `{identifier}`") + + await ctx.send(f"Deleted `{identifier}` {model.name.lower()}") async def update(self, ctx, model, identifier, attribute, value=None): """ diff --git a/DexScript/package/utils.py b/DexScript/package/utils.py index dc16c9a..b352ef8 100644 --- a/DexScript/package/utils.py +++ b/DexScript/package/utils.py @@ -267,8 +267,10 @@ async def create_model(model, identifier, fields_only=False): "Ignore": ["id", "short_name"], } + model_ids = Utils.models(True, lambda s: f"{str.lower(s)}_id") + for field, field_type in model._meta.fields_map.items(): - if field_type.null or field in special_list["Ignore"]: + if field_type.null or field in special_list["Ignore"] or field in model_ids: continue if field in special_list["Identifiers"]: @@ -284,7 +286,7 @@ async def create_model(model, identifier, fields_only=False): if instance is None: raise Exception(f"Could not find default {casing_field}") - fields[field] = instance.pk + fields[f"{field}_id"] = instance.pk case "BigIntField": fields[field] = 100**8 From e8facc3e55af43b66f12926971dabde2293ca605 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Mon, 21 Apr 2025 02:38:34 -0400 Subject: [PATCH 129/133] Minor changes --- DexScript/package/commands.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index d02efb5..7588ced 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -237,7 +237,7 @@ async def delete(self, ctx, model, attribute, value, tortoise_operator=None): await model.value.filter(**{casing_name: new_value}).delete() await ctx.send( - f"Deleted all `{model.name}` instances with a `{attribute}` " f"value of `{value}`" + f"Deleted all `{model.name}` instances with a `{attribute}` value of `{value}`" ) async def view(self, ctx, model, attribute, value, tortoise_operator=None): @@ -265,6 +265,12 @@ async def view(self, ctx, model, attribute, value, tortoise_operator=None): model.extra_data[0], flat=True ) + if instances == []: + await ctx.send( + f"No {model.name}s found with a `{attribute}` value of `{value}`" + ) + return + await Utils.message_list(ctx, instances) @@ -299,7 +305,7 @@ async def save(self, ctx, name): try: message = await self.bot.wait_for( "message", - check=lambda m: m.author == ctx.message.author, + check=lambda m: m.author == ctx.author and m.channel = ctx.channel, timeout=20, ) except asyncio.TimeoutError: From 28a5b98f7e0e9fc4feb5b84aee9d528013ddb6f5 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Mon, 21 Apr 2025 02:45:52 -0400 Subject: [PATCH 130/133] Fix image paths --- DexScript/package/commands.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 7588ced..12a05d2 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -86,8 +86,6 @@ async def update(self, ctx, model, identifier, attribute, value=None): returned_model = await Utils.get_model(model, identifier) self.attribute_error(model, attribute_name) - model_field = Utils.get_field(model.value, attribute_name) - image_fields = Utils.fetch_fields( model.value, lambda _, field_type: ( @@ -95,7 +93,7 @@ async def update(self, ctx, model, identifier, attribute, value=None): ), ) - if value is None and self.shared.attachments and model_field in image_fields: + if value is None and self.shared.attachments and attribute_name in image_fields: image_path = await Utils.save_file(self.shared.attachments.pop(0)) new_value = f"/{image_path}" @@ -141,8 +139,8 @@ async def view(self, ctx, model, identifier, attribute=None): new_attribute = getattr(returned_model, attribute_name) - if isinstance(new_attribute, str) and os.path.isfile(new_attribute[1:]): - await ctx.send(f"```{new_attribute}```", file=discord.File(new_attribute[1:])) + if isinstance(new_attribute, str) and Utils.is_image(new_attribute): + await ctx.send(f"```{new_attribute}```", file=discord.File(Utils.image_path(new_attribute))) return if attribute.type == Types.MODEL: From d66d5f3de073021df38081ca4c3850fdc0629878 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Mon, 21 Apr 2025 02:46:47 -0400 Subject: [PATCH 131/133] SyntaxError fix --- DexScript/package/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 12a05d2..75af41a 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -303,7 +303,7 @@ async def save(self, ctx, name): try: message = await self.bot.wait_for( "message", - check=lambda m: m.author == ctx.author and m.channel = ctx.channel, + check=lambda m: m.author == ctx.author and m.channel == ctx.channel, timeout=20, ) except asyncio.TimeoutError: From a9d3ec1a09ba2605c0adee2b6d4f75b2f5aac684 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Mon, 21 Apr 2025 02:48:40 -0400 Subject: [PATCH 132/133] Add suffix --- DexScript/package/commands.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 75af41a..aa1001c 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -105,7 +105,9 @@ async def update(self, ctx, model, identifier, attribute, value=None): setattr(returned_model, attribute_name, new_value) await returned_model.save(update_fields=[attribute_name]) - await ctx.send(f"Updated `{identifier}'s` {attribute} to `{value.name}`") + suffix = "" if value is None else f" to `{value.name}`" + + await ctx.send(f"Updated `{identifier}'s` {attribute}{suffix}") async def view(self, ctx, model, identifier, attribute=None): """ From 2b7bfc69fd3e30d1e8c35edd208227c70730cee3 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Mon, 21 Apr 2025 03:00:03 -0400 Subject: [PATCH 133/133] Poetry and Ruff --- DexScript/package/commands.py | 4 +- poetry.lock | 1061 ++++++++++++++++++--------------- pyproject.toml | 4 +- 3 files changed, 592 insertions(+), 477 deletions(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index aa1001c..830b07a 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -142,7 +142,9 @@ async def view(self, ctx, model, identifier, attribute=None): new_attribute = getattr(returned_model, attribute_name) if isinstance(new_attribute, str) and Utils.is_image(new_attribute): - await ctx.send(f"```{new_attribute}```", file=discord.File(Utils.image_path(new_attribute))) + await ctx.send( + f"```{new_attribute}```", file=discord.File(Utils.image_path(new_attribute)) + ) return if attribute.type == Types.MODEL: diff --git a/poetry.lock b/poetry.lock index 1a27b3c..9ff107d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,101 +1,106 @@ -# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" -version = "2.4.4" +version = "2.6.1" description = "Happy Eyeballs for asyncio" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "aiohappyeyeballs-2.4.4-py3-none-any.whl", hash = "sha256:a980909d50efcd44795c4afeca523296716d50cd756ddca6af8c65b996e27de8"}, - {file = "aiohappyeyeballs-2.4.4.tar.gz", hash = "sha256:5fdd7d87889c63183afc18ce9271f9b0a7d32c2303e394468dd45d514a757745"}, + {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, + {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, ] [[package]] name = "aiohttp" -version = "3.11.11" +version = "3.11.16" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "aiohttp-3.11.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a60804bff28662cbcf340a4d61598891f12eea3a66af48ecfdc975ceec21e3c8"}, - {file = "aiohttp-3.11.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b4fa1cb5f270fb3eab079536b764ad740bb749ce69a94d4ec30ceee1b5940d5"}, - {file = "aiohttp-3.11.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:731468f555656767cda219ab42e033355fe48c85fbe3ba83a349631541715ba2"}, - {file = "aiohttp-3.11.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb23d8bb86282b342481cad4370ea0853a39e4a32a0042bb52ca6bdde132df43"}, - {file = "aiohttp-3.11.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f047569d655f81cb70ea5be942ee5d4421b6219c3f05d131f64088c73bb0917f"}, - {file = "aiohttp-3.11.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd7659baae9ccf94ae5fe8bfaa2c7bc2e94d24611528395ce88d009107e00c6d"}, - {file = "aiohttp-3.11.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af01e42ad87ae24932138f154105e88da13ce7d202a6de93fafdafb2883a00ef"}, - {file = "aiohttp-3.11.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5854be2f3e5a729800bac57a8d76af464e160f19676ab6aea74bde18ad19d438"}, - {file = "aiohttp-3.11.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6526e5fb4e14f4bbf30411216780c9967c20c5a55f2f51d3abd6de68320cc2f3"}, - {file = "aiohttp-3.11.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:85992ee30a31835fc482468637b3e5bd085fa8fe9392ba0bdcbdc1ef5e9e3c55"}, - {file = "aiohttp-3.11.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:88a12ad8ccf325a8a5ed80e6d7c3bdc247d66175afedbe104ee2aaca72960d8e"}, - {file = "aiohttp-3.11.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0a6d3fbf2232e3a08c41eca81ae4f1dff3d8f1a30bae415ebe0af2d2458b8a33"}, - {file = "aiohttp-3.11.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:84a585799c58b795573c7fa9b84c455adf3e1d72f19a2bf498b54a95ae0d194c"}, - {file = "aiohttp-3.11.11-cp310-cp310-win32.whl", hash = "sha256:bfde76a8f430cf5c5584553adf9926534352251d379dcb266ad2b93c54a29745"}, - {file = "aiohttp-3.11.11-cp310-cp310-win_amd64.whl", hash = "sha256:0fd82b8e9c383af11d2b26f27a478640b6b83d669440c0a71481f7c865a51da9"}, - {file = "aiohttp-3.11.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ba74ec819177af1ef7f59063c6d35a214a8fde6f987f7661f4f0eecc468a8f76"}, - {file = "aiohttp-3.11.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4af57160800b7a815f3fe0eba9b46bf28aafc195555f1824555fa2cfab6c1538"}, - {file = "aiohttp-3.11.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ffa336210cf9cd8ed117011085817d00abe4c08f99968deef0013ea283547204"}, - {file = "aiohttp-3.11.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81b8fe282183e4a3c7a1b72f5ade1094ed1c6345a8f153506d114af5bf8accd9"}, - {file = "aiohttp-3.11.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3af41686ccec6a0f2bdc66686dc0f403c41ac2089f80e2214a0f82d001052c03"}, - {file = "aiohttp-3.11.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70d1f9dde0e5dd9e292a6d4d00058737052b01f3532f69c0c65818dac26dc287"}, - {file = "aiohttp-3.11.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:249cc6912405917344192b9f9ea5cd5b139d49e0d2f5c7f70bdfaf6b4dbf3a2e"}, - {file = "aiohttp-3.11.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0eb98d90b6690827dcc84c246811feeb4e1eea683c0eac6caed7549be9c84665"}, - {file = "aiohttp-3.11.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec82bf1fda6cecce7f7b915f9196601a1bd1a3079796b76d16ae4cce6d0ef89b"}, - {file = "aiohttp-3.11.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9fd46ce0845cfe28f108888b3ab17abff84ff695e01e73657eec3f96d72eef34"}, - {file = "aiohttp-3.11.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bd176afcf8f5d2aed50c3647d4925d0db0579d96f75a31e77cbaf67d8a87742d"}, - {file = "aiohttp-3.11.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ec2aa89305006fba9ffb98970db6c8221541be7bee4c1d027421d6f6df7d1ce2"}, - {file = "aiohttp-3.11.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:92cde43018a2e17d48bb09c79e4d4cb0e236de5063ce897a5e40ac7cb4878773"}, - {file = "aiohttp-3.11.11-cp311-cp311-win32.whl", hash = "sha256:aba807f9569455cba566882c8938f1a549f205ee43c27b126e5450dc9f83cc62"}, - {file = "aiohttp-3.11.11-cp311-cp311-win_amd64.whl", hash = "sha256:ae545f31489548c87b0cced5755cfe5a5308d00407000e72c4fa30b19c3220ac"}, - {file = "aiohttp-3.11.11-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e595c591a48bbc295ebf47cb91aebf9bd32f3ff76749ecf282ea7f9f6bb73886"}, - {file = "aiohttp-3.11.11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3ea1b59dc06396b0b424740a10a0a63974c725b1c64736ff788a3689d36c02d2"}, - {file = "aiohttp-3.11.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8811f3f098a78ffa16e0ea36dffd577eb031aea797cbdba81be039a4169e242c"}, - {file = "aiohttp-3.11.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7227b87a355ce1f4bf83bfae4399b1f5bb42e0259cb9405824bd03d2f4336a"}, - {file = "aiohttp-3.11.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d40f9da8cabbf295d3a9dae1295c69975b86d941bc20f0a087f0477fa0a66231"}, - {file = "aiohttp-3.11.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffb3dc385f6bb1568aa974fe65da84723210e5d9707e360e9ecb51f59406cd2e"}, - {file = "aiohttp-3.11.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8f5f7515f3552d899c61202d99dcb17d6e3b0de777900405611cd747cecd1b8"}, - {file = "aiohttp-3.11.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3499c7ffbfd9c6a3d8d6a2b01c26639da7e43d47c7b4f788016226b1e711caa8"}, - {file = "aiohttp-3.11.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8e2bf8029dbf0810c7bfbc3e594b51c4cc9101fbffb583a3923aea184724203c"}, - {file = "aiohttp-3.11.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b6212a60e5c482ef90f2d788835387070a88d52cf6241d3916733c9176d39eab"}, - {file = "aiohttp-3.11.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d119fafe7b634dbfa25a8c597718e69a930e4847f0b88e172744be24515140da"}, - {file = "aiohttp-3.11.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:6fba278063559acc730abf49845d0e9a9e1ba74f85f0ee6efd5803f08b285853"}, - {file = "aiohttp-3.11.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:92fc484e34b733704ad77210c7957679c5c3877bd1e6b6d74b185e9320cc716e"}, - {file = "aiohttp-3.11.11-cp312-cp312-win32.whl", hash = "sha256:9f5b3c1ed63c8fa937a920b6c1bec78b74ee09593b3f5b979ab2ae5ef60d7600"}, - {file = "aiohttp-3.11.11-cp312-cp312-win_amd64.whl", hash = "sha256:1e69966ea6ef0c14ee53ef7a3d68b564cc408121ea56c0caa2dc918c1b2f553d"}, - {file = "aiohttp-3.11.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:541d823548ab69d13d23730a06f97460f4238ad2e5ed966aaf850d7c369782d9"}, - {file = "aiohttp-3.11.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:929f3ed33743a49ab127c58c3e0a827de0664bfcda566108989a14068f820194"}, - {file = "aiohttp-3.11.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0882c2820fd0132240edbb4a51eb8ceb6eef8181db9ad5291ab3332e0d71df5f"}, - {file = "aiohttp-3.11.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b63de12e44935d5aca7ed7ed98a255a11e5cb47f83a9fded7a5e41c40277d104"}, - {file = "aiohttp-3.11.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa54f8ef31d23c506910c21163f22b124facb573bff73930735cf9fe38bf7dff"}, - {file = "aiohttp-3.11.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a344d5dc18074e3872777b62f5f7d584ae4344cd6006c17ba12103759d407af3"}, - {file = "aiohttp-3.11.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7fb429ab1aafa1f48578eb315ca45bd46e9c37de11fe45c7f5f4138091e2f1"}, - {file = "aiohttp-3.11.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c341c7d868750e31961d6d8e60ff040fb9d3d3a46d77fd85e1ab8e76c3e9a5c4"}, - {file = "aiohttp-3.11.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ed9ee95614a71e87f1a70bc81603f6c6760128b140bc4030abe6abaa988f1c3d"}, - {file = "aiohttp-3.11.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:de8d38f1c2810fa2a4f1d995a2e9c70bb8737b18da04ac2afbf3971f65781d87"}, - {file = "aiohttp-3.11.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a9b7371665d4f00deb8f32208c7c5e652059b0fda41cf6dbcac6114a041f1cc2"}, - {file = "aiohttp-3.11.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:620598717fce1b3bd14dd09947ea53e1ad510317c85dda2c9c65b622edc96b12"}, - {file = "aiohttp-3.11.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bf8d9bfee991d8acc72d060d53860f356e07a50f0e0d09a8dfedea1c554dd0d5"}, - {file = "aiohttp-3.11.11-cp313-cp313-win32.whl", hash = "sha256:9d73ee3725b7a737ad86c2eac5c57a4a97793d9f442599bea5ec67ac9f4bdc3d"}, - {file = "aiohttp-3.11.11-cp313-cp313-win_amd64.whl", hash = "sha256:c7a06301c2fb096bdb0bd25fe2011531c1453b9f2c163c8031600ec73af1cc99"}, - {file = "aiohttp-3.11.11-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3e23419d832d969f659c208557de4a123e30a10d26e1e14b73431d3c13444c2e"}, - {file = "aiohttp-3.11.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:21fef42317cf02e05d3b09c028712e1d73a9606f02467fd803f7c1f39cc59add"}, - {file = "aiohttp-3.11.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1f21bb8d0235fc10c09ce1d11ffbd40fc50d3f08a89e4cf3a0c503dc2562247a"}, - {file = "aiohttp-3.11.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1642eceeaa5ab6c9b6dfeaaa626ae314d808188ab23ae196a34c9d97efb68350"}, - {file = "aiohttp-3.11.11-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2170816e34e10f2fd120f603e951630f8a112e1be3b60963a1f159f5699059a6"}, - {file = "aiohttp-3.11.11-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8be8508d110d93061197fd2d6a74f7401f73b6d12f8822bbcd6d74f2b55d71b1"}, - {file = "aiohttp-3.11.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4eed954b161e6b9b65f6be446ed448ed3921763cc432053ceb606f89d793927e"}, - {file = "aiohttp-3.11.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6c9af134da4bc9b3bd3e6a70072509f295d10ee60c697826225b60b9959acdd"}, - {file = "aiohttp-3.11.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:44167fc6a763d534a6908bdb2592269b4bf30a03239bcb1654781adf5e49caf1"}, - {file = "aiohttp-3.11.11-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:479b8c6ebd12aedfe64563b85920525d05d394b85f166b7873c8bde6da612f9c"}, - {file = "aiohttp-3.11.11-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:10b4ff0ad793d98605958089fabfa350e8e62bd5d40aa65cdc69d6785859f94e"}, - {file = "aiohttp-3.11.11-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:b540bd67cfb54e6f0865ceccd9979687210d7ed1a1cc8c01f8e67e2f1e883d28"}, - {file = "aiohttp-3.11.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1dac54e8ce2ed83b1f6b1a54005c87dfed139cf3f777fdc8afc76e7841101226"}, - {file = "aiohttp-3.11.11-cp39-cp39-win32.whl", hash = "sha256:568c1236b2fde93b7720f95a890741854c1200fba4a3471ff48b2934d2d93fd3"}, - {file = "aiohttp-3.11.11-cp39-cp39-win_amd64.whl", hash = "sha256:943a8b052e54dfd6439fd7989f67fc6a7f2138d0a2cf0a7de5f18aa4fe7eb3b1"}, - {file = "aiohttp-3.11.11.tar.gz", hash = "sha256:bb49c7f1e6ebf3821a42d81d494f538107610c3a705987f53068546b0e90303e"}, + {file = "aiohttp-3.11.16-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb46bb0f24813e6cede6cc07b1961d4b04f331f7112a23b5e21f567da4ee50aa"}, + {file = "aiohttp-3.11.16-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:54eb3aead72a5c19fad07219acd882c1643a1027fbcdefac9b502c267242f955"}, + {file = "aiohttp-3.11.16-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:38bea84ee4fe24ebcc8edeb7b54bf20f06fd53ce4d2cc8b74344c5b9620597fd"}, + {file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0666afbe984f6933fe72cd1f1c3560d8c55880a0bdd728ad774006eb4241ecd"}, + {file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ba92a2d9ace559a0a14b03d87f47e021e4fa7681dc6970ebbc7b447c7d4b7cd"}, + {file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ad1d59fd7114e6a08c4814983bb498f391c699f3c78712770077518cae63ff7"}, + {file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b88a2bf26965f2015a771381624dd4b0839034b70d406dc74fd8be4cc053e3"}, + {file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:576f5ca28d1b3276026f7df3ec841ae460e0fc3aac2a47cbf72eabcfc0f102e1"}, + {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a2a450bcce4931b295fc0848f384834c3f9b00edfc2150baafb4488c27953de6"}, + {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:37dcee4906454ae377be5937ab2a66a9a88377b11dd7c072df7a7c142b63c37c"}, + {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4d0c970c0d602b1017e2067ff3b7dac41c98fef4f7472ec2ea26fd8a4e8c2149"}, + {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:004511d3413737700835e949433536a2fe95a7d0297edd911a1e9705c5b5ea43"}, + {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:c15b2271c44da77ee9d822552201180779e5e942f3a71fb74e026bf6172ff287"}, + {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ad9509ffb2396483ceacb1eee9134724443ee45b92141105a4645857244aecc8"}, + {file = "aiohttp-3.11.16-cp310-cp310-win32.whl", hash = "sha256:634d96869be6c4dc232fc503e03e40c42d32cfaa51712aee181e922e61d74814"}, + {file = "aiohttp-3.11.16-cp310-cp310-win_amd64.whl", hash = "sha256:938f756c2b9374bbcc262a37eea521d8a0e6458162f2a9c26329cc87fdf06534"}, + {file = "aiohttp-3.11.16-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8cb0688a8d81c63d716e867d59a9ccc389e97ac7037ebef904c2b89334407180"}, + {file = "aiohttp-3.11.16-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ad1fb47da60ae1ddfb316f0ff16d1f3b8e844d1a1e154641928ea0583d486ed"}, + {file = "aiohttp-3.11.16-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:df7db76400bf46ec6a0a73192b14c8295bdb9812053f4fe53f4e789f3ea66bbb"}, + {file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc3a145479a76ad0ed646434d09216d33d08eef0d8c9a11f5ae5cdc37caa3540"}, + {file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d007aa39a52d62373bd23428ba4a2546eed0e7643d7bf2e41ddcefd54519842c"}, + {file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6ddd90d9fb4b501c97a4458f1c1720e42432c26cb76d28177c5b5ad4e332601"}, + {file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a2f451849e6b39e5c226803dcacfa9c7133e9825dcefd2f4e837a2ec5a3bb98"}, + {file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8df6612df74409080575dca38a5237282865408016e65636a76a2eb9348c2567"}, + {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:78e6e23b954644737e385befa0deb20233e2dfddf95dd11e9db752bdd2a294d3"}, + {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:696ef00e8a1f0cec5e30640e64eca75d8e777933d1438f4facc9c0cdf288a810"}, + {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e3538bc9fe1b902bef51372462e3d7c96fce2b566642512138a480b7adc9d508"}, + {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3ab3367bb7f61ad18793fea2ef71f2d181c528c87948638366bf1de26e239183"}, + {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:56a3443aca82abda0e07be2e1ecb76a050714faf2be84256dae291182ba59049"}, + {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:61c721764e41af907c9d16b6daa05a458f066015abd35923051be8705108ed17"}, + {file = "aiohttp-3.11.16-cp311-cp311-win32.whl", hash = "sha256:3e061b09f6fa42997cf627307f220315e313ece74907d35776ec4373ed718b86"}, + {file = "aiohttp-3.11.16-cp311-cp311-win_amd64.whl", hash = "sha256:745f1ed5e2c687baefc3c5e7b4304e91bf3e2f32834d07baaee243e349624b24"}, + {file = "aiohttp-3.11.16-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:911a6e91d08bb2c72938bc17f0a2d97864c531536b7832abee6429d5296e5b27"}, + {file = "aiohttp-3.11.16-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac13b71761e49d5f9e4d05d33683bbafef753e876e8e5a7ef26e937dd766713"}, + {file = "aiohttp-3.11.16-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fd36c119c5d6551bce374fcb5c19269638f8d09862445f85a5a48596fd59f4bb"}, + {file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d489d9778522fbd0f8d6a5c6e48e3514f11be81cb0a5954bdda06f7e1594b321"}, + {file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69a2cbd61788d26f8f1e626e188044834f37f6ae3f937bd9f08b65fc9d7e514e"}, + {file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd464ba806e27ee24a91362ba3621bfc39dbbb8b79f2e1340201615197370f7c"}, + {file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce63ae04719513dd2651202352a2beb9f67f55cb8490c40f056cea3c5c355ce"}, + {file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09b00dd520d88eac9d1768439a59ab3d145065c91a8fab97f900d1b5f802895e"}, + {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7f6428fee52d2bcf96a8aa7b62095b190ee341ab0e6b1bcf50c615d7966fd45b"}, + {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:13ceac2c5cdcc3f64b9015710221ddf81c900c5febc505dbd8f810e770011540"}, + {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fadbb8f1d4140825069db3fedbbb843290fd5f5bc0a5dbd7eaf81d91bf1b003b"}, + {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6a792ce34b999fbe04a7a71a90c74f10c57ae4c51f65461a411faa70e154154e"}, + {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f4065145bf69de124accdd17ea5f4dc770da0a6a6e440c53f6e0a8c27b3e635c"}, + {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa73e8c2656a3653ae6c307b3f4e878a21f87859a9afab228280ddccd7369d71"}, + {file = "aiohttp-3.11.16-cp312-cp312-win32.whl", hash = "sha256:f244b8e541f414664889e2c87cac11a07b918cb4b540c36f7ada7bfa76571ea2"}, + {file = "aiohttp-3.11.16-cp312-cp312-win_amd64.whl", hash = "sha256:23a15727fbfccab973343b6d1b7181bfb0b4aa7ae280f36fd2f90f5476805682"}, + {file = "aiohttp-3.11.16-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a3814760a1a700f3cfd2f977249f1032301d0a12c92aba74605cfa6ce9f78489"}, + {file = "aiohttp-3.11.16-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9b751a6306f330801665ae69270a8a3993654a85569b3469662efaad6cf5cc50"}, + {file = "aiohttp-3.11.16-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ad497f38a0d6c329cb621774788583ee12321863cd4bd9feee1effd60f2ad133"}, + {file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca37057625693d097543bd88076ceebeb248291df9d6ca8481349efc0b05dcd0"}, + {file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5abcbba9f4b463a45c8ca8b7720891200658f6f46894f79517e6cd11f3405ca"}, + {file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f420bfe862fb357a6d76f2065447ef6f484bc489292ac91e29bc65d2d7a2c84d"}, + {file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58ede86453a6cf2d6ce40ef0ca15481677a66950e73b0a788917916f7e35a0bb"}, + {file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fdec0213244c39973674ca2a7f5435bf74369e7d4e104d6c7473c81c9bcc8c4"}, + {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:72b1b03fb4655c1960403c131740755ec19c5898c82abd3961c364c2afd59fe7"}, + {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:780df0d837276276226a1ff803f8d0fa5f8996c479aeef52eb040179f3156cbd"}, + {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ecdb8173e6c7aa09eee342ac62e193e6904923bd232e76b4157ac0bfa670609f"}, + {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a6db7458ab89c7d80bc1f4e930cc9df6edee2200127cfa6f6e080cf619eddfbd"}, + {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2540ddc83cc724b13d1838026f6a5ad178510953302a49e6d647f6e1de82bc34"}, + {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3b4e6db8dc4879015b9955778cfb9881897339c8fab7b3676f8433f849425913"}, + {file = "aiohttp-3.11.16-cp313-cp313-win32.whl", hash = "sha256:493910ceb2764f792db4dc6e8e4b375dae1b08f72e18e8f10f18b34ca17d0979"}, + {file = "aiohttp-3.11.16-cp313-cp313-win_amd64.whl", hash = "sha256:42864e70a248f5f6a49fdaf417d9bc62d6e4d8ee9695b24c5916cb4bb666c802"}, + {file = "aiohttp-3.11.16-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bbcba75fe879ad6fd2e0d6a8d937f34a571f116a0e4db37df8079e738ea95c71"}, + {file = "aiohttp-3.11.16-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:87a6e922b2b2401e0b0cf6b976b97f11ec7f136bfed445e16384fbf6fd5e8602"}, + {file = "aiohttp-3.11.16-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccf10f16ab498d20e28bc2b5c1306e9c1512f2840f7b6a67000a517a4b37d5ee"}, + {file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb3d0cc5cdb926090748ea60172fa8a213cec728bd6c54eae18b96040fcd6227"}, + {file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d07502cc14ecd64f52b2a74ebbc106893d9a9717120057ea9ea1fd6568a747e7"}, + {file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:776c8e959a01e5e8321f1dec77964cb6101020a69d5a94cd3d34db6d555e01f7"}, + {file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0902e887b0e1d50424112f200eb9ae3dfed6c0d0a19fc60f633ae5a57c809656"}, + {file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e87fd812899aa78252866ae03a048e77bd11b80fb4878ce27c23cade239b42b2"}, + {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0a950c2eb8ff17361abd8c85987fd6076d9f47d040ebffce67dce4993285e973"}, + {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:c10d85e81d0b9ef87970ecbdbfaeec14a361a7fa947118817fcea8e45335fa46"}, + {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:7951decace76a9271a1ef181b04aa77d3cc309a02a51d73826039003210bdc86"}, + {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14461157d8426bcb40bd94deb0450a6fa16f05129f7da546090cebf8f3123b0f"}, + {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9756d9b9d4547e091f99d554fbba0d2a920aab98caa82a8fb3d3d9bee3c9ae85"}, + {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:87944bd16b7fe6160607f6a17808abd25f17f61ae1e26c47a491b970fb66d8cb"}, + {file = "aiohttp-3.11.16-cp39-cp39-win32.whl", hash = "sha256:92b7ee222e2b903e0a4b329a9943d432b3767f2d5029dbe4ca59fb75223bbe2e"}, + {file = "aiohttp-3.11.16-cp39-cp39-win_amd64.whl", hash = "sha256:17ae4664031aadfbcb34fd40ffd90976671fa0c0286e6c4113989f78bebab37a"}, + {file = "aiohttp-3.11.16.tar.gz", hash = "sha256:16f8a2c9538c14a557b4d309ed4d0a7c60f0253e8ed7b6c9a2859a7582f8b1b8"}, ] [package.dependencies] @@ -108,7 +113,7 @@ propcache = ">=0.2.0" yarl = ">=1.17.0,<2.0" [package.extras] -speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] +speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.2.0) ; sys_platform == \"linux\" or sys_platform == \"darwin\"", "brotlicffi ; platform_python_implementation != \"CPython\""] [[package]] name = "aiosignal" @@ -127,145 +132,203 @@ frozenlist = ">=1.1.0" [[package]] name = "attrs" -version = "24.3.0" +version = "25.3.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "attrs-24.3.0-py3-none-any.whl", hash = "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308"}, - {file = "attrs-24.3.0.tar.gz", hash = "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff"}, + {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, + {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, ] [package.extras] -benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] +benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] +tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] + +[[package]] +name = "audioop-lts" +version = "0.2.1" +description = "LTS Port of Python audioop" +optional = false +python-versions = ">=3.13" +groups = ["main"] +markers = "python_version >= \"3.13\"" +files = [ + {file = "audioop_lts-0.2.1-cp313-abi3-macosx_10_13_universal2.whl", hash = "sha256:fd1345ae99e17e6910f47ce7d52673c6a1a70820d78b67de1b7abb3af29c426a"}, + {file = "audioop_lts-0.2.1-cp313-abi3-macosx_10_13_x86_64.whl", hash = "sha256:e175350da05d2087e12cea8e72a70a1a8b14a17e92ed2022952a4419689ede5e"}, + {file = "audioop_lts-0.2.1-cp313-abi3-macosx_11_0_arm64.whl", hash = "sha256:4a8dd6a81770f6ecf019c4b6d659e000dc26571b273953cef7cd1d5ce2ff3ae6"}, + {file = "audioop_lts-0.2.1-cp313-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1cd3c0b6f2ca25c7d2b1c3adeecbe23e65689839ba73331ebc7d893fcda7ffe"}, + {file = "audioop_lts-0.2.1-cp313-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff3f97b3372c97782e9c6d3d7fdbe83bce8f70de719605bd7ee1839cd1ab360a"}, + {file = "audioop_lts-0.2.1-cp313-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a351af79edefc2a1bd2234bfd8b339935f389209943043913a919df4b0f13300"}, + {file = "audioop_lts-0.2.1-cp313-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2aeb6f96f7f6da80354330470b9134d81b4cf544cdd1c549f2f45fe964d28059"}, + {file = "audioop_lts-0.2.1-cp313-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c589f06407e8340e81962575fcffbba1e92671879a221186c3d4662de9fe804e"}, + {file = "audioop_lts-0.2.1-cp313-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fbae5d6925d7c26e712f0beda5ed69ebb40e14212c185d129b8dfbfcc335eb48"}, + {file = "audioop_lts-0.2.1-cp313-abi3-musllinux_1_2_i686.whl", hash = "sha256:d2d5434717f33117f29b5691fbdf142d36573d751716249a288fbb96ba26a281"}, + {file = "audioop_lts-0.2.1-cp313-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:f626a01c0a186b08f7ff61431c01c055961ee28769591efa8800beadd27a2959"}, + {file = "audioop_lts-0.2.1-cp313-abi3-musllinux_1_2_s390x.whl", hash = "sha256:05da64e73837f88ee5c6217d732d2584cf638003ac72df124740460531e95e47"}, + {file = "audioop_lts-0.2.1-cp313-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:56b7a0a4dba8e353436f31a932f3045d108a67b5943b30f85a5563f4d8488d77"}, + {file = "audioop_lts-0.2.1-cp313-abi3-win32.whl", hash = "sha256:6e899eb8874dc2413b11926b5fb3857ec0ab55222840e38016a6ba2ea9b7d5e3"}, + {file = "audioop_lts-0.2.1-cp313-abi3-win_amd64.whl", hash = "sha256:64562c5c771fb0a8b6262829b9b4f37a7b886c01b4d3ecdbae1d629717db08b4"}, + {file = "audioop_lts-0.2.1-cp313-abi3-win_arm64.whl", hash = "sha256:c45317debeb64002e980077642afbd977773a25fa3dfd7ed0c84dccfc1fafcb0"}, + {file = "audioop_lts-0.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:3827e3fce6fee4d69d96a3d00cd2ab07f3c0d844cb1e44e26f719b34a5b15455"}, + {file = "audioop_lts-0.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:161249db9343b3c9780ca92c0be0d1ccbfecdbccac6844f3d0d44b9c4a00a17f"}, + {file = "audioop_lts-0.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5b7b4ff9de7a44e0ad2618afdc2ac920b91f4a6d3509520ee65339d4acde5abf"}, + {file = "audioop_lts-0.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72e37f416adb43b0ced93419de0122b42753ee74e87070777b53c5d2241e7fab"}, + {file = "audioop_lts-0.2.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:534ce808e6bab6adb65548723c8cbe189a3379245db89b9d555c4210b4aaa9b6"}, + {file = "audioop_lts-0.2.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2de9b6fb8b1cf9f03990b299a9112bfdf8b86b6987003ca9e8a6c4f56d39543"}, + {file = "audioop_lts-0.2.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f24865991b5ed4b038add5edbf424639d1358144f4e2a3e7a84bc6ba23e35074"}, + {file = "audioop_lts-0.2.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bdb3b7912ccd57ea53197943f1bbc67262dcf29802c4a6df79ec1c715d45a78"}, + {file = "audioop_lts-0.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:120678b208cca1158f0a12d667af592e067f7a50df9adc4dc8f6ad8d065a93fb"}, + {file = "audioop_lts-0.2.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:54cd4520fc830b23c7d223693ed3e1b4d464997dd3abc7c15dce9a1f9bd76ab2"}, + {file = "audioop_lts-0.2.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:d6bd20c7a10abcb0fb3d8aaa7508c0bf3d40dfad7515c572014da4b979d3310a"}, + {file = "audioop_lts-0.2.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:f0ed1ad9bd862539ea875fb339ecb18fcc4148f8d9908f4502df28f94d23491a"}, + {file = "audioop_lts-0.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e1af3ff32b8c38a7d900382646e91f2fc515fd19dea37e9392275a5cbfdbff63"}, + {file = "audioop_lts-0.2.1-cp313-cp313t-win32.whl", hash = "sha256:f51bb55122a89f7a0817d7ac2319744b4640b5b446c4c3efcea5764ea99ae509"}, + {file = "audioop_lts-0.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f0f2f336aa2aee2bce0b0dcc32bbba9178995454c7b979cf6ce086a8801e14c7"}, + {file = "audioop_lts-0.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:78bfb3703388c780edf900be66e07de5a3d4105ca8e8720c5c4d67927e0b15d0"}, + {file = "audioop_lts-0.2.1.tar.gz", hash = "sha256:e81268da0baa880431b68b1308ab7257eb33f356e57a5f9b1f915dfb13dd1387"}, +] [[package]] name = "discord-py" -version = "2.4.0" +version = "2.5.2" description = "A Python wrapper for the Discord API" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "discord.py-2.4.0-py3-none-any.whl", hash = "sha256:b8af6711c70f7e62160bfbecb55be699b5cb69d007426759ab8ab06b1bd77d1d"}, - {file = "discord_py-2.4.0.tar.gz", hash = "sha256:d07cb2a223a185873a1d0ee78b9faa9597e45b3f6186df21a95cec1e9bcdc9a5"}, + {file = "discord_py-2.5.2-py3-none-any.whl", hash = "sha256:81f23a17c50509ffebe0668441cb80c139e74da5115305f70e27ce821361295a"}, + {file = "discord_py-2.5.2.tar.gz", hash = "sha256:01cd362023bfea1a4a1d43f5280b5ef00cad2c7eba80098909f98bf28e578524"}, ] [package.dependencies] aiohttp = ">=3.7.4,<4" +audioop-lts = {version = "*", markers = "python_version >= \"3.13\""} [package.extras] -docs = ["sphinx (==4.4.0)", "sphinx-inline-tabs (==2023.4.21)", "sphinxcontrib-applehelp (==1.0.4)", "sphinxcontrib-devhelp (==1.0.2)", "sphinxcontrib-htmlhelp (==2.0.1)", "sphinxcontrib-jsmath (==1.0.1)", "sphinxcontrib-qthelp (==1.0.3)", "sphinxcontrib-serializinghtml (==1.1.5)", "sphinxcontrib-trio (==1.1.2)", "sphinxcontrib-websupport (==1.2.4)", "typing-extensions (>=4.3,<5)"] -speed = ["Brotli", "aiodns (>=1.1)", "cchardet (==2.1.7)", "orjson (>=3.5.4)"] -test = ["coverage[toml]", "pytest", "pytest-asyncio", "pytest-cov", "pytest-mock", "typing-extensions (>=4.3,<5)", "tzdata"] +dev = ["black (==22.6)", "typing_extensions (>=4.3,<5)"] +docs = ["imghdr-lts (==1.0.0) ; python_version >= \"3.13\"", "sphinx (==4.4.0)", "sphinx-inline-tabs (==2023.4.21)", "sphinxcontrib-applehelp (==1.0.4)", "sphinxcontrib-devhelp (==1.0.2)", "sphinxcontrib-htmlhelp (==2.0.1)", "sphinxcontrib-jsmath (==1.0.1)", "sphinxcontrib-qthelp (==1.0.3)", "sphinxcontrib-serializinghtml (==1.1.5)", "sphinxcontrib-websupport (==1.2.4)", "sphinxcontrib_trio (==1.1.2)", "typing-extensions (>=4.3,<5)"] +speed = ["Brotli", "aiodns (>=1.1) ; sys_platform != \"win32\"", "cchardet (==2.1.7) ; python_version < \"3.10\"", "orjson (>=3.5.4)", "zstandard (>=0.23.0)"] +test = ["coverage[toml]", "pytest", "pytest-asyncio", "pytest-cov", "pytest-mock", "typing-extensions (>=4.3,<5)", "tzdata ; sys_platform == \"win32\""] voice = ["PyNaCl (>=1.3.0,<1.6)"] [[package]] name = "frozenlist" -version = "1.5.0" +version = "1.6.0" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a"}, - {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb"}, - {file = "frozenlist-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15538c0cbf0e4fa11d1e3a71f823524b0c46299aed6e10ebb4c2089abd8c3bec"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e79225373c317ff1e35f210dd5f1344ff31066ba8067c307ab60254cd3a78ad5"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9272fa73ca71266702c4c3e2d4a28553ea03418e591e377a03b8e3659d94fa76"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:498524025a5b8ba81695761d78c8dd7382ac0b052f34e66939c42df860b8ff17"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b5278ed9d50fe610185ecd23c55d8b307d75ca18e94c0e7de328089ac5dcba"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f3c8c1dacd037df16e85227bac13cca58c30da836c6f936ba1df0c05d046d8d"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2ac49a9bedb996086057b75bf93538240538c6d9b38e57c82d51f75a73409d2"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e66cc454f97053b79c2ab09c17fbe3c825ea6b4de20baf1be28919460dd7877f"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3ba5f9a0dfed20337d3e966dc359784c9f96503674c2faf015f7fe8e96798c"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6321899477db90bdeb9299ac3627a6a53c7399c8cd58d25da094007402b039ab"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76e4753701248476e6286f2ef492af900ea67d9706a0155335a40ea21bf3b2f5"}, - {file = "frozenlist-1.5.0-cp310-cp310-win32.whl", hash = "sha256:977701c081c0241d0955c9586ffdd9ce44f7a7795df39b9151cd9a6fd0ce4cfb"}, - {file = "frozenlist-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:189f03b53e64144f90990d29a27ec4f7997d91ed3d01b51fa39d2dbe77540fd4"}, - {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30"}, - {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5"}, - {file = "frozenlist-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf"}, - {file = "frozenlist-1.5.0-cp311-cp311-win32.whl", hash = "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942"}, - {file = "frozenlist-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d"}, - {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21"}, - {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d"}, - {file = "frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f"}, - {file = "frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8"}, - {file = "frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f"}, - {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953"}, - {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0"}, - {file = "frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03"}, - {file = "frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c"}, - {file = "frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28"}, - {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:dd94994fc91a6177bfaafd7d9fd951bc8689b0a98168aa26b5f543868548d3ca"}, - {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0da8bbec082bf6bf18345b180958775363588678f64998c2b7609e34719b10"}, - {file = "frozenlist-1.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:73f2e31ea8dd7df61a359b731716018c2be196e5bb3b74ddba107f694fbd7604"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:828afae9f17e6de596825cf4228ff28fbdf6065974e5ac1410cecc22f699d2b3"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1577515d35ed5649d52ab4319db757bb881ce3b2b796d7283e6634d99ace307"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2150cc6305a2c2ab33299453e2968611dacb970d2283a14955923062c8d00b10"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a72b7a6e3cd2725eff67cd64c8f13335ee18fc3c7befc05aed043d24c7b9ccb9"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c16d2fa63e0800723139137d667e1056bee1a1cf7965153d2d104b62855e9b99"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:17dcc32fc7bda7ce5875435003220a457bcfa34ab7924a49a1c19f55b6ee185c"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:97160e245ea33d8609cd2b8fd997c850b56db147a304a262abc2b3be021a9171"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f1e6540b7fa044eee0bb5111ada694cf3dc15f2b0347ca125ee9ca984d5e9e6e"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:91d6c171862df0a6c61479d9724f22efb6109111017c87567cfeb7b5d1449fdf"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c1fac3e2ace2eb1052e9f7c7db480818371134410e1f5c55d65e8f3ac6d1407e"}, - {file = "frozenlist-1.5.0-cp38-cp38-win32.whl", hash = "sha256:b97f7b575ab4a8af9b7bc1d2ef7f29d3afee2226bd03ca3875c16451ad5a7723"}, - {file = "frozenlist-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:374ca2dabdccad8e2a76d40b1d037f5bd16824933bf7bcea3e59c891fd4a0923"}, - {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9bbcdfaf4af7ce002694a4e10a0159d5a8d20056a12b05b45cea944a4953f972"}, - {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1893f948bf6681733aaccf36c5232c231e3b5166d607c5fa77773611df6dc336"}, - {file = "frozenlist-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2b5e23253bb709ef57a8e95e6ae48daa9ac5f265637529e4ce6b003a37b2621f"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f253985bb515ecd89629db13cb58d702035ecd8cfbca7d7a7e29a0e6d39af5f"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04a5c6babd5e8fb7d3c871dc8b321166b80e41b637c31a995ed844a6139942b6"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9fe0f1c29ba24ba6ff6abf688cb0b7cf1efab6b6aa6adc55441773c252f7411"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:226d72559fa19babe2ccd920273e767c96a49b9d3d38badd7c91a0fdeda8ea08"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b731db116ab3aedec558573c1a5eec78822b32292fe4f2f0345b7f697745c2"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:366d8f93e3edfe5a918c874702f78faac300209a4d5bf38352b2c1bdc07a766d"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1b96af8c582b94d381a1c1f51ffaedeb77c821c690ea5f01da3d70a487dd0a9b"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c03eff4a41bd4e38415cbed054bbaff4a075b093e2394b6915dca34a40d1e38b"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:50cf5e7ee9b98f22bdecbabf3800ae78ddcc26e4a435515fc72d97903e8488e0"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1e76bfbc72353269c44e0bc2cfe171900fbf7f722ad74c9a7b638052afe6a00c"}, - {file = "frozenlist-1.5.0-cp39-cp39-win32.whl", hash = "sha256:666534d15ba8f0fda3f53969117383d5dc021266b3c1a42c9ec4855e4b58b9d3"}, - {file = "frozenlist-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:5c28f4b5dbef8a0d8aad0d4de24d1e9e981728628afaf4ea0792f5d0939372f0"}, - {file = "frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3"}, - {file = "frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817"}, + {file = "frozenlist-1.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e6e558ea1e47fd6fa8ac9ccdad403e5dd5ecc6ed8dda94343056fa4277d5c65e"}, + {file = "frozenlist-1.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f4b3cd7334a4bbc0c472164f3744562cb72d05002cc6fcf58adb104630bbc352"}, + {file = "frozenlist-1.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9799257237d0479736e2b4c01ff26b5c7f7694ac9692a426cb717f3dc02fff9b"}, + {file = "frozenlist-1.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a7bb0fe1f7a70fb5c6f497dc32619db7d2cdd53164af30ade2f34673f8b1fc"}, + {file = "frozenlist-1.6.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:36d2fc099229f1e4237f563b2a3e0ff7ccebc3999f729067ce4e64a97a7f2869"}, + {file = "frozenlist-1.6.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f27a9f9a86dcf00708be82359db8de86b80d029814e6693259befe82bb58a106"}, + {file = "frozenlist-1.6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75ecee69073312951244f11b8627e3700ec2bfe07ed24e3a685a5979f0412d24"}, + {file = "frozenlist-1.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2c7d5aa19714b1b01a0f515d078a629e445e667b9da869a3cd0e6fe7dec78bd"}, + {file = "frozenlist-1.6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69bbd454f0fb23b51cadc9bdba616c9678e4114b6f9fa372d462ff2ed9323ec8"}, + {file = "frozenlist-1.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7daa508e75613809c7a57136dec4871a21bca3080b3a8fc347c50b187df4f00c"}, + {file = "frozenlist-1.6.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:89ffdb799154fd4d7b85c56d5fa9d9ad48946619e0eb95755723fffa11022d75"}, + {file = "frozenlist-1.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:920b6bd77d209931e4c263223381d63f76828bec574440f29eb497cf3394c249"}, + {file = "frozenlist-1.6.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d3ceb265249fb401702fce3792e6b44c1166b9319737d21495d3611028d95769"}, + {file = "frozenlist-1.6.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:52021b528f1571f98a7d4258c58aa8d4b1a96d4f01d00d51f1089f2e0323cb02"}, + {file = "frozenlist-1.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0f2ca7810b809ed0f1917293050163c7654cefc57a49f337d5cd9de717b8fad3"}, + {file = "frozenlist-1.6.0-cp310-cp310-win32.whl", hash = "sha256:0e6f8653acb82e15e5443dba415fb62a8732b68fe09936bb6d388c725b57f812"}, + {file = "frozenlist-1.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:f1a39819a5a3e84304cd286e3dc62a549fe60985415851b3337b6f5cc91907f1"}, + {file = "frozenlist-1.6.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae8337990e7a45683548ffb2fee1af2f1ed08169284cd829cdd9a7fa7470530d"}, + {file = "frozenlist-1.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8c952f69dd524558694818a461855f35d36cc7f5c0adddce37e962c85d06eac0"}, + {file = "frozenlist-1.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8f5fef13136c4e2dee91bfb9a44e236fff78fc2cd9f838eddfc470c3d7d90afe"}, + {file = "frozenlist-1.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:716bbba09611b4663ecbb7cd022f640759af8259e12a6ca939c0a6acd49eedba"}, + {file = "frozenlist-1.6.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7b8c4dc422c1a3ffc550b465090e53b0bf4839047f3e436a34172ac67c45d595"}, + {file = "frozenlist-1.6.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b11534872256e1666116f6587a1592ef395a98b54476addb5e8d352925cb5d4a"}, + {file = "frozenlist-1.6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c6eceb88aaf7221f75be6ab498dc622a151f5f88d536661af3ffc486245a626"}, + {file = "frozenlist-1.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62c828a5b195570eb4b37369fcbbd58e96c905768d53a44d13044355647838ff"}, + {file = "frozenlist-1.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1c6bd2c6399920c9622362ce95a7d74e7f9af9bfec05fff91b8ce4b9647845a"}, + {file = "frozenlist-1.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:49ba23817781e22fcbd45fd9ff2b9b8cdb7b16a42a4851ab8025cae7b22e96d0"}, + {file = "frozenlist-1.6.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:431ef6937ae0f853143e2ca67d6da76c083e8b1fe3df0e96f3802fd37626e606"}, + {file = "frozenlist-1.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9d124b38b3c299ca68433597ee26b7819209cb8a3a9ea761dfe9db3a04bba584"}, + {file = "frozenlist-1.6.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:118e97556306402e2b010da1ef21ea70cb6d6122e580da64c056b96f524fbd6a"}, + {file = "frozenlist-1.6.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fb3b309f1d4086b5533cf7bbcf3f956f0ae6469664522f1bde4feed26fba60f1"}, + {file = "frozenlist-1.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54dece0d21dce4fdb188a1ffc555926adf1d1c516e493c2914d7c370e454bc9e"}, + {file = "frozenlist-1.6.0-cp311-cp311-win32.whl", hash = "sha256:654e4ba1d0b2154ca2f096bed27461cf6160bc7f504a7f9a9ef447c293caf860"}, + {file = "frozenlist-1.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:3e911391bffdb806001002c1f860787542f45916c3baf764264a52765d5a5603"}, + {file = "frozenlist-1.6.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c5b9e42ace7d95bf41e19b87cec8f262c41d3510d8ad7514ab3862ea2197bfb1"}, + {file = "frozenlist-1.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ca9973735ce9f770d24d5484dcb42f68f135351c2fc81a7a9369e48cf2998a29"}, + {file = "frozenlist-1.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6ac40ec76041c67b928ca8aaffba15c2b2ee3f5ae8d0cb0617b5e63ec119ca25"}, + {file = "frozenlist-1.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95b7a8a3180dfb280eb044fdec562f9b461614c0ef21669aea6f1d3dac6ee576"}, + {file = "frozenlist-1.6.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c444d824e22da6c9291886d80c7d00c444981a72686e2b59d38b285617cb52c8"}, + {file = "frozenlist-1.6.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb52c8166499a8150bfd38478248572c924c003cbb45fe3bcd348e5ac7c000f9"}, + {file = "frozenlist-1.6.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b35298b2db9c2468106278537ee529719228950a5fdda686582f68f247d1dc6e"}, + {file = "frozenlist-1.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d108e2d070034f9d57210f22fefd22ea0d04609fc97c5f7f5a686b3471028590"}, + {file = "frozenlist-1.6.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e1be9111cb6756868ac242b3c2bd1f09d9aea09846e4f5c23715e7afb647103"}, + {file = "frozenlist-1.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:94bb451c664415f02f07eef4ece976a2c65dcbab9c2f1705b7031a3a75349d8c"}, + {file = "frozenlist-1.6.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d1a686d0b0949182b8faddea596f3fc11f44768d1f74d4cad70213b2e139d821"}, + {file = "frozenlist-1.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ea8e59105d802c5a38bdbe7362822c522230b3faba2aa35c0fa1765239b7dd70"}, + {file = "frozenlist-1.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:abc4e880a9b920bc5020bf6a431a6bb40589d9bca3975c980495f63632e8382f"}, + {file = "frozenlist-1.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9a79713adfe28830f27a3c62f6b5406c37376c892b05ae070906f07ae4487046"}, + {file = "frozenlist-1.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a0318c2068e217a8f5e3b85e35899f5a19e97141a45bb925bb357cfe1daf770"}, + {file = "frozenlist-1.6.0-cp312-cp312-win32.whl", hash = "sha256:853ac025092a24bb3bf09ae87f9127de9fe6e0c345614ac92536577cf956dfcc"}, + {file = "frozenlist-1.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:2bdfe2d7e6c9281c6e55523acd6c2bf77963cb422fdc7d142fb0cb6621b66878"}, + {file = "frozenlist-1.6.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1d7fb014fe0fbfee3efd6a94fc635aeaa68e5e1720fe9e57357f2e2c6e1a647e"}, + {file = "frozenlist-1.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01bcaa305a0fdad12745502bfd16a1c75b14558dabae226852f9159364573117"}, + {file = "frozenlist-1.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b314faa3051a6d45da196a2c495e922f987dc848e967d8cfeaee8a0328b1cd4"}, + {file = "frozenlist-1.6.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da62fecac21a3ee10463d153549d8db87549a5e77eefb8c91ac84bb42bb1e4e3"}, + {file = "frozenlist-1.6.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1eb89bf3454e2132e046f9599fbcf0a4483ed43b40f545551a39316d0201cd1"}, + {file = "frozenlist-1.6.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18689b40cb3936acd971f663ccb8e2589c45db5e2c5f07e0ec6207664029a9c"}, + {file = "frozenlist-1.6.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e67ddb0749ed066b1a03fba812e2dcae791dd50e5da03be50b6a14d0c1a9ee45"}, + {file = "frozenlist-1.6.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fc5e64626e6682638d6e44398c9baf1d6ce6bc236d40b4b57255c9d3f9761f1f"}, + {file = "frozenlist-1.6.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:437cfd39564744ae32ad5929e55b18ebd88817f9180e4cc05e7d53b75f79ce85"}, + {file = "frozenlist-1.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:62dd7df78e74d924952e2feb7357d826af8d2f307557a779d14ddf94d7311be8"}, + {file = "frozenlist-1.6.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a66781d7e4cddcbbcfd64de3d41a61d6bdde370fc2e38623f30b2bd539e84a9f"}, + {file = "frozenlist-1.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:482fe06e9a3fffbcd41950f9d890034b4a54395c60b5e61fae875d37a699813f"}, + {file = "frozenlist-1.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e4f9373c500dfc02feea39f7a56e4f543e670212102cc2eeb51d3a99c7ffbde6"}, + {file = "frozenlist-1.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e69bb81de06827147b7bfbaeb284d85219fa92d9f097e32cc73675f279d70188"}, + {file = "frozenlist-1.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7613d9977d2ab4a9141dde4a149f4357e4065949674c5649f920fec86ecb393e"}, + {file = "frozenlist-1.6.0-cp313-cp313-win32.whl", hash = "sha256:4def87ef6d90429f777c9d9de3961679abf938cb6b7b63d4a7eb8a268babfce4"}, + {file = "frozenlist-1.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:37a8a52c3dfff01515e9bbbee0e6063181362f9de3db2ccf9bc96189b557cbfd"}, + {file = "frozenlist-1.6.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:46138f5a0773d064ff663d273b309b696293d7a7c00a0994c5c13a5078134b64"}, + {file = "frozenlist-1.6.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f88bc0a2b9c2a835cb888b32246c27cdab5740059fb3688852bf91e915399b91"}, + {file = "frozenlist-1.6.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:777704c1d7655b802c7850255639672e90e81ad6fa42b99ce5ed3fbf45e338dd"}, + {file = "frozenlist-1.6.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85ef8d41764c7de0dcdaf64f733a27352248493a85a80661f3c678acd27e31f2"}, + {file = "frozenlist-1.6.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:da5cb36623f2b846fb25009d9d9215322318ff1c63403075f812b3b2876c8506"}, + {file = "frozenlist-1.6.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cbb56587a16cf0fb8acd19e90ff9924979ac1431baea8681712716a8337577b0"}, + {file = "frozenlist-1.6.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6154c3ba59cda3f954c6333025369e42c3acd0c6e8b6ce31eb5c5b8116c07e0"}, + {file = "frozenlist-1.6.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e8246877afa3f1ae5c979fe85f567d220f86a50dc6c493b9b7d8191181ae01e"}, + {file = "frozenlist-1.6.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b0f6cce16306d2e117cf9db71ab3a9e8878a28176aeaf0dbe35248d97b28d0c"}, + {file = "frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1b8e8cd8032ba266f91136d7105706ad57770f3522eac4a111d77ac126a25a9b"}, + {file = "frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:e2ada1d8515d3ea5378c018a5f6d14b4994d4036591a52ceaf1a1549dec8e1ad"}, + {file = "frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:cdb2c7f071e4026c19a3e32b93a09e59b12000751fc9b0b7758da899e657d215"}, + {file = "frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:03572933a1969a6d6ab509d509e5af82ef80d4a5d4e1e9f2e1cdd22c77a3f4d2"}, + {file = "frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:77effc978947548b676c54bbd6a08992759ea6f410d4987d69feea9cd0919911"}, + {file = "frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a2bda8be77660ad4089caf2223fdbd6db1858462c4b85b67fbfa22102021e497"}, + {file = "frozenlist-1.6.0-cp313-cp313t-win32.whl", hash = "sha256:a4d96dc5bcdbd834ec6b0f91027817214216b5b30316494d2b1aebffb87c534f"}, + {file = "frozenlist-1.6.0-cp313-cp313t-win_amd64.whl", hash = "sha256:e18036cb4caa17ea151fd5f3d70be9d354c99eb8cf817a3ccde8a7873b074348"}, + {file = "frozenlist-1.6.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:536a1236065c29980c15c7229fbb830dedf809708c10e159b8136534233545f0"}, + {file = "frozenlist-1.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ed5e3a4462ff25ca84fb09e0fada8ea267df98a450340ead4c91b44857267d70"}, + {file = "frozenlist-1.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e19c0fc9f4f030fcae43b4cdec9e8ab83ffe30ec10c79a4a43a04d1af6c5e1ad"}, + {file = "frozenlist-1.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7c608f833897501dac548585312d73a7dca028bf3b8688f0d712b7acfaf7fb3"}, + {file = "frozenlist-1.6.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0dbae96c225d584f834b8d3cc688825911960f003a85cb0fd20b6e5512468c42"}, + {file = "frozenlist-1.6.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:625170a91dd7261a1d1c2a0c1a353c9e55d21cd67d0852185a5fef86587e6f5f"}, + {file = "frozenlist-1.6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1db8b2fc7ee8a940b547a14c10e56560ad3ea6499dc6875c354e2335812f739d"}, + {file = "frozenlist-1.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4da6fc43048b648275a220e3a61c33b7fff65d11bdd6dcb9d9c145ff708b804c"}, + {file = "frozenlist-1.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef8e7e8f2f3820c5f175d70fdd199b79e417acf6c72c5d0aa8f63c9f721646f"}, + {file = "frozenlist-1.6.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aa733d123cc78245e9bb15f29b44ed9e5780dc6867cfc4e544717b91f980af3b"}, + {file = "frozenlist-1.6.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ba7f8d97152b61f22d7f59491a781ba9b177dd9f318486c5fbc52cde2db12189"}, + {file = "frozenlist-1.6.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:56a0b8dd6d0d3d971c91f1df75e824986667ccce91e20dca2023683814344791"}, + {file = "frozenlist-1.6.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:5c9e89bf19ca148efcc9e3c44fd4c09d5af85c8a7dd3dbd0da1cb83425ef4983"}, + {file = "frozenlist-1.6.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:1330f0a4376587face7637dfd245380a57fe21ae8f9d360c1c2ef8746c4195fa"}, + {file = "frozenlist-1.6.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2187248203b59625566cac53572ec8c2647a140ee2738b4e36772930377a533c"}, + {file = "frozenlist-1.6.0-cp39-cp39-win32.whl", hash = "sha256:2b8cf4cfea847d6c12af06091561a89740f1f67f331c3fa8623391905e878530"}, + {file = "frozenlist-1.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:1255d5d64328c5a0d066ecb0f02034d086537925f1f04b50b1ae60d37afbf572"}, + {file = "frozenlist-1.6.0-py3-none-any.whl", hash = "sha256:535eec9987adb04701266b92745d6cdcef2e77669299359c3009c3404dd5d191"}, + {file = "frozenlist-1.6.0.tar.gz", hash = "sha256:b99655c32c1c8e06d111e7f41c06c29a5318cb1835df23a45518e02a47c63b68"}, ] [[package]] @@ -285,324 +348,374 @@ all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2 [[package]] name = "multidict" -version = "6.1.0" +version = "6.4.3" description = "multidict implementation" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, - {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"}, - {file = "multidict-6.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a114d03b938376557927ab23f1e950827c3b893ccb94b62fd95d430fd0e5cf53"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1c416351ee6271b2f49b56ad7f308072f6f44b37118d69c2cad94f3fa8a40d5"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b5d83030255983181005e6cfbac1617ce9746b219bc2aad52201ad121226581"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e97b5e938051226dc025ec80980c285b053ffb1e25a3db2a3aa3bc046bf7f56"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d618649d4e70ac6efcbba75be98b26ef5078faad23592f9b51ca492953012429"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10524ebd769727ac77ef2278390fb0068d83f3acb7773792a5080f2b0abf7748"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ff3827aef427c89a25cc96ded1759271a93603aba9fb977a6d264648ebf989db"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:06809f4f0f7ab7ea2cabf9caca7d79c22c0758b58a71f9d32943ae13c7ace056"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f179dee3b863ab1c59580ff60f9d99f632f34ccb38bf67a33ec6b3ecadd0fd76"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:aaed8b0562be4a0876ee3b6946f6869b7bcdb571a5d1496683505944e268b160"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c8b88a2ccf5493b6c8da9076fb151ba106960a2df90c2633f342f120751a9e7"}, - {file = "multidict-6.1.0-cp310-cp310-win32.whl", hash = "sha256:4a9cb68166a34117d6646c0023c7b759bf197bee5ad4272f420a0141d7eb03a0"}, - {file = "multidict-6.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:20b9b5fbe0b88d0bdef2012ef7dee867f874b72528cf1d08f1d59b0e3850129d"}, - {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6"}, - {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156"}, - {file = "multidict-6.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753"}, - {file = "multidict-6.1.0-cp311-cp311-win32.whl", hash = "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80"}, - {file = "multidict-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926"}, - {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa"}, - {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436"}, - {file = "multidict-6.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3"}, - {file = "multidict-6.1.0-cp312-cp312-win32.whl", hash = "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133"}, - {file = "multidict-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1"}, - {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008"}, - {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f"}, - {file = "multidict-6.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6"}, - {file = "multidict-6.1.0-cp313-cp313-win32.whl", hash = "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81"}, - {file = "multidict-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774"}, - {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:db7457bac39421addd0c8449933ac32d8042aae84a14911a757ae6ca3eef1392"}, - {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d094ddec350a2fb899fec68d8353c78233debde9b7d8b4beeafa70825f1c281a"}, - {file = "multidict-6.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5845c1fd4866bb5dd3125d89b90e57ed3138241540897de748cdf19de8a2fca2"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9079dfc6a70abe341f521f78405b8949f96db48da98aeb43f9907f342f627cdc"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3914f5aaa0f36d5d60e8ece6a308ee1c9784cd75ec8151062614657a114c4478"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c08be4f460903e5a9d0f76818db3250f12e9c344e79314d1d570fc69d7f4eae4"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d093be959277cb7dee84b801eb1af388b6ad3ca6a6b6bf1ed7585895789d027d"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3702ea6872c5a2a4eeefa6ffd36b042e9773f05b1f37ae3ef7264b1163c2dcf6"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2090f6a85cafc5b2db085124d752757c9d251548cedabe9bd31afe6363e0aff2"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:f67f217af4b1ff66c68a87318012de788dd95fcfeb24cc889011f4e1c7454dfd"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:189f652a87e876098bbc67b4da1049afb5f5dfbaa310dd67c594b01c10388db6"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:6bb5992037f7a9eff7991ebe4273ea7f51f1c1c511e6a2ce511d0e7bdb754492"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f4c2b9e770c4e393876e35a7046879d195cd123b4f116d299d442b335bcd"}, - {file = "multidict-6.1.0-cp38-cp38-win32.whl", hash = "sha256:e27bbb6d14416713a8bd7aaa1313c0fc8d44ee48d74497a0ff4c3a1b6ccb5167"}, - {file = "multidict-6.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:22f3105d4fb15c8f57ff3959a58fcab6ce36814486500cd7485651230ad4d4ef"}, - {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4e18b656c5e844539d506a0a06432274d7bd52a7487e6828c63a63d69185626c"}, - {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a185f876e69897a6f3325c3f19f26a297fa058c5e456bfcff8015e9a27e83ae1"}, - {file = "multidict-6.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab7c4ceb38d91570a650dba194e1ca87c2b543488fe9309b4212694174fd539c"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e617fb6b0b6953fffd762669610c1c4ffd05632c138d61ac7e14ad187870669c"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16e5f4bf4e603eb1fdd5d8180f1a25f30056f22e55ce51fb3d6ad4ab29f7d96f"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c035da3f544b1882bac24115f3e2e8760f10a0107614fc9839fd232200b875"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:957cf8e4b6e123a9eea554fa7ebc85674674b713551de587eb318a2df3e00255"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:483a6aea59cb89904e1ceabd2b47368b5600fb7de78a6e4a2c2987b2d256cf30"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:87701f25a2352e5bf7454caa64757642734da9f6b11384c1f9d1a8e699758057"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:682b987361e5fd7a139ed565e30d81fd81e9629acc7d925a205366877d8c8657"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce2186a7df133a9c895dea3331ddc5ddad42cdd0d1ea2f0a51e5d161e4762f28"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9f636b730f7e8cb19feb87094949ba54ee5357440b9658b2a32a5ce4bce53972"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:73eae06aa53af2ea5270cc066dcaf02cc60d2994bbb2c4ef5764949257d10f43"}, - {file = "multidict-6.1.0-cp39-cp39-win32.whl", hash = "sha256:1ca0083e80e791cffc6efce7660ad24af66c8d4079d2a750b29001b53ff59ada"}, - {file = "multidict-6.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:aa466da5b15ccea564bdab9c89175c762bc12825f4659c11227f515cee76fa4a"}, - {file = "multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506"}, - {file = "multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a"}, + {file = "multidict-6.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:32a998bd8a64ca48616eac5a8c1cc4fa38fb244a3facf2eeb14abe186e0f6cc5"}, + {file = "multidict-6.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a54ec568f1fc7f3c313c2f3b16e5db346bf3660e1309746e7fccbbfded856188"}, + {file = "multidict-6.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a7be07e5df178430621c716a63151165684d3e9958f2bbfcb644246162007ab7"}, + {file = "multidict-6.4.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b128dbf1c939674a50dd0b28f12c244d90e5015e751a4f339a96c54f7275e291"}, + {file = "multidict-6.4.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b9cb19dfd83d35b6ff24a4022376ea6e45a2beba8ef3f0836b8a4b288b6ad685"}, + {file = "multidict-6.4.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3cf62f8e447ea2c1395afa289b332e49e13d07435369b6f4e41f887db65b40bf"}, + {file = "multidict-6.4.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:909f7d43ff8f13d1adccb6a397094adc369d4da794407f8dd592c51cf0eae4b1"}, + {file = "multidict-6.4.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0bb8f8302fbc7122033df959e25777b0b7659b1fd6bcb9cb6bed76b5de67afef"}, + {file = "multidict-6.4.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:224b79471b4f21169ea25ebc37ed6f058040c578e50ade532e2066562597b8a9"}, + {file = "multidict-6.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a7bd27f7ab3204f16967a6f899b3e8e9eb3362c0ab91f2ee659e0345445e0078"}, + {file = "multidict-6.4.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:99592bd3162e9c664671fd14e578a33bfdba487ea64bcb41d281286d3c870ad7"}, + {file = "multidict-6.4.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a62d78a1c9072949018cdb05d3c533924ef8ac9bcb06cbf96f6d14772c5cd451"}, + {file = "multidict-6.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:3ccdde001578347e877ca4f629450973c510e88e8865d5aefbcb89b852ccc666"}, + {file = "multidict-6.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:eccb67b0e78aa2e38a04c5ecc13bab325a43e5159a181a9d1a6723db913cbb3c"}, + {file = "multidict-6.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8b6fcf6054fc4114a27aa865f8840ef3d675f9316e81868e0ad5866184a6cba5"}, + {file = "multidict-6.4.3-cp310-cp310-win32.whl", hash = "sha256:f92c7f62d59373cd93bc9969d2da9b4b21f78283b1379ba012f7ee8127b3152e"}, + {file = "multidict-6.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:b57e28dbc031d13916b946719f213c494a517b442d7b48b29443e79610acd887"}, + {file = "multidict-6.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f6f19170197cc29baccd33ccc5b5d6a331058796485857cf34f7635aa25fb0cd"}, + {file = "multidict-6.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f2882bf27037eb687e49591690e5d491e677272964f9ec7bc2abbe09108bdfb8"}, + {file = "multidict-6.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fbf226ac85f7d6b6b9ba77db4ec0704fde88463dc17717aec78ec3c8546c70ad"}, + {file = "multidict-6.4.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e329114f82ad4b9dd291bef614ea8971ec119ecd0f54795109976de75c9a852"}, + {file = "multidict-6.4.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1f4e0334d7a555c63f5c8952c57ab6f1c7b4f8c7f3442df689fc9f03df315c08"}, + {file = "multidict-6.4.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:740915eb776617b57142ce0bb13b7596933496e2f798d3d15a20614adf30d229"}, + {file = "multidict-6.4.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255dac25134d2b141c944b59a0d2f7211ca12a6d4779f7586a98b4b03ea80508"}, + {file = "multidict-6.4.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4e8535bd4d741039b5aad4285ecd9b902ef9e224711f0b6afda6e38d7ac02c7"}, + {file = "multidict-6.4.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c433a33be000dd968f5750722eaa0991037be0be4a9d453eba121774985bc8"}, + {file = "multidict-6.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4eb33b0bdc50acd538f45041f5f19945a1f32b909b76d7b117c0c25d8063df56"}, + {file = "multidict-6.4.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:75482f43465edefd8a5d72724887ccdcd0c83778ded8f0cb1e0594bf71736cc0"}, + {file = "multidict-6.4.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ce5b3082e86aee80b3925ab4928198450d8e5b6466e11501fe03ad2191c6d777"}, + {file = "multidict-6.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e413152e3212c4d39f82cf83c6f91be44bec9ddea950ce17af87fbf4e32ca6b2"}, + {file = "multidict-6.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:8aac2eeff69b71f229a405c0a4b61b54bade8e10163bc7b44fcd257949620618"}, + {file = "multidict-6.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ab583ac203af1d09034be41458feeab7863c0635c650a16f15771e1386abf2d7"}, + {file = "multidict-6.4.3-cp311-cp311-win32.whl", hash = "sha256:1b2019317726f41e81154df636a897de1bfe9228c3724a433894e44cd2512378"}, + {file = "multidict-6.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:43173924fa93c7486402217fab99b60baf78d33806af299c56133a3755f69589"}, + {file = "multidict-6.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1f1c2f58f08b36f8475f3ec6f5aeb95270921d418bf18f90dffd6be5c7b0e676"}, + {file = "multidict-6.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:26ae9ad364fc61b936fb7bf4c9d8bd53f3a5b4417142cd0be5c509d6f767e2f1"}, + {file = "multidict-6.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:659318c6c8a85f6ecfc06b4e57529e5a78dfdd697260cc81f683492ad7e9435a"}, + {file = "multidict-6.4.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1eb72c741fd24d5a28242ce72bb61bc91f8451877131fa3fe930edb195f7054"}, + {file = "multidict-6.4.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3cd06d88cb7398252284ee75c8db8e680aa0d321451132d0dba12bc995f0adcc"}, + {file = "multidict-6.4.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4543d8dc6470a82fde92b035a92529317191ce993533c3c0c68f56811164ed07"}, + {file = "multidict-6.4.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:30a3ebdc068c27e9d6081fca0e2c33fdf132ecea703a72ea216b81a66860adde"}, + {file = "multidict-6.4.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b038f10e23f277153f86f95c777ba1958bcd5993194fda26a1d06fae98b2f00c"}, + {file = "multidict-6.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c605a2b2dc14282b580454b9b5d14ebe0668381a3a26d0ac39daa0ca115eb2ae"}, + {file = "multidict-6.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8bd2b875f4ca2bb527fe23e318ddd509b7df163407b0fb717df229041c6df5d3"}, + {file = "multidict-6.4.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c2e98c840c9c8e65c0e04b40c6c5066c8632678cd50c8721fdbcd2e09f21a507"}, + {file = "multidict-6.4.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:66eb80dd0ab36dbd559635e62fba3083a48a252633164857a1d1684f14326427"}, + {file = "multidict-6.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c23831bdee0a2a3cf21be057b5e5326292f60472fb6c6f86392bbf0de70ba731"}, + {file = "multidict-6.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1535cec6443bfd80d028052e9d17ba6ff8a5a3534c51d285ba56c18af97e9713"}, + {file = "multidict-6.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3b73e7227681f85d19dec46e5b881827cd354aabe46049e1a61d2f9aaa4e285a"}, + {file = "multidict-6.4.3-cp312-cp312-win32.whl", hash = "sha256:8eac0c49df91b88bf91f818e0a24c1c46f3622978e2c27035bfdca98e0e18124"}, + {file = "multidict-6.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:11990b5c757d956cd1db7cb140be50a63216af32cd6506329c2c59d732d802db"}, + {file = "multidict-6.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a76534263d03ae0cfa721fea40fd2b5b9d17a6f85e98025931d41dc49504474"}, + {file = "multidict-6.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:805031c2f599eee62ac579843555ed1ce389ae00c7e9f74c2a1b45e0564a88dd"}, + {file = "multidict-6.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c56c179839d5dcf51d565132185409d1d5dd8e614ba501eb79023a6cab25576b"}, + {file = "multidict-6.4.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c64f4ddb3886dd8ab71b68a7431ad4aa01a8fa5be5b11543b29674f29ca0ba3"}, + {file = "multidict-6.4.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3002a856367c0b41cad6784f5b8d3ab008eda194ed7864aaa58f65312e2abcac"}, + {file = "multidict-6.4.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d75e621e7d887d539d6e1d789f0c64271c250276c333480a9e1de089611f790"}, + {file = "multidict-6.4.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:995015cf4a3c0d72cbf453b10a999b92c5629eaf3a0c3e1efb4b5c1f602253bb"}, + {file = "multidict-6.4.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2b0fabae7939d09d7d16a711468c385272fa1b9b7fb0d37e51143585d8e72e0"}, + {file = "multidict-6.4.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:61ed4d82f8a1e67eb9eb04f8587970d78fe7cddb4e4d6230b77eda23d27938f9"}, + {file = "multidict-6.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:062428944a8dc69df9fdc5d5fc6279421e5f9c75a9ee3f586f274ba7b05ab3c8"}, + {file = "multidict-6.4.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:b90e27b4674e6c405ad6c64e515a505c6d113b832df52fdacb6b1ffd1fa9a1d1"}, + {file = "multidict-6.4.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7d50d4abf6729921e9613d98344b74241572b751c6b37feed75fb0c37bd5a817"}, + {file = "multidict-6.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:43fe10524fb0a0514be3954be53258e61d87341008ce4914f8e8b92bee6f875d"}, + {file = "multidict-6.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:236966ca6c472ea4e2d3f02f6673ebfd36ba3f23159c323f5a496869bc8e47c9"}, + {file = "multidict-6.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:422a5ec315018e606473ba1f5431e064cf8b2a7468019233dcf8082fabad64c8"}, + {file = "multidict-6.4.3-cp313-cp313-win32.whl", hash = "sha256:f901a5aace8e8c25d78960dcc24c870c8d356660d3b49b93a78bf38eb682aac3"}, + {file = "multidict-6.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:1c152c49e42277bc9a2f7b78bd5fa10b13e88d1b0328221e7aef89d5c60a99a5"}, + {file = "multidict-6.4.3-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:be8751869e28b9c0d368d94f5afcb4234db66fe8496144547b4b6d6a0645cfc6"}, + {file = "multidict-6.4.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0d4b31f8a68dccbcd2c0ea04f0e014f1defc6b78f0eb8b35f2265e8716a6df0c"}, + {file = "multidict-6.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:032efeab3049e37eef2ff91271884303becc9e54d740b492a93b7e7266e23756"}, + {file = "multidict-6.4.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e78006af1a7c8a8007e4f56629d7252668344442f66982368ac06522445e375"}, + {file = "multidict-6.4.3-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:daeac9dd30cda8703c417e4fddccd7c4dc0c73421a0b54a7da2713be125846be"}, + {file = "multidict-6.4.3-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f6f90700881438953eae443a9c6f8a509808bc3b185246992c4233ccee37fea"}, + {file = "multidict-6.4.3-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f84627997008390dd15762128dcf73c3365f4ec0106739cde6c20a07ed198ec8"}, + {file = "multidict-6.4.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3307b48cd156153b117c0ea54890a3bdbf858a5b296ddd40dc3852e5f16e9b02"}, + {file = "multidict-6.4.3-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ead46b0fa1dcf5af503a46e9f1c2e80b5d95c6011526352fa5f42ea201526124"}, + {file = "multidict-6.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1748cb2743bedc339d63eb1bca314061568793acd603a6e37b09a326334c9f44"}, + {file = "multidict-6.4.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:acc9fa606f76fc111b4569348cc23a771cb52c61516dcc6bcef46d612edb483b"}, + {file = "multidict-6.4.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:31469d5832b5885adeb70982e531ce86f8c992334edd2f2254a10fa3182ac504"}, + {file = "multidict-6.4.3-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ba46b51b6e51b4ef7bfb84b82f5db0dc5e300fb222a8a13b8cd4111898a869cf"}, + {file = "multidict-6.4.3-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:389cfefb599edf3fcfd5f64c0410da686f90f5f5e2c4d84e14f6797a5a337af4"}, + {file = "multidict-6.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:64bc2bbc5fba7b9db5c2c8d750824f41c6994e3882e6d73c903c2afa78d091e4"}, + {file = "multidict-6.4.3-cp313-cp313t-win32.whl", hash = "sha256:0ecdc12ea44bab2807d6b4a7e5eef25109ab1c82a8240d86d3c1fc9f3b72efd5"}, + {file = "multidict-6.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:7146a8742ea71b5d7d955bffcef58a9e6e04efba704b52a460134fefd10a8208"}, + {file = "multidict-6.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5427a2679e95a642b7f8b0f761e660c845c8e6fe3141cddd6b62005bd133fc21"}, + {file = "multidict-6.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:24a8caa26521b9ad09732972927d7b45b66453e6ebd91a3c6a46d811eeb7349b"}, + {file = "multidict-6.4.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6b5a272bc7c36a2cd1b56ddc6bff02e9ce499f9f14ee4a45c45434ef083f2459"}, + {file = "multidict-6.4.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edf74dc5e212b8c75165b435c43eb0d5e81b6b300a938a4eb82827119115e840"}, + {file = "multidict-6.4.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9f35de41aec4b323c71f54b0ca461ebf694fb48bec62f65221f52e0017955b39"}, + {file = "multidict-6.4.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae93e0ff43b6f6892999af64097b18561691ffd835e21a8348a441e256592e1f"}, + {file = "multidict-6.4.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e3929269e9d7eff905d6971d8b8c85e7dbc72c18fb99c8eae6fe0a152f2e343"}, + {file = "multidict-6.4.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb6214fe1750adc2a1b801a199d64b5a67671bf76ebf24c730b157846d0e90d2"}, + {file = "multidict-6.4.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d79cf5c0c6284e90f72123f4a3e4add52d6c6ebb4a9054e88df15b8d08444c6"}, + {file = "multidict-6.4.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2427370f4a255262928cd14533a70d9738dfacadb7563bc3b7f704cc2360fc4e"}, + {file = "multidict-6.4.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:fbd8d737867912b6c5f99f56782b8cb81f978a97b4437a1c476de90a3e41c9a1"}, + {file = "multidict-6.4.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0ee1bf613c448997f73fc4efb4ecebebb1c02268028dd4f11f011f02300cf1e8"}, + {file = "multidict-6.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:578568c4ba5f2b8abd956baf8b23790dbfdc953e87d5b110bce343b4a54fc9e7"}, + {file = "multidict-6.4.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:a059ad6b80de5b84b9fa02a39400319e62edd39d210b4e4f8c4f1243bdac4752"}, + {file = "multidict-6.4.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:dd53893675b729a965088aaadd6a1f326a72b83742b056c1065bdd2e2a42b4df"}, + {file = "multidict-6.4.3-cp39-cp39-win32.whl", hash = "sha256:abcfed2c4c139f25c2355e180bcc077a7cae91eefbb8b3927bb3f836c9586f1f"}, + {file = "multidict-6.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:b1b389ae17296dd739015d5ddb222ee99fd66adeae910de21ac950e00979d897"}, + {file = "multidict-6.4.3-py3-none-any.whl", hash = "sha256:59fe01ee8e2a1e8ceb3f6dbb216b09c8d9f4ef1c22c4fc825d045a147fa2ebc9"}, + {file = "multidict-6.4.3.tar.gz", hash = "sha256:3ada0b058c9f213c5f95ba301f922d402ac234f1111a7d8fd70f1b99f3c281ec"}, ] [[package]] name = "propcache" -version = "0.2.1" +version = "0.3.1" description = "Accelerated property cache" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "propcache-0.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6b3f39a85d671436ee3d12c017f8fdea38509e4f25b28eb25877293c98c243f6"}, - {file = "propcache-0.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d51fbe4285d5db5d92a929e3e21536ea3dd43732c5b177c7ef03f918dff9f2"}, - {file = "propcache-0.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6445804cf4ec763dc70de65a3b0d9954e868609e83850a47ca4f0cb64bd79fea"}, - {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9479aa06a793c5aeba49ce5c5692ffb51fcd9a7016e017d555d5e2b0045d212"}, - {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9631c5e8b5b3a0fda99cb0d29c18133bca1e18aea9effe55adb3da1adef80d3"}, - {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3156628250f46a0895f1f36e1d4fbe062a1af8718ec3ebeb746f1d23f0c5dc4d"}, - {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b6fb63ae352e13748289f04f37868099e69dba4c2b3e271c46061e82c745634"}, - {file = "propcache-0.2.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:887d9b0a65404929641a9fabb6452b07fe4572b269d901d622d8a34a4e9043b2"}, - {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a96dc1fa45bd8c407a0af03b2d5218392729e1822b0c32e62c5bf7eeb5fb3958"}, - {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a7e65eb5c003a303b94aa2c3852ef130230ec79e349632d030e9571b87c4698c"}, - {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:999779addc413181912e984b942fbcc951be1f5b3663cd80b2687758f434c583"}, - {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:19a0f89a7bb9d8048d9c4370c9c543c396e894c76be5525f5e1ad287f1750ddf"}, - {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1ac2f5fe02fa75f56e1ad473f1175e11f475606ec9bd0be2e78e4734ad575034"}, - {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:574faa3b79e8ebac7cb1d7930f51184ba1ccf69adfdec53a12f319a06030a68b"}, - {file = "propcache-0.2.1-cp310-cp310-win32.whl", hash = "sha256:03ff9d3f665769b2a85e6157ac8b439644f2d7fd17615a82fa55739bc97863f4"}, - {file = "propcache-0.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:2d3af2e79991102678f53e0dbf4c35de99b6b8b58f29a27ca0325816364caaba"}, - {file = "propcache-0.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ffc3cca89bb438fb9c95c13fc874012f7b9466b89328c3c8b1aa93cdcfadd16"}, - {file = "propcache-0.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f174bbd484294ed9fdf09437f889f95807e5f229d5d93588d34e92106fbf6717"}, - {file = "propcache-0.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:70693319e0b8fd35dd863e3e29513875eb15c51945bf32519ef52927ca883bc3"}, - {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b480c6a4e1138e1aa137c0079b9b6305ec6dcc1098a8ca5196283e8a49df95a9"}, - {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d27b84d5880f6d8aa9ae3edb253c59d9f6642ffbb2c889b78b60361eed449787"}, - {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:857112b22acd417c40fa4595db2fe28ab900c8c5fe4670c7989b1c0230955465"}, - {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf6c4150f8c0e32d241436526f3c3f9cbd34429492abddbada2ffcff506c51af"}, - {file = "propcache-0.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66d4cfda1d8ed687daa4bc0274fcfd5267873db9a5bc0418c2da19273040eeb7"}, - {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2f992c07c0fca81655066705beae35fc95a2fa7366467366db627d9f2ee097f"}, - {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:4a571d97dbe66ef38e472703067021b1467025ec85707d57e78711c085984e54"}, - {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bb6178c241278d5fe853b3de743087be7f5f4c6f7d6d22a3b524d323eecec505"}, - {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ad1af54a62ffe39cf34db1aa6ed1a1873bd548f6401db39d8e7cd060b9211f82"}, - {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e7048abd75fe40712005bcfc06bb44b9dfcd8e101dda2ecf2f5aa46115ad07ca"}, - {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:160291c60081f23ee43d44b08a7e5fb76681221a8e10b3139618c5a9a291b84e"}, - {file = "propcache-0.2.1-cp311-cp311-win32.whl", hash = "sha256:819ce3b883b7576ca28da3861c7e1a88afd08cc8c96908e08a3f4dd64a228034"}, - {file = "propcache-0.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:edc9fc7051e3350643ad929df55c451899bb9ae6d24998a949d2e4c87fb596d3"}, - {file = "propcache-0.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:081a430aa8d5e8876c6909b67bd2d937bfd531b0382d3fdedb82612c618bc41a"}, - {file = "propcache-0.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2ccec9ac47cf4e04897619c0e0c1a48c54a71bdf045117d3a26f80d38ab1fb0"}, - {file = "propcache-0.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:14d86fe14b7e04fa306e0c43cdbeebe6b2c2156a0c9ce56b815faacc193e320d"}, - {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:049324ee97bb67285b49632132db351b41e77833678432be52bdd0289c0e05e4"}, - {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cd9a1d071158de1cc1c71a26014dcdfa7dd3d5f4f88c298c7f90ad6f27bb46d"}, - {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98110aa363f1bb4c073e8dcfaefd3a5cea0f0834c2aab23dda657e4dab2f53b5"}, - {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:647894f5ae99c4cf6bb82a1bb3a796f6e06af3caa3d32e26d2350d0e3e3faf24"}, - {file = "propcache-0.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfd3223c15bebe26518d58ccf9a39b93948d3dcb3e57a20480dfdd315356baff"}, - {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d71264a80f3fcf512eb4f18f59423fe82d6e346ee97b90625f283df56aee103f"}, - {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e73091191e4280403bde6c9a52a6999d69cdfde498f1fdf629105247599b57ec"}, - {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3935bfa5fede35fb202c4b569bb9c042f337ca4ff7bd540a0aa5e37131659348"}, - {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f508b0491767bb1f2b87fdfacaba5f7eddc2f867740ec69ece6d1946d29029a6"}, - {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1672137af7c46662a1c2be1e8dc78cb6d224319aaa40271c9257d886be4363a6"}, - {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b74c261802d3d2b85c9df2dfb2fa81b6f90deeef63c2db9f0e029a3cac50b518"}, - {file = "propcache-0.2.1-cp312-cp312-win32.whl", hash = "sha256:d09c333d36c1409d56a9d29b3a1b800a42c76a57a5a8907eacdbce3f18768246"}, - {file = "propcache-0.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:c214999039d4f2a5b2073ac506bba279945233da8c786e490d411dfc30f855c1"}, - {file = "propcache-0.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aca405706e0b0a44cc6bfd41fbe89919a6a56999157f6de7e182a990c36e37bc"}, - {file = "propcache-0.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:12d1083f001ace206fe34b6bdc2cb94be66d57a850866f0b908972f90996b3e9"}, - {file = "propcache-0.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d93f3307ad32a27bda2e88ec81134b823c240aa3abb55821a8da553eed8d9439"}, - {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba278acf14471d36316159c94a802933d10b6a1e117b8554fe0d0d9b75c9d536"}, - {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4e6281aedfca15301c41f74d7005e6e3f4ca143584ba696ac69df4f02f40d629"}, - {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b750a8e5a1262434fb1517ddf64b5de58327f1adc3524a5e44c2ca43305eb0b"}, - {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf72af5e0fb40e9babf594308911436c8efde3cb5e75b6f206c34ad18be5c052"}, - {file = "propcache-0.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2d0a12018b04f4cb820781ec0dffb5f7c7c1d2a5cd22bff7fb055a2cb19ebce"}, - {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e800776a79a5aabdb17dcc2346a7d66d0777e942e4cd251defeb084762ecd17d"}, - {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:4160d9283bd382fa6c0c2b5e017acc95bc183570cd70968b9202ad6d8fc48dce"}, - {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:30b43e74f1359353341a7adb783c8f1b1c676367b011709f466f42fda2045e95"}, - {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:58791550b27d5488b1bb52bc96328456095d96206a250d28d874fafe11b3dfaf"}, - {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0f022d381747f0dfe27e99d928e31bc51a18b65bb9e481ae0af1380a6725dd1f"}, - {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:297878dc9d0a334358f9b608b56d02e72899f3b8499fc6044133f0d319e2ec30"}, - {file = "propcache-0.2.1-cp313-cp313-win32.whl", hash = "sha256:ddfab44e4489bd79bda09d84c430677fc7f0a4939a73d2bba3073036f487a0a6"}, - {file = "propcache-0.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:556fc6c10989f19a179e4321e5d678db8eb2924131e64652a51fe83e4c3db0e1"}, - {file = "propcache-0.2.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6a9a8c34fb7bb609419a211e59da8887eeca40d300b5ea8e56af98f6fbbb1541"}, - {file = "propcache-0.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ae1aa1cd222c6d205853b3013c69cd04515f9d6ab6de4b0603e2e1c33221303e"}, - {file = "propcache-0.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:accb6150ce61c9c4b7738d45550806aa2b71c7668c6942f17b0ac182b6142fd4"}, - {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5eee736daafa7af6d0a2dc15cc75e05c64f37fc37bafef2e00d77c14171c2097"}, - {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7a31fc1e1bd362874863fdeed71aed92d348f5336fd84f2197ba40c59f061bd"}, - {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba4cfa1052819d16699e1d55d18c92b6e094d4517c41dd231a8b9f87b6fa681"}, - {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f089118d584e859c62b3da0892b88a83d611c2033ac410e929cb6754eec0ed16"}, - {file = "propcache-0.2.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:781e65134efaf88feb447e8c97a51772aa75e48b794352f94cb7ea717dedda0d"}, - {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31f5af773530fd3c658b32b6bdc2d0838543de70eb9a2156c03e410f7b0d3aae"}, - {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:a7a078f5d37bee6690959c813977da5291b24286e7b962e62a94cec31aa5188b"}, - {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cea7daf9fc7ae6687cf1e2c049752f19f146fdc37c2cc376e7d0032cf4f25347"}, - {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:8b3489ff1ed1e8315674d0775dc7d2195fb13ca17b3808721b54dbe9fd020faf"}, - {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9403db39be1393618dd80c746cb22ccda168efce239c73af13c3763ef56ffc04"}, - {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5d97151bc92d2b2578ff7ce779cdb9174337390a535953cbb9452fb65164c587"}, - {file = "propcache-0.2.1-cp39-cp39-win32.whl", hash = "sha256:9caac6b54914bdf41bcc91e7eb9147d331d29235a7c967c150ef5df6464fd1bb"}, - {file = "propcache-0.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:92fc4500fcb33899b05ba73276dfb684a20d31caa567b7cb5252d48f896a91b1"}, - {file = "propcache-0.2.1-py3-none-any.whl", hash = "sha256:52277518d6aae65536e9cea52d4e7fd2f7a66f4aa2d30ed3f2fcea620ace3c54"}, - {file = "propcache-0.2.1.tar.gz", hash = "sha256:3f77ce728b19cb537714499928fe800c3dda29e8d9428778fc7c186da4c09a64"}, + {file = "propcache-0.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f27785888d2fdd918bc36de8b8739f2d6c791399552333721b58193f68ea3e98"}, + {file = "propcache-0.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4e89cde74154c7b5957f87a355bb9c8ec929c167b59c83d90654ea36aeb6180"}, + {file = "propcache-0.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:730178f476ef03d3d4d255f0c9fa186cb1d13fd33ffe89d39f2cda4da90ceb71"}, + {file = "propcache-0.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:967a8eec513dbe08330f10137eacb427b2ca52118769e82ebcfcab0fba92a649"}, + {file = "propcache-0.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b9145c35cc87313b5fd480144f8078716007656093d23059e8993d3a8fa730f"}, + {file = "propcache-0.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e64e948ab41411958670f1093c0a57acfdc3bee5cf5b935671bbd5313bcf229"}, + {file = "propcache-0.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:319fa8765bfd6a265e5fa661547556da381e53274bc05094fc9ea50da51bfd46"}, + {file = "propcache-0.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c66d8ccbc902ad548312b96ed8d5d266d0d2c6d006fd0f66323e9d8f2dd49be7"}, + {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2d219b0dbabe75e15e581fc1ae796109b07c8ba7d25b9ae8d650da582bed01b0"}, + {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:cd6a55f65241c551eb53f8cf4d2f4af33512c39da5d9777694e9d9c60872f519"}, + {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9979643ffc69b799d50d3a7b72b5164a2e97e117009d7af6dfdd2ab906cb72cd"}, + {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4cf9e93a81979f1424f1a3d155213dc928f1069d697e4353edb8a5eba67c6259"}, + {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2fce1df66915909ff6c824bbb5eb403d2d15f98f1518e583074671a30fe0c21e"}, + {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4d0dfdd9a2ebc77b869a0b04423591ea8823f791293b527dc1bb896c1d6f1136"}, + {file = "propcache-0.3.1-cp310-cp310-win32.whl", hash = "sha256:1f6cc0ad7b4560e5637eb2c994e97b4fa41ba8226069c9277eb5ea7101845b42"}, + {file = "propcache-0.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:47ef24aa6511e388e9894ec16f0fbf3313a53ee68402bc428744a367ec55b833"}, + {file = "propcache-0.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7f30241577d2fef2602113b70ef7231bf4c69a97e04693bde08ddab913ba0ce5"}, + {file = "propcache-0.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:43593c6772aa12abc3af7784bff4a41ffa921608dd38b77cf1dfd7f5c4e71371"}, + {file = "propcache-0.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a75801768bbe65499495660b777e018cbe90c7980f07f8aa57d6be79ea6f71da"}, + {file = "propcache-0.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6f1324db48f001c2ca26a25fa25af60711e09b9aaf4b28488602776f4f9a744"}, + {file = "propcache-0.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cdb0f3e1eb6dfc9965d19734d8f9c481b294b5274337a8cb5cb01b462dcb7e0"}, + {file = "propcache-0.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1eb34d90aac9bfbced9a58b266f8946cb5935869ff01b164573a7634d39fbcb5"}, + {file = "propcache-0.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f35c7070eeec2cdaac6fd3fe245226ed2a6292d3ee8c938e5bb645b434c5f256"}, + {file = "propcache-0.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b23c11c2c9e6d4e7300c92e022046ad09b91fd00e36e83c44483df4afa990073"}, + {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3e19ea4ea0bf46179f8a3652ac1426e6dcbaf577ce4b4f65be581e237340420d"}, + {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:bd39c92e4c8f6cbf5f08257d6360123af72af9f4da75a690bef50da77362d25f"}, + {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b0313e8b923b3814d1c4a524c93dfecea5f39fa95601f6a9b1ac96cd66f89ea0"}, + {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e861ad82892408487be144906a368ddbe2dc6297074ade2d892341b35c59844a"}, + {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:61014615c1274df8da5991a1e5da85a3ccb00c2d4701ac6f3383afd3ca47ab0a"}, + {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:71ebe3fe42656a2328ab08933d420df5f3ab121772eef78f2dc63624157f0ed9"}, + {file = "propcache-0.3.1-cp311-cp311-win32.whl", hash = "sha256:58aa11f4ca8b60113d4b8e32d37e7e78bd8af4d1a5b5cb4979ed856a45e62005"}, + {file = "propcache-0.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:9532ea0b26a401264b1365146c440a6d78269ed41f83f23818d4b79497aeabe7"}, + {file = "propcache-0.3.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f78eb8422acc93d7b69964012ad7048764bb45a54ba7a39bb9e146c72ea29723"}, + {file = "propcache-0.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:89498dd49c2f9a026ee057965cdf8192e5ae070ce7d7a7bd4b66a8e257d0c976"}, + {file = "propcache-0.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:09400e98545c998d57d10035ff623266927cb784d13dd2b31fd33b8a5316b85b"}, + {file = "propcache-0.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa8efd8c5adc5a2c9d3b952815ff8f7710cefdcaf5f2c36d26aff51aeca2f12f"}, + {file = "propcache-0.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2fe5c910f6007e716a06d269608d307b4f36e7babee5f36533722660e8c4a70"}, + {file = "propcache-0.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a0ab8cf8cdd2194f8ff979a43ab43049b1df0b37aa64ab7eca04ac14429baeb7"}, + {file = "propcache-0.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:563f9d8c03ad645597b8d010ef4e9eab359faeb11a0a2ac9f7b4bc8c28ebef25"}, + {file = "propcache-0.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb6e0faf8cb6b4beea5d6ed7b5a578254c6d7df54c36ccd3d8b3eb00d6770277"}, + {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1c5c7ab7f2bb3f573d1cb921993006ba2d39e8621019dffb1c5bc94cdbae81e8"}, + {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:050b571b2e96ec942898f8eb46ea4bfbb19bd5502424747e83badc2d4a99a44e"}, + {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e1c4d24b804b3a87e9350f79e2371a705a188d292fd310e663483af6ee6718ee"}, + {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e4fe2a6d5ce975c117a6bb1e8ccda772d1e7029c1cca1acd209f91d30fa72815"}, + {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:feccd282de1f6322f56f6845bf1207a537227812f0a9bf5571df52bb418d79d5"}, + {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ec314cde7314d2dd0510c6787326bbffcbdc317ecee6b7401ce218b3099075a7"}, + {file = "propcache-0.3.1-cp312-cp312-win32.whl", hash = "sha256:7d2d5a0028d920738372630870e7d9644ce437142197f8c827194fca404bf03b"}, + {file = "propcache-0.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:88c423efef9d7a59dae0614eaed718449c09a5ac79a5f224a8b9664d603f04a3"}, + {file = "propcache-0.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f1528ec4374617a7a753f90f20e2f551121bb558fcb35926f99e3c42367164b8"}, + {file = "propcache-0.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc1915ec523b3b494933b5424980831b636fe483d7d543f7afb7b3bf00f0c10f"}, + {file = "propcache-0.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a110205022d077da24e60b3df8bcee73971be9575dec5573dd17ae5d81751111"}, + {file = "propcache-0.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d249609e547c04d190e820d0d4c8ca03ed4582bcf8e4e160a6969ddfb57b62e5"}, + {file = "propcache-0.3.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ced33d827625d0a589e831126ccb4f5c29dfdf6766cac441d23995a65825dcb"}, + {file = "propcache-0.3.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4114c4ada8f3181af20808bedb250da6bae56660e4b8dfd9cd95d4549c0962f7"}, + {file = "propcache-0.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:975af16f406ce48f1333ec5e912fe11064605d5c5b3f6746969077cc3adeb120"}, + {file = "propcache-0.3.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a34aa3a1abc50740be6ac0ab9d594e274f59960d3ad253cd318af76b996dd654"}, + {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9cec3239c85ed15bfaded997773fdad9fb5662b0a7cbc854a43f291eb183179e"}, + {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:05543250deac8e61084234d5fc54f8ebd254e8f2b39a16b1dce48904f45b744b"}, + {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5cb5918253912e088edbf023788de539219718d3b10aef334476b62d2b53de53"}, + {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f3bbecd2f34d0e6d3c543fdb3b15d6b60dd69970c2b4c822379e5ec8f6f621d5"}, + {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aca63103895c7d960a5b9b044a83f544b233c95e0dcff114389d64d762017af7"}, + {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a0a9898fdb99bf11786265468571e628ba60af80dc3f6eb89a3545540c6b0ef"}, + {file = "propcache-0.3.1-cp313-cp313-win32.whl", hash = "sha256:3a02a28095b5e63128bcae98eb59025924f121f048a62393db682f049bf4ac24"}, + {file = "propcache-0.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:813fbb8b6aea2fc9659815e585e548fe706d6f663fa73dff59a1677d4595a037"}, + {file = "propcache-0.3.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a444192f20f5ce8a5e52761a031b90f5ea6288b1eef42ad4c7e64fef33540b8f"}, + {file = "propcache-0.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0fbe94666e62ebe36cd652f5fc012abfbc2342de99b523f8267a678e4dfdee3c"}, + {file = "propcache-0.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f011f104db880f4e2166bcdcf7f58250f7a465bc6b068dc84c824a3d4a5c94dc"}, + {file = "propcache-0.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e584b6d388aeb0001d6d5c2bd86b26304adde6d9bb9bfa9c4889805021b96de"}, + {file = "propcache-0.3.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a17583515a04358b034e241f952f1715243482fc2c2945fd99a1b03a0bd77d6"}, + {file = "propcache-0.3.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5aed8d8308215089c0734a2af4f2e95eeb360660184ad3912686c181e500b2e7"}, + {file = "propcache-0.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d8e309ff9a0503ef70dc9a0ebd3e69cf7b3894c9ae2ae81fc10943c37762458"}, + {file = "propcache-0.3.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b655032b202028a582d27aeedc2e813299f82cb232f969f87a4fde491a233f11"}, + {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f64d91b751df77931336b5ff7bafbe8845c5770b06630e27acd5dbb71e1931c"}, + {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:19a06db789a4bd896ee91ebc50d059e23b3639c25d58eb35be3ca1cbe967c3bf"}, + {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:bef100c88d8692864651b5f98e871fb090bd65c8a41a1cb0ff2322db39c96c27"}, + {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:87380fb1f3089d2a0b8b00f006ed12bd41bd858fabfa7330c954c70f50ed8757"}, + {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e474fc718e73ba5ec5180358aa07f6aded0ff5f2abe700e3115c37d75c947e18"}, + {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:17d1c688a443355234f3c031349da69444be052613483f3e4158eef751abcd8a"}, + {file = "propcache-0.3.1-cp313-cp313t-win32.whl", hash = "sha256:359e81a949a7619802eb601d66d37072b79b79c2505e6d3fd8b945538411400d"}, + {file = "propcache-0.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e7fb9a84c9abbf2b2683fa3e7b0d7da4d8ecf139a1c635732a8bda29c5214b0e"}, + {file = "propcache-0.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ed5f6d2edbf349bd8d630e81f474d33d6ae5d07760c44d33cd808e2f5c8f4ae6"}, + {file = "propcache-0.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:668ddddc9f3075af019f784456267eb504cb77c2c4bd46cc8402d723b4d200bf"}, + {file = "propcache-0.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0c86e7ceea56376216eba345aa1fc6a8a6b27ac236181f840d1d7e6a1ea9ba5c"}, + {file = "propcache-0.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83be47aa4e35b87c106fc0c84c0fc069d3f9b9b06d3c494cd404ec6747544894"}, + {file = "propcache-0.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:27c6ac6aa9fc7bc662f594ef380707494cb42c22786a558d95fcdedb9aa5d035"}, + {file = "propcache-0.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64a956dff37080b352c1c40b2966b09defb014347043e740d420ca1eb7c9b908"}, + {file = "propcache-0.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82de5da8c8893056603ac2d6a89eb8b4df49abf1a7c19d536984c8dd63f481d5"}, + {file = "propcache-0.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c3c3a203c375b08fd06a20da3cf7aac293b834b6f4f4db71190e8422750cca5"}, + {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b303b194c2e6f171cfddf8b8ba30baefccf03d36a4d9cab7fd0bb68ba476a3d7"}, + {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:916cd229b0150129d645ec51614d38129ee74c03293a9f3f17537be0029a9641"}, + {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:a461959ead5b38e2581998700b26346b78cd98540b5524796c175722f18b0294"}, + {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:069e7212890b0bcf9b2be0a03afb0c2d5161d91e1bf51569a64f629acc7defbf"}, + {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ef2e4e91fb3945769e14ce82ed53007195e616a63aa43b40fb7ebaaf907c8d4c"}, + {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8638f99dca15b9dff328fb6273e09f03d1c50d9b6512f3b65a4154588a7595fe"}, + {file = "propcache-0.3.1-cp39-cp39-win32.whl", hash = "sha256:6f173bbfe976105aaa890b712d1759de339d8a7cef2fc0a1714cc1a1e1c47f64"}, + {file = "propcache-0.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:603f1fe4144420374f1a69b907494c3acbc867a581c2d49d4175b0de7cc64566"}, + {file = "propcache-0.3.1-py3-none-any.whl", hash = "sha256:9a8ecf38de50a7f518c21568c80f985e776397b902f1ce0b01f799aba1608b40"}, + {file = "propcache-0.3.1.tar.gz", hash = "sha256:40d980c33765359098837527e18eddefc9a24cea5b45e078a7f3bb5b032c6ecf"}, ] [[package]] name = "ruff" -version = "0.9.3" +version = "0.11.6" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "ruff-0.9.3-py3-none-linux_armv6l.whl", hash = "sha256:7f39b879064c7d9670197d91124a75d118d00b0990586549949aae80cdc16624"}, - {file = "ruff-0.9.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a187171e7c09efa4b4cc30ee5d0d55a8d6c5311b3e1b74ac5cb96cc89bafc43c"}, - {file = "ruff-0.9.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c59ab92f8e92d6725b7ded9d4a31be3ef42688a115c6d3da9457a5bda140e2b4"}, - {file = "ruff-0.9.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dc153c25e715be41bb228bc651c1e9b1a88d5c6e5ed0194fa0dfea02b026439"}, - {file = "ruff-0.9.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:646909a1e25e0dc28fbc529eab8eb7bb583079628e8cbe738192853dbbe43af5"}, - {file = "ruff-0.9.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a5a46e09355695fbdbb30ed9889d6cf1c61b77b700a9fafc21b41f097bfbba4"}, - {file = "ruff-0.9.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c4bb09d2bbb394e3730d0918c00276e79b2de70ec2a5231cd4ebb51a57df9ba1"}, - {file = "ruff-0.9.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96a87ec31dc1044d8c2da2ebbed1c456d9b561e7d087734336518181b26b3aa5"}, - {file = "ruff-0.9.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb7554aca6f842645022fe2d301c264e6925baa708b392867b7a62645304df4"}, - {file = "ruff-0.9.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cabc332b7075a914ecea912cd1f3d4370489c8018f2c945a30bcc934e3bc06a6"}, - {file = "ruff-0.9.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:33866c3cc2a575cbd546f2cd02bdd466fed65118e4365ee538a3deffd6fcb730"}, - {file = "ruff-0.9.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:006e5de2621304c8810bcd2ee101587712fa93b4f955ed0985907a36c427e0c2"}, - {file = "ruff-0.9.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ba6eea4459dbd6b1be4e6bfc766079fb9b8dd2e5a35aff6baee4d9b1514ea519"}, - {file = "ruff-0.9.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:90230a6b8055ad47d3325e9ee8f8a9ae7e273078a66401ac66df68943ced029b"}, - {file = "ruff-0.9.3-py3-none-win32.whl", hash = "sha256:eabe5eb2c19a42f4808c03b82bd313fc84d4e395133fb3fc1b1516170a31213c"}, - {file = "ruff-0.9.3-py3-none-win_amd64.whl", hash = "sha256:040ceb7f20791dfa0e78b4230ee9dce23da3b64dd5848e40e3bf3ab76468dcf4"}, - {file = "ruff-0.9.3-py3-none-win_arm64.whl", hash = "sha256:800d773f6d4d33b0a3c60e2c6ae8f4c202ea2de056365acfa519aa48acf28e0b"}, - {file = "ruff-0.9.3.tar.gz", hash = "sha256:8293f89985a090ebc3ed1064df31f3b4b56320cdfcec8b60d3295bddb955c22a"}, + {file = "ruff-0.11.6-py3-none-linux_armv6l.whl", hash = "sha256:d84dcbe74cf9356d1bdb4a78cf74fd47c740bf7bdeb7529068f69b08272239a1"}, + {file = "ruff-0.11.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9bc583628e1096148011a5d51ff3c836f51899e61112e03e5f2b1573a9b726de"}, + {file = "ruff-0.11.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f2959049faeb5ba5e3b378709e9d1bf0cab06528b306b9dd6ebd2a312127964a"}, + {file = "ruff-0.11.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63c5d4e30d9d0de7fedbfb3e9e20d134b73a30c1e74b596f40f0629d5c28a193"}, + {file = "ruff-0.11.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26a4b9a4e1439f7d0a091c6763a100cef8fbdc10d68593df6f3cfa5abdd9246e"}, + {file = "ruff-0.11.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b5edf270223dd622218256569636dc3e708c2cb989242262fe378609eccf1308"}, + {file = "ruff-0.11.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f55844e818206a9dd31ff27f91385afb538067e2dc0beb05f82c293ab84f7d55"}, + {file = "ruff-0.11.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d8f782286c5ff562e4e00344f954b9320026d8e3fae2ba9e6948443fafd9ffc"}, + {file = "ruff-0.11.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:01c63ba219514271cee955cd0adc26a4083df1956d57847978383b0e50ffd7d2"}, + {file = "ruff-0.11.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15adac20ef2ca296dd3d8e2bedc6202ea6de81c091a74661c3666e5c4c223ff6"}, + {file = "ruff-0.11.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4dd6b09e98144ad7aec026f5588e493c65057d1b387dd937d7787baa531d9bc2"}, + {file = "ruff-0.11.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:45b2e1d6c0eed89c248d024ea95074d0e09988d8e7b1dad8d3ab9a67017a5b03"}, + {file = "ruff-0.11.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:bd40de4115b2ec4850302f1a1d8067f42e70b4990b68838ccb9ccd9f110c5e8b"}, + {file = "ruff-0.11.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:77cda2dfbac1ab73aef5e514c4cbfc4ec1fbef4b84a44c736cc26f61b3814cd9"}, + {file = "ruff-0.11.6-py3-none-win32.whl", hash = "sha256:5151a871554be3036cd6e51d0ec6eef56334d74dfe1702de717a995ee3d5b287"}, + {file = "ruff-0.11.6-py3-none-win_amd64.whl", hash = "sha256:cce85721d09c51f3b782c331b0abd07e9d7d5f775840379c640606d3159cae0e"}, + {file = "ruff-0.11.6-py3-none-win_arm64.whl", hash = "sha256:3567ba0d07fb170b1b48d944715e3294b77f5b7679e8ba258199a250383ccb79"}, + {file = "ruff-0.11.6.tar.gz", hash = "sha256:bec8bcc3ac228a45ccc811e45f7eb61b950dbf4cf31a67fa89352574b01c7d79"}, ] [[package]] name = "yarl" -version = "1.18.3" +version = "1.20.0" description = "Yet another URL library" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34"}, - {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7"}, - {file = "yarl-1.18.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:602d98f2c2d929f8e697ed274fbadc09902c4025c5a9963bf4e9edfc3ab6f7ed"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c654d5207c78e0bd6d749f6dae1dcbbfde3403ad3a4b11f3c5544d9906969dde"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5094d9206c64181d0f6e76ebd8fb2f8fe274950a63890ee9e0ebfd58bf9d787b"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35098b24e0327fc4ebdc8ffe336cee0a87a700c24ffed13161af80124b7dc8e5"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3236da9272872443f81fedc389bace88408f64f89f75d1bdb2256069a8730ccc"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2c08cc9b16f4f4bc522771d96734c7901e7ebef70c6c5c35dd0f10845270bcd"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80316a8bd5109320d38eef8833ccf5f89608c9107d02d2a7f985f98ed6876990"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c1e1cc06da1491e6734f0ea1e6294ce00792193c463350626571c287c9a704db"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fea09ca13323376a2fdfb353a5fa2e59f90cd18d7ca4eaa1fd31f0a8b4f91e62"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e3b9fd71836999aad54084906f8663dffcd2a7fb5cdafd6c37713b2e72be1760"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:757e81cae69244257d125ff31663249b3013b5dc0a8520d73694aed497fb195b"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b1771de9944d875f1b98a745bc547e684b863abf8f8287da8466cf470ef52690"}, - {file = "yarl-1.18.3-cp310-cp310-win32.whl", hash = "sha256:8874027a53e3aea659a6d62751800cf6e63314c160fd607489ba5c2edd753cf6"}, - {file = "yarl-1.18.3-cp310-cp310-win_amd64.whl", hash = "sha256:93b2e109287f93db79210f86deb6b9bbb81ac32fc97236b16f7433db7fc437d8"}, - {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8503ad47387b8ebd39cbbbdf0bf113e17330ffd339ba1144074da24c545f0069"}, - {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02ddb6756f8f4517a2d5e99d8b2f272488e18dd0bfbc802f31c16c6c20f22193"}, - {file = "yarl-1.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a283dd2882ac98cc6318384f565bffc751ab564605959df4752d42483ad889"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d980e0325b6eddc81331d3f4551e2a333999fb176fd153e075c6d1c2530aa8a8"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b643562c12680b01e17239be267bc306bbc6aac1f34f6444d1bded0c5ce438ca"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c017a3b6df3a1bd45b9fa49a0f54005e53fbcad16633870104b66fa1a30a29d8"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75674776d96d7b851b6498f17824ba17849d790a44d282929c42dbb77d4f17ae"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccaa3a4b521b780a7e771cc336a2dba389a0861592bbce09a476190bb0c8b4b3"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d06d3005e668744e11ed80812e61efd77d70bb7f03e33c1598c301eea20efbb"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9d41beda9dc97ca9ab0b9888cb71f7539124bc05df02c0cff6e5acc5a19dcc6e"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ba23302c0c61a9999784e73809427c9dbedd79f66a13d84ad1b1943802eaaf59"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6748dbf9bfa5ba1afcc7556b71cda0d7ce5f24768043a02a58846e4a443d808d"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0b0cad37311123211dc91eadcb322ef4d4a66008d3e1bdc404808992260e1a0e"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fb2171a4486bb075316ee754c6d8382ea6eb8b399d4ec62fde2b591f879778a"}, - {file = "yarl-1.18.3-cp311-cp311-win32.whl", hash = "sha256:61b1a825a13bef4a5f10b1885245377d3cd0bf87cba068e1d9a88c2ae36880e1"}, - {file = "yarl-1.18.3-cp311-cp311-win_amd64.whl", hash = "sha256:b9d60031cf568c627d028239693fd718025719c02c9f55df0a53e587aab951b5"}, - {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1dd4bdd05407ced96fed3d7f25dbbf88d2ffb045a0db60dbc247f5b3c5c25d50"}, - {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7c33dd1931a95e5d9a772d0ac5e44cac8957eaf58e3c8da8c1414de7dd27c576"}, - {file = "yarl-1.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b411eddcfd56a2f0cd6a384e9f4f7aa3efee14b188de13048c25b5e91f1640"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436c4fc0a4d66b2badc6c5fc5ef4e47bb10e4fd9bf0c79524ac719a01f3607c2"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e35ef8683211db69ffe129a25d5634319a677570ab6b2eba4afa860f54eeaf75"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84b2deecba4a3f1a398df819151eb72d29bfeb3b69abb145a00ddc8d30094512"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e5a1fea0fd4f5bfa7440a47eff01d9822a65b4488f7cff83155a0f31a2ecba"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0e883008013c0e4aef84dcfe2a0b172c4d23c2669412cf5b3371003941f72bb"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a3f356548e34a70b0172d8890006c37be92995f62d95a07b4a42e90fba54272"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ccd17349166b1bee6e529b4add61727d3f55edb7babbe4069b5764c9587a8cc6"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b958ddd075ddba5b09bb0be8a6d9906d2ce933aee81100db289badbeb966f54e"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c7d79f7d9aabd6011004e33b22bc13056a3e3fb54794d138af57f5ee9d9032cb"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4891ed92157e5430874dad17b15eb1fda57627710756c27422200c52d8a4e393"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ce1af883b94304f493698b00d0f006d56aea98aeb49d75ec7d98cd4a777e9285"}, - {file = "yarl-1.18.3-cp312-cp312-win32.whl", hash = "sha256:f91c4803173928a25e1a55b943c81f55b8872f0018be83e3ad4938adffb77dd2"}, - {file = "yarl-1.18.3-cp312-cp312-win_amd64.whl", hash = "sha256:7e2ee16578af3b52ac2f334c3b1f92262f47e02cc6193c598502bd46f5cd1477"}, - {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90adb47ad432332d4f0bc28f83a5963f426ce9a1a8809f5e584e704b82685dcb"}, - {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:913829534200eb0f789d45349e55203a091f45c37a2674678744ae52fae23efa"}, - {file = "yarl-1.18.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9f7768395923c3039055c14334ba4d926f3baf7b776c923c93d80195624782"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a19f62ff30117e706ebc9090b8ecc79aeb77d0b1f5ec10d2d27a12bc9f66d0"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e17c9361d46a4d5addf777c6dd5eab0715a7684c2f11b88c67ac37edfba6c482"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a74a13a4c857a84a845505fd2d68e54826a2cd01935a96efb1e9d86c728e186"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41f7ce59d6ee7741af71d82020346af364949314ed3d87553763a2df1829cc58"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f52a265001d830bc425f82ca9eabda94a64a4d753b07d623a9f2863fde532b53"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:82123d0c954dc58db301f5021a01854a85bf1f3bb7d12ae0c01afc414a882ca2"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2ec9bbba33b2d00999af4631a3397d1fd78290c48e2a3e52d8dd72db3a067ac8"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbd6748e8ab9b41171bb95c6142faf068f5ef1511935a0aa07025438dd9a9bc1"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:877d209b6aebeb5b16c42cbb377f5f94d9e556626b1bfff66d7b0d115be88d0a"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b464c4ab4bfcb41e3bfd3f1c26600d038376c2de3297760dfe064d2cb7ea8e10"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d39d351e7faf01483cc7ff7c0213c412e38e5a340238826be7e0e4da450fdc8"}, - {file = "yarl-1.18.3-cp313-cp313-win32.whl", hash = "sha256:61ee62ead9b68b9123ec24bc866cbef297dd266175d53296e2db5e7f797f902d"}, - {file = "yarl-1.18.3-cp313-cp313-win_amd64.whl", hash = "sha256:578e281c393af575879990861823ef19d66e2b1d0098414855dd367e234f5b3c"}, - {file = "yarl-1.18.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:61e5e68cb65ac8f547f6b5ef933f510134a6bf31bb178be428994b0cb46c2a04"}, - {file = "yarl-1.18.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe57328fbc1bfd0bd0514470ac692630f3901c0ee39052ae47acd1d90a436719"}, - {file = "yarl-1.18.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a440a2a624683108a1b454705ecd7afc1c3438a08e890a1513d468671d90a04e"}, - {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09c7907c8548bcd6ab860e5f513e727c53b4a714f459b084f6580b49fa1b9cee"}, - {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4f6450109834af88cb4cc5ecddfc5380ebb9c228695afc11915a0bf82116789"}, - {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9ca04806f3be0ac6d558fffc2fdf8fcef767e0489d2684a21912cc4ed0cd1b8"}, - {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77a6e85b90a7641d2e07184df5557132a337f136250caafc9ccaa4a2a998ca2c"}, - {file = "yarl-1.18.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6333c5a377c8e2f5fae35e7b8f145c617b02c939d04110c76f29ee3676b5f9a5"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0b3c92fa08759dbf12b3a59579a4096ba9af8dd344d9a813fc7f5070d86bbab1"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4ac515b860c36becb81bb84b667466885096b5fc85596948548b667da3bf9f24"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:045b8482ce9483ada4f3f23b3774f4e1bf4f23a2d5c912ed5170f68efb053318"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:a4bb030cf46a434ec0225bddbebd4b89e6471814ca851abb8696170adb163985"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:54d6921f07555713b9300bee9c50fb46e57e2e639027089b1d795ecd9f7fa910"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1d407181cfa6e70077df3377938c08012d18893f9f20e92f7d2f314a437c30b1"}, - {file = "yarl-1.18.3-cp39-cp39-win32.whl", hash = "sha256:ac36703a585e0929b032fbaab0707b75dc12703766d0b53486eabd5139ebadd5"}, - {file = "yarl-1.18.3-cp39-cp39-win_amd64.whl", hash = "sha256:ba87babd629f8af77f557b61e49e7c7cac36f22f871156b91e10a6e9d4f829e9"}, - {file = "yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b"}, - {file = "yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1"}, + {file = "yarl-1.20.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f1f6670b9ae3daedb325fa55fbe31c22c8228f6e0b513772c2e1c623caa6ab22"}, + {file = "yarl-1.20.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85a231fa250dfa3308f3c7896cc007a47bc76e9e8e8595c20b7426cac4884c62"}, + {file = "yarl-1.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1a06701b647c9939d7019acdfa7ebbfbb78ba6aa05985bb195ad716ea759a569"}, + {file = "yarl-1.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7595498d085becc8fb9203aa314b136ab0516c7abd97e7d74f7bb4eb95042abe"}, + {file = "yarl-1.20.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af5607159085dcdb055d5678fc2d34949bd75ae6ea6b4381e784bbab1c3aa195"}, + {file = "yarl-1.20.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:95b50910e496567434cb77a577493c26bce0f31c8a305135f3bda6a2483b8e10"}, + {file = "yarl-1.20.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b594113a301ad537766b4e16a5a6750fcbb1497dcc1bc8a4daae889e6402a634"}, + {file = "yarl-1.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:083ce0393ea173cd37834eb84df15b6853b555d20c52703e21fbababa8c129d2"}, + {file = "yarl-1.20.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f1a350a652bbbe12f666109fbddfdf049b3ff43696d18c9ab1531fbba1c977a"}, + {file = "yarl-1.20.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fb0caeac4a164aadce342f1597297ec0ce261ec4532bbc5a9ca8da5622f53867"}, + {file = "yarl-1.20.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:d88cc43e923f324203f6ec14434fa33b85c06d18d59c167a0637164863b8e995"}, + {file = "yarl-1.20.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e52d6ed9ea8fd3abf4031325dc714aed5afcbfa19ee4a89898d663c9976eb487"}, + {file = "yarl-1.20.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ce360ae48a5e9961d0c730cf891d40698a82804e85f6e74658fb175207a77cb2"}, + {file = "yarl-1.20.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:06d06c9d5b5bc3eb56542ceeba6658d31f54cf401e8468512447834856fb0e61"}, + {file = "yarl-1.20.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c27d98f4e5c4060582f44e58309c1e55134880558f1add7a87c1bc36ecfade19"}, + {file = "yarl-1.20.0-cp310-cp310-win32.whl", hash = "sha256:f4d3fa9b9f013f7050326e165c3279e22850d02ae544ace285674cb6174b5d6d"}, + {file = "yarl-1.20.0-cp310-cp310-win_amd64.whl", hash = "sha256:bc906b636239631d42eb8a07df8359905da02704a868983265603887ed68c076"}, + {file = "yarl-1.20.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fdb5204d17cb32b2de2d1e21c7461cabfacf17f3645e4b9039f210c5d3378bf3"}, + {file = "yarl-1.20.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:eaddd7804d8e77d67c28d154ae5fab203163bd0998769569861258e525039d2a"}, + {file = "yarl-1.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:634b7ba6b4a85cf67e9df7c13a7fb2e44fa37b5d34501038d174a63eaac25ee2"}, + {file = "yarl-1.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d409e321e4addf7d97ee84162538c7258e53792eb7c6defd0c33647d754172e"}, + {file = "yarl-1.20.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ea52f7328a36960ba3231c6677380fa67811b414798a6e071c7085c57b6d20a9"}, + {file = "yarl-1.20.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c8703517b924463994c344dcdf99a2d5ce9eca2b6882bb640aa555fb5efc706a"}, + {file = "yarl-1.20.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:077989b09ffd2f48fb2d8f6a86c5fef02f63ffe6b1dd4824c76de7bb01e4f2e2"}, + {file = "yarl-1.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0acfaf1da020253f3533526e8b7dd212838fdc4109959a2c53cafc6db611bff2"}, + {file = "yarl-1.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4230ac0b97ec5eeb91d96b324d66060a43fd0d2a9b603e3327ed65f084e41f8"}, + {file = "yarl-1.20.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a6a1e6ae21cdd84011c24c78d7a126425148b24d437b5702328e4ba640a8902"}, + {file = "yarl-1.20.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:86de313371ec04dd2531f30bc41a5a1a96f25a02823558ee0f2af0beaa7ca791"}, + {file = "yarl-1.20.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:dd59c9dd58ae16eaa0f48c3d0cbe6be8ab4dc7247c3ff7db678edecbaf59327f"}, + {file = "yarl-1.20.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a0bc5e05f457b7c1994cc29e83b58f540b76234ba6b9648a4971ddc7f6aa52da"}, + {file = "yarl-1.20.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c9471ca18e6aeb0e03276b5e9b27b14a54c052d370a9c0c04a68cefbd1455eb4"}, + {file = "yarl-1.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:40ed574b4df723583a26c04b298b283ff171bcc387bc34c2683235e2487a65a5"}, + {file = "yarl-1.20.0-cp311-cp311-win32.whl", hash = "sha256:db243357c6c2bf3cd7e17080034ade668d54ce304d820c2a58514a4e51d0cfd6"}, + {file = "yarl-1.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:8c12cd754d9dbd14204c328915e23b0c361b88f3cffd124129955e60a4fbfcfb"}, + {file = "yarl-1.20.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e06b9f6cdd772f9b665e5ba8161968e11e403774114420737f7884b5bd7bdf6f"}, + {file = "yarl-1.20.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b9ae2fbe54d859b3ade40290f60fe40e7f969d83d482e84d2c31b9bff03e359e"}, + {file = "yarl-1.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6d12b8945250d80c67688602c891237994d203d42427cb14e36d1a732eda480e"}, + {file = "yarl-1.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:087e9731884621b162a3e06dc0d2d626e1542a617f65ba7cc7aeab279d55ad33"}, + {file = "yarl-1.20.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:69df35468b66c1a6e6556248e6443ef0ec5f11a7a4428cf1f6281f1879220f58"}, + {file = "yarl-1.20.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b2992fe29002fd0d4cbaea9428b09af9b8686a9024c840b8a2b8f4ea4abc16f"}, + {file = "yarl-1.20.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c903e0b42aab48abfbac668b5a9d7b6938e721a6341751331bcd7553de2dcae"}, + {file = "yarl-1.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf099e2432131093cc611623e0b0bcc399b8cddd9a91eded8bfb50402ec35018"}, + {file = "yarl-1.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a7f62f5dc70a6c763bec9ebf922be52aa22863d9496a9a30124d65b489ea672"}, + {file = "yarl-1.20.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:54ac15a8b60382b2bcefd9a289ee26dc0920cf59b05368c9b2b72450751c6eb8"}, + {file = "yarl-1.20.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:25b3bc0763a7aca16a0f1b5e8ef0f23829df11fb539a1b70476dcab28bd83da7"}, + {file = "yarl-1.20.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b2586e36dc070fc8fad6270f93242124df68b379c3a251af534030a4a33ef594"}, + {file = "yarl-1.20.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:866349da9d8c5290cfefb7fcc47721e94de3f315433613e01b435473be63daa6"}, + {file = "yarl-1.20.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:33bb660b390a0554d41f8ebec5cd4475502d84104b27e9b42f5321c5192bfcd1"}, + {file = "yarl-1.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:737e9f171e5a07031cbee5e9180f6ce21a6c599b9d4b2c24d35df20a52fabf4b"}, + {file = "yarl-1.20.0-cp312-cp312-win32.whl", hash = "sha256:839de4c574169b6598d47ad61534e6981979ca2c820ccb77bf70f4311dd2cc64"}, + {file = "yarl-1.20.0-cp312-cp312-win_amd64.whl", hash = "sha256:3d7dbbe44b443b0c4aa0971cb07dcb2c2060e4a9bf8d1301140a33a93c98e18c"}, + {file = "yarl-1.20.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2137810a20b933b1b1b7e5cf06a64c3ed3b4747b0e5d79c9447c00db0e2f752f"}, + {file = "yarl-1.20.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:447c5eadd750db8389804030d15f43d30435ed47af1313303ed82a62388176d3"}, + {file = "yarl-1.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:42fbe577272c203528d402eec8bf4b2d14fd49ecfec92272334270b850e9cd7d"}, + {file = "yarl-1.20.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18e321617de4ab170226cd15006a565d0fa0d908f11f724a2c9142d6b2812ab0"}, + {file = "yarl-1.20.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4345f58719825bba29895011e8e3b545e6e00257abb984f9f27fe923afca2501"}, + {file = "yarl-1.20.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d9b980d7234614bc4674468ab173ed77d678349c860c3af83b1fffb6a837ddc"}, + {file = "yarl-1.20.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af4baa8a445977831cbaa91a9a84cc09debb10bc8391f128da2f7bd070fc351d"}, + {file = "yarl-1.20.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:123393db7420e71d6ce40d24885a9e65eb1edefc7a5228db2d62bcab3386a5c0"}, + {file = "yarl-1.20.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab47acc9332f3de1b39e9b702d9c916af7f02656b2a86a474d9db4e53ef8fd7a"}, + {file = "yarl-1.20.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4a34c52ed158f89876cba9c600b2c964dfc1ca52ba7b3ab6deb722d1d8be6df2"}, + {file = "yarl-1.20.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:04d8cfb12714158abf2618f792c77bc5c3d8c5f37353e79509608be4f18705c9"}, + {file = "yarl-1.20.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7dc63ad0d541c38b6ae2255aaa794434293964677d5c1ec5d0116b0e308031f5"}, + {file = "yarl-1.20.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d02b591a64e4e6ca18c5e3d925f11b559c763b950184a64cf47d74d7e41877"}, + {file = "yarl-1.20.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:95fc9876f917cac7f757df80a5dda9de59d423568460fe75d128c813b9af558e"}, + {file = "yarl-1.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bb769ae5760cd1c6a712135ee7915f9d43f11d9ef769cb3f75a23e398a92d384"}, + {file = "yarl-1.20.0-cp313-cp313-win32.whl", hash = "sha256:70e0c580a0292c7414a1cead1e076c9786f685c1fc4757573d2967689b370e62"}, + {file = "yarl-1.20.0-cp313-cp313-win_amd64.whl", hash = "sha256:4c43030e4b0af775a85be1fa0433119b1565673266a70bf87ef68a9d5ba3174c"}, + {file = "yarl-1.20.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b6c4c3d0d6a0ae9b281e492b1465c72de433b782e6b5001c8e7249e085b69051"}, + {file = "yarl-1.20.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8681700f4e4df891eafa4f69a439a6e7d480d64e52bf460918f58e443bd3da7d"}, + {file = "yarl-1.20.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:84aeb556cb06c00652dbf87c17838eb6d92cfd317799a8092cee0e570ee11229"}, + {file = "yarl-1.20.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f166eafa78810ddb383e930d62e623d288fb04ec566d1b4790099ae0f31485f1"}, + {file = "yarl-1.20.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5d3d6d14754aefc7a458261027a562f024d4f6b8a798adb472277f675857b1eb"}, + {file = "yarl-1.20.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a8f64df8ed5d04c51260dbae3cc82e5649834eebea9eadfd829837b8093eb00"}, + {file = "yarl-1.20.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4d9949eaf05b4d30e93e4034a7790634bbb41b8be2d07edd26754f2e38e491de"}, + {file = "yarl-1.20.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c366b254082d21cc4f08f522ac201d0d83a8b8447ab562732931d31d80eb2a5"}, + {file = "yarl-1.20.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91bc450c80a2e9685b10e34e41aef3d44ddf99b3a498717938926d05ca493f6a"}, + {file = "yarl-1.20.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9c2aa4387de4bc3a5fe158080757748d16567119bef215bec643716b4fbf53f9"}, + {file = "yarl-1.20.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:d2cbca6760a541189cf87ee54ff891e1d9ea6406079c66341008f7ef6ab61145"}, + {file = "yarl-1.20.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:798a5074e656f06b9fad1a162be5a32da45237ce19d07884d0b67a0aa9d5fdda"}, + {file = "yarl-1.20.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f106e75c454288472dbe615accef8248c686958c2e7dd3b8d8ee2669770d020f"}, + {file = "yarl-1.20.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:3b60a86551669c23dc5445010534d2c5d8a4e012163218fc9114e857c0586fdd"}, + {file = "yarl-1.20.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3e429857e341d5e8e15806118e0294f8073ba9c4580637e59ab7b238afca836f"}, + {file = "yarl-1.20.0-cp313-cp313t-win32.whl", hash = "sha256:65a4053580fe88a63e8e4056b427224cd01edfb5f951498bfefca4052f0ce0ac"}, + {file = "yarl-1.20.0-cp313-cp313t-win_amd64.whl", hash = "sha256:53b2da3a6ca0a541c1ae799c349788d480e5144cac47dba0266c7cb6c76151fe"}, + {file = "yarl-1.20.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:119bca25e63a7725b0c9d20ac67ca6d98fa40e5a894bd5d4686010ff73397914"}, + {file = "yarl-1.20.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:35d20fb919546995f1d8c9e41f485febd266f60e55383090010f272aca93edcc"}, + {file = "yarl-1.20.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:484e7a08f72683c0f160270566b4395ea5412b4359772b98659921411d32ad26"}, + {file = "yarl-1.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d8a3d54a090e0fff5837cd3cc305dd8a07d3435a088ddb1f65e33b322f66a94"}, + {file = "yarl-1.20.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f0cf05ae2d3d87a8c9022f3885ac6dea2b751aefd66a4f200e408a61ae9b7f0d"}, + {file = "yarl-1.20.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a884b8974729e3899d9287df46f015ce53f7282d8d3340fa0ed57536b440621c"}, + {file = "yarl-1.20.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f8d8aa8dd89ffb9a831fedbcb27d00ffd9f4842107d52dc9d57e64cb34073d5c"}, + {file = "yarl-1.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b4e88d6c3c8672f45a30867817e4537df1bbc6f882a91581faf1f6d9f0f1b5a"}, + {file = "yarl-1.20.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdb77efde644d6f1ad27be8a5d67c10b7f769804fff7a966ccb1da5a4de4b656"}, + {file = "yarl-1.20.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4ba5e59f14bfe8d261a654278a0f6364feef64a794bd456a8c9e823071e5061c"}, + {file = "yarl-1.20.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:d0bf955b96ea44ad914bc792c26a0edcd71b4668b93cbcd60f5b0aeaaed06c64"}, + {file = "yarl-1.20.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:27359776bc359ee6eaefe40cb19060238f31228799e43ebd3884e9c589e63b20"}, + {file = "yarl-1.20.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:04d9c7a1dc0a26efb33e1acb56c8849bd57a693b85f44774356c92d610369efa"}, + {file = "yarl-1.20.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:faa709b66ae0e24c8e5134033187a972d849d87ed0a12a0366bedcc6b5dc14a5"}, + {file = "yarl-1.20.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:44869ee8538208fe5d9342ed62c11cc6a7a1af1b3d0bb79bb795101b6e77f6e0"}, + {file = "yarl-1.20.0-cp39-cp39-win32.whl", hash = "sha256:b7fa0cb9fd27ffb1211cde944b41f5c67ab1c13a13ebafe470b1e206b8459da8"}, + {file = "yarl-1.20.0-cp39-cp39-win_amd64.whl", hash = "sha256:d4fad6e5189c847820288286732075f213eabf81be4d08d6cc309912e62be5b7"}, + {file = "yarl-1.20.0-py3-none-any.whl", hash = "sha256:5d0fe6af927a47a230f31e6004621fd0959eaa915fc62acfafa67ff7229a3124"}, + {file = "yarl-1.20.0.tar.gz", hash = "sha256:686d51e51ee5dfe62dec86e4866ee0e9ed66df700d55c828a615640adc885307"}, ] [package.dependencies] idna = ">=2.0" multidict = ">=4.0" -propcache = ">=0.2.0" +propcache = ">=0.2.1" [metadata] lock-version = "2.1" -python-versions = "^3.12" -content-hash = "533677253d4b6098e7b76cf439d6b1cc09a59b2e0b7744481bace360757f0cb7" +python-versions = ">=3.12" +content-hash = "acca86dc3528062287dee36e3a3ee37e3d8c303c4943a7c75411c05e5e519366" diff --git a/pyproject.toml b/pyproject.toml index ea5bc68..fed695d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,8 +7,8 @@ license = "MIT" [tool.poetry.dependencies] python = ">=3.12" -"discord.py" = "^2.4.0" -ruff = "^0.9.7" +"discord.py" = "^2.5.0" +ruff = "^0.11.6" [build-system] requires = ["poetry-core>=1.0.0"]

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 Date: Fri, 31 Jan 2025 00:07:26 -0500 Subject: [PATCH 033/133] Add screenshot --- README.md | 4 ++-- assets/screenshots/showcase1.png | Bin 0 -> 46779 bytes 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 assets/screenshots/showcase1.png diff --git a/README.md b/README.md index 02a15b4..59beb48 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,9 @@ DexScript is a set of commands for Ballsdex and CarFigures created by DotZZ that expands on the standalone admin commands and substitutes for the admin panel. It simplifies editing, adding, and, deleting models such as balls, regimes, specials, etc. -Let's say you wanted to update a ball's rarity to 2. You could run `UPDATE > BALL > Kingdom of Cyprus > RARITY > 2.0`. +Let's say you wanted to update a ball's rarity to 2. You could run `UPDATE > BALL > Mongolia > RARITY > 2.0`. -_**INSERT SCREENSHOT HERE**_ +![Updating rarity showcase](assets/screenshots/showcase1.png) See how simple that is? Using DexScript is way more efficient than eval commands and the admin panel. diff --git a/assets/screenshots/showcase1.png b/assets/screenshots/showcase1.png new file mode 100644 index 0000000000000000000000000000000000000000..93220fba5712aaa8109b217e71da64ef83bf68a7 GIT binary patch literal 46779 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 bKHJ_<7w|5esBvB(~68ygazae Date: Fri, 31 Jan 2025 14:57:28 -0500 Subject: [PATCH 034/133] WIP #3 --- DexScript/dexscript.py | 229 +++++++++++++++++++---------------------- 1 file changed, 108 insertions(+), 121 deletions(-) diff --git a/DexScript/dexscript.py b/DexScript/dexscript.py index 35378a2..3169748 100644 --- a/DexScript/dexscript.py +++ b/DexScript/dexscript.py @@ -36,23 +36,23 @@ START_CODE_BLOCK_RE = re.compile(r"^((```sql?)(?=\s)|(```))") FILENAME_RE = re.compile(r"^(.+)(\.\S+)$") +MEDIA_PATH = "./admin_panel/media" if os.path.isdir("./admin_panel.media") else "./static/uploads" + class Types(Enum): METHOD = 0 - NUMBER = 1 - STRING = 2 - BOOLEAN = 3 - MODEL = 4 - DATETIME = 5 + METHOD_EXT = 1 + BOOLEAN = 2 + MODEL = 3 + DATETIME = 4 class DexScriptError(Exception): pass -# Ported from the Ballsdex admin package. async def save_file(attachment: discord.Attachment) -> Path: - path = Path(f"./static/uploads/{attachment.filename}") + path = Path(f"{MEDIA_PATH}/{attachment.filename}") match = FILENAME_RE.match(attachment.filename) if not match: @@ -61,7 +61,7 @@ async def save_file(attachment: discord.Attachment) -> Path: i = 1 while path.exists(): - path = Path(f"./static/uploads/{match.group(1)}-{i}{match.group(2)}") + path = Path(f"{MEDIA_PATH}/{match.group(1)}-{i}{match.group(2)}") i = i + 1 await attachment.save(path) @@ -71,7 +71,7 @@ async def save_file(attachment: discord.Attachment) -> Path: @dataclass class Value: name: Any - type: Types + type: Types = Types.DEFAULT extra_data: list = datafield(default_factory=list) def __str__(self): @@ -168,25 +168,21 @@ class Utils: """ @staticmethod - def cleanup_code(content): - """ - Automatically removes code blocks from the code. - """ + def remove_code_markdown(content) -> str: if content.startswith("```") and content.endswith("```"): return START_CODE_BLOCK_RE.sub("", content)[:-3] return content.strip("` \n") - @staticmethod - def is_number(string): - try: - float(string) - return True - except ValueError: - return False + @static_method + def is_image(path) -> bool: + if path.startswith("/static/uploads"): + path.replace("/static/uploads/", "") + + return os.path.isfile(f"{MEDIA_PATH}/{path}") @staticmethod - def is_date(string): + def is_date(string) -> bool: try: parse_date(string) return True @@ -194,10 +190,15 @@ def is_date(string): return False -class Methods: - def __init__(self, parser): - self.parser = parser +@dataclass +class IncludeParser: + parser: Any +@dataclass +class Methods(IncludeParser): + """ + Main methods for DexScript. + """ async def help(self, ctx): """ @@ -250,7 +251,7 @@ async def update(self, ctx, model, identifier, attribute, value=None): if value is None and self.parser.attachments != []: image_path = await save_file(self.parser.attachments[0]) - new_attribute = Value(f"/{image_path}", Types.STRING) + new_attribute = Value(f"/{image_path}") self.parser.attachments.pop(0) @@ -283,7 +284,7 @@ async def view(self, ctx, model, identifier, attribute=None): fields["content"] += f"{key}: {value}\n" - if isinstance(value, str) and value.startswith("/static"): + if isinstance(value, str) and Utils.is_image(value): if fields.get("files") is None: fields["files"] = [] @@ -303,58 +304,6 @@ async def view(self, ctx, model, identifier, attribute=None): await ctx.send(f"```{new_attribute}```") - # TODO: Add attachment support. - async def filter_update( - self, ctx, model, attribute, old_value, new_value, tortoise_operator=None - ): - """ - Updates all instances of a model to the specified value where the specified attribute - meets the condition defined by the optional `TORTOISE_OPERATOR` argument - (e.g., greater than, equal to, etc.). - - Documentation - ------------- - FILTER_UPDATE > MODEL > ATTRIBUTE > OLD_VALUE > NEW_VALUE > TORTOISE_OPERATOR(?) - """ - lower_name = attribute.name.lower() - - if tortoise_operator is not None: - lower_name += f"__{tortoise_operator.name.lower()}" - - await model.name.filter(**{lower_name: old_value.name}).update( - **{lower_name: new_value.name} - ) - - await ctx.send( - f"Updated all `{model}` instances from a " - f"`{attribute}` value of `{old_value}` to `{new_value}`" - ) - - - async def filter_delete( - self, ctx, model, attribute, value, tortoise_operator=None - ): - """ - Deletes all instances of a model where the specified attribute meets the condition - defined by the optional `TORTOISE_OPERATOR` argument (e.g., greater than, equal to, etc.). - - Documentation - ------------- - FILTER_DELETE > MODEL > ATTRIBUTE > VALUE > TORTOISE_OPERATOR(?) - """ - lower_name = attribute.name.lower() - - if tortoise_operator is not None: - lower_name += f"__{tortoise_operator.name.lower()}" - - await model.name.filter(**{lower_name: value.name}).delete() - - await ctx.send( - f"Deleted all `{model}` instances with a " - f"`{attribute}` value of `{value}`" - ) - - async def attributes(self, ctx, model): """ Lists all changeable attributes of a model. @@ -378,13 +327,66 @@ async def attributes(self, ctx, model): await ctx.send(f"```{parameters}```") + class Filter: + """ + Filter commands used for mass updating, deleting, and viewing models. + """ + + # TODO: Add attachment support. + async def update( + self, ctx, model, attribute, old_value, new_value, tortoise_operator=None + ): + """ + Updates all instances of a model to the specified value where the specified attribute + meets the condition defined by the optional `TORTOISE_OPERATOR` argument + (e.g., greater than, equal to, etc.). + + Documentation + ------------- + FILTER > UPDATE > MODEL > ATTRIBUTE > OLD_VALUE > NEW_VALUE > TORTOISE_OPERATOR(?) + """ + lower_name = attribute.name.lower() + + if tortoise_operator is not None: + lower_name += f"__{tortoise_operator.name.lower()}" + + await model.name.filter(**{lower_name: old_value.name}).update( + **{lower_name: new_value.name} + ) + + await ctx.send( + f"Updated all `{model}` instances from a " + f"`{attribute}` value of `{old_value}` to `{new_value}`" + ) + + def delete( + self, ctx, model, attribute, value, tortoise_operator=None + ): + """ + Deletes all instances of a model where the specified attribute meets the condition + defined by the optional `TORTOISE_OPERATOR` argument + (e.g., greater than, equal to, etc.). + + Documentation + ------------- + FILTER > DELETE > MODEL > ATTRIBUTE > VALUE > TORTOISE_OPERATOR(?) + """ + lower_name = attribute.name.lower() + + if tortoise_operator is not None: + lower_name += f"__{tortoise_operator.name.lower()}" + + await model.name.filter(**{lower_name: value.name}).delete() + + await ctx.send( + f"Deleted all `{model}` instances with a " + f"`{attribute}` value of `{value}`" + ) + + class Eval: """ Developer commands for executing evals. - - Documentation - ------------- - REFER TO WIKI. """ def __loaded__(self): @@ -429,7 +431,7 @@ async def save(self, ctx, name): await channel.send("Preset saving has timed out.") return with open(f"eval_presets/{name}.py", "w") as file: - file.write(Utils.cleanup_code(message.content)) + file.write(Utils.remove_code_markdown(message.content)) await ctx.send(f"`{name}` eval preset has been saved!") @@ -456,38 +458,9 @@ async def run(self, ctx, name): # TODO: Allow args to be passed through `run`. await ctx.message.add_reaction("✅") - async def dev(self, ctx, operation, arg1): - """ - Developer commands for executing evals. - - Documentation - ------------- - DEV > OPERATION > ARG1(?) - """ - match operation: - case "exec_git": - link = arg1.name.replace("https://github.com/", "") - "" - https://github.com/Dotsian/DexScript/blob/dev/installer.py - r = requests.get( - "https://api.github.com/repos/Dotsian/DexScript/contents/DexScript/github/installer.py", - {"ref": "dev"} - ) - - if r.status_code == requests.codes.ok: - content = base64.b64decode(r.json()["content"]) - await ctx.invoke(bot.get_command("eval"), body=content.decode("UTF-8")) - else: - await ctx.send("Failed to install DexScript BETA.\nReport this issue to `dot_zz` on Discord.") - print(f"ERROR CODE: {r.status_code}") - class File: """ Developer commands for managing and modifying the bot's internal filesystem. - - Documentation - ------------- - REFER TO WIKI. """ async def read(self, ctx, file_path): @@ -617,16 +590,26 @@ async def get_model(self, model, identifier): return returned_model[0] def create_value(self, line): - value = Value(line, Types.STRING) + value = Value(line) lower = line.lower() - method_functions = [x for x in dir(Methods) if not x.startswith("__")] + classes = [] + functions = [] + + method_items = [x for x in dir(Methods) if not x.startswith("__")] + + for func in method_items: + if inspect.isclass(getattr(Methods, func)): + classes.append(func) + continue + + functions.append(func) type_dict = { - Types.METHOD: lower in method_functions, + Types.METHOD: lower in functions, + Types.METHOD_EXT: lower in classes, Types.MODEL: lower in Models.all(True, key=str.lower), Types.DATETIME: Utils.is_date(lower) and lower.count("-") >= 2, - Types.NUMBER: Utils.is_number(lower), Types.BOOLEAN: lower in ["true", "false"] } @@ -673,18 +656,22 @@ async def execute(self, code: str): method = line2[0] + line2.pop(0) + if method.type != Types.METHOD: return self.error( f"'{method.name}' is not a valid command.", traceback.format_exc() ) - - method_function = getattr(loaded_methods, method.name.lower()) - line2.pop(0) + method_call = getattr(loaded_methods, method.name.lower()) + + if line2[0].type == Types.METHOD_EXT: + method_call = getattr(method_call, line2[1].name.title()) + line2.pop(0) try: - await method_function(self.ctx, *line2) + await method_call(self.ctx, *line2) except TypeError: return self.error( f"Argument missing when calling '{method.name}'.", @@ -736,7 +723,7 @@ async def run(self, ctx: commands.Context, *, code: str): code: str The code you want to execute. """ - body = Utils.cleanup_code(code) + body = Utils.remove_code_markdown(code) version_check = self.check_version() From 3b638ff3e6b276c12051fa5ba04df1e83b99cabf Mon Sep 17 00:00:00 2001 From: Dotsian Date: Fri, 31 Jan 2025 19:21:03 -0500 Subject: [PATCH 035/133] Add DexScript Logo --- assets/DexScriptLogo.png | Bin 0 -> 19500 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/DexScriptLogo.png diff --git a/assets/DexScriptLogo.png b/assets/DexScriptLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..89c50d844bc49aa6cf2cebbe5da5191d55c67f94 GIT binary patch literal 19500 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 Date: Sat, 1 Feb 2025 03:40:06 -0500 Subject: [PATCH 036/133] Convert DexScript into a package --- DexScript/dexscript.py | 833 ------------------ DexScript/github/__EXCLUDED__/installer.py | 191 ++++ .../github/{ => __EXCLUDED__}/uninstaller.py | 0 DexScript/github/installer.py | 378 ++++---- DexScript/package/__init__.py | 5 + DexScript/package/cog.py | 159 ++++ DexScript/package/commands.py | 376 ++++++++ DexScript/package/parser.py | 170 ++++ DexScript/package/utils.py | 125 +++ README.md | 2 - pyproject.toml | 2 +- 11 files changed, 1215 insertions(+), 1026 deletions(-) delete mode 100644 DexScript/dexscript.py create mode 100644 DexScript/github/__EXCLUDED__/installer.py rename DexScript/github/{ => __EXCLUDED__}/uninstaller.py (100%) create mode 100644 DexScript/package/__init__.py create mode 100644 DexScript/package/cog.py create mode 100644 DexScript/package/commands.py create mode 100644 DexScript/package/parser.py create mode 100644 DexScript/package/utils.py diff --git a/DexScript/dexscript.py b/DexScript/dexscript.py deleted file mode 100644 index 3169748..0000000 --- a/DexScript/dexscript.py +++ /dev/null @@ -1,833 +0,0 @@ -import asyncio -import base64 -import inspect -import logging -import os -import re -import shutil -import traceback -from dataclasses import dataclass -from dataclasses import field as datafield -from difflib import get_close_matches -from enum import Enum -from pathlib import Path -from typing import Any - -import discord -import requests -from dateutil.parser import parse as parse_date -from discord.ext import commands - -DIR = "ballsdex" if os.path.isdir("ballsdex") else "carfigures" - -if DIR == "ballsdex": - from ballsdex.core.models import Ball, Economy, Regime, Special # noqa: F401, I001 - from ballsdex.settings import settings -else: - from carfigures.core.models import Car, CarType, Country, Event, FontsPack # noqa: F401, I001 - from carfigures.settings import settings - - -log = logging.getLogger(f"{DIR}.core.dexscript") - -__version__ = "0.5" - - -START_CODE_BLOCK_RE = re.compile(r"^((```sql?)(?=\s)|(```))") -FILENAME_RE = re.compile(r"^(.+)(\.\S+)$") - -MEDIA_PATH = "./admin_panel/media" if os.path.isdir("./admin_panel.media") else "./static/uploads" - - -class Types(Enum): - METHOD = 0 - METHOD_EXT = 1 - BOOLEAN = 2 - MODEL = 3 - DATETIME = 4 - - -class DexScriptError(Exception): - pass - - -async def save_file(attachment: discord.Attachment) -> Path: - path = Path(f"{MEDIA_PATH}/{attachment.filename}") - match = FILENAME_RE.match(attachment.filename) - - if not match: - raise TypeError("The file you uploaded lacks an extension.") - - i = 1 - - while path.exists(): - path = Path(f"{MEDIA_PATH}/{match.group(1)}-{i}{match.group(2)}") - i = i + 1 - - await attachment.save(path) - return path - - -@dataclass -class Value: - name: Any - type: Types = Types.DEFAULT - extra_data: list = datafield(default_factory=list) - - def __str__(self): - return str(self.name) - - -@dataclass -class Settings: - """ - Settings class for DexScript. - """ - - debug: bool = False - versioncheck: bool = False - reference: str = "main" - - -config = Settings() - - -@dataclass -class Models: - """ - Model functions. - """ - - @staticmethod - def fetch_model(field): - return globals().get(field) - - @staticmethod - def all(names=False, key=None): - allowed_list = { - "ballsdex": [ - "Ball", - "Regime", - "Economy", - "Special" - ], - "carfigures": [ - "Car", - "CarType", - "Country", - "Event", - "FontsPack" - ] - } - - return_list = allowed_list[DIR] - - if not names: - return_list = [globals().get(x) for x in return_list if globals().get(x) is not None] - - if key is not None: - return_list = [key(x) for x in return_list] - - return return_list - - - - @staticmethod - def port(original: str | list[str]): - """ - Translates model and field names into a format for both Ballsdex and CarFigures. - - Parameters - ---------- - original: str | list[str] - The original string or list of strings you want to translate. - """ - if DIR == "ballsdex": - return original - - translation = { - "BALL": "ENTITY", - "COUNTRY": "fullName", - "SHORT_NAME": "shortName", - "CATCH_NAMES": "catchNames", - "ICON": "image", - } - - if isinstance(original, list): - translated_copy = [translation.get(x.upper(), x) for x in original] - else: - translated_copy = translation.get(original.upper(), original) - - return translated_copy - - -@dataclass -class Utils: - """ - DexScript utility functions. - """ - - @staticmethod - def remove_code_markdown(content) -> str: - if content.startswith("```") and content.endswith("```"): - return START_CODE_BLOCK_RE.sub("", content)[:-3] - - return content.strip("` \n") - - @static_method - def is_image(path) -> bool: - if path.startswith("/static/uploads"): - path.replace("/static/uploads/", "") - - return os.path.isfile(f"{MEDIA_PATH}/{path}") - - @staticmethod - def is_date(string) -> bool: - try: - parse_date(string) - return True - except Exception: - return False - - -@dataclass -class IncludeParser: - parser: Any - -@dataclass -class Methods(IncludeParser): - """ - Main methods for DexScript. - """ - - async def help(self, ctx): - """ - Lists all DexScript commands and provides documentation for them. - - Documentation - ------------- - HELP - """ - # getattr(Methods, command).__doc__.replace("\n", "").split("Documentation-------------") - pass - - - async def create(self, ctx, model, identifier): - """ - Creates a model instance. - - Documentation - ------------- - CREATE > MODEL > IDENTIFIER - """ - await self.parser.create_model(model.name, identifier) - - await ctx.send(f"Created `{identifier}`") - - - async def delete(self, ctx, model, identifier): - """ - Deletes a model instance. - - Documentation - ------------- - DELETE > MODEL > IDENTIFIER - """ - await self.parser.get_model(model, identifier.name).delete() - - await ctx.send(f"Deleted `{identifier}`") - - - async def update(self, ctx, model, identifier, attribute, value=None): - """ - Updates a model instance's attribute. If value is None, it will check - for any attachments. - - Documentation - ------------- - UPDATE > MODEL > IDENTIFIER > ATTRIBUTE > VALUE(?) - """ - new_attribute = value - - if value is None and self.parser.attachments != []: - image_path = await save_file(self.parser.attachments[0]) - new_attribute = Value(f"/{image_path}") - - self.parser.attachments.pop(0) - - # models_list = Models.all(True) - - await self.parser.get_model(model, identifier.name).update( - **{attribute.name.lower(): new_attribute.name} - ) - - await ctx.send(f"Updated `{identifier}'s` {attribute} to `{new_attribute.name}`") - - - async def view(self, ctx, model, identifier, attribute=None): - """ - Displays an attribute of a model instance. If `ATTRIBUTE` is left blank, - it will display every attribute of that model instance. - - Documentation - ------------- - VIEW > MODEL > IDENTIFIER > ATTRIBUTE(?) - """ - returned_model = await self.parser.get_model(model, identifier.name) - - if attribute is None: - fields = {"content": "```"} - - for key, value in vars(returned_model).items(): - if key.startswith("_"): - continue - - fields["content"] += f"{key}: {value}\n" - - if isinstance(value, str) and Utils.is_image(value): - if fields.get("files") is None: - fields["files"] = [] - - fields["files"].append(discord.File(value[1:])) - - fields["content"] += "```" - - await ctx.send(**fields) - return - - new_attribute = getattr(returned_model, attribute.name.lower()) - - if isinstance(new_attribute, str) and os.path.isfile(new_attribute[1:]): - await ctx.send(f"```{new_attribute}```", file=discord.File(new_attribute[1:])) - return - - await ctx.send(f"```{new_attribute}```") - - - async def attributes(self, ctx, model): - """ - Lists all changeable attributes of a model. - - Documentation - ------------- - ATTRIBUTES > MODEL - """ - model_name = ( - model.name if isinstance(model.name, str) else model.name.__name__ - ) - - parameters = f"{model_name.upper()} ATTRIBUTES:\n\n" - - for field in vars(model.name()): # type: ignore - if field[:1] == "_": - continue - - parameters += f"- {field.replace(' ', '_').upper()}\n" - - await ctx.send(f"```{parameters}```") - - - class Filter: - """ - Filter commands used for mass updating, deleting, and viewing models. - """ - - # TODO: Add attachment support. - async def update( - self, ctx, model, attribute, old_value, new_value, tortoise_operator=None - ): - """ - Updates all instances of a model to the specified value where the specified attribute - meets the condition defined by the optional `TORTOISE_OPERATOR` argument - (e.g., greater than, equal to, etc.). - - Documentation - ------------- - FILTER > UPDATE > MODEL > ATTRIBUTE > OLD_VALUE > NEW_VALUE > TORTOISE_OPERATOR(?) - """ - lower_name = attribute.name.lower() - - if tortoise_operator is not None: - lower_name += f"__{tortoise_operator.name.lower()}" - - await model.name.filter(**{lower_name: old_value.name}).update( - **{lower_name: new_value.name} - ) - - await ctx.send( - f"Updated all `{model}` instances from a " - f"`{attribute}` value of `{old_value}` to `{new_value}`" - ) - - def delete( - self, ctx, model, attribute, value, tortoise_operator=None - ): - """ - Deletes all instances of a model where the specified attribute meets the condition - defined by the optional `TORTOISE_OPERATOR` argument - (e.g., greater than, equal to, etc.). - - Documentation - ------------- - FILTER > DELETE > MODEL > ATTRIBUTE > VALUE > TORTOISE_OPERATOR(?) - """ - lower_name = attribute.name.lower() - - if tortoise_operator is not None: - lower_name += f"__{tortoise_operator.name.lower()}" - - await model.name.filter(**{lower_name: value.name}).delete() - - await ctx.send( - f"Deleted all `{model}` instances with a " - f"`{attribute}` value of `{value}`" - ) - - - class Eval: - """ - Developer commands for executing evals. - """ - - def __loaded__(self): - os.makedirs("eval_presets", exist_ok=True) - - - async def exec_git(self, ctx, link): - link = link.split("/") - api = f"https://api.github.com/repos/{link[0]}/{link[1]}/contents/{link[2]}" - - request = requests.get(api) - - if request.status_code != requests.codes.ok: - raise DexScriptError(f"Request Error Code {request.status_code}") - - content = base64.b64decode(r.json()["content"]) - - try: - await ctx.invoke(bot.get_command("eval"), body=content.decode("UTF-8")) - except Exception as error: - raise DexScriptError(error) - else: - await ctx.message.add_reaction("✅") - - - async def save(self, ctx, name): - if len(name) > 25: - raise DexScriptError(f"`{name}` is above the 25 character limit.") - - if os.path.isfile(f"eval_presets/{name}.py"): - raise DexScriptError(f"`{name}` aleady exists.") - - save_content = "" - - await ctx.send("Please paste the eval command below...") - - try: - message = await bot.wait_for( - "message", timeout=15, check=lambda m: m.author == ctx.message.author - ) - except asyncio.TimeoutError: - await channel.send("Preset saving has timed out.") - return - with open(f"eval_presets/{name}.py", "w") as file: - file.write(Utils.remove_code_markdown(message.content)) - - await ctx.send(f"`{name}` eval preset has been saved!") - - - async def remove(self, ctx, name): - if not os.path.isfile(f"eval_presets/{name}.py"): - raise DexScriptError(f"`{name}` does not exists.") - - os.remove(f"eval_presets/{name}.py") - - await ctx.send(f"Removed `{name}` preset.") - - - async def run(self, ctx, name): # TODO: Allow args to be passed through `run`. - if not os.path.isfile(f"eval_presets/{name}.py"): - raise DexScriptError(f"`{name}` does not exists.") - - with open(f"eval_presets/{name}.py", "r") as file: - try: - await ctx.invoke(bot.get_command("eval"), body=file.read()) - except Exception as error: - raise DexScriptError(error) - else: - await ctx.message.add_reaction("✅") - - - class File: - """ - Developer commands for managing and modifying the bot's internal filesystem. - """ - - async def read(self, ctx, file_path): - await ctx.send(file=discord.File(file_path.name)) - - - async def write(self, ctx, file_path): - new_file = ctx.message.attachments[0] - - with open(file_path.name, "w") as opened_file: - contents = await new_file.read() - opened_file.write(contents.decode("utf-8")) - - await ctx.send(f"Wrote to `{file_path}`") - - - async def clear(self, ctx, file_path): - with open(file_path.name, "w") as _: - pass - - await ctx.send(f"Cleared `{file_path}`") - - - async def listdir(self, ctx, file_path=None): - path = file_path.name if file_path is not None else None - - await ctx.send(f"```{'\n'.join(os.listdir(path))}```") - - - async def delete(self, ctx, file_path): - is_dir = os.path.isdir(file_path.name) - - file_type = "directory" if is_dir else "file" - - if is_dir: - shutil.rmtree(file_path.name) - else: - os.remove(file_path.name) - - await ctx.send(f"Deleted `{file_path}` {file_type}") - - -class DexScriptParser: - """ - This class is used to parse DexScript into Python code. - """ - - def __init__(self, ctx): - self.ctx = ctx - self.attachments = ctx.message.attachments - self.values = [] - - @staticmethod - def autocorrect(string, correction_list, error="does not exist."): - autocorrection = get_close_matches(string, correction_list) - - if not autocorrection or autocorrection[0] != string: - suggestion = f"\nDid you mean '{autocorrection[0]}'?" if autocorrection else "" - - raise DexScriptError(f"'{string}' {error}{suggestion}") - - return autocorrection[0] - - @staticmethod - def extract_str_attr(object): - expression = r"return\s+self\.(\w+)" - - return re.search(expression, inspect.getsource(object.__str__)).group(1) - - async def create_model(self, model, identifier): - fields = {} - - for field, field_type in model._meta.fields_map.items(): - field_data = vars(model()).get(field) - - special_list = { - "Identifiers": ["country", "catch_names", "name"], - "Ignore": ["id", "short_name"] - } - - for key, value in special_list.items(): - special_list[key] = Models.port(value) - - if field_data is not None or field in special_list["Ignore"]: - continue - - if key in special_list["Identifiers"]: - fields[key] = identifier - continue - - match field_type: - case "ForeignKeyFieldInstance": - fetched_model = Models.fetch_model(field) - instance = await fetched_model.first() - - if instance is None: - raise DexScriptError(f"Could not find default {field}") - - fields[field] = instance.pk - - case "BigIntField": - fields[field] = 100 ** 8 - - case "BackwardFKRelation" | "JSONField": - continue - - case _: - fields[field] = 1 - - await model.create(**fields) - - async def get_model(self, model, identifier): - attribute = self.extract_str_attr(model.name) - - correction_list = await model.name.all().values_list(attribute, flat=True) - translated_identifier = Models.port(model.extra_data[0].lower()) - - try: - returned_model = await model.name.filter( - **{ - translated_identifier: self.autocorrect(identifier, correction_list) - } - ) - except AttributeError: - raise DexScriptError(f"'{model}' is not a valid model.") - - return returned_model[0] - - def create_value(self, line): - value = Value(line) - lower = line.lower() - - classes = [] - functions = [] - - method_items = [x for x in dir(Methods) if not x.startswith("__")] - - for func in method_items: - if inspect.isclass(getattr(Methods, func)): - classes.append(func) - continue - - functions.append(func) - - type_dict = { - Types.METHOD: lower in functions, - Types.METHOD_EXT: lower in classes, - Types.MODEL: lower in Models.all(True, key=str.lower), - Types.DATETIME: Utils.is_date(lower) and lower.count("-") >= 2, - Types.BOOLEAN: lower in ["true", "false"] - } - - for key, operation in type_dict.items(): - if operation is False: - continue - - value.type = key - break - - match value.type: - case Types.MODEL: - model = Models.fetch_model(line) - - string_key = self.extract_str_attr(model) - - value.name = model - value.extra_data.append(string_key) - - case Types.BOOLEAN: - value.name = lower == "true" - - case Types.DATETIME: - value.name = parse_date(value.name) - - return value - - def error(self, message, log): - return (message, log)[config.debug] - - async def execute(self, code: str): - loaded_methods = Methods(self) - - split_code = [x for x in code.split("\n") if x.strip() != ""] - - parsed_code = [ - [self.create_value(s.strip()) for s in re.findall(r"[^>]+", line)] - for line in split_code if not line.strip().startswith("--") - ] - - for line2 in parsed_code: - if line2 == []: - continue - - method = line2[0] - - line2.pop(0) - - if method.type != Types.METHOD: - return self.error( - f"'{method.name}' is not a valid command.", - traceback.format_exc() - ) - - method_call = getattr(loaded_methods, method.name.lower()) - - if line2[0].type == Types.METHOD_EXT: - method_call = getattr(method_call, line2[1].name.title()) - line2.pop(0) - - try: - await method_call(self.ctx, *line2) - except TypeError: - return self.error( - f"Argument missing when calling '{method.name}'.", - traceback.format_exc() - ) - - -class DexScript(commands.Cog): - """ - DexScript commands - """ - - def __init__(self, bot): - self.bot = bot - - @staticmethod - def check_version(): - if not config.versioncheck: - return None - - r = requests.get( - "https://api.github.com/repos/Dotsian/DexScript/contents/pyproject.toml", - {"ref": config.branch}, - ) - - if r.status_code != requests.codes.ok: - return - - toml_content = base64.b64decode(r.json()["content"]).decode("UTF-8") - new_version = re.search(r'version\s*=\s*"(.*?)"', toml_content).group(1) - - if new_version != __version__: - return ( - f"Your DexScript version ({__version__}) is outdated. " - f"Please update to version ({new_version}) " - f"using `{settings.prefix}update-ds`." - ) - - return None - - @commands.command() - @commands.is_owner() - async def run(self, ctx: commands.Context, *, code: str): - """ - Executes DexScript code. - - Parameters - ---------- - code: str - The code you want to execute. - """ - body = Utils.remove_code_markdown(code) - - version_check = self.check_version() - - if version_check: - await ctx.send(f"-# {version_check}") - - dexscript_instance = DexScriptParser(ctx) - - try: - result = await dexscript_instance.execute(body) - except Exception as error: - full_error = traceback.format_exc() if config.debug else error - - await ctx.send(f"```ERROR: {full_error}```") - return - else: - if result is not None: - await ctx.send(f"```ERROR: {result}```") - return - - await ctx.message.add_reaction("✅") - - @commands.command() - @commands.is_owner() - async def about(self, ctx: commands.Context): - """ - Displays information about DexScript. - """ - guide_link = "https://github.com/Dotsian/DexScript/wiki/Commands" - discord_link = "https://discord.gg/EhCxuNQfzt" - - description = ( - "DexScript is a set of commands for Ballsdex and CarFigures created by DotZZ " - "that expands on the standalone admin commands and substitutes for the admin panel. " - "It simplifies editing, adding, deleting, and displaying data for models such as " - "balls, regimes, specials, etc.\n\n" - f"Refer to the official [DexScript guide](<{guide_link}>) for information " - f"about DexScript's functionality or use the `{settings.prefix}.run HELP` to display " - "a list of all commands and what they do.\n\n" - "If you want to follow DexScript or require assistance, join the official " - f"[DexScript Discord server](<{discord_link}>)." - ) - - embed = discord.Embed( - title="DexScript - BETA", - description=description, - color=discord.Color.from_str("#03BAFC"), - ) - - embed.add_field( - name="Updating DexScript", - value=( - "To update DexScript, run " - f"`{settings.prefix}.run EVAL > EXEC_GIT > Dotsian/DexScript/installer.py`" - ) - ) - - version_check = "OUTDATED" if self.check_version() is not None else "LATEST" - - embed.set_thumbnail(url="https://i.imgur.com/uKfx0qO.png") - embed.set_footer(text=f"DexScript {__version__} ({version_check})") - - await ctx.send(embed=embed) - - @commands.command(name="reload-ds") - @commands.is_owner() - async def reload_ds(self, ctx: commands.Context): - """ - Reloads DexScript. - """ - await self.bot.reload_extension(f"{DIR}.core.dexscript") - await ctx.send("Reloaded DexScript") - - @commands.command() - @commands.is_owner() - async def setting( - self, ctx: commands.Context, setting: str, value: str | None = None - ): - """ - Changes a setting based on the value provided. - - Parameters - ---------- - setting: str - The setting you want to toggle. - value: str | None - The value you want to set the setting to. - """ - setting = setting.lower() - - if setting not in vars(config): - await ctx.send(f"`{setting}` is not a valid setting.") - return - - setting_value = vars(config)[setting] - new_value = value - - if isinstance(setting_value, bool): - new_value = bool(value) if value else not setting_value - - setattr(config, setting, new_value) - - await ctx.send(f"`{setting}` has been set to `{new_value}`") - - -async def setup(bot): - await bot.add_cog(DexScript(bot)) diff --git a/DexScript/github/__EXCLUDED__/installer.py b/DexScript/github/__EXCLUDED__/installer.py new file mode 100644 index 0000000..e50c12a --- /dev/null +++ b/DexScript/github/__EXCLUDED__/installer.py @@ -0,0 +1,191 @@ +# # # # # # # # # # # # # # # # # # # # # # # # # # # # +# OFFICIAL DEXSCRIPT INSTALLER # +# # +# This will install DexScript onto your bot. # +# For additional information, read the wiki guide. # +# An explanation of the code will be provided below. # +# # +# THIS CODE IS RAN VIA THE `EVAL` COMMAND. # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # + + +from base64 import b64decode +from dataclasses import dataclass +from datetime import datetime +from io import StringIO +from os import path +from time import time +from traceback import format_exc + +from requests import codes, get + +DIR = "ballsdex" if path.isdir("ballsdex") else "carfigures" + +if DIR == "ballsdex": + from ballsdex.settings import settings +else: + from carfigures.settings import settings + + +@dataclass +class InstallerConfig: + """ + Configuration class for the installer. + """ + + github = ["Dotsian/DexScript", "dev"] + migrations = [ + ( + "¶¶await self.add_cog(Core(self))", + '¶¶await self.load_extension("$DIR.packages.dexscript")\n' + ) + ] + +config = InstallerConfig() + + +class Installer: + def __init__(self): + self.message = None + self.managed_time = None + + self.keywords = ["Installed", "Installing", "Install"] + self.updating = path.isdir(f"{DIR}/packages/dexscript") + + if self.updating: + self.keywords = ["Updated", "Updating", "Update"] + + self.embed = discord.Embed( + title=f"{self.keywords[1]} DexScript", + description=( + f"DexScript is being {self.keywords[0].lower()} to your bot.\n" + "Please do not turn off your bot." + ), + color=discord.Color.from_str("#FFF" if DIR == "carfigures" else "#03BAFC"), + timestamp=datetime.now(), + ) + + self.embed.set_thumbnail(url="https://i.imgur.com/uKfx0qO.png") + + @staticmethod + def format_migration(line): + return ( + line.replace(" ", "") + .replace("¶", " ") + .replace("/n", "\n") + .replace("$DIR", DIR) + ) + + async def error(self, error, exception=False): + self.embed.title = "DexScript ERROR" + + description = ( + f"Please submit a [bug report]" + f"() to the GitHub page" + ) + + if exception: + description += " and attach the file below." + else: + description += f".\n```{error}```" + + self.embed.description = description + self.embed.color = discord.Color.red() + + fields = {"embed": self.embed} + + if exception: + fields["attachments"] = [discord.File(StringIO(error), filename="DexScript.log")] + + await self.message.edit(**fields) + + async def run(self, ctx): + """ + Installs or updates the latest DexScript version. + + - Fetches the contents of the `dexscript.py` file from the official DexScript repository, + and writes that content onto a local `dexscript.py` file. + + - Apply migrations from the `config.migrations` list onto the `bot.py` file to allow + DexScript to load on bot startup. + + - Load or reload the DexScript extension. + """ + self.message = await ctx.send(embed=self.embed) + self.managed_time = time() + + link = f"https://api.github.com/repos/{config.github[0]}/contents/" + + request = get(f"{link}/dexscript.py", {"ref": config.github[1]}) + + if request.status_code != codes.ok: + await self.error( + "Failed to fetch the `dexscript.py` file. " + f"Recieved request status code `{request.status_code}`." + ) + return + + request = request.json() + content = b64decode(request["content"]) + + with open(f"{DIR}/core/dexscript.py", "w") as opened_file: + opened_file.write(content.decode("UTF-8")) + + with open(f"{DIR}/core/bot.py", "r") as read_file: + lines = read_file.readlines() + + stripped_lines = [x.rstrip() for x in lines] + + for index, line in enumerate(lines): + for migration in config.migrations: + original = self.format_migration(migration[0]) + new = self.format_migration(migration[1]) + + if line.rstrip() != original or lines[index + 1] == new: + continue + + lines.insert(stripped_lines.index(original) + 1, new) + + with open(f"{DIR}/core/bot.py", "w") as write_file: + write_file.writelines(lines) + + try: + await bot.load_extension(f"{DIR}.core.dexscript") + except commands.ExtensionAlreadyLoaded: + await bot.reload_extension(f"{DIR}.core.dexscript") + + if self.updating: + request = get(f"{link}/version.txt", {"ref": config.github[1]}) + + new_version = ( + b64decode(request.json()["content"]).decode("UTF-8").rstrip() + ) + + self.embed.description = ( + f"DexScript has been updated to v{new_version}.\n" + f"Use `{settings.prefix}about` to view details about DexScript." + ) + else: + self.embed.description = ( + "DexScript has been installed to your bot\n" + f"Use `{settings.prefix}about` to view details about DexScript." + ) + + self.embed.set_footer( + text=f"DexScript took {round((time() - self.managed_time) * 1000)}ms " + f"to {self.keywords[2].lower()}" + ) + + await self.message.edit(embed=self.embed) + +installer = Installer() + +try: + await installer.run(ctx) +except Exception: + installer.embed.set_footer( + text=f"Error occurred {round((time() - installer.managed_time) * 1000)}ms " + f"into {installer.keywords[1].lower()}" + ) + + await installer.error(format_exc(), True) diff --git a/DexScript/github/uninstaller.py b/DexScript/github/__EXCLUDED__/uninstaller.py similarity index 100% rename from DexScript/github/uninstaller.py rename to DexScript/github/__EXCLUDED__/uninstaller.py diff --git a/DexScript/github/installer.py b/DexScript/github/installer.py index a582a14..1da5ac3 100644 --- a/DexScript/github/installer.py +++ b/DexScript/github/installer.py @@ -1,190 +1,188 @@ -# # # # # # # # # # # # # # # # # # # # # # # # # # # # -# OFFICIAL DEXSCRIPT INSTALLER # -# # -# This will install DexScript onto your bot. # -# For additional information, read the wiki guide. # -# An explanation of the code will be provided below. # -# # -# THIS CODE IS RAN VIA THE `EVAL` COMMAND. # -# # # # # # # # # # # # # # # # # # # # # # # # # # # # - - -from base64 import b64decode -from dataclasses import dataclass -from datetime import datetime -from io import StringIO -from os import path -from time import time -from traceback import format_exc - -from requests import codes, get - -DIR = "ballsdex" if path.isdir("ballsdex") else "carfigures" - -if DIR == "ballsdex": - from ballsdex.settings import settings -else: - from carfigures.settings import settings - - -@dataclass -class InstallerConfig: - """ - Configuration class for the installer. - """ - - github = ["Dotsian/DexScript", "dev"] - migrations = [ - ( - "¶¶await self.add_cog(Core(self))", - '¶¶await self.load_extension("$DIR.core.dexscript")\n' - ) - ] - -config = InstallerConfig() - -class Installer: - def __init__(self): - self.message = None - self.managed_time = None - - self.keywords = ["Installed", "Installing", "Install"] - self.updating = path.isfile(f"{DIR}/core/dexscript.py") - - if self.updating: - self.keywords = ["Updated", "Updating", "Update"] - - self.embed = discord.Embed( - title=f"{self.keywords[1]} DexScript", - description=( - f"DexScript is being {self.keywords[0].lower()} to your bot.\n" - "Please do not turn off your bot." - ), - color=discord.Color.from_str("#03BAFC"), - timestamp=datetime.now(), - ) - - self.embed.set_thumbnail(url="https://i.imgur.com/uKfx0qO.png") - - @staticmethod - def format_migration(line): - return ( - line.replace(" ", "") - .replace("¶", " ") - .replace("/n", "\n") - .replace("$DIR", DIR) - ) - - async def error(self, error, exception=False): - self.embed.title = "DexScript ERROR" - - description = ( - f"Please submit a [bug report]" - f"() to the GitHub page" - ) - - if exception: - description += " and attach the file below." - else: - description += f".\n```{error}```" - - self.embed.description = description - self.embed.color = discord.Color.red() - - fields = {"embed": self.embed} - - if exception: - fields["attachments"] = [discord.File(StringIO(error), filename="DexScript.log")] - - await self.message.edit(**fields) - - async def run(self, ctx): - """ - Installs or updates the latest DexScript version. - - - Fetches the contents of the `dexscript.py` file from the official DexScript repository, - and writes that content onto a local `dexscript.py` file. - - - Apply migrations from the `config.migrations` list onto the `bot.py` file to allow - DexScript to load on bot startup. - - - Load or reload the DexScript extension. - """ - self.message = await ctx.send(embed=self.embed) - self.managed_time = time() - - link = f"https://api.github.com/repos/{config.github[0]}/contents/" - - request = get(f"{link}/dexscript.py", {"ref": config.github[1]}) - - if request.status_code != codes.ok: - await self.error( - "Failed to fetch the `dexscript.py` file. " - f"Recieved request status code `{request.status_code}`." - ) - return - - request = request.json() - content = b64decode(request["content"]) - - with open(f"{DIR}/core/dexscript.py", "w") as opened_file: - opened_file.write(content.decode("UTF-8")) - - with open(f"{DIR}/core/bot.py", "r") as read_file: - lines = read_file.readlines() - - stripped_lines = [x.rstrip() for x in lines] - - for index, line in enumerate(lines): - for migration in config.migrations: - original = self.format_migration(migration[0]) - new = self.format_migration(migration[1]) - - if line.rstrip() != original or lines[index + 1] == new: - continue - - lines.insert(stripped_lines.index(original) + 1, new) - - with open(f"{DIR}/core/bot.py", "w") as write_file: - write_file.writelines(lines) - - try: - await bot.load_extension(f"{DIR}.core.dexscript") - except commands.ExtensionAlreadyLoaded: - await bot.reload_extension(f"{DIR}.core.dexscript") - - if self.updating: - request = get(f"{link}/version.txt", {"ref": config.github[1]}) - - new_version = ( - b64decode(request.json()["content"]).decode("UTF-8").rstrip() - ) - - self.embed.description = ( - f"DexScript has been updated to v{new_version}.\n" - f"Use `{settings.prefix}about` to view details about DexScript." - ) - else: - self.embed.description = ( - "DexScript has been installed to your bot\n" - f"Use `{settings.prefix}about` to view details about DexScript." - ) - - self.embed.set_footer( - text=f"DexScript took {round((time() - self.managed_time) * 1000)}ms " - f"to {self.keywords[2].lower()}" - ) - - await self.message.edit(embed=self.embed) - -installer = Installer() - -try: - await installer.run(ctx) -except Exception: - installer.embed.set_footer( - text=f"Error occurred {round((time() - installer.managed_time) * 1000)}ms " - f"into {installer.keywords[1].lower()}" - ) - - await installer.error(format_exc(), True) +import re +import requests +from base64 import b64decode +from os import path +from dataclasses import dataclass +from datetime import datetime + +import discord + +DIR = "ballsdex" if path.isdir("ballsdex") else "carfigures" +UPDATING = path.isdir(f"{DIR}/packages/dexscript") + +@dataclass +class InstallerConfig: + """ + Configuration class for the installer. + """ + + github = ["Dotsian/DexScript", "dev"] + files = ["__init__.py", "cog.py", "commands.py", "parser.py", "utils.py"] + migrations = [ + ( + "||await self.add_cog(Core(self))", + '||await self.load_extension("$DIR.packages.dexscript")\n' + ) + ] + path = f"{DIR}/packages/dexscript" + + +config = InstallerConfig() + + +class InstallerEmbed(discord.Embed): + def __init__(self, installer): + super().__init__() + + self.installer = installer + + self.title = "DexScript Installation" + self.description = "Welcome to the DexScript installer!" + self.color = discord.Color.from_str("#FFF" if DIR == "carfigures" else "#03BAFC") + self.timestamp = datetime.now() + + latest_version = self.installer.latest_version + current_version = self.installer.current_version + + if UPDATING and latest_version is not None and current_version is not None: + self.description += ( + "\n**Your current DexScript package version is outdated.**\n" + f"The latest version of DexScript is version {latest_version}, " + f"while this DexScript instance is on version {current_version}." + ) + + self.set_image(url="https://raw.githubusercontent.com/Dotsian/DexScript/refs/heads/dev/assets/DexScriptPromo.png") + + +class InstallerView(discord.ui.View): + def __init__(self, installer): + super().__init__() + self.installer = installer + + @discord.ui.button(style=discord.ButtonStyle.primary, label="Update" if UPDATING else "Install") + async def install_button(self, interaction: discord.Interaction): + self.install_button.disabled = True + self.quit_button.disabled = True + + await self.installer.install() + + await interaction.message.edit(**self.installer.interface.fields) + await interaction.response.defer() + + @discord.ui.button(style=discord.ButtonStyle.red, label="Exit") + async def quit_button(self, interaction: discord.Interaction): + self.install_button.disabled = True + self.quit_button.disabled = True + + await interaction.message.edit(**self.installer.interface.fields) + await interaction.response.defer() + + +class InstallerGUI: + def __init__(self, installer): + self.loaded = False + + self.installer = installer + + self.embed = InstallerEmbed(installer) + self.view = InstallerView(installer) + + @property + def fields(self): + return {"embed": self.embed, "view": self.view} + + async def reload(self): + if not self.loaded: + self.loaded = True + + await ctx.send(**main_gui.fields) # type: ignore + return + + await ctx.message.edit(**main_gui.fields) # type: ignore + + +class Installer: + def __init__(self): + self.interface = InstallerGUI(self) + + async def install(self): + link = f"https://api.github.com/repos/{config.github[0]}/contents/" + + for file in config.files: + request = requests.get(f"{link}/DexScript/package/{file}", {"ref": config.github[1]}) + + if request.status_code != requests.codes.ok: + pass # TODO: Add error handling + + request = request.json() + content = b64decode(request["content"]) + + with open(f"{config.path}/{file}", "w") as opened_file: + opened_file.write(content.decode("UTF-8")) + + with open(f"{DIR}/core/bot.py", "r") as read_file: + lines = read_file.readlines() + + stripped_lines = [x.rstrip() for x in lines] + + for index, line in enumerate(lines): + for migration in config.migrations: + original = self.format_migration(migration[0]) + new = self.format_migration(migration[1]) + + if line.rstrip() != original or lines[index + 1] == new: + continue + + lines.insert(stripped_lines.index(original) + 1, new) + + with open(f"{DIR}/core/bot.py", "w") as write_file: + write_file.writelines(lines) + + # try: + # await bot.load_extension(config.path) + # except commands.ExtensionAlreadyLoaded: + # await bot.reload_extension(config.path) + + @staticmethod + def format_migration(line): + return ( + line.replace(" ", "") + .replace("|", " ") + .replace("/n", "\n") + .replace("$DIR", DIR) + ) + + @property + def latest_version(self): + pyproject_request = requests.get( + "https://api.github.com/repos/Dotsian/DexScript/contents/pyproject.toml", + {"ref": config.github[1]}, + ) + + if pyproject_request.status_code != requests.codes.ok: + return + + toml_content = b64decode(pyproject_request.json()["content"]).decode("UTF-8") + new_version = re.search(r'version\s*=\s*"(.*?)"', toml_content) + + if not new_version: + return + + return new_version.group(1) + + @property + def current_version(self): + if not path.isfile(f"{config.path}/cog.py"): + return + + with open(f"{config.path}/cog.py", "r") as file: + old_version = re.search(r'__version__\s*=\s*["\']([^"\']+)["\']', file.read()) + + if not old_version: + return + + return old_version.group(1) + + +installer = Installer() +await installer.interface.reload() # type: ignore diff --git a/DexScript/package/__init__.py b/DexScript/package/__init__.py new file mode 100644 index 0000000..6b27689 --- /dev/null +++ b/DexScript/package/__init__.py @@ -0,0 +1,5 @@ +from .cog import DexScript + + +async def setup(bot): + await bot.add_cog(DexScript(bot)) diff --git a/DexScript/package/cog.py b/DexScript/package/cog.py new file mode 100644 index 0000000..6fb9403 --- /dev/null +++ b/DexScript/package/cog.py @@ -0,0 +1,159 @@ +import base64 +import re +import traceback + +import discord +import requests +from discord.ext import commands + +from .utils import config, Utils, DIR +from .parser import DexScriptParser + +if DIR == "ballsdex": + from ballsdex.settings import settings +else: + from carfigures.settings import settings + + +__version__ = "0.5" + + +class DexScript(commands.Cog): + """ + DexScript commands + """ + + def __init__(self, bot): + self.bot = bot + + @staticmethod + def check_version(): + if not config.versioncheck: + return None + + r = requests.get( + "https://api.github.com/repos/Dotsian/DexScript/contents/pyproject.toml", + {"ref": config.branch}, + ) + + if r.status_code != requests.codes.ok: + return + + toml_content = base64.b64decode(r.json()["content"]).decode("UTF-8") + new_version = re.search(r'version\s*=\s*"(.*?)"', toml_content).group(1) + + if new_version != __version__: + return ( + f"Your DexScript version ({__version__}) is outdated. " + f"Please update to version ({new_version}) " + f"by running the command in `{settings.prefix}.about`." + ) + + return None + + @commands.command() + @commands.is_owner() + async def run(self, ctx: commands.Context, *, code: str): + """ + Executes DexScript code. + + Parameters + ---------- + code: str + The code you want to execute. + """ + body = Utils.remove_code_markdown(code) + + version_check = self.check_version() + + if version_check: + await ctx.send(f"-# {version_check}") + + dexscript_instance = DexScriptParser(ctx, self.bot) + + try: + result = await dexscript_instance.execute(body) + except Exception as error: + full_error = traceback.format_exc() if config.debug else error + + await ctx.send(f"```ERROR: {full_error}```") + return + else: + if result is not None: + await ctx.send(f"```ERROR: {result}```") + return + + await ctx.message.add_reaction("✅") + + @commands.command() + @commands.is_owner() + async def about(self, ctx: commands.Context): + """ + Displays information about DexScript. + """ + guide_link = "https://github.com/Dotsian/DexScript/wiki/Commands" + discord_link = "https://discord.gg/EhCxuNQfzt" + + description = ( + "DexScript is a set of commands for Ballsdex and CarFigures created by DotZZ " + "that expands on the standalone admin commands and substitutes for the admin panel. " + "It simplifies editing, adding, deleting, and displaying data for models such as " + "balls, regimes, specials, etc.\n\n" + f"Refer to the official [DexScript guide](<{guide_link}>) for information " + f"about DexScript's functionality or use the `{settings.prefix}.run HELP` to display " + "a list of all commands and what they do.\n\n" + "If you want to follow DexScript or require assistance, join the official " + f"[DexScript Discord server](<{discord_link}>)." + ) + + embed = discord.Embed( + title="DexScript - BETA", + description=description, + color=discord.Color.from_str("#03BAFC"), + ) + + embed.add_field( + name="Updating DexScript", + value=( + "To update DexScript, run " + f"`{settings.prefix}.run EVAL > EXEC_GIT > Dotsian/DexScript/installer.py`" + ) + ) + + version_check = "OUTDATED" if self.check_version() is not None else "LATEST" + + embed.set_thumbnail(url="https://i.imgur.com/uKfx0qO.png") + embed.set_footer(text=f"DexScript {__version__} ({version_check})") + + await ctx.send(embed=embed) + + @commands.command() + @commands.is_owner() + async def setting( + self, ctx: commands.Context, setting: str, value: str | None = None + ): + """ + Changes a setting based on the value provided. + + Parameters + ---------- + setting: str + The setting you want to toggle. + value: str | None + The value you want to set the setting to. + """ + setting = setting.lower() + + if setting not in vars(config): + await ctx.send(f"`{setting}` is not a valid setting.") + return + + setting_value = vars(config)[setting] + new_value = value + + if isinstance(setting_value, bool): + new_value = bool(value) if value else not setting_value + + setattr(config, setting, new_value) + + await ctx.send(f"`{setting}` has been set to `{new_value}`") diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py new file mode 100644 index 0000000..648aa5c --- /dev/null +++ b/DexScript/package/commands.py @@ -0,0 +1,376 @@ +import asyncio +import os +import requests +import shutil +from base64 import b64decode + +import discord + +from .utils import Utils, MEDIA_PATH + + +class DexCommand: + def __init__(self, parser, bot): + self.parser = parser + self.bot = bot + + def __loaded__(self): + pass + + async def create_model(self, model, identifier): + fields = {} + + special_list = {k: Utils.port(v) for k, v in { + "Identifiers": ["country", "catch_names", "name"], + "Ignore": ["id", "short_name"] + }.items()} + + for field, field_type in model._meta.fields_map.items(): + if vars(model()).get(field) is not None or field in special_list["Ignore"]: + continue + + if field in special_list["Identifiers"]: + fields[field] = identifier + continue + + match field_type: + case "ForeignKeyFieldInstance": + pass + # instance = await Models.fetch_model(field).first() + + # if instance is None: + # raise Exception(f"Could not find default {field}") + + # fields[field] = instance.pk + + case "BigIntField": + fields[field] = 100 ** 8 + + case "BackwardFKRelation" | "JSONField": + continue + + case _: + fields[field] = 1 + + await model.create(**fields) + + async def get_model(self, model, identifier): + attribute = self.extract_str_attr(model.name) + + correction_list = await model.name.all().values_list(attribute, flat=True) + translated_identifier = Utils.port(model.extra_data[0].lower()) + + try: + returned_model = await model.name.filter( + **{ + translated_identifier: Utils.autocorrect(identifier, correction_list) + } + ) + except AttributeError: + raise Exception(f"'{model}' is not a valid model.") + + return returned_model[0] + + +class Global(DexCommand): + """ + Main methods for DexScript. + """ + + async def help(self, ctx): + """ + Lists all DexScript commands and provides documentation for them. + + Documentation + ------------- + HELP + """ + # getattr(Methods, command).__doc__.replace("\n", "").split("Documentation-------------") + pass + + async def create(self, ctx, model, identifier): + """ + Creates a model instance. + + Documentation + ------------- + CREATE > MODEL > IDENTIFIER + """ + await self.parser.create_model(model.name, identifier) + + await ctx.send(f"Created `{identifier}`") + + + async def delete(self, ctx, model, identifier): + """ + Deletes a model instance. + + Documentation + ------------- + DELETE > MODEL > IDENTIFIER + """ + await self.parser.get_model(model, identifier.name).delete() + + await ctx.send(f"Deleted `{identifier}`") + + + async def update(self, ctx, model, identifier, attribute, value=None): + """ + Updates a model instance's attribute. If value is None, it will check + for any attachments. + + Documentation + ------------- + UPDATE > MODEL > IDENTIFIER > ATTRIBUTE > VALUE(?) + """ + new_attribute = value.name if value is not None else None + + if value is None and self.parser.attachments != []: + image_path = await Utils.save_file(self.parser.attachments[0]) + new_attribute = f"{MEDIA_PATH}/{image_path}" + + self.parser.attachments.pop(0) + + await self.parser.get_model(model, identifier.name).update( + **{attribute.name.lower(): new_attribute} + ) + + await ctx.send(f"Updated `{identifier}'s` {attribute} to `{new_attribute}`") + + + async def view(self, ctx, model, identifier, attribute=None): + """ + Displays an attribute of a model instance. If `ATTRIBUTE` is left blank, + it will display every attribute of that model instance. + + Documentation + ------------- + VIEW > MODEL > IDENTIFIER > ATTRIBUTE(?) + """ + returned_model = await self.parser.get_model(model, identifier.name) + + if attribute is None: + fields = {"content": "```"} + + for key, value in vars(returned_model).items(): + if key.startswith("_"): + continue + + fields["content"] += f"{key}: {value}\n" + + if isinstance(value, str) and Utils.is_image(value): + if fields.get("files") is None: + fields["files"] = [] + + fields["files"].append(discord.File(value[1:])) + + fields["content"] += "```" + + await ctx.send(**fields) + return + + new_attribute = getattr(returned_model, attribute.name.lower()) + + if isinstance(new_attribute, str) and os.path.isfile(new_attribute[1:]): + await ctx.send(f"```{new_attribute}```", file=discord.File(new_attribute[1:])) + return + + await ctx.send(f"```{new_attribute}```") + + + async def attributes(self, ctx, model): + """ + Lists all changeable attributes of a model. + + Documentation + ------------- + ATTRIBUTES > MODEL + """ + model_name = ( + model.name if isinstance(model.name, str) else model.name.__name__ + ) + + parameters = f"{model_name.upper()} ATTRIBUTES:\n\n" + + for field in vars(model.name()): # type: ignore + if field[:1] == "_": + continue + + parameters += f"- {field.replace(' ', '_').upper()}\n" + + await ctx.send(f"```{parameters}```") + + +class Filter(DexCommand): + """ + Filter commands used for mass updating, deleting, and viewing models. + """ + + # TODO: Add attachment support. + async def update( + self, ctx, model, attribute, old_value, new_value, tortoise_operator=None + ): + """ + Updates all instances of a model to the specified value where the specified attribute + meets the condition defined by the optional `TORTOISE_OPERATOR` argument + (e.g., greater than, equal to, etc.). + + Documentation + ------------- + FILTER > UPDATE > MODEL > ATTRIBUTE > OLD_VALUE > NEW_VALUE > TORTOISE_OPERATOR(?) + """ + lower_name = attribute.name.lower() + + if tortoise_operator is not None: + lower_name += f"__{tortoise_operator.name.lower()}" + + await model.name.filter(**{lower_name: old_value.name}).update( + **{lower_name: new_value.name} + ) + + await ctx.send( + f"Updated all `{model}` instances from a " + f"`{attribute}` value of `{old_value}` to `{new_value}`" + ) + + + async def delete( + self, ctx, model, attribute, value, tortoise_operator=None + ): + """ + Deletes all instances of a model where the specified attribute meets the condition + defined by the optional `TORTOISE_OPERATOR` argument + (e.g., greater than, equal to, etc.). + + Documentation + ------------- + FILTER > DELETE > MODEL > ATTRIBUTE > VALUE > TORTOISE_OPERATOR(?) + """ + lower_name = attribute.name.lower() + + if tortoise_operator is not None: + lower_name += f"__{tortoise_operator.name.lower()}" + + await model.name.filter(**{lower_name: value.name}).delete() + + await ctx.send( + f"Deleted all `{model}` instances with a " + f"`{attribute}` value of `{value}`" + ) + + +class Eval(DexCommand): + """ + Developer commands for executing evals. + """ + + def __loaded__(self): + os.makedirs("eval_presets", exist_ok=True) + + + async def exec_git(self, ctx, link): + link = link.split("/") + api = f"https://api.github.com/repos/{link[0]}/{link[1]}/contents/{link[2]}" + + request = requests.get(api) + + if request.status_code != requests.codes.ok: + raise Exception(f"Request Error Code {request.status_code}") + + content = b64decode(request.json()["content"]) + + try: + await ctx.invoke(self.bot.get_command("eval"), body=content.decode("UTF-8")) + except Exception as error: + raise Exception(error) + else: + await ctx.message.add_reaction("✅") + + + async def save(self, ctx, name): + if len(name) > 25: + raise Exception(f"`{name}` is above the 25 character limit.") + + if os.path.isfile(f"eval_presets/{name}.py"): + raise Exception(f"`{name}` aleady exists.") + + await ctx.send("Please paste the eval command below...") + + try: + message = await self.bot.wait_for( + "message", timeout=15, check=lambda m: m.author == ctx.message.author + ) + except asyncio.TimeoutError: + await ctx.send("Preset saving has timed out.") + return + with open(f"eval_presets/{name}.py", "w") as file: + file.write(Utils.remove_code_markdown(message.content)) + + await ctx.send(f"`{name}` eval preset has been saved!") + + + async def remove(self, ctx, name): + if not os.path.isfile(f"eval_presets/{name}.py"): + raise Exception(f"`{name}` does not exists.") + + os.remove(f"eval_presets/{name}.py") + + await ctx.send(f"Removed `{name}` preset.") + + + async def run(self, ctx, name): # TODO: Allow args to be passed through `run`. + if not os.path.isfile(f"eval_presets/{name}.py"): + raise Exception(f"`{name}` does not exists.") + + with open(f"eval_presets/{name}.py", "r") as file: + try: + await ctx.invoke(self.bot.get_command("eval"), body=file.read()) + except Exception as error: + raise Exception(error) + else: + await ctx.message.add_reaction("✅") + + +class File(DexCommand): + """ + Developer commands for managing and modifying the bot's internal filesystem. + """ + + async def read(self, ctx, file_path): + await ctx.send(file=discord.File(file_path.name)) + + + async def write(self, ctx, file_path): + new_file = ctx.message.attachments[0] + + with open(file_path.name, "w") as opened_file: + contents = await new_file.read() + opened_file.write(contents.decode("utf-8")) + + await ctx.send(f"Wrote to `{file_path}`") + + + async def clear(self, ctx, file_path): + with open(file_path.name, "w") as _: + pass + + await ctx.send(f"Cleared `{file_path}`") + + + async def listdir(self, ctx, file_path=None): + path = file_path.name if file_path is not None else None + + await ctx.send(f"```{'\n'.join(os.listdir(path))}```") + + + async def delete(self, ctx, file_path): + is_dir = os.path.isdir(file_path.name) + + file_type = "directory" if is_dir else "file" + + if is_dir: + shutil.rmtree(file_path.name) + else: + os.remove(file_path.name) + + await ctx.send(f"Deleted `{file_path}` {file_type}") diff --git a/DexScript/package/parser.py b/DexScript/package/parser.py new file mode 100644 index 0000000..a265cd4 --- /dev/null +++ b/DexScript/package/parser.py @@ -0,0 +1,170 @@ +import inspect +import re +import traceback +from dataclasses import dataclass, field as datafield +from enum import Enum +from typing import Any + +from dateutil.parser import parse as parse_date + +from . import commands +from .utils import config, Utils, DIR + +if DIR == "ballsdex": + from ballsdex.core.models import Ball, Economy, Regime, Special # noqa: F401, I001 +else: + from carfigures.core.models import Car, CarType, Country, Event, FontsPack # noqa: F401, I001 + + +class Types(Enum): + DEFAULT = 0 + METHOD = 1 + CLASS = 2 + BOOLEAN = 3 + MODEL = 4 + DATETIME = 5 + + +@dataclass +class Value: + name: Any + type: Types = Types.DEFAULT + extra_data: list = datafield(default_factory=list) + + def __str__(self): + return str(self.name) + + +@dataclass +class Models: + """ + Model functions. + """ + + @staticmethod + def all(names=False, key=None): + model_list = { + "ballsdex": [ + "Ball", + "Regime", + "Economy", + "Special" + ], + "carfigures": [ + "Car", + "CarType", + "Country", + "Event", + "FontsPack" + ] + } + + return_list = model_list[DIR] + + if not names: + return_list = [globals().get(x) for x in return_list if globals().get(x) is not None] + + if key is not None: + return_list = [key(x) for x in return_list] + + return return_list + + +class DexScriptParser: + """ + This class is used to parse DexScript into Python code. + """ + + def __init__(self, ctx, bot): + self.ctx = ctx + self.bot = bot + self.attachments = ctx.message.attachments + + self.command_classes = inspect.getmembers( + commands, lambda o: ( + inspect.isclass(o) and issubclass(o, commands.DexCommand) + and not issubclass(o, commands.Global) and o.__name__ != "DexCommand" + ) + ) + + self.global_methods = [x for x in dir(commands.Global) if not x.startswith("__")] + + def create_value(self, line): + value = Value(line) + lower = line.lower() + + type_dict = { + Types.METHOD: lower in self.global_methods, + Types.CLASS: lower in [x[0].lower() for x in self.command_classes], + Types.MODEL: lower in Models.all(True, key=str.lower), + Types.DATETIME: Utils.is_date(lower) and lower.count("-") >= 2, + Types.BOOLEAN: lower in ["true", "false"] + } + + for key, operation in type_dict.items(): + if operation is False: + continue + + value.type = key + break + + match value.type: + case Types.MODEL: + model = globals().get(line) + + string_key = self.extract_str_attr(model) + + value.name = model + value.extra_data.append(string_key) + + case Types.BOOLEAN: + value.name = lower == "true" + + case Types.DATETIME: + value.name = parse_date(value.name) + + return value + + def error(self, message, log): + return (message, log)[config.debug] + + async def execute(self, code: str): + split_code = [x for x in code.split("\n") if x.strip() != ""] + + parsed_code = [ + [self.create_value(s.strip()) for s in re.findall(r"[^>]+", line)] + for line in split_code if not line.strip().startswith("--") + ] + + for line2 in parsed_code: + if line2 == []: + continue + + method = line2[0] + + if method.type not in (Types.METHOD, Types.CLASS): + return self.error( + f"'{method.name}' is not a valid command.", + traceback.format_exc() + ) + + if method.type == Types.CLASS: + line2.pop(0) + method = (getattr(commands, method.name.title()), line2[0]) + else: + method = (commands.Global, line2[0]) + + line2.pop(0) + + class_loaded = commands.Global() if method[0] == commands.Global else method[0]() + class_loaded.__loaded__() + + method_call = getattr(class_loaded, method[1].name.lower()) + + try: + await method_call(self.ctx, *line2) + except TypeError: + return self.error( + f"Argument missing when calling '{method[1].name}'.", + traceback.format_exc() + ) diff --git a/DexScript/package/utils.py b/DexScript/package/utils.py new file mode 100644 index 0000000..3aa565c --- /dev/null +++ b/DexScript/package/utils.py @@ -0,0 +1,125 @@ +import inspect +import os +import re +from dataclasses import dataclass +from difflib import get_close_matches +from pathlib import Path + +import discord +from dateutil.parser import parse as parse_date + +DIR = "ballsdex" if os.path.isdir("ballsdex") else "carfigures" + +START_CODE_BLOCK_RE = re.compile(r"^((```sql?)(?=\s)|(```))") +FILENAME_RE = re.compile(r"^(.+)(\.\S+)$") + +MEDIA_PATH = "./admin_panel/media" if os.path.isdir("./admin_panel/media") else "/static/uploads" + + +@dataclass +class Settings: + """ + Settings class for DexScript. + """ + + debug: bool = False + versioncheck: bool = False + reference: str = "main" + + +config = Settings() + + +@dataclass +class Utils: + """ + Utility functions for DexScript. + """ + + @staticmethod + def autocorrect(string, correction_list, error="does not exist."): + autocorrection = get_close_matches(string, correction_list) + + if not autocorrection or autocorrection[0] != string: + suggestion = f"\nDid you mean '{autocorrection[0]}'?" if autocorrection else "" + + raise Exception(f"'{string}' {error}{suggestion}") + + return autocorrection[0] + + @staticmethod + def port(original: str | list[str]): + """ + Translates model and field names into a format for both Ballsdex and CarFigures. + + Parameters + ---------- + original: str | list[str] + The original string or list of strings you want to translate. + """ + if DIR == "ballsdex": + return original + + translation = { + "BALL": "ENTITY", + "COUNTRY": "fullName", + "SHORT_NAME": "shortName", + "CATCH_NAMES": "catchNames", + "ICON": "image", + } + + if isinstance(original, list): + translated_copy = [translation.get(x.upper(), x) for x in original] + else: + translated_copy = translation.get(original.upper(), original) + + return translated_copy + + @staticmethod + async def save_file(attachment: discord.Attachment) -> Path: + path = Path(f"{MEDIA_PATH}/{attachment.filename}") + match = FILENAME_RE.match(attachment.filename) + + if not match: + raise TypeError("The file you uploaded lacks an extension.") + + i = 1 + + while path.exists(): + path = Path(f"{MEDIA_PATH}/{match.group(1)}-{i}{match.group(2)}") + i = i + 1 + + await attachment.save(path) + + if MEDIA_PATH == "./admin_panel/media": + return path.relative_to("./admin_panel/media/") + + return path.relative_to("/static/uploads") + + @staticmethod + def extract_str_attr(object): + expression = r"return\s+self\.(\w+)" + + return re.search(expression, inspect.getsource(object.__str__)).group(1) + + @staticmethod + def remove_code_markdown(content) -> str: + if content.startswith("```") and content.endswith("```"): + return START_CODE_BLOCK_RE.sub("", content)[:-3] + + return content.strip("` \n") + + @staticmethod + def is_image(path) -> bool: + if path.startswith("/static/uploads/"): + path.replace("/static/uploads/", "") + + return os.path.isfile(f"{MEDIA_PATH}/{path}") + + @staticmethod + def is_date(string) -> bool: + try: + parse_date(string) + return True + except Exception: + return False diff --git a/README.md b/README.md index 59beb48..de48b04 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,6 @@ To install DexScript, run the following eval command: ```py import base64, requests - await ctx.invoke(bot.get_command("eval"), body=base64.b64decode(requests.get("https://api.github.com/repos/Dotsian/DexScript/contents/installer.py").json()["content"]).decode()) ``` @@ -45,7 +44,6 @@ await ctx.invoke(bot.get_command("eval"), body=base64.b64decode(requests.get("ht ```py import base64, requests - await ctx.invoke(bot.get_command("eval"), body=base64.b64decode(requests.get("https://api.github.com/repos/Dotsian/DexScript/contents/DexScript/github/installer.py", {"ref": "dev"}).json()["content"]).decode()) ``` diff --git a/pyproject.toml b/pyproject.toml index 54c4171..bd11308 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ authors = ["dotzz "] license = "MIT" [tool.poetry.dependencies] -python = "^3.12" +python = ">=3.12" "discord.py" = "^2.4.0" ruff = "^0.9.3" From f64e93a2727ccae9f36dd0007a56d7917a55f7d7 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Sat, 1 Feb 2025 03:44:01 -0500 Subject: [PATCH 037/133] Remove old dexscript file and add installer notice --- DexScript/github/installer.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/DexScript/github/installer.py b/DexScript/github/installer.py index 1da5ac3..4adf53e 100644 --- a/DexScript/github/installer.py +++ b/DexScript/github/installer.py @@ -1,14 +1,24 @@ +# # # # # # # # # # # # # # # # # # # # # # # # # # # # +# OFFICIAL DEXSCRIPT INSTALLER # +# # +# This will install DexScript onto your bot. # +# For additional information, read the wiki guide. # +# An explanation of the code will be provided below. # +# # +# THIS CODE IS RAN VIA THE `EVAL` COMMAND. # + + +import os import re import requests from base64 import b64decode -from os import path from dataclasses import dataclass from datetime import datetime import discord -DIR = "ballsdex" if path.isdir("ballsdex") else "carfigures" -UPDATING = path.isdir(f"{DIR}/packages/dexscript") +DIR = "ballsdex" if os.path.isdir("ballsdex") else "carfigures" +UPDATING = os.path.isdir(f"{DIR}/packages/dexscript") @dataclass class InstallerConfig: @@ -106,6 +116,9 @@ def __init__(self): self.interface = InstallerGUI(self) async def install(self): + if os.path.isfile(f"{DIR}/core/dexscript.py"): + os.remove(f"{DIR}/core/dexscript.py") + link = f"https://api.github.com/repos/{config.github[0]}/contents/" for file in config.files: @@ -172,7 +185,7 @@ def latest_version(self): @property def current_version(self): - if not path.isfile(f"{config.path}/cog.py"): + if not os.path.isfile(f"{config.path}/cog.py"): return with open(f"{config.path}/cog.py", "r") as file: From 7c160d42c75995aea95d1edb9ae706b81d9588dc Mon Sep 17 00:00:00 2001 From: Dotsian Date: Sat, 1 Feb 2025 04:07:10 -0500 Subject: [PATCH 038/133] Remove `main_gui` --- DexScript/github/installer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DexScript/github/installer.py b/DexScript/github/installer.py index 4adf53e..ad97697 100644 --- a/DexScript/github/installer.py +++ b/DexScript/github/installer.py @@ -105,10 +105,10 @@ async def reload(self): if not self.loaded: self.loaded = True - await ctx.send(**main_gui.fields) # type: ignore + await ctx.send(**self.fields) # type: ignore return - await ctx.message.edit(**main_gui.fields) # type: ignore + await ctx.message.edit(**self.fields) # type: ignore class Installer: From b8c2e000e53eba6782d39eb661508c38e3f0842f Mon Sep 17 00:00:00 2001 From: Dotsian Date: Sat, 1 Feb 2025 04:09:01 -0500 Subject: [PATCH 039/133] Add button --- DexScript/github/installer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DexScript/github/installer.py b/DexScript/github/installer.py index ad97697..d83046c 100644 --- a/DexScript/github/installer.py +++ b/DexScript/github/installer.py @@ -70,7 +70,7 @@ def __init__(self, installer): self.installer = installer @discord.ui.button(style=discord.ButtonStyle.primary, label="Update" if UPDATING else "Install") - async def install_button(self, interaction: discord.Interaction): + async def install_button(self, interaction: discord.Interaction, _: discord.ui.Button): self.install_button.disabled = True self.quit_button.disabled = True @@ -80,7 +80,7 @@ async def install_button(self, interaction: discord.Interaction): await interaction.response.defer() @discord.ui.button(style=discord.ButtonStyle.red, label="Exit") - async def quit_button(self, interaction: discord.Interaction): + async def quit_button(self, interaction: discord.Interaction, _: discord.ui.Button): self.install_button.disabled = True self.quit_button.disabled = True From 05aa8c37ce9b0f49bcc7f5867ec8e46ead081032 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Sat, 1 Feb 2025 04:11:01 -0500 Subject: [PATCH 040/133] Create path directory --- DexScript/github/installer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DexScript/github/installer.py b/DexScript/github/installer.py index d83046c..950da9d 100644 --- a/DexScript/github/installer.py +++ b/DexScript/github/installer.py @@ -121,6 +121,8 @@ async def install(self): link = f"https://api.github.com/repos/{config.github[0]}/contents/" + os.makedirs(config.path, exist_ok=True) + for file in config.files: request = requests.get(f"{link}/DexScript/package/{file}", {"ref": config.github[1]}) From cfbe9437fa9417f81d56ff3f288ff85b3c742768 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Sat, 1 Feb 2025 04:23:33 -0500 Subject: [PATCH 041/133] Add args --- DexScript/package/parser.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/DexScript/package/parser.py b/DexScript/package/parser.py index a265cd4..b4c2193 100644 --- a/DexScript/package/parser.py +++ b/DexScript/package/parser.py @@ -156,7 +156,8 @@ async def execute(self, code: str): line2.pop(0) - class_loaded = commands.Global() if method[0] == commands.Global else method[0]() + class_loaded = commands.Global if method[0] == commands.Global else method[0] + class_loaded = class_loaded(self, self.bot) class_loaded.__loaded__() method_call = getattr(class_loaded, method[1].name.lower()) From 9e66a6f9c0b596152e599b0bb194cbdde6d98209 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Sat, 1 Feb 2025 04:27:40 -0500 Subject: [PATCH 042/133] Fix attribute error and fix outdated notice on installer --- DexScript/github/installer.py | 6 +++--- DexScript/package/commands.py | 2 +- DexScript/package/parser.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/DexScript/github/installer.py b/DexScript/github/installer.py index 950da9d..71c5931 100644 --- a/DexScript/github/installer.py +++ b/DexScript/github/installer.py @@ -54,9 +54,9 @@ def __init__(self, installer): latest_version = self.installer.latest_version current_version = self.installer.current_version - if UPDATING and latest_version is not None and current_version is not None: + if UPDATING and latest_version and current_version and latest_version != current_version: self.description += ( - "\n**Your current DexScript package version is outdated.**\n" + "\n\n**Your current DexScript package version is outdated.**\n" f"The latest version of DexScript is version {latest_version}, " f"while this DexScript instance is on version {current_version}." ) @@ -122,7 +122,7 @@ async def install(self): link = f"https://api.github.com/repos/{config.github[0]}/contents/" os.makedirs(config.path, exist_ok=True) - + for file in config.files: request = requests.get(f"{link}/DexScript/package/{file}", {"ref": config.github[1]}) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 648aa5c..92ed321 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -55,7 +55,7 @@ async def create_model(self, model, identifier): await model.create(**fields) async def get_model(self, model, identifier): - attribute = self.extract_str_attr(model.name) + attribute = Utils.extract_str_attr(model.name) correction_list = await model.name.all().values_list(attribute, flat=True) translated_identifier = Utils.port(model.extra_data[0].lower()) diff --git a/DexScript/package/parser.py b/DexScript/package/parser.py index b4c2193..d69f234 100644 --- a/DexScript/package/parser.py +++ b/DexScript/package/parser.py @@ -112,7 +112,7 @@ def create_value(self, line): case Types.MODEL: model = globals().get(line) - string_key = self.extract_str_attr(model) + string_key = Utils.extract_str_attr(model) value.name = model value.extra_data.append(string_key) From 641f8df8fd4d6bd4b5e47dfed221ee414a293aae Mon Sep 17 00:00:00 2001 From: Dotsian Date: Sat, 1 Feb 2025 04:39:44 -0500 Subject: [PATCH 043/133] Remove parser argument and fix case sensitivity --- DexScript/package/commands.py | 17 +++++++---------- DexScript/package/parser.py | 13 ++++++++++++- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 92ed321..dfa0a09 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -10,8 +10,7 @@ class DexCommand: - def __init__(self, parser, bot): - self.parser = parser + def __init__(self, bot): self.bot = bot def __loaded__(self): @@ -96,7 +95,7 @@ async def create(self, ctx, model, identifier): ------------- CREATE > MODEL > IDENTIFIER """ - await self.parser.create_model(model.name, identifier) + await self.create_model(model.name, identifier) await ctx.send(f"Created `{identifier}`") @@ -109,7 +108,7 @@ async def delete(self, ctx, model, identifier): ------------- DELETE > MODEL > IDENTIFIER """ - await self.parser.get_model(model, identifier.name).delete() + await self.get_model(model, identifier.name).delete() await ctx.send(f"Deleted `{identifier}`") @@ -125,13 +124,11 @@ async def update(self, ctx, model, identifier, attribute, value=None): """ new_attribute = value.name if value is not None else None - if value is None and self.parser.attachments != []: - image_path = await Utils.save_file(self.parser.attachments[0]) + if value is None: + image_path = await Utils.save_file(ctx.message.attachments[0]) new_attribute = f"{MEDIA_PATH}/{image_path}" - self.parser.attachments.pop(0) - - await self.parser.get_model(model, identifier.name).update( + await self.get_model(model, identifier.name).update( **{attribute.name.lower(): new_attribute} ) @@ -147,7 +144,7 @@ async def view(self, ctx, model, identifier, attribute=None): ------------- VIEW > MODEL > IDENTIFIER > ATTRIBUTE(?) """ - returned_model = await self.parser.get_model(model, identifier.name) + returned_model = await self.get_model(model, identifier.name) if attribute is None: fields = {"content": "```"} diff --git a/DexScript/package/parser.py b/DexScript/package/parser.py index d69f234..deecdc5 100644 --- a/DexScript/package/parser.py +++ b/DexScript/package/parser.py @@ -112,6 +112,17 @@ def create_value(self, line): case Types.MODEL: model = globals().get(line) + if model is None: + examples = "Ball, Regime, Special" + + if DIR == "carfigures": + examples = "Car, CarType, Event" + + raise Exception( + f"'{line}' is not a valid model\n" + f"Make sure you check your capitalization (e.g. {examples})" + ) + string_key = Utils.extract_str_attr(model) value.name = model @@ -157,7 +168,7 @@ async def execute(self, code: str): line2.pop(0) class_loaded = commands.Global if method[0] == commands.Global else method[0] - class_loaded = class_loaded(self, self.bot) + class_loaded = class_loaded(self.bot) class_loaded.__loaded__() method_call = getattr(class_loaded, method[1].name.lower()) From cd7026ecd109432ec996dd2b31031d2828778bbf Mon Sep 17 00:00:00 2001 From: Dotsian Date: Sat, 1 Feb 2025 04:50:19 -0500 Subject: [PATCH 044/133] Fix description, fix `exec_git` command when handling links, and fix `save` not working --- DexScript/package/cog.py | 4 ++-- DexScript/package/commands.py | 12 ++++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/DexScript/package/cog.py b/DexScript/package/cog.py index 6fb9403..2420d39 100644 --- a/DexScript/package/cog.py +++ b/DexScript/package/cog.py @@ -100,7 +100,7 @@ async def about(self, ctx: commands.Context): "It simplifies editing, adding, deleting, and displaying data for models such as " "balls, regimes, specials, etc.\n\n" f"Refer to the official [DexScript guide](<{guide_link}>) for information " - f"about DexScript's functionality or use the `{settings.prefix}.run HELP` to display " + f"about DexScript's functionality or use `{settings.prefix}run HELP` to display " "a list of all commands and what they do.\n\n" "If you want to follow DexScript or require assistance, join the official " f"[DexScript Discord server](<{discord_link}>)." @@ -116,7 +116,7 @@ async def about(self, ctx: commands.Context): name="Updating DexScript", value=( "To update DexScript, run " - f"`{settings.prefix}.run EVAL > EXEC_GIT > Dotsian/DexScript/installer.py`" + f"`{settings.prefix}run EVAL > EXEC_GIT > Dotsian/DexScript/github/installer.py`" ) ) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index dfa0a09..17baec3 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -267,7 +267,12 @@ def __loaded__(self): async def exec_git(self, ctx, link): link = link.split("/") - api = f"https://api.github.com/repos/{link[0]}/{link[1]}/contents/{link[2]}" + start = f"{link[0]}/{link[1]}" + + link.pop(0) + link.pop(0) + + api = f"https://api.github.com/repos/{start}/contents/{'/'.join(link)}" request = requests.get(api) @@ -285,7 +290,7 @@ async def exec_git(self, ctx, link): async def save(self, ctx, name): - if len(name) > 25: + if len(name.name) > 25: raise Exception(f"`{name}` is above the 25 character limit.") if os.path.isfile(f"eval_presets/{name}.py"): @@ -348,6 +353,9 @@ async def write(self, ctx, file_path): async def clear(self, ctx, file_path): + if not os.path.isfile(file_path): + raise Exception(f"'{file_path}' does not exist") + with open(file_path.name, "w") as _: pass From d60f27b70b8c74960f1cdd41c78d2a4cb5c6c258 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Sat, 1 Feb 2025 04:53:39 -0500 Subject: [PATCH 045/133] Add CF `Exclusive` model --- DexScript/package/parser.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/DexScript/package/parser.py b/DexScript/package/parser.py index deecdc5..b0a087a 100644 --- a/DexScript/package/parser.py +++ b/DexScript/package/parser.py @@ -48,14 +48,15 @@ def all(names=False, key=None): "Ball", "Regime", "Economy", - "Special" + "Special", ], "carfigures": [ "Car", "CarType", "Country", "Event", - "FontsPack" + "FontsPack", + "Exclusive", ] } From c0a9b5e7627ade326ee540e55a5199da6944afda Mon Sep 17 00:00:00 2001 From: Dotsian Date: Sat, 1 Feb 2025 05:06:07 -0500 Subject: [PATCH 046/133] Fix `exec_git` and add command descriptions --- DexScript/package/commands.py | 75 ++++++++++++++++++++++++++++++++--- 1 file changed, 69 insertions(+), 6 deletions(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 17baec3..5f5933e 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -258,7 +258,7 @@ async def delete( class Eval(DexCommand): """ - Developer commands for executing evals. + Commands for executing evals and managing eval presets. """ def __loaded__(self): @@ -266,7 +266,14 @@ def __loaded__(self): async def exec_git(self, ctx, link): - link = link.split("/") + """ + Executes an eval command based on a GitHub file link. + + Documentation + ------------- + EVAL > EXEC_GIT > LINK + """ + link = link.name.split("/") start = f"{link[0]}/{link[1]}" link.pop(0) @@ -290,8 +297,15 @@ async def exec_git(self, ctx, link): async def save(self, ctx, name): + """ + Saves an eval preset. + + Documentation + ------------- + EVAL > SAVE > NAME + """ if len(name.name) > 25: - raise Exception(f"`{name}` is above the 25 character limit.") + raise Exception(f"`{name}` is above the 25 character limit") if os.path.isfile(f"eval_presets/{name}.py"): raise Exception(f"`{name}` aleady exists.") @@ -312,8 +326,15 @@ async def save(self, ctx, name): async def remove(self, ctx, name): + """ + Removes an eval preset. + + Documentation + ------------- + EVAL > REMOVE > NAME + """ if not os.path.isfile(f"eval_presets/{name}.py"): - raise Exception(f"`{name}` does not exists.") + raise Exception(f"`{name}` does not exists") os.remove(f"eval_presets/{name}.py") @@ -321,8 +342,15 @@ async def remove(self, ctx, name): async def run(self, ctx, name): # TODO: Allow args to be passed through `run`. + """ + Runs an eval preset. + + Documentation + ------------- + EVAL > RUN > NAME + """ if not os.path.isfile(f"eval_presets/{name}.py"): - raise Exception(f"`{name}` does not exists.") + raise Exception(f"`{name}` does not exists") with open(f"eval_presets/{name}.py", "r") as file: try: @@ -335,14 +363,28 @@ async def run(self, ctx, name): # TODO: Allow args to be passed through `run`. class File(DexCommand): """ - Developer commands for managing and modifying the bot's internal filesystem. + Commands for managing and modifying the bot's internal filesystem. """ async def read(self, ctx, file_path): + """ + Sends a file based on the specified file path. + + Documentation + ------------- + FILE > READ > FILE_PATH + """ await ctx.send(file=discord.File(file_path.name)) async def write(self, ctx, file_path): + """ + Writes to a file using the attached file's contents. + + Documentation + ------------- + FILE > WRITE > FILE_PATH + """ new_file = ctx.message.attachments[0] with open(file_path.name, "w") as opened_file: @@ -353,6 +395,13 @@ async def write(self, ctx, file_path): async def clear(self, ctx, file_path): + """ + Clears the contents of a file. + + Documentation + ------------- + FILE > CLEAR > FILE_PATH + """ if not os.path.isfile(file_path): raise Exception(f"'{file_path}' does not exist") @@ -363,12 +412,26 @@ async def clear(self, ctx, file_path): async def listdir(self, ctx, file_path=None): + """ + Lists all files inside of a directory. + + Documentation + ------------- + FILE > LISTDIR > FILE_PATH(?) + """ path = file_path.name if file_path is not None else None await ctx.send(f"```{'\n'.join(os.listdir(path))}```") async def delete(self, ctx, file_path): + """ + Deletes a file or directory based on the specified file path. + + Documentation + ------------- + FILE > DELETE > FILE_PATH + """ is_dir = os.path.isdir(file_path.name) file_type = "directory" if is_dir else "file" From b6b8bafaeeb12aea3f600424175cdda65961f632 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Sat, 1 Feb 2025 05:11:51 -0500 Subject: [PATCH 047/133] Format ruff and fix checks --- DexScript/github/__EXCLUDED__/installer.py | 23 ++--- DexScript/github/installer.py | 110 +++++++++++---------- DexScript/github/migration.py | 4 +- DexScript/package/cog.py | 12 +-- DexScript/package/commands.py | 81 ++++++--------- DexScript/package/parser.py | 37 +++---- DexScript/package/utils.py | 8 +- pyproject.toml | 2 +- 8 files changed, 129 insertions(+), 148 deletions(-) diff --git a/DexScript/github/__EXCLUDED__/installer.py b/DexScript/github/__EXCLUDED__/installer.py index e50c12a..3d8a666 100644 --- a/DexScript/github/__EXCLUDED__/installer.py +++ b/DexScript/github/__EXCLUDED__/installer.py @@ -1,4 +1,4 @@ -# # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # OFFICIAL DEXSCRIPT INSTALLER # # # # This will install DexScript onto your bot. # @@ -6,7 +6,7 @@ # An explanation of the code will be provided below. # # # # THIS CODE IS RAN VIA THE `EVAL` COMMAND. # -# # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # from base64 import b64decode @@ -32,15 +32,16 @@ class InstallerConfig: """ Configuration class for the installer. """ - + github = ["Dotsian/DexScript", "dev"] migrations = [ ( "¶¶await self.add_cog(Core(self))", - '¶¶await self.load_extension("$DIR.packages.dexscript")\n' + '¶¶await self.load_extension("$DIR.packages.dexscript")\n', ) ] + config = InstallerConfig() @@ -70,10 +71,7 @@ def __init__(self): @staticmethod def format_migration(line): return ( - line.replace(" ", "") - .replace("¶", " ") - .replace("/n", "\n") - .replace("$DIR", DIR) + line.replace(" ", "").replace("¶", " ").replace("/n", "\n").replace("$DIR", DIR) ) async def error(self, error, exception=False): @@ -103,10 +101,10 @@ async def run(self, ctx): """ Installs or updates the latest DexScript version. - - Fetches the contents of the `dexscript.py` file from the official DexScript repository, + - Fetches the contents of the `dexscript.py` file from the official DexScript repository, and writes that content onto a local `dexscript.py` file. - - Apply migrations from the `config.migrations` list onto the `bot.py` file to allow + - Apply migrations from the `config.migrations` list onto the `bot.py` file to allow DexScript to load on bot startup. - Load or reload the DexScript extension. @@ -157,9 +155,7 @@ async def run(self, ctx): if self.updating: request = get(f"{link}/version.txt", {"ref": config.github[1]}) - new_version = ( - b64decode(request.json()["content"]).decode("UTF-8").rstrip() - ) + new_version = b64decode(request.json()["content"]).decode("UTF-8").rstrip() self.embed.description = ( f"DexScript has been updated to v{new_version}.\n" @@ -178,6 +174,7 @@ async def run(self, ctx): await self.message.edit(embed=self.embed) + installer = Installer() try: diff --git a/DexScript/github/installer.py b/DexScript/github/installer.py index 71c5931..750f7fb 100644 --- a/DexScript/github/installer.py +++ b/DexScript/github/installer.py @@ -1,4 +1,4 @@ -# # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # OFFICIAL DEXSCRIPT INSTALLER # # # # This will install DexScript onto your bot. # @@ -10,28 +10,29 @@ import os import re -import requests from base64 import b64decode from dataclasses import dataclass from datetime import datetime import discord +import requests DIR = "ballsdex" if os.path.isdir("ballsdex") else "carfigures" UPDATING = os.path.isdir(f"{DIR}/packages/dexscript") + @dataclass class InstallerConfig: """ Configuration class for the installer. """ - + github = ["Dotsian/DexScript", "dev"] files = ["__init__.py", "cog.py", "commands.py", "parser.py", "utils.py"] migrations = [ ( "||await self.add_cog(Core(self))", - '||await self.load_extension("$DIR.packages.dexscript")\n' + '||await self.load_extension("$DIR.packages.dexscript")\n', ) ] path = f"{DIR}/packages/dexscript" @@ -42,26 +43,28 @@ class InstallerConfig: class InstallerEmbed(discord.Embed): def __init__(self, installer): - super().__init__() + super().__init__() - self.installer = installer + self.installer = installer - self.title = "DexScript Installation" - self.description = "Welcome to the DexScript installer!" - self.color = discord.Color.from_str("#FFF" if DIR == "carfigures" else "#03BAFC") - self.timestamp = datetime.now() + self.title = "DexScript Installation" + self.description = "Welcome to the DexScript installer!" + self.color = discord.Color.from_str("#FFF" if DIR == "carfigures" else "#03BAFC") + self.timestamp = datetime.now() - latest_version = self.installer.latest_version - current_version = self.installer.current_version + latest_version = self.installer.latest_version + current_version = self.installer.current_version - if UPDATING and latest_version and current_version and latest_version != current_version: - self.description += ( - "\n\n**Your current DexScript package version is outdated.**\n" - f"The latest version of DexScript is version {latest_version}, " - f"while this DexScript instance is on version {current_version}." - ) + if UPDATING and latest_version and current_version and latest_version != current_version: + self.description += ( + "\n\n**Your current DexScript package version is outdated.**\n" + f"The latest version of DexScript is version {latest_version}, " + f"while this DexScript instance is on version {current_version}." + ) - self.set_image(url="https://raw.githubusercontent.com/Dotsian/DexScript/refs/heads/dev/assets/DexScriptPromo.png") + self.set_image( + url="https://raw.githubusercontent.com/Dotsian/DexScript/refs/heads/dev/assets/DexScriptPromo.png" + ) class InstallerView(discord.ui.View): @@ -69,13 +72,15 @@ def __init__(self, installer): super().__init__() self.installer = installer - @discord.ui.button(style=discord.ButtonStyle.primary, label="Update" if UPDATING else "Install") + @discord.ui.button( + style=discord.ButtonStyle.primary, label="Update" if UPDATING else "Install" + ) async def install_button(self, interaction: discord.Interaction, _: discord.ui.Button): self.install_button.disabled = True self.quit_button.disabled = True await self.installer.install() - + await interaction.message.edit(**self.installer.interface.fields) await interaction.response.defer() @@ -83,42 +88,42 @@ async def install_button(self, interaction: discord.Interaction, _: discord.ui.B async def quit_button(self, interaction: discord.Interaction, _: discord.ui.Button): self.install_button.disabled = True self.quit_button.disabled = True - + await interaction.message.edit(**self.installer.interface.fields) await interaction.response.defer() class InstallerGUI: - def __init__(self, installer): - self.loaded = False + def __init__(self, installer): + self.loaded = False - self.installer = installer + self.installer = installer - self.embed = InstallerEmbed(installer) - self.view = InstallerView(installer) + self.embed = InstallerEmbed(installer) + self.view = InstallerView(installer) - @property - def fields(self): - return {"embed": self.embed, "view": self.view} + @property + def fields(self): + return {"embed": self.embed, "view": self.view} - async def reload(self): - if not self.loaded: - self.loaded = True + async def reload(self): + if not self.loaded: + self.loaded = True - await ctx.send(**self.fields) # type: ignore - return + await ctx.send(**self.fields) # type: ignore + return - await ctx.message.edit(**self.fields) # type: ignore + await ctx.message.edit(**self.fields) # type: ignore class Installer: def __init__(self): - self.interface = InstallerGUI(self) - + self.interface = InstallerGUI(self) + async def install(self): if os.path.isfile(f"{DIR}/core/dexscript.py"): - os.remove(f"{DIR}/core/dexscript.py") - + os.remove(f"{DIR}/core/dexscript.py") + link = f"https://api.github.com/repos/{config.github[0]}/contents/" os.makedirs(config.path, exist_ok=True) @@ -127,7 +132,7 @@ async def install(self): request = requests.get(f"{link}/DexScript/package/{file}", {"ref": config.github[1]}) if request.status_code != requests.codes.ok: - pass # TODO: Add error handling + pass # TODO: Add error handling request = request.json() content = b64decode(request["content"]) @@ -154,17 +159,14 @@ async def install(self): write_file.writelines(lines) # try: - # await bot.load_extension(config.path) + # await bot.load_extension(config.path) # except commands.ExtensionAlreadyLoaded: - # await bot.reload_extension(config.path) + # await bot.reload_extension(config.path) @staticmethod def format_migration(line): return ( - line.replace(" ", "") - .replace("|", " ") - .replace("/n", "\n") - .replace("$DIR", DIR) + line.replace(" ", "").replace("|", " ").replace("/n", "\n").replace("$DIR", DIR) ) @property @@ -181,23 +183,23 @@ def latest_version(self): new_version = re.search(r'version\s*=\s*"(.*?)"', toml_content) if not new_version: - return + return return new_version.group(1) - + @property def current_version(self): if not os.path.isfile(f"{config.path}/cog.py"): - return - + return + with open(f"{config.path}/cog.py", "r") as file: old_version = re.search(r'__version__\s*=\s*["\']([^"\']+)["\']', file.read()) if not old_version: - return - + return + return old_version.group(1) installer = Installer() -await installer.interface.reload() # type: ignore +await installer.interface.reload() # type: ignore diff --git a/DexScript/github/migration.py b/DexScript/github/migration.py index a11365f..e02b4a9 100644 --- a/DexScript/github/migration.py +++ b/DexScript/github/migration.py @@ -3,7 +3,7 @@ DIR = "ballsdex" if path.isdir("ballsdex") else "carfigures" -#|-----------------------------------------------------------------------------------------|# +# |-----------------------------------------------------------------------------------------|# def repair_bot_file(): @@ -30,7 +30,7 @@ def repair_bot_file(): file.writelines(new_lines) -#|-----------------------------------------------------------------------------------------|# +# |-----------------------------------------------------------------------------------------|# repair_bot_file() diff --git a/DexScript/package/cog.py b/DexScript/package/cog.py index 2420d39..c56ce53 100644 --- a/DexScript/package/cog.py +++ b/DexScript/package/cog.py @@ -6,8 +6,8 @@ import requests from discord.ext import commands -from .utils import config, Utils, DIR from .parser import DexScriptParser +from .utils import DIR, Utils, config if DIR == "ballsdex": from ballsdex.settings import settings @@ -82,7 +82,7 @@ async def run(self, ctx: commands.Context, *, code: str): if result is not None: await ctx.send(f"```ERROR: {result}```") return - + await ctx.message.add_reaction("✅") @commands.command() @@ -117,7 +117,7 @@ async def about(self, ctx: commands.Context): value=( "To update DexScript, run " f"`{settings.prefix}run EVAL > EXEC_GIT > Dotsian/DexScript/github/installer.py`" - ) + ), ) version_check = "OUTDATED" if self.check_version() is not None else "LATEST" @@ -129,9 +129,7 @@ async def about(self, ctx: commands.Context): @commands.command() @commands.is_owner() - async def setting( - self, ctx: commands.Context, setting: str, value: str | None = None - ): + async def setting(self, ctx: commands.Context, setting: str, value: str | None = None): """ Changes a setting based on the value provided. @@ -147,7 +145,7 @@ async def setting( if setting not in vars(config): await ctx.send(f"`{setting}` is not a valid setting.") return - + setting_value = vars(config)[setting] new_value = value diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 5f5933e..3101e0f 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -1,12 +1,12 @@ import asyncio import os -import requests import shutil from base64 import b64decode import discord +import requests -from .utils import Utils, MEDIA_PATH +from .utils import MEDIA_PATH, Utils class DexCommand: @@ -18,11 +18,14 @@ def __loaded__(self): async def create_model(self, model, identifier): fields = {} - - special_list = {k: Utils.port(v) for k, v in { - "Identifiers": ["country", "catch_names", "name"], - "Ignore": ["id", "short_name"] - }.items()} + + special_list = { + k: Utils.port(v) + for k, v in { + "Identifiers": ["country", "catch_names", "name"], + "Ignore": ["id", "short_name"], + }.items() + } for field, field_type in model._meta.fields_map.items(): if vars(model()).get(field) is not None or field in special_list["Ignore"]: @@ -36,14 +39,14 @@ async def create_model(self, model, identifier): case "ForeignKeyFieldInstance": pass # instance = await Models.fetch_model(field).first() - + # if instance is None: - # raise Exception(f"Could not find default {field}") - + # raise Exception(f"Could not find default {field}") + # fields[field] = instance.pk case "BigIntField": - fields[field] = 100 ** 8 + fields[field] = 100**8 case "BackwardFKRelation" | "JSONField": continue @@ -61,9 +64,7 @@ async def get_model(self, model, identifier): try: returned_model = await model.name.filter( - **{ - translated_identifier: Utils.autocorrect(identifier, correction_list) - } + **{translated_identifier: Utils.autocorrect(identifier, correction_list)} ) except AttributeError: raise Exception(f"'{model}' is not a valid model.") @@ -99,7 +100,6 @@ async def create(self, ctx, model, identifier): await ctx.send(f"Created `{identifier}`") - async def delete(self, ctx, model, identifier): """ Deletes a model instance. @@ -112,7 +112,6 @@ async def delete(self, ctx, model, identifier): await ctx.send(f"Deleted `{identifier}`") - async def update(self, ctx, model, identifier, attribute, value=None): """ Updates a model instance's attribute. If value is None, it will check @@ -134,10 +133,9 @@ async def update(self, ctx, model, identifier, attribute, value=None): await ctx.send(f"Updated `{identifier}'s` {attribute} to `{new_attribute}`") - async def view(self, ctx, model, identifier, attribute=None): """ - Displays an attribute of a model instance. If `ATTRIBUTE` is left blank, + Displays an attribute of a model instance. If `ATTRIBUTE` is left blank, it will display every attribute of that model instance. Documentation @@ -158,7 +156,7 @@ async def view(self, ctx, model, identifier, attribute=None): if isinstance(value, str) and Utils.is_image(value): if fields.get("files") is None: fields["files"] = [] - + fields["files"].append(discord.File(value[1:])) fields["content"] += "```" @@ -174,7 +172,6 @@ async def view(self, ctx, model, identifier, attribute=None): await ctx.send(f"```{new_attribute}```") - async def attributes(self, ctx, model): """ Lists all changeable attributes of a model. @@ -183,9 +180,7 @@ async def attributes(self, ctx, model): ------------- ATTRIBUTES > MODEL """ - model_name = ( - model.name if isinstance(model.name, str) else model.name.__name__ - ) + model_name = model.name if isinstance(model.name, str) else model.name.__name__ parameters = f"{model_name.upper()} ATTRIBUTES:\n\n" @@ -204,12 +199,10 @@ class Filter(DexCommand): """ # TODO: Add attachment support. - async def update( - self, ctx, model, attribute, old_value, new_value, tortoise_operator=None - ): + async def update(self, ctx, model, attribute, old_value, new_value, tortoise_operator=None): """ - Updates all instances of a model to the specified value where the specified attribute - meets the condition defined by the optional `TORTOISE_OPERATOR` argument + Updates all instances of a model to the specified value where the specified attribute + meets the condition defined by the optional `TORTOISE_OPERATOR` argument (e.g., greater than, equal to, etc.). Documentation @@ -230,13 +223,10 @@ async def update( f"`{attribute}` value of `{old_value}` to `{new_value}`" ) - - async def delete( - self, ctx, model, attribute, value, tortoise_operator=None - ): + async def delete(self, ctx, model, attribute, value, tortoise_operator=None): """ - Deletes all instances of a model where the specified attribute meets the condition - defined by the optional `TORTOISE_OPERATOR` argument + Deletes all instances of a model where the specified attribute meets the condition + defined by the optional `TORTOISE_OPERATOR` argument (e.g., greater than, equal to, etc.). Documentation @@ -244,15 +234,14 @@ async def delete( FILTER > DELETE > MODEL > ATTRIBUTE > VALUE > TORTOISE_OPERATOR(?) """ lower_name = attribute.name.lower() - + if tortoise_operator is not None: lower_name += f"__{tortoise_operator.name.lower()}" - + await model.name.filter(**{lower_name: value.name}).delete() await ctx.send( - f"Deleted all `{model}` instances with a " - f"`{attribute}` value of `{value}`" + f"Deleted all `{model}` instances with a " f"`{attribute}` value of `{value}`" ) @@ -264,7 +253,6 @@ class Eval(DexCommand): def __loaded__(self): os.makedirs("eval_presets", exist_ok=True) - async def exec_git(self, ctx, link): """ Executes an eval command based on a GitHub file link. @@ -280,7 +268,7 @@ async def exec_git(self, ctx, link): link.pop(0) api = f"https://api.github.com/repos/{start}/contents/{'/'.join(link)}" - + request = requests.get(api) if request.status_code != requests.codes.ok: @@ -294,7 +282,6 @@ async def exec_git(self, ctx, link): raise Exception(error) else: await ctx.message.add_reaction("✅") - async def save(self, ctx, name): """ @@ -306,7 +293,7 @@ async def save(self, ctx, name): """ if len(name.name) > 25: raise Exception(f"`{name}` is above the 25 character limit") - + if os.path.isfile(f"eval_presets/{name}.py"): raise Exception(f"`{name}` aleady exists.") @@ -324,7 +311,6 @@ async def save(self, ctx, name): await ctx.send(f"`{name}` eval preset has been saved!") - async def remove(self, ctx, name): """ Removes an eval preset. @@ -340,8 +326,7 @@ async def remove(self, ctx, name): await ctx.send(f"Removed `{name}` preset.") - - async def run(self, ctx, name): # TODO: Allow args to be passed through `run`. + async def run(self, ctx, name): # TODO: Allow args to be passed through `run`. """ Runs an eval preset. @@ -376,7 +361,6 @@ async def read(self, ctx, file_path): """ await ctx.send(file=discord.File(file_path.name)) - async def write(self, ctx, file_path): """ Writes to a file using the attached file's contents. @@ -393,7 +377,6 @@ async def write(self, ctx, file_path): await ctx.send(f"Wrote to `{file_path}`") - async def clear(self, ctx, file_path): """ Clears the contents of a file. @@ -404,13 +387,12 @@ async def clear(self, ctx, file_path): """ if not os.path.isfile(file_path): raise Exception(f"'{file_path}' does not exist") - + with open(file_path.name, "w") as _: pass await ctx.send(f"Cleared `{file_path}`") - async def listdir(self, ctx, file_path=None): """ Lists all files inside of a directory. @@ -422,7 +404,6 @@ async def listdir(self, ctx, file_path=None): path = file_path.name if file_path is not None else None await ctx.send(f"```{'\n'.join(os.listdir(path))}```") - async def delete(self, ctx, file_path): """ diff --git a/DexScript/package/parser.py b/DexScript/package/parser.py index b0a087a..8ca5302 100644 --- a/DexScript/package/parser.py +++ b/DexScript/package/parser.py @@ -1,19 +1,20 @@ import inspect import re import traceback -from dataclasses import dataclass, field as datafield +from dataclasses import dataclass +from dataclasses import field as datafield from enum import Enum from typing import Any from dateutil.parser import parse as parse_date from . import commands -from .utils import config, Utils, DIR +from .utils import DIR, Utils, config if DIR == "ballsdex": - from ballsdex.core.models import Ball, Economy, Regime, Special # noqa: F401, I001 + from ballsdex.core.models import Ball, Economy, Regime, Special # noqa: F401, I001 else: - from carfigures.core.models import Car, CarType, Country, Event, FontsPack # noqa: F401, I001 + from carfigures.core.models import Car, CarType, Country, Event, FontsPack # noqa: F401, I001 class Types(Enum): @@ -57,7 +58,7 @@ def all(names=False, key=None): "Event", "FontsPack", "Exclusive", - ] + ], } return_list = model_list[DIR] @@ -82,10 +83,13 @@ def __init__(self, ctx, bot): self.attachments = ctx.message.attachments self.command_classes = inspect.getmembers( - commands, lambda o: ( - inspect.isclass(o) and issubclass(o, commands.DexCommand) - and not issubclass(o, commands.Global) and o.__name__ != "DexCommand" - ) + commands, + lambda o: ( + inspect.isclass(o) + and issubclass(o, commands.DexCommand) + and not issubclass(o, commands.Global) + and o.__name__ != "DexCommand" + ), ) self.global_methods = [x for x in dir(commands.Global) if not x.startswith("__")] @@ -99,7 +103,7 @@ def create_value(self, line): Types.CLASS: lower in [x[0].lower() for x in self.command_classes], Types.MODEL: lower in Models.all(True, key=str.lower), Types.DATETIME: Utils.is_date(lower) and lower.count("-") >= 2, - Types.BOOLEAN: lower in ["true", "false"] + Types.BOOLEAN: lower in ["true", "false"], } for key, operation in type_dict.items(): @@ -145,21 +149,21 @@ async def execute(self, code: str): parsed_code = [ [self.create_value(s.strip()) for s in re.findall(r"[^>]+", line)] - for line in split_code if not line.strip().startswith("--") + for line in split_code + if not line.strip().startswith("--") ] for line2 in parsed_code: if line2 == []: continue - + method = line2[0] if method.type not in (Types.METHOD, Types.CLASS): return self.error( - f"'{method.name}' is not a valid command.", - traceback.format_exc() + f"'{method.name}' is not a valid command.", traceback.format_exc() ) - + if method.type == Types.CLASS: line2.pop(0) method = (getattr(commands, method.name.title()), line2[0]) @@ -178,6 +182,5 @@ async def execute(self, code: str): await method_call(self.ctx, *line2) except TypeError: return self.error( - f"Argument missing when calling '{method[1].name}'.", - traceback.format_exc() + f"Argument missing when calling '{method[1].name}'.", traceback.format_exc() ) diff --git a/DexScript/package/utils.py b/DexScript/package/utils.py index 3aa565c..7768b0c 100644 --- a/DexScript/package/utils.py +++ b/DexScript/package/utils.py @@ -82,20 +82,20 @@ async def save_file(attachment: discord.Attachment) -> Path: if not match: raise TypeError("The file you uploaded lacks an extension.") - + i = 1 while path.exists(): path = Path(f"{MEDIA_PATH}/{match.group(1)}-{i}{match.group(2)}") i = i + 1 - + await attachment.save(path) if MEDIA_PATH == "./admin_panel/media": return path.relative_to("./admin_panel/media/") return path.relative_to("/static/uploads") - + @staticmethod def extract_str_attr(object): expression = r"return\s+self\.(\w+)" @@ -113,7 +113,7 @@ def remove_code_markdown(content) -> str: def is_image(path) -> bool: if path.startswith("/static/uploads/"): path.replace("/static/uploads/", "") - + return os.path.isfile(f"{MEDIA_PATH}/{path}") @staticmethod diff --git a/pyproject.toml b/pyproject.toml index bd11308..52e57e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ license = "MIT" [tool.poetry.dependencies] python = ">=3.12" "discord.py" = "^2.4.0" -ruff = "^0.9.3" +ruff = "^0.9.4" [build-system] requires = ["poetry-core>=1.0.0"] From e7d3e6e41bf2790c108c82ae17a5974500795a9f Mon Sep 17 00:00:00 2001 From: Dotsian Date: Sat, 1 Feb 2025 05:21:32 -0500 Subject: [PATCH 048/133] Add back the `upgrade` command --- DexScript/package/cog.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/DexScript/package/cog.py b/DexScript/package/cog.py index c56ce53..dd76342 100644 --- a/DexScript/package/cog.py +++ b/DexScript/package/cog.py @@ -7,7 +7,7 @@ from discord.ext import commands from .parser import DexScriptParser -from .utils import DIR, Utils, config +from .utils import config, DIR, Utils, config if DIR == "ballsdex": from ballsdex.settings import settings @@ -46,7 +46,7 @@ def check_version(): return ( f"Your DexScript version ({__version__}) is outdated. " f"Please update to version ({new_version}) " - f"by running the command in `{settings.prefix}.about`." + f"by running `{settings.prefix}upgrade`" ) return None @@ -101,7 +101,7 @@ async def about(self, ctx: commands.Context): "balls, regimes, specials, etc.\n\n" f"Refer to the official [DexScript guide](<{guide_link}>) for information " f"about DexScript's functionality or use `{settings.prefix}run HELP` to display " - "a list of all commands and what they do.\n\n" + f"a list of all commands and what they do.\nTo update DexScript, run `{settings.prefix}upgrade`.\n\n" "If you want to follow DexScript or require assistance, join the official " f"[DexScript Discord server](<{discord_link}>)." ) @@ -112,14 +112,6 @@ async def about(self, ctx: commands.Context): color=discord.Color.from_str("#03BAFC"), ) - embed.add_field( - name="Updating DexScript", - value=( - "To update DexScript, run " - f"`{settings.prefix}run EVAL > EXEC_GIT > Dotsian/DexScript/github/installer.py`" - ), - ) - version_check = "OUTDATED" if self.check_version() is not None else "LATEST" embed.set_thumbnail(url="https://i.imgur.com/uKfx0qO.png") @@ -127,6 +119,16 @@ async def about(self, ctx: commands.Context): await ctx.send(embed=embed) + @commands.command() + @commands.is_owner() + async def upgrade(self, ctx: commands.Context): + link = "https://api.github.com/repos/Dotsian/DexScript/contents/DexScript/github/installer.py" + + await ctx.invoke( + self.bot.get_command("eval"), + body=base64.b64decode(requests.get(link, {"ref": config.reference}).json()["content"]).decode() + ) + @commands.command() @commands.is_owner() async def setting(self, ctx: commands.Context, setting: str, value: str | None = None): From 74eae86323dca65742afe4d9da7a97c28c1d72e0 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Sat, 1 Feb 2025 05:23:18 -0500 Subject: [PATCH 049/133] Fix checks and reference --- DexScript/package/cog.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/DexScript/package/cog.py b/DexScript/package/cog.py index dd76342..92fb77e 100644 --- a/DexScript/package/cog.py +++ b/DexScript/package/cog.py @@ -7,7 +7,7 @@ from discord.ext import commands from .parser import DexScriptParser -from .utils import config, DIR, Utils, config +from .utils import DIR, Utils, config if DIR == "ballsdex": from ballsdex.settings import settings @@ -33,7 +33,7 @@ def check_version(): r = requests.get( "https://api.github.com/repos/Dotsian/DexScript/contents/pyproject.toml", - {"ref": config.branch}, + {"ref": config.reference}, ) if r.status_code != requests.codes.ok: @@ -101,7 +101,8 @@ async def about(self, ctx: commands.Context): "balls, regimes, specials, etc.\n\n" f"Refer to the official [DexScript guide](<{guide_link}>) for information " f"about DexScript's functionality or use `{settings.prefix}run HELP` to display " - f"a list of all commands and what they do.\nTo update DexScript, run `{settings.prefix}upgrade`.\n\n" + "a list of all commands and what they do.\n" + f"To update DexScript, run `{settings.prefix}upgrade`.\n\n" "If you want to follow DexScript or require assistance, join the official " f"[DexScript Discord server](<{discord_link}>)." ) From e3fd8381e8dc72646a5497fc584b64eca6ede4e9 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Sat, 1 Feb 2025 05:24:44 -0500 Subject: [PATCH 050/133] Fix checks #2 --- DexScript/package/cog.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/DexScript/package/cog.py b/DexScript/package/cog.py index 92fb77e..b600754 100644 --- a/DexScript/package/cog.py +++ b/DexScript/package/cog.py @@ -124,11 +124,9 @@ async def about(self, ctx: commands.Context): @commands.is_owner() async def upgrade(self, ctx: commands.Context): link = "https://api.github.com/repos/Dotsian/DexScript/contents/DexScript/github/installer.py" - - await ctx.invoke( - self.bot.get_command("eval"), - body=base64.b64decode(requests.get(link, {"ref": config.reference}).json()["content"]).decode() - ) + content = requests.get(link, {"ref": config.reference}).json()["content"] + + await ctx.invoke(self.bot.get_command("eval"), body=base64.b64decode(content).decode()) @commands.command() @commands.is_owner() From 764f327cacf0e526883009eade9f6a69108db030 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Sat, 1 Feb 2025 23:10:25 -0500 Subject: [PATCH 051/133] Fix create method --- DexScript/package/commands.py | 31 ++++++++-------- DexScript/package/parser.py | 45 ++---------------------- DexScript/package/utils.py | 66 ++++++++++++++++++++--------------- 3 files changed, 55 insertions(+), 87 deletions(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 3101e0f..aae81ff 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -6,7 +6,7 @@ import discord import requests -from .utils import MEDIA_PATH, Utils +from .utils import DIR, MEDIA_PATH, Utils class DexCommand: @@ -20,13 +20,16 @@ async def create_model(self, model, identifier): fields = {} special_list = { - k: Utils.port(v) - for k, v in { - "Identifiers": ["country", "catch_names", "name"], - "Ignore": ["id", "short_name"], - }.items() + "Identifiers": ["country", "catch_names", "name"], + "Ignore": ["id", "short_name"] } + if DIR == "carfigures": + special_list = { + "Identifiers": ["fullName", "catchNames", "name"], + "Ignore": ["id", "shortName"] + } + for field, field_type in model._meta.fields_map.items(): if vars(model()).get(field) is not None or field in special_list["Ignore"]: continue @@ -37,13 +40,12 @@ async def create_model(self, model, identifier): match field_type: case "ForeignKeyFieldInstance": - pass - # instance = await Models.fetch_model(field).first() + instance = Utils.fetch_model(field).first() - # if instance is None: - # raise Exception(f"Could not find default {field}") + if instance is None: + raise Exception(f"Could not find default {field}") - # fields[field] = instance.pk + fields[field] = instance.pk case "BigIntField": fields[field] = 100**8 @@ -57,14 +59,11 @@ async def create_model(self, model, identifier): await model.create(**fields) async def get_model(self, model, identifier): - attribute = Utils.extract_str_attr(model.name) - - correction_list = await model.name.all().values_list(attribute, flat=True) - translated_identifier = Utils.port(model.extra_data[0].lower()) + correction_list = await model.name.all().values_list(model.extra_data[0], flat=True) try: returned_model = await model.name.filter( - **{translated_identifier: Utils.autocorrect(identifier, correction_list)} + **{model.extra_data[0]: Utils.autocorrect(identifier, correction_list)} ) except AttributeError: raise Exception(f"'{model}' is not a valid model.") diff --git a/DexScript/package/parser.py b/DexScript/package/parser.py index 8ca5302..ef45b30 100644 --- a/DexScript/package/parser.py +++ b/DexScript/package/parser.py @@ -11,11 +11,6 @@ from . import commands from .utils import DIR, Utils, config -if DIR == "ballsdex": - from ballsdex.core.models import Ball, Economy, Regime, Special # noqa: F401, I001 -else: - from carfigures.core.models import Car, CarType, Country, Event, FontsPack # noqa: F401, I001 - class Types(Enum): DEFAULT = 0 @@ -36,42 +31,6 @@ def __str__(self): return str(self.name) -@dataclass -class Models: - """ - Model functions. - """ - - @staticmethod - def all(names=False, key=None): - model_list = { - "ballsdex": [ - "Ball", - "Regime", - "Economy", - "Special", - ], - "carfigures": [ - "Car", - "CarType", - "Country", - "Event", - "FontsPack", - "Exclusive", - ], - } - - return_list = model_list[DIR] - - if not names: - return_list = [globals().get(x) for x in return_list if globals().get(x) is not None] - - if key is not None: - return_list = [key(x) for x in return_list] - - return return_list - - class DexScriptParser: """ This class is used to parse DexScript into Python code. @@ -101,7 +60,7 @@ def create_value(self, line): type_dict = { Types.METHOD: lower in self.global_methods, Types.CLASS: lower in [x[0].lower() for x in self.command_classes], - Types.MODEL: lower in Models.all(True, key=str.lower), + Types.MODEL: lower in Utils.models(True, key=str.lower), Types.DATETIME: Utils.is_date(lower) and lower.count("-") >= 2, Types.BOOLEAN: lower in ["true", "false"], } @@ -115,7 +74,7 @@ def create_value(self, line): match value.type: case Types.MODEL: - model = globals().get(line) + model = Utils.fetch_model(line) if model is None: examples = "Ball, Regime, Special" diff --git a/DexScript/package/utils.py b/DexScript/package/utils.py index 7768b0c..e5ead48 100644 --- a/DexScript/package/utils.py +++ b/DexScript/package/utils.py @@ -10,6 +10,11 @@ DIR = "ballsdex" if os.path.isdir("ballsdex") else "carfigures" +if DIR == "ballsdex": + from ballsdex.core.models import Ball, Economy, Regime, Special # noqa: F401, I001 +else: + from carfigures.core.models import Car, CarType, Country, Event, FontsPack # noqa: F401, I001 + START_CODE_BLOCK_RE = re.compile(r"^((```sql?)(?=\s)|(```))") FILENAME_RE = re.compile(r"^(.+)(\.\S+)$") @@ -36,6 +41,39 @@ class Utils: Utility functions for DexScript. """ + @staticmethod + def fetch_model(model): + return globals().get(model) + + @staticmethod + def models(names=False, key=None): + model_list = { + "ballsdex": [ + "Ball", + "Regime", + "Economy", + "Special", + ], + "carfigures": [ + "Car", + "CarType", + "Country", + "Event", + "FontsPack", + "Exclusive", + ], + } + + return_list = model_list[DIR] + + if not names: + return_list = [globals().get(x) for x in return_list if globals().get(x) is not None] + + if key is not None: + return_list = [key(x) for x in return_list] + + return return_list + @staticmethod def autocorrect(string, correction_list, error="does not exist."): autocorrection = get_close_matches(string, correction_list) @@ -47,34 +85,6 @@ def autocorrect(string, correction_list, error="does not exist."): return autocorrection[0] - @staticmethod - def port(original: str | list[str]): - """ - Translates model and field names into a format for both Ballsdex and CarFigures. - - Parameters - ---------- - original: str | list[str] - The original string or list of strings you want to translate. - """ - if DIR == "ballsdex": - return original - - translation = { - "BALL": "ENTITY", - "COUNTRY": "fullName", - "SHORT_NAME": "shortName", - "CATCH_NAMES": "catchNames", - "ICON": "image", - } - - if isinstance(original, list): - translated_copy = [translation.get(x.upper(), x) for x in original] - else: - translated_copy = translation.get(original.upper(), original) - - return translated_copy - @staticmethod async def save_file(attachment: discord.Attachment) -> Path: path = Path(f"{MEDIA_PATH}/{attachment.filename}") From e09c6a6d33d0027b25ccdd8032e8ba6c158bb126 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Sun, 2 Feb 2025 02:21:05 -0500 Subject: [PATCH 052/133] Readme changes --- README.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index de48b04..d500aea 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,14 @@ Let's say you wanted to update a ball's rarity to 2. You could run `UPDATE > BAL ![Updating rarity showcase](assets/screenshots/showcase1.png) -See how simple that is? Using DexScript is way more efficient than eval commands and the admin panel. +DexScript has a ton more features too! All of them can be found within our extensive documentation. Here's a simple list of the most popular features. + +* Creating Models: `CREATE > Ball > Dex Empire` - creates a ball called "Dex Empire" +* Updating Models: `UPDATE > Regime > Democracy > name > Monarchy` - updates the democracy regime's name to "Monarchy" +* Deleting Models: `DELETE > Special > Lunar New Year` - deletes an event called "Lunar New Year" +* Mass Updating Models: `FILTER > UPDATE > Ball > rarity > 5.0 > 10.0 > GT` - Updates all balls with higher than a rarity of 5 to a rarity of 10. +* Mass Deleting Models: `FILTER > DELETE > Special > enabled > False` - Deletes all events that aren't enabled. +* Eval Presets: `EVAL > SAVE > show leaderboard` and `EVAL > RUN > show leaderboard` - saves an eval called "show leaderboard" and runs it. ## Installation @@ -36,17 +43,16 @@ To install DexScript, run the following eval command: #### Release Version ```py -import base64, requests -await ctx.invoke(bot.get_command("eval"), body=base64.b64decode(requests.get("https://api.github.com/repos/Dotsian/DexScript/contents/installer.py").json()["content"]).decode()) +import base64, requests; await ctx.invoke(bot.get_command("eval"), body=base64.b64decode(requests.get("https://api.github.com/repos/Dotsian/DexScript/contents/installer.py").json()["content"]).decode()) ``` #### Development Version ```py -import base64, requests -await ctx.invoke(bot.get_command("eval"), body=base64.b64decode(requests.get("https://api.github.com/repos/Dotsian/DexScript/contents/DexScript/github/installer.py", {"ref": "dev"}).json()["content"]).decode()) +import base64, requests; await ctx.invoke(bot.get_command("eval"), body=base64.b64decode(requests.get("https://api.github.com/repos/Dotsian/DexScript/contents/DexScript/github/installer.py", {"ref": "dev"}).json()["content"]).decode()) + ``` ## Updating -The command above will automatically update DexScript, however, if you already have DexScript, you can run `DEV > EXEC_GIT > Dotsian/DexScript/installer.py` to update DexScript. +The command above will automatically update DexScript, however, if you already have DexScript, you can run `b.upgrade` to update DexScript, replacing `b.` with your bot's prefix. From 94250efb668f5da3307de4c7609b3cd8d94aa1ec Mon Sep 17 00:00:00 2001 From: Dotsian Date: Sun, 2 Feb 2025 02:27:54 -0500 Subject: [PATCH 053/133] Finalize readme --- README.md | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index d500aea..6dcbc5a 100644 --- a/README.md +++ b/README.md @@ -16,23 +16,18 @@ Let's say you wanted to update a ball's rarity to 2. You could run `UPDATE > BAL DexScript has a ton more features too! All of them can be found within our extensive documentation. Here's a simple list of the most popular features. -* Creating Models: `CREATE > Ball > Dex Empire` - creates a ball called "Dex Empire" -* Updating Models: `UPDATE > Regime > Democracy > name > Monarchy` - updates the democracy regime's name to "Monarchy" -* Deleting Models: `DELETE > Special > Lunar New Year` - deletes an event called "Lunar New Year" -* Mass Updating Models: `FILTER > UPDATE > Ball > rarity > 5.0 > 10.0 > GT` - Updates all balls with higher than a rarity of 5 to a rarity of 10. -* Mass Deleting Models: `FILTER > DELETE > Special > enabled > False` - Deletes all events that aren't enabled. -* Eval Presets: `EVAL > SAVE > show leaderboard` and `EVAL > RUN > show leaderboard` - saves an eval called "show leaderboard" and runs it. +* **Creating, updating, and deleting Balls, Regimes, Specials, etc.** +* **Mass updating and deleting Balls, Regimes, Specials, etc.** +* **Saving evals and loading them**. -## Installation - -### Requirements +## DexScript Requirements To install DexScript, you must have the following: * Ballsdex or CarFigures v2.2.0+ * Eval access -### Installing +## Installing DexScript DexScript has two versions, the release version and the development version. @@ -50,7 +45,6 @@ import base64, requests; await ctx.invoke(bot.get_command("eval"), body=base64.b ```py import base64, requests; await ctx.invoke(bot.get_command("eval"), body=base64.b64decode(requests.get("https://api.github.com/repos/Dotsian/DexScript/contents/DexScript/github/installer.py", {"ref": "dev"}).json()["content"]).decode()) - ``` ## Updating From de7a48e356274615129655e8872ece219ca8d774 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Sun, 2 Feb 2025 16:38:32 -0500 Subject: [PATCH 054/133] Work on CF casing support, add error handling for incorrect attributes, and add `EVAL > LIST` command. --- DexScript/package/commands.py | 70 ++++++++++++++++++++++++----------- DexScript/package/parser.py | 2 +- DexScript/package/utils.py | 29 +++++++++++++++ 3 files changed, 79 insertions(+), 22 deletions(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index aae81ff..9a52445 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -25,10 +25,8 @@ async def create_model(self, model, identifier): } if DIR == "carfigures": - special_list = { - "Identifiers": ["fullName", "catchNames", "name"], - "Ignore": ["id", "shortName"] - } + special_list["Identifiers"] = Utils.to_camel_case(special_list["Identifiers"]) + special_list["Ignore"] = Utils.to_camel_case(special_list["Ignore"]) for field, field_type in model._meta.fields_map.items(): if vars(model()).get(field) is not None or field in special_list["Ignore"]: @@ -120,17 +118,24 @@ async def update(self, ctx, model, identifier, attribute, value=None): ------------- UPDATE > MODEL > IDENTIFIER > ATTRIBUTE > VALUE(?) """ - new_attribute = value.name if value is not None else None + attribute_name = Utils.casing(attribute.name) + new_value = Utils.casing(value.name) if value is not None else None + + returned_model = self.get_model(model, identifier.name) + + if not hasattr(returned_model, attribute_name): + raise Exception( + f"'{attribute_name}' is not a valid {model} attribute\n" + f"Run `ATTRIBUTES > {model}` to see a list of all attributes for that model" + ) if value is None: image_path = await Utils.save_file(ctx.message.attachments[0]) - new_attribute = f"{MEDIA_PATH}/{image_path}" + new_value = f"{MEDIA_PATH}/{image_path}" - await self.get_model(model, identifier.name).update( - **{attribute.name.lower(): new_attribute} - ) + await returned_model.update(**{attribute_name: new_value}) - await ctx.send(f"Updated `{identifier}'s` {attribute} to `{new_attribute}`") + await ctx.send(f"Updated `{identifier}'s` {attribute} to `{new_value}`") async def view(self, ctx, model, identifier, attribute=None): """ @@ -150,7 +155,7 @@ async def view(self, ctx, model, identifier, attribute=None): if key.startswith("_"): continue - fields["content"] += f"{key}: {value}\n" + fields["content"] += f"{Utils.to_snake_case(key)}: {value}\n" if isinstance(value, str) and Utils.is_image(value): if fields.get("files") is None: @@ -162,8 +167,16 @@ async def view(self, ctx, model, identifier, attribute=None): await ctx.send(**fields) return + + attribute_name = Utils.casing(attribute.name.lower()) + + if not hasattr(returned_model, attribute_name): + raise Exception( + f"'{new_attribute}' is not a valid {model} attribute\n" + f"Run `ATTRIBUTES > {model}` to see a list of all attributes for that model" + ) - new_attribute = getattr(returned_model, attribute.name.lower()) + new_attribute = getattr(returned_model, attribute_name) if isinstance(new_attribute, str) and os.path.isfile(new_attribute[1:]): await ctx.send(f"```{new_attribute}```", file=discord.File(new_attribute[1:])) @@ -187,7 +200,7 @@ async def attributes(self, ctx, model): if field[:1] == "_": continue - parameters += f"- {field.replace(' ', '_').upper()}\n" + parameters += f"- {Utils.to_snake_case(field).replace(' ', '_').upper()}\n" await ctx.send(f"```{parameters}```") @@ -208,13 +221,19 @@ async def update(self, ctx, model, attribute, old_value, new_value, tortoise_ope ------------- FILTER > UPDATE > MODEL > ATTRIBUTE > OLD_VALUE > NEW_VALUE > TORTOISE_OPERATOR(?) """ - lower_name = attribute.name.lower() + casing_name = Utils.casing(attribute.name.lower()) + + if not hasattr(model.name, casing_name): + raise Exception( + f"'{casing_name}' is not a valid {model} attribute\n" + f"Run `ATTRIBUTES > {model}` to see a list of all attributes for that model" + ) if tortoise_operator is not None: - lower_name += f"__{tortoise_operator.name.lower()}" + casing_name += f"__{tortoise_operator.name.lower()}" - await model.name.filter(**{lower_name: old_value.name}).update( - **{lower_name: new_value.name} + await model.name.filter(**{casing_name: old_value.name}).update( + **{casing_name: new_value.name} ) await ctx.send( @@ -232,12 +251,18 @@ async def delete(self, ctx, model, attribute, value, tortoise_operator=None): ------------- FILTER > DELETE > MODEL > ATTRIBUTE > VALUE > TORTOISE_OPERATOR(?) """ - lower_name = attribute.name.lower() + casing_name = Utils.casing(attribute.name.lower()) + + if not hasattr(model.name, casing_name): + raise Exception( + f"'{casing_name}' is not a valid {model} attribute\n" + f"Run `ATTRIBUTES > {model}` to see a list of all attributes for that model" + ) if tortoise_operator is not None: - lower_name += f"__{tortoise_operator.name.lower()}" + casing_name += f"__{tortoise_operator.name.lower()}" - await model.name.filter(**{lower_name: value.name}).delete() + await model.name.filter(**{casing_name: value.name}).delete() await ctx.send( f"Deleted all `{model}` instances with a " f"`{attribute}` value of `{value}`" @@ -291,7 +316,7 @@ async def save(self, ctx, name): EVAL > SAVE > NAME """ if len(name.name) > 25: - raise Exception(f"`{name}` is above the 25 character limit") + raise Exception(f"`{name}` is above the 25 character limit ({len(name)} > 25)") if os.path.isfile(f"eval_presets/{name}.py"): raise Exception(f"`{name}` aleady exists.") @@ -325,6 +350,9 @@ async def remove(self, ctx, name): await ctx.send(f"Removed `{name}` preset.") + async def list(self, ctx): + await ctx.send(f"```{'\n'.join(os.listdir("eval_presets"))}```") + async def run(self, ctx, name): # TODO: Allow args to be passed through `run`. """ Runs an eval preset. diff --git a/DexScript/package/parser.py b/DexScript/package/parser.py index ef45b30..241c6d4 100644 --- a/DexScript/package/parser.py +++ b/DexScript/package/parser.py @@ -39,7 +39,7 @@ class DexScriptParser: def __init__(self, ctx, bot): self.ctx = ctx self.bot = bot - self.attachments = ctx.message.attachments + # self.attachments = ctx.message.attachments self.command_classes = inspect.getmembers( commands, diff --git a/DexScript/package/utils.py b/DexScript/package/utils.py index e5ead48..c4a8fc2 100644 --- a/DexScript/package/utils.py +++ b/DexScript/package/utils.py @@ -73,6 +73,35 @@ def models(names=False, key=None): return_list = [key(x) for x in return_list] return return_list + + @staticmethod + def _common_format(string_or_list: str | list[str], func): + if isinstance(string_or_list, str): + return func(string_or_list) + + return [func(x) for x in string_or_list] + + @staticmethod + def to_camel_case(item): + """ + Formats a string or list from snake_case into camelCase for CarFigure support. + """ + return Utils._common_format( + item, func=lambda s: re.sub(r"(_[a-z])", lambda m: m.group(1)[1].upper(), s) + ) + + @staticmethod + def to_snake_case(item): + """ + Formats a string or list from camelCase into snake_case for CarFigure support. + """ + return Utils._common_format( + item, func=lambda s: re.sub(r"(? Date: Sun, 2 Feb 2025 16:41:48 -0500 Subject: [PATCH 055/133] Fix casing --- DexScript/package/utils.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/DexScript/package/utils.py b/DexScript/package/utils.py index c4a8fc2..1f0439c 100644 --- a/DexScript/package/utils.py +++ b/DexScript/package/utils.py @@ -100,8 +100,14 @@ def to_snake_case(item): ) @staticmethod - def casing(): - return Utils.to_camel_case if DIR == "carfigures" else Utils.to_snake_case + def casing(item): + """ + Determines whether to use camelCase or snake_case depending on if the bot is using + CarFigures or Ballsdex. + """ + return ( + Utils.to_camel_case(item) if DIR == "carfigures" else Utils.to_snake_case(item) + ) @staticmethod def autocorrect(string, correction_list, error="does not exist."): From 7e4ae153ae1b2f9bb8944db7d31c59983c8b9d57 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Sun, 2 Feb 2025 17:08:07 -0500 Subject: [PATCH 056/133] Fix migration file --- DexScript/github/migration.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/DexScript/github/migration.py b/DexScript/github/migration.py index e02b4a9..1047287 100644 --- a/DexScript/github/migration.py +++ b/DexScript/github/migration.py @@ -10,21 +10,24 @@ def repair_bot_file(): """ Repairs the `bot.py` file and removes extra newlines caused by an old DexScript installer. """ - with open(f"{DIR}/core/bot.py", "r") as file: - content = file.read() + new_lines = [] - if "import asyncio\n\n" not in content: + with open(f"{DIR}/core/bot.py", "r") as file: + if "import asyncio\n\n" not in file.read(): return - new_lines = [] - last_was_newline = False + with open(f"{DIR}/core/bot.py", "r") as file: + last_was_newline = False - for line in content.splitlines(keepends=True): - if last_was_newline and line == "\n": - continue + for line in file.readlines(): + if last_was_newline is True: + last_was_newline = False + continue + + if line.endswith("\n") and line != "\n" or line == "\n": + last_was_newline = True - last_was_newline = line == "\n" - new_lines.append(line) + new_lines.append(line) with open(f"{DIR}/core/bot.py", "w") as file: file.writelines(new_lines) From 567fbcbc6487bcb6e3abd9043fa30a917f22b902 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Sun, 2 Feb 2025 22:13:16 -0500 Subject: [PATCH 057/133] Uninstall button WIP --- DexScript/github/installer.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/DexScript/github/installer.py b/DexScript/github/installer.py index 750f7fb..e4d49a4 100644 --- a/DexScript/github/installer.py +++ b/DexScript/github/installer.py @@ -84,6 +84,15 @@ async def install_button(self, interaction: discord.Interaction, _: discord.ui.B await interaction.message.edit(**self.installer.interface.fields) await interaction.response.defer() + @discord.ui.button( + style=discord.ButtonStyle.primary, label="Uninstall", disabled=not UPDATING + ) + async def uninstall_button(self, interaction: discord.Interaction, _: discord.ui.Button): + # TODO: Add uninstallation + + await interaction.message.edit(**self.installer.interface.fields) + await interaction.response.defer() + @discord.ui.button(style=discord.ButtonStyle.red, label="Exit") async def quit_button(self, interaction: discord.Interaction, _: discord.ui.Button): self.install_button.disabled = True From cb647dc43c2c5d80e40f6fcbc3846ace8fd579ea Mon Sep 17 00:00:00 2001 From: Dotsian Date: Mon, 3 Feb 2025 14:47:59 -0500 Subject: [PATCH 058/133] Work on logging --- DexScript/github/installer.py | 76 ++++++++++++++++++++++++++++++----- DexScript/package/parser.py | 3 +- 2 files changed, 66 insertions(+), 13 deletions(-) diff --git a/DexScript/github/installer.py b/DexScript/github/installer.py index e4d49a4..0c29b6a 100644 --- a/DexScript/github/installer.py +++ b/DexScript/github/installer.py @@ -11,8 +11,10 @@ import os import re from base64 import b64decode -from dataclasses import dataclass +from dataclasses import dataclass, field as datafield from datetime import datetime +from enum import Enum +from traceback import format_exc import discord import requests @@ -21,6 +23,11 @@ UPDATING = os.path.isdir(f"{DIR}/packages/dexscript") +class MigrationType(Enum): + APPEND = 1 + REPLACE = 2 + + @dataclass class InstallerConfig: """ @@ -33,12 +40,28 @@ class InstallerConfig: ( "||await self.add_cog(Core(self))", '||await self.load_extension("$DIR.packages.dexscript")\n', + MigrationType.APPEND + ), + ( + '||await self.load_extension("$DIR.core.dexscript")', + '||await self.load_extension("$DIR.packages.dexscript")', + MigrationType.REPLACE ) ] path = f"{DIR}/packages/dexscript" +@dataclass +class Logger: + name: str + output: list = datafield(default_factory=list) + + def log(self, content, level): + current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + self.output.append(f"{current_time} [{self.name}] {level} - {content}") + config = InstallerConfig() +logger = Logger("DEXSCRIPT-INSTALLER") class InstallerEmbed(discord.Embed): @@ -66,6 +89,11 @@ def __init__(self, installer): url="https://raw.githubusercontent.com/Dotsian/DexScript/refs/heads/dev/assets/DexScriptPromo.png" ) + def error(self, log): + self.title = "DexScript ERROR" + self.description = "An error occured in DexScript's installation setup." + self.color = discord.Color.red() + class InstallerView(discord.ui.View): def __init__(self, installer): @@ -76,10 +104,15 @@ def __init__(self, installer): style=discord.ButtonStyle.primary, label="Update" if UPDATING else "Install" ) async def install_button(self, interaction: discord.Interaction, _: discord.ui.Button): - self.install_button.disabled = True self.quit_button.disabled = True - await self.installer.install() + await interaction.message.edit(**self.installer.interface.fields) + + try: + await self.installer.install() + except Exception as error: # TODO: Add error handling + logger.log(format_exc(), "ERROR") + self. await interaction.message.edit(**self.installer.interface.fields) await interaction.response.defer() @@ -138,10 +171,15 @@ async def install(self): os.makedirs(config.path, exist_ok=True) for file in config.files: + logger.log(f"Fetching {file} from '{link}/DexScript/package'", "INFO") + request = requests.get(f"{link}/DexScript/package/{file}", {"ref": config.github[1]}) if request.status_code != requests.codes.ok: - pass # TODO: Add error handling + raise Exception( + f"Request to return {file} from '{link}/DexScript/package' " + f"resulted with error code {request.status_code}" + ) request = request.json() content = b64decode(request["content"]) @@ -149,6 +187,10 @@ async def install(self): with open(f"{config.path}/{file}", "w") as opened_file: opened_file.write(content.decode("UTF-8")) + logger.log(f"Installed {file} from '{link}/DexScript/package'", "INFO") + + logger.log("Applying bot.py migrations", "INFO") + with open(f"{DIR}/core/bot.py", "r") as read_file: lines = read_file.readlines() @@ -159,18 +201,30 @@ async def install(self): original = self.format_migration(migration[0]) new = self.format_migration(migration[1]) - if line.rstrip() != original or lines[index + 1] == new: - continue + match migration[2]: + case MigrationType.REPLACE: + if line.rstrip() != original: + continue + + lines[index] = new + break + case MigrationType.APPEND: + if line.rstrip() != original or lines[index + 1] == new: + continue - lines.insert(stripped_lines.index(original) + 1, new) + lines.insert(stripped_lines.index(original) + 1, new) with open(f"{DIR}/core/bot.py", "w") as write_file: write_file.writelines(lines) - # try: - # await bot.load_extension(config.path) - # except commands.ExtensionAlreadyLoaded: - # await bot.reload_extension(config.path) + logger.log("Loading DexScript extension", "INFO") + + try: + await bot.load_extension(config.path) + except commands.ExtensionAlreadyLoaded: + await bot.reload_extension(config.path) + + logger.log("DexScript installation finished", "INFO") @staticmethod def format_migration(line): diff --git a/DexScript/package/parser.py b/DexScript/package/parser.py index 241c6d4..b67b2ba 100644 --- a/DexScript/package/parser.py +++ b/DexScript/package/parser.py @@ -1,8 +1,7 @@ import inspect import re import traceback -from dataclasses import dataclass -from dataclasses import field as datafield +from dataclasses import dataclass, field as datafield from enum import Enum from typing import Any From fa955d08d4d0ac952812fae60ae6c7c2d4f200d5 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Mon, 3 Feb 2025 22:55:31 -0500 Subject: [PATCH 059/133] Work on installer, add error logo, and finalize new logo design --- DexScript/github/installer.py | 81 +++++++++++++++++++++++++++++----- DexScript/package/cog.py | 4 +- assets/DexScriptLogoError.png | Bin 0 -> 13258 bytes 3 files changed, 73 insertions(+), 12 deletions(-) create mode 100644 assets/DexScriptLogoError.png diff --git a/DexScript/github/installer.py b/DexScript/github/installer.py index 0c29b6a..4b0a23c 100644 --- a/DexScript/github/installer.py +++ b/DexScript/github/installer.py @@ -14,12 +14,20 @@ from dataclasses import dataclass, field as datafield from datetime import datetime from enum import Enum +from io import StringIO from traceback import format_exc import discord import requests +from discord.ext import commands DIR = "ballsdex" if os.path.isdir("ballsdex") else "carfigures" + +if DIR == "ballsdex": + from ballsdex.settings import settings +else: + from carfigures.settings import settings + UPDATING = os.path.isdir(f"{DIR}/packages/dexscript") @@ -36,6 +44,11 @@ class InstallerConfig: github = ["Dotsian/DexScript", "dev"] files = ["__init__.py", "cog.py", "commands.py", "parser.py", "utils.py"] + appearance = { + "logo": "https://raw.githubusercontent.com/Dotsian/DexScript/refs/heads/dev/assets/DexScriptLogo.png", + "logo_error": "", # TODO: Add error logo + "banner": "https://raw.githubusercontent.com/Dotsian/DexScript/refs/heads/dev/assets/DexScriptPromo.png" + } migrations = [ ( "||await self.add_cog(Core(self))", @@ -59,17 +72,29 @@ def log(self, content, level): current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") self.output.append(f"{current_time} [{self.name}] {level} - {content}") + def file(self, name: str): + return discord.File(StringIO("\n".join(self.output)), filename=name) + config = InstallerConfig() logger = Logger("DEXSCRIPT-INSTALLER") class InstallerEmbed(discord.Embed): - def __init__(self, installer): + def __init__(self, installer, embed_type="setup"): super().__init__() self.installer = installer + match embed_type: + case "setup": + self.setup() + case "error": + self.error() + case "installed": + self.installed() + + def setup(self): self.title = "DexScript Installation" self.description = "Welcome to the DexScript installer!" self.color = discord.Color.from_str("#FFF" if DIR == "carfigures" else "#03BAFC") @@ -85,14 +110,34 @@ def __init__(self, installer): f"while this DexScript instance is on version {current_version}." ) - self.set_image( - url="https://raw.githubusercontent.com/Dotsian/DexScript/refs/heads/dev/assets/DexScriptPromo.png" - ) + self.set_image(url=config.appearance["banner"]) - def error(self, log): + def error(self): self.title = "DexScript ERROR" - self.description = "An error occured in DexScript's installation setup." + self.description = ( + "An error occured within DexScript's installation setup.\n" + "Please submit a bug report and attach the file provided." + ) self.color = discord.Color.red() + self.timestamp = datetime.now() + + if logger.log != []: + self.description += f"\n```{logger.log[-1]}```" + + self.installer.interface.attachments = [logger.file("DexScript.log")] + + self.set_image(url=config.appearance["logo_error"]) + + def installed(self): + self.title = "DexScript Installed!" + self.description = ( + "DexScript has been succesfully installed to your bot.\n" + f"Run the `{settings.prefix}about` command to view details about DexScript." + ) + self.color = discord.Color.from_str("#FFF" if DIR == "carfigures" else "#03BAFC") + self.timestamp = datetime.now() + + self.set_image(url=config.appearance["logo"]) class InstallerView(discord.ui.View): @@ -110,9 +155,16 @@ async def install_button(self, interaction: discord.Interaction, _: discord.ui.B try: await self.installer.install() - except Exception as error: # TODO: Add error handling + except Exception: logger.log(format_exc(), "ERROR") - self. + + self.install_button.disabled = True + self.uninstall_button.disabled = True + self.quit_button.disabled = True + + self.installer.interface.embed = InstallerEmbed(self.installer, "error") + else: + self.installer.interface.embed = InstallerEmbed(self.installer, "installed") await interaction.message.edit(**self.installer.interface.fields) await interaction.response.defer() @@ -144,9 +196,16 @@ def __init__(self, installer): self.embed = InstallerEmbed(installer) self.view = InstallerView(installer) + self.attachments = [] + @property def fields(self): - return {"embed": self.embed, "view": self.view} + fields = {"embed": self.embed, "view": self.view} + + if self.attachments != []: + fields["attachments"] = self.attachments + + return fields async def reload(self): if not self.loaded: @@ -220,9 +279,9 @@ async def install(self): logger.log("Loading DexScript extension", "INFO") try: - await bot.load_extension(config.path) + await bot.load_extension(config.path) # type: ignore except commands.ExtensionAlreadyLoaded: - await bot.reload_extension(config.path) + await bot.reload_extension(config.path) # type: ignore logger.log("DexScript installation finished", "INFO") diff --git a/DexScript/package/cog.py b/DexScript/package/cog.py index b600754..0fa179e 100644 --- a/DexScript/package/cog.py +++ b/DexScript/package/cog.py @@ -115,7 +115,9 @@ async def about(self, ctx: commands.Context): version_check = "OUTDATED" if self.check_version() is not None else "LATEST" - embed.set_thumbnail(url="https://i.imgur.com/uKfx0qO.png") + embed.set_thumbnail( + url="https://raw.githubusercontent.com/Dotsian/DexScript/refs/heads/dev/assets/DexScriptLogo.png" + ) embed.set_footer(text=f"DexScript {__version__} ({version_check})") await ctx.send(embed=embed) diff --git a/assets/DexScriptLogoError.png b/assets/DexScriptLogoError.png new file mode 100644 index 0000000000000000000000000000000000000000..41c04dce91eff4351fcae6fd77bafe692f8eb912 GIT binary patch literal 13258 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 YrhIscdfEl}j}GX@bt8D?wY&HK4|Uy+dH?_b literal 0 HcmV?d00001 From 3cebbe706a42281e13c38b760d508c7f04ca400c Mon Sep 17 00:00:00 2001 From: Dotsian Date: Mon, 3 Feb 2025 23:00:57 -0500 Subject: [PATCH 060/133] Fix ruff checks and add error logo to installer --- DexScript/github/installer.py | 5 +++-- DexScript/package/parser.py | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/DexScript/github/installer.py b/DexScript/github/installer.py index 4b0a23c..871aed9 100644 --- a/DexScript/github/installer.py +++ b/DexScript/github/installer.py @@ -11,7 +11,8 @@ import os import re from base64 import b64decode -from dataclasses import dataclass, field as datafield +from dataclasses import dataclass +from dataclasses import field as datafield from datetime import datetime from enum import Enum from io import StringIO @@ -46,7 +47,7 @@ class InstallerConfig: files = ["__init__.py", "cog.py", "commands.py", "parser.py", "utils.py"] appearance = { "logo": "https://raw.githubusercontent.com/Dotsian/DexScript/refs/heads/dev/assets/DexScriptLogo.png", - "logo_error": "", # TODO: Add error logo + "logo_error": "https://raw.githubusercontent.com/Dotsian/DexScript/refs/heads/dev/assets/DexScriptLogoError.png", "banner": "https://raw.githubusercontent.com/Dotsian/DexScript/refs/heads/dev/assets/DexScriptPromo.png" } migrations = [ diff --git a/DexScript/package/parser.py b/DexScript/package/parser.py index b67b2ba..241c6d4 100644 --- a/DexScript/package/parser.py +++ b/DexScript/package/parser.py @@ -1,7 +1,8 @@ import inspect import re import traceback -from dataclasses import dataclass, field as datafield +from dataclasses import dataclass +from dataclasses import field as datafield from enum import Enum from typing import Any From d6b757237d6add62bb00b74e985943e516473c23 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Mon, 3 Feb 2025 23:04:56 -0500 Subject: [PATCH 061/133] Fix to extension --- DexScript/github/installer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DexScript/github/installer.py b/DexScript/github/installer.py index 871aed9..085e275 100644 --- a/DexScript/github/installer.py +++ b/DexScript/github/installer.py @@ -280,9 +280,9 @@ async def install(self): logger.log("Loading DexScript extension", "INFO") try: - await bot.load_extension(config.path) # type: ignore + await bot.load_extension(config.path.replace("/", ".")) # type: ignore except commands.ExtensionAlreadyLoaded: - await bot.reload_extension(config.path) # type: ignore + await bot.reload_extension(config.path.replace("/", ".")) # type: ignore logger.log("DexScript installation finished", "INFO") From 8b13383a81a01233296a590c353fb48bbb2331e6 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Mon, 3 Feb 2025 23:07:30 -0500 Subject: [PATCH 062/133] Fix attachment --- DexScript/github/installer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DexScript/github/installer.py b/DexScript/github/installer.py index 085e275..ff2dfe3 100644 --- a/DexScript/github/installer.py +++ b/DexScript/github/installer.py @@ -125,7 +125,7 @@ def error(self): if logger.log != []: self.description += f"\n```{logger.log[-1]}```" - self.installer.interface.attachments = [logger.file("DexScript.log")] + self.installer.interface.attachments.append(logger.file("DexScript.log")) self.set_image(url=config.appearance["logo_error"]) From b9b1949324510fafc9594312be427bc5387d23c9 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Mon, 3 Feb 2025 23:10:50 -0500 Subject: [PATCH 063/133] Fix logos --- DexScript/github/installer.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/DexScript/github/installer.py b/DexScript/github/installer.py index ff2dfe3..945598c 100644 --- a/DexScript/github/installer.py +++ b/DexScript/github/installer.py @@ -127,7 +127,7 @@ def error(self): self.installer.interface.attachments.append(logger.file("DexScript.log")) - self.set_image(url=config.appearance["logo_error"]) + self.set_thumbnail(url=config.appearance["logo_error"]) def installed(self): self.title = "DexScript Installed!" @@ -138,7 +138,7 @@ def installed(self): self.color = discord.Color.from_str("#FFF" if DIR == "carfigures" else "#03BAFC") self.timestamp = datetime.now() - self.set_image(url=config.appearance["logo"]) + self.set_thumbnail(url=config.appearance["logo"]) class InstallerView(discord.ui.View): @@ -155,6 +155,7 @@ async def install_button(self, interaction: discord.Interaction, _: discord.ui.B await interaction.message.edit(**self.installer.interface.fields) try: + raise Exception("hi") await self.installer.install() except Exception: logger.log(format_exc(), "ERROR") From 82fcd1b45b5865f1cc207da375746c396982fb3c Mon Sep 17 00:00:00 2001 From: Dotsian Date: Mon, 3 Feb 2025 23:18:51 -0500 Subject: [PATCH 064/133] Remove view and fix output mistake --- DexScript/github/installer.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/DexScript/github/installer.py b/DexScript/github/installer.py index 945598c..16acb47 100644 --- a/DexScript/github/installer.py +++ b/DexScript/github/installer.py @@ -122,8 +122,8 @@ def error(self): self.color = discord.Color.red() self.timestamp = datetime.now() - if logger.log != []: - self.description += f"\n```{logger.log[-1]}```" + if logger.output != []: + self.description += f"\n```{logger.output[-1]}```" self.installer.interface.attachments.append(logger.file("DexScript.log")) @@ -165,8 +165,10 @@ async def install_button(self, interaction: discord.Interaction, _: discord.ui.B self.quit_button.disabled = True self.installer.interface.embed = InstallerEmbed(self.installer, "error") + self.installer.interface.view = None else: self.installer.interface.embed = InstallerEmbed(self.installer, "installed") + self.installer.interface.view = None await interaction.message.edit(**self.installer.interface.fields) await interaction.response.defer() @@ -202,7 +204,10 @@ def __init__(self, installer): @property def fields(self): - fields = {"embed": self.embed, "view": self.view} + fields = {"embed": self.embed} + + if self.view is not None: + fields["view"] = self.view if self.attachments != []: fields["attachments"] = self.attachments From a466a878708b6ad4e9450989776d4f302482db58 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Mon, 3 Feb 2025 23:20:33 -0500 Subject: [PATCH 065/133] Remove testing exception --- DexScript/github/installer.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/DexScript/github/installer.py b/DexScript/github/installer.py index 16acb47..2d8d2aa 100644 --- a/DexScript/github/installer.py +++ b/DexScript/github/installer.py @@ -125,7 +125,7 @@ def error(self): if logger.output != []: self.description += f"\n```{logger.output[-1]}```" - self.installer.interface.attachments.append(logger.file("DexScript.log")) + self.installer.interface.attachments = [logger.file("DexScript.log")] self.set_thumbnail(url=config.appearance["logo_error"]) @@ -155,7 +155,6 @@ async def install_button(self, interaction: discord.Interaction, _: discord.ui.B await interaction.message.edit(**self.installer.interface.fields) try: - raise Exception("hi") await self.installer.install() except Exception: logger.log(format_exc(), "ERROR") @@ -204,10 +203,7 @@ def __init__(self, installer): @property def fields(self): - fields = {"embed": self.embed} - - if self.view is not None: - fields["view"] = self.view + fields = {"embed": self.embed, "view": self.view} if self.attachments != []: fields["attachments"] = self.attachments From ca9c340045abbe826f31945864c72e98cf372d4b Mon Sep 17 00:00:00 2001 From: Dotsian Date: Mon, 3 Feb 2025 23:34:27 -0500 Subject: [PATCH 066/133] Add uninstallation and fix CF view error --- DexScript/github/installer.py | 55 ++++++++++++++++++++++++++++++----- DexScript/package/commands.py | 2 +- 2 files changed, 48 insertions(+), 9 deletions(-) diff --git a/DexScript/github/installer.py b/DexScript/github/installer.py index 2d8d2aa..bc41b2a 100644 --- a/DexScript/github/installer.py +++ b/DexScript/github/installer.py @@ -10,6 +10,7 @@ import os import re +import shutil from base64 import b64decode from dataclasses import dataclass from dataclasses import field as datafield @@ -94,6 +95,8 @@ def __init__(self, installer, embed_type="setup"): self.error() case "installed": self.installed() + case "uninstalled": + self.uninstalled() def setup(self): self.title = "DexScript Installation" @@ -140,6 +143,14 @@ def installed(self): self.set_thumbnail(url=config.appearance["logo"]) + def uninstalled(self): + self.title = "DexScript Uninstalled!" + self.description = "DexScript has been succesfully uninstalled from your bot." + self.color = discord.Color.from_str("#FFF" if DIR == "carfigures" else "#03BAFC") + self.timestamp = datetime.now() + + self.set_thumbnail(url=config.appearance["logo"]) + class InstallerView(discord.ui.View): def __init__(self, installer): @@ -159,15 +170,11 @@ async def install_button(self, interaction: discord.Interaction, _: discord.ui.B except Exception: logger.log(format_exc(), "ERROR") - self.install_button.disabled = True - self.uninstall_button.disabled = True - self.quit_button.disabled = True - self.installer.interface.embed = InstallerEmbed(self.installer, "error") - self.installer.interface.view = None else: self.installer.interface.embed = InstallerEmbed(self.installer, "installed") - self.installer.interface.view = None + + self.installer.interface.view = None await interaction.message.edit(**self.installer.interface.fields) await interaction.response.defer() @@ -176,7 +183,10 @@ async def install_button(self, interaction: discord.Interaction, _: discord.ui.B style=discord.ButtonStyle.primary, label="Uninstall", disabled=not UPDATING ) async def uninstall_button(self, interaction: discord.Interaction, _: discord.ui.Button): - # TODO: Add uninstallation + await self.installer.uninstall() + + self.installer.interface.embed = InstallerEmbed(self.installer, "uninstalled") + self.installer.interface.view = None await interaction.message.edit(**self.installer.interface.fields) await interaction.response.defer() @@ -269,7 +279,6 @@ async def install(self): continue lines[index] = new - break case MigrationType.APPEND: if line.rstrip() != original or lines[index + 1] == new: continue @@ -288,6 +297,36 @@ async def install(self): logger.log("DexScript installation finished", "INFO") + async def uninstall(self): + shutil.rmtree(config.path) + + with open(f"{DIR}/core/bot.py", "r") as read_file: + lines = read_file.readlines() + + stripped_lines = [x.rstrip() for x in lines] + + for index, line in enumerate(lines): + for migration in config.migrations: + original = self.format_migration(migration[0]) + new = self.format_migration(migration[1]) + + if line.rstrip() != new: + continue + + match migration[2]: + case MigrationType.REPLACE: + lines[index] = original + case MigrationType.APPEND: + if line.rstrip() != new: + continue + + lines.pop(stripped_lines.index(new)) + + with open(f"{DIR}/core/bot.py", "w") as write_file: + write_file.writelines(lines) + + await bot.unload_extension(config.path.replace("/", ".")) # type: ignore + @staticmethod def format_migration(line): return ( diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 9a52445..e6d43e7 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -172,7 +172,7 @@ async def view(self, ctx, model, identifier, attribute=None): if not hasattr(returned_model, attribute_name): raise Exception( - f"'{new_attribute}' is not a valid {model} attribute\n" + f"'{attribute_name}' is not a valid {model} attribute\n" f"Run `ATTRIBUTES > {model}` to see a list of all attributes for that model" ) From 694fd0b3d90d6455b724fdfc69682e71c4da5dac Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 00:00:25 -0500 Subject: [PATCH 067/133] Work on uninstaller and fix naming --- DexScript/github/installer.py | 104 +++++++++++++++++----------------- DexScript/package/commands.py | 16 +++--- 2 files changed, 59 insertions(+), 61 deletions(-) diff --git a/DexScript/github/installer.py b/DexScript/github/installer.py index bc41b2a..6f7197b 100644 --- a/DexScript/github/installer.py +++ b/DexScript/github/installer.py @@ -51,18 +51,22 @@ class InstallerConfig: "logo_error": "https://raw.githubusercontent.com/Dotsian/DexScript/refs/heads/dev/assets/DexScriptLogoError.png", "banner": "https://raw.githubusercontent.com/Dotsian/DexScript/refs/heads/dev/assets/DexScriptPromo.png" } - migrations = [ + install_migrations = [ ( "||await self.add_cog(Core(self))", '||await self.load_extension("$DIR.packages.dexscript")\n', - MigrationType.APPEND + MigrationType.APPEND, ), ( - '||await self.load_extension("$DIR.core.dexscript")', - '||await self.load_extension("$DIR.packages.dexscript")', - MigrationType.REPLACE + '||await self.load_extension("$DIR.core.dexscript")\n', + '||await self.load_extension("$DIR.packages.dexscript")\n', + MigrationType.REPLACE, ) ] + uninstall_migrations = [ + '||await self.load_extension("$DIR.core.dexscript")\n' + '||await self.load_extension("$DIR.packages.dexscript")\n' + ] path = f"{DIR}/packages/dexscript" @dataclass @@ -234,6 +238,46 @@ class Installer: def __init__(self): self.interface = InstallerGUI(self) + def uninstall_migrate(self): + with open(f"{DIR}/core/bot.py", "r") as read_file: + lines = read_file.readlines() + + for index, line in enumerate(lines): + for migration in config.uninstall_migrations: + original = self.format_migration(migration) + + if line != original: + continue + + lines.pop(index) + + with open(f"{DIR}/core/bot.py", "w") as write_file: + write_file.writelines(lines) + + def install_migrate(self): + with open(f"{DIR}/core/bot.py", "r") as read_file: + lines = read_file.readlines() + + for index, line in enumerate(lines): + for migration in config.install_migrations: + original = self.format_migration(migration[0]) + new = self.format_migration(migration[1]) + + match migration[2]: + case MigrationType.REPLACE: + if line.rstrip() != original: + continue + + lines[index] = new + case MigrationType.APPEND: + if line.rstrip() != original or new in lines: + continue + + lines.insert(index + 1, new) + + with open(f"{DIR}/core/bot.py", "w") as write_file: + write_file.writelines(lines) + async def install(self): if os.path.isfile(f"{DIR}/core/dexscript.py"): os.remove(f"{DIR}/core/dexscript.py") @@ -263,30 +307,7 @@ async def install(self): logger.log("Applying bot.py migrations", "INFO") - with open(f"{DIR}/core/bot.py", "r") as read_file: - lines = read_file.readlines() - - stripped_lines = [x.rstrip() for x in lines] - - for index, line in enumerate(lines): - for migration in config.migrations: - original = self.format_migration(migration[0]) - new = self.format_migration(migration[1]) - - match migration[2]: - case MigrationType.REPLACE: - if line.rstrip() != original: - continue - - lines[index] = new - case MigrationType.APPEND: - if line.rstrip() != original or lines[index + 1] == new: - continue - - lines.insert(stripped_lines.index(original) + 1, new) - - with open(f"{DIR}/core/bot.py", "w") as write_file: - write_file.writelines(lines) + self.migrate() logger.log("Loading DexScript extension", "INFO") @@ -300,30 +321,7 @@ async def install(self): async def uninstall(self): shutil.rmtree(config.path) - with open(f"{DIR}/core/bot.py", "r") as read_file: - lines = read_file.readlines() - - stripped_lines = [x.rstrip() for x in lines] - - for index, line in enumerate(lines): - for migration in config.migrations: - original = self.format_migration(migration[0]) - new = self.format_migration(migration[1]) - - if line.rstrip() != new: - continue - - match migration[2]: - case MigrationType.REPLACE: - lines[index] = original - case MigrationType.APPEND: - if line.rstrip() != new: - continue - - lines.pop(stripped_lines.index(new)) - - with open(f"{DIR}/core/bot.py", "w") as write_file: - write_file.writelines(lines) + self.uninstall_migrate() await bot.unload_extension(config.path.replace("/", ".")) # type: ignore diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index e6d43e7..fc6221c 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -125,8 +125,8 @@ async def update(self, ctx, model, identifier, attribute, value=None): if not hasattr(returned_model, attribute_name): raise Exception( - f"'{attribute_name}' is not a valid {model} attribute\n" - f"Run `ATTRIBUTES > {model}` to see a list of all attributes for that model" + f"'{attribute_name}' is not a valid {model.name.__name__} attribute\n" + f"Run `ATTRIBUTES > {model.name.__name__}` to see a list of all attributes for that model" ) if value is None: @@ -172,8 +172,8 @@ async def view(self, ctx, model, identifier, attribute=None): if not hasattr(returned_model, attribute_name): raise Exception( - f"'{attribute_name}' is not a valid {model} attribute\n" - f"Run `ATTRIBUTES > {model}` to see a list of all attributes for that model" + f"'{attribute_name}' is not a valid {model.name.__name__} attribute\n" + f"Run `ATTRIBUTES > {model.name.__name__}` to see a list of all attributes for that model" ) new_attribute = getattr(returned_model, attribute_name) @@ -225,8 +225,8 @@ async def update(self, ctx, model, attribute, old_value, new_value, tortoise_ope if not hasattr(model.name, casing_name): raise Exception( - f"'{casing_name}' is not a valid {model} attribute\n" - f"Run `ATTRIBUTES > {model}` to see a list of all attributes for that model" + f"'{casing_name}' is not a valid {model.name.__name__} attribute\n" + f"Run `ATTRIBUTES > {model.name.__name__}` to see a list of all attributes for that model" ) if tortoise_operator is not None: @@ -255,8 +255,8 @@ async def delete(self, ctx, model, attribute, value, tortoise_operator=None): if not hasattr(model.name, casing_name): raise Exception( - f"'{casing_name}' is not a valid {model} attribute\n" - f"Run `ATTRIBUTES > {model}` to see a list of all attributes for that model" + f"'{casing_name}' is not a valid {model.name.__name__} attribute\n" + f"Run `ATTRIBUTES > {model.name.__name__}` to see a list of all attributes for that model" ) if tortoise_operator is not None: From 7ec5c8047e61c4748b464390e97847b254219ccc Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 08:19:55 -0500 Subject: [PATCH 068/133] Fix small error and checks --- DexScript/github/installer.py | 2 +- DexScript/package/commands.py | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/DexScript/github/installer.py b/DexScript/github/installer.py index 6f7197b..34cebd7 100644 --- a/DexScript/github/installer.py +++ b/DexScript/github/installer.py @@ -64,7 +64,7 @@ class InstallerConfig: ) ] uninstall_migrations = [ - '||await self.load_extension("$DIR.core.dexscript")\n' + '||await self.load_extension("$DIR.core.dexscript")\n', '||await self.load_extension("$DIR.packages.dexscript")\n' ] path = f"{DIR}/packages/dexscript" diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index fc6221c..2c1fcdd 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -126,7 +126,8 @@ async def update(self, ctx, model, identifier, attribute, value=None): if not hasattr(returned_model, attribute_name): raise Exception( f"'{attribute_name}' is not a valid {model.name.__name__} attribute\n" - f"Run `ATTRIBUTES > {model.name.__name__}` to see a list of all attributes for that model" + f"Run `ATTRIBUTES > {model.name.__name__}` to see a list of " + "all attributes for that model" ) if value is None: @@ -173,7 +174,8 @@ async def view(self, ctx, model, identifier, attribute=None): if not hasattr(returned_model, attribute_name): raise Exception( f"'{attribute_name}' is not a valid {model.name.__name__} attribute\n" - f"Run `ATTRIBUTES > {model.name.__name__}` to see a list of all attributes for that model" + f"Run `ATTRIBUTES > {model.name.__name__}` to see a list of " + "all attributes for that model" ) new_attribute = getattr(returned_model, attribute_name) @@ -226,7 +228,8 @@ async def update(self, ctx, model, attribute, old_value, new_value, tortoise_ope if not hasattr(model.name, casing_name): raise Exception( f"'{casing_name}' is not a valid {model.name.__name__} attribute\n" - f"Run `ATTRIBUTES > {model.name.__name__}` to see a list of all attributes for that model" + f"Run `ATTRIBUTES > {model.name.__name__}` to see a list of " + "all attributes for that model" ) if tortoise_operator is not None: @@ -256,7 +259,8 @@ async def delete(self, ctx, model, attribute, value, tortoise_operator=None): if not hasattr(model.name, casing_name): raise Exception( f"'{casing_name}' is not a valid {model.name.__name__} attribute\n" - f"Run `ATTRIBUTES > {model.name.__name__}` to see a list of all attributes for that model" + f"Run `ATTRIBUTES > {model.name.__name__}` to see a list of " + "all attributes for that model" ) if tortoise_operator is not None: From aa3350d09b40e524b0faa40a6d52879f1d67ac32 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 10:19:19 -0500 Subject: [PATCH 069/133] Finish insaller, format code, work on README.md, and add DATAPOLICY.md --- .github/ISSUE_TEMPLATE/bug_report.yml | 18 +- DATAPOLICY.md | 20 ++ DexScript/github/__EXCLUDED__/installer.py | 188 ------------------- DexScript/github/__EXCLUDED__/uninstaller.py | 79 -------- DexScript/github/installer.py | 2 +- DexScript/package/cog.py | 31 ++- DexScript/package/commands.py | 8 +- README.md | 34 +++- 8 files changed, 92 insertions(+), 288 deletions(-) create mode 100644 DATAPOLICY.md delete mode 100644 DexScript/github/__EXCLUDED__/installer.py delete mode 100644 DexScript/github/__EXCLUDED__/uninstaller.py diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 9a1c7ca..5bb707e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -14,11 +14,27 @@ body: options: - label: Did you read the error page and view the common errors? required: true + - type: dropdown + id: location + attributes: + label: Bug Location + description: Where did this bug occur? If you select "Other", please specify in the Bug Description section. + options: + - While installing. + - While updating. + - While uninstalling. + - When running a command. + - When trying to run a command. + - When using a text command, such as ".about" or ".installer". + - When trying to turn on the bot. + - Other (please specify in the Bug Description section) + validations: + required: true - type: textarea id: description attributes: label: Bug Description - description: Explain what the bug was and where it occurred. + description: Explain the bug and what happened before it appeared. placeholder: Bug explanation... validations: required: true diff --git a/DATAPOLICY.md b/DATAPOLICY.md new file mode 100644 index 0000000..c55aafb --- /dev/null +++ b/DATAPOLICY.md @@ -0,0 +1,20 @@ +# Data Policy + +**"We"** refers to the DexScript team, consisting of DotZZ (dot_zz on Discord). This policy explains how DexScript interacts with your application’s data and files. + +## Data Interaction + +We do not collect, store, or access any data from your application. DexScript only interacts with your application’s data for functionality, and we do not have access to your bot's data, nor do we operate DexScript's commands. + +## File Modifications + +During the DexScript installation process, the following changes will be made to your application's files: + +- If you are using **BallsDex**, DexScript will modify your `ballsdex/core/bot.py` file. +- If you are using **CarFigures**, DexScript will modify your `carfigures/core/bot.py` file. + +DexScript will add a single line of code to allow the DexScript extension to load when your application starts. This modification is solely for the purpose of running DexScript. You can view the code added by checking the `DexScript/github/installer.py` file in the official DexScript GitHub repository. + +## Contact Information + +For any questions, comments, or concerns regarding this data policy, please contact DotZZ on Discord (dot_zz). diff --git a/DexScript/github/__EXCLUDED__/installer.py b/DexScript/github/__EXCLUDED__/installer.py deleted file mode 100644 index 3d8a666..0000000 --- a/DexScript/github/__EXCLUDED__/installer.py +++ /dev/null @@ -1,188 +0,0 @@ -# # # # # # # # # # # # # # # # # # # # # # # # # # # # -# OFFICIAL DEXSCRIPT INSTALLER # -# # -# This will install DexScript onto your bot. # -# For additional information, read the wiki guide. # -# An explanation of the code will be provided below. # -# # -# THIS CODE IS RAN VIA THE `EVAL` COMMAND. # -# # # # # # # # # # # # # # # # # # # # # # # # # # # # - - -from base64 import b64decode -from dataclasses import dataclass -from datetime import datetime -from io import StringIO -from os import path -from time import time -from traceback import format_exc - -from requests import codes, get - -DIR = "ballsdex" if path.isdir("ballsdex") else "carfigures" - -if DIR == "ballsdex": - from ballsdex.settings import settings -else: - from carfigures.settings import settings - - -@dataclass -class InstallerConfig: - """ - Configuration class for the installer. - """ - - github = ["Dotsian/DexScript", "dev"] - migrations = [ - ( - "¶¶await self.add_cog(Core(self))", - '¶¶await self.load_extension("$DIR.packages.dexscript")\n', - ) - ] - - -config = InstallerConfig() - - -class Installer: - def __init__(self): - self.message = None - self.managed_time = None - - self.keywords = ["Installed", "Installing", "Install"] - self.updating = path.isdir(f"{DIR}/packages/dexscript") - - if self.updating: - self.keywords = ["Updated", "Updating", "Update"] - - self.embed = discord.Embed( - title=f"{self.keywords[1]} DexScript", - description=( - f"DexScript is being {self.keywords[0].lower()} to your bot.\n" - "Please do not turn off your bot." - ), - color=discord.Color.from_str("#FFF" if DIR == "carfigures" else "#03BAFC"), - timestamp=datetime.now(), - ) - - self.embed.set_thumbnail(url="https://i.imgur.com/uKfx0qO.png") - - @staticmethod - def format_migration(line): - return ( - line.replace(" ", "").replace("¶", " ").replace("/n", "\n").replace("$DIR", DIR) - ) - - async def error(self, error, exception=False): - self.embed.title = "DexScript ERROR" - - description = ( - f"Please submit a [bug report]" - f"() to the GitHub page" - ) - - if exception: - description += " and attach the file below." - else: - description += f".\n```{error}```" - - self.embed.description = description - self.embed.color = discord.Color.red() - - fields = {"embed": self.embed} - - if exception: - fields["attachments"] = [discord.File(StringIO(error), filename="DexScript.log")] - - await self.message.edit(**fields) - - async def run(self, ctx): - """ - Installs or updates the latest DexScript version. - - - Fetches the contents of the `dexscript.py` file from the official DexScript repository, - and writes that content onto a local `dexscript.py` file. - - - Apply migrations from the `config.migrations` list onto the `bot.py` file to allow - DexScript to load on bot startup. - - - Load or reload the DexScript extension. - """ - self.message = await ctx.send(embed=self.embed) - self.managed_time = time() - - link = f"https://api.github.com/repos/{config.github[0]}/contents/" - - request = get(f"{link}/dexscript.py", {"ref": config.github[1]}) - - if request.status_code != codes.ok: - await self.error( - "Failed to fetch the `dexscript.py` file. " - f"Recieved request status code `{request.status_code}`." - ) - return - - request = request.json() - content = b64decode(request["content"]) - - with open(f"{DIR}/core/dexscript.py", "w") as opened_file: - opened_file.write(content.decode("UTF-8")) - - with open(f"{DIR}/core/bot.py", "r") as read_file: - lines = read_file.readlines() - - stripped_lines = [x.rstrip() for x in lines] - - for index, line in enumerate(lines): - for migration in config.migrations: - original = self.format_migration(migration[0]) - new = self.format_migration(migration[1]) - - if line.rstrip() != original or lines[index + 1] == new: - continue - - lines.insert(stripped_lines.index(original) + 1, new) - - with open(f"{DIR}/core/bot.py", "w") as write_file: - write_file.writelines(lines) - - try: - await bot.load_extension(f"{DIR}.core.dexscript") - except commands.ExtensionAlreadyLoaded: - await bot.reload_extension(f"{DIR}.core.dexscript") - - if self.updating: - request = get(f"{link}/version.txt", {"ref": config.github[1]}) - - new_version = b64decode(request.json()["content"]).decode("UTF-8").rstrip() - - self.embed.description = ( - f"DexScript has been updated to v{new_version}.\n" - f"Use `{settings.prefix}about` to view details about DexScript." - ) - else: - self.embed.description = ( - "DexScript has been installed to your bot\n" - f"Use `{settings.prefix}about` to view details about DexScript." - ) - - self.embed.set_footer( - text=f"DexScript took {round((time() - self.managed_time) * 1000)}ms " - f"to {self.keywords[2].lower()}" - ) - - await self.message.edit(embed=self.embed) - - -installer = Installer() - -try: - await installer.run(ctx) -except Exception: - installer.embed.set_footer( - text=f"Error occurred {round((time() - installer.managed_time) * 1000)}ms " - f"into {installer.keywords[1].lower()}" - ) - - await installer.error(format_exc(), True) diff --git a/DexScript/github/__EXCLUDED__/uninstaller.py b/DexScript/github/__EXCLUDED__/uninstaller.py deleted file mode 100644 index 4fe722c..0000000 --- a/DexScript/github/__EXCLUDED__/uninstaller.py +++ /dev/null @@ -1,79 +0,0 @@ -# OLD UNINSTALLER -# SUBJECT TO CHANGE - -import datetime -import os -import time - -dir_type = "ballsdex" if os.path.isdir("ballsdex") else "carfigures" - -t1 = time.time() - -embed = discord.Embed( - title="Removing DexScript", - description="DexScript is being removed from your bot.\nPlease do not turn off your bot.", - color=discord.Color.red(), - timestamp=datetime.datetime.now(), -) - -embed.set_thumbnail(url="https://i.imgur.com/uKfx0qO.png") - -original_message = await ctx.send(embed=embed) - - -async def uninstall(): - if os.path.isfile(f"{dir_type}/core/dexscript.py"): - os.remove(f"{dir_type}/core/dexscript.py") - - exclude = [ - f"from {dir_type}.core.dexscript import DexScript", - " await self.add_cog(DexScript(self))", - f' await self.load_extension("{dir_type}.core.dexscript")', - ] - - # Add the ability to remove DexScript-related code from the bot.py file. - with open(f"{dir_type}/core/bot.py", "r") as opened_file_1: - lines = opened_file_1.readlines() - content = "" - - for line in lines: - if line.rstrip() in exclude: - continue - - content += line - - with open(f"{dir_type}/core/bot.py", "w") as opened_file_2: - opened_file_2.write(content) - - await bot.remove_cog("DexScript") - - -try: - await uninstall() -except Exception as e: - link = "" - - embed.title = "DexScript ERROR" - embed.description = ( - "DexScript failed to uninstall.\n" - f"Please submit a [bug report]({link}) on the GitHub page.\n\n" - f"```\nERROR: {e}\n```" - ) - - embed.set_footer( - text=f"Error occurred {round((time.time() - t1) * 1000)}ms into uninstallation" - ) - - await original_message.edit(embed=embed) - return - -t2 = time.time() - -embed.title = "DexScript Removed" -embed.description = ( - "DexScript has been removed from your bot.\n" - "All DexScript-related commands have been removed." -) -embed.set_footer(text=f"DexScript took {round((t2 - t1) * 1000)}ms to uninstall") - -await original_message.edit(embed=embed) diff --git a/DexScript/github/installer.py b/DexScript/github/installer.py index 34cebd7..88de7a6 100644 --- a/DexScript/github/installer.py +++ b/DexScript/github/installer.py @@ -307,7 +307,7 @@ async def install(self): logger.log("Applying bot.py migrations", "INFO") - self.migrate() + self.install_migrate() logger.log("Loading DexScript extension", "INFO") diff --git a/DexScript/package/cog.py b/DexScript/package/cog.py index 0fa179e..6e68261 100644 --- a/DexScript/package/cog.py +++ b/DexScript/package/cog.py @@ -31,15 +31,15 @@ def check_version(): if not config.versioncheck: return None - r = requests.get( + request = requests.get( "https://api.github.com/repos/Dotsian/DexScript/contents/pyproject.toml", {"ref": config.reference}, ) - if r.status_code != requests.codes.ok: + if request.status_code != requests.codes.ok: return - toml_content = base64.b64decode(r.json()["content"]).decode("UTF-8") + toml_content = base64.b64decode(request.json()["content"]).decode() new_version = re.search(r'version\s*=\s*"(.*?)"', toml_content).group(1) if new_version != __version__: @@ -100,9 +100,8 @@ async def about(self, ctx: commands.Context): "It simplifies editing, adding, deleting, and displaying data for models such as " "balls, regimes, specials, etc.\n\n" f"Refer to the official [DexScript guide](<{guide_link}>) for information " - f"about DexScript's functionality or use `{settings.prefix}run HELP` to display " - "a list of all commands and what they do.\n" - f"To update DexScript, run `{settings.prefix}upgrade`.\n\n" + f"about DexScript's functionality\n" + f"To update or uninstall DexScript, run `{settings.prefix}installer`.\n\n" "If you want to follow DexScript or require assistance, join the official " f"[DexScript Discord server](<{discord_link}>)." ) @@ -124,11 +123,23 @@ async def about(self, ctx: commands.Context): @commands.command() @commands.is_owner() - async def upgrade(self, ctx: commands.Context): + async def installer(self, ctx: commands.Context): link = "https://api.github.com/repos/Dotsian/DexScript/contents/DexScript/github/installer.py" - content = requests.get(link, {"ref": config.reference}).json()["content"] - - await ctx.invoke(self.bot.get_command("eval"), body=base64.b64decode(content).decode()) + request = requests.get(link, {"ref": config.reference}) + + match request.status_code: + case requests.codes.not_found: + await ctx.send(f"Could not find installer for the {config.reference} branch.") + + case requests.codes.ok: + content = requests.get(link, {"ref": config.reference}).json()["content"] + + await ctx.invoke( + self.bot.get_command("eval"), body=base64.b64decode(content).decode() + ) + + case _: + await ctx.send(f"Request raised error code `{request.status_code}`.") @commands.command() @commands.is_owner() diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 2c1fcdd..5a13f07 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -74,7 +74,7 @@ class Global(DexCommand): Main methods for DexScript. """ - async def help(self, ctx): + # async def help(self, ctx): """ Lists all DexScript commands and provides documentation for them. @@ -83,7 +83,6 @@ async def help(self, ctx): HELP """ # getattr(Methods, command).__doc__.replace("\n", "").split("Documentation-------------") - pass async def create(self, ctx, model, identifier): """ @@ -94,7 +93,6 @@ async def create(self, ctx, model, identifier): CREATE > MODEL > IDENTIFIER """ await self.create_model(model.name, identifier) - await ctx.send(f"Created `{identifier}`") async def delete(self, ctx, model, identifier): @@ -106,7 +104,6 @@ async def delete(self, ctx, model, identifier): DELETE > MODEL > IDENTIFIER """ await self.get_model(model, identifier.name).delete() - await ctx.send(f"Deleted `{identifier}`") async def update(self, ctx, model, identifier, attribute, value=None): @@ -211,8 +208,7 @@ class Filter(DexCommand): """ Filter commands used for mass updating, deleting, and viewing models. """ - - # TODO: Add attachment support. + async def update(self, ctx, model, attribute, old_value, new_value, tortoise_operator=None): """ Updates all instances of a model to the specified value where the specified attribute diff --git a/README.md b/README.md index 6dcbc5a..2b0c016 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,8 @@ DexScript has a ton more features too! All of them can be found within our exten * **Mass updating and deleting Balls, Regimes, Specials, etc.** * **Saving evals and loading them**. +DexScript is currently in beta. However, the latest version is a release candidate for the full release. + ## DexScript Requirements To install DexScript, you must have the following: @@ -27,7 +29,11 @@ To install DexScript, you must have the following: * Ballsdex or CarFigures v2.2.0+ * Eval access -## Installing DexScript +## DexScript Setup + +The DexScript installer is a intuitive menu that can allow you to easily update, install, and uninstall DexScript. To bring up the DexScript installer, all you have to do is run one eval command! + +### Versions DexScript has two versions, the release version and the development version. @@ -47,6 +53,28 @@ import base64, requests; await ctx.invoke(bot.get_command("eval"), body=base64.b import base64, requests; await ctx.invoke(bot.get_command("eval"), body=base64.b64decode(requests.get("https://api.github.com/repos/Dotsian/DexScript/contents/DexScript/github/installer.py", {"ref": "dev"}).json()["content"]).decode()) ``` -## Updating +### DexScript Installer + +> [!NOTE] +> If DexScript is already installed, you can run `b.installer` to show the DexScript installer, replacing `b.` with your application's prefix.. + +Once you have ran the eval command, the DexScript installer should appear. There will be three buttons: + +* Install [or] Update +* Uninstall +* Exit + +> [!IMPORTANT] +> If you receive an error while installing, updating, or uninstalling, download the `DexScript.log` file and submit a bug report in this GitHub repository. + +#### Installing + +If you are installing DexScript for the first time, you will see a button called "Install". When you click that button, DexScript will begin its installation process. + +#### Updating + +If you already have DexScript, you will see a button called "Update". When you click that button, DexScript will update to the latest version. + +#### Uninstall -The command above will automatically update DexScript, however, if you already have DexScript, you can run `b.upgrade` to update DexScript, replacing `b.` with your bot's prefix. +If you already have DexScript, you will see a button called "Uninstall". Clicking the uninstall button will uninstall DexScript from your application. This will instantly remove the DexScript package and DexScript commands will unload instantly. From 683037789d3df5ae3d0e89396e54e12ddd4539b3 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 10:22:05 -0500 Subject: [PATCH 070/133] Remove HELP command temporarily --- DexScript/package/commands.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 5a13f07..b23581c 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -73,17 +73,7 @@ class Global(DexCommand): """ Main methods for DexScript. """ - - # async def help(self, ctx): - """ - Lists all DexScript commands and provides documentation for them. - - Documentation - ------------- - HELP - """ - # getattr(Methods, command).__doc__.replace("\n", "").split("Documentation-------------") - + async def create(self, ctx, model, identifier): """ Creates a model instance. From cffb5a5b0d4368eb0de07fa68f65a803f151e8d5 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 10:26:22 -0500 Subject: [PATCH 071/133] Fix syntax --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2b0c016..336a161 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ DexScript is a set of commands for Ballsdex and CarFigures created by DotZZ that expands on the standalone admin commands and substitutes for the admin panel. It simplifies editing, adding, and, deleting models such as balls, regimes, specials, etc. -Let's say you wanted to update a ball's rarity to 2. You could run `UPDATE > BALL > Mongolia > RARITY > 2.0`. +Let's say you wanted to update a ball's rarity to 2. You could run `UPDATE > Ball > Mongolia > RARITY > 2.0`. ![Updating rarity showcase](assets/screenshots/showcase1.png) @@ -69,11 +69,11 @@ Once you have ran the eval command, the DexScript installer should appear. There #### Installing -If you are installing DexScript for the first time, you will see a button called "Install". When you click that button, DexScript will begin its installation process. +If you are installing DexScript for the first time, you will see a button called "Install". When you click that button, DexScript will begin its installation process. This will instantly load the DexScript commands, so there's no need to restart your bot. #### Updating -If you already have DexScript, you will see a button called "Update". When you click that button, DexScript will update to the latest version. +If you already have DexScript, you will see a button called "Update". When you click that button, DexScript will update to the latest version. This will instantly update DexScript, which means you don't have to restart your bot. #### Uninstall From 8c75956a4eba7802c268e875a057a16749cdfcd7 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 10:53:05 -0500 Subject: [PATCH 072/133] Prepare for the v0.5 update --- DexScript/github/installer.py | 3 +++ DexScript/package/parser.py | 5 ++++- version.txt | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 version.txt diff --git a/DexScript/github/installer.py b/DexScript/github/installer.py index 88de7a6..4fdffdd 100644 --- a/DexScript/github/installer.py +++ b/DexScript/github/installer.py @@ -319,6 +319,9 @@ async def install(self): logger.log("DexScript installation finished", "INFO") async def uninstall(self): + if os.path.isfile(f"{DIR}/core/dexscript.py"): + os.remove(f"{DIR}/core/dexscript.py") + shutil.rmtree(config.path) self.uninstall_migrate() diff --git a/DexScript/package/parser.py b/DexScript/package/parser.py index 241c6d4..9cef2e4 100644 --- a/DexScript/package/parser.py +++ b/DexScript/package/parser.py @@ -103,7 +103,7 @@ def create_value(self, line): def error(self, message, log): return (message, log)[config.debug] - async def execute(self, code: str): + async def execute(self, code: str, run_commands=True): split_code = [x for x in code.split("\n") if x.strip() != ""] parsed_code = [ @@ -112,6 +112,9 @@ async def execute(self, code: str): if not line.strip().startswith("--") ] + if not run_commands: + return parsed_code + for line2 in parsed_code: if line2 == []: continue diff --git a/version.txt b/version.txt new file mode 100644 index 0000000..ea2303b --- /dev/null +++ b/version.txt @@ -0,0 +1 @@ +0.5 \ No newline at end of file From 1682381440736a94b41f48b722d432f49e90ab82 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 11:16:43 -0500 Subject: [PATCH 073/133] Automatically use model identifier when referencing a model --- DexScript/package/commands.py | 25 ++++++++++++++++++++----- DexScript/package/parser.py | 12 +----------- DexScript/package/utils.py | 9 +++++++++ 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index b23581c..65a661f 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -6,7 +6,7 @@ import discord import requests -from .utils import DIR, MEDIA_PATH, Utils +from .utils import DIR, MEDIA_PATH, Types, Utils class DexCommand: @@ -73,7 +73,7 @@ class Global(DexCommand): """ Main methods for DexScript. """ - + async def create(self, ctx, model, identifier): """ Creates a model instance. @@ -121,6 +121,9 @@ async def update(self, ctx, model, identifier, attribute, value=None): image_path = await Utils.save_file(ctx.message.attachments[0]) new_value = f"{MEDIA_PATH}/{image_path}" + if attribute.type == Types.MODEL: + new_value = self.get_model(attribute, new_value) + await returned_model.update(**{attribute_name: new_value}) await ctx.send(f"Updated `{identifier}'s` {attribute} to `{new_value}`") @@ -221,8 +224,15 @@ async def update(self, ctx, model, attribute, old_value, new_value, tortoise_ope if tortoise_operator is not None: casing_name += f"__{tortoise_operator.name.lower()}" - await model.name.filter(**{casing_name: old_value.name}).update( - **{casing_name: new_value.name} + value_old = old_value.name + value_new = new_value.name + + if attribute.type == Types.MODEL: + value_old = self.get_model(attribute, value_old) + value_new = self.get_model(attribute, value_new) + + await model.name.filter(**{casing_name: value_old}).update( + **{casing_name: value_new} ) await ctx.send( @@ -252,7 +262,12 @@ async def delete(self, ctx, model, attribute, value, tortoise_operator=None): if tortoise_operator is not None: casing_name += f"__{tortoise_operator.name.lower()}" - await model.name.filter(**{casing_name: value.name}).delete() + new_value = value.name + + if attribute.type == Types.MODEL: + new_value = self.get_model(attribute, new_value) + + await model.name.filter(**{casing_name: new_value}).delete() await ctx.send( f"Deleted all `{model}` instances with a " f"`{attribute}` value of `{value}`" diff --git a/DexScript/package/parser.py b/DexScript/package/parser.py index 9cef2e4..b82902c 100644 --- a/DexScript/package/parser.py +++ b/DexScript/package/parser.py @@ -3,22 +3,12 @@ import traceback from dataclasses import dataclass from dataclasses import field as datafield -from enum import Enum from typing import Any from dateutil.parser import parse as parse_date from . import commands -from .utils import DIR, Utils, config - - -class Types(Enum): - DEFAULT = 0 - METHOD = 1 - CLASS = 2 - BOOLEAN = 3 - MODEL = 4 - DATETIME = 5 +from .utils import DIR, Types, Utils, config @dataclass diff --git a/DexScript/package/utils.py b/DexScript/package/utils.py index 1f0439c..201e247 100644 --- a/DexScript/package/utils.py +++ b/DexScript/package/utils.py @@ -3,6 +3,7 @@ import re from dataclasses import dataclass from difflib import get_close_matches +from enum import Enum from pathlib import Path import discord @@ -21,6 +22,14 @@ MEDIA_PATH = "./admin_panel/media" if os.path.isdir("./admin_panel/media") else "/static/uploads" +class Types(Enum): + DEFAULT = 0 + METHOD = 1 + CLASS = 2 + BOOLEAN = 3 + MODEL = 4 + DATETIME = 5 + @dataclass class Settings: """ From 517e63515ec4644a492facb7a98c0a1ca1a5281c Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 11:22:51 -0500 Subject: [PATCH 074/133] Fix model name --- DexScript/package/commands.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 65a661f..8735626 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -105,7 +105,9 @@ async def update(self, ctx, model, identifier, attribute, value=None): ------------- UPDATE > MODEL > IDENTIFIER > ATTRIBUTE > VALUE(?) """ - attribute_name = Utils.casing(attribute.name) + _attr_name = attrbute.name.__name__ if attribute.type == Types.MODEL else attribute.name + + attribute_name = Utils.casing(_attr_name.lower()) new_value = Utils.casing(value.name) if value is not None else None returned_model = self.get_model(model, identifier.name) @@ -158,8 +160,10 @@ async def view(self, ctx, model, identifier, attribute=None): await ctx.send(**fields) return + + _attr_name = attrbute.name.__name__ if attribute.type == Types.MODEL else attribute.name - attribute_name = Utils.casing(attribute.name.lower()) + attribute_name = Utils.casing(_attr_name.lower()) if not hasattr(returned_model, attribute_name): raise Exception( @@ -212,7 +216,8 @@ async def update(self, ctx, model, attribute, old_value, new_value, tortoise_ope ------------- FILTER > UPDATE > MODEL > ATTRIBUTE > OLD_VALUE > NEW_VALUE > TORTOISE_OPERATOR(?) """ - casing_name = Utils.casing(attribute.name.lower()) + _attr_name = attrbute.name.__name__ if attribute.type == Types.MODEL else attribute.name + casing_name = Utils.casing(_attr_name.lower()) if not hasattr(model.name, casing_name): raise Exception( @@ -250,7 +255,9 @@ async def delete(self, ctx, model, attribute, value, tortoise_operator=None): ------------- FILTER > DELETE > MODEL > ATTRIBUTE > VALUE > TORTOISE_OPERATOR(?) """ - casing_name = Utils.casing(attribute.name.lower()) + _attr_name = attrbute.name.__name__ if attribute.type == Types.MODEL else attribute.name + + casing_name = Utils.casing(_attr_name.lower()) if not hasattr(model.name, casing_name): raise Exception( From 21f15ddedb21fe56fa631aa78c126159b5dc8bc4 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 11:25:04 -0500 Subject: [PATCH 075/133] Fix typos --- DexScript/package/commands.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 8735626..5e0236b 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -105,7 +105,7 @@ async def update(self, ctx, model, identifier, attribute, value=None): ------------- UPDATE > MODEL > IDENTIFIER > ATTRIBUTE > VALUE(?) """ - _attr_name = attrbute.name.__name__ if attribute.type == Types.MODEL else attribute.name + _attr_name = attribute.name.__name__ if attribute.type == Types.MODEL else attribute.name attribute_name = Utils.casing(_attr_name.lower()) new_value = Utils.casing(value.name) if value is not None else None @@ -161,7 +161,7 @@ async def view(self, ctx, model, identifier, attribute=None): await ctx.send(**fields) return - _attr_name = attrbute.name.__name__ if attribute.type == Types.MODEL else attribute.name + _attr_name = attribute.name.__name__ if attribute.type == Types.MODEL else attribute.name attribute_name = Utils.casing(_attr_name.lower()) @@ -216,7 +216,7 @@ async def update(self, ctx, model, attribute, old_value, new_value, tortoise_ope ------------- FILTER > UPDATE > MODEL > ATTRIBUTE > OLD_VALUE > NEW_VALUE > TORTOISE_OPERATOR(?) """ - _attr_name = attrbute.name.__name__ if attribute.type == Types.MODEL else attribute.name + _attr_name = attribute.name.__name__ if attribute.type == Types.MODEL else attribute.name casing_name = Utils.casing(_attr_name.lower()) if not hasattr(model.name, casing_name): @@ -255,7 +255,7 @@ async def delete(self, ctx, model, attribute, value, tortoise_operator=None): ------------- FILTER > DELETE > MODEL > ATTRIBUTE > VALUE > TORTOISE_OPERATOR(?) """ - _attr_name = attrbute.name.__name__ if attribute.type == Types.MODEL else attribute.name + _attr_name = attribute.name.__name__ if attribute.type == Types.MODEL else attribute.name casing_name = Utils.casing(_attr_name.lower()) From 872ae3f6de0d9930db7b9f141fd789d1ab8fdf9d Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 11:33:39 -0500 Subject: [PATCH 076/133] Fix view method returning QuerySet --- DexScript/package/commands.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 5e0236b..74354b4 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -178,6 +178,9 @@ async def view(self, ctx, model, identifier, attribute=None): await ctx.send(f"```{new_attribute}```", file=discord.File(new_attribute[1:])) return + if attribute.type == Types.MODEL: + new_attribute = await new_attribute.values_list(attribute.extra_data[0], flat=True) + await ctx.send(f"```{new_attribute}```") async def attributes(self, ctx, model): From f64efccb8d3b610da984ce6eb208cf91a922043f Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 11:45:26 -0500 Subject: [PATCH 077/133] Fix attribute exception --- DexScript/package/commands.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 74354b4..18ce87e 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -112,7 +112,7 @@ async def update(self, ctx, model, identifier, attribute, value=None): returned_model = self.get_model(model, identifier.name) - if not hasattr(returned_model, attribute_name): + if not hasattr(model.name, attribute_name): raise Exception( f"'{attribute_name}' is not a valid {model.name.__name__} attribute\n" f"Run `ATTRIBUTES > {model.name.__name__}` to see a list of " @@ -165,7 +165,7 @@ async def view(self, ctx, model, identifier, attribute=None): attribute_name = Utils.casing(_attr_name.lower()) - if not hasattr(returned_model, attribute_name): + if not hasattr(model.name, attribute_name): raise Exception( f"'{attribute_name}' is not a valid {model.name.__name__} attribute\n" f"Run `ATTRIBUTES > {model.name.__name__}` to see a list of " From 595aa62e66466f52937fa073173874663c177a16 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 11:47:23 -0500 Subject: [PATCH 078/133] Fix model attribute error #2 --- DexScript/package/commands.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 18ce87e..ac384bf 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -112,7 +112,7 @@ async def update(self, ctx, model, identifier, attribute, value=None): returned_model = self.get_model(model, identifier.name) - if not hasattr(model.name, attribute_name): + if not hasattr(model.name(), attribute_name): raise Exception( f"'{attribute_name}' is not a valid {model.name.__name__} attribute\n" f"Run `ATTRIBUTES > {model.name.__name__}` to see a list of " @@ -165,7 +165,7 @@ async def view(self, ctx, model, identifier, attribute=None): attribute_name = Utils.casing(_attr_name.lower()) - if not hasattr(model.name, attribute_name): + if not hasattr(model.name(), attribute_name): raise Exception( f"'{attribute_name}' is not a valid {model.name.__name__} attribute\n" f"Run `ATTRIBUTES > {model.name.__name__}` to see a list of " @@ -222,7 +222,7 @@ async def update(self, ctx, model, attribute, old_value, new_value, tortoise_ope _attr_name = attribute.name.__name__ if attribute.type == Types.MODEL else attribute.name casing_name = Utils.casing(_attr_name.lower()) - if not hasattr(model.name, casing_name): + if not hasattr(model.name(), casing_name): raise Exception( f"'{casing_name}' is not a valid {model.name.__name__} attribute\n" f"Run `ATTRIBUTES > {model.name.__name__}` to see a list of " @@ -262,7 +262,7 @@ async def delete(self, ctx, model, attribute, value, tortoise_operator=None): casing_name = Utils.casing(_attr_name.lower()) - if not hasattr(model.name, casing_name): + if not hasattr(model.name(), casing_name): raise Exception( f"'{casing_name}' is not a valid {model.name.__name__} attribute\n" f"Run `ATTRIBUTES > {model.name.__name__}` to see a list of " From fee00868c8f5542e22a811e8e78aa21c648a9397 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 11:50:04 -0500 Subject: [PATCH 079/133] Add await --- DexScript/package/commands.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index ac384bf..e6cbcd6 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -110,7 +110,7 @@ async def update(self, ctx, model, identifier, attribute, value=None): attribute_name = Utils.casing(_attr_name.lower()) new_value = Utils.casing(value.name) if value is not None else None - returned_model = self.get_model(model, identifier.name) + returned_model = await self.get_model(model, identifier.name) if not hasattr(model.name(), attribute_name): raise Exception( @@ -124,7 +124,7 @@ async def update(self, ctx, model, identifier, attribute, value=None): new_value = f"{MEDIA_PATH}/{image_path}" if attribute.type == Types.MODEL: - new_value = self.get_model(attribute, new_value) + new_value = await self.get_model(attribute, new_value) await returned_model.update(**{attribute_name: new_value}) @@ -236,8 +236,8 @@ async def update(self, ctx, model, attribute, old_value, new_value, tortoise_ope value_new = new_value.name if attribute.type == Types.MODEL: - value_old = self.get_model(attribute, value_old) - value_new = self.get_model(attribute, value_new) + value_old = await self.get_model(attribute, value_old) + value_new = await self.get_model(attribute, value_new) await model.name.filter(**{casing_name: value_old}).update( **{casing_name: value_new} @@ -275,7 +275,7 @@ async def delete(self, ctx, model, attribute, value, tortoise_operator=None): new_value = value.name if attribute.type == Types.MODEL: - new_value = self.get_model(attribute, new_value) + new_value = await self.get_model(attribute, new_value) await model.name.filter(**{casing_name: new_value}).delete() From 01589578a9e61df026b08fd8572b68f4c3179e67 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 12:00:39 -0500 Subject: [PATCH 080/133] Fix issues (wow very detailed) --- DexScript/package/commands.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index e6cbcd6..e000842 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -82,7 +82,7 @@ async def create(self, ctx, model, identifier): ------------- CREATE > MODEL > IDENTIFIER """ - await self.create_model(model.name, identifier) + await self.create_model(model.name, identifier.name) await ctx.send(f"Created `{identifier}`") async def delete(self, ctx, model, identifier): @@ -126,7 +126,9 @@ async def update(self, ctx, model, identifier, attribute, value=None): if attribute.type == Types.MODEL: new_value = await self.get_model(attribute, new_value) - await returned_model.update(**{attribute_name: new_value}) + setattr(returned_model, attribute_name, new_value) + + await returned_model.save(update_fields=[attribute_name]) await ctx.send(f"Updated `{identifier}'s` {attribute} to `{new_value}`") From 22f3d0f8528a786564f9fb72787ff045ab2dde83 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 12:05:16 -0500 Subject: [PATCH 081/133] Fix creation naming --- DexScript/package/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index e000842..5719147 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -36,7 +36,7 @@ async def create_model(self, model, identifier): fields[field] = identifier continue - match field_type: + match field_type.__class__.__name__: case "ForeignKeyFieldInstance": instance = Utils.fetch_model(field).first() From f36d3d4f37e4997e39a8493425af6b5f76c823cb Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 12:11:04 -0500 Subject: [PATCH 082/133] Fix key field for CF --- DexScript/package/commands.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 5719147..0c38366 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -38,10 +38,15 @@ async def create_model(self, model, identifier): match field_type.__class__.__name__: case "ForeignKeyFieldInstance": - instance = Utils.fetch_model(field).first() + if field == "cartype": + field == "car_type" + + casing_field = Utils.casing(field).title() + + instance = Utils.fetch_model(casing_field).first() if instance is None: - raise Exception(f"Could not find default {field}") + raise Exception(f"Could not find default {casing_field}") fields[field] = instance.pk From 7db82ec6865b5c609d12a0a6aaca9f7294adb0d6 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 12:52:06 -0500 Subject: [PATCH 083/133] Add `to_pascal_case` method --- DexScript/package/commands.py | 2 +- DexScript/package/utils.py | 33 ++++++++++++++++++++++++++------- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 0c38366..fd4a151 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -41,7 +41,7 @@ async def create_model(self, model, identifier): if field == "cartype": field == "car_type" - casing_field = Utils.casing(field).title() + casing_field = Utils.casing(field, True) instance = Utils.fetch_model(casing_field).first() diff --git a/DexScript/package/utils.py b/DexScript/package/utils.py index 201e247..88e346a 100644 --- a/DexScript/package/utils.py +++ b/DexScript/package/utils.py @@ -96,7 +96,20 @@ def to_camel_case(item): Formats a string or list from snake_case into camelCase for CarFigure support. """ return Utils._common_format( - item, func=lambda s: re.sub(r"(_[a-z])", lambda m: m.group(1)[1].upper(), s) + item, func=lambda s: re.sub( + r"(_[a-z])", lambda m: m.group(1)[1].upper(), s.replace("_", "") + ) + ) + + @staticmethod + def to_pascal_case(item): + """ + Formats a string or list from snake or camel case to pascal case for class support. + """ + return Utils._common_format( + item, func=lambda s: re.sub( + r"(_[a-z])", lambda m: m.group(1)[1].upper(), s[:1].upper() + s[1:] + ) ) @staticmethod @@ -109,14 +122,20 @@ def to_snake_case(item): ) @staticmethod - def casing(item): + def casing(item, pascal=False): """ - Determines whether to use camelCase or snake_case depending on if the bot is using - CarFigures or Ballsdex. + Determines whether to use camelCase, snake_case, or pascal case depending on + if the bot is using CarFigures or Ballsdex. """ - return ( - Utils.to_camel_case(item) if DIR == "carfigures" else Utils.to_snake_case(item) - ) + main_casing = Utils.to_snake_case(item) + + if DIR == "carfigures": + main_casing = Utils.to_camel_case(item) + + if pascal: + main_casing = Utils.to_pascal_case(item) + + return main_casing @staticmethod def autocorrect(string, correction_list, error="does not exist."): From c979a96952283ad860d9cb456235d0d43c732884 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 13:05:21 -0500 Subject: [PATCH 084/133] bruh --- DexScript/package/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index fd4a151..1a9bbf7 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -39,7 +39,7 @@ async def create_model(self, model, identifier): match field_type.__class__.__name__: case "ForeignKeyFieldInstance": if field == "cartype": - field == "car_type" + field = "car_type" casing_field = Utils.casing(field, True) From db9ac942a72e142d93ebb524024c3c834bd588c5 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 13:06:11 -0500 Subject: [PATCH 085/133] add await --- DexScript/package/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 1a9bbf7..8755daa 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -43,7 +43,7 @@ async def create_model(self, model, identifier): casing_field = Utils.casing(field, True) - instance = Utils.fetch_model(casing_field).first() + instance = await Utils.fetch_model(casing_field).first() if instance is None: raise Exception(f"Could not find default {casing_field}") From 1ec78747c9a0d4a7a6d4ec28702ab5b4ef55dc48 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 13:29:08 -0500 Subject: [PATCH 086/133] Add debugging --- DexScript/package/commands.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 8755daa..74d77ad 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -16,7 +16,7 @@ def __init__(self, bot): def __loaded__(self): pass - async def create_model(self, model, identifier): + async def create_model(self, model, identifier, fields_only=False): fields = {} special_list = { @@ -58,6 +58,9 @@ async def create_model(self, model, identifier): case _: fields[field] = 1 + + if fields_only: + return fields await model.create(**fields) From 7da3f32b12fe74223c7b72a44f5665672dc30592 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 13:32:59 -0500 Subject: [PATCH 087/133] fix special_list --- DexScript/package/commands.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 74d77ad..7ec5347 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -25,8 +25,10 @@ async def create_model(self, model, identifier, fields_only=False): } if DIR == "carfigures": - special_list["Identifiers"] = Utils.to_camel_case(special_list["Identifiers"]) - special_list["Ignore"] = Utils.to_camel_case(special_list["Ignore"]) + special_list = { + "Identifiers": ["fullName", "catchNames", "name"], + "Ignore": ["id", "shortName"] + } for field, field_type in model._meta.fields_map.items(): if vars(model()).get(field) is not None or field in special_list["Ignore"]: From b279dbad9f7fb5bc44baa6d3495f675b1b7bbe94 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 13:49:26 -0500 Subject: [PATCH 088/133] Fix null --- DexScript/package/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 7ec5347..0f43d0f 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -31,7 +31,7 @@ async def create_model(self, model, identifier, fields_only=False): } for field, field_type in model._meta.fields_map.items(): - if vars(model()).get(field) is not None or field in special_list["Ignore"]: + if field_type.null or field in special_list["Ignore"]: continue if field in special_list["Identifiers"]: From c9515a1925cf81a650d75da7c4d8db8602372b8b Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 14:41:43 -0500 Subject: [PATCH 089/133] Fix camel case --- DexScript/package/utils.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/DexScript/package/utils.py b/DexScript/package/utils.py index 88e346a..950a381 100644 --- a/DexScript/package/utils.py +++ b/DexScript/package/utils.py @@ -96,9 +96,7 @@ def to_camel_case(item): Formats a string or list from snake_case into camelCase for CarFigure support. """ return Utils._common_format( - item, func=lambda s: re.sub( - r"(_[a-z])", lambda m: m.group(1)[1].upper(), s.replace("_", "") - ) + item, func=lambda s: re.sub( r"(_[a-z])", lambda m: m.group(1)[1].upper(), s) ) @staticmethod From c6fe994ecda96f7148e9ac41ed8cf50cc93f2da1 Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 14:59:19 -0500 Subject: [PATCH 090/133] WIP fix path --- DexScript/package/commands.py | 9 +++++++-- DexScript/package/utils.py | 12 ++++++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 0f43d0f..60f07fc 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -256,7 +256,7 @@ async def update(self, ctx, model, attribute, old_value, new_value, tortoise_ope ) await ctx.send( - f"Updated all `{model}` instances from a " + f"Updated all `{model.name.__name__}` instances from a " f"`{attribute}` value of `{old_value}` to `{new_value}`" ) @@ -292,7 +292,8 @@ async def delete(self, ctx, model, attribute, value, tortoise_operator=None): await model.name.filter(**{casing_name: new_value}).delete() await ctx.send( - f"Deleted all `{model}` instances with a " f"`{attribute}` value of `{value}`" + f"Deleted all `{model.name.__name__}` instances with a " + f"`{attribute}` value of `{value}`" ) @@ -378,6 +379,10 @@ async def remove(self, ctx, name): await ctx.send(f"Removed `{name}` preset.") async def list(self, ctx): + if os.listdir("eval_presets") == []: + await ctx.send("You have no eval presets saved.") + return + await ctx.send(f"```{'\n'.join(os.listdir("eval_presets"))}```") async def run(self, ctx, name): # TODO: Allow args to be passed through `run`. diff --git a/DexScript/package/utils.py b/DexScript/package/utils.py index 950a381..871eb75 100644 --- a/DexScript/package/utils.py +++ b/DexScript/package/utils.py @@ -19,7 +19,7 @@ START_CODE_BLOCK_RE = re.compile(r"^((```sql?)(?=\s)|(```))") FILENAME_RE = re.compile(r"^(.+)(\.\S+)$") -MEDIA_PATH = "./admin_panel/media" if os.path.isdir("./admin_panel/media") else "/static/uploads" +MEDIA_PATH = "./admin_panel/media" if os.path.isdir("./admin_panel/media") else "./static/uploads" class Types(Enum): @@ -181,11 +181,15 @@ def remove_code_markdown(content) -> str: return content.strip("` \n") @staticmethod - def is_image(path) -> bool: - if path.startswith("/static/uploads/"): + def image_path(path): + if path.startsiwth("/static/uploads/"): path.replace("/static/uploads/", "") - return os.path.isfile(f"{MEDIA_PATH}/{path}") + return f"{MEDIA_PATH}/{path}" + + @staticmethod + def is_image(path) -> bool: + return os.path.isfile(Utils.image_path(path)) @staticmethod def is_date(string) -> bool: From af64a95b37d48fa5547592c5976236833c28da6e Mon Sep 17 00:00:00 2001 From: Dotsian Date: Tue, 4 Feb 2025 17:38:38 -0500 Subject: [PATCH 091/133] Fix images --- DexScript/package/commands.py | 35 +++++++++++------------------- DexScript/package/utils.py | 6 ++--- assets/examples/updating_ball.png | Bin 0 -> 34337 bytes 3 files changed, 16 insertions(+), 25 deletions(-) create mode 100644 assets/examples/updating_ball.png diff --git a/DexScript/package/commands.py b/DexScript/package/commands.py index 60f07fc..404c87e 100644 --- a/DexScript/package/commands.py +++ b/DexScript/package/commands.py @@ -16,6 +16,13 @@ def __init__(self, bot): def __loaded__(self): pass + def attribute_error(self, model, attribute): + raise Exception( + f"'{attribute}' is not a valid {model.name.__name__} attribute\n" + f"Run `ATTRIBUTES > {model.name.__name__}` to see a list of " + "all attributes for that model" + ) + async def create_model(self, model, identifier, fields_only=False): fields = {} @@ -123,15 +130,11 @@ async def update(self, ctx, model, identifier, attribute, value=None): returned_model = await self.get_model(model, identifier.name) if not hasattr(model.name(), attribute_name): - raise Exception( - f"'{attribute_name}' is not a valid {model.name.__name__} attribute\n" - f"Run `ATTRIBUTES > {model.name.__name__}` to see a list of " - "all attributes for that model" - ) + self.attribute_error(model, attribute_name) if value is None: image_path = await Utils.save_file(ctx.message.attachments[0]) - new_value = f"{MEDIA_PATH}/{image_path}" + new_value = Utils.image_path(image_path) if attribute.type == Types.MODEL: new_value = await self.get_model(attribute, new_value) @@ -166,7 +169,7 @@ async def view(self, ctx, model, identifier, attribute=None): if fields.get("files") is None: fields["files"] = [] - fields["files"].append(discord.File(value[1:])) + fields["files"].append(discord.File(Utils.image_path(value))) fields["content"] += "```" @@ -178,11 +181,7 @@ async def view(self, ctx, model, identifier, attribute=None): attribute_name = Utils.casing(_attr_name.lower()) if not hasattr(model.name(), attribute_name): - raise Exception( - f"'{attribute_name}' is not a valid {model.name.__name__} attribute\n" - f"Run `ATTRIBUTES > {model.name.__name__}` to see a list of " - "all attributes for that model" - ) + self.attribute_error(model, attribute_name) new_attribute = getattr(returned_model, attribute_name) @@ -235,11 +234,7 @@ async def update(self, ctx, model, attribute, old_value, new_value, tortoise_ope casing_name = Utils.casing(_attr_name.lower()) if not hasattr(model.name(), casing_name): - raise Exception( - f"'{casing_name}' is not a valid {model.name.__name__} attribute\n" - f"Run `ATTRIBUTES > {model.name.__name__}` to see a list of " - "all attributes for that model" - ) + self.attribute_error(model, casing_name) if tortoise_operator is not None: casing_name += f"__{tortoise_operator.name.lower()}" @@ -275,11 +270,7 @@ async def delete(self, ctx, model, attribute, value, tortoise_operator=None): casing_name = Utils.casing(_attr_name.lower()) if not hasattr(model.name(), casing_name): - raise Exception( - f"'{casing_name}' is not a valid {model.name.__name__} attribute\n" - f"Run `ATTRIBUTES > {model.name.__name__}` to see a list of " - "all attributes for that model" - ) + self.attribute_error(model, casing_name) if tortoise_operator is not None: casing_name += f"__{tortoise_operator.name.lower()}" diff --git a/DexScript/package/utils.py b/DexScript/package/utils.py index 871eb75..cb6632b 100644 --- a/DexScript/package/utils.py +++ b/DexScript/package/utils.py @@ -179,10 +179,10 @@ def remove_code_markdown(content) -> str: return START_CODE_BLOCK_RE.sub("", content)[:-3] return content.strip("` \n") - + @staticmethod - def image_path(path): - if path.startsiwth("/static/uploads/"): + def image_path(path) -> bool: + if path.startswith("/static/uploads/"): path.replace("/static/uploads/", "") return f"{MEDIA_PATH}/{path}" diff --git a/assets/examples/updating_ball.png b/assets/examples/updating_ball.png new file mode 100644 index 0000000000000000000000000000000000000000..ab5541e2f6696da09186cafe07184e987e4132b2 GIT binary patch literal 34337 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