From 2970de97c0b5a031988ae85642759df956414765 Mon Sep 17 00:00:00 2001 From: Phanabani Date: Tue, 8 Nov 2022 21:30:03 -0500 Subject: [PATCH 01/33] Remove pipenv files. --- Pipfile | 29 -- Pipfile.lock | 838 --------------------------------------------------- 2 files changed, 867 deletions(-) delete mode 100644 Pipfile delete mode 100644 Pipfile.lock diff --git a/Pipfile b/Pipfile deleted file mode 100644 index 71ae94e..0000000 --- a/Pipfile +++ /dev/null @@ -1,29 +0,0 @@ -[[source]] -url = "https://pypi.org/simple" -verify_ssl = true -name = "pypi" - -[packages] -aiosqlite = "~=0.17" -alembic = "~=1.6" -certifi = "*" -"discord.py" = "~=1.7" -fuzzywuzzy = {version = "~=0.18", extras = ["speedup"]} -pytz = "~=2021.1" -regex = "~=2021.8" -semver = "~=2.13" -sqlalchemy = "~=1.4" -Pint = "~=0.17" -tzlocal = "~=2.1" - -[dev-packages] -coverage = "~=5.5" -ipython = "~=7.26" -nest_asyncio = "~=1.5" -pytest = "~=6.2" -pytest-asyncio = "~=0.17" -pytest-profiling = "~=1.7" -yappi = "~=1.3" - -[requires] -python_version = "3.9" diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index 2cee206..0000000 --- a/Pipfile.lock +++ /dev/null @@ -1,838 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "dd2c46957f35a55c35267c4a11dbf7c78628160d080f4d071a8060312f795f8b" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.9" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "aiohttp": { - "hashes": [ - "sha256:02f46fc0e3c5ac58b80d4d56eb0a7c7d97fcef69ace9326289fb9f1955e65cfe", - "sha256:0563c1b3826945eecd62186f3f5c7d31abb7391fedc893b7e2b26303b5a9f3fe", - "sha256:114b281e4d68302a324dd33abb04778e8557d88947875cbf4e842c2c01a030c5", - "sha256:14762875b22d0055f05d12abc7f7d61d5fd4fe4642ce1a249abdf8c700bf1fd8", - "sha256:15492a6368d985b76a2a5fdd2166cddfea5d24e69eefed4630cbaae5c81d89bd", - "sha256:17c073de315745a1510393a96e680d20af8e67e324f70b42accbd4cb3315c9fb", - "sha256:209b4a8ee987eccc91e2bd3ac36adee0e53a5970b8ac52c273f7f8fd4872c94c", - "sha256:230a8f7e24298dea47659251abc0fd8b3c4e38a664c59d4b89cca7f6c09c9e87", - "sha256:2e19413bf84934d651344783c9f5e22dee452e251cfd220ebadbed2d9931dbf0", - "sha256:393f389841e8f2dfc86f774ad22f00923fdee66d238af89b70ea314c4aefd290", - "sha256:3cf75f7cdc2397ed4442594b935a11ed5569961333d49b7539ea741be2cc79d5", - "sha256:3d78619672183be860b96ed96f533046ec97ca067fd46ac1f6a09cd9b7484287", - "sha256:40eced07f07a9e60e825554a31f923e8d3997cfc7fb31dbc1328c70826e04cde", - "sha256:493d3299ebe5f5a7c66b9819eacdcfbbaaf1a8e84911ddffcdc48888497afecf", - "sha256:4b302b45040890cea949ad092479e01ba25911a15e648429c7c5aae9650c67a8", - "sha256:515dfef7f869a0feb2afee66b957cc7bbe9ad0cdee45aec7fdc623f4ecd4fb16", - "sha256:547da6cacac20666422d4882cfcd51298d45f7ccb60a04ec27424d2f36ba3eaf", - "sha256:5df68496d19f849921f05f14f31bd6ef53ad4b00245da3195048c69934521809", - "sha256:64322071e046020e8797117b3658b9c2f80e3267daec409b350b6a7a05041213", - "sha256:7615dab56bb07bff74bc865307aeb89a8bfd9941d2ef9d817b9436da3a0ea54f", - "sha256:79ebfc238612123a713a457d92afb4096e2148be17df6c50fb9bf7a81c2f8013", - "sha256:7b18b97cf8ee5452fa5f4e3af95d01d84d86d32c5e2bfa260cf041749d66360b", - "sha256:932bb1ea39a54e9ea27fc9232163059a0b8855256f4052e776357ad9add6f1c9", - "sha256:a00bb73540af068ca7390e636c01cbc4f644961896fa9363154ff43fd37af2f5", - "sha256:a5ca29ee66f8343ed336816c553e82d6cade48a3ad702b9ffa6125d187e2dedb", - "sha256:af9aa9ef5ba1fd5b8c948bb11f44891968ab30356d65fd0cc6707d989cd521df", - "sha256:bb437315738aa441251214dad17428cafda9cdc9729499f1d6001748e1d432f4", - "sha256:bdb230b4943891321e06fc7def63c7aace16095be7d9cf3b1e01be2f10fba439", - "sha256:c6e9dcb4cb338d91a73f178d866d051efe7c62a7166653a91e7d9fb18274058f", - "sha256:cffe3ab27871bc3ea47df5d8f7013945712c46a3cc5a95b6bee15887f1675c22", - "sha256:d012ad7911653a906425d8473a1465caa9f8dea7fcf07b6d870397b774ea7c0f", - "sha256:d9e13b33afd39ddeb377eff2c1c4f00544e191e1d1dee5b6c51ddee8ea6f0cf5", - "sha256:e4b2b334e68b18ac9817d828ba44d8fcb391f6acb398bcc5062b14b2cbeac970", - "sha256:e54962802d4b8b18b6207d4a927032826af39395a3bd9196a5af43fc4e60b009", - "sha256:f705e12750171c0ab4ef2a3c76b9a4024a62c4103e3a55dd6f99265b9bc6fcfc", - "sha256:f881853d2643a29e643609da57b96d5f9c9b93f62429dcc1cbb413c7d07f0e1a", - "sha256:fe60131d21b31fd1a14bd43e6bb88256f69dfc3188b3a89d736d6c71ed43ec95" - ], - "markers": "python_version >= '3.6'", - "version": "==3.7.4.post0" - }, - "aiosqlite": { - "hashes": [ - "sha256:6c49dc6d3405929b1d08eeccc72306d3677503cc5e5e43771efc1e00232e8231", - "sha256:f0e6acc24bc4864149267ac82fb46dfb3be4455f99fe21df82609cc6e6baee51" - ], - "index": "pypi", - "version": "==0.17.0" - }, - "alembic": { - "hashes": [ - "sha256:0a024d7f2de88d738d7395ff866997314c837be6104e90c5724350313dee4da4", - "sha256:cd0b5e45b14b706426b833f06369b9a6d5ee03f826ec3238723ce8caaf6e5ffa" - ], - "index": "pypi", - "version": "==1.8.1" - }, - "async-timeout": { - "hashes": [ - "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f", - "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3" - ], - "markers": "python_full_version >= '3.5.3'", - "version": "==3.0.1" - }, - "attrs": { - "hashes": [ - "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6", - "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c" - ], - "markers": "python_version >= '3.5'", - "version": "==22.1.0" - }, - "certifi": { - "hashes": [ - "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d", - "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412" - ], - "index": "pypi", - "version": "==2022.6.15" - }, - "chardet": { - "hashes": [ - "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", - "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==4.0.0" - }, - "discord.py": { - "hashes": [ - "sha256:462cd0fe307aef8b29cbfa8dd613e548ae4b2cb581d46da9ac0d46fb6ea19408", - "sha256:c6f64db136de0e18e090f6752ea68bdd4ab0a61b82dfe7acecefa22d6477bb0c" - ], - "index": "pypi", - "version": "==1.7.3" - }, - "fuzzywuzzy": { - "extras": [ - "speedup" - ], - "hashes": [ - "sha256:45016e92264780e58972dca1b3d939ac864b78437422beecebb3095f8efd00e8", - "sha256:928244b28db720d1e0ee7587acf660ea49d7e4c632569cad4f1cd7e68a5f0993" - ], - "index": "pypi", - "version": "==0.18.0" - }, - "greenlet": { - "hashes": [ - "sha256:0051c6f1f27cb756ffc0ffbac7d2cd48cb0362ac1736871399a739b2885134d3", - "sha256:00e44c8afdbe5467e4f7b5851be223be68adb4272f44696ee71fe46b7036a711", - "sha256:013d61294b6cd8fe3242932c1c5e36e5d1db2c8afb58606c5a67efce62c1f5fd", - "sha256:049fe7579230e44daef03a259faa24511d10ebfa44f69411d99e6a184fe68073", - "sha256:14d4f3cd4e8b524ae9b8aa567858beed70c392fdec26dbdb0a8a418392e71708", - "sha256:166eac03e48784a6a6e0e5f041cfebb1ab400b394db188c48b3a84737f505b67", - "sha256:17ff94e7a83aa8671a25bf5b59326ec26da379ace2ebc4411d690d80a7fbcf23", - "sha256:1e12bdc622676ce47ae9abbf455c189e442afdde8818d9da983085df6312e7a1", - "sha256:21915eb821a6b3d9d8eefdaf57d6c345b970ad722f856cd71739493ce003ad08", - "sha256:288c6a76705dc54fba69fbcb59904ae4ad768b4c768839b8ca5fdadec6dd8cfd", - "sha256:2bde6792f313f4e918caabc46532aa64aa27a0db05d75b20edfc5c6f46479de2", - "sha256:32ca72bbc673adbcfecb935bb3fb1b74e663d10a4b241aaa2f5a75fe1d1f90aa", - "sha256:356b3576ad078c89a6107caa9c50cc14e98e3a6c4874a37c3e0273e4baf33de8", - "sha256:40b951f601af999a8bf2ce8c71e8aaa4e8c6f78ff8afae7b808aae2dc50d4c40", - "sha256:572e1787d1460da79590bf44304abbc0a2da944ea64ec549188fa84d89bba7ab", - "sha256:58df5c2a0e293bf665a51f8a100d3e9956febfbf1d9aaf8c0677cf70218910c6", - "sha256:64e6175c2e53195278d7388c454e0b30997573f3f4bd63697f88d855f7a6a1fc", - "sha256:7227b47e73dedaa513cdebb98469705ef0d66eb5a1250144468e9c3097d6b59b", - "sha256:7418b6bfc7fe3331541b84bb2141c9baf1ec7132a7ecd9f375912eca810e714e", - "sha256:7cbd7574ce8e138bda9df4efc6bf2ab8572c9aff640d8ecfece1b006b68da963", - "sha256:7ff61ff178250f9bb3cd89752df0f1dd0e27316a8bd1465351652b1b4a4cdfd3", - "sha256:833e1551925ed51e6b44c800e71e77dacd7e49181fdc9ac9a0bf3714d515785d", - "sha256:8639cadfda96737427330a094476d4c7a56ac03de7265622fcf4cfe57c8ae18d", - "sha256:8c5d5b35f789a030ebb95bff352f1d27a93d81069f2adb3182d99882e095cefe", - "sha256:8c790abda465726cfb8bb08bd4ca9a5d0a7bd77c7ac1ca1b839ad823b948ea28", - "sha256:8d2f1fb53a421b410751887eb4ff21386d119ef9cde3797bf5e7ed49fb51a3b3", - "sha256:903bbd302a2378f984aef528f76d4c9b1748f318fe1294961c072bdc7f2ffa3e", - "sha256:93f81b134a165cc17123626ab8da2e30c0455441d4ab5576eed73a64c025b25c", - "sha256:95e69877983ea39b7303570fa6760f81a3eec23d0e3ab2021b7144b94d06202d", - "sha256:9633b3034d3d901f0a46b7939f8c4d64427dfba6bbc5a36b1a67364cf148a1b0", - "sha256:97e5306482182170ade15c4b0d8386ded995a07d7cc2ca8f27958d34d6736497", - "sha256:9f3cba480d3deb69f6ee2c1825060177a22c7826431458c697df88e6aeb3caee", - "sha256:aa5b467f15e78b82257319aebc78dd2915e4c1436c3c0d1ad6f53e47ba6e2713", - "sha256:abb7a75ed8b968f3061327c433a0fbd17b729947b400747c334a9c29a9af6c58", - "sha256:aec52725173bd3a7b56fe91bc56eccb26fbdff1386ef123abb63c84c5b43b63a", - "sha256:b11548073a2213d950c3f671aa88e6f83cda6e2fb97a8b6317b1b5b33d850e06", - "sha256:b1692f7d6bc45e3200844be0dba153612103db241691088626a33ff1f24a0d88", - "sha256:b336501a05e13b616ef81ce329c0e09ac5ed8c732d9ba7e3e983fcc1a9e86965", - "sha256:b8c008de9d0daba7b6666aa5bbfdc23dcd78cafc33997c9b7741ff6353bafb7f", - "sha256:b92e29e58bef6d9cfd340c72b04d74c4b4e9f70c9fa7c78b674d1fec18896dc4", - "sha256:be5f425ff1f5f4b3c1e33ad64ab994eed12fc284a6ea71c5243fd564502ecbe5", - "sha256:dd0b1e9e891f69e7675ba5c92e28b90eaa045f6ab134ffe70b52e948aa175b3c", - "sha256:e30f5ea4ae2346e62cedde8794a56858a67b878dd79f7df76a0767e356b1744a", - "sha256:e6a36bb9474218c7a5b27ae476035497a6990e21d04c279884eb10d9b290f1b1", - "sha256:e859fcb4cbe93504ea18008d1df98dee4f7766db66c435e4882ab35cf70cac43", - "sha256:eb6ea6da4c787111adf40f697b4e58732ee0942b5d3bd8f435277643329ba627", - "sha256:ec8c433b3ab0419100bd45b47c9c8551248a5aee30ca5e9d399a0b57ac04651b", - "sha256:eff9d20417ff9dcb0d25e2defc2574d10b491bf2e693b4e491914738b7908168", - "sha256:f0214eb2a23b85528310dad848ad2ac58e735612929c8072f6093f3585fd342d", - "sha256:f276df9830dba7a333544bd41070e8175762a7ac20350786b322b714b0e654f5", - "sha256:f3acda1924472472ddd60c29e5b9db0cec629fbe3c5c5accb74d6d6d14773478", - "sha256:f70a9e237bb792c7cc7e44c531fd48f5897961701cdaa06cf22fc14965c496cf", - "sha256:f9d29ca8a77117315101425ec7ec2a47a22ccf59f5593378fc4077ac5b754fce", - "sha256:fa877ca7f6b48054f847b61d6fa7bed5cebb663ebc55e018fda12db09dcc664c", - "sha256:fdcec0b8399108577ec290f55551d926d9a1fa6cad45882093a7a07ac5ec147b" - ], - "markers": "python_version >= '3' and platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32')))))", - "version": "==1.1.2" - }, - "idna": { - "hashes": [ - "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", - "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" - ], - "markers": "python_version >= '3.5'", - "version": "==3.3" - }, - "mako": { - "hashes": [ - "sha256:df3921c3081b013c8a2d5ff03c18375651684921ae83fd12e64800b7da923257", - "sha256:f054a5ff4743492f1aa9ecc47172cb33b42b9d993cffcc146c9de17e717b0307" - ], - "markers": "python_version >= '3.7'", - "version": "==1.2.1" - }, - "markupsafe": { - "hashes": [ - "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003", - "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88", - "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5", - "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7", - "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a", - "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603", - "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1", - "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135", - "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247", - "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6", - "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601", - "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77", - "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02", - "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e", - "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63", - "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f", - "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980", - "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b", - "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812", - "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff", - "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96", - "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1", - "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925", - "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a", - "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6", - "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e", - "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f", - "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4", - "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f", - "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3", - "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c", - "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a", - "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417", - "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a", - "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a", - "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37", - "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452", - "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933", - "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a", - "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7" - ], - "markers": "python_version >= '3.7'", - "version": "==2.1.1" - }, - "multidict": { - "hashes": [ - "sha256:0327292e745a880459ef71be14e709aaea2f783f3537588fb4ed09b6c01bca60", - "sha256:041b81a5f6b38244b34dc18c7b6aba91f9cdaf854d9a39e5ff0b58e2b5773b9c", - "sha256:0556a1d4ea2d949efe5fd76a09b4a82e3a4a30700553a6725535098d8d9fb672", - "sha256:05f6949d6169878a03e607a21e3b862eaf8e356590e8bdae4227eedadacf6e51", - "sha256:07a017cfa00c9890011628eab2503bee5872f27144936a52eaab449be5eaf032", - "sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2", - "sha256:19adcfc2a7197cdc3987044e3f415168fc5dc1f720c932eb1ef4f71a2067e08b", - "sha256:19d9bad105dfb34eb539c97b132057a4e709919ec4dd883ece5838bcbf262b80", - "sha256:225383a6603c086e6cef0f2f05564acb4f4d5f019a4e3e983f572b8530f70c88", - "sha256:23b616fdc3c74c9fe01d76ce0d1ce872d2d396d8fa8e4899398ad64fb5aa214a", - "sha256:2957489cba47c2539a8eb7ab32ff49101439ccf78eab724c828c1a54ff3ff98d", - "sha256:2d36e929d7f6a16d4eb11b250719c39560dd70545356365b494249e2186bc389", - "sha256:2e4a0785b84fb59e43c18a015ffc575ba93f7d1dbd272b4cdad9f5134b8a006c", - "sha256:3368bf2398b0e0fcbf46d85795adc4c259299fec50c1416d0f77c0a843a3eed9", - "sha256:373ba9d1d061c76462d74e7de1c0c8e267e9791ee8cfefcf6b0b2495762c370c", - "sha256:4070613ea2227da2bfb2c35a6041e4371b0af6b0be57f424fe2318b42a748516", - "sha256:45183c96ddf61bf96d2684d9fbaf6f3564d86b34cb125761f9a0ef9e36c1d55b", - "sha256:4571f1beddff25f3e925eea34268422622963cd8dc395bb8778eb28418248e43", - "sha256:47e6a7e923e9cada7c139531feac59448f1f47727a79076c0b1ee80274cd8eee", - "sha256:47fbeedbf94bed6547d3aa632075d804867a352d86688c04e606971595460227", - "sha256:497988d6b6ec6ed6f87030ec03280b696ca47dbf0648045e4e1d28b80346560d", - "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae", - "sha256:50bd442726e288e884f7be9071016c15a8742eb689a593a0cac49ea093eef0a7", - "sha256:514fe2b8d750d6cdb4712346a2c5084a80220821a3e91f3f71eec11cf8d28fd4", - "sha256:5774d9218d77befa7b70d836004a768fb9aa4fdb53c97498f4d8d3f67bb9cfa9", - "sha256:5fdda29a3c7e76a064f2477c9aab1ba96fd94e02e386f1e665bca1807fc5386f", - "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013", - "sha256:626fe10ac87851f4cffecee161fc6f8f9853f0f6f1035b59337a51d29ff3b4f9", - "sha256:6701bf8a5d03a43375909ac91b6980aea74b0f5402fbe9428fc3f6edf5d9677e", - "sha256:684133b1e1fe91eda8fa7447f137c9490a064c6b7f392aa857bba83a28cfb693", - "sha256:6f3cdef8a247d1eafa649085812f8a310e728bdf3900ff6c434eafb2d443b23a", - "sha256:75bdf08716edde767b09e76829db8c1e5ca9d8bb0a8d4bd94ae1eafe3dac5e15", - "sha256:7c40b7bbece294ae3a87c1bc2abff0ff9beef41d14188cda94ada7bcea99b0fb", - "sha256:8004dca28e15b86d1b1372515f32eb6f814bdf6f00952699bdeb541691091f96", - "sha256:8064b7c6f0af936a741ea1efd18690bacfbae4078c0c385d7c3f611d11f0cf87", - "sha256:89171b2c769e03a953d5969b2f272efa931426355b6c0cb508022976a17fd376", - "sha256:8cbf0132f3de7cc6c6ce00147cc78e6439ea736cee6bca4f068bcf892b0fd658", - "sha256:9cc57c68cb9139c7cd6fc39f211b02198e69fb90ce4bc4a094cf5fe0d20fd8b0", - "sha256:a007b1638e148c3cfb6bf0bdc4f82776cef0ac487191d093cdc316905e504071", - "sha256:a2c34a93e1d2aa35fbf1485e5010337c72c6791407d03aa5f4eed920343dd360", - "sha256:a45e1135cb07086833ce969555df39149680e5471c04dfd6a915abd2fc3f6dbc", - "sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3", - "sha256:aef9cc3d9c7d63d924adac329c33835e0243b5052a6dfcbf7732a921c6e918ba", - "sha256:b9d153e7f1f9ba0b23ad1568b3b9e17301e23b042c23870f9ee0522dc5cc79e8", - "sha256:bfba7c6d5d7c9099ba21f84662b037a0ffd4a5e6b26ac07d19e423e6fdf965a9", - "sha256:c207fff63adcdf5a485969131dc70e4b194327666b7e8a87a97fbc4fd80a53b2", - "sha256:d0509e469d48940147e1235d994cd849a8f8195e0bca65f8f5439c56e17872a3", - "sha256:d16cce709ebfadc91278a1c005e3c17dd5f71f5098bfae1035149785ea6e9c68", - "sha256:d48b8ee1d4068561ce8033d2c344cf5232cb29ee1a0206a7b828c79cbc5982b8", - "sha256:de989b195c3d636ba000ee4281cd03bb1234635b124bf4cd89eeee9ca8fcb09d", - "sha256:e07c8e79d6e6fd37b42f3250dba122053fddb319e84b55dd3a8d6446e1a7ee49", - "sha256:e2c2e459f7050aeb7c1b1276763364884595d47000c1cddb51764c0d8976e608", - "sha256:e5b20e9599ba74391ca0cfbd7b328fcc20976823ba19bc573983a25b32e92b57", - "sha256:e875b6086e325bab7e680e4316d667fc0e5e174bb5611eb16b3ea121c8951b86", - "sha256:f4f052ee022928d34fe1f4d2bc743f32609fb79ed9c49a1710a5ad6b2198db20", - "sha256:fcb91630817aa8b9bc4a74023e4198480587269c272c58b3279875ed7235c293", - "sha256:fd9fc9c4849a07f3635ccffa895d57abce554b467d611a5009ba4f39b78a8849", - "sha256:feba80698173761cddd814fa22e88b0661e98cb810f9f986c54aa34d281e4937", - "sha256:feea820722e69451743a3d56ad74948b68bf456984d63c1a92e8347b7b88452d" - ], - "markers": "python_version >= '3.7'", - "version": "==6.0.2" - }, - "pint": { - "hashes": [ - "sha256:e1d4989ff510b378dad64f91711e7bdabe5ca78d75b06a18569ac454678c4baf" - ], - "index": "pypi", - "version": "==0.19.2" - }, - "python-levenshtein": { - "hashes": [ - "sha256:dc2395fbd148a1ab31090dd113c366695934b9e85fe5a4b2a032745efd0346f6" - ], - "version": "==0.12.2" - }, - "pytz": { - "hashes": [ - "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c", - "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326" - ], - "index": "pypi", - "version": "==2021.3" - }, - "regex": { - "hashes": [ - "sha256:0416f7399e918c4b0e074a0f66e5191077ee2ca32a0f99d4c187a62beb47aa05", - "sha256:05b7d6d7e64efe309972adab77fc2af8907bb93217ec60aa9fe12a0dad35874f", - "sha256:0617383e2fe465732af4509e61648b77cbe3aee68b6ac8c0b6fe934db90be5cc", - "sha256:07856afef5ffcc052e7eccf3213317fbb94e4a5cd8177a2caa69c980657b3cb4", - "sha256:0f594b96fe2e0821d026365f72ac7b4f0b487487fb3d4aaf10dd9d97d88a9737", - "sha256:139a23d1f5d30db2cc6c7fd9c6d6497872a672db22c4ae1910be22d4f4b2068a", - "sha256:162abfd74e88001d20cb73ceaffbfe601469923e875caf9118333b1a4aaafdc4", - "sha256:2207ae4f64ad3af399e2d30dde66f0b36ae5c3129b52885f1bffc2f05ec505c8", - "sha256:2409b5c9cef7054dde93a9803156b411b677affc84fca69e908b1cb2c540025d", - "sha256:2fee3ed82a011184807d2127f1733b4f6b2ff6ec7151d83ef3477f3b96a13d03", - "sha256:30ab804ea73972049b7a2a5c62d97687d69b5a60a67adca07eb73a0ddbc9e29f", - "sha256:3598893bde43091ee5ca0a6ad20f08a0435e93a69255eeb5f81b85e81e329264", - "sha256:3b5df18db1fccd66de15aa59c41e4f853b5df7550723d26aa6cb7f40e5d9da5a", - "sha256:3c5fb32cc6077abad3bbf0323067636d93307c9fa93e072771cf9a64d1c0f3ef", - "sha256:416c5f1a188c91e3eb41e9c8787288e707f7d2ebe66e0a6563af280d9b68478f", - "sha256:42b50fa6666b0d50c30a990527127334d6b96dd969011e843e726a64011485da", - "sha256:432bd15d40ed835a51617521d60d0125867f7b88acf653e4ed994a1f8e4995dc", - "sha256:473e67837f786404570eae33c3b64a4b9635ae9f00145250851a1292f484c063", - "sha256:4aaa4e0705ef2b73dd8e36eeb4c868f80f8393f5f4d855e94025ce7ad8525f50", - "sha256:50a7ddf3d131dc5633dccdb51417e2d1910d25cbcf842115a3a5893509140a3a", - "sha256:529801a0d58809b60b3531ee804d3e3be4b412c94b5d267daa3de7fadef00f49", - "sha256:537ca6a3586931b16a85ac38c08cc48f10fc870a5b25e51794c74df843e9966d", - "sha256:53db2c6be8a2710b359bfd3d3aa17ba38f8aa72a82309a12ae99d3c0c3dcd74d", - "sha256:5537f71b6d646f7f5f340562ec4c77b6e1c915f8baae822ea0b7e46c1f09b733", - "sha256:563d5f9354e15e048465061509403f68424fef37d5add3064038c2511c8f5e00", - "sha256:5d408a642a5484b9b4d11dea15a489ea0928c7e410c7525cd892f4d04f2f617b", - "sha256:61600a7ca4bcf78a96a68a27c2ae9389763b5b94b63943d5158f2a377e09d29a", - "sha256:6650f16365f1924d6014d2ea770bde8555b4a39dc9576abb95e3cd1ff0263b36", - "sha256:666abff54e474d28ff42756d94544cdfd42e2ee97065857413b72e8a2d6a6345", - "sha256:68a067c11463de2a37157930d8b153005085e42bcb7ad9ca562d77ba7d1404e0", - "sha256:6e1d2cc79e8dae442b3fa4a26c5794428b98f81389af90623ffcc650ce9f6732", - "sha256:74cbeac0451f27d4f50e6e8a8f3a52ca074b5e2da9f7b505c4201a57a8ed6286", - "sha256:780b48456a0f0ba4d390e8b5f7c661fdd218934388cde1a974010a965e200e12", - "sha256:788aef3549f1924d5c38263104dae7395bf020a42776d5ec5ea2b0d3d85d6646", - "sha256:7ee1227cf08b6716c85504aebc49ac827eb88fcc6e51564f010f11a406c0a667", - "sha256:7f301b11b9d214f83ddaf689181051e7f48905568b0c7017c04c06dfd065e244", - "sha256:83ee89483672b11f8952b158640d0c0ff02dc43d9cb1b70c1564b49abe92ce29", - "sha256:85bfa6a5413be0ee6c5c4a663668a2cad2cbecdee367630d097d7823041bdeec", - "sha256:9345b6f7ee578bad8e475129ed40123d265464c4cfead6c261fd60fc9de00bcf", - "sha256:93a5051fcf5fad72de73b96f07d30bc29665697fb8ecdfbc474f3452c78adcf4", - "sha256:962b9a917dd7ceacbe5cd424556914cb0d636001e393b43dc886ba31d2a1e449", - "sha256:96fc32c16ea6d60d3ca7f63397bff5c75c5a562f7db6dec7d412f7c4d2e78ec0", - "sha256:98ba568e8ae26beb726aeea2273053c717641933836568c2a0278a84987b2a1a", - "sha256:a3feefd5e95871872673b08636f96b61ebef62971eab044f5124fb4dea39919d", - "sha256:a955b747d620a50408b7fdf948e04359d6e762ff8a85f5775d907ceced715129", - "sha256:b43c2b8a330a490daaef5a47ab114935002b13b3f9dc5da56d5322ff218eeadb", - "sha256:b483c9d00a565633c87abd0aaf27eb5016de23fed952e054ecc19ce32f6a9e7e", - "sha256:b9ed0b1e5e0759d6b7f8e2f143894b2a7f3edd313f38cf44e1e15d360e11749b", - "sha256:ba05430e819e58544e840a68b03b28b6d328aff2e41579037e8bab7653b37d83", - "sha256:ca49e1ab99593438b204e00f3970e7a5f70d045267051dfa6b5f4304fcfa1dbf", - "sha256:ca5f18a75e1256ce07494e245cdb146f5a9267d3c702ebf9b65c7f8bd843431e", - "sha256:cd410a1cbb2d297c67d8521759ab2ee3f1d66206d2e4328502a487589a2cb21b", - "sha256:ce298e3d0c65bd03fa65ffcc6db0e2b578e8f626d468db64fdf8457731052942", - "sha256:d5ca078bb666c4a9d1287a379fe617a6dccd18c3e8a7e6c7e1eb8974330c626a", - "sha256:d5fd67df77bab0d3f4ea1d7afca9ef15c2ee35dfb348c7b57ffb9782a6e4db6e", - "sha256:da1a90c1ddb7531b1d5ff1e171b4ee61f6345119be7351104b67ff413843fe94", - "sha256:dba70f30fd81f8ce6d32ddeef37d91c8948e5d5a4c63242d16a2b2df8143aafc", - "sha256:dc07f021ee80510f3cd3af2cad5b6a3b3a10b057521d9e6aaeb621730d320c5a", - "sha256:dd33eb9bdcfbabab3459c9ee651d94c842bc8a05fabc95edf4ee0c15a072495e", - "sha256:e0538c43565ee6e703d3a7c3bdfe4037a5209250e8502c98f20fea6f5fdf2965", - "sha256:e1f54b9b4b6c53369f40028d2dd07a8c374583417ee6ec0ea304e710a20f80a0", - "sha256:e32d2a2b02ccbef10145df9135751abea1f9f076e67a4e261b05f24b94219e36", - "sha256:e6096b0688e6e14af6a1b10eaad86b4ff17935c49aa774eac7c95a57a4e8c296", - "sha256:e71255ba42567d34a13c03968736c5d39bb4a97ce98188fafb27ce981115beec", - "sha256:ed2e07c6a26ed4bea91b897ee2b0835c21716d9a469a96c3e878dc5f8c55bb23", - "sha256:eef2afb0fd1747f33f1ee3e209bce1ed582d1896b240ccc5e2697e3275f037c7", - "sha256:f23222527b307970e383433daec128d769ff778d9b29343fb3496472dc20dabe", - "sha256:f341ee2df0999bfdf7a95e448075effe0db212a59387de1a70690e4acb03d4c6", - "sha256:f5be7805e53dafe94d295399cfbe5227f39995a997f4fd8539bf3cbdc8f47ca8", - "sha256:f7f325be2804246a75a4f45c72d4ce80d2443ab815063cdf70ee8fb2ca59ee1b", - "sha256:f8af619e3be812a2059b212064ea7a640aff0568d972cd1b9e920837469eb3cb", - "sha256:fa8c626d6441e2d04b6ee703ef2d1e17608ad44c7cb75258c09dd42bacdfc64b", - "sha256:fbb9dc00e39f3e6c0ef48edee202f9520dafb233e8b51b06b8428cfcb92abd30", - "sha256:fff55f3ce50a3ff63ec8e2a8d3dd924f1941b250b0aac3d3d42b687eeff07a8e" - ], - "index": "pypi", - "version": "==2021.11.10" - }, - "semver": { - "hashes": [ - "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4", - "sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f" - ], - "index": "pypi", - "version": "==2.13.0" - }, - "setuptools": { - "hashes": [ - "sha256:273b6847ae61f7829c1affcdd9a32f67aa65233be508f4fbaab866c5faa4e408", - "sha256:d5340d16943a0f67057329db59b564e938bb3736c6e50ae16ea84d5e5d9ba6d0" - ], - "markers": "python_version >= '3.7'", - "version": "==63.3.0" - }, - "sqlalchemy": { - "hashes": [ - "sha256:047ef5ccd8860f6147b8ac6c45a4bc573d4e030267b45d9a1c47b55962ff0e6f", - "sha256:05a05771617bfa723ba4cef58d5b25ac028b0d68f28f403edebed5b8243b3a87", - "sha256:0ec54460475f0c42512895c99c63d90dd2d9cbd0c13491a184182e85074b04c5", - "sha256:107df519eb33d7f8e0d0d052128af2f25066c1a0f6b648fd1a9612ab66800b86", - "sha256:14ea8ff2d33c48f8e6c3c472111d893b9e356284d1482102da9678195e5a8eac", - "sha256:1745987ada1890b0e7978abdb22c133eca2e89ab98dc17939042240063e1ef21", - "sha256:1962dfee37b7fb17d3d4889bf84c4ea08b1c36707194c578f61e6e06d12ab90f", - "sha256:20bf65bcce65c538e68d5df27402b39341fabeecf01de7e0e72b9d9836c13c52", - "sha256:26146c59576dfe9c546c9f45397a7c7c4a90c25679492ff610a7500afc7d03a6", - "sha256:365b75938049ae31cf2176efd3d598213ddb9eb883fbc82086efa019a5f649df", - "sha256:4770eb3ba69ec5fa41c681a75e53e0e342ac24c1f9220d883458b5596888e43a", - "sha256:50e7569637e2e02253295527ff34666706dbb2bc5f6c61a5a7f44b9610c9bb09", - "sha256:5c2d19bfb33262bf987ef0062345efd0f54c4189c2d95159c72995457bf4a359", - "sha256:621f050e72cc7dfd9ad4594ff0abeaad954d6e4a2891545e8f1a53dcdfbef445", - "sha256:6d81de54e45f1d756785405c9d06cd17918c2eecc2d4262dc2d276ca612c2f61", - "sha256:6f95706da857e6e79b54c33c1214f5467aab10600aa508ddd1239d5df271986e", - "sha256:752ef2e8dbaa3c5d419f322e3632f00ba6b1c3230f65bc97c2ff5c5c6c08f441", - "sha256:7b2785dd2a0c044a36836857ac27310dc7a99166253551ee8f5408930958cc60", - "sha256:7f13644b15665f7322f9e0635129e0ef2098409484df67fcd225d954c5861559", - "sha256:8194896038753b46b08a0b0ae89a5d80c897fb601dd51e243ed5720f1f155d27", - "sha256:864d4f89f054819cb95e93100b7d251e4d114d1c60bc7576db07b046432af280", - "sha256:8b773c9974c272aae0fa7e95b576d98d17ee65f69d8644f9b6ffc90ee96b4d19", - "sha256:8f901be74f00a13bf375241a778455ee864c2c21c79154aad196b7a994e1144f", - "sha256:91d2b89bb0c302f89e753bea008936acfa4e18c156fb264fe41eb6bbb2bbcdeb", - "sha256:b0538b66f959771c56ff996d828081908a6a52a47c5548faed4a3d0a027a5368", - "sha256:b30e70f1594ee3c8902978fd71900d7312453922827c4ce0012fa6a8278d6df4", - "sha256:b71be98ef6e180217d1797185c75507060a57ab9cd835653e0112db16a710f0d", - "sha256:c6d00cb9da8d0cbfaba18cad046e94b06de6d4d0ffd9d4095a3ad1838af22528", - "sha256:d1f665e50592caf4cad3caed3ed86f93227bffe0680218ccbb293bd5a6734ca8", - "sha256:e6e2c8581c6620136b9530137954a8376efffd57fe19802182c7561b0ab48b48", - "sha256:e7a7667d928ba6ee361a3176e1bef6847c1062b37726b33505cc84136f657e0d", - "sha256:ec3985c883d6d217cf2013028afc6e3c82b8907192ba6195d6e49885bfc4b19d", - "sha256:ede13a472caa85a13abe5095e71676af985d7690eaa8461aeac5c74f6600b6c0", - "sha256:f24d4d6ec301688c59b0c4bb1c1c94c5d0bff4ecad33bb8f5d9efdfb8d8bc925", - "sha256:f2a42acc01568b9701665e85562bbff78ec3e21981c7d51d56717c22e5d3d58b", - "sha256:fbc076f79d830ae4c9d49926180a1140b49fa675d0f0d555b44c9a15b29f4c80" - ], - "index": "pypi", - "version": "==1.4.39" - }, - "typing-extensions": { - "hashes": [ - "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02", - "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6" - ], - "markers": "python_version >= '3.7'", - "version": "==4.3.0" - }, - "tzlocal": { - "hashes": [ - "sha256:643c97c5294aedc737780a49d9df30889321cbe1204eac2c2ec6134035a92e44", - "sha256:e2cb6c6b5b604af38597403e9852872d7f534962ae2954c7f35efcb1ccacf4a4" - ], - "index": "pypi", - "version": "==2.1" - }, - "yarl": { - "hashes": [ - "sha256:04e7bf550c829c0938e85f8890d67009cd3e01a932df95784825e2f6ca7fa9c7", - "sha256:0602496cddf713a6912e488a7d9adf41b6b6c037a0bc134efe036c3ac32c76c8", - "sha256:0e46322af73f0489ce19387ce7684a44fe364424820d7f8c456b0c8006cccf29", - "sha256:15a982861d07c4660e03a11f212465f0d88b87e15f4c8d7d5c5dc46a8440c9cc", - "sha256:21bc84c8bba3bccc5aaac6ea155933c5d7960e805151e15137f6c7a0c528cb03", - "sha256:27272989b83a0b4226fb3c8e9e7a88732d4abb73bed1591702ddd68f1561a49a", - "sha256:2ff9a28884b551bd104a9f3bdeb48961f0df7fc74fca58d3493a679aabf6b8ef", - "sha256:31936eca866d600f0ec14ce070b484838a9fe3ee3ec8912b76298cc8884f9078", - "sha256:35a7a8bb7af9c4ae54285e0473fba5aa8b7b737b5bcf57032bf4a50b74f9afc2", - "sha256:3b48fe07c58e45a2ed4ca7b93fdfa59f79b637a1d9867fae5f022a83abe86d48", - "sha256:3b54d1a434c05d4783865b98f6b31adc192775094ab4dd2bece0ba067a1396fd", - "sha256:3ce3b9efc17562040d30d4776d80453bc4a50a4dd884c83cbf257a05888b8a11", - "sha256:4085a5f8cda7a36870d4ecaec76818f8ab4c8140fdbc6df21bc29e254c3fae79", - "sha256:45ae1e36906007c1be5fb9b1fff2c80a719496c026b970443839e93656111f38", - "sha256:47cc3cc1c9cd7ff9a322157b0e1d5849158996fcfa1a715134dfaa7ea297fd20", - "sha256:532376ff0c756c97c83b83037e7c784089e1628a6cd22131c2e544e79fda01d7", - "sha256:53488178c22aea9acdde278277fe0ce310fd705b10fb35b8c3d5d5aad9e33af7", - "sha256:538b4b5a41b7c96c735406dd7518dfa3166e6a37b9ef2543a320b7e94b6d7a12", - "sha256:53aeb54d5d6e423f526498e2086796eb28989fb774433e17ce81f9e5aef0d9ae", - "sha256:5637c50ceef5b06420b002048f67d487573c517c9aa5ccf2aec12f6c836a2f92", - "sha256:5e7861a1cc27da656338335fd7ec0b2844ebe7c1ea03b75b21b2875a9dd0e5dc", - "sha256:60726e212921491555bad6aa69d0f25141b8c0acda6b47cc3fa619b1d2db09f8", - "sha256:6aa89cbc2b435481b41478204fa79addc9abef76ea887c7500ddf391e18e3871", - "sha256:71420b1161617cd1ccbbbbb06db3b33e21a3771394f078d1416eb19d79e4c416", - "sha256:785e71a55363dfc505cea8681cb9f35994156cf34742e136721d9d988febdc57", - "sha256:7e0311c7a42fd6ffc0dfbfe688f6fe58359065a7ef8a2d952a5cb1aefde6a31b", - "sha256:7f7fd3ec57065521236e3a8eb094d77480e85022bed8b5bed0d7cf613842012a", - "sha256:805f22865d04b0de1aa52e3c327d49bcb76e34c73637fc9546d69d5bdc096a41", - "sha256:84a3ea22a9097994ea2cc61b0b62d9b204a3c09067bdf1499d60e1130e175d7a", - "sha256:8abe9e25efbb67028c764061758e9dc83d6b0193a884e1b278a512f8413deac6", - "sha256:8cdef68334fcd0ba3d5e81c5c3ebdbc215a2b7fef06e117a7b694ad0709f5916", - "sha256:8d8b7bb315588ef1d5ef92d0eea418fa9f513c8a4dfd0b594e6ddeca78a7508b", - "sha256:8e4c9beebb829d244d315cbe1575022d33652fc7b02105c40d8188cb63135077", - "sha256:91ea2bbf9efa9651e132a888b65eadbd291fac2e2e8031632e1da1b51a6d3fd8", - "sha256:945d50d7cb79099a4f423bf8c5d81add00dcb0002e505e2009fd314894fe12f2", - "sha256:94c40a9f9b9fb3a7c51b62dce35b35770cfd14325032e9b272d702268a06319c", - "sha256:a11235bf975a369a77bdc1daf34acfd4239ef3d20e474ec5b638ca4d992f0219", - "sha256:a2ccefbee8ec1e4df73248713f6d9c9f9832466305036675da1819ac0ee1233d", - "sha256:b50fa656491ecf957de375749f2e7f1c69ed6cd540b03d437f58f74dfdcc9b30", - "sha256:b8658ed44af257358178378e921a11a6a1f4b54806118a9213d67e8f842f8b40", - "sha256:ba723d989185640bfd948116c0cf9f5f0c7b6f5c94a1d15d48c4870974ef6e9d", - "sha256:be86c3ea85b2d48e5885a2310a1363591d95bf0554a36a06f881109fdf3f351a", - "sha256:c41af1770f7f24637dd0fadc22e2f0398d3e6ebe25cfe3068ac0b942ab5eaa05", - "sha256:c50f6f5ee11244179428825daf89dc58bc3b0620385ce1812052f903fdd6ba64", - "sha256:c6bc788161d6358b055a9f1439ba18c27fb5ad416c7297f7308b8aacfd894d89", - "sha256:c92a10e4684aa6c5e545f07164b8f92a189f369dccd3ebd68a280903eb9e9e7c", - "sha256:cd3c01c1c57cd3b77ed635aa2589beccc265ddc67d8600f0474dd0b5f9306b90", - "sha256:d5fd25d3bee3b900b1194d6a649f39eb5527302156ee02b83176527d02eca1fc", - "sha256:d65aa55e9d86b24691092d85e1d2904f67707a67c8df4a8945f1f9fe0bd23a82", - "sha256:d846b34b14cc27e6f22a5e0fc4ad21f4b03f351286e0bef31d3fa872e218bedb", - "sha256:dda8df8a510465c7ee997b358c874194921c0c0cc5bf5e31c83d7cecd66af3cf", - "sha256:e311415a34126d56e4efabd78f18128457a816a09e084523b77162a7af1885a6", - "sha256:e70f87f8f0f49fba56232e6d4b9b64f87433f5f3b41d06221f462ce51531975c", - "sha256:e9016af1eb7f02568690c500535187ffaf56878ed6396c6b1d4316ab10829ecf", - "sha256:eb921dcb4bd91af9ea237dc0c802c948c182780564764fb9320742fd9db07248", - "sha256:ebc8e626fb1b8ee078ec12b8e7f9c78549e34cf4c2c6e56e027a29c501458c81", - "sha256:ecd0f629ca1cf92afa66c6a3d58d82fd5751d2b890464390d956ee79d7da55e2", - "sha256:f8bea1803054f8ff0f67d1cb935310228aaa29248d2690880577536c42c11c50", - "sha256:fa588d5740c3419fd484be34697360001020d48095e6c0504f613046a35a4941" - ], - "markers": "python_version >= '3.7'", - "version": "==1.8.0" - } - }, - "develop": { - "atomicwrites": { - "hashes": [ - "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11" - ], - "markers": "sys_platform == 'win32'", - "version": "==1.4.1" - }, - "attrs": { - "hashes": [ - "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6", - "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c" - ], - "markers": "python_version >= '3.5'", - "version": "==22.1.0" - }, - "backcall": { - "hashes": [ - "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e", - "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255" - ], - "version": "==0.2.0" - }, - "colorama": { - "hashes": [ - "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da", - "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4" - ], - "markers": "sys_platform == 'win32'", - "version": "==0.4.5" - }, - "coverage": { - "hashes": [ - "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c", - "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6", - "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45", - "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a", - "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03", - "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529", - "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a", - "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a", - "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2", - "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6", - "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759", - "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53", - "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a", - "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4", - "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff", - "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502", - "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793", - "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb", - "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905", - "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821", - "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b", - "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81", - "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0", - "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b", - "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3", - "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184", - "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701", - "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a", - "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82", - "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638", - "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5", - "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083", - "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6", - "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90", - "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465", - "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a", - "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3", - "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e", - "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066", - "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf", - "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b", - "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae", - "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669", - "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873", - "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b", - "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6", - "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb", - "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160", - "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c", - "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079", - "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d", - "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6" - ], - "index": "pypi", - "version": "==5.5" - }, - "decorator": { - "hashes": [ - "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330", - "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186" - ], - "markers": "python_version >= '3.5'", - "version": "==5.1.1" - }, - "gprof2dot": { - "hashes": [ - "sha256:45b4d298bd36608fccf9511c3fd88a773f7a1abc04d6cd39445b11ba43133ec5", - "sha256:f165b3851d3c52ee4915eb1bd6cca571e5759823c2cd0f71a79bda93c2dc85d6" - ], - "markers": "python_version >= '2.7'", - "version": "==2022.7.29" - }, - "iniconfig": { - "hashes": [ - "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", - "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" - ], - "version": "==1.1.1" - }, - "ipython": { - "hashes": [ - "sha256:af3bdb46aa292bce5615b1b2ebc76c2080c5f77f54bda2ec72461317273e7cd6", - "sha256:c175d2440a1caff76116eb719d40538fbb316e214eda85c5515c303aacbfb23e" - ], - "index": "pypi", - "version": "==7.34.0" - }, - "jedi": { - "hashes": [ - "sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d", - "sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab" - ], - "markers": "python_version >= '3.6'", - "version": "==0.18.1" - }, - "matplotlib-inline": { - "hashes": [ - "sha256:a04bfba22e0d1395479f866853ec1ee28eea1485c1d69a6faf00dc3e24ff34ee", - "sha256:aed605ba3b72462d64d475a21a9296f400a19c4f74a31b59103d2a99ffd5aa5c" - ], - "markers": "python_version >= '3.5'", - "version": "==0.1.3" - }, - "nest-asyncio": { - "hashes": [ - "sha256:b98e3ec1b246135e4642eceffa5a6c23a3ab12c82ff816a92c612d68205813b2", - "sha256:e442291cd942698be619823a17a86a5759eabe1f8613084790de189fe9e16d65" - ], - "index": "pypi", - "version": "==1.5.5" - }, - "packaging": { - "hashes": [ - "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", - "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" - ], - "markers": "python_version >= '3.6'", - "version": "==21.3" - }, - "parso": { - "hashes": [ - "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0", - "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75" - ], - "markers": "python_version >= '3.6'", - "version": "==0.8.3" - }, - "pickleshare": { - "hashes": [ - "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca", - "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56" - ], - "version": "==0.7.5" - }, - "pluggy": { - "hashes": [ - "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", - "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" - ], - "markers": "python_version >= '3.6'", - "version": "==1.0.0" - }, - "prompt-toolkit": { - "hashes": [ - "sha256:859b283c50bde45f5f97829f77a4674d1c1fcd88539364f1b28a37805cfd89c0", - "sha256:d8916d3f62a7b67ab353a952ce4ced6a1d2587dfe9ef8ebc30dd7c386751f289" - ], - "markers": "python_full_version >= '3.6.2'", - "version": "==3.0.30" - }, - "py": { - "hashes": [ - "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", - "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==1.11.0" - }, - "pygments": { - "hashes": [ - "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb", - "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519" - ], - "markers": "python_version >= '3.6'", - "version": "==2.12.0" - }, - "pyparsing": { - "hashes": [ - "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb", - "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc" - ], - "markers": "python_full_version >= '3.6.8'", - "version": "==3.0.9" - }, - "pytest": { - "hashes": [ - "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89", - "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134" - ], - "index": "pypi", - "version": "==6.2.5" - }, - "pytest-asyncio": { - "hashes": [ - "sha256:7a97e37cfe1ed296e2e84941384bdd37c376453912d397ed39293e0916f521fa", - "sha256:ac4ebf3b6207259750bc32f4c1d8fcd7e79739edbc67ad0c58dd150b1d072fed" - ], - "index": "pypi", - "version": "==0.19.0" - }, - "pytest-profiling": { - "hashes": [ - "sha256:3b255f9db36cb2dd7536a8e7e294c612c0be7f7850a7d30754878e4315d56600", - "sha256:6bce4e2edc04409d2f3158c16750fab8074f62d404cc38eeb075dff7fcbb996c", - "sha256:93938f147662225d2b8bd5af89587b979652426a8a6ffd7e73ec4a23e24b7f29", - "sha256:999cc9ac94f2e528e3f5d43465da277429984a1c237ae9818f8cfd0b06acb019" - ], - "index": "pypi", - "version": "==1.7.0" - }, - "setuptools": { - "hashes": [ - "sha256:273b6847ae61f7829c1affcdd9a32f67aa65233be508f4fbaab866c5faa4e408", - "sha256:d5340d16943a0f67057329db59b564e938bb3736c6e50ae16ea84d5e5d9ba6d0" - ], - "markers": "python_version >= '3.7'", - "version": "==63.3.0" - }, - "six": { - "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.16.0" - }, - "toml": { - "hashes": [ - "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", - "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" - ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==0.10.2" - }, - "traitlets": { - "hashes": [ - "sha256:0bb9f1f9f017aa8ec187d8b1b2a7a6626a2a1d877116baba52a129bfa124f8e2", - "sha256:65fa18961659635933100db8ca120ef6220555286949774b9cfc106f941d1c7a" - ], - "markers": "python_version >= '3.7'", - "version": "==5.3.0" - }, - "wcwidth": { - "hashes": [ - "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784", - "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83" - ], - "version": "==0.2.5" - }, - "yappi": { - "hashes": [ - "sha256:f54c25f04aa7c613633b529bffd14e0699a4363f414dc9c065616fd52064a49b" - ], - "index": "pypi", - "version": "==1.3.5" - } - } -} From 6fad53760fc426027792c5ff3d0532d3475b9c11 Mon Sep 17 00:00:00 2001 From: Phanabani Date: Tue, 8 Nov 2022 21:35:30 -0500 Subject: [PATCH 02/33] Add poetry's `pyproject.toml`. --- pyproject.toml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..ff83594 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,34 @@ +[tool.poetry] +name = "sandpiper" +version = "2.0.0" +description = "A Discord bot that makes it easier to communicate with friends around the world." +authors = ["Phanabani "] +license = "MIT" +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.9" +aiosqlite = "^0.17.0" +alembic = "^1.7.5" +certifi = "*" +"discord.py" = "^1.7" +fuzzywuzzy = {extras = ["speedup"], version = "^0.18.0"} +pytz = "*" +regex = "^2021.11.10" +semver = "^2.13.0" +SQLAlchemy = "^1.4.22" +tzlocal = "^4.1" +Pint = "^0.17" + +[tool.poetry.dev-dependencies] +coverage = "^6.2" +ipython = "^7.30.1" +nest-asyncio = "^1.5.4" +pytest = "^6.2.5" +pytest-asyncio = "^0.16.0" +pytest-profiling = "^1.7.0" +yappi = "^1.3.3" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" From b530c59eda92d068d34451f53806906eff9df49e Mon Sep 17 00:00:00 2001 From: Phanabani Date: Tue, 8 Nov 2022 21:39:50 -0500 Subject: [PATCH 03/33] Don't ignore `.python-version`. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8c4c632..e3c7588 100644 --- a/.gitignore +++ b/.gitignore @@ -88,7 +88,7 @@ profile_default/ ipython_config.py # pyenv -.python-version +# .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. From 354e8bc8946a319993091f84fd949f7088db3358 Mon Sep 17 00:00:00 2001 From: Phanabani Date: Tue, 8 Nov 2022 21:40:22 -0500 Subject: [PATCH 04/33] Upgrade Python to ^3.10. --- .python-version | 1 + pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 .python-version diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..ac957df --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.10.6 diff --git a/pyproject.toml b/pyproject.toml index ff83594..8669d61 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ license = "MIT" readme = "README.md" [tool.poetry.dependencies] -python = "^3.9" +python = "^3.10" aiosqlite = "^0.17.0" alembic = "^1.7.5" certifi = "*" From 710c24b9664d00c169dd8fe5d8e5f11a57d6d54b Mon Sep 17 00:00:00 2001 From: Phanabani Date: Tue, 8 Nov 2022 21:58:19 -0500 Subject: [PATCH 05/33] Generate lock file. --- poetry.lock | 1538 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1538 insertions(+) create mode 100644 poetry.lock diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..9d60915 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,1538 @@ +[[package]] +name = "aiohttp" +version = "3.7.4.post0" +description = "Async http client/server framework (asyncio)" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +async-timeout = ">=3.0,<4.0" +attrs = ">=17.3.0" +chardet = ">=2.0,<5.0" +multidict = ">=4.5,<7.0" +typing-extensions = ">=3.6.5" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["aiodns", "brotlipy", "cchardet"] + +[[package]] +name = "aiosqlite" +version = "0.17.0" +description = "asyncio bridge to the standard sqlite3 module" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +typing_extensions = ">=3.7.2" + +[[package]] +name = "alembic" +version = "1.8.1" +description = "A database migration tool for SQLAlchemy." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +Mako = "*" +SQLAlchemy = ">=1.3.0" + +[package.extras] +tz = ["python-dateutil"] + +[[package]] +name = "appnope" +version = "0.1.3" +description = "Disable App Nap on macOS >= 10.9" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "async-timeout" +version = "3.0.1" +description = "Timeout context manager for asyncio programs" +category = "main" +optional = false +python-versions = ">=3.5.3" + +[[package]] +name = "atomicwrites" +version = "1.4.1" +description = "Atomic file writes." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "22.1.0" +description = "Classes Without Boilerplate" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.extras] +dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] +docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] +tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] +tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] + +[[package]] +name = "backcall" +version = "0.2.0" +description = "Specifications for callback functions passed in to an API" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "certifi" +version = "2022.9.24" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "chardet" +version = "4.0.0" +description = "Universal encoding detector for Python 2 and 3" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" + +[[package]] +name = "coverage" +version = "6.5.0" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "decorator" +version = "5.1.1" +description = "Decorators for Humans" +category = "dev" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "discord-py" +version = "1.7.3" +description = "A Python wrapper for the Discord API" +category = "main" +optional = false +python-versions = ">=3.5.3" + +[package.dependencies] +aiohttp = ">=3.6.0,<3.8.0" + +[package.extras] +docs = ["sphinx (==3.0.3)", "sphinxcontrib-trio (==1.1.2)", "sphinxcontrib-websupport"] +voice = ["PyNaCl (>=1.3.0,<1.5)"] + +[[package]] +name = "fuzzywuzzy" +version = "0.18.0" +description = "Fuzzy string matching in python" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +python-levenshtein = {version = ">=0.12", optional = true, markers = "extra == \"speedup\""} + +[package.extras] +speedup = ["python-levenshtein (>=0.12)"] + +[[package]] +name = "gprof2dot" +version = "2022.7.29" +description = "Generate a dot graph from the output of several profilers." +category = "dev" +optional = false +python-versions = ">=2.7" + +[[package]] +name = "greenlet" +version = "2.0.1" +description = "Lightweight in-process concurrent programming" +category = "main" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" + +[package.extras] +docs = ["Sphinx", "docutils (<0.18)"] +test = ["faulthandler", "objgraph", "psutil"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "ipython" +version = "7.34.0" +description = "IPython: Productive Interactive Computing" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +appnope = {version = "*", markers = "sys_platform == \"darwin\""} +backcall = "*" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} +pickleshare = "*" +prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" +pygments = "*" +setuptools = ">=18.5" +traitlets = ">=4.2" + +[package.extras] +all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.17)", "pygments", "qtconsole", "requests", "testpath"] +doc = ["Sphinx (>=1.3)"] +kernel = ["ipykernel"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["ipywidgets", "notebook"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["ipykernel", "nbformat", "nose (>=0.10.1)", "numpy (>=1.17)", "pygments", "requests", "testpath"] + +[[package]] +name = "jedi" +version = "0.18.1" +description = "An autocompletion tool for Python that can be used for text editors." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +parso = ">=0.8.0,<0.9.0" + +[package.extras] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<7.0.0)"] + +[[package]] +name = "levenshtein" +version = "0.20.8" +description = "Python extension for computing string edit distances and similarities." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +rapidfuzz = ">=2.3.0,<3.0.0" + +[[package]] +name = "mako" +version = "1.2.3" +description = "A super-fast templating language that borrows the best ideas from the existing templating languages." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +MarkupSafe = ">=0.9.2" + +[package.extras] +babel = ["Babel"] +lingua = ["lingua"] +testing = ["pytest"] + +[[package]] +name = "markupsafe" +version = "2.1.1" +description = "Safely add untrusted strings to HTML/XML markup." +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "matplotlib-inline" +version = "0.1.6" +description = "Inline Matplotlib backend for Jupyter" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +traitlets = "*" + +[[package]] +name = "multidict" +version = "6.0.2" +description = "multidict implementation" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "nest-asyncio" +version = "1.5.6" +description = "Patch asyncio to allow nested event loops" +category = "dev" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "packaging" +version = "21.3" +description = "Core utilities for Python packages" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" + +[[package]] +name = "parso" +version = "0.8.3" +description = "A Python Parser" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["docopt", "pytest (<6.0.0)"] + +[[package]] +name = "pexpect" +version = "4.8.0" +description = "Pexpect allows easy control of interactive console applications." +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "pickleshare" +version = "0.7.5" +description = "Tiny 'shelve'-like database with concurrency support" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "pint" +version = "0.17" +description = "Physical quantities module" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +packaging = "*" + +[package.extras] +numpy = ["numpy (>=1.14)"] +test = ["pytest", "pytest-cov", "pytest-mpl", "pytest-subtests"] +uncertainties = ["uncertainties (>=3.0)"] + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "prompt-toolkit" +version = "3.0.32" +description = "Library for building powerful interactive command lines in Python" +category = "dev" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +wcwidth = "*" + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "py" +version = "1.11.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pygments" +version = "2.13.0" +description = "Pygments is a syntax highlighting package written in Python." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +plugins = ["importlib-metadata"] + +[[package]] +name = "pyparsing" +version = "3.0.9" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +category = "main" +optional = false +python-versions = ">=3.6.8" + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pytest" +version = "6.2.5" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +py = ">=1.8.2" +toml = "*" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +name = "pytest-asyncio" +version = "0.16.0" +description = "Pytest support for asyncio." +category = "dev" +optional = false +python-versions = ">= 3.6" + +[package.dependencies] +pytest = ">=5.4.0" + +[package.extras] +testing = ["coverage", "hypothesis (>=5.7.1)"] + +[[package]] +name = "pytest-profiling" +version = "1.7.0" +description = "Profiling plugin for py.test" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +gprof2dot = "*" +pytest = "*" +six = "*" + +[package.extras] +tests = ["pytest-virtualenv"] + +[[package]] +name = "python-levenshtein" +version = "0.20.8" +description = "Python extension for computing string edit distances and similarities." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +Levenshtein = "0.20.8" + +[[package]] +name = "pytz" +version = "2022.6" +description = "World timezone definitions, modern and historical" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pytz-deprecation-shim" +version = "0.1.0.post0" +description = "Shims to make deprecation of pytz easier" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" + +[package.dependencies] +tzdata = {version = "*", markers = "python_version >= \"3.6\""} + +[[package]] +name = "rapidfuzz" +version = "2.13.2" +description = "rapid fuzzy string matching" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +full = ["numpy"] + +[[package]] +name = "regex" +version = "2021.11.10" +description = "Alternative regular expression module, to replace re." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "semver" +version = "2.13.0" +description = "Python helper for Semantic Versioning (http://semver.org/)" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "setuptools" +version = "65.5.1" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "sqlalchemy" +version = "1.4.43" +description = "Database Abstraction Library" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} + +[package.extras] +aiomysql = ["aiomysql", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"] +mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx_oracle (>=7)", "cx_oracle (>=7,<8)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +pymysql = ["pymysql", "pymysql (<1)"] +sqlcipher = ["sqlcipher3_binary"] + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "traitlets" +version = "5.5.0" +description = "" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["pre-commit", "pytest"] + +[[package]] +name = "typing-extensions" +version = "4.4.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "tzdata" +version = "2022.6" +description = "Provider of IANA time zone data" +category = "main" +optional = false +python-versions = ">=2" + +[[package]] +name = "tzlocal" +version = "4.2" +description = "tzinfo object for the local timezone" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pytz-deprecation-shim = "*" +tzdata = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +devenv = ["black", "pyroma", "pytest-cov", "zest.releaser"] +test = ["pytest (>=4.3)", "pytest-mock (>=3.3)"] + +[[package]] +name = "wcwidth" +version = "0.2.5" +description = "Measures the displayed width of unicode strings in a terminal" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "yappi" +version = "1.4.0" +description = "Yet Another Python Profiler" +category = "dev" +optional = false +python-versions = "*" + +[package.extras] +test = ["gevent (>=20.6.2)"] + +[[package]] +name = "yarl" +version = "1.8.1" +description = "Yet another URL library" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + +[metadata] +lock-version = "1.1" +python-versions = "^3.10" +content-hash = "4151a86ee8c266c9502024fe74751b3def7547afea99a529cd217e5ae8de11c6" + +[metadata.files] +aiohttp = [ + {file = "aiohttp-3.7.4.post0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:3cf75f7cdc2397ed4442594b935a11ed5569961333d49b7539ea741be2cc79d5"}, + {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:4b302b45040890cea949ad092479e01ba25911a15e648429c7c5aae9650c67a8"}, + {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:fe60131d21b31fd1a14bd43e6bb88256f69dfc3188b3a89d736d6c71ed43ec95"}, + {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:393f389841e8f2dfc86f774ad22f00923fdee66d238af89b70ea314c4aefd290"}, + {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:c6e9dcb4cb338d91a73f178d866d051efe7c62a7166653a91e7d9fb18274058f"}, + {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:5df68496d19f849921f05f14f31bd6ef53ad4b00245da3195048c69934521809"}, + {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:0563c1b3826945eecd62186f3f5c7d31abb7391fedc893b7e2b26303b5a9f3fe"}, + {file = "aiohttp-3.7.4.post0-cp36-cp36m-win32.whl", hash = "sha256:3d78619672183be860b96ed96f533046ec97ca067fd46ac1f6a09cd9b7484287"}, + {file = "aiohttp-3.7.4.post0-cp36-cp36m-win_amd64.whl", hash = "sha256:f705e12750171c0ab4ef2a3c76b9a4024a62c4103e3a55dd6f99265b9bc6fcfc"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:230a8f7e24298dea47659251abc0fd8b3c4e38a664c59d4b89cca7f6c09c9e87"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2e19413bf84934d651344783c9f5e22dee452e251cfd220ebadbed2d9931dbf0"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e4b2b334e68b18ac9817d828ba44d8fcb391f6acb398bcc5062b14b2cbeac970"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:d012ad7911653a906425d8473a1465caa9f8dea7fcf07b6d870397b774ea7c0f"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:40eced07f07a9e60e825554a31f923e8d3997cfc7fb31dbc1328c70826e04cde"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:209b4a8ee987eccc91e2bd3ac36adee0e53a5970b8ac52c273f7f8fd4872c94c"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:14762875b22d0055f05d12abc7f7d61d5fd4fe4642ce1a249abdf8c700bf1fd8"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-win32.whl", hash = "sha256:7615dab56bb07bff74bc865307aeb89a8bfd9941d2ef9d817b9436da3a0ea54f"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-win_amd64.whl", hash = "sha256:d9e13b33afd39ddeb377eff2c1c4f00544e191e1d1dee5b6c51ddee8ea6f0cf5"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:547da6cacac20666422d4882cfcd51298d45f7ccb60a04ec27424d2f36ba3eaf"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:af9aa9ef5ba1fd5b8c948bb11f44891968ab30356d65fd0cc6707d989cd521df"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:64322071e046020e8797117b3658b9c2f80e3267daec409b350b6a7a05041213"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:bb437315738aa441251214dad17428cafda9cdc9729499f1d6001748e1d432f4"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:e54962802d4b8b18b6207d4a927032826af39395a3bd9196a5af43fc4e60b009"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:a00bb73540af068ca7390e636c01cbc4f644961896fa9363154ff43fd37af2f5"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:79ebfc238612123a713a457d92afb4096e2148be17df6c50fb9bf7a81c2f8013"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-win32.whl", hash = "sha256:515dfef7f869a0feb2afee66b957cc7bbe9ad0cdee45aec7fdc623f4ecd4fb16"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-win_amd64.whl", hash = "sha256:114b281e4d68302a324dd33abb04778e8557d88947875cbf4e842c2c01a030c5"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:7b18b97cf8ee5452fa5f4e3af95d01d84d86d32c5e2bfa260cf041749d66360b"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:15492a6368d985b76a2a5fdd2166cddfea5d24e69eefed4630cbaae5c81d89bd"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bdb230b4943891321e06fc7def63c7aace16095be7d9cf3b1e01be2f10fba439"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:cffe3ab27871bc3ea47df5d8f7013945712c46a3cc5a95b6bee15887f1675c22"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:f881853d2643a29e643609da57b96d5f9c9b93f62429dcc1cbb413c7d07f0e1a"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:a5ca29ee66f8343ed336816c553e82d6cade48a3ad702b9ffa6125d187e2dedb"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:17c073de315745a1510393a96e680d20af8e67e324f70b42accbd4cb3315c9fb"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-win32.whl", hash = "sha256:932bb1ea39a54e9ea27fc9232163059a0b8855256f4052e776357ad9add6f1c9"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-win_amd64.whl", hash = "sha256:02f46fc0e3c5ac58b80d4d56eb0a7c7d97fcef69ace9326289fb9f1955e65cfe"}, + {file = "aiohttp-3.7.4.post0.tar.gz", hash = "sha256:493d3299ebe5f5a7c66b9819eacdcfbbaaf1a8e84911ddffcdc48888497afecf"}, +] +aiosqlite = [ + {file = "aiosqlite-0.17.0-py3-none-any.whl", hash = "sha256:6c49dc6d3405929b1d08eeccc72306d3677503cc5e5e43771efc1e00232e8231"}, + {file = "aiosqlite-0.17.0.tar.gz", hash = "sha256:f0e6acc24bc4864149267ac82fb46dfb3be4455f99fe21df82609cc6e6baee51"}, +] +alembic = [ + {file = "alembic-1.8.1-py3-none-any.whl", hash = "sha256:0a024d7f2de88d738d7395ff866997314c837be6104e90c5724350313dee4da4"}, + {file = "alembic-1.8.1.tar.gz", hash = "sha256:cd0b5e45b14b706426b833f06369b9a6d5ee03f826ec3238723ce8caaf6e5ffa"}, +] +appnope = [ + {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, + {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, +] +async-timeout = [ + {file = "async-timeout-3.0.1.tar.gz", hash = "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f"}, + {file = "async_timeout-3.0.1-py3-none-any.whl", hash = "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"}, +] +atomicwrites = [ + {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, +] +attrs = [ + {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, + {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, +] +backcall = [ + {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, + {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, +] +certifi = [ + {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, + {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, +] +chardet = [ + {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, + {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, +] +colorama = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] +coverage = [ + {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"}, + {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"}, + {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"}, + {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"}, + {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"}, + {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"}, + {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"}, + {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"}, + {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"}, + {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"}, + {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"}, + {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"}, + {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"}, + {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"}, + {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"}, + {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"}, + {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"}, + {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"}, + {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"}, + {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, +] +decorator = [ + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, +] +discord-py = [ + {file = "discord.py-1.7.3-py3-none-any.whl", hash = "sha256:c6f64db136de0e18e090f6752ea68bdd4ab0a61b82dfe7acecefa22d6477bb0c"}, + {file = "discord.py-1.7.3.tar.gz", hash = "sha256:462cd0fe307aef8b29cbfa8dd613e548ae4b2cb581d46da9ac0d46fb6ea19408"}, +] +fuzzywuzzy = [ + {file = "fuzzywuzzy-0.18.0-py2.py3-none-any.whl", hash = "sha256:928244b28db720d1e0ee7587acf660ea49d7e4c632569cad4f1cd7e68a5f0993"}, + {file = "fuzzywuzzy-0.18.0.tar.gz", hash = "sha256:45016e92264780e58972dca1b3d939ac864b78437422beecebb3095f8efd00e8"}, +] +gprof2dot = [ + {file = "gprof2dot-2022.7.29-py2.py3-none-any.whl", hash = "sha256:f165b3851d3c52ee4915eb1bd6cca571e5759823c2cd0f71a79bda93c2dc85d6"}, + {file = "gprof2dot-2022.7.29.tar.gz", hash = "sha256:45b4d298bd36608fccf9511c3fd88a773f7a1abc04d6cd39445b11ba43133ec5"}, +] +greenlet = [ + {file = "greenlet-2.0.1-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:9ed358312e63bf683b9ef22c8e442ef6c5c02973f0c2a939ec1d7b50c974015c"}, + {file = "greenlet-2.0.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:4f09b0010e55bec3239278f642a8a506b91034f03a4fb28289a7d448a67f1515"}, + {file = "greenlet-2.0.1-cp27-cp27m-win32.whl", hash = "sha256:1407fe45246632d0ffb7a3f4a520ba4e6051fc2cbd61ba1f806900c27f47706a"}, + {file = "greenlet-2.0.1-cp27-cp27m-win_amd64.whl", hash = "sha256:3001d00eba6bbf084ae60ec7f4bb8ed375748f53aeaefaf2a37d9f0370558524"}, + {file = "greenlet-2.0.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d566b82e92ff2e09dd6342df7e0eb4ff6275a3f08db284888dcd98134dbd4243"}, + {file = "greenlet-2.0.1-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:0722c9be0797f544a3ed212569ca3fe3d9d1a1b13942d10dd6f0e8601e484d26"}, + {file = "greenlet-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d37990425b4687ade27810e3b1a1c37825d242ebc275066cfee8cb6b8829ccd"}, + {file = "greenlet-2.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be35822f35f99dcc48152c9839d0171a06186f2d71ef76dc57fa556cc9bf6b45"}, + {file = "greenlet-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c140e7eb5ce47249668056edf3b7e9900c6a2e22fb0eaf0513f18a1b2c14e1da"}, + {file = "greenlet-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d21681f09e297a5adaa73060737e3aa1279a13ecdcfcc6ef66c292cb25125b2d"}, + {file = "greenlet-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fb412b7db83fe56847df9c47b6fe3f13911b06339c2aa02dcc09dce8bbf582cd"}, + {file = "greenlet-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:c6a08799e9e88052221adca55741bf106ec7ea0710bca635c208b751f0d5b617"}, + {file = "greenlet-2.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9e112e03d37987d7b90c1e98ba5e1b59e1645226d78d73282f45b326f7bddcb9"}, + {file = "greenlet-2.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56961cfca7da2fdd178f95ca407fa330c64f33289e1804b592a77d5593d9bd94"}, + {file = "greenlet-2.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:13ba6e8e326e2116c954074c994da14954982ba2795aebb881c07ac5d093a58a"}, + {file = "greenlet-2.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bf633a50cc93ed17e494015897361010fc08700d92676c87931d3ea464123ce"}, + {file = "greenlet-2.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9f2c221eecb7ead00b8e3ddb913c67f75cba078fd1d326053225a3f59d850d72"}, + {file = "greenlet-2.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:13ebf93c343dd8bd010cd98e617cb4c1c1f352a0cf2524c82d3814154116aa82"}, + {file = "greenlet-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:6f61d71bbc9b4a3de768371b210d906726535d6ca43506737682caa754b956cd"}, + {file = "greenlet-2.0.1-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:2d0bac0385d2b43a7bd1d651621a4e0f1380abc63d6fb1012213a401cbd5bf8f"}, + {file = "greenlet-2.0.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:f6327b6907b4cb72f650a5b7b1be23a2aab395017aa6f1adb13069d66360eb3f"}, + {file = "greenlet-2.0.1-cp35-cp35m-win32.whl", hash = "sha256:81b0ea3715bf6a848d6f7149d25bf018fd24554a4be01fcbbe3fdc78e890b955"}, + {file = "greenlet-2.0.1-cp35-cp35m-win_amd64.whl", hash = "sha256:38255a3f1e8942573b067510f9611fc9e38196077b0c8eb7a8c795e105f9ce77"}, + {file = "greenlet-2.0.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:04957dc96669be041e0c260964cfef4c77287f07c40452e61abe19d647505581"}, + {file = "greenlet-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:4aeaebcd91d9fee9aa768c1b39cb12214b30bf36d2b7370505a9f2165fedd8d9"}, + {file = "greenlet-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:974a39bdb8c90a85982cdb78a103a32e0b1be986d411303064b28a80611f6e51"}, + {file = "greenlet-2.0.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dca09dedf1bd8684767bc736cc20c97c29bc0c04c413e3276e0962cd7aeb148"}, + {file = "greenlet-2.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4c0757db9bd08470ff8277791795e70d0bf035a011a528ee9a5ce9454b6cba2"}, + {file = "greenlet-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:5067920de254f1a2dee8d3d9d7e4e03718e8fd2d2d9db962c8c9fa781ae82a39"}, + {file = "greenlet-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:5a8e05057fab2a365c81abc696cb753da7549d20266e8511eb6c9d9f72fe3e92"}, + {file = "greenlet-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:3d75b8d013086b08e801fbbb896f7d5c9e6ccd44f13a9241d2bf7c0df9eda928"}, + {file = "greenlet-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:097e3dae69321e9100202fc62977f687454cd0ea147d0fd5a766e57450c569fd"}, + {file = "greenlet-2.0.1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:cb242fc2cda5a307a7698c93173d3627a2a90d00507bccf5bc228851e8304963"}, + {file = "greenlet-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:72b00a8e7c25dcea5946692a2485b1a0c0661ed93ecfedfa9b6687bd89a24ef5"}, + {file = "greenlet-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5b0ff9878333823226d270417f24f4d06f235cb3e54d1103b71ea537a6a86ce"}, + {file = "greenlet-2.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be9e0fb2ada7e5124f5282d6381903183ecc73ea019568d6d63d33f25b2a9000"}, + {file = "greenlet-2.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b493db84d124805865adc587532ebad30efa68f79ad68f11b336e0a51ec86c2"}, + {file = "greenlet-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a20d33124935d27b80e6fdacbd34205732660e0a1d35d8b10b3328179a2b51a1"}, + {file = "greenlet-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:ea688d11707d30e212e0110a1aac7f7f3f542a259235d396f88be68b649e47d1"}, + {file = "greenlet-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:afe07421c969e259e9403c3bb658968702bc3b78ec0b6fde3ae1e73440529c23"}, + {file = "greenlet-2.0.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:cd4ccc364cf75d1422e66e247e52a93da6a9b73cefa8cad696f3cbbb75af179d"}, + {file = "greenlet-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4c8b1c43e75c42a6cafcc71defa9e01ead39ae80bd733a2608b297412beede68"}, + {file = "greenlet-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:659f167f419a4609bc0516fb18ea69ed39dbb25594934bd2dd4d0401660e8a1e"}, + {file = "greenlet-2.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:356e4519d4dfa766d50ecc498544b44c0249b6de66426041d7f8b751de4d6b48"}, + {file = "greenlet-2.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:811e1d37d60b47cb8126e0a929b58c046251f28117cb16fcd371eed61f66b764"}, + {file = "greenlet-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0109af1138afbfb8ae647e31a2b1ab030f58b21dd8528c27beaeb0093b7938a9"}, + {file = "greenlet-2.0.1-cp38-cp38-win32.whl", hash = "sha256:88c8d517e78acdf7df8a2134a3c4b964415b575d2840a2746ddb1cc6175f8608"}, + {file = "greenlet-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:d6ee1aa7ab36475035eb48c01efae87d37936a8173fc4d7b10bb02c2d75dd8f6"}, + {file = "greenlet-2.0.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:b1992ba9d4780d9af9726bbcef6a1db12d9ab1ccc35e5773685a24b7fb2758eb"}, + {file = "greenlet-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:b5e83e4de81dcc9425598d9469a624826a0b1211380ac444c7c791d4a2137c19"}, + {file = "greenlet-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:505138d4fa69462447a562a7c2ef723c6025ba12ac04478bc1ce2fcc279a2db5"}, + {file = "greenlet-2.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cce1e90dd302f45716a7715517c6aa0468af0bf38e814ad4eab58e88fc09f7f7"}, + {file = "greenlet-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e9744c657d896c7b580455e739899e492a4a452e2dd4d2b3e459f6b244a638d"}, + {file = "greenlet-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:41b825d65f31e394b523c84db84f9383a2f7eefc13d987f308f4663794d2687e"}, + {file = "greenlet-2.0.1-cp39-cp39-win32.whl", hash = "sha256:db38f80540083ea33bdab614a9d28bcec4b54daa5aff1668d7827a9fc769ae0a"}, + {file = "greenlet-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:b23d2a46d53210b498e5b701a1913697671988f4bf8e10f935433f6e7c332fb6"}, + {file = "greenlet-2.0.1.tar.gz", hash = "sha256:42e602564460da0e8ee67cb6d7236363ee5e131aa15943b6670e44e5c2ed0f67"}, +] +idna = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] +ipython = [ + {file = "ipython-7.34.0-py3-none-any.whl", hash = "sha256:c175d2440a1caff76116eb719d40538fbb316e214eda85c5515c303aacbfb23e"}, + {file = "ipython-7.34.0.tar.gz", hash = "sha256:af3bdb46aa292bce5615b1b2ebc76c2080c5f77f54bda2ec72461317273e7cd6"}, +] +jedi = [ + {file = "jedi-0.18.1-py2.py3-none-any.whl", hash = "sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d"}, + {file = "jedi-0.18.1.tar.gz", hash = "sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab"}, +] +levenshtein = [ + {file = "Levenshtein-0.20.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0e8c505bfb2b64d4d14a6e9f9d115877d150a398a09a97b7827df4711f1e5b4b"}, + {file = "Levenshtein-0.20.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:49bbdd707f435ad0c0e8c2727dd07233ff1db52ac607f9a67f18bf1eb275de66"}, + {file = "Levenshtein-0.20.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3bf05b99f268330a833f4749b35bf84a31985ae2a52d228207efa3e2863ba832"}, + {file = "Levenshtein-0.20.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b42f81d9fd1c89fe57f17288032b93020eff9ece58a2b8fbea77fc60bc321c11"}, + {file = "Levenshtein-0.20.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f71e89af67e7eb8eed11178711cc23e151e29fe488017cfc56a0a644c5e5d19d"}, + {file = "Levenshtein-0.20.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e614c3d02693f2355c42638753c4de7f5682265691a8bd7fd7d7e1fb6372f7e2"}, + {file = "Levenshtein-0.20.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9039bbcdb306b74542e36aa72fd3afa16db67516bbab65cbfd7ad10480b13c64"}, + {file = "Levenshtein-0.20.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:97a5424784b4c0989b2ec227ae000e80b293d7cc1bd3b02866d7943a3cf6b65c"}, + {file = "Levenshtein-0.20.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8b6e23ec767c8985a381869d3cb3935f8e67e57b80cbbdb5986329bb75f348b5"}, + {file = "Levenshtein-0.20.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5201b798e0905adab04c8b9d6ff5dd6cae13456b3e390fcd099991ce4fd32967"}, + {file = "Levenshtein-0.20.8-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:744d374f8227624028642d078ae4692f73e9e53b0ff87d25232e75abe842b00c"}, + {file = "Levenshtein-0.20.8-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:d6b247b22703d1989cb97a0b26485db7a3397322d3975c974c22e10a1592a414"}, + {file = "Levenshtein-0.20.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ff3e7e6a58892ee30f90507a4da1d316e0c3e8e3f1f70b694eeedc397a3d0802"}, + {file = "Levenshtein-0.20.8-cp310-cp310-win32.whl", hash = "sha256:ef65c16113028688353b10d159b6f12a6a471890ea6aa466a0626cd11c9239a5"}, + {file = "Levenshtein-0.20.8-cp310-cp310-win_amd64.whl", hash = "sha256:a8aac56e669a733e03c81b2e015dcd8d287db716bf2c6c9a480c4227300a7c52"}, + {file = "Levenshtein-0.20.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:96070d1a1c0d908d65cd9765a7b63ecafa8ef52d7a6460dccdf4d1bd29ad98fd"}, + {file = "Levenshtein-0.20.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:079a216a55a3735f59661e0b25ff3a2122e18a710682d88e0d7576bff594f547"}, + {file = "Levenshtein-0.20.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d5cba3eab536188f16115ab6ea5de1e19440c35bb01c817388e3696b811bca21"}, + {file = "Levenshtein-0.20.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0963497f51ff5aa73447173178daefff4a80e4165c2575d1357503c030f0a6b"}, + {file = "Levenshtein-0.20.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d140a0a450813ef8658f2a87d5c7d9ea2fff3f8b8d80e7ac80061b21c6a733f0"}, + {file = "Levenshtein-0.20.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd2c3a10c7d5d1ddb181121d7a038918408efb92206519428bcee6fe6a9fec30"}, + {file = "Levenshtein-0.20.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59a9067d5a9b63ec5ce7e23624dfe2c26438deecb3e46103fa68c72baef544ec"}, + {file = "Levenshtein-0.20.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c587c15278877e6974f2899c2bc15e561f762a71c2b8d8771de88c650738d87"}, + {file = "Levenshtein-0.20.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a66a3ef2e58ac7eb8f96ae8ba8b9aec34d1446865faaeab86d3559b9a0d422aa"}, + {file = "Levenshtein-0.20.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6b3745a6c1dee2178feab4e37ee46669ea2a7b8122eb63803e96547fff9bf1fc"}, + {file = "Levenshtein-0.20.8-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:404d30d0d4b36ea504b30c508107327fa06b2cdae88291d1864b236744858ac5"}, + {file = "Levenshtein-0.20.8-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:7dc85e9d8a74af873621d2b26e04aa1f41e02588a98dc859bc448e5c04aebb2a"}, + {file = "Levenshtein-0.20.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b134a8f6fa42355baf6687c7c6890f82672de95ea44fdb59f99696087ec19439"}, + {file = "Levenshtein-0.20.8-cp311-cp311-win32.whl", hash = "sha256:e48dd2f143f2236717a7604fed2dc2e078b96a0c3881e490f258a5f4bde11324"}, + {file = "Levenshtein-0.20.8-cp311-cp311-win_amd64.whl", hash = "sha256:43505eb89a80ea529c019391809c6fbf552eb5b4a5e34780c842b0f324d2b769"}, + {file = "Levenshtein-0.20.8-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d3e14702351d7c844aaef5fe1ffc53f590e4e6a17b12836517f9771379b0da49"}, + {file = "Levenshtein-0.20.8-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71f0c835dc7a8fd7a8b94374bb60ad238fbf7a4264f40868ec4d884130b8d96f"}, + {file = "Levenshtein-0.20.8-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cf9c8a45ce9fd60ff0fcdd5cfa44c83f2d2a0188c9e85d7eef36579919cb574"}, + {file = "Levenshtein-0.20.8-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:940c193991eb12922f76c422946f6c3d30ad144233c0b49dfa48e132a445cbcb"}, + {file = "Levenshtein-0.20.8-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f8d2d36c1d8ad9a699b1b41948c08161d4c645921ddb643b1d478b8457c859a"}, + {file = "Levenshtein-0.20.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:477265c6ba07bb01f96e64fe427ec51f63872dcb345f6340f5e5c7403e367282"}, + {file = "Levenshtein-0.20.8-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:7a9622d9bba7173df575cbfc4bc0a0d929d1733e1eeac85829e4f1ad21a24de5"}, + {file = "Levenshtein-0.20.8-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:db71d6ea4f2412b06bec7ed697dd2efe4f42ff953db58354364902d8f29587a4"}, + {file = "Levenshtein-0.20.8-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:828ceb9a947be03dded59d7d44fa8629ab7fc32185e460ada6355d23412ced7c"}, + {file = "Levenshtein-0.20.8-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ede6b8eaeb73aff5714459cbf5c18345fc40ca90b1885bd7e5cf0c27d99f118a"}, + {file = "Levenshtein-0.20.8-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:1481916946b50c7c80d4d318e290392614a07f9fcc612aadf3c6ce2c685ccbe9"}, + {file = "Levenshtein-0.20.8-cp36-cp36m-win32.whl", hash = "sha256:f7e4be20d10ffd24723363d4796ea78371c73d961725d7663381bbfa07ad086a"}, + {file = "Levenshtein-0.20.8-cp36-cp36m-win_amd64.whl", hash = "sha256:e8ecf0a10823ca4c90c42adb79478a845ea4979a751f32bc7cdb3b32e39e9c7f"}, + {file = "Levenshtein-0.20.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c09cb048098396a3ece6afe032cfdca0bb0fa49d5fb6b146d762e2d45274abba"}, + {file = "Levenshtein-0.20.8-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:204e8454d9f0be127ae21f0ff05506ed6785937c78d4ed8597504283675fdd79"}, + {file = "Levenshtein-0.20.8-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ed457334940a0dff6617e86f7df6b3ff64aa9f5a0571c7bd889228f6a9863f6"}, + {file = "Levenshtein-0.20.8-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:438d95ca36c8d277fb0cb078eab227173e1a59c6742cdb6dd97f106106e38ffd"}, + {file = "Levenshtein-0.20.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76ee654d6a741e43039094c9c68396f952d48c4b74d4da1450c87b77db5a7f83"}, + {file = "Levenshtein-0.20.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0f56896b12f4276a77e774be560d00bd6b173af1a5554554ed86a288ff111a4"}, + {file = "Levenshtein-0.20.8-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ba530885bc063ad90e8dff6d7e4cb55e523213ca1815a41ccc67a2efb405ad3b"}, + {file = "Levenshtein-0.20.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:caf6ad0c8c0780d78c3f5eb270fb8aa5820cf423057d252a1de6a79b1f07e76b"}, + {file = "Levenshtein-0.20.8-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:dae722ee22c610243d6fade46ce04ab16a36862b3edc732a60ccc57a4e9c6af8"}, + {file = "Levenshtein-0.20.8-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5d8cb2b06d8e7683c321e20740385545447cb2faf2286e2817ca03745354bf0c"}, + {file = "Levenshtein-0.20.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:130404e7a917c3ba80879cd8fcdd6f6e84e2cdc3018cd225d95fea906be8d8fa"}, + {file = "Levenshtein-0.20.8-cp37-cp37m-win32.whl", hash = "sha256:73f9db6a178a926c15dd1a8c9ccd810407c3eaddbfc2145f68242b55e238fd85"}, + {file = "Levenshtein-0.20.8-cp37-cp37m-win_amd64.whl", hash = "sha256:2c38b1005e6b7fe62dfc50c56bd0b7fc85688a7197f6a116459a3107461472e2"}, + {file = "Levenshtein-0.20.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:7b42a6470ee44e80332acc95ed55890d12b5f27676a942e0c268b0092569f06c"}, + {file = "Levenshtein-0.20.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2f586f3b192805ee19fb56bed803d05966ef0edc544822e934bb106f688d427b"}, + {file = "Levenshtein-0.20.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4619c165f1fe1048fc22065290b779ac3903d0d75b0fb2c600abc3913993036c"}, + {file = "Levenshtein-0.20.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3512354f28a13e680c5d7e83e9edc23afa5b807895db7933efb0be3fee3d50e1"}, + {file = "Levenshtein-0.20.8-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cab33decf636cebb20bf90563e89eb500ccfb758b0af19630ce149606f0b130f"}, + {file = "Levenshtein-0.20.8-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:675a91e9020a24c53301de11cd4b9b9b07827f93e2ddb56187c7ba2eede58dbc"}, + {file = "Levenshtein-0.20.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c8995f69db76b6c9fc113dc65b9b17a1f04f718f2c18203f6c0ac84a07462c7"}, + {file = "Levenshtein-0.20.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6463f3bc2a426703b2b259c1d6aaa73bd0c3fc85492c0f4d5dfd60658271ee9"}, + {file = "Levenshtein-0.20.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:085e7a8f5d8a2d71790f2c16363dbd15bdc6b2d66bb12adfeef453d9436fc641"}, + {file = "Levenshtein-0.20.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:94c773c9fb3a90cb7e191087beb06fb49b8d0452fb0f1abf27e9c644b19a0557"}, + {file = "Levenshtein-0.20.8-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:07836b04b628f9db65f24c34bb63fb453723aadf00b45e093384108ec05ba545"}, + {file = "Levenshtein-0.20.8-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:feaf0ae11827a917cdb314f189c711e0fb2b677f4e69e2c61276a65f2209ac4e"}, + {file = "Levenshtein-0.20.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c7b39b572cd3d2c2b8c5753246a52eda3cf3360f49de95e3f671062acc6caaf4"}, + {file = "Levenshtein-0.20.8-cp38-cp38-win32.whl", hash = "sha256:939b819874903a689863f3a552a90e1742ac342cda80f5d3e8adb42e1729d37e"}, + {file = "Levenshtein-0.20.8-cp38-cp38-win_amd64.whl", hash = "sha256:d9ad1515424b708608596a1bf314781d240f5b4fd934528d2bf0c9d7d0620e78"}, + {file = "Levenshtein-0.20.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:df4ba85d1c2361b2591d8b8c01740ed9803a7c32d706f1015446f5f382c4b8ec"}, + {file = "Levenshtein-0.20.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7677c9abbeff907bbd7869e33e47874e6ecf6e4341298e968c9e3dbf4c344765"}, + {file = "Levenshtein-0.20.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:36a479f799613479871da1ed221b7099cb77caf5b060fa9eb916d5b90c0ae206"}, + {file = "Levenshtein-0.20.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72ac6ae6581a586ef7f325413d4f87c39dc5e50734c5731586c73c7f837af8fc"}, + {file = "Levenshtein-0.20.8-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7d8cb8a401633ff6210c2fe4cd5a1410b26cef47f42e0e1ab3563c5f94975c4c"}, + {file = "Levenshtein-0.20.8-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6456643a8c5de3ced585d9fd4fba8ae78a3b7860bed7a670f76e7a7865b34c78"}, + {file = "Levenshtein-0.20.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65dc11345ac7d4c13f2392ec3610c392498500a7ba6455408abdd129becea41f"}, + {file = "Levenshtein-0.20.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:55baa3498b31ceb62b54e5278e6f5830b4748b4709fa291e91b7d022f9bef63a"}, + {file = "Levenshtein-0.20.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ea2f847e209a463387b8e8d6743b1cd929e60fdce85d8ab19022ba6678546c5"}, + {file = "Levenshtein-0.20.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2e3eb8e529cad2419d3f4bbb9f5fa0ea845c142b5e17a59c3f4ffe82b1ebfa29"}, + {file = "Levenshtein-0.20.8-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:4d61ed0b452fb6456be49ab1b9b2f1d6366db611a8c71a2fecab37118010e6c5"}, + {file = "Levenshtein-0.20.8-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:5bc69cbb75c502492666fd45b345e3f31a8a709b4019146cfc19f331fe1f6b38"}, + {file = "Levenshtein-0.20.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b7d71485f4bf92d5cddfdb91a82a0308a7a6b6a216887de44ecdab0c0495ca1"}, + {file = "Levenshtein-0.20.8-cp39-cp39-win32.whl", hash = "sha256:59da44a59861f60b049178a56c9d723d7181c5e2e6adf04bf54120c2d1d589a1"}, + {file = "Levenshtein-0.20.8-cp39-cp39-win_amd64.whl", hash = "sha256:8dc2232dd6918aa3f6be80c9c620ce0a0e361b1b979a49d1e3fa831af224e721"}, + {file = "Levenshtein-0.20.8-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:153ff8e7491f80b72250cabce926922d3d4a623d0846602a3f4fef974c3e7f4d"}, + {file = "Levenshtein-0.20.8-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e21df20e8d8df9be6b33ed013852ba6a317d139d11647f8b241c894c8b76d523"}, + {file = "Levenshtein-0.20.8-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fae73b02e929da1b52d988e54f029347fe9e5b024282c9492cfc4d8a951cd93"}, + {file = "Levenshtein-0.20.8-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:918a97459df93a8984355f91886f29dc97ddef85dfff185b38b1b748512411a1"}, + {file = "Levenshtein-0.20.8-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:c4c1e802c746679c765c780fb0dce82af1cc000bc0bea9c37bd4d6d295ddd339"}, + {file = "Levenshtein-0.20.8-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d17615b69f01a7db2bd4269e14fdad71bc7c3f08ca7a366914f85b08341953a0"}, + {file = "Levenshtein-0.20.8-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a57710c9c35cf41df77f4eb24ae5e9568a2d9fba3010ace657a8ec7d044c871"}, + {file = "Levenshtein-0.20.8-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24ffa06dd1a348b45584a192a86ff5ae8233d1c1cea4bd49fa9243485b8cf838"}, + {file = "Levenshtein-0.20.8-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b714581e760c79ff990d8196bd0978d9246427a93eefb1f9f1087b88e17c594d"}, + {file = "Levenshtein-0.20.8-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:9e8c5d97068fb4a78f5bfe0f5d8fa365867f2431f399f5df9dd2b741558c3684"}, + {file = "Levenshtein-0.20.8-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9d1c2766a63f1231119d6744502913e432c33aa0ceb6fabc536a4fbffcc90a46"}, + {file = "Levenshtein-0.20.8-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f87c9ed45e05fb9ad800560e3c063b1945cda981b6f92b1abfefb603b8f9113a"}, + {file = "Levenshtein-0.20.8-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0385d69ccca8bc1a091e1071357b8610af758af8968ed32bedcedf96c51a6bb9"}, + {file = "Levenshtein-0.20.8-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:98e741e54b7e66a63d92af04193b9d819b8f29292ccfcfd2cc6eee67ab2469f1"}, + {file = "Levenshtein-0.20.8-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:8746be2afafdab5ffc05094b0a954cea4f95bcc3e2bd48dce04b5e51063aeefb"}, + {file = "Levenshtein-0.20.8.tar.gz", hash = "sha256:a8cc52849264d3aa6e16c9daca95a02d59e9496c86f18def7131413cfba617cc"}, +] +mako = [ + {file = "Mako-1.2.3-py3-none-any.whl", hash = "sha256:c413a086e38cd885088d5e165305ee8eed04e8b3f8f62df343480da0a385735f"}, + {file = "Mako-1.2.3.tar.gz", hash = "sha256:7fde96466fcfeedb0eed94f187f20b23d85e4cb41444be0e542e2c8c65c396cd"}, +] +markupsafe = [ + {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, + {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, +] +matplotlib-inline = [ + {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"}, + {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"}, +] +multidict = [ + {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2"}, + {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3"}, + {file = "multidict-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:041b81a5f6b38244b34dc18c7b6aba91f9cdaf854d9a39e5ff0b58e2b5773b9c"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fdda29a3c7e76a064f2477c9aab1ba96fd94e02e386f1e665bca1807fc5386f"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3368bf2398b0e0fcbf46d85795adc4c259299fec50c1416d0f77c0a843a3eed9"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4f052ee022928d34fe1f4d2bc743f32609fb79ed9c49a1710a5ad6b2198db20"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:225383a6603c086e6cef0f2f05564acb4f4d5f019a4e3e983f572b8530f70c88"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50bd442726e288e884f7be9071016c15a8742eb689a593a0cac49ea093eef0a7"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:47e6a7e923e9cada7c139531feac59448f1f47727a79076c0b1ee80274cd8eee"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0556a1d4ea2d949efe5fd76a09b4a82e3a4a30700553a6725535098d8d9fb672"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:626fe10ac87851f4cffecee161fc6f8f9853f0f6f1035b59337a51d29ff3b4f9"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:8064b7c6f0af936a741ea1efd18690bacfbae4078c0c385d7c3f611d11f0cf87"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2d36e929d7f6a16d4eb11b250719c39560dd70545356365b494249e2186bc389"}, + {file = "multidict-6.0.2-cp310-cp310-win32.whl", hash = "sha256:fcb91630817aa8b9bc4a74023e4198480587269c272c58b3279875ed7235c293"}, + {file = "multidict-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:8cbf0132f3de7cc6c6ce00147cc78e6439ea736cee6bca4f068bcf892b0fd658"}, + {file = "multidict-6.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:05f6949d6169878a03e607a21e3b862eaf8e356590e8bdae4227eedadacf6e51"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2c2e459f7050aeb7c1b1276763364884595d47000c1cddb51764c0d8976e608"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d0509e469d48940147e1235d994cd849a8f8195e0bca65f8f5439c56e17872a3"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:514fe2b8d750d6cdb4712346a2c5084a80220821a3e91f3f71eec11cf8d28fd4"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19adcfc2a7197cdc3987044e3f415168fc5dc1f720c932eb1ef4f71a2067e08b"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9d153e7f1f9ba0b23ad1568b3b9e17301e23b042c23870f9ee0522dc5cc79e8"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:aef9cc3d9c7d63d924adac329c33835e0243b5052a6dfcbf7732a921c6e918ba"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4571f1beddff25f3e925eea34268422622963cd8dc395bb8778eb28418248e43"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:d48b8ee1d4068561ce8033d2c344cf5232cb29ee1a0206a7b828c79cbc5982b8"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:45183c96ddf61bf96d2684d9fbaf6f3564d86b34cb125761f9a0ef9e36c1d55b"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:75bdf08716edde767b09e76829db8c1e5ca9d8bb0a8d4bd94ae1eafe3dac5e15"}, + {file = "multidict-6.0.2-cp37-cp37m-win32.whl", hash = "sha256:a45e1135cb07086833ce969555df39149680e5471c04dfd6a915abd2fc3f6dbc"}, + {file = "multidict-6.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6f3cdef8a247d1eafa649085812f8a310e728bdf3900ff6c434eafb2d443b23a"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0327292e745a880459ef71be14e709aaea2f783f3537588fb4ed09b6c01bca60"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e875b6086e325bab7e680e4316d667fc0e5e174bb5611eb16b3ea121c8951b86"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feea820722e69451743a3d56ad74948b68bf456984d63c1a92e8347b7b88452d"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc57c68cb9139c7cd6fc39f211b02198e69fb90ce4bc4a094cf5fe0d20fd8b0"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:497988d6b6ec6ed6f87030ec03280b696ca47dbf0648045e4e1d28b80346560d"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:89171b2c769e03a953d5969b2f272efa931426355b6c0cb508022976a17fd376"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:684133b1e1fe91eda8fa7447f137c9490a064c6b7f392aa857bba83a28cfb693"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd9fc9c4849a07f3635ccffa895d57abce554b467d611a5009ba4f39b78a8849"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e07c8e79d6e6fd37b42f3250dba122053fddb319e84b55dd3a8d6446e1a7ee49"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4070613ea2227da2bfb2c35a6041e4371b0af6b0be57f424fe2318b42a748516"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:47fbeedbf94bed6547d3aa632075d804867a352d86688c04e606971595460227"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5774d9218d77befa7b70d836004a768fb9aa4fdb53c97498f4d8d3f67bb9cfa9"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2957489cba47c2539a8eb7ab32ff49101439ccf78eab724c828c1a54ff3ff98d"}, + {file = "multidict-6.0.2-cp38-cp38-win32.whl", hash = "sha256:e5b20e9599ba74391ca0cfbd7b328fcc20976823ba19bc573983a25b32e92b57"}, + {file = "multidict-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:8004dca28e15b86d1b1372515f32eb6f814bdf6f00952699bdeb541691091f96"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2e4a0785b84fb59e43c18a015ffc575ba93f7d1dbd272b4cdad9f5134b8a006c"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6701bf8a5d03a43375909ac91b6980aea74b0f5402fbe9428fc3f6edf5d9677e"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a007b1638e148c3cfb6bf0bdc4f82776cef0ac487191d093cdc316905e504071"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07a017cfa00c9890011628eab2503bee5872f27144936a52eaab449be5eaf032"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c207fff63adcdf5a485969131dc70e4b194327666b7e8a87a97fbc4fd80a53b2"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:373ba9d1d061c76462d74e7de1c0c8e267e9791ee8cfefcf6b0b2495762c370c"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfba7c6d5d7c9099ba21f84662b037a0ffd4a5e6b26ac07d19e423e6fdf965a9"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19d9bad105dfb34eb539c97b132057a4e709919ec4dd883ece5838bcbf262b80"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:de989b195c3d636ba000ee4281cd03bb1234635b124bf4cd89eeee9ca8fcb09d"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7c40b7bbece294ae3a87c1bc2abff0ff9beef41d14188cda94ada7bcea99b0fb"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:d16cce709ebfadc91278a1c005e3c17dd5f71f5098bfae1035149785ea6e9c68"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:a2c34a93e1d2aa35fbf1485e5010337c72c6791407d03aa5f4eed920343dd360"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:feba80698173761cddd814fa22e88b0661e98cb810f9f986c54aa34d281e4937"}, + {file = "multidict-6.0.2-cp39-cp39-win32.whl", hash = "sha256:23b616fdc3c74c9fe01d76ce0d1ce872d2d396d8fa8e4899398ad64fb5aa214a"}, + {file = "multidict-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae"}, + {file = "multidict-6.0.2.tar.gz", hash = "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"}, +] +nest-asyncio = [ + {file = "nest_asyncio-1.5.6-py3-none-any.whl", hash = "sha256:b9a953fb40dceaa587d109609098db21900182b16440652454a146cffb06e8b8"}, + {file = "nest_asyncio-1.5.6.tar.gz", hash = "sha256:d267cc1ff794403f7df692964d1d2a3fa9418ffea2a3f6859a439ff482fef290"}, +] +packaging = [ + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, +] +parso = [ + {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, + {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, +] +pexpect = [ + {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, + {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, +] +pickleshare = [ + {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, + {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, +] +pint = [ + {file = "Pint-0.17-py2.py3-none-any.whl", hash = "sha256:6593c5dfaf2f701c54f17453191dff05e90ec9ebc3d1901468a59cfcb3289a4c"}, + {file = "Pint-0.17.tar.gz", hash = "sha256:f4d0caa713239e6847a7c6eefe2427358566451fe56497d533f21fb590a3f313"}, +] +pluggy = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] +prompt-toolkit = [ + {file = "prompt_toolkit-3.0.32-py3-none-any.whl", hash = "sha256:24becda58d49ceac4dc26232eb179ef2b21f133fecda7eed6018d341766ed76e"}, + {file = "prompt_toolkit-3.0.32.tar.gz", hash = "sha256:e7f2129cba4ff3b3656bbdda0e74ee00d2f874a8bcdb9dd16f5fec7b3e173cae"}, +] +ptyprocess = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] +py = [ + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, +] +pygments = [ + {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"}, + {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"}, +] +pyparsing = [ + {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, + {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, +] +pytest = [ + {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, + {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, +] +pytest-asyncio = [ + {file = "pytest-asyncio-0.16.0.tar.gz", hash = "sha256:7496c5977ce88c34379df64a66459fe395cd05543f0a2f837016e7144391fcfb"}, + {file = "pytest_asyncio-0.16.0-py3-none-any.whl", hash = "sha256:5f2a21273c47b331ae6aa5b36087047b4899e40f03f18397c0e65fa5cca54e9b"}, +] +pytest-profiling = [ + {file = "pytest-profiling-1.7.0.tar.gz", hash = "sha256:93938f147662225d2b8bd5af89587b979652426a8a6ffd7e73ec4a23e24b7f29"}, + {file = "pytest_profiling-1.7.0-py2.py3-none-any.whl", hash = "sha256:999cc9ac94f2e528e3f5d43465da277429984a1c237ae9818f8cfd0b06acb019"}, +] +python-levenshtein = [ + {file = "python-Levenshtein-0.20.8.tar.gz", hash = "sha256:c2d68fc480394ef87e20097d9c1605ae101340f0a8060c518eacd2e634b190f8"}, + {file = "python_Levenshtein-0.20.8-py3-none-any.whl", hash = "sha256:a932f149c72d2707963947ba9df9729ac36983d2e9db4eb93e4d5b69a8899070"}, +] +pytz = [ + {file = "pytz-2022.6-py2.py3-none-any.whl", hash = "sha256:222439474e9c98fced559f1709d89e6c9cbf8d79c794ff3eb9f8800064291427"}, + {file = "pytz-2022.6.tar.gz", hash = "sha256:e89512406b793ca39f5971bc999cc538ce125c0e51c27941bef4568b460095e2"}, +] +pytz-deprecation-shim = [ + {file = "pytz_deprecation_shim-0.1.0.post0-py2.py3-none-any.whl", hash = "sha256:8314c9692a636c8eb3bda879b9f119e350e93223ae83e70e80c31675a0fdc1a6"}, + {file = "pytz_deprecation_shim-0.1.0.post0.tar.gz", hash = "sha256:af097bae1b616dde5c5744441e2ddc69e74dfdcb0c263129610d85b87445a59d"}, +] +rapidfuzz = [ + {file = "rapidfuzz-2.13.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91c049f7591d9e9f8bcc3c556c0c4b448223f564ad04511a8719d28f5d38daed"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:26e4b7f7941b92546a9b06ed75b40b5d7ceace8f3074d06cb3369349388d700d"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ba2a8fbd21079093118c40e8e80068750c1619a5988e54220ea0929de48e7d65"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de707808f1997574014d9ba87c2d9f8a619688d615520e3dce958bf4398514c7"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba3f47a5b82de7304ae08e2a111ccc90a6ea06ecc3f25d7870d08be0973c94cb"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a181b6ef9b480b56b29bdc58dc50c198e93d33398d2f8e57da05cbddb095bd9e"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1e569953a2abe945f116a6c22b71e8fc02d7c27068af2af40990115f25c93e4"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:026f6ecd8948e168a89fc015ef34b6bcb200f30ac33f1480554d722181b38bea"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daf5e4f6b048c225a494c941a21463a0d397c39a080db8fece9b3136297ed240"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e39ae60598ed533f513db6d0370755685666024ab187a144fc688dd16cfa2d33"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e8d71f1611431c445ced872b303cd61f215551a11df0c7171e5993bed84867d5"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:f5d07dca69bf5a9f1e1cd5756ded6c197a27e8d8f2d8a3d99565add37a3bd1ec"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ac95981911559c842e1e4532e2f89ca255531db1d87257e5e69cd8c0c0d585fc"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-win32.whl", hash = "sha256:b4162b96d0908cb0ca218513eab559e9a77c8a1d9705c9133813634d9db27f4f"}, + {file = "rapidfuzz-2.13.2-cp310-cp310-win_amd64.whl", hash = "sha256:84fd3cfc1cb872019e60a3844b1deedb176de0b9ded11bf30147137ac65185f5"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a599cc5cec196c0776faf65b74ac957354bd036f878905a16be9e20884870d02"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dbad2b7dad98b854a468d2c6a0b11464f68ce841428aded2f24f201a17a144eb"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ad78fb90540dc752b532345065146371acd3804a917c31fdd8a337951da9def2"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed0f99e0037b7f9f7117493e8723851c9eece4629906b2d5da21d3ef124149a2"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9abdffc590ef08d27dfd14d32e571f4a0f5f797f433f00c5faf4cf56ab62792a"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:352c920e166e838bc560014885ba979df656938fcc29a12c73ff06dc76b150d8"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c40acbadc965e72f1b44b3c665a59ec78a5e959757e52520bf73687c84ce6854"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a4053d5b62cedec83ff67d55e50da35f7736bed0a3b2af51fa6143f5fef3785"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0c324d82871fe50471f7ba38a21c3e68167e868f541f57ac0ef23c053bbef6e6"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cb4bd75518838b141dab8fe663de988c4d08502999068dc0b3949d43bd86ace6"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:4b785ffbc16795fca27c9e899993df7721d886249061689c48dbfe60fa7d02a1"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:1f363bf95d79dbafa8eac17697965e02e74da6f21b231b3fb808b2185bfed337"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f7cfc25d8143a7570f5e4c9da072a1e1c335d81a6926eb10c1fd3f637fa3c022"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-win32.whl", hash = "sha256:580f32cda7f911fef8266c7d811e580c18734cd12308d099b9975b914f33fcaf"}, + {file = "rapidfuzz-2.13.2-cp311-cp311-win_amd64.whl", hash = "sha256:98be3e873c8f9d90a982891b2b061521ba4e5e49552ba2d3c1b0806dd5677f88"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:de8ec700127b645b0e2e28e694a2bba6dcb6a305ef080ad312f3086d47fb6973"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0ec73e6d3ad9442cfb5b94c137cf4241fff2860d81a9ee8be8c3d987bb400c0"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da5b7f35fc824cff36a2baa62486d5b427bf0fd7714c19704b5a7df82c2950b4"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f186b3a32d78af7a805584a7e1c2fdf6f6fd62939936e4f3df869158c147a55"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:68f2e23eec59fc77bef164157889a2f7fb9800c47d615c58ee3809e2be3c8509"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4291a8c02d32aa6ebdffe63cf91abc2846383de95ae04a275f036c4e7a27f9ba"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a2eeee09ff716c8ff75942c1b93f0bca129590499f1127cbeb1b5cefbdc0c3d5"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2345656b30d7e18d18a4df5b765e4059111860a69bf3a36608a7d625e92567e6"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:1e1dd1a328464dd2ae70f0e31ec403593fbb1b254bab7ac9f0cd08ba71c797d0"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:54fe1835f96c1033cdb7e4677497e784704c81d028c962d2222239ded93d978b"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6b68b6a12411cfacca16ace22d42ae8e9946315d79f49c6c97089789c235e795"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-win32.whl", hash = "sha256:9a740ddd3f7725c80e500f16b1b02b83a58b47164c0f3ddd9379208629c8c4b5"}, + {file = "rapidfuzz-2.13.2-cp37-cp37m-win_amd64.whl", hash = "sha256:378554acdcf8370cc5c777b1312921a2a670f68888e999ea1305599c55b67f5d"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:aa96955f2878116239db55506fe825f574651a8893d07a83de7b3c76a2f0386e"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b4df886481ca27a6d53d30a73625fb86dd308cf7d6d99d32e0dfbfcc8e8a75b9"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c66f3b8e93cdc3063ffd7224cad84951834d9434ffd27fa3fabad2e942ddab7"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d6d5ab0f12f2d7ae6aad77af67ae6253b6c1d54c320484f1acd2fce38b39ac2"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f0574d5d97722cfaf51b7dd667c8c836fa9fdf5a7d8158a787b98ee2788f6c5"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:83ff31d33c1391a0a6b23273b7f839dc8f7b5fb75ddca59ce4f334b83ca822bb"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94d8c65f48665f82064bea8a48ff185409a309ba396f5aec3a846831cbe36e6d"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c065a83883af2a9a0303b6c06844a700af0db97ff6dc894324f656ad8efe405"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:773c60a5368a361253efea194552ff9ed6879756f6feb71b61b514723f8cb726"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:12ece1a4d024297afa4b76d2ce71c2c65fc7eaa487a9ae9f6e17c160253cfd23"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:2b491f2fac36718247070c3343f53aadbbe8684f3e0cf3b6cce1bd099e1d05cb"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:31370273787dca851e2df6f32f1ec8c61f86e9bbeb1cc42787020b6dfff952fd"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:47b5b227dc0bd53530dda55f344e1b24087fa99bb1bd7fceb6f5a2b1e2831ad4"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-win32.whl", hash = "sha256:8f09a16ae84b1decb9df9a7e393ec84a0b2a11da6356c3eedcf86da8cabe3071"}, + {file = "rapidfuzz-2.13.2-cp38-cp38-win_amd64.whl", hash = "sha256:e038e187270cbb987cf7c5d4b574fce7a32bc3d9593e9346d129874a7dc08dc3"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:aee5dce78e157e503269121ad6f886acab4b1ab3e3956bcdf0549d54596eab57"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:80073e897af0669f496d23899583b5c2f0decc2ec06aa7c36a3b8fb16eda5e0e"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ce40c2a68fe28e05a4f66229c11885ef928086fbcd2eff086decdacfe5254da9"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd268701bf930bbb2d12f6f7f75c681e16fee646ea1663d258e825bf919ca7a1"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5d93e77881497f76e77056feea4c375732d27151151273d6e4cb8a1defbf17a"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b27c3e2b1789a635b9df1d74838ae032dc2dbc596ece5d89f9de2c37ba0a6dfe"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e49f412fe58c793af61b04fb5536534dfc95000b6c2bf0bfa42fcf7eb1453d42"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27bbdee91718019e251d315c6e9b03aa5b7663b90e4228ac1ddb0a567ff3634b"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b51d45cb9ed81669206e338413ba224c06a8900ab0cc9106f4750ac73dc687bb"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3479a2fd88504cc41eb707650e81fd7ce864f2418fee24f7224775b539536b39"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7adb4327453c1550f51d6ba13d718a84091f82230c1d0daca6db628e57d0fa5a"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3a4e87aae287d757d9c5b045c819c985b02b38dea3f75630cc24d53826e640be"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:13e175b1643306558a3d7604789c4a8c217a64406fe82bf1a9e52efb5dea53ae"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-win32.whl", hash = "sha256:fb896fafa206db4d55f4412135c3ae28fbc56b8afc476970d0c5f29d2ce50948"}, + {file = "rapidfuzz-2.13.2-cp39-cp39-win_amd64.whl", hash = "sha256:37a9a8f5737b8e429291148be67d2dd8ba779a69a87ad95d2785bb3d80fd1df7"}, + {file = "rapidfuzz-2.13.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d6cb51a8459e7160366c6c7b31e8f9a671f7d617591c0ad305f2697707061da2"}, + {file = "rapidfuzz-2.13.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:343fe1fcbbf55c994b22962bfb46f6b6903faeac5a2671b2f0fa5e3664de3e66"}, + {file = "rapidfuzz-2.13.2-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d9d081cd8e0110661c8a3e728d7b491a903bb54d34de40b17d19144563bd5f6"}, + {file = "rapidfuzz-2.13.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f93a6740fef239a8aca6521cc1891d448664115b53528a3dd7f95c1781a5fa6"}, + {file = "rapidfuzz-2.13.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:deaf26cc23cfbf90650993108de888533635b981a7157a0234b4753527ac6e5c"}, + {file = "rapidfuzz-2.13.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b6a0617ba60f81a8df3b9ddca09f591a0a0c8269402169825fcd50daa03e5c25"}, + {file = "rapidfuzz-2.13.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bee1065d55edfeabdb98211bb673cb44a8b118cded42d743f7d59c07b05a80d"}, + {file = "rapidfuzz-2.13.2-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e5afd5477332ceeb960e2002d5bb0b04ad00b40037a0ab1de9916041badcf00"}, + {file = "rapidfuzz-2.13.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eead76c172ba08d49ea621016cf84031fff1ee33d7db751d7003e491e55e66af"}, + {file = "rapidfuzz-2.13.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:83b1e8aca6c3fad058d8a2b7653b7496df0c4aca903d589bb0e4184868290767"}, + {file = "rapidfuzz-2.13.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:41610c3a9be4febcbcac2b69b2f45d0da33e39d1194e5ffa3dd3a104d5a67a70"}, + {file = "rapidfuzz-2.13.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3aacc4eb58d6bccf6ec571619bee35861d4103961b9873d9b0829d347ca8a63e"}, + {file = "rapidfuzz-2.13.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:791d90aa1c68b5485f6340a8dc485aba7e9bcb729572449174ded0692e7e7ad0"}, + {file = "rapidfuzz-2.13.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d4f94b408c9f9218d61e8af55e43c8102f813eea2cf82de10906b032ddcb9aa"}, + {file = "rapidfuzz-2.13.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ac6a8a34f858f3862798383f51012788df6be823e2874fa426667a4da94ded7e"}, + {file = "rapidfuzz-2.13.2.tar.gz", hash = "sha256:1c67007161655c59e13bba130a2db29d7c9e5c81bcecb8846a3dd7386065eb24"}, +] +regex = [ + {file = "regex-2021.11.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9345b6f7ee578bad8e475129ed40123d265464c4cfead6c261fd60fc9de00bcf"}, + {file = "regex-2021.11.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:416c5f1a188c91e3eb41e9c8787288e707f7d2ebe66e0a6563af280d9b68478f"}, + {file = "regex-2021.11.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0538c43565ee6e703d3a7c3bdfe4037a5209250e8502c98f20fea6f5fdf2965"}, + {file = "regex-2021.11.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ee1227cf08b6716c85504aebc49ac827eb88fcc6e51564f010f11a406c0a667"}, + {file = "regex-2021.11.10-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6650f16365f1924d6014d2ea770bde8555b4a39dc9576abb95e3cd1ff0263b36"}, + {file = "regex-2021.11.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30ab804ea73972049b7a2a5c62d97687d69b5a60a67adca07eb73a0ddbc9e29f"}, + {file = "regex-2021.11.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68a067c11463de2a37157930d8b153005085e42bcb7ad9ca562d77ba7d1404e0"}, + {file = "regex-2021.11.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:162abfd74e88001d20cb73ceaffbfe601469923e875caf9118333b1a4aaafdc4"}, + {file = "regex-2021.11.10-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b9ed0b1e5e0759d6b7f8e2f143894b2a7f3edd313f38cf44e1e15d360e11749b"}, + {file = "regex-2021.11.10-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:473e67837f786404570eae33c3b64a4b9635ae9f00145250851a1292f484c063"}, + {file = "regex-2021.11.10-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2fee3ed82a011184807d2127f1733b4f6b2ff6ec7151d83ef3477f3b96a13d03"}, + {file = "regex-2021.11.10-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:d5fd67df77bab0d3f4ea1d7afca9ef15c2ee35dfb348c7b57ffb9782a6e4db6e"}, + {file = "regex-2021.11.10-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5d408a642a5484b9b4d11dea15a489ea0928c7e410c7525cd892f4d04f2f617b"}, + {file = "regex-2021.11.10-cp310-cp310-win32.whl", hash = "sha256:98ba568e8ae26beb726aeea2273053c717641933836568c2a0278a84987b2a1a"}, + {file = "regex-2021.11.10-cp310-cp310-win_amd64.whl", hash = "sha256:780b48456a0f0ba4d390e8b5f7c661fdd218934388cde1a974010a965e200e12"}, + {file = "regex-2021.11.10-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:dba70f30fd81f8ce6d32ddeef37d91c8948e5d5a4c63242d16a2b2df8143aafc"}, + {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1f54b9b4b6c53369f40028d2dd07a8c374583417ee6ec0ea304e710a20f80a0"}, + {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fbb9dc00e39f3e6c0ef48edee202f9520dafb233e8b51b06b8428cfcb92abd30"}, + {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666abff54e474d28ff42756d94544cdfd42e2ee97065857413b72e8a2d6a6345"}, + {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5537f71b6d646f7f5f340562ec4c77b6e1c915f8baae822ea0b7e46c1f09b733"}, + {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed2e07c6a26ed4bea91b897ee2b0835c21716d9a469a96c3e878dc5f8c55bb23"}, + {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ca5f18a75e1256ce07494e245cdb146f5a9267d3c702ebf9b65c7f8bd843431e"}, + {file = "regex-2021.11.10-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:74cbeac0451f27d4f50e6e8a8f3a52ca074b5e2da9f7b505c4201a57a8ed6286"}, + {file = "regex-2021.11.10-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:3598893bde43091ee5ca0a6ad20f08a0435e93a69255eeb5f81b85e81e329264"}, + {file = "regex-2021.11.10-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:50a7ddf3d131dc5633dccdb51417e2d1910d25cbcf842115a3a5893509140a3a"}, + {file = "regex-2021.11.10-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:61600a7ca4bcf78a96a68a27c2ae9389763b5b94b63943d5158f2a377e09d29a"}, + {file = "regex-2021.11.10-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:563d5f9354e15e048465061509403f68424fef37d5add3064038c2511c8f5e00"}, + {file = "regex-2021.11.10-cp36-cp36m-win32.whl", hash = "sha256:93a5051fcf5fad72de73b96f07d30bc29665697fb8ecdfbc474f3452c78adcf4"}, + {file = "regex-2021.11.10-cp36-cp36m-win_amd64.whl", hash = "sha256:b483c9d00a565633c87abd0aaf27eb5016de23fed952e054ecc19ce32f6a9e7e"}, + {file = "regex-2021.11.10-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fff55f3ce50a3ff63ec8e2a8d3dd924f1941b250b0aac3d3d42b687eeff07a8e"}, + {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e32d2a2b02ccbef10145df9135751abea1f9f076e67a4e261b05f24b94219e36"}, + {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53db2c6be8a2710b359bfd3d3aa17ba38f8aa72a82309a12ae99d3c0c3dcd74d"}, + {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2207ae4f64ad3af399e2d30dde66f0b36ae5c3129b52885f1bffc2f05ec505c8"}, + {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5ca078bb666c4a9d1287a379fe617a6dccd18c3e8a7e6c7e1eb8974330c626a"}, + {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd33eb9bdcfbabab3459c9ee651d94c842bc8a05fabc95edf4ee0c15a072495e"}, + {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:05b7d6d7e64efe309972adab77fc2af8907bb93217ec60aa9fe12a0dad35874f"}, + {file = "regex-2021.11.10-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:42b50fa6666b0d50c30a990527127334d6b96dd969011e843e726a64011485da"}, + {file = "regex-2021.11.10-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6e1d2cc79e8dae442b3fa4a26c5794428b98f81389af90623ffcc650ce9f6732"}, + {file = "regex-2021.11.10-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:0416f7399e918c4b0e074a0f66e5191077ee2ca32a0f99d4c187a62beb47aa05"}, + {file = "regex-2021.11.10-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:ce298e3d0c65bd03fa65ffcc6db0e2b578e8f626d468db64fdf8457731052942"}, + {file = "regex-2021.11.10-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dc07f021ee80510f3cd3af2cad5b6a3b3a10b057521d9e6aaeb621730d320c5a"}, + {file = "regex-2021.11.10-cp37-cp37m-win32.whl", hash = "sha256:e71255ba42567d34a13c03968736c5d39bb4a97ce98188fafb27ce981115beec"}, + {file = "regex-2021.11.10-cp37-cp37m-win_amd64.whl", hash = "sha256:07856afef5ffcc052e7eccf3213317fbb94e4a5cd8177a2caa69c980657b3cb4"}, + {file = "regex-2021.11.10-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba05430e819e58544e840a68b03b28b6d328aff2e41579037e8bab7653b37d83"}, + {file = "regex-2021.11.10-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7f301b11b9d214f83ddaf689181051e7f48905568b0c7017c04c06dfd065e244"}, + {file = "regex-2021.11.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aaa4e0705ef2b73dd8e36eeb4c868f80f8393f5f4d855e94025ce7ad8525f50"}, + {file = "regex-2021.11.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:788aef3549f1924d5c38263104dae7395bf020a42776d5ec5ea2b0d3d85d6646"}, + {file = "regex-2021.11.10-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f8af619e3be812a2059b212064ea7a640aff0568d972cd1b9e920837469eb3cb"}, + {file = "regex-2021.11.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85bfa6a5413be0ee6c5c4a663668a2cad2cbecdee367630d097d7823041bdeec"}, + {file = "regex-2021.11.10-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f23222527b307970e383433daec128d769ff778d9b29343fb3496472dc20dabe"}, + {file = "regex-2021.11.10-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:da1a90c1ddb7531b1d5ff1e171b4ee61f6345119be7351104b67ff413843fe94"}, + {file = "regex-2021.11.10-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f5be7805e53dafe94d295399cfbe5227f39995a997f4fd8539bf3cbdc8f47ca8"}, + {file = "regex-2021.11.10-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a955b747d620a50408b7fdf948e04359d6e762ff8a85f5775d907ceced715129"}, + {file = "regex-2021.11.10-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:139a23d1f5d30db2cc6c7fd9c6d6497872a672db22c4ae1910be22d4f4b2068a"}, + {file = "regex-2021.11.10-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ca49e1ab99593438b204e00f3970e7a5f70d045267051dfa6b5f4304fcfa1dbf"}, + {file = "regex-2021.11.10-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:96fc32c16ea6d60d3ca7f63397bff5c75c5a562f7db6dec7d412f7c4d2e78ec0"}, + {file = "regex-2021.11.10-cp38-cp38-win32.whl", hash = "sha256:0617383e2fe465732af4509e61648b77cbe3aee68b6ac8c0b6fe934db90be5cc"}, + {file = "regex-2021.11.10-cp38-cp38-win_amd64.whl", hash = "sha256:a3feefd5e95871872673b08636f96b61ebef62971eab044f5124fb4dea39919d"}, + {file = "regex-2021.11.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f7f325be2804246a75a4f45c72d4ce80d2443ab815063cdf70ee8fb2ca59ee1b"}, + {file = "regex-2021.11.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:537ca6a3586931b16a85ac38c08cc48f10fc870a5b25e51794c74df843e9966d"}, + {file = "regex-2021.11.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eef2afb0fd1747f33f1ee3e209bce1ed582d1896b240ccc5e2697e3275f037c7"}, + {file = "regex-2021.11.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:432bd15d40ed835a51617521d60d0125867f7b88acf653e4ed994a1f8e4995dc"}, + {file = "regex-2021.11.10-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b43c2b8a330a490daaef5a47ab114935002b13b3f9dc5da56d5322ff218eeadb"}, + {file = "regex-2021.11.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:962b9a917dd7ceacbe5cd424556914cb0d636001e393b43dc886ba31d2a1e449"}, + {file = "regex-2021.11.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fa8c626d6441e2d04b6ee703ef2d1e17608ad44c7cb75258c09dd42bacdfc64b"}, + {file = "regex-2021.11.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3c5fb32cc6077abad3bbf0323067636d93307c9fa93e072771cf9a64d1c0f3ef"}, + {file = "regex-2021.11.10-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cd410a1cbb2d297c67d8521759ab2ee3f1d66206d2e4328502a487589a2cb21b"}, + {file = "regex-2021.11.10-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e6096b0688e6e14af6a1b10eaad86b4ff17935c49aa774eac7c95a57a4e8c296"}, + {file = "regex-2021.11.10-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:529801a0d58809b60b3531ee804d3e3be4b412c94b5d267daa3de7fadef00f49"}, + {file = "regex-2021.11.10-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0f594b96fe2e0821d026365f72ac7b4f0b487487fb3d4aaf10dd9d97d88a9737"}, + {file = "regex-2021.11.10-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2409b5c9cef7054dde93a9803156b411b677affc84fca69e908b1cb2c540025d"}, + {file = "regex-2021.11.10-cp39-cp39-win32.whl", hash = "sha256:3b5df18db1fccd66de15aa59c41e4f853b5df7550723d26aa6cb7f40e5d9da5a"}, + {file = "regex-2021.11.10-cp39-cp39-win_amd64.whl", hash = "sha256:83ee89483672b11f8952b158640d0c0ff02dc43d9cb1b70c1564b49abe92ce29"}, + {file = "regex-2021.11.10.tar.gz", hash = "sha256:f341ee2df0999bfdf7a95e448075effe0db212a59387de1a70690e4acb03d4c6"}, +] +semver = [ + {file = "semver-2.13.0-py2.py3-none-any.whl", hash = "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4"}, + {file = "semver-2.13.0.tar.gz", hash = "sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f"}, +] +setuptools = [ + {file = "setuptools-65.5.1-py3-none-any.whl", hash = "sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31"}, + {file = "setuptools-65.5.1.tar.gz", hash = "sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f"}, +] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +sqlalchemy = [ + {file = "SQLAlchemy-1.4.43-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:491d94879f9ec0dea7e1cb053cd9cc65a28d2467960cf99f7b3c286590406060"}, + {file = "SQLAlchemy-1.4.43-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:eeb55a555eef1a9607c1635bbdddd0b8a2bb9713bcb5bc8da1e8fae8ee46d1d8"}, + {file = "SQLAlchemy-1.4.43-cp27-cp27m-win32.whl", hash = "sha256:7d6293010aa0af8bd3b0c9993259f8979db2422d6abf85a31d70ec69cb2ee4dc"}, + {file = "SQLAlchemy-1.4.43-cp27-cp27m-win_amd64.whl", hash = "sha256:27479b5a1e110e64c56b18ffbf8cf99e101572a3d1a43943ea02158f1304108e"}, + {file = "SQLAlchemy-1.4.43-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:13ce4f3a068ec4ef7598d2a77f42adc3d90c76981f5a7c198756b25c4f4a22ea"}, + {file = "SQLAlchemy-1.4.43-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:aa12e27cb465b4b006ffb777624fc6023363e01cfed2d3f89d33fb6da80f6de2"}, + {file = "SQLAlchemy-1.4.43-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d16aca30fad4753aeb4ebde564bbd4a248b9673e4f879b940f4e806a17be87f"}, + {file = "SQLAlchemy-1.4.43-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cde363fb5412ab178f1cc1e596e9cfc396464da8a4fe8e733cc6d6b4e2c23aa9"}, + {file = "SQLAlchemy-1.4.43-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4abda3e693d24169221ffc7aa0444ccef3dc43dfeab6ad8665d3836751cd6af7"}, + {file = "SQLAlchemy-1.4.43-cp310-cp310-win32.whl", hash = "sha256:fa46d86a17cccd48c6762df1a60aecf5aaa2e0c0973efacf146c637694b62ffd"}, + {file = "SQLAlchemy-1.4.43-cp310-cp310-win_amd64.whl", hash = "sha256:962c7c80c54a42836c47cb0d8a53016986c8584e8d98e90e2ea723a4ed0ba85b"}, + {file = "SQLAlchemy-1.4.43-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f6e036714a586f757a3e12ff0798ce9a90aa04a60cff392d8bcacc5ecf79c95e"}, + {file = "SQLAlchemy-1.4.43-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d05d7365c2d1df03a69d90157a3e9b3e7b62088cca8ee6686aed2598659a6e14"}, + {file = "SQLAlchemy-1.4.43-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59bd0ae166253f7fed8c3f4f6265d2637f25d2f6614d00df34d7ee0d95d29c91"}, + {file = "SQLAlchemy-1.4.43-cp311-cp311-win32.whl", hash = "sha256:0c8a174f23bc021aac97bcb27fbe2ae3d4652d3d23e5768bc2ec3d44e386c7eb"}, + {file = "SQLAlchemy-1.4.43-cp311-cp311-win_amd64.whl", hash = "sha256:5d5937e1bf7921e4d1acdfad72dd98d9e7f9ea5c52aeb12b3b05b534b527692d"}, + {file = "SQLAlchemy-1.4.43-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:ed1c950aba723b7a5b702b88f05d883607c587de918d7d8c2014fe7f55cf67e0"}, + {file = "SQLAlchemy-1.4.43-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5438f6c768b7e928f0463777b545965648ba0d55877afd14a4e96d2a99702e7"}, + {file = "SQLAlchemy-1.4.43-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:41df873cdae1d56fde97a1b4f6ffa118f40e4b2d6a6aa8c25c50eea31ecbeb08"}, + {file = "SQLAlchemy-1.4.43-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22f46440e61d90100e0f378faac40335fb5bbf278472df0d83dc15b653b9896"}, + {file = "SQLAlchemy-1.4.43-cp36-cp36m-win32.whl", hash = "sha256:529e2cc8af75811114e5ab2eb116fd71b6e252c6bdb32adbfcd5e0c5f6d5ab06"}, + {file = "SQLAlchemy-1.4.43-cp36-cp36m-win_amd64.whl", hash = "sha256:c1ced2fae7a1177a36cf94d0a5567452d195d3b4d7d932dd61f123fb15ddf87b"}, + {file = "SQLAlchemy-1.4.43-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:736d4e706adb3c95a0a7e660073a5213dfae78ff2df6addf8ff2918c83fbeebe"}, + {file = "SQLAlchemy-1.4.43-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23a4569d3db1ce44370d05c5ad79be4f37915fcc97387aef9da232b95db7b695"}, + {file = "SQLAlchemy-1.4.43-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:42bff29eaecbb284f614f4bb265bb0c268625f5b93ce6268f8017811e0afbdde"}, + {file = "SQLAlchemy-1.4.43-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee9613b0460dce970414cfc990ca40afe518bc139e697243fcdf890285fb30ac"}, + {file = "SQLAlchemy-1.4.43-cp37-cp37m-win32.whl", hash = "sha256:dc1e005d490c101d27657481a05765851ab795cc8aedeb8d9425595088b20736"}, + {file = "SQLAlchemy-1.4.43-cp37-cp37m-win_amd64.whl", hash = "sha256:c9a6e878e63286392b262d86d21fe16e6eec12b95ccb0a92c392f2b1e0acca03"}, + {file = "SQLAlchemy-1.4.43-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:c6de20de7c19b965c007c9da240268dde1451865099ca10f0f593c347041b845"}, + {file = "SQLAlchemy-1.4.43-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fef01240d32ada9007387afd8e0b2230f99efdc4b57ca6f1d1192fca4fcf6a5"}, + {file = "SQLAlchemy-1.4.43-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b6fd58e25e6cdd2a131d7e97f9713f8f2142360cd40c75af8aa5b83d535f811c"}, + {file = "SQLAlchemy-1.4.43-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35dc0a5e934c41e282e019c889069b01ff4cd356b2ea452c9985e1542734cfb1"}, + {file = "SQLAlchemy-1.4.43-cp38-cp38-win32.whl", hash = "sha256:fb9a44e7124f72b79023ab04e1c8fcd8f392939ef0d7a75beae8634e15605d30"}, + {file = "SQLAlchemy-1.4.43-cp38-cp38-win_amd64.whl", hash = "sha256:4a791e7a1e5ac33f70a3598f8f34fdd3b60c68593bbb038baf58bc50e02d7468"}, + {file = "SQLAlchemy-1.4.43-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:c9b59863e2b1f1e1ebf9ee517f86cdfa82d7049c8d81ad71ab58d442b137bbe9"}, + {file = "SQLAlchemy-1.4.43-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd80300d81d92661e2488a4bf4383f0c5dc6e7b05fa46d2823e231af4e30539a"}, + {file = "SQLAlchemy-1.4.43-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c3dde668edea70dc8d55a74d933d5446e5a97786cdd1c67c8e4971c73bd087ad"}, + {file = "SQLAlchemy-1.4.43-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b462c070769f0ef06ea5fe65206b970bcf2b59cb3fda2bec2f4729e1be89c13"}, + {file = "SQLAlchemy-1.4.43-cp39-cp39-win32.whl", hash = "sha256:c1f5bfffc3227d05d90c557b10604962f655b4a83c9f3ad507a81ac8d6847679"}, + {file = "SQLAlchemy-1.4.43-cp39-cp39-win_amd64.whl", hash = "sha256:a7fa3e57a7b0476fbcba72b231150503d53dbcbdd23f4a86be5152912a923b6e"}, + {file = "SQLAlchemy-1.4.43.tar.gz", hash = "sha256:c628697aad7a141da8fc3fd81b4874a711cc84af172e1b1e7bbfadf760446496"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] +traitlets = [ + {file = "traitlets-5.5.0-py3-none-any.whl", hash = "sha256:1201b2c9f76097195989cdf7f65db9897593b0dfd69e4ac96016661bb6f0d30f"}, + {file = "traitlets-5.5.0.tar.gz", hash = "sha256:b122f9ff2f2f6c1709dab289a05555be011c87828e911c0cf4074b85cb780a79"}, +] +typing-extensions = [ + {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, + {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, +] +tzdata = [ + {file = "tzdata-2022.6-py2.py3-none-any.whl", hash = "sha256:04a680bdc5b15750c39c12a448885a51134a27ec9af83667663f0b3a1bf3f342"}, + {file = "tzdata-2022.6.tar.gz", hash = "sha256:91f11db4503385928c15598c98573e3af07e7229181bee5375bd30f1695ddcae"}, +] +tzlocal = [ + {file = "tzlocal-4.2-py3-none-any.whl", hash = "sha256:89885494684c929d9191c57aa27502afc87a579be5cdd3225c77c463ea043745"}, + {file = "tzlocal-4.2.tar.gz", hash = "sha256:ee5842fa3a795f023514ac2d801c4a81d1743bbe642e3940143326b3a00addd7"}, +] +wcwidth = [ + {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, + {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, +] +yappi = [ + {file = "yappi-1.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:71a6bce588d03240d8c05aa734d97d69c595ac382644701eaaca2421f6e37c9e"}, + {file = "yappi-1.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d80262ef4bf8ebd7c81e37832b41fe3b0b74621a24eb853b0444e06b01a44a1a"}, + {file = "yappi-1.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8791dbdf17673fb14a6cff150a8b2c85a5e40c455eebb37a62ea4dc74c077408"}, + {file = "yappi-1.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:57f9d3a88b822e3727505cf0a59e4b1038de4cd34555749bdc65ac258a58ca23"}, + {file = "yappi-1.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f3fb92fe0ea47142275fbe6e5d1daa9685c2e25bfd6a9478c2669e8828b3abf8"}, + {file = "yappi-1.4.0-cp310-cp310-win32.whl", hash = "sha256:52b82a8ec9d5e86e828fe35821a8482c94ca1dec8a278bb8001d21f2c8af98a8"}, + {file = "yappi-1.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:a5767d79a44d47a34be469d798ddc56cff251394af1f4fde2463de9359a8c38e"}, + {file = "yappi-1.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8bc404a3201ec9dc93ab669a700b4f3736bbe3a029e85dc046f278541b83f74"}, + {file = "yappi-1.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:733b4088a54996e7811dca94de633ffe4b906b6e6b8147c31913b674ae6e90cc"}, + {file = "yappi-1.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5cd0ed067b4499fa45f08e78e0caf9154bc5ae28eca90167107b1fcfa741dac"}, + {file = "yappi-1.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:34aed429e1ef04d5b432bbbd719d7c7707b9fb310e30f78c61d0b31733626af8"}, + {file = "yappi-1.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:01fc1f7f76a43a2d0ded34313c97395e3c3323e796945b183569a5a0365b14a3"}, + {file = "yappi-1.4.0-cp311-cp311-win32.whl", hash = "sha256:987c8f658e1d2e4029612c33a4ff7b04f9a8fbd96e315eefb0384943830ae68b"}, + {file = "yappi-1.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:4f42a9de88cfcbcd3f05834b4cc585e6e70ae0c4e03918b41865ccca02d2514b"}, + {file = "yappi-1.4.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a3bb2d75620ac9ef69f11c62e737469ef155e566e51ed85a74126871e45d2051"}, + {file = "yappi-1.4.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3033cdbd482a79ecafcafef8a1a0699ad333ee87bc7a28bd07c461ef196b2ea3"}, + {file = "yappi-1.4.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42ddd97258604bb1bea1b7dce2790c24f9b9bca970d844cb7afe98a9fbbf1425"}, + {file = "yappi-1.4.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:3a9652e7785f4b4c8bb3a8fa9ee33adf5e3f6dd893de4465008f75b1306f7895"}, + {file = "yappi-1.4.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:3e5d5a95a8681dc91f5a22c81d458109dcbcd718a551b647d28075574dfb8cbb"}, + {file = "yappi-1.4.0-cp36-cp36m-win32.whl", hash = "sha256:89d352ea770860617f55539e860440a166c5b9c1a67a7f351fed4030af9943b0"}, + {file = "yappi-1.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:64529504c5522b1c8c79eeb27a68f84979ce9415150c32cd7e06618383443bcc"}, + {file = "yappi-1.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d878f66d0b5d79396d6f034f8d89615517a4c4410e97b84d48402e940f9501d5"}, + {file = "yappi-1.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bad0766003eaa683e56f77166d4551c2f7530ec13aa602ada5cd8ddfe130d42b"}, + {file = "yappi-1.4.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ab8c17980e6bdb522b03b118f6d62362c92f7be40a81a4e89746d0eeae1e3ab"}, + {file = "yappi-1.4.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:194a565ab0145ff10e31389fb364a35a4f5160ad6af17362355592cfddf2ae6e"}, + {file = "yappi-1.4.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:14068a34907f4e7404b6b87a7bda2d55be2bde4d4d7f9e254b2cd26187cc2ebc"}, + {file = "yappi-1.4.0-cp37-cp37m-win32.whl", hash = "sha256:407b119f394ab60bb0a3d07efcb92d4846ef40ab40fff02c8902ca8d800f85d3"}, + {file = "yappi-1.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:5e4de1137021f80a238217444c0ad5f0e393082f4744ecae3d92eb3a9b98ca3e"}, + {file = "yappi-1.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7b286c4fcc72812adbd19280329a3c0144582abd1e5a3513d93a8bb2d3d1abaa"}, + {file = "yappi-1.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aef7a5bd5cd7e36adb90419984f29809eee51c9a9b74849b9dfa6077075da21f"}, + {file = "yappi-1.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5092940caea4cc150ba21d9afbafc8b00770f33ab5de55638c2bbd2c6f7f82cf"}, + {file = "yappi-1.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ec8ada826232137560e6ac7644ace8305b4dacbca0f9fff246ffee52db0a3c3a"}, + {file = "yappi-1.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1413d3fb200c0011b22764a227fb9e56e479acb1ec2b7c134c62d70a76a7e1f2"}, + {file = "yappi-1.4.0-cp38-cp38-win32.whl", hash = "sha256:38b0235574b7c0c549d97baa63f5fa4660b6d34a0b00ee8cc48d04ef19cf71fb"}, + {file = "yappi-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ab23d95fe8130445f1e089af7efec21f172611b306283496c99089839ef61c5"}, + {file = "yappi-1.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:61a2c7dc15eeccd1909c6bc5783e63fb06ee7725e5aa006b83cd6afb49a343c7"}, + {file = "yappi-1.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c81d957b10085ce32bb232896d258e9e87ae4ac4e044e755eb505f1c8eb148da"}, + {file = "yappi-1.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e4a0af76310957d12ff2d661e2ec3509ee4b4661929fec04d0dc40a0c8866ae"}, + {file = "yappi-1.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a279bb01f9c4b4c99cb959210e49151afd6c76693eca8a01311343efe8f31262"}, + {file = "yappi-1.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:36eaa02d53842b22157f1b150db79d03cae1cc635f708fa82737bcdfd4aa2bd9"}, + {file = "yappi-1.4.0-cp39-cp39-win32.whl", hash = "sha256:05b2c6c7f0667b46cd7cccbd36cff1b10f4b3f6625aacea5eb0ac99cd9ca7520"}, + {file = "yappi-1.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:bbdd6043e24f5c84a042ea8af69a1f2720571426fd1985814cf41e6d7a17f5c9"}, + {file = "yappi-1.4.0.tar.gz", hash = "sha256:504b5d8fc7433736cb5e257991d2e7f2946019174f1faec7b2fe947881a17fc0"}, +] +yarl = [ + {file = "yarl-1.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:abc06b97407868ef38f3d172762f4069323de52f2b70d133d096a48d72215d28"}, + {file = "yarl-1.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:07b21e274de4c637f3e3b7104694e53260b5fc10d51fb3ec5fed1da8e0f754e3"}, + {file = "yarl-1.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9de955d98e02fab288c7718662afb33aab64212ecb368c5dc866d9a57bf48880"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ec362167e2c9fd178f82f252b6d97669d7245695dc057ee182118042026da40"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:20df6ff4089bc86e4a66e3b1380460f864df3dd9dccaf88d6b3385d24405893b"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5999c4662631cb798496535afbd837a102859568adc67d75d2045e31ec3ac497"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed19b74e81b10b592084a5ad1e70f845f0aacb57577018d31de064e71ffa267a"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e4808f996ca39a6463f45182e2af2fae55e2560be586d447ce8016f389f626f"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2d800b9c2eaf0684c08be5f50e52bfa2aa920e7163c2ea43f4f431e829b4f0fd"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6628d750041550c5d9da50bb40b5cf28a2e63b9388bac10fedd4f19236ef4957"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f5af52738e225fcc526ae64071b7e5342abe03f42e0e8918227b38c9aa711e28"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:76577f13333b4fe345c3704811ac7509b31499132ff0181f25ee26619de2c843"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0c03f456522d1ec815893d85fccb5def01ffaa74c1b16ff30f8aaa03eb21e453"}, + {file = "yarl-1.8.1-cp310-cp310-win32.whl", hash = "sha256:ea30a42dc94d42f2ba4d0f7c0ffb4f4f9baa1b23045910c0c32df9c9902cb272"}, + {file = "yarl-1.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:9130ddf1ae9978abe63808b6b60a897e41fccb834408cde79522feb37fb72fb0"}, + {file = "yarl-1.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0ab5a138211c1c366404d912824bdcf5545ccba5b3ff52c42c4af4cbdc2c5035"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0fb2cb4204ddb456a8e32381f9a90000429489a25f64e817e6ff94879d432fc"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:85cba594433915d5c9a0d14b24cfba0339f57a2fff203a5d4fd070e593307d0b"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ca7e596c55bd675432b11320b4eacc62310c2145d6801a1f8e9ad160685a231"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0f77539733e0ec2475ddcd4e26777d08996f8cd55d2aef82ec4d3896687abda"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29e256649f42771829974e742061c3501cc50cf16e63f91ed8d1bf98242e5507"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7fce6cbc6c170ede0221cc8c91b285f7f3c8b9fe28283b51885ff621bbe0f8ee"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:59ddd85a1214862ce7c7c66457f05543b6a275b70a65de366030d56159a979f0"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:12768232751689c1a89b0376a96a32bc7633c08da45ad985d0c49ede691f5c0d"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:b19255dde4b4f4c32e012038f2c169bb72e7f081552bea4641cab4d88bc409dd"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6c8148e0b52bf9535c40c48faebb00cb294ee577ca069d21bd5c48d302a83780"}, + {file = "yarl-1.8.1-cp37-cp37m-win32.whl", hash = "sha256:de839c3a1826a909fdbfe05f6fe2167c4ab033f1133757b5936efe2f84904c07"}, + {file = "yarl-1.8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:dd032e8422a52e5a4860e062eb84ac94ea08861d334a4bcaf142a63ce8ad4802"}, + {file = "yarl-1.8.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:19cd801d6f983918a3f3a39f3a45b553c015c5aac92ccd1fac619bd74beece4a"}, + {file = "yarl-1.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6347f1a58e658b97b0a0d1ff7658a03cb79bdbda0331603bed24dd7054a6dea1"}, + {file = "yarl-1.8.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c0da7e44d0c9108d8b98469338705e07f4bb7dab96dbd8fa4e91b337db42548"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5587bba41399854703212b87071c6d8638fa6e61656385875f8c6dff92b2e461"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31a9a04ecccd6b03e2b0e12e82131f1488dea5555a13a4d32f064e22a6003cfe"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:205904cffd69ae972a1707a1bd3ea7cded594b1d773a0ce66714edf17833cdae"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea513a25976d21733bff523e0ca836ef1679630ef4ad22d46987d04b372d57fc"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0b51530877d3ad7a8d47b2fff0c8df3b8f3b8deddf057379ba50b13df2a5eae"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d2b8f245dad9e331540c350285910b20dd913dc86d4ee410c11d48523c4fd546"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ab2a60d57ca88e1d4ca34a10e9fb4ab2ac5ad315543351de3a612bbb0560bead"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:449c957ffc6bc2309e1fbe67ab7d2c1efca89d3f4912baeb8ead207bb3cc1cd4"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a165442348c211b5dea67c0206fc61366212d7082ba8118c8c5c1c853ea4d82e"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b3ded839a5c5608eec8b6f9ae9a62cb22cd037ea97c627f38ae0841a48f09eae"}, + {file = "yarl-1.8.1-cp38-cp38-win32.whl", hash = "sha256:c1445a0c562ed561d06d8cbc5c8916c6008a31c60bc3655cdd2de1d3bf5174a0"}, + {file = "yarl-1.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:56c11efb0a89700987d05597b08a1efcd78d74c52febe530126785e1b1a285f4"}, + {file = "yarl-1.8.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e80ed5a9939ceb6fda42811542f31c8602be336b1fb977bccb012e83da7e4936"}, + {file = "yarl-1.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6afb336e23a793cd3b6476c30f030a0d4c7539cd81649683b5e0c1b0ab0bf350"}, + {file = "yarl-1.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4c322cbaa4ed78a8aac89b2174a6df398faf50e5fc12c4c191c40c59d5e28357"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fae37373155f5ef9b403ab48af5136ae9851151f7aacd9926251ab26b953118b"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5395da939ffa959974577eff2cbfc24b004a2fb6c346918f39966a5786874e54"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:076eede537ab978b605f41db79a56cad2e7efeea2aa6e0fa8f05a26c24a034fb"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d1a50e461615747dd93c099f297c1994d472b0f4d2db8a64e55b1edf704ec1c"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7de89c8456525650ffa2bb56a3eee6af891e98f498babd43ae307bd42dca98f6"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4a88510731cd8d4befaba5fbd734a7dd914de5ab8132a5b3dde0bbd6c9476c64"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2d93a049d29df172f48bcb09acf9226318e712ce67374f893b460b42cc1380ae"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:21ac44b763e0eec15746a3d440f5e09ad2ecc8b5f6dcd3ea8cb4773d6d4703e3"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d0272228fabe78ce00a3365ffffd6f643f57a91043e119c289aaba202f4095b0"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:99449cd5366fe4608e7226c6cae80873296dfa0cde45d9b498fefa1de315a09e"}, + {file = "yarl-1.8.1-cp39-cp39-win32.whl", hash = "sha256:8b0af1cf36b93cee99a31a545fe91d08223e64390c5ecc5e94c39511832a4bb6"}, + {file = "yarl-1.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:de49d77e968de6626ba7ef4472323f9d2e5a56c1d85b7c0e2a190b2173d3b9be"}, + {file = "yarl-1.8.1.tar.gz", hash = "sha256:af887845b8c2e060eb5605ff72b6f2dd2aab7a761379373fd89d314f4752abbf"}, +] From 626d4051ba8e0437fcbab11d2c71480dce5f96cd Mon Sep 17 00:00:00 2001 From: Phanabani Date: Thu, 10 Nov 2022 23:42:40 -0500 Subject: [PATCH 06/33] Reformat with black. --- sandpiper/__init__.py | 7 +- sandpiper/_version.py | 2 +- sandpiper/bios/cog.py | 345 ++++++----- sandpiper/bios/strings.py | 40 +- sandpiper/birthdays/__init__.py | 5 +- sandpiper/birthdays/cog.py | 94 ++- sandpiper/birthdays/message.py | 40 +- sandpiper/common/IANA/database.py | 30 +- sandpiper/common/discord.py | 40 +- sandpiper/common/embeds.py | 40 +- sandpiper/common/misc.py | 10 +- sandpiper/common/paths.py | 2 +- sandpiper/common/test_misc.py | 23 +- sandpiper/common/time.py | 224 +++---- sandpiper/config.py | 35 +- sandpiper/conversion/cog.py | 50 +- sandpiper/conversion/time_conversion.py | 60 +- sandpiper/conversion/unit_conversion.py | 62 +- sandpiper/conversion/unit_map.py | 6 +- sandpiper/help.py | 66 ++- sandpiper/piperfig/exceptions.py | 18 +- sandpiper/piperfig/misc.py | 7 +- sandpiper/piperfig/parser.py | 131 ++-- sandpiper/piperfig/test_parser.py | 97 +-- sandpiper/piperfig/test_transformers.py | 63 +- sandpiper/piperfig/transformers.py | 52 +- sandpiper/sandpiper.py | 60 +- sandpiper/tests/__init__.py | 4 +- sandpiper/tests/conftest.py | 133 +++-- sandpiper/tests/helpers/discord.py | 40 +- sandpiper/tests/helpers/misc.py | 14 +- sandpiper/tests/helpers/mocking.py | 30 +- sandpiper/tests/test_bios.py | 557 +++++++++--------- sandpiper/tests/test_birthdays.py | 326 +++++----- sandpiper/tests/test_common_time.py | 211 ++----- sandpiper/tests/test_conversion.py | 496 +++++++--------- sandpiper/tests/test_database.py | 183 +++--- sandpiper/upgrades/cog.py | 7 +- sandpiper/upgrades/upgrades.py | 19 +- sandpiper/upgrades/versions/__init__.py | 4 +- .../upgrades/versions/sandpiper_1_6_0.py | 20 +- sandpiper/user_data/__init__.py | 21 +- sandpiper/user_data/alembic/env.py | 2 +- .../643337f23b38_add_sandpiper_meta_table.py | 12 +- ...3_add_birthday_notification_sent_column.py | 16 +- .../alembic/versions/91e18fdd475a_init.py | 31 +- .../versions/a2d3dc3c170e_add_guilds_table.py | 12 +- ...add_server_default_value_for_privacies_.py | 23 +- ...6d27e9d_rename_user_data_table_to_users.py | 8 +- ...9_rename_birthday_notification_sent_to_.py | 20 +- ...promote_snowflake_fields_from_integers_.py | 24 +- sandpiper/user_data/alembic_utils.py | 10 +- sandpiper/user_data/cog.py | 8 +- sandpiper/user_data/database.py | 72 +-- sandpiper/user_data/database_sqlite.py | 262 ++++---- sandpiper/user_data/models/_types.py | 6 +- sandpiper/user_data/models/guild.py | 4 +- sandpiper/user_data/models/sandpiper_meta.py | 4 +- sandpiper/user_data/models/user.py | 23 +- sandpiper/user_data/pronouns.py | 72 +-- sandpiper/user_data/test_pronouns.py | 88 ++- yappi_profile_tests.py | 120 ++-- 62 files changed, 2089 insertions(+), 2402 deletions(-) diff --git a/sandpiper/__init__.py b/sandpiper/__init__.py index 9cb2b23..9fa9bd7 100644 --- a/sandpiper/__init__.py +++ b/sandpiper/__init__.py @@ -1,11 +1,14 @@ import sys -if sys.platform == 'win32': + +if sys.platform == "win32": # Temporary fix for expired certificates on Windows import certifi import os - os.environ['SSL_CERT_FILE'] = certifi.where() + + os.environ["SSL_CERT_FILE"] = certifi.where() import logging + logger = logging.getLogger(__name__) from ._version import __version__ diff --git a/sandpiper/_version.py b/sandpiper/_version.py index bcd8d54..e4adfb8 100644 --- a/sandpiper/_version.py +++ b/sandpiper/_version.py @@ -1 +1 @@ -__version__ = '1.6.0' +__version__ = "1.6.0" diff --git a/sandpiper/bios/cog.py b/sandpiper/bios/cog.py index 4ce22ba..39cb7a4 100644 --- a/sandpiper/bios/cog.py +++ b/sandpiper/bios/cog.py @@ -12,9 +12,9 @@ from sandpiper.common.time import format_date, fuzzy_match_timezone from sandpiper.user_data import * -__all__ = ['Bios'] +__all__ = ["Bios"] -logger = logging.getLogger('sandpiper.bios') +logger = logging.getLogger("sandpiper.bios") def maybe_dm_only(): @@ -23,6 +23,7 @@ async def predicate(ctx: commands.Context): if not bios.allow_public_setting: return await commands.dm_only().predicate(ctx) return True + return commands.check(predicate) @@ -37,27 +38,25 @@ class Bios(commands.Cog): "time conversion and birthday notifications." ) - _show_aliases = ('get',) + _show_aliases = ("get",) _set_aliases = () - _delete_aliases = ('clear', 'remove') + _delete_aliases = ("clear", "remove") auto_order = AutoOrder() - def __init__( - self, bot: commands.Bot, *, allow_public_setting: bool = False - ): + def __init__(self, bot: commands.Bot, *, allow_public_setting: bool = False): self.bot = bot self.allow_public_setting = allow_public_setting async def _get_database(self) -> Database: - user_data: Optional[UserData] = self.bot.get_cog('UserData') + user_data: Optional[UserData] = self.bot.get_cog("UserData") if user_data is None: - raise RuntimeError('UserData cog is not loaded.') + raise RuntimeError("UserData cog is not loaded.") return await user_data.get_database() @commands.Cog.listener() async def on_command_error( - self, ctx: commands.Context, error: commands.CommandError + self, ctx: commands.Context, error: commands.CommandError ): if isinstance(error, commands.CommandInvokeError): if isinstance(error.original, DatabaseUnavailable): @@ -76,9 +75,9 @@ async def on_command_error( else: logger.error( f'Unexpected error in "{ctx.command}" (' - f'content={ctx.message.content!r} ' - f'message={ctx.message!r})', - exc_info=error.original + f"content={ctx.message.content!r} " + f"message={ctx.message!r})", + exc_info=error.original, ) await ErrorEmbed("Unexpected error.").send(ctx) else: @@ -88,45 +87,47 @@ async def on_command_error( async def on_command(self, ctx: commands.Context): logger.info( f'Running command "{ctx.command}" (author={ctx.author} ' - f'content={ctx.message.content!r})' + f"content={ctx.message.content!r})" ) - @commands.Cog.listener('on_command_completion') + @commands.Cog.listener("on_command_completion") async def notify_birthdays_cog(self, ctx: commands.Context): if ctx.command_failed: # Not sure if this is possible here but might as well check return if ctx.command.qualified_name in ( - 'birthday set', 'timezone set', - 'privacy all', 'privacy birthday', 'privacy timezone' + "birthday set", + "timezone set", + "privacy all", + "privacy birthday", + "privacy timezone", ): logger.debug( f"Notifying birthdays cog about change from command " f"{ctx.command.qualified_name} (user_id={ctx.author.id})" ) birthdays_cog: Birthdays - birthdays_cog = self.bot.get_cog('Birthdays') + birthdays_cog = self.bot.get_cog("Birthdays") if birthdays_cog is None: - logger.debug( - "No birthdays cog loaded; skipping change notification" - ) + logger.debug("No birthdays cog loaded; skipping change notification") return await birthdays_cog.notify_change(ctx.author.id) @auto_order @commands.group( brief="Personal info commands.", - help="Commands for managing all of your personal info at once." + help="Commands for managing all of your personal info at once.", ) async def bio(self, ctx: commands.Context): pass @auto_order @bio.command( - name='show', aliases=_show_aliases, + name="show", + aliases=_show_aliases, brief="Show all stored info.", - help="Display all of your personal info stored in Sandpiper." + help="Display all of your personal info stored in Sandpiper.", ) @maybe_dm_only() async def bio_show(self, ctx: commands.Context): @@ -138,7 +139,7 @@ async def bio_show(self, ctx: commands.Context): birthday = await db.get_birthday(user_id) birthday = format_date(birthday) age = await db.get_age(user_id) - age = age if age is not None else 'N/A' + age = age if age is not None else "N/A" timezone = await db.get_timezone(user_id) p_preferred_name = await db.get_privacy_preferred_name(user_id) @@ -147,19 +148,22 @@ async def bio_show(self, ctx: commands.Context): p_age = await db.get_privacy_age(user_id) p_timezone = await db.get_privacy_timezone(user_id) - await InfoEmbed([ - user_info_str('Name', preferred_name, p_preferred_name), - user_info_str('Pronouns', pronouns, p_pronouns), - user_info_str('Birthday', birthday, p_birthday), - user_info_str('Age', age, p_age), - user_info_str('Timezone', timezone, p_timezone) - ]).send(ctx) + await InfoEmbed( + [ + user_info_str("Name", preferred_name, p_preferred_name), + user_info_str("Pronouns", pronouns, p_pronouns), + user_info_str("Birthday", birthday, p_birthday), + user_info_str("Age", age, p_age), + user_info_str("Timezone", timezone, p_timezone), + ] + ).send(ctx) @auto_order @bio.command( - name='delete', aliases=_delete_aliases, + name="delete", + aliases=_delete_aliases, brief="Delete all stored info.", - help="Delete all of your personal info stored in Sandpiper." + help="Delete all of your personal info stored in Sandpiper.", ) @maybe_dm_only() async def bio_delete(self, ctx: commands.Context): @@ -172,26 +176,25 @@ async def bio_delete(self, ctx: commands.Context): @auto_order @commands.group( - name='privacy', invoke_without_command=False, + name="privacy", + invoke_without_command=False, brief="Personal info privacy commands.", - help="Commands for setting the privacy of your personal info." + help="Commands for setting the privacy of your personal info.", ) async def privacy(self, ctx: commands.Context): pass @auto_order @privacy.command( - name='all', + name="all", brief="Set all privacies at once.", help=( "Set the privacy of all of your personal info at once to either " "'private' or 'public'." ), - example="privacy all public" + example="privacy all public", ) - async def privacy_all( - self, ctx: commands.Context, new_privacy: privacy_handler - ): + async def privacy_all(self, ctx: commands.Context, new_privacy: privacy_handler): user_id: int = ctx.author.id db = await self._get_database() await db.set_privacy_preferred_name(user_id, new_privacy) @@ -200,7 +203,7 @@ async def privacy_all( await db.set_privacy_age(user_id, new_privacy) await db.set_privacy_timezone(user_id, new_privacy) - embed = SuccessEmbed("All privacies set!", join='\n\n') + embed = SuccessEmbed("All privacies set!", join="\n\n") if new_privacy is PrivacyType.PUBLIC: embed.append(BirthdayExplanations.birthday_is_public) embed.append(BirthdayExplanations.age_is_public) @@ -211,17 +214,14 @@ async def privacy_all( @auto_order @privacy.command( - name='name', + name="name", brief="Set preferred name privacy.", help=( - "Set the privacy of your preferred name to either 'private' or " - "'public'." + "Set the privacy of your preferred name to either 'private' or " "'public'." ), - example="privacy name public" + example="privacy name public", ) - async def privacy_name( - self, ctx: commands.Context, new_privacy: privacy_handler - ): + async def privacy_name(self, ctx: commands.Context, new_privacy: privacy_handler): user_id: int = ctx.author.id db = await self._get_database() await db.set_privacy_preferred_name(user_id, new_privacy) @@ -229,15 +229,13 @@ async def privacy_name( @auto_order @privacy.command( - name='pronouns', + name="pronouns", brief="Set pronouns privacy.", - help=( - "Set the privacy of your pronouns to either 'private' or 'public'." - ), - example="privacy pronouns public" + help=("Set the privacy of your pronouns to either 'private' or 'public'."), + example="privacy pronouns public", ) async def privacy_pronouns( - self, ctx: commands.Context, new_privacy: privacy_handler + self, ctx: commands.Context, new_privacy: privacy_handler ): user_id: int = ctx.author.id db = await self._get_database() @@ -246,20 +244,18 @@ async def privacy_pronouns( @auto_order @privacy.command( - name='birthday', + name="birthday", brief="Set birthday privacy.", - help=( - "Set the privacy of your birthday to either 'private' or 'public'." - ), - example="privacy birthday public" + help=("Set the privacy of your birthday to either 'private' or 'public'."), + example="privacy birthday public", ) async def privacy_birthday( - self, ctx: commands.Context, new_privacy: privacy_handler + self, ctx: commands.Context, new_privacy: privacy_handler ): user_id: int = ctx.author.id db = await self._get_database() await db.set_privacy_birthday(user_id, new_privacy) - embed = SuccessEmbed("Birthday privacy set!", join='\n\n') + embed = SuccessEmbed("Birthday privacy set!", join="\n\n") # Tell them how their privacy affects their birthday announcement if new_privacy is PrivacyType.PRIVATE: @@ -277,20 +273,16 @@ async def privacy_birthday( @auto_order @privacy.command( - name='age', + name="age", brief="Set age privacy.", - help=( - "Set the privacy of your age to either 'private' or 'public'." - ), - example="privacy age public" + help=("Set the privacy of your age to either 'private' or 'public'."), + example="privacy age public", ) - async def privacy_age( - self, ctx: commands.Context, new_privacy: privacy_handler - ): + async def privacy_age(self, ctx: commands.Context, new_privacy: privacy_handler): user_id: int = ctx.author.id db = await self._get_database() await db.set_privacy_age(user_id, new_privacy) - embed = SuccessEmbed("Age privacy set!", join='\n\n') + embed = SuccessEmbed("Age privacy set!", join="\n\n") # Tell them how their privacy affects their birthday announcement bday_privacy = await db.get_privacy_birthday(user_id) @@ -304,15 +296,13 @@ async def privacy_age( @auto_order @privacy.command( - name='timezone', + name="timezone", brief="Set timezone privacy.", - help=( - "Set the privacy of your timezone to either 'private' or 'public'." - ), - example="privacy timezone public" + help=("Set the privacy of your timezone to either 'private' or 'public'."), + example="privacy timezone public", ) async def privacy_timezone( - self, ctx: commands.Context, new_privacy: privacy_handler + self, ctx: commands.Context, new_privacy: privacy_handler ): user_id: int = ctx.author.id db = await self._get_database() @@ -323,17 +313,17 @@ async def privacy_timezone( @auto_order @commands.group( - name='name', invoke_without_command=False, + name="name", + invoke_without_command=False, brief="Preferred name commands.", - help="Commands for managing your preferred name." + help="Commands for managing your preferred name.", ) async def name(self, ctx: commands.Context): pass @auto_order @name.command( - name='show', aliases=_show_aliases, - help="Display your preferred name." + name="show", aliases=_show_aliases, help="Display your preferred name." ) @maybe_dm_only() async def name_show(self, ctx: commands.Context): @@ -341,14 +331,15 @@ async def name_show(self, ctx: commands.Context): db = await self._get_database() preferred_name = await db.get_preferred_name(user_id) privacy = await db.get_privacy_preferred_name(user_id) - await InfoEmbed(user_info_str('Name', preferred_name, privacy)).send(ctx) + await InfoEmbed(user_info_str("Name", preferred_name, privacy)).send(ctx) @auto_order @name.command( - name='set', aliases=_set_aliases, + name="set", + aliases=_set_aliases, brief="Set your preferred name.", help="Set your preferred name. Must be 64 characters or less.", - example="name set Phana" + example="name set Phana", ) @maybe_dm_only() async def name_set(self, ctx: commands.Context, *, new_name: str): @@ -359,17 +350,16 @@ async def name_set(self, ctx: commands.Context, *, new_name: str): f"Name must be 64 characters or less (yours: {len(new_name)})." ) await db.set_preferred_name(user_id, new_name) - embed = SuccessEmbed("Preferred name set!", join='\n\n') + embed = SuccessEmbed("Preferred name set!", join="\n\n") if await db.get_privacy_preferred_name(user_id) is PrivacyType.PRIVATE: - embed.append(PrivacyExplanation.get('name')) + embed.append(PrivacyExplanation.get("name")) await embed.send(ctx) @auto_order @name.command( - name='delete', aliases=_delete_aliases, - help="Delete your preferred name." + name="delete", aliases=_delete_aliases, help="Delete your preferred name." ) @maybe_dm_only() async def name_delete(self, ctx: commands.Context): @@ -382,32 +372,31 @@ async def name_delete(self, ctx: commands.Context): @auto_order @commands.group( - name='pronouns', invoke_without_command=False, + name="pronouns", + invoke_without_command=False, brief="Pronouns commands.", - help="Commands for managing your pronouns." + help="Commands for managing your pronouns.", ) async def pronouns(self, ctx: commands.Context): pass @auto_order - @pronouns.command( - name='show', aliases=_show_aliases, - help="Display your pronouns." - ) + @pronouns.command(name="show", aliases=_show_aliases, help="Display your pronouns.") @maybe_dm_only() async def pronouns_show(self, ctx: commands.Context): user_id: int = ctx.author.id db = await self._get_database() pronouns = await db.get_pronouns(user_id) privacy = await db.get_privacy_pronouns(user_id) - await InfoEmbed(user_info_str('Pronouns', pronouns, privacy)).send(ctx) + await InfoEmbed(user_info_str("Pronouns", pronouns, privacy)).send(ctx) @auto_order @pronouns.command( - name='set', aliases=_set_aliases, + name="set", + aliases=_set_aliases, brief="Set your pronouns.", help="Set your pronouns. Must be 64 characters or less.", - example="pronouns set She/Her" + example="pronouns set She/Her", ) @maybe_dm_only() async def pronouns_set(self, ctx: commands.Context, *, new_pronouns: str): @@ -419,17 +408,16 @@ async def pronouns_set(self, ctx: commands.Context, *, new_pronouns: str): f"{len(new_pronouns)})." ) await db.set_pronouns(user_id, new_pronouns) - embed = SuccessEmbed('Pronouns set!', join='\n\n') + embed = SuccessEmbed("Pronouns set!", join="\n\n") if await db.get_privacy_pronouns(user_id) == PrivacyType.PRIVATE: - embed.append(PrivacyExplanation.get('pronouns')) + embed.append(PrivacyExplanation.get("pronouns")) await embed.send(ctx) @auto_order @pronouns.command( - name='delete', aliases=_delete_aliases, - help="Delete your pronouns." + name="delete", aliases=_delete_aliases, help="Delete your pronouns." ) @maybe_dm_only() async def pronouns_delete(self, ctx: commands.Context): @@ -442,18 +430,16 @@ async def pronouns_delete(self, ctx: commands.Context): @auto_order @commands.group( - name='birthday', invoke_without_command=False, + name="birthday", + invoke_without_command=False, brief="Birthday commands.", - help="Commands for managing your birthday." + help="Commands for managing your birthday.", ) async def birthday(self, ctx: commands.Context): pass @auto_order - @birthday.command( - name='show', aliases=_show_aliases, - help="Display your birthday." - ) + @birthday.command(name="show", aliases=_show_aliases, help="Display your birthday.") @maybe_dm_only() async def birthday_show(self, ctx: commands.Context): user_id: int = ctx.author.id @@ -461,11 +447,12 @@ async def birthday_show(self, ctx: commands.Context): birthday = await db.get_birthday(user_id) birthday = format_date(birthday) privacy = await db.get_privacy_birthday(user_id) - await InfoEmbed(user_info_str('Birthday', birthday, privacy)).send(ctx) + await InfoEmbed(user_info_str("Birthday", birthday, privacy)).send(ctx) @auto_order @birthday.command( - name='set', aliases=_set_aliases, + name="set", + aliases=_set_aliases, brief="Set your birthday.", help=( "Set your birthday. There are several allowed formats, and some " @@ -480,17 +467,15 @@ async def birthday_show(self, ctx: commands.Context): "birthday set Aug 8 1997", "birthday set August 8", "birthday set 8 Aug", - ) + ), ) @maybe_dm_only() - async def birthday_set( - self, ctx: commands.Context, *, new_birthday: date_handler - ): + async def birthday_set(self, ctx: commands.Context, *, new_birthday: date_handler): user_id: int = ctx.author.id db = await self._get_database() await db.set_birthday(user_id, new_birthday) - embed = SuccessEmbed("Birthday set!", join='\n\n') + embed = SuccessEmbed("Birthday set!", join="\n\n") # Tell them how their privacy affects their birthday announcement bday_privacy = await db.get_privacy_birthday(user_id) @@ -510,8 +495,7 @@ async def birthday_set( @auto_order @birthday.command( - name='delete', aliases=_delete_aliases, - help="Delete your birthday." + name="delete", aliases=_delete_aliases, help="Delete your birthday." ) @maybe_dm_only() async def birthday_delete(self, ctx: commands.Context): @@ -524,37 +508,41 @@ async def birthday_delete(self, ctx: commands.Context): @auto_order @commands.group( - name='age', invoke_without_command=False, + name="age", + invoke_without_command=False, brief="Age commands.", - help="Commands for managing your age." + help="Commands for managing your age.", ) async def age(self, ctx: commands.Context): pass @auto_order @age.command( - name='show', aliases=_show_aliases, + name="show", + aliases=_show_aliases, brief="Display your age.", - help="Display your age (calculated automatically using your birthday)." + help="Display your age (calculated automatically using your birthday).", ) @maybe_dm_only() async def age_show(self, ctx: commands.Context): user_id: int = ctx.author.id db = await self._get_database() age = await db.get_age(user_id) - age = age if age is not None else 'N/A' + age = age if age is not None else "N/A" privacy = await db.get_privacy_age(user_id) - await InfoEmbed(user_info_str('Age', age, privacy)).send(ctx) + await InfoEmbed(user_info_str("Age", age, privacy)).send(ctx) # noinspection PyUnusedLocal @auto_order @age.command( - name='set', aliases=_set_aliases, hidden=True, + name="set", + aliases=_set_aliases, + hidden=True, brief="This command does nothing.", help=( "Age is automatically calculated using your birthday. This " "command exists only to let you know that you don't have to set it." - ) + ), ) @maybe_dm_only() async def age_set(self, ctx: commands.Context): @@ -566,12 +554,14 @@ async def age_set(self, ctx: commands.Context): # noinspection PyUnusedLocal @auto_order @age.command( - name='delete', aliases=_delete_aliases, hidden=True, + name="delete", + aliases=_delete_aliases, + hidden=True, brief="This command does nothing.", help=( "Age is automatically calculated using your birthday. This command " "exists only to let you know that you can only delete your birthday." - ) + ), ) @maybe_dm_only() async def age_delete(self, ctx: commands.Context): @@ -586,29 +576,28 @@ async def age_delete(self, ctx: commands.Context): @auto_order @commands.group( - name='timezone', invoke_without_command=False, + name="timezone", + invoke_without_command=False, brief="Timezone commands.", - help="Commands for managing your timezone." + help="Commands for managing your timezone.", ) async def timezone(self, ctx: commands.Context): pass @auto_order - @timezone.command( - name='show', aliases=_show_aliases, - help="Display your timezone." - ) + @timezone.command(name="show", aliases=_show_aliases, help="Display your timezone.") @maybe_dm_only() async def timezone_show(self, ctx: commands.Context): user_id: int = ctx.author.id db = await self._get_database() timezone = await db.get_timezone(user_id) privacy = await db.get_privacy_timezone(user_id) - await InfoEmbed(user_info_str('Timezone', timezone, privacy)).send(ctx) + await InfoEmbed(user_info_str("Timezone", timezone, privacy)).send(ctx) @auto_order @timezone.command( - name='set', aliases=_set_aliases, + name="set", + aliases=_set_aliases, brief="Set your timezone.", help=( "Set your timezone. Don't worry about formatting. Typing the " @@ -623,7 +612,7 @@ async def timezone_show(self, ctx: commands.Context): "timezone set new york", "timezone set amsterdam", "timezone set london", - ) + ), ) @maybe_dm_only() async def timezone_set(self, ctx: commands.Context, *, new_timezone: str): @@ -631,8 +620,7 @@ async def timezone_set(self, ctx: commands.Context, *, new_timezone: str): db = await self._get_database() tz_matches = fuzzy_match_timezone( - new_timezone, best_match_threshold=50, lower_score_cutoff=50, - limit=5 + new_timezone, best_match_threshold=50, lower_score_cutoff=50, limit=5 ) if not tz_matches.matches: # No matches @@ -646,30 +634,33 @@ async def timezone_set(self, ctx: commands.Context, *, new_timezone: str): if not tz_matches.best_match: # No best match; display other possible matches - await ErrorEmbed([ - "Couldn't find a good match for the timezone you entered.", - "\nPossible matches:", - '\n'.join([f'- {name}' for name, _ in tz_matches.matches]) - ]).send(ctx) + await ErrorEmbed( + [ + "Couldn't find a good match for the timezone you entered.", + "\nPossible matches:", + "\n".join([f"- {name}" for name, _ in tz_matches.matches]), + ] + ).send(ctx) return # Display best match with other possible matches await db.set_timezone(user_id, tz_matches.best_match) - embed = SuccessEmbed([ - f"Timezone set to **{tz_matches.best_match}**!", - len(tz_matches.matches) > 1 and "\nOther possible matches:", - *[f'- {name}' for name, _ in tz_matches.matches[1:]] - ]) + embed = SuccessEmbed( + [ + f"Timezone set to **{tz_matches.best_match}**!", + len(tz_matches.matches) > 1 and "\nOther possible matches:", + *[f"- {name}" for name, _ in tz_matches.matches[1:]], + ] + ) if await db.get_privacy_timezone(user_id) == PrivacyType.PRIVATE: - embed.append('\n' + PrivacyExplanation.get('timezone')) + embed.append("\n" + PrivacyExplanation.get("timezone")) await embed.send(ctx) @auto_order @timezone.command( - name='delete', aliases=_delete_aliases, - help="Delete your timezone." + name="delete", aliases=_delete_aliases, help="Delete your timezone." ) @maybe_dm_only() async def timezone_delete(self, ctx: commands.Context): @@ -682,9 +673,10 @@ async def timezone_delete(self, ctx: commands.Context): @auto_order @commands.group( - name='server', invoke_without_command=False, + name="server", + invoke_without_command=False, brief="Server commands. (admin only)", - help="Commands for managing server settings. (admin only)" + help="Commands for managing server settings. (admin only)", ) async def server(self, ctx: commands.Context): pass @@ -693,23 +685,23 @@ async def server(self, ctx: commands.Context): @auto_order @server.group( - name='birthday_channel', invoke_without_command=False, + name="birthday_channel", + invoke_without_command=False, brief="Birthday notification channel commands", - help=( - "Commands for managing the birthday notification channel" - ) + help=("Commands for managing the birthday notification channel"), ) async def server_birthday_channel(self, ctx: commands.Context): pass @auto_order @server_birthday_channel.command( - name='show', aliases=_show_aliases, + name="show", + aliases=_show_aliases, brief="Show the birthday notification channel", help=( "Show the birthday notification channel in your server. This is " "where Sandpiper will send messages when it's someone's birthday." - ) + ), ) @commands.has_guild_permissions(administrator=True) async def server_birthday_channel_show(self, ctx: commands.Context): @@ -721,22 +713,23 @@ async def server_birthday_channel_show(self, ctx: commands.Context): await InfoEmbed(info_str("Birthday channel", "N/A")).send(ctx) return - await InfoEmbed(info_str( - "Birthday channel", f"<#{bday_channel_id}> (id={bday_channel_id})" - )).send(ctx) + await InfoEmbed( + info_str("Birthday channel", f"<#{bday_channel_id}> (id={bday_channel_id})") + ).send(ctx) @auto_order @server_birthday_channel.command( - name='set', aliases=_set_aliases, + name="set", + aliases=_set_aliases, brief="Set the birthday notification channel", help=( "Set the birthday notification channel in your server. This is " "where Sandpiper will send messages when it's someone's birthday." - ) + ), ) @commands.has_guild_permissions(administrator=True) async def server_birthday_channel_set( - self, ctx: commands.Context, new_channel: discord.TextChannel + self, ctx: commands.Context, new_channel: discord.TextChannel ): guild_id: int = ctx.guild.id db = await self._get_database() @@ -745,12 +738,13 @@ async def server_birthday_channel_set( @auto_order @server_birthday_channel.command( - name='delete', aliases=_delete_aliases, + name="delete", + aliases=_delete_aliases, brief="Delete the birthday notification channel", help=( "Delete the birthday notification channel in your server. This is " "where Sandpiper will send messages when it's someone's birthday." - ) + ), ) @commands.has_guild_permissions(administrator=True) async def server_birthday_channel_delete(self, ctx: commands.Context): @@ -766,14 +760,14 @@ async def server_birthday_channel_delete(self, ctx: commands.Context): @auto_order @commands.command( - name='whois', + name="whois", brief="Search for a user.", help=( "Search for a user by one of their names. Outputs a list of " "matching users, showing their preferred name, Discord username, " "and nicknames in servers you share with them." ), - example="whois phana" + example="whois phana", ) async def whois(self, ctx: commands.Context, *, name: str): if len(name) < 2: @@ -808,8 +802,8 @@ def should_skip_user(user_id: int, *, skip_guild_check=False): # We're in DMs, so check if the executor shares a guild # with the target if not find_user_in_mutual_guilds( - ctx.bot, ctx.author.id, user_id, - short_circuit=True): + ctx.bot, ctx.author.id, user_id, short_circuit=True + ): # Executor doesn't share a guild with target return True return False @@ -824,24 +818,21 @@ def should_skip_user(user_id: int, *, skip_guild_check=False): user_strs.append(names) for user_id, display_name in find_users_by_display_name( - ctx.bot, ctx.author.id, name, guild=ctx.guild): + ctx.bot, ctx.author.id, name, guild=ctx.guild + ): # Get display names from guilds # This search function filters out non-mutual-guild users as part # of its optimization, so we don't need to do that again if should_skip_user(user_id, skip_guild_check=True): continue - names = await user_names_str( - ctx, db, user_id, display_name=display_name - ) + names = await user_names_str(ctx, db, user_id, display_name=display_name) user_strs.append(names) for user_id, username in find_users_by_username(ctx.bot, name): # Get usernames from client if should_skip_user(user_id): continue - names = await user_names_str( - ctx, db, user_id, username=username - ) + names = await user_names_str(ctx, db, user_id, username=username) user_strs.append(names) if user_strs: diff --git a/sandpiper/bios/strings.py b/sandpiper/bios/strings.py index dce1471..96333d1 100644 --- a/sandpiper/bios/strings.py +++ b/sandpiper/bios/strings.py @@ -8,18 +8,17 @@ from sandpiper.user_data import Database, PrivacyType __all__ = [ - 'PrivacyExplanation', 'BirthdayExplanations', - 'info_str', 'user_info_str', 'user_names_str', + "PrivacyExplanation", + "BirthdayExplanations", + "info_str", + "user_info_str", + "user_names_str", ] -privacy_emojis = { - PrivacyType.PRIVATE: '⛔', - PrivacyType.PUBLIC: '✅' -} +privacy_emojis = {PrivacyType.PRIVATE: "⛔", PrivacyType.PUBLIC: "✅"} class PrivacyExplanation: - @staticmethod def get(friendly_name: str, privacy_name: Optional[str] = None): if privacy_name is None: @@ -66,9 +65,13 @@ def user_info_str(field_name: str, value: Any, privacy: PrivacyType): async def user_names_str( - ctx: commands.Context, db: Database, user_id: int, - *, preferred_name: str = None, username: str = None, - display_name: str = None + ctx: commands.Context, + db: Database, + user_id: int, + *, + preferred_name: str = None, + username: str = None, + display_name: str = None, ): """ Create a string with a user's names (preferred name, Discord username, @@ -90,23 +93,23 @@ async def user_names_str( if privacy_preferred_name == PrivacyType.PUBLIC: preferred_name = await db.get_preferred_name(user_id) if preferred_name is None: - preferred_name = '`No preferred name`' + preferred_name = "`No preferred name`" else: - preferred_name = '`No preferred name`' + preferred_name = "`No preferred name`" # Get discord username and discriminator if username is None: user: discord.User = ctx.bot.get_user(user_id) if user is not None: - username = f'{user.name}#{user.discriminator}' + username = f"{user.name}#{user.discriminator}" else: - username = '`User not found`' + username = "`User not found`" if ctx.guild is None: # Find the user's nicknames on servers they share with the executor # of the whois command members = find_user_in_mutual_guilds(ctx.bot, ctx.author.id, user_id) - display_names = ', '.join(m.display_name for m in members) + display_names = ", ".join(m.display_name for m in members) else: if display_name is None: # Find the user's nickname in the current guild ONLY @@ -116,7 +119,8 @@ async def user_names_str( display_names = display_name return join( - join(preferred_name, pronouns and f'({pronouns})', sep=' '), - username, display_names, - sep=' • ' + join(preferred_name, pronouns and f"({pronouns})", sep=" "), + username, + display_names, + sep=" • ", ) diff --git a/sandpiper/birthdays/__init__.py b/sandpiper/birthdays/__init__.py index e629fb9..e3c7c2c 100644 --- a/sandpiper/birthdays/__init__.py +++ b/sandpiper/birthdays/__init__.py @@ -5,9 +5,10 @@ def setup(bot: Sandpiper): config = bot.modules_config.birthdays birthdays = Birthdays( - bot, message_templates_no_age=config.message_templates_no_age, + bot, + message_templates_no_age=config.message_templates_no_age, message_templates_with_age=config.message_templates_with_age, past_birthdays_day_range=config.past_birthdays_day_range, - upcoming_birthdays_day_range=config.upcoming_birthdays_day_range + upcoming_birthdays_day_range=config.upcoming_birthdays_day_range, ) bot.add_cog(birthdays) diff --git a/sandpiper/birthdays/cog.py b/sandpiper/birthdays/cog.py index a7de2de..3e42d25 100644 --- a/sandpiper/birthdays/cog.py +++ b/sandpiper/birthdays/cog.py @@ -12,23 +12,22 @@ from sandpiper.birthdays.message import format_birthday_message from sandpiper.common.discord import AutoOrder, cheap_user_hash from sandpiper.common.time import sort_dates_no_year, utc_now -from sandpiper.user_data import ( - UserData, Database, PrivacyType, - common_pronouns -) +from sandpiper.user_data import UserData, Database, PrivacyType, common_pronouns -__all__ = ['Birthdays'] +__all__ = ["Birthdays"] -logger = logging.getLogger('sandpiper.birthdays') +logger = logging.getLogger("sandpiper.birthdays") class Birthdays(commands.Cog): - def __init__( - self, bot: commands.Bot, *, message_templates_no_age: list[str], - message_templates_with_age: list[str], - past_birthdays_day_range: int, - upcoming_birthdays_day_range: int + self, + bot: commands.Bot, + *, + message_templates_no_age: list[str], + message_templates_with_age: list[str], + past_birthdays_day_range: int, + upcoming_birthdays_day_range: int, ): """Send happy birthday messages to users.""" self.bot = bot @@ -46,9 +45,9 @@ def _create_birthday_task(self, user_id: int, midnight_delta: dt.timedelta): task.add_done_callback(self._handle_task_exception) async def _get_database(self) -> Database: - user_data: Optional[UserData] = self.bot.get_cog('UserData') + user_data: Optional[UserData] = self.bot.get_cog("UserData") if user_data is None: - raise RuntimeError('UserData cog is not loaded.') + raise RuntimeError("UserData cog is not loaded.") return await user_data.get_database() def _get_random_message(self, age=False): @@ -66,9 +65,7 @@ def _handle_task_exception(self, task: asyncio.Task) -> None: async def _try_cancel_task(self, user_id): if user_id in self.tasks: - logger.info( - f"Canceling birthday notification task (user={user_id})" - ) + logger.info(f"Canceling birthday notification task (user={user_id})") self.tasks[user_id].cancel() del self.tasks[user_id] @@ -100,8 +97,9 @@ async def schedule_todays_birthdays(self): # account scheduled_count = 0 birthdays_today_tomorrow = await db.get_birthdays_range( - today - dt.timedelta(days=1), today + dt.timedelta(days=1), - max_last_notification_time=now - dt.timedelta(hours=24) + today - dt.timedelta(days=1), + today + dt.timedelta(days=1), + max_last_notification_time=now - dt.timedelta(hours=24), ) for user_id, birthday in birthdays_today_tomorrow: if await self.schedule_birthday(user_id, birthday, now=now): @@ -109,8 +107,7 @@ async def schedule_todays_birthdays(self): logger.info(f"{scheduled_count} birthdays scheduled for today") async def schedule_birthday( - self, user_id: int, birthday: dt.date, - *, now: Optional[dt.datetime] = None + self, user_id: int, birthday: dt.date, *, now: Optional[dt.datetime] = None ) -> bool: """ Schedule a task that will wish this user happy birthday if their @@ -161,8 +158,10 @@ async def schedule_birthday( # Otherwise, if we missed their midnight but it's still their birthday, # we can send immediately (negative delta will not sleep) now_local: dt.datetime = now.astimezone(timezone) - if (midnight_delta < dt.timedelta(0) - and now_local.date() == birthday_this_year.date()): + if ( + midnight_delta < dt.timedelta(0) + and now_local.date() == birthday_this_year.date() + ): self._create_birthday_task(user_id, midnight_delta) return True @@ -183,9 +182,7 @@ async def send_birthday_message(self, user_id: int, delta: dt.timedelta): ) await asyncio.sleep(delta.total_seconds()) - logger.info( - f"Sending birthday notifications for user (user={user_id})" - ) + logger.info(f"Sending birthday notifications for user (user={user_id})") db = await self._get_database() user: discord.User = self.bot.get_user(user_id) if user is None: @@ -216,7 +213,7 @@ async def send_birthday_message(self, user_id: int, delta: dt.timedelta): # but we need to pick one, so this is the best we can do for now pronouns = pronouns[0] else: - pronouns = common_pronouns['they'] + pronouns = common_pronouns["they"] age = None if (await db.get_privacy_age(user_id)) is PrivacyType.PUBLIC: @@ -260,8 +257,11 @@ async def send_birthday_message(self, user_id: int, delta: dt.timedelta): else: bday_msg_template = self._get_random_message(age=age is not None) bday_msg = format_birthday_message( - bday_msg_template, user_id=user_id, - name=name, pronouns=pronouns, age=age + bday_msg_template, + user_id=user_id, + name=name, + pronouns=pronouns, + age=age, ) await bday_channel.send(bday_msg) @@ -269,8 +269,7 @@ async def send_birthday_message(self, user_id: int, delta: dt.timedelta): await db.set_last_birthday_notification(user_id, utc_now()) async def get_past_upcoming_birthdays( - self, past_birthdays_day_range: int = 7, - upcoming_birthdays_day_range: int = 14 + self, past_birthdays_day_range: int = 7, upcoming_birthdays_day_range: int = 14 ) -> tuple[list[tuple[int, dt.date]], list[tuple[int, dt.date]]]: """ Get two lists of past and upcoming birthdays. This may be used in a @@ -286,9 +285,7 @@ async def get_past_upcoming_birthdays( today = now.date() past_delta = dt.timedelta(days=past_birthdays_day_range) upcoming_delta = dt.timedelta(days=upcoming_birthdays_day_range) - past_birthdays = await db.get_birthdays_range( - today - past_delta, today - ) + past_birthdays = await db.get_birthdays_range(today - past_delta, today) upcoming_birthdays = await db.get_birthdays_range( today + dt.timedelta(days=1), today + upcoming_delta ) @@ -309,10 +306,7 @@ async def notify_change(self, user_id: int): # Either of these two conditions means the birthday must be canceled # and we will not reschedule - if ( - birthday is None - or birthday_privacy is PrivacyType.PRIVATE - ): + if birthday is None or birthday_privacy is PrivacyType.PRIVATE: await self._try_cancel_task(user_id) return @@ -321,20 +315,21 @@ async def notify_change(self, user_id: int): # region Commands auto_order = AutoOrder() - PAST_BIRTHDAY_EMOJIS = '🔷' - UPCOMING_BIRTHDAY_EMOJIS = '🎂🍰🧁🎈🎁🎉🎊' + PAST_BIRTHDAY_EMOJIS = "🔷" + UPCOMING_BIRTHDAY_EMOJIS = "🎂🍰🧁🎈🎁🎉🎊" @auto_order @commands.group( - name='birthdays', invoke_without_command=False, + name="birthdays", + invoke_without_command=False, brief="Birthday commands.", - help="Commands for viewing birthdays." + help="Commands for viewing birthdays.", ) async def birthdays(self, ctx: commands.Context): pass async def format_bday_upcoming( - self, user_id: int, guild: discord.Guild, past: bool + self, user_id: int, guild: discord.Guild, past: bool ) -> Optional[str]: db = await self._get_database() @@ -363,8 +358,7 @@ async def format_bday_upcoming( @auto_order @birthdays.command( - name='upcoming', aliases=('soon',), - help="View upcoming birthdays." + name="upcoming", aliases=("soon",), help="View upcoming birthdays." ) async def birthdays_upcoming(self, ctx: commands.Context): past_raw, upcoming_raw = await self.get_past_upcoming_birthdays( @@ -374,17 +368,13 @@ async def birthdays_upcoming(self, ctx: commands.Context): past = [] for user_id, _ in sort_dates_no_year(past_raw, lambda x: x[1], now): - bday_str = await self.format_bday_upcoming( - user_id, ctx.guild, past=True - ) + bday_str = await self.format_bday_upcoming(user_id, ctx.guild, past=True) if bday_str: past.append(bday_str) upcoming = [] for user_id, _ in sort_dates_no_year(upcoming_raw, lambda x: x[1], now): - bday_str = await self.format_bday_upcoming( - user_id, ctx.guild, past=False - ) + bday_str = await self.format_bday_upcoming(user_id, ctx.guild, past=False) if bday_str: upcoming.append(bday_str) @@ -398,12 +388,12 @@ async def birthdays_upcoming(self, ctx: commands.Context): msg.extend(past) if past and upcoming: - msg.append('') + msg.append("") if upcoming: msg.append(f"Upcoming birthdays:") msg.extend(upcoming) - await ctx.send('\n'.join(msg)) + await ctx.send("\n".join(msg)) # endregion diff --git a/sandpiper/birthdays/message.py b/sandpiper/birthdays/message.py index be4857f..81e4342 100644 --- a/sandpiper/birthdays/message.py +++ b/sandpiper/birthdays/message.py @@ -5,17 +5,13 @@ from sandpiper.user_data import Pronouns, common_pronouns -base_ordinal_suffix = 'th' -ordinal_suffixes = { - 1: 'st', - 2: 'nd', - 3: 'rd' -} +base_ordinal_suffix = "th" +ordinal_suffixes = {1: "st", 2: "nd", 3: "rd"} def capitalize_first(string: str) -> str: if not string: - return '' + return "" return string[0].upper() + string[1:] @@ -24,30 +20,30 @@ def get_ordinal_suffix(number: int) -> str: def age_with_suffix(age: int) -> str: - return f'{age}{get_ordinal_suffix(age)}' + return f"{age}{get_ordinal_suffix(age)}" def format_birthday_message( - msg: str, - user_id: int, - name: str, - pronouns: Pronouns = common_pronouns['they'], - age: Optional[int] = None + msg: str, + user_id: int, + name: str, + pronouns: Pronouns = common_pronouns["they"], + age: Optional[int] = None, ): p = pronouns # Generate normal (not explicitly lower), capitalized, and upper versions # of these args generate_cases = { - 'name': name, - 'they': p.subjective, - 'them': p.objective, - 'their': p.determiner, - 'theirs': p.possessive, - 'themself': p.reflexive, - 'are': p.to_be_conjugation, - 'theyre': p.subjective_to_be_contraction, - 'age_suffixed': age_with_suffix(age) if age is not None else '', + "name": name, + "they": p.subjective, + "them": p.objective, + "their": p.determiner, + "theirs": p.possessive, + "themself": p.reflexive, + "are": p.to_be_conjugation, + "theyre": p.subjective_to_be_contraction, + "age_suffixed": age_with_suffix(age) if age is not None else "", } args_generated_cases = {} for k, v in generate_cases.items(): diff --git a/sandpiper/common/IANA/database.py b/sandpiper/common/IANA/database.py index 525925d..6c43d57 100644 --- a/sandpiper/common/IANA/database.py +++ b/sandpiper/common/IANA/database.py @@ -5,39 +5,37 @@ from sandpiper.common.time import TimezoneType __all__ = ( - 'DEFAULT_FLAG', - 'country_code_to_country_name', - 'timezone_to_country_code', - 'to_regional_indicator', - 'get_country_flag_emoji', - 'get_country_flag_emoji_from_timezone' + "DEFAULT_FLAG", + "country_code_to_country_name", + "timezone_to_country_code", + "to_regional_indicator", + "get_country_flag_emoji", + "get_country_flag_emoji_from_timezone", ) -DEFAULT_FLAG = ':flag_white:' +DEFAULT_FLAG = ":flag_white:" -def _parse_db_file( - file_name, per_line: Callable[[dict, list[str]], NoReturn] -) -> dict: +def _parse_db_file(file_name, per_line: Callable[[dict, list[str]], NoReturn]) -> dict: file = Path(__file__).parent / file_name if not file.exists(): raise FileNotFoundError(f"Can't find IANA database file {file}") out = {} - with file.open('rt') as f: + with file.open("rt") as f: for line in f: - if line.startswith('#'): + if line.startswith("#"): continue - per_line(out, line.strip('\n').split('\t')) + per_line(out, line.strip("\n").split("\t")) return out country_code_to_country_name: dict[str, str] = _parse_db_file( - 'iso3166.tab', lambda d, fields: setitem(d, fields[0], fields[1]) + "iso3166.tab", lambda d, fields: setitem(d, fields[0], fields[1]) ) timezone_to_country_code: dict[str, str] = _parse_db_file( - 'zone.tab', lambda d, fields: setitem(d, fields[2], fields[0]) + "zone.tab", lambda d, fields: setitem(d, fields[2], fields[0]) ) @@ -51,7 +49,7 @@ def to_regional_indicator(char: str) -> str: def get_country_flag_emoji(country_id: str) -> str: if len(country_id) != 2: raise ValueError(f"country_id must be a 2-character string") - return ''.join(to_regional_indicator(i) for i in country_id) + return "".join(to_regional_indicator(i) for i in country_id) def get_country_flag_emoji_from_timezone(tz: Union[str, TimezoneType]): diff --git a/sandpiper/common/discord.py b/sandpiper/common/discord.py index 3600697..50d6086 100644 --- a/sandpiper/common/discord.py +++ b/sandpiper/common/discord.py @@ -9,13 +9,16 @@ from sandpiper.user_data.enums import PrivacyType __all__ = [ - 'AutoOrder', 'date_handler', 'privacy_handler', - 'cheap_user_hash', - 'find_user_in_mutual_guilds', 'find_users_by_display_name', - 'find_users_by_username' + "AutoOrder", + "date_handler", + "privacy_handler", + "cheap_user_hash", + "find_user_in_mutual_guilds", + "find_users_by_display_name", + "find_users_by_username", ] -logger = logging.getLogger('sandpiper.common.discord') +logger = logging.getLogger("sandpiper.common.discord") class AutoOrder: @@ -47,7 +50,7 @@ def __call__(self, command: Command): # Order by parent's commands count order = len(command.parent.commands) - 1 - command.__original_kwargs__['order'] = order + command.__original_kwargs__["order"] = order return command @@ -72,12 +75,15 @@ def privacy_handler(privacy_str: str) -> PrivacyType: return PrivacyType[privacy_str] except KeyError: privacy_names = [n.lower() for n in PrivacyType.__members__.keys()] - raise BadArgument(f'Privacy must be one of {privacy_names}') + raise BadArgument(f"Privacy must be one of {privacy_names}") def find_user_in_mutual_guilds( - client: discord.Client, whos_looking: int, for_whom: int, - *, short_circuit: bool = False + client: discord.Client, + whos_looking: int, + for_whom: int, + *, + short_circuit: bool = False, ) -> list[discord.Member]: """ Find a user who shares mutual guild's with someone else. @@ -100,8 +106,9 @@ def find_user_in_mutual_guilds( return found_members -def find_users_by_username(client: discord.Client, - name: str) -> [list[tuple[int, str]]]: +def find_users_by_username( + client: discord.Client, name: str +) -> [list[tuple[int, str]]]: """ Search for users by their Discord username. @@ -113,14 +120,17 @@ def find_users_by_username(client: discord.Client, name = name.casefold() for user in client.users: user: discord.User - if name in f'{user.name.casefold()}#{user.discriminator}': - users.append((user.id, f'{user.name}#{user.discriminator}')) + if name in f"{user.name.casefold()}#{user.discriminator}": + users.append((user.id, f"{user.name}#{user.discriminator}")) return users def find_users_by_display_name( - client: discord.Client, whos_looking: int, name: str, - *, guild: Optional[discord.Guild] = None, + client: discord.Client, + whos_looking: int, + name: str, + *, + guild: Optional[discord.Guild] = None, ) -> [list[tuple[int, str]]]: """ Search for users by their display name (nickname in a guild). You may pass diff --git a/sandpiper/common/embeds.py b/sandpiper/common/embeds.py index 3820e73..ca31aeb 100644 --- a/sandpiper/common/embeds.py +++ b/sandpiper/common/embeds.py @@ -3,9 +3,12 @@ import discord __all__ = [ - 'SimpleEmbed', - 'SuccessEmbed', 'WarningEmbed', 'ErrorEmbed', - 'InfoEmbed', 'SpecialEmbed', + "SimpleEmbed", + "SuccessEmbed", + "WarningEmbed", + "ErrorEmbed", + "InfoEmbed", + "SpecialEmbed", ] T_Field = tuple[str, str, bool] @@ -13,14 +16,17 @@ class SimpleEmbed: - title = '' + title = "" color = 0x000000 def __init__( - self, msg: Union[None, str, list[str]] = None, - fields: list[T_Field] = None, - *, title: Optional[str] = None, color: Optional[int] = None, - join: str = '\n' + self, + msg: Union[None, str, list[str]] = None, + fields: list[T_Field] = None, + *, + title: Optional[str] = None, + color: Optional[int] = None, + join: str = "\n", ): """ :param msg: the message to add to the embed. May be a list of str @@ -36,9 +42,7 @@ def __init__( elif isinstance(msg, str): msg = [msg] elif not isinstance(msg, list): - raise TypeError( - f"message must be None or of type (str, list[str])" - ) + raise TypeError(f"message must be None or of type (str, list[str])") self.message_parts: list[str] = msg if fields is None: @@ -73,9 +77,7 @@ async def send(self, messageable: discord.abc.Messageable): if self.message_parts: desc = self.join_str.join(self.message_parts) - embed = discord.Embed( - title=self.title, description=desc, color=self.color - ) + embed = discord.Embed(title=self.title, description=desc, color=self.color) if self.fields: for name, value, inline in self.fields: @@ -86,24 +88,24 @@ async def send(self, messageable: discord.abc.Messageable): class SuccessEmbed(SimpleEmbed): color = 0x57FCA5 - title = 'Success' + title = "Success" class WarningEmbed(SimpleEmbed): color = 0xE7D900 - title = 'Warning' + title = "Warning" class ErrorEmbed(SimpleEmbed): color = 0xFF0000 - title = 'Error' + title = "Error" class InfoEmbed(SimpleEmbed): color = 0x5E5FFF - title = 'Info' + title = "Info" class SpecialEmbed(SimpleEmbed): color = 0xF656F1 - title = 'Announcement' + title = "Announcement" diff --git a/sandpiper/common/misc.py b/sandpiper/common/misc.py index 38756ee..f33c3ad 100644 --- a/sandpiper/common/misc.py +++ b/sandpiper/common/misc.py @@ -1,10 +1,10 @@ from collections.abc import Iterable, Sequence from typing import Optional, Union -__all__ = ['join', 'prune', 'listify', 'RuntimeMessages'] +__all__ = ["join", "prune", "listify", "RuntimeMessages"] -def join(*fragments, sep=''): +def join(*fragments, sep=""): return sep.join(str(f) for f in fragments if f) @@ -14,7 +14,7 @@ def prune(iterable: Iterable): def listify(it: Sequence, trim_after: Optional[int] = None) -> str: if len(it) == 0: - return '' + return "" if len(it) == 1: return str(it[0]) if len(it) == 2: @@ -26,7 +26,7 @@ def listify(it: Sequence, trim_after: Optional[int] = None) -> str: class RuntimeMessages: - __slots__ = ('info', 'exceptions') + __slots__ = ("info", "exceptions") info: list[str] exceptions: list[Exception] @@ -44,7 +44,7 @@ def __iadd__(self, item: Union[str, Exception]): elif isinstance(item, Exception): self.exceptions.append(item) else: - raise ValueError('item must be of type str or Exception') + raise ValueError("item must be of type str or Exception") return self def add_type_once(self, exc: Exception): diff --git a/sandpiper/common/paths.py b/sandpiper/common/paths.py index 467017b..8634559 100644 --- a/sandpiper/common/paths.py +++ b/sandpiper/common/paths.py @@ -1,4 +1,4 @@ from pathlib import Path import sys -MODULE_PATH = Path(sys.modules['sandpiper'].__file__).parent.absolute() +MODULE_PATH = Path(sys.modules["sandpiper"].__file__).parent.absolute() diff --git a/sandpiper/common/test_misc.py b/sandpiper/common/test_misc.py index 7f61bc0..3fc6c41 100644 --- a/sandpiper/common/test_misc.py +++ b/sandpiper/common/test_misc.py @@ -4,27 +4,26 @@ class TestListify: - def test_zero(self): values = [] - assert listify(values) == '' + assert listify(values) == "" def test_one(self): - values = ['a'] - assert listify(values) == 'a' + values = ["a"] + assert listify(values) == "a" def test_two(self): - values = list('ab') - assert listify(values) == 'a and b' + values = list("ab") + assert listify(values) == "a and b" def test_three(self): - values = list('abc') - assert listify(values) == 'a, b, and c' + values = list("abc") + assert listify(values) == "a, b, and c" def test_five(self): - values = list('abcde') - assert listify(values) == 'a, b, c, d, and e' + values = list("abcde") + assert listify(values) == "a, b, c, d, and e" def test_five_trim_three(self): - values = list('abcde') - assert listify(values, 3) == 'a, b, c, and 2 others' + values = list("abcde") + assert listify(values, 3) == "a, b, c, and 2 others" diff --git a/sandpiper/common/time.py b/sandpiper/common/time.py index 025b5cc..d553a14 100644 --- a/sandpiper/common/time.py +++ b/sandpiper/common/time.py @@ -8,104 +8,117 @@ import tzlocal __all__ = [ - 'TimezoneType', - 'no_zeropad', 'time_format', 'parse_time', - 'parse_date', 'format_date', - 'utc_now', - 'localize_time_to_datetime', - 'day_of_the_year', 'sort_dates_no_year', - 'TimezoneMatches', 'fuzzy_match_timezone' + "TimezoneType", + "no_zeropad", + "time_format", + "parse_time", + "parse_date", + "format_date", + "utc_now", + "localize_time_to_datetime", + "day_of_the_year", + "sort_dates_no_year", + "TimezoneMatches", + "fuzzy_match_timezone", ] TimezoneType = Union[pytz.tzinfo.StaticTzInfo, pytz.tzinfo.DstTzInfo] time_pattern = re.compile( - r'^' - r'(?P[0-2]?\d)' - r'(?::?(?P\d{2}))?' - r'\s*' - r'(?:(?Pa|am)|(?Pp|pm))?' - r'$', - re.I + r"^" + r"(?P[0-2]?\d)" + r"(?::?(?P\d{2}))?" + r"\s*" + r"(?:(?Pa|am)|(?Pp|pm))?" + r"$", + re.I, ) time_pattern_with_timezone = re.compile( - r'^' - r'(?:' - r'(?P[0-2]?\d)' - r'(?:' - r'(?P:)?' - r'(?P\d{2})' - r')?' - r'(?: ?(?P' - r'(?Pa|am)' - r'|(?Pp|pm)' - r'))?' - r'|(?P' - r'(?Pnow)' - r'|(?Pnoon)' - r'|(?Pmidnight)' - r')' - r')' - r'(?(period)' - r' (?P\S.*)' - r'|(?(colon)' - r' (?P\S.*)' - r'|(?(keyword)' - r' (?P\S.*)' - r')' - r')' - r')?' - r'$', - re.I + r"^" + r"(?:" + r"(?P[0-2]?\d)" + r"(?:" + r"(?P:)?" + r"(?P\d{2})" + r")?" + r"(?: ?(?P" + r"(?Pa|am)" + r"|(?Pp|pm)" + r"))?" + r"|(?P" + r"(?Pnow)" + r"|(?Pnoon)" + r"|(?Pmidnight)" + r")" + r")" + r"(?(period)" + r" (?P\S.*)" + r"|(?(colon)" + r" (?P\S.*)" + r"|(?(keyword)" + r" (?P\S.*)" + r")" + r")" + r")?" + r"$", + re.I, ) date_pattern_simple = re.compile( - r'^(?P\d{4})' - r'[/-](?P\d\d)' - r'[/-](?P\d\d)$' + r"^(?P\d{4})" r"[/-](?P\d\d)" r"[/-](?P\d\d)$" ) date_pattern_words = re.compile( - r'^(?:(?P\d{1,2}) )?' - r'(?P' - r'jan(?:uary)?' - r'|feb(?:ruary)?' - r'|mar(?:ch)?' - r'|apr(?:il)?' - r'|may' - r'|june?' - r'|july?' - r'|aug(?:ust)?' - r'|sep(?:t(?:ember)?)?' - r'|oct(?:ober)?' - r'|nov(?:ember)?' - r'|dec(?:ember)?' - r')' - r'(?: (?P\d{1,2}))?' - r'(?: (?P\d{4}))?$', - re.I + r"^(?:(?P\d{1,2}) )?" + r"(?P" + r"jan(?:uary)?" + r"|feb(?:ruary)?" + r"|mar(?:ch)?" + r"|apr(?:il)?" + r"|may" + r"|june?" + r"|july?" + r"|aug(?:ust)?" + r"|sep(?:t(?:ember)?)?" + r"|oct(?:ober)?" + r"|nov(?:ember)?" + r"|dec(?:ember)?" + r")" + r"(?: (?P\d{1,2}))?" + r"(?: (?P\d{4}))?$", + re.I, ) months = { - 'jan': 1, 'feb': 2, 'mar': 3, 'apr': 4, 'may': 5, 'jun': 6, - 'jul': 7, 'aug': 8, 'sep': 9, 'oct': 10, 'nov': 11, 'dec': 12 + "jan": 1, + "feb": 2, + "mar": 3, + "apr": 4, + "may": 5, + "jun": 6, + "jul": 7, + "aug": 8, + "sep": 9, + "oct": 10, + "nov": 11, + "dec": 12, } try: # Unix strip zero-padding - no_zeropad = '-' - dt.datetime.now().strftime(f'%{no_zeropad}d') + no_zeropad = "-" + dt.datetime.now().strftime(f"%{no_zeropad}d") except ValueError: try: # Windows strip zero-padding - no_zeropad = '#' - dt.datetime.now().strftime(f'%{no_zeropad}d') + no_zeropad = "#" + dt.datetime.now().strftime(f"%{no_zeropad}d") except ValueError: # Fallback without stripping zero-padding - no_zeropad = '' + no_zeropad = "" -time_format = f'%{no_zeropad}I:%M %p (%H:%M)' +time_format = f"%{no_zeropad}I:%M %p (%H:%M)" def utc_now() -> dt.datetime: @@ -127,11 +140,11 @@ def parse_time(time_str: str) -> tuple[dt.time, Optional[str], bool]: match = time_pattern_with_timezone.match(time_str) if not match: - raise ValueError('No match') + raise ValueError("No match") # Handle keyword times - if match['keyword']: - if match['now']: + if match["keyword"]: + if match["now"]: now = utc_now() # This is a little heavy-handed because instead of just passing # back the localized datetime, we're passing the time and the @@ -140,21 +153,21 @@ def parse_time(time_str: str) -> tuple[dt.time, Optional[str], bool]: # handle the timezone parsing. Maybe it could change in the future. return now.time(), cast(TimezoneType, now.tzinfo).zone, True - if match['midnight']: + if match["midnight"]: time = dt.time(0, 0) - elif match['noon']: + elif match["noon"]: time = dt.time(12, 0) else: raise ValueError("This should be impossible but let's be safe") - return time, match['timezone_keyword'] or None, True + return time, match["timezone_keyword"] or None, True - hour = int(match['hour']) - minute = int(match['minute'] or 0) + hour = int(match["hour"]) + minute = int(match["minute"] or 0) if (0 > hour > 23) or (0 > minute > 59): - raise ValueError('Hour or minute is out of range') + raise ValueError("Hour or minute is out of range") - if match['period_pm']: + if match["period_pm"]: if hour < 12: # This is PM and we use 24 hour times in datetime, so add 12 hours hour += 12 @@ -162,8 +175,8 @@ def parse_time(time_str: str) -> tuple[dt.time, Optional[str], bool]: # 12 PM is 12:00 pass else: - raise ValueError('24 hour times do not use AM or PM') - elif match['period_am']: + raise ValueError("24 hour times do not use AM or PM") + elif match["period_am"]: if hour < 12: # AM, so no change pass @@ -171,32 +184,32 @@ def parse_time(time_str: str) -> tuple[dt.time, Optional[str], bool]: # 12 AM is 00:00 hour = 0 else: - raise ValueError('24 hour times do not use AM or PM') + raise ValueError("24 hour times do not use AM or PM") return ( dt.time(hour, minute), - match['timezone1'] or match['timezone2'] or None, - bool(match['colon'] or match['period']) + match["timezone1"] or match["timezone2"] or None, + bool(match["colon"] or match["period"]), ) def parse_date(date_str: str) -> dt.date: if match := date_pattern_simple.match(date_str): - year = int(match['year']) - month = int(match['month']) - day = int(match['day']) + year = int(match["year"]) + month = int(match["month"]) + day = int(match["day"]) elif match := date_pattern_words.match(date_str): - year = int(match['year'] or 1) - month = months[match['month'][:3].lower()] - day1 = match['day1'] - day2 = match['day2'] + year = int(match["year"] or 1) + month = months[match["month"][:3].lower()] + day1 = match["day1"] + day2 = match["day2"] if (day1 is None) == (day2 is None): raise ValueError("You must specify the day") day = int(day1 if day1 is not None else day2) else: - raise ValueError('No match') + raise ValueError("No match") return dt.date(year, month, day) @@ -205,13 +218,11 @@ def format_date(date: Optional[dt.date]): if date is None: return None if date.year == 1: - return date.strftime(f'%B %{no_zeropad}d') - return date.strftime('%Y-%m-%d') + return date.strftime(f"%B %{no_zeropad}d") + return date.strftime("%Y-%m-%d") -def localize_time_to_datetime( - time: dt.time, basis_tz: TimezoneType -) -> dt.datetime: +def localize_time_to_datetime(time: dt.time, basis_tz: TimezoneType) -> dt.datetime: """ Turn a time into a datetime, localized in the given timezone based on the current day in that timezone. @@ -236,7 +247,7 @@ def localize_time_to_datetime( def day_of_the_year(datetime: Union[dt.date, dt.datetime]): - return int(datetime.strftime('%j')) + return int(datetime.strftime("%j")) def _sort_dates_no_year_func(d: dt.date, now: dt.datetime): @@ -247,9 +258,7 @@ def _sort_dates_no_year_func(d: dt.date, now: dt.datetime): return d_int -def sort_dates_no_year( - dates: list, key=lambda x: x, now: Optional[dt.datetime] = None -): +def sort_dates_no_year(dates: list, key=lambda x: x, now: Optional[dt.datetime] = None): if now is None: now = utc_now() return sorted(dates, key=lambda x: _sort_dates_no_year_func(key(x), now)) @@ -263,7 +272,7 @@ class TimezoneMatches: def fuzzy_match_timezone( - tz_str: str, best_match_threshold=75, lower_score_cutoff=50, limit=5 + tz_str: str, best_match_threshold=75, lower_score_cutoff=50, limit=5 ) -> TimezoneMatches: """ Fuzzily match a timezone based on given timezone name. @@ -282,8 +291,11 @@ def fuzzy_match_timezone( # substrings. Searching "Amst" would pick "GMT" rather than "Amsterdam". # The _set_ratio methods are totally unusable. matches: list[tuple[str, int]] = fuzzy_process.extractBests( - tz_str, pytz.common_timezones, scorer=fuzz.partial_token_sort_ratio, - score_cutoff=lower_score_cutoff, limit=limit + tz_str, + pytz.common_timezones, + scorer=fuzz.partial_token_sort_ratio, + score_cutoff=lower_score_cutoff, + limit=limit, ) tz_matches = TimezoneMatches(matches) diff --git a/sandpiper/config.py b/sandpiper/config.py index e92c8df..38c4fdf 100644 --- a/sandpiper/config.py +++ b/sandpiper/config.py @@ -8,7 +8,7 @@ from sandpiper.common.paths import MODULE_PATH from sandpiper.piperfig import * -__all__ = ('SandpiperConfig',) +__all__ = ("SandpiperConfig",) class SandpiperConfig(ConfigSchema): @@ -43,43 +43,34 @@ class _Birthdays(ConfigSchema): upcoming_birthdays_day_range: Annotated[int, Bounded(0, 365)] = 14 message_templates_no_age: list[str] = [ "Hey!! It's {name}'s birthday! Happy birthday {ping}!", - - "{name}! It's your birthday!! Hope it's a great one " - "{ping}!", - + "{name}! It's your birthday!! Hope it's a great one " "{ping}!", "omg! did yall know it's {name}'s birthday?? happy " "birthday {ping}! :D", - "I am pleased to announce... IT'S {NAME}'s BIRTHDAY!! " - "Happy birthday {ping}!!" + "Happy birthday {ping}!!", ] message_templates_with_age: list[str] = [ "Hey!! It's {name}'s birthday! {They} turned {age} today. " "Happy birthday {ping}!", - "{name}! It's your birthday!! I can't believe you're " "already {age} ;u; Hope it's a great one " "{ping}!", - "omg! did yall know it's {name}'s birthday?? {Theyre} " "{age} now! happy birthday {ping}! :D", - "I am pleased to announce... IT'S {NAME}'S BIRTHDAY!! " - "{They} just turned {age}! Happy birthday {ping}!!" + "{They} just turned {age}! Happy birthday {ping}!!", ] class _Logging(ConfigSchema): - _logging_levels = Literal[ - 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL' - ] + _logging_levels = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] - sandpiper_logging_level: _logging_levels = 'INFO' - discord_logging_level: _logging_levels = 'WARNING' - output_file: Annotated[Path, MaybeRelativePath(MODULE_PATH)] = ( - './logs/sandpiper.log' - ) - when: Literal['S', 'M', 'H', 'D', 'midnight'] = 'midnight' + sandpiper_logging_level: _logging_levels = "INFO" + discord_logging_level: _logging_levels = "WARNING" + output_file: Annotated[ + Path, MaybeRelativePath(MODULE_PATH) + ] = "./logs/sandpiper.log" + when: Literal["S", "M", "H", "D", "midnight"] = "midnight" interval: Annotated[int, Bounded(1, None)] = 1 backup_count: Annotated[int, Bounded(0, None)] = 7 format = "%(asctime)s %(levelname)s %(name)s | %(message)s" @@ -100,6 +91,6 @@ def handler(self): return handler -if __name__ == '__main__': - config = SandpiperConfig({'bot_token': ''}) +if __name__ == "__main__": + config = SandpiperConfig({"bot_token": ""}) print(config.serialize()) diff --git a/sandpiper/conversion/cog.py b/sandpiper/conversion/cog.py index 6f41b43..f363620 100644 --- a/sandpiper/conversion/cog.py +++ b/sandpiper/conversion/cog.py @@ -14,25 +14,24 @@ import sandpiper.conversion.unit_conversion as unit_conversion from sandpiper.user_data import DatabaseUnavailable, UserData -logger = logging.getLogger('sandpiper.unit_conversion') +logger = logging.getLogger("sandpiper.unit_conversion") conversion_pattern = regex.compile( # Skip anything inside a code block - r'(?[^>]+?) *' - r'(?:> *(?P\S.*?) *)?' - r'}' + r"{ *" + r"(?P[^>]+?) *" + r"(?:> *(?P\S.*?) *)?" + r"}" ) class Conversion(commands.Cog): - def __init__(self, bot: commands.Bot): self.bot = bot - @commands.Cog.listener(name='on_message') + @commands.Cog.listener(name="on_message") async def conversions(self, msg: discord.Message): """ Scan a message for conversion strings. @@ -50,7 +49,7 @@ async def conversions(self, msg: discord.Message): await self.convert_measurements(msg.channel, conversion_strs) async def convert_time( - self, msg: discord.Message, time_strs: list[tuple[str, str]] + self, msg: discord.Message, time_strs: list[tuple[str, str]] ) -> list[tuple[str, str]]: """ Convert a list of time strings (like "5:45 PM") to different users' @@ -61,7 +60,7 @@ async def convert_time( :returns: a list of strings that could not be converted """ - user_data: UserData = self.bot.get_cog('UserData') + user_data: UserData = self.bot.get_cog("UserData") if user_data is None: # User data cog couldn't be retrieved, so consider all conversions # failed @@ -79,9 +78,9 @@ async def convert_time( if runtime_msgs.exceptions: # Send embed with any errors that happened during conversion - await ErrorEmbed( - [str(e) for e in runtime_msgs.exceptions], join='\n' - ).send(msg.channel) + await ErrorEmbed([str(e) for e in runtime_msgs.exceptions], join="\n").send( + msg.channel + ) return failed if converted_times: @@ -97,21 +96,20 @@ async def convert_time( for timezone_out, times in conversions: # Print the converted times for each timezone on a new line - times = ' | '.join( - f'`{time.strftime(time_format)}`' for time in times + times = " | ".join( + f"`{time.strftime(time_format)}`" for time in times ) flag = get_country_flag_emoji_from_timezone(timezone_out) - output.append(f'{flag} **{timezone_out}** - {times}') + output.append(f"{flag} **{timezone_out}** - {times}") - output.append('') + output.append("") - await msg.channel.send('\n'.join(output[:-1])) + await msg.channel.send("\n".join(output[:-1])) return failed async def convert_measurements( - self, channel: discord.TextChannel, - quantity_strs: list[tuple[str, str]] + self, channel: discord.TextChannel, quantity_strs: list[tuple[str, str]] ) -> NoReturn: """ Convert a list of quantity strings (like "5 km") between imperial and @@ -131,18 +129,18 @@ async def convert_measurements( ) if isinstance(q, tuple): # We parsed as a quantity and got a conversion - conversions.append(f'`{q[0]:.2f~P}` = `{q[1]:.2f~P}`') + conversions.append(f"`{q[0]:.2f~P}` = `{q[1]:.2f~P}`") elif isinstance(q, Decimal): # We parsed as dimensionless and got a numeric type back - conversions.append(f'`{qstr}` = `{q}`') + conversions.append(f"`{qstr}` = `{q}`") else: failed.append((qstr, unit)) if runtime_msgs.exceptions: # Send embed with any errors that happened during conversion - await ErrorEmbed( - [str(e) for e in runtime_msgs.exceptions], join='\n' - ).send(channel) + await ErrorEmbed([str(e) for e in runtime_msgs.exceptions], join="\n").send( + channel + ) if conversions: - await channel.send('\n'.join(conversions)) + await channel.send("\n".join(conversions)) diff --git a/sandpiper/conversion/time_conversion.py b/sandpiper/conversion/time_conversion.py index d79faae..79b8937 100644 --- a/sandpiper/conversion/time_conversion.py +++ b/sandpiper/conversion/time_conversion.py @@ -10,20 +10,17 @@ from sandpiper.common.misc import RuntimeMessages from sandpiper.user_data import Database -__all__ = ( - 'UserTimezoneUnset', - 'TimezoneNotFound', - 'convert_time_to_user_timezones' -) +__all__ = ("UserTimezoneUnset", "TimezoneNotFound", "convert_time_to_user_timezones") -logger = logging.getLogger('sandpiper.conversion.time_conversion') +logger = logging.getLogger("sandpiper.conversion.time_conversion") T_ConvertedTimes = list[tuple[str, list[dt.datetime]]] -T_ConvertedTimesGroupedUnderInputTimezones = list[tuple[Optional[str], T_ConvertedTimes]] +T_ConvertedTimesGroupedUnderInputTimezones = list[ + tuple[Optional[str], T_ConvertedTimes] +] class UserTimezoneUnset(Exception): - def __str__(self): return ( "Your timezone is not set. Use the `help timezone set` command " @@ -32,12 +29,11 @@ def __str__(self): class TimezoneNotFound(Exception): - def __init__(self, timezone: str): self.timezone = timezone def __str__(self): - return f"Timezone \"{self.timezone}\" not found" + return f'Timezone "{self.timezone}" not found' def _get_timezone(name: str) -> Optional[TimezoneType]: @@ -60,13 +56,14 @@ async def _get_guild_timezones(db: Database, guild: discord.Guild) -> set[Timezo all_timezones = await db.get_all_timezones() return { # Filter out timezones of users outside this guild - tz for user_id, tz in all_timezones if guild.get_member(user_id) + tz + for user_id, tz in all_timezones + if guild.get_member(user_id) } async def _convert_times( - times: list[dt.datetime], - out_timezones: Union[TimezoneType, Iterable[TimezoneType]] + times: list[dt.datetime], out_timezones: Union[TimezoneType, Iterable[TimezoneType]] ) -> T_ConvertedTimes: """ Convert a list of datetimes to the given timezones. @@ -81,22 +78,20 @@ async def _convert_times( conversions: T_ConvertedTimes = [] for tz in out_timezones: - times = [ - time.astimezone(tz) for time in times - ] + times = [time.astimezone(tz) for time in times] conversions.append((tz.zone, times)) conversions.sort(key=lambda conv: conv[1][0].utcoffset()) return conversions async def convert_time_to_user_timezones( - db: Database, user_id: int, guild: discord.Guild, - time_strs: list[tuple[str, str]], - *, runtime_msgs: RuntimeMessages -) -> tuple[ - T_ConvertedTimesGroupedUnderInputTimezones, - list[tuple[str, str]] -]: + db: Database, + user_id: int, + guild: discord.Guild, + time_strs: list[tuple[str, str]], + *, + runtime_msgs: RuntimeMessages, +) -> tuple[T_ConvertedTimesGroupedUnderInputTimezones, list[tuple[str, str]]]: """ Convert times. @@ -125,8 +120,8 @@ async def convert_time_to_user_timezones( # Each datetime under a given timezone will be converted to that timezone # only. The None key is a special case, where each datetime mapped to it # will be converted to all user timezones in the database - out_timezone_map: dict[Optional[TimezoneType], list[dt.datetime]] = ( - defaultdict(list) + out_timezone_map: dict[Optional[TimezoneType], list[dt.datetime]] = defaultdict( + list ) failed: list[tuple[str, str]] = [] # Strings that should pass on to unit conversion user_tz = None @@ -134,16 +129,14 @@ async def convert_time_to_user_timezones( try: parsed_time, timezone_in_str, definitely_time = parse_time(tstr) except ValueError as e: - logger.info( - f"Failed to parse time string (string={tstr!r}, reason={e})" - ) + logger.info(f"Failed to parse time string (string={tstr!r}, reason={e})") # Failed to parse as a time, so pass it on to unit conversion failed.append((tstr, timezone_out_str)) continue except: logger.warning( - f"Unhandled exception while parsing time string " - f"(string={tstr!r})", exc_info=True + f"Unhandled exception while parsing time string " f"(string={tstr!r})", + exc_info=True, ) continue @@ -200,7 +193,7 @@ async def convert_time_to_user_timezones( conversions.append((None, converted)) else: # Otherwise, print this header to distinguish it from the others - conversions.append(('All timezones', converted)) + conversions.append(("All timezones", converted)) # Handle any other output timezones for timezone_out, times in out_timezone_map.items(): @@ -210,10 +203,7 @@ async def convert_time_to_user_timezones( if not times: continue converted = await _convert_times(times, timezone_out) - conversions.append(( - cast(TimezoneType, times[0].tzinfo).zone, - converted - )) + conversions.append((cast(TimezoneType, times[0].tzinfo).zone, converted)) return conversions, failed diff --git a/sandpiper/conversion/unit_conversion.py b/sandpiper/conversion/unit_conversion.py index 1bfad8b..a4d10df 100644 --- a/sandpiper/conversion/unit_conversion.py +++ b/sandpiper/conversion/unit_conversion.py @@ -10,23 +10,21 @@ from sandpiper.common.misc import RuntimeMessages from sandpiper.conversion.unit_map import UnitMap -__all__ = ['convert_measurement'] +__all__ = ["convert_measurement"] -logger = logging.getLogger('sandpiper.conversion.unit_conversion') +logger = logging.getLogger("sandpiper.conversion.unit_conversion") ureg = UnitRegistry( - autoconvert_offset_to_baseunit=True, # For temperatures - non_int_type=Decimal + autoconvert_offset_to_baseunit=True, non_int_type=Decimal # For temperatures ) ureg.define( - '@alias degreeC = c = C = degreec = degc = degC = °C = °c ' - '= Celsius = celsius' + "@alias degreeC = c = C = degreec = degc = degC = °C = °c " "= Celsius = celsius" ) ureg.define( - '@alias degreeF = f = F = degreef = degf = degF = °F = °f ' - '= Fahrenheit = fahrenheit' + "@alias degreeF = f = F = degreef = degf = degF = °F = °f " + "= Fahrenheit = fahrenheit" ) -ureg.define('@alias hour = h') +ureg.define("@alias hour = h") Q_ = ureg.Quantity unit_map: UnitMap[Unit] = UnitMap( @@ -77,59 +75,55 @@ ureg.minute: ureg.hour, ureg.hour: ureg.day, ureg.day: ureg.week, - } + }, ) imperial_shorthand_pattern = re.compile( # Either feet or inches may be excluded, but not both, so make sure # at least one of them matches with this lookahead - r'^(?=.)' + r"^(?=.)" # Only allow integer foot values - r'(?:(?P[\d]+)\')?' - r'(?:' - # Allow a space if foot is matched - r'(?(foot) ?|)' - # Allow integer or decimal inch values - r'(?P\d+|\d*\.\d+)\"' - r')?' - r'$' + r"(?:(?P[\d]+)\')?" + r"(?:" + # Allow a space if foot is matched + r"(?(foot) ?|)" + # Allow integer or decimal inch values + r"(?P\d+|\d*\.\d+)\"" + r")?" + r"$" ) class UndefinedUnitError(Exception): - def __init__(self, unit: str): self.unit = unit def __str__(self): - return f"Unknown unit \"{self.unit}\"" + return f'Unknown unit "{self.unit}"' class NotAMeasurementError(Exception): - def __init__(self, value: str): self.value = value def __str__(self): - return f"\"{self.value}\" is not a measurement" + return f'"{self.value}" is not a measurement' class UnmappedUnitError(Exception): - def __init__(self, quantity: Quantity): self.quantity = quantity def __str__(self): return ( - f"I don't know what unit to convert \"{self.quantity.u}\" to. You " + f'I don\'t know what unit to convert "{self.quantity.u}" to. You ' f"can specify an output unit like this: " f"{{{self.quantity} > otherunit}}" ) def convert_measurement( - quantity_str: str, unit: str = None, - *, runtime_msgs: RuntimeMessages = None + quantity_str: str, unit: str = None, *, runtime_msgs: RuntimeMessages = None ) -> Union[tuple[Quantity, Quantity], Decimal, None]: """ Parse and convert a quantity string between imperial and metric @@ -148,9 +142,9 @@ def convert_measurement( if height := imperial_shorthand_pattern.match(quantity_str): # User used imperial length shorthand # e.g. 5' 8" == 5 feet + 8 inches - logger.info('Imperial length shorthand detected') - foot = Q_(Decimal(foot), 'foot') if (foot := height['foot']) else 0 - inch = Q_(Decimal(inch), 'inch') if (inch := height['inch']) else 0 + logger.info("Imperial length shorthand detected") + foot = Q_(Decimal(foot), "foot") if (foot := height["foot"]) else 0 + inch = Q_(Decimal(inch), "inch") if (inch := height["inch"]) else 0 quantity: Quantity = foot + inch else: # Regular parsing @@ -174,8 +168,7 @@ def convert_measurement( if not isinstance(quantity, Quantity): logger.warning( - f"Unexpected type {type(quantity)} encountered after parsing " - f"expression" + f"Unexpected type {type(quantity)} encountered after parsing " f"expression" ) return None @@ -202,8 +195,5 @@ def convert_measurement( runtime_msgs += UndefinedUnitError(unit) return None - logger.info( - f"Conversion successful: " - f"{quantity:.2f~P} -> {quantity_out:.2f~P}" - ) + logger.info(f"Conversion successful: " f"{quantity:.2f~P} -> {quantity_out:.2f~P}") return quantity, quantity_out diff --git a/sandpiper/conversion/unit_map.py b/sandpiper/conversion/unit_map.py index d383338..8865d25 100644 --- a/sandpiper/conversion/unit_map.py +++ b/sandpiper/conversion/unit_map.py @@ -2,7 +2,7 @@ from typing import Generic, TypeVar -T = TypeVar('T') +T = TypeVar("T") class UnitMap(Generic[T]): @@ -10,9 +10,7 @@ class UnitMap(Generic[T]): _two_way: dict[T, T] _one_way: dict[T, T] - def __init__( - self, *, two_way: dict[T, T], one_way: dict[T, T] = None - ): + def __init__(self, *, two_way: dict[T, T], one_way: dict[T, T] = None): if not isinstance(two_way, dict): raise ValueError(f"two_way must be a dict") self._two_way = self._create_bidict(two_way) diff --git a/sandpiper/help.py b/sandpiper/help.py index 984f696..e9963a9 100644 --- a/sandpiper/help.py +++ b/sandpiper/help.py @@ -3,12 +3,12 @@ from discord.ext.commands import DefaultHelpCommand, Group, Command, Cog -__all__ = ['HelpCommand'] +__all__ = ["HelpCommand"] def sort_commands_key(c: Command): try: - return c.__original_kwargs__['order'] + return c.__original_kwargs__["order"] except KeyError: return c.name @@ -18,29 +18,27 @@ def sort_commands(commands: Iterable[Command]): def boxify(inside: str): - line = '─' * (len(inside) + 2) - top = f'╭{line}╮' - bot = f'╰{line}╯' + line = "─" * (len(inside) + 2) + top = f"╭{line}╮" + bot = f"╰{line}╯" return f"{top}\n│ {inside} │\n{bot}" def is_dm_only(command: Command): for check in command.checks: - if check.__qualname__.startswith('dm_only'): + if check.__qualname__.startswith("dm_only"): return True return False class HelpCommand(DefaultHelpCommand): - def __init__(self, **options): - options.pop('verify_checks', None) + options.pop("verify_checks", None) super().__init__(verify_checks=False, **options) - def shorten_text(self, text: str, suffix: str = ''): + def shorten_text(self, text: str, suffix: str = ""): if len(text) + len(suffix) > self.width: - return (text[:self.width - len(suffix) - 3] - + '...' + suffix) + return text[: self.width - len(suffix) - 3] + "..." + suffix return text + suffix def get_ending_note(self): @@ -51,48 +49,53 @@ def get_ending_note(self): ) async def add_commands_recursive( - self, commands: Iterable[Command], depth=0, vertical_connectors='', - *, parent_path: str = None + self, + commands: Iterable[Command], + depth=0, + vertical_connectors="", + *, + parent_path: str = None, ): if parent_path: # Print the nodes of this branch leading from the root to the # current command - parent_path = parent_path.split(' ') + parent_path = parent_path.split(" ") depth = len(parent_path) for i, parent in enumerate(parent_path): if i == 0: self.paginator.add_line(parent) else: self.paginator.add_line(f"{vertical_connectors}└─ {parent}") - vertical_connectors += ' ' + vertical_connectors += " " commands = list(commands) for i, c in enumerate(sort_commands(commands)): if depth != 0: if i == len(commands) - 1: # Last item; terminate this level of the tree - horizontal_connector = '└─ ' - next_vertical = vertical_connectors + ' ' + horizontal_connector = "└─ " + next_vertical = vertical_connectors + " " else: # Continue this level of the tree - horizontal_connector = '├─ ' - next_vertical = vertical_connectors + '│ ' + horizontal_connector = "├─ " + next_vertical = vertical_connectors + "│ " else: # Don't print connectors for the top level of the tree - horizontal_connector = next_vertical = '' + horizontal_connector = next_vertical = "" - line = (f"{vertical_connectors}{horizontal_connector}" - f"{c.name} \N{EN DASH} {c.short_doc}") + line = ( + f"{vertical_connectors}{horizontal_connector}" + f"{c.name} \N{EN DASH} {c.short_doc}" + ) if is_dm_only(c): - line = self.shorten_text(line, ' (DM only)') + line = self.shorten_text(line, " (DM only)") else: line = self.shorten_text(line) self.paginator.add_line(line) if isinstance(c, Group): await self.add_commands_recursive( - await self.filter_commands(c.commands), - depth+1, next_vertical + await self.filter_commands(c.commands), depth + 1, next_vertical ) if depth == 0: # Add line breaks after first-level groups @@ -110,8 +113,7 @@ def get_category(command) -> str: cog = command.cog return cog.qualified_name if cog is not None else self.no_category - filtered = await self.filter_commands( - bot.commands, sort=True, key=get_category) + filtered = await self.filter_commands(bot.commands, sort=True, key=get_category) to_iterate = itertools.groupby(filtered, key=get_category) # Now we can add the commands to the page. @@ -145,8 +147,7 @@ async def send_group_help(self, group: Group): self.add_command_formatting(group) filtered = await self.filter_commands(group.commands) - await self.add_commands_recursive( - filtered, parent_path=group.qualified_name) + await self.add_commands_recursive(filtered, parent_path=group.qualified_name) if filtered: note = self.get_ending_note() @@ -159,18 +160,19 @@ async def send_group_help(self, group: Group): def add_command_formatting(self, command: Command): if is_dm_only(command): self.paginator.add_line( - '** You must DM Sandpiper to use this command **', empty=True) + "** You must DM Sandpiper to use this command **", empty=True + ) super().add_command_formatting(command) try: - example = command.__original_kwargs__['example'] + example = command.__original_kwargs__["example"] except KeyError: pass else: if isinstance(example, str): self.paginator.add_line(f"Example: {example}") elif isinstance(example, (list, tuple)): - self.paginator.add_line('Examples:') + self.paginator.add_line("Examples:") for ex in example: self.paginator.add_line(f" {ex}") diff --git a/sandpiper/piperfig/exceptions.py b/sandpiper/piperfig/exceptions.py index aded8f9..ff71d2b 100644 --- a/sandpiper/piperfig/exceptions.py +++ b/sandpiper/piperfig/exceptions.py @@ -1,13 +1,14 @@ from typing import Any __all__ = ( - 'ConfigSchemaError', 'ConfigParsingError', 'MissingFieldError', - 'ParsingError' + "ConfigSchemaError", + "ConfigParsingError", + "MissingFieldError", + "ParsingError", ) class ConfigSchemaError(Exception): - def __init__(self, cls: type, field_name: str, msg: str): self.cls = cls self.field_name = field_name @@ -25,7 +26,6 @@ class ConfigParsingError(Exception): class MissingFieldError(ConfigParsingError): - def __init__(self, qualified_name: str): self.qualified_name = qualified_name @@ -34,10 +34,12 @@ def __str__(self): class ParsingError(ConfigParsingError): - def __init__( - self, value: Any, target_type: type, base_exc: Exception, - qualified_name: str = '' + self, + value: Any, + target_type: type, + base_exc: Exception, + qualified_name: str = "", ): self.value = value self.target_type = target_type @@ -48,6 +50,6 @@ def __str__(self): return ( f"Failed to parse config value (" f"qualified_name={self.qualified_name} value={self.value!r} " - f"target_type={self.target_type} exc=\"{self.base_exc}\"" + f'target_type={self.target_type} exc="{self.base_exc}"' f")" ) diff --git a/sandpiper/piperfig/misc.py b/sandpiper/piperfig/misc.py index 4f0f112..0bf116a 100644 --- a/sandpiper/piperfig/misc.py +++ b/sandpiper/piperfig/misc.py @@ -1,15 +1,14 @@ from typing import NoReturn, Union -__all__ = ('qualified', 'typecheck') +__all__ = ("qualified", "typecheck") def qualified(parent: str, name: str) -> str: - return (parent + '.' if parent else '') + name + return (parent + "." if parent else "") + name def typecheck( - type_: Union[type, tuple[type, ...]], value, name: str, - use_isinstance=False + type_: Union[type, tuple[type, ...]], value, name: str, use_isinstance=False ) -> NoReturn: if use_isinstance: is_type = isinstance(value, type_) diff --git a/sandpiper/piperfig/parser.py b/sandpiper/piperfig/parser.py index 26746b9..374f649 100644 --- a/sandpiper/piperfig/parser.py +++ b/sandpiper/piperfig/parser.py @@ -3,16 +3,24 @@ import json import sys from types import MethodType + # noinspection PyPep8Naming from typing import ( - Any, Annotated, Literal, NoReturn, TextIO, Union, get_type_hints, overload + Any, + Annotated, + Literal, + NoReturn, + TextIO, + Union, + get_type_hints, + overload, ) from .exceptions import * from .misc import * from .transformers import * -__all__ = ('ConfigSchema',) +__all__ = ("ConfigSchema",) NoDefault = object() @@ -22,27 +30,21 @@ def is_json_type(type_: type) -> bool: def is_annotated(type_): - return hasattr(type_, '__metadata__') and hasattr(type_, '__origin__') + return hasattr(type_, "__metadata__") and hasattr(type_, "__origin__") def should_skip(name: str, value: Any = None) -> bool: - return ( - name.startswith('_') - or ( - value is not None - and isinstance(value, (MethodType, cached_property)) - ) + return name.startswith("_") or ( + value is not None and isinstance(value, (MethodType, cached_property)) ) class ConfigSchema: __path: str - __fields: dict[ - str, tuple[Annotated[Any, 'Type'], Annotated[Any, 'Default']] - ] + __fields: dict[str, tuple[Annotated[Any, "Type"], Annotated[Any, "Default"]]] - def __init__(self, config: Union[dict, str, TextIO], *, _schema_path=''): + def __init__(self, config: Union[dict, str, TextIO], *, _schema_path=""): self.__path = _schema_path self.deserialize(config) @@ -59,7 +61,7 @@ def __init_subclass__(cls, /, **kwargs): cls, globalns=vars(sys.modules[cls.__module__]), localns=vars(cls), - include_extras=True + include_extras=True, ) cls_dict = cls.__dict__ @@ -79,9 +81,10 @@ def __init_subclass__(cls, /, **kwargs): _convert(default, field_type, field_name) except Exception: raise ConfigSchemaError( - cls, field_name, + cls, + field_name, f"Default value {default} does not match type " - f"annotation {field_type}" + f"annotation {field_type}", ) cls.__fields[field_name] = field_type, default @@ -94,9 +97,10 @@ def __init_subclass__(cls, /, **kwargs): field_type = _infer_type(default) except TypeError: raise ConfigSchemaError( - cls, field_name, + cls, + field_name, f"Could not infer type of default value {default}. " - f"It's probably an invalid type." + f"It's probably an invalid type.", ) cls.__fields[field_name] = (field_type, default) @@ -124,12 +128,14 @@ def deserialize(self, config: Union[dict, str, TextIO]): self.__read_field(config, field_name, field_type, default) def __read_field( - self, json_parsed: dict[str, Any], field_name: str, - field_type: Any, default: Any = NoDefault + self, + json_parsed: dict[str, Any], + field_name: str, + field_type: Any, + default: Any = NoDefault, ): qualified_name = qualified(self.__path, field_name) - if (isinstance(field_type, type) - and issubclass(field_type, ConfigSchema)): + if isinstance(field_type, type) and issubclass(field_type, ConfigSchema): assert default is NoDefault, ( f"Config field {qualified_name} is annotated as a schema " f"and should not have a default value" @@ -137,8 +143,7 @@ def __read_field( # The type is a schema, so pass the json-parsed dict into the # schema type for further parsing final_value = field_type( - json_parsed.get(field_name, {}), - _schema_path=qualified_name + json_parsed.get(field_name, {}), _schema_path=qualified_name ) setattr(self, field_name, final_value) return @@ -179,8 +184,7 @@ def serialize(self, json_=True) -> Union[dict, str]: @staticmethod def __serialize_field(field_type, value) -> Any: - if (isinstance(field_type, type) - and issubclass(field_type, ConfigSchema)): + if isinstance(field_type, type) and issubclass(field_type, ConfigSchema): return value.serialize(json_=False) if is_annotated(field_type): @@ -198,7 +202,7 @@ def _infer_type(value): if isinstance(value, dict): if any(not isinstance(i, str) for i in value.keys()): - raise TypeError('Dict keys must be strings to conform with JSON') + raise TypeError("Dict keys must be strings to conform with JSON") return dict[str, Union[tuple(_infer_type(i) for i in value.values())]] if is_json_type(type(value)): @@ -214,16 +218,17 @@ def _validate_annotation(cls: type, field_name: str, type_) -> NoReturn: if isinstance(type_, ConfigTransformer): raise ConfigSchemaError( - cls, field_name, + cls, + field_name, f"You may only use ConfigTransformers as metadata in " f"typing.Annotated. Try something like " - f"Annotated[out_type, {type_!r}]" + f"Annotated[out_type, {type_!r}]", ) if isinstance(type_, type) and issubclass(type_, ConfigSchema): return - if hasattr(type_, '__origin__') and hasattr(type_, '__metadata__'): + if hasattr(type_, "__origin__") and hasattr(type_, "__metadata__"): # This annotation is Annotated with metadata # noinspection PyTypeChecker needs_origin_check = _validate_transformers(cls, field_name, type_) @@ -231,7 +236,7 @@ def _validate_annotation(cls: type, field_name: str, type_) -> NoReturn: _validate_annotation(cls, field_name, type_.__origin__) return - if hasattr(type_, '__origin__') and hasattr(type_, '__args__'): + if hasattr(type_, "__origin__") and hasattr(type_, "__args__"): # Use special rules for typing module types type_origin = type_.__origin__ type_args = type_.__args__ @@ -266,15 +271,15 @@ def _validate_annotation(cls: type, field_name: str, type_) -> NoReturn: for literal in type_args: if isinstance(literal, list) or not is_json_type(type(literal)): raise ConfigSchemaError( - cls, field_name, + cls, + field_name, f"Literal values may only be instances of NoneType, " - f"bool, int, float, or str" + f"bool, int, float, or str", ) return raise ConfigSchemaError( - cls, field_name, - f"Special type annotation {type_origin} is not accepted." + cls, field_name, f"Special type annotation {type_origin} is not accepted." ) if type_ is tuple: @@ -287,9 +292,10 @@ def _validate_annotation(cls: type, field_name: str, type_) -> NoReturn: # Some other annotation we can't handle raise ConfigSchemaError( - cls, field_name, + cls, + field_name, f"Type annotation {type_} is not accepted. Maybe you want to use the " - f"FromType transformer?" + f"FromType transformer?", ) @@ -311,18 +317,20 @@ def _validate_transformers(cls: type, field_name: str, type_) -> bool: # Implicit to_type for FromType is only allowed as the last # transformer raise ConfigSchemaError( - cls, field_name, + cls, + field_name, "A FromType transformer with an implicit to_type may only be " - "the last transformer in the sequence." + "the last transformer in the sequence.", ) if prev_type is not None and trans.in_type != prev_type: # The input type of this transformer doesn't match the output type # of the previous transformer raise ConfigSchemaError( - cls, field_name, + cls, + field_name, f"Input type {trans.in_type} of Transformer {trans} does not " - f"match the output type {prev_type} of the previous transformer" + f"match the output type {prev_type} of the previous transformer", ) prev_type = trans.out_type @@ -333,9 +341,10 @@ def _validate_transformers(cls: type, field_name: str, type_) -> bool: # JSON type if not is_json_type(trans.from_type): raise ConfigSchemaError( - cls, field_name, + cls, + field_name, f"The input type of the first FromType transformer " - f"must be a valid JSON type, got {trans.from_type}" + f"must be a valid JSON type, got {trans.from_type}", ) first_fromtype_encountered = True @@ -349,25 +358,24 @@ def _validate_transformers(cls: type, field_name: str, type_) -> bool: return True elif target_type is not prev_type: raise ConfigSchemaError( - cls, field_name, + cls, + field_name, f"out_type {prev_type} of the final transformer does not match the " - f"annotated type {target_type} of this field" + f"annotated type {target_type} of this field", ) return False -def _convert( - value: Any, type_: Any, qualified_name: str -): +def _convert(value: Any, type_: Any, qualified_name: str): if type_ is Any: # Any type is accepted return value - if hasattr(type_, '__metadata__'): + if hasattr(type_, "__metadata__"): # Annotated with transformers return do_transformations(value, type_) - if hasattr(type_, '__origin__') and hasattr(type_, '__args__'): + if hasattr(type_, "__origin__") and hasattr(type_, "__args__"): # Use special rules for typing module types type_origin = type_.__origin__ type_args = type_.__args__ @@ -394,15 +402,12 @@ def _convert( typecheck((list, tuple), value, qualified_name) if len(value) != len(type_args): raise ValueError( - f"Expected a tuple of length {len(type_args)}, got " - f"{len(value)}" + f"Expected a tuple of length {len(type_args)}, got " f"{len(value)}" ) converted_list = [] for i, subtype in enumerate(type_args): - converted = _convert( - value[i], subtype, f"{qualified_name}[{i}]" - ) + converted = _convert(value[i], subtype, f"{qualified_name}[{i}]") converted_list.append(converted) return tuple(converted_list) @@ -412,9 +417,7 @@ def _convert( list_type = type_args[0] converted_list = [] for i, subvalue in enumerate(value): - converted = _convert( - subvalue, list_type, f"{qualified_name}[{i}]" - ) + converted = _convert(subvalue, list_type, f"{qualified_name}[{i}]") converted_list.append(converted) return converted_list @@ -424,17 +427,13 @@ def _convert( key_type, value_type = type_args if key_type is not str: # Ideally should never happen - raise RuntimeError( - "The dict keys type annotation should be str" - ) + raise RuntimeError("The dict keys type annotation should be str") converted_dict = {} for key, val in value.items(): dict_qual_name = f"{qualified_name}[{key}]" typecheck(key_type, key, dict_qual_name) - converted = _convert( - val, value_type, dict_qual_name - ) + converted = _convert(val, value_type, dict_qual_name) converted_dict[key] = converted return converted_dict @@ -442,9 +441,7 @@ def _convert( if type_origin is Literal: # Check equality with one of the literal values if value not in type_args: - raise ValueError( - f"Value must be equal to one of {type_args}" - ) + raise ValueError(f"Value must be equal to one of {type_args}") return value if type_ is tuple: diff --git a/sandpiper/piperfig/test_parser.py b/sandpiper/piperfig/test_parser.py index df0708b..7c7a82e 100644 --- a/sandpiper/piperfig/test_parser.py +++ b/sandpiper/piperfig/test_parser.py @@ -15,10 +15,10 @@ def assert_type_value(value, assert_type: type, assert_value): class TestSimple: - def test_none(self): class C(ConfigSchema): field: None + parsed = C('{"field": null}') assert parsed.field is None with pytest.raises(TypeError): @@ -27,6 +27,7 @@ class C(ConfigSchema): def test_bool(self): class C(ConfigSchema): field: bool + parsed = C('{"field": true}') assert parsed.field is True with pytest.raises(TypeError): @@ -35,6 +36,7 @@ class C(ConfigSchema): def test_int(self): class C(ConfigSchema): field: int + parsed = C('{"field": 1}') assert_type_value(parsed.field, int, 1) @@ -44,6 +46,7 @@ class C(ConfigSchema): def test_float(self): class C(ConfigSchema): field: float + parsed = C('{"field": 1.0}') assert_type_value(parsed.field, float, 1.0) with pytest.raises(TypeError): @@ -52,13 +55,15 @@ class C(ConfigSchema): def test_str(self): class C(ConfigSchema): field: str + parsed = C('{"field": "hi"}') - assert_type_value(parsed.field, str, 'hi') + assert_type_value(parsed.field, str, "hi") with pytest.raises(TypeError): parsed = C('{"field": 1}') def test_invalid(self): with pytest.raises(ConfigSchemaError): + class C(ConfigSchema): field: bytes @@ -69,19 +74,18 @@ class C(ConfigSchema): parsed = C('{"fieldA": 1, "fieldB": "hi"}') assert_type_value(parsed.fieldA, int, 1) - assert_type_value(parsed.fieldB, str, 'hi') + assert_type_value(parsed.fieldB, str, "hi") def test_missing_field(self): class C(ConfigSchema): fieldA: int fieldB: int - with pytest.raises(MissingFieldError, match='fieldA'): + with pytest.raises(MissingFieldError, match="fieldA"): C('{"fieldB": 1}') class TestCollections: - def test_tuple(self): class C(ConfigSchema): field: tuple @@ -90,7 +94,7 @@ class C(ConfigSchema): assert isinstance(parsed.field, tuple) assert parsed.field[0] is None assert parsed.field[1] is True - assert_type_value(parsed.field[2], str, 'hi') + assert_type_value(parsed.field[2], str, "hi") def test_tuple_args(self): class C(ConfigSchema): @@ -99,7 +103,7 @@ class C(ConfigSchema): parsed = C('{"field": [true, 1, "hi"]}') assert parsed.field[0] is True assert_type_value(parsed.field[1], int, 1) - assert_type_value(parsed.field[2], str, 'hi') + assert_type_value(parsed.field[2], str, "hi") def test_tuple_args_type_err(self): class C(ConfigSchema): @@ -112,7 +116,7 @@ def test_tuple_args_len_err(self): class C(ConfigSchema): field: tuple[bool, int, str] - with pytest.raises(ValueError, match=r'length.+3'): + with pytest.raises(ValueError, match=r"length.+3"): parsed = C('{"field": [true, 1, "hi", "extra"]}') def test_list(self): @@ -123,7 +127,7 @@ class C(ConfigSchema): assert isinstance(parsed.field, list) assert parsed.field[0] is None assert parsed.field[1] is True - assert_type_value(parsed.field[2], str, 'hi') + assert_type_value(parsed.field[2], str, "hi") def test_list_args(self): class C(ConfigSchema): @@ -149,9 +153,9 @@ class C(ConfigSchema): parsed = C('{"field": {"bool": true, "int": 1, "str": "hi"}}') assert isinstance(parsed.field, dict) - assert parsed.field['bool'] is True - assert_type_value(parsed.field['int'], int, 1) - assert_type_value(parsed.field['str'], str, 'hi') + assert parsed.field["bool"] is True + assert_type_value(parsed.field["int"], int, 1) + assert_type_value(parsed.field["str"], str, "hi") def test_dict_args(self): class C(ConfigSchema): @@ -159,8 +163,8 @@ class C(ConfigSchema): parsed = C('{"field": {"one": 1, "two": 2}}') assert isinstance(parsed.field, dict) - assert_type_value(parsed.field['one'], int, 1) - assert_type_value(parsed.field['two'], int, 2) + assert_type_value(parsed.field["one"], int, 1) + assert_type_value(parsed.field["two"], int, 2) def test_dict_args_type_err(self): class C(ConfigSchema): @@ -171,12 +175,12 @@ class C(ConfigSchema): def test_dict_key_not_str(self): with pytest.raises(TypeError): + class C(ConfigSchema): field: dict[int, str] class TestSpecialTyping: - def test_any(self): class C(ConfigSchema): field: Any @@ -188,7 +192,7 @@ class C(ConfigSchema): assert_type_value(parsed.field, int, 1) parsed = C('{"field": "hi"}') - assert_type_value(parsed.field, str, 'hi') + assert_type_value(parsed.field, str, "hi") def test_union(self): class C(ConfigSchema): @@ -196,6 +200,7 @@ class C(ConfigSchema): def test_union_err(self): with pytest.raises(ConfigSchemaError): + class C(ConfigSchema): field: Union[int, bytes] @@ -205,6 +210,7 @@ class C(ConfigSchema): def test_union_nested_err(self): with pytest.raises(ConfigSchemaError): + class C(ConfigSchema): field: Union[bool, Union[int, Union[float, bytes]]] @@ -233,10 +239,10 @@ class C(ConfigSchema): class TestNestedSchemas: - def test_simple(self): class C(ConfigSchema): nested: _Nested + class _Nested(ConfigSchema): field: int @@ -248,6 +254,7 @@ def test_same_field_names(self): class C(ConfigSchema): field: str nested: _Nested + class _Nested(ConfigSchema): field: int @@ -259,14 +266,18 @@ class _Nested(ConfigSchema): def test_super_nested(self): class C(ConfigSchema): nested1: _Nested + class _Nested(ConfigSchema): nested2: _Nested + class _Nested(ConfigSchema): nested3: _Nested + class _Nested(ConfigSchema): field: str - parsed = C(''' + parsed = C( + """ { "nested1": { "nested2": { @@ -276,18 +287,19 @@ class _Nested(ConfigSchema): } } } - ''') - assert_type_value(parsed.nested1.nested2.nested3.field, str, 'hi') + """ + ) + assert_type_value(parsed.nested1.nested2.nested3.field, str, "hi") class TestDefaults: - @staticmethod @pytest.fixture def simple_schema(): class C(ConfigSchema): intField = 3 - strField = 'hi' + strField = "hi" + return C @staticmethod @@ -295,6 +307,7 @@ class C(ConfigSchema): def tuple_schema(): class C(ConfigSchema): field = (1, 2, 3) + return C @staticmethod @@ -302,41 +315,43 @@ class C(ConfigSchema): def list_schema(): class C(ConfigSchema): field = [1, 2, 3] + return C @staticmethod @pytest.fixture def dict_schema(): class C(ConfigSchema): - field = {'one': 1, 'two': 2} + field = {"one": 1, "two": 2} + return C def test_empty(self, simple_schema): - parsed = simple_schema('{}') + parsed = simple_schema("{}") assert_type_value(parsed.intField, int, 3) - assert_type_value(parsed.strField, str, 'hi') + assert_type_value(parsed.strField, str, "hi") def test_any(self): class C(ConfigSchema): field: Any = 1 - parsed = C('{}') + parsed = C("{}") assert_type_value(parsed.field, int, 1) parsed = C('{"field": 2}') assert_type_value(parsed.field, int, 2) parsed = C('{"field": "hi"}') - assert_type_value(parsed.field, str, 'hi') + assert_type_value(parsed.field, str, "hi") def test_missing_one(self, simple_schema): parsed = simple_schema('{"intField": 2}') assert_type_value(parsed.intField, int, 2) - assert_type_value(parsed.strField, str, 'hi') + assert_type_value(parsed.strField, str, "hi") parsed = simple_schema('{"strField": "hello"}') assert_type_value(parsed.intField, int, 3) - assert_type_value(parsed.strField, str, 'hello') + assert_type_value(parsed.strField, str, "hello") def test_bad_type_parsing(self, simple_schema): with pytest.raises(TypeError): @@ -344,11 +359,12 @@ def test_bad_type_parsing(self, simple_schema): def test_bad_type_definition(self): with pytest.raises(ConfigSchemaError): + class C(ConfigSchema): - field: int = 'hi' + field: int = "hi" def test_tuple(self, tuple_schema): - parsed = tuple_schema('{}') + parsed = tuple_schema("{}") assert parsed.field == (1, 2, 3) def test_tuple_type_inference(self, tuple_schema): @@ -361,7 +377,7 @@ def test_tuple_type_inference(self, tuple_schema): parsed = tuple_schema('{"field": ["hi", "there", "friend"]}') def test_list(self, list_schema): - parsed1 = list_schema('{}') + parsed1 = list_schema("{}") assert parsed1.field == [1, 2, 3] def test_list_type_inference(self, list_schema): @@ -372,25 +388,26 @@ def test_list_type_inference(self, list_schema): parsed = list_schema('{"field": ["hi", "there"]}') def test_list_identity(self, list_schema): - parsed1 = list_schema('{}') - parsed2 = list_schema('{}') + parsed1 = list_schema("{}") + parsed2 = list_schema("{}") assert parsed1.field == [1, 2, 3] assert parsed2.field == [1, 2, 3] assert parsed2.field is not parsed1.field def test_dict(self, dict_schema): - parsed1 = dict_schema('{}') - assert parsed1.field == {'one': 1, 'two': 2} + parsed1 = dict_schema("{}") + assert parsed1.field == {"one": 1, "two": 2} def test_dict_type_inference(self): class C(ConfigSchema): - field = {'one': 1, 'two': 'two', 'three': True, 'four': 4} + field = {"one": 1, "two": "two", "three": True, "four": 4} # noinspection PyUnresolvedReferences - field_type = C._ConfigSchema__fields['field'][0] + field_type = C._ConfigSchema__fields["field"][0] assert field_type == dict[str, Union[int, str, bool]] def test_dict_type_inference_key_not_str(self): - with pytest.raises(ConfigSchemaError, match=r'key.+str'): + with pytest.raises(ConfigSchemaError, match=r"key.+str"): + class C(ConfigSchema): - field = {'one': 1, 2: "two"} + field = {"one": 1, 2: "two"} diff --git a/sandpiper/piperfig/test_transformers.py b/sandpiper/piperfig/test_transformers.py index 5fc9607..55dea65 100644 --- a/sandpiper/piperfig/test_transformers.py +++ b/sandpiper/piperfig/test_transformers.py @@ -21,22 +21,24 @@ def dedent_strip(str_: str) -> str: class TestMisc: - def test_no_annotated(self): - with pytest.raises(ConfigSchemaError, match=r'Annotated'): + with pytest.raises(ConfigSchemaError, match=r"Annotated"): + class C(ConfigSchema): field: FromType(int, str) def test_bad_origin(self): with pytest.raises(ConfigSchemaError): + class C(ConfigSchema): - field: Annotated[Path, ''] + field: Annotated[Path, ""] @pytest.mark.skip( "I'm not sure how to handle this yet, but it's a bit of an edge case" ) def test_bounded_before_fromtype(self): with pytest.raises(ConfigSchemaError): + class C(ConfigSchema): field: Annotated[int, Bounded(5, 6), FromType(str, int)] @@ -45,25 +47,29 @@ class C(ConfigSchema): # noinspection PyTypeHints field: Annotated[ Path, - FromType(str, float), FromType(float, int), FromType(int, str), - MaybeRelativePath(Path('/root/dir')) + FromType(str, float), + FromType(float, int), + FromType(int, str), + MaybeRelativePath(Path("/root/dir")), ] parsed = C('{"field": "5.3"}') - assert parsed.field == Path('/root/dir/5') + assert parsed.field == Path("/root/dir/5") serialized = parsed.serialize() assert_type_value( - serialized, str, - dedent_strip(''' + serialized, + str, + dedent_strip( + """ { "field": "5.0" } - ''') + """ + ), ) class TestFromType: - def test_implicit_to(self): class C(ConfigSchema): field: Annotated[str, FromType(int)] @@ -75,7 +81,8 @@ class C(ConfigSchema): C('{"field": "123"}') def test_multiple_implicit_to_type_err(self): - with pytest.raises(ConfigSchemaError, match='implicit'): + with pytest.raises(ConfigSchemaError, match="implicit"): + class C(ConfigSchema): field: Annotated[str, FromType(int), FromType(str)] @@ -95,6 +102,7 @@ class C(ConfigSchema): def test_explicit_to_type_mismatch(self): with pytest.raises(ConfigSchemaError): + class C(ConfigSchema): field: Annotated[str, FromType(int, float)] @@ -103,6 +111,7 @@ def test_implicit_with_bounded_after(self): # the last FromType, however I think it's difficult to understand # what's going on if more transformers come after it with pytest.raises(ConfigSchemaError): + class C(ConfigSchema): field: Annotated[int, FromType(str), Bounded(2, 4)] @@ -118,6 +127,7 @@ class C(ConfigSchema): def test_invalid_from_type(self): with pytest.raises(ConfigSchemaError): + class C(ConfigSchema): field: Annotated[int, FromType(Path, int)] @@ -133,7 +143,6 @@ def test_back_err(self): class TestBounded: - def test_min(self): class C(ConfigSchema): field: Annotated[int, Bounded(5, None)] @@ -194,6 +203,7 @@ class C(ConfigSchema): def test_min_greater_than_max(self): with pytest.raises(ValueError): + class C(ConfigSchema): field: Annotated[int, Bounded(2, 1)] @@ -209,42 +219,41 @@ def test_back_err(self): # Let's use Posix paths for our tests -@mock.patch('pathlib.os.name', 'posix') -@mock.patch('pathlib._PosixFlavour.is_supported', True) +@mock.patch("pathlib.os.name", "posix") +@mock.patch("pathlib._PosixFlavour.is_supported", True) class TestMaybeRelativePath: - def test_relative(self): class C(ConfigSchema): # noinspection PyTypeHints - field: Annotated[Path, MaybeRelativePath(Path('/root/dir'))] + field: Annotated[Path, MaybeRelativePath(Path("/root/dir"))] parsed = C('{"field": "relative/path"}') - assert parsed.field == Path('/root/dir/relative/path') + assert parsed.field == Path("/root/dir/relative/path") def test_relative_with_dot(self): class C(ConfigSchema): # noinspection PyTypeHints - field: Annotated[Path, MaybeRelativePath(Path('/root/dir'))] + field: Annotated[Path, MaybeRelativePath(Path("/root/dir"))] parsed = C('{"field": "./relative/path"}') - assert parsed.field == Path('/root/dir/relative/path') + assert parsed.field == Path("/root/dir/relative/path") def test_absolute(self): class C(ConfigSchema): # noinspection PyTypeHints - field: Annotated[Path, MaybeRelativePath(Path('/root/dir'))] + field: Annotated[Path, MaybeRelativePath(Path("/root/dir"))] parsed = C('{"field": "/absolute/path"}') - assert parsed.field == Path('/absolute/path') + assert parsed.field == Path("/absolute/path") def test_back_relative(self): - trans = MaybeRelativePath(Path('/root/dir')) - path = Path('/root/dir/relative/path') + trans = MaybeRelativePath(Path("/root/dir")) + path = Path("/root/dir/relative/path") back = trans.transform_back(path) - assert_type_value(back, str, 'relative/path') + assert_type_value(back, str, "relative/path") def test_back_absolute(self): - trans = MaybeRelativePath(Path('/root/dir')) - path = Path('/absolute/path') + trans = MaybeRelativePath(Path("/root/dir")) + path = Path("/absolute/path") back = trans.transform_back(path) - assert_type_value(back, str, '/absolute/path') + assert_type_value(back, str, "/absolute/path") diff --git a/sandpiper/piperfig/transformers.py b/sandpiper/piperfig/transformers.py index 1fd8dde..4562a4d 100644 --- a/sandpiper/piperfig/transformers.py +++ b/sandpiper/piperfig/transformers.py @@ -5,18 +5,20 @@ from .misc import typecheck __all__ = ( - 'do_transformations', 'do_transformations_back', - 'ConfigTransformer', 'FromType', - 'Bounded', 'MaybeRelativePath' + "do_transformations", + "do_transformations_back", + "ConfigTransformer", + "FromType", + "Bounded", + "MaybeRelativePath", ) -V1 = TypeVar('V1') -V2 = TypeVar('V2') +V1 = TypeVar("V1") +V2 = TypeVar("V2") def do_transformations(value, annotation): - if (not hasattr(annotation, '__origin__') - or not hasattr(annotation, '__metadata__')): + if not hasattr(annotation, "__origin__") or not hasattr(annotation, "__metadata__"): raise TypeError(f"Value {annotation} is not an Annotated instance") target_type: type = annotation.__origin__ @@ -52,8 +54,7 @@ def do_transformations(value, annotation): def do_transformations_back(value, annotation): - if (not hasattr(annotation, '__origin__') - or not hasattr(annotation, '__metadata__')): + if not hasattr(annotation, "__origin__") or not hasattr(annotation, "__metadata__"): raise TypeError(f"Value {annotation} is not an Annotated instance") target_type: type = annotation.__origin__ @@ -87,7 +88,6 @@ def do_transformations_back(value, annotation): class ConfigTransformer(metaclass=ABCMeta): - @property @abstractmethod def in_type(self) -> Type[V1]: @@ -116,16 +116,12 @@ class FromType(ConfigTransformer): ) # TODO update to generic type[] once jetbrains fixes a bug in pycharm - def __init__( - self, from_type: Type[V1], to_type: Optional[Type[V2]] = None - ): + def __init__(self, from_type: Type[V1], to_type: Optional[Type[V2]] = None): self.from_type = from_type self.to_type = to_type def __repr__(self): - return ( - f"FromType({self.from_type.__name__!r}, {self.to_type.__name__!r})" - ) + return f"FromType({self.from_type.__name__!r}, {self.to_type.__name__!r})" def __str__(self): return f"" @@ -139,14 +135,14 @@ def out_type(self) -> type: return self.to_type def transform(self, value: V1) -> V2: - typecheck(self.from_type, value, 'value') + typecheck(self.from_type, value, "value") if self.to_type is not None: return self.to_type(value) raise RuntimeError(self.__implicit_err_msg) def transform_back(self, value: V2) -> V1: if self.to_type is not None: - typecheck(self.to_type, value, 'value') + typecheck(self.to_type, value, "value") else: raise RuntimeError(self.__implicit_err_msg) return self.from_type(value) @@ -154,7 +150,6 @@ def transform_back(self, value: V2) -> V1: # noinspection PyShadowingBuiltins class Bounded(ConfigTransformer): - @overload def __init__(self, min: V1, max: V1): pass @@ -175,9 +170,7 @@ def __init__(self, min: Any, max: Any): f"{type(max)}" ) if min > max: - raise ValueError( - f"min {min} is greater than max {max}" - ) + raise ValueError(f"min {min} is greater than max {max}") self.type = type(min) elif min is not None: self.type = type(min) @@ -207,12 +200,10 @@ def check(self, value: V1): f"Value {value} must be greater than or equal to {self.min}" ) if self.max is not None and value > self.max: - raise ValueError( - f"Value {value} must be less than or equal to {self.max}" - ) + raise ValueError(f"Value {value} must be less than or equal to {self.max}") def transform(self, value: V1) -> V1: - typecheck(self.type, value, 'value') + typecheck(self.type, value, "value") self.check(value) return value @@ -221,12 +212,9 @@ def transform_back(self, value: V1) -> V1: class MaybeRelativePath(ConfigTransformer): - def __init__(self, root_path: Path): if not isinstance(root_path, Path): - raise TypeError( - f"root_path must be of type Path, got {type(root_path)}" - ) + raise TypeError(f"root_path must be of type Path, got {type(root_path)}") self.root_path = root_path def __repr__(self): @@ -244,14 +232,14 @@ def out_type(self) -> type: return Path def transform(self, value: str) -> Path: - typecheck(str, value, 'value') + typecheck(str, value, "value") path = Path(value) if not path.is_absolute(): return self.root_path / path return path def transform_back(self, value: Path) -> str: - typecheck(Path, value, 'value', use_isinstance=True) + typecheck(Path, value, "value", use_isinstance=True) if value.is_relative_to(self.root_path): return str(value.relative_to(self.root_path)) return str(value.absolute()) diff --git a/sandpiper/sandpiper.py b/sandpiper/sandpiper.py index cf91e9f..7a0524a 100644 --- a/sandpiper/sandpiper.py +++ b/sandpiper/sandpiper.py @@ -8,30 +8,25 @@ from .config import SandpiperConfig from .help import HelpCommand -__all__ = ('Sandpiper', 'run_bot') +__all__ = ("Sandpiper", "run_bot") -logger = logging.getLogger('sandpiper') +logger = logging.getLogger("sandpiper") # noinspection PyMethodMayBeStatic class Sandpiper(commands.Bot): - def __init__(self, config: SandpiperConfig._Bot): # noinspection PyUnusedLocal def get_prefix(bot: commands.Bot, msg: discord.Message) -> str: """Allows prefix-less command invocation in DMs""" if isinstance(msg.channel, discord.DMChannel): - return '' + return "" return commands.when_mentioned_or(config.command_prefix)(bot, msg) - intents = discord.Intents( - guilds=True, - members=True, - messages=True - ) + intents = discord.Intents(guilds=True, members=True, messages=True) allowed_mentions = discord.AllowedMentions(users=True) - activity = discord.Game(f'{config.command_prefix}help') + activity = discord.Game(f"{config.command_prefix}help") super().__init__( # Client params @@ -42,7 +37,7 @@ def get_prefix(bot: commands.Bot, msg: discord.Message) -> str: # Bot params command_prefix=get_prefix, description=config.description, - help_command=HelpCommand() + help_command=HelpCommand(), ) # Add a dummy command that triggers when the user tries to use the @@ -50,57 +45,56 @@ def get_prefix(bot: commands.Bot, msg: discord.Message) -> str: # user to just omit it @self.command(name=config.command_prefix.strip(), hidden=True) async def noprefix_notify(ctx: commands.Context, *, rest: str): - if ctx.prefix == '': + if ctx.prefix == "": raise commands.BadArgument( - f"You don't need to prefix commands here. " - f"Just type `{rest}`." + f"You don't need to prefix commands here. " f"Just type `{rest}`." ) self.modules_config = config.modules - self.load_extension('sandpiper.user_data') - self.load_extension('sandpiper.bios') - self.load_extension('sandpiper.birthdays') - self.load_extension('sandpiper.conversion') - self.load_extension('sandpiper.upgrades') + self.load_extension("sandpiper.user_data") + self.load_extension("sandpiper.bios") + self.load_extension("sandpiper.birthdays") + self.load_extension("sandpiper.conversion") + self.load_extension("sandpiper.upgrades") async def on_connect(self): - logger.info('Client connected') + logger.info("Client connected") async def on_disconnect(self): - logger.info('Client disconnected') + logger.info("Client disconnected") async def on_resumed(self): - logger.info('Session resumed') + logger.info("Session resumed") async def on_ready(self): - logger.info('Client started') + logger.info("Client started") async def on_error(self, event_method: str, *args, **kwargs): exc_type, __, __ = sys.exc_info() if exc_type is discord.HTTPException: - logger.warning('HTTP exception', exc_info=True) + logger.warning("HTTP exception", exc_info=True) elif exc_type is discord.Forbidden: - logger.warning('Forbidden request', exc_info=True) + logger.warning("Forbidden request", exc_info=True) - elif event_method == 'on_message': + elif event_method == "on_message": msg: discord.Message = args[0] logger.error( - f'Unhandled in on_message (content: {msg.content!r} ' - f'author: {msg.author} channel: {msg.channel})', - exc_info=True + f"Unhandled in on_message (content: {msg.content!r} " + f"author: {msg.author} channel: {msg.channel})", + exc_info=True, ) else: logger.error( f"Unhandled in {event_method} (args: {args} kwargs: {kwargs})", - exc_info=True + exc_info=True, ) def run_bot(): # Load config - config_path = Path(__file__).parent / 'config.json' + config_path = Path(__file__).parent / "config.json" with config_path.open() as f: config = SandpiperConfig(f) @@ -110,12 +104,12 @@ def run_bot(): config.bot_token = None # Sandpiper logging - logger = logging.getLogger('sandpiper') + logger = logging.getLogger("sandpiper") logger.setLevel(config.logging.sandpiper_logging_level) logger.addHandler(config.logging.handler) # Discord logging - logger = logging.getLogger('discord') + logger = logging.getLogger("discord") logger.setLevel(config.logging.discord_logging_level) logger.addHandler(config.logging.handler) diff --git a/sandpiper/tests/__init__.py b/sandpiper/tests/__init__.py index 25f23c6..3ae2f75 100644 --- a/sandpiper/tests/__init__.py +++ b/sandpiper/tests/__init__.py @@ -1,4 +1,4 @@ import pytest -for helper in ('discord', 'misc', 'mocking'): - pytest.register_assert_rewrite(f'{__spec__.parent}.helpers.{helper}') +for helper in ("discord", "misc", "mocking"): + pytest.register_assert_rewrite(f"{__spec__.parent}.helpers.{helper}") diff --git a/sandpiper/tests/conftest.py b/sandpiper/tests/conftest.py index 6b69876..9a1353d 100644 --- a/sandpiper/tests/conftest.py +++ b/sandpiper/tests/conftest.py @@ -19,10 +19,12 @@ @pytest.fixture() def new_id(): last_id = 0 + def f() -> int: nonlocal last_id last_id += 1 return last_id + return f @@ -36,16 +38,19 @@ def users_map() -> dict[int, discord.User]: return {} -T_BaseUserType = TypeVar('T_BaseUserType', bound=Type[discord.user.BaseUser]) +T_BaseUserType = TypeVar("T_BaseUserType", bound=Type[discord.user.BaseUser]) def __create_mock_user( - users: list[discord.User], users_map: dict[int, discord.User], - guilds: list[discord.Guild], client_user_id: int, - mock_spec: T_BaseUserType, - id: int, name: Optional[str] = None, - discriminator: Optional[int] = None, - **kwargs + users: list[discord.User], + users_map: dict[int, discord.User], + guilds: list[discord.Guild], + client_user_id: int, + mock_spec: T_BaseUserType, + id: int, + name: Optional[str] = None, + discriminator: Optional[int] = None, + **kwargs, ) -> T_BaseUserType: """ Create a mock user and add it to the mapping structures. @@ -70,20 +75,20 @@ def __create_mock_user( if id in users_map: raise ValueError(f"User with id={id} already exists") if name is None: - name = 'A_User' + name = "A_User" if discriminator is None: discriminator = id % 10000 - user = mock.create_autospec( - mock_spec, id=id, discriminator=discriminator, **kwargs - ) + user = mock.create_autospec(mock_spec, id=id, discriminator=discriminator, **kwargs) user.name = name def get_mutual_guilds(): mutual_guilds = [] for guild in guilds: - if (guild.get_member(client_user_id) is not None - and guild.get_member(id) is not None): + if ( + guild.get_member(client_user_id) is not None + and guild.get_member(id) is not None + ): # Both the bot and the user are in this guild mutual_guilds.append(guild) return mutual_guilds @@ -102,8 +107,10 @@ def get_mutual_guilds(): @pytest.fixture() def make_user(bot, guilds, new_id, users, users_map): def f( - id: Optional[int] = None, name: Optional[str] = None, - discriminator: Optional[int] = None, **kwargs + id: Optional[int] = None, + name: Optional[str] = None, + discriminator: Optional[int] = None, + **kwargs, ) -> T_BaseUserType: """ Add a mock user to the client. You can access users through the list @@ -119,8 +126,14 @@ def f( if id is None: id = new_id() return __create_mock_user( - users, users_map, guilds, bot.user.id, discord.User, - id=id, name=name, discriminator=discriminator + users, + users_map, + guilds, + bot.user.id, + discord.User, + id=id, + name=name, + discriminator=discriminator, ) return f @@ -139,8 +152,10 @@ def channels_map() -> dict[int, discord.TextChannel]: @pytest.fixture() def make_channel(new_id, channels, channels_map): def f( - guild: discord.Guild, id_: Optional[int] = None, - name: Optional[str] = None, **kwargs + guild: discord.Guild, + id_: Optional[int] = None, + name: Optional[str] = None, + **kwargs, ) -> discord.TextChannel: """ Add a mock text channel to the client. You can access text channels @@ -158,7 +173,7 @@ def f( if id_ in channels_map: raise ValueError(f"Channel with id={id_} already exists") if name is None: - name = 'a-channel' + name = "a-channel" channel = mock.create_autospec(discord.TextChannel, id=id_, **kwargs) channel.name = name @@ -190,7 +205,7 @@ def guilds_map() -> dict[int, discord.Guild]: @pytest.fixture() def make_guild(new_id, guilds, guilds_map): def f( - id_: Optional[int] = None, name: Optional[str] = None, **kwargs + id_: Optional[int] = None, name: Optional[str] = None, **kwargs ) -> discord.Guild: """ Add a mock guild to the client. You can access guilds through the list @@ -207,7 +222,7 @@ def f( if id_ in guilds_map: raise ValueError(f"Guild with id={id_} already exists") if name is None: - name = 'A_Guild' + name = "A_Guild" guild = mock.create_autospec(discord.Guild, id=id_, **kwargs) guild.name = name @@ -230,7 +245,7 @@ def f( @pytest.fixture() def add_user_to_guild(users_map, guilds_map): def f( - guild_id: int, user_id: int, display_name: Optional[str], **kwargs + guild_id: int, user_id: int, display_name: Optional[str], **kwargs ) -> discord.Member: """ Create a mock member by adding a user to a guild. The user and guild @@ -251,14 +266,16 @@ def f( guild = guilds_map[guild_id] # noinspection PyUnresolvedReferences if user_id in guild._members_map: - raise ValueError( - f"Member with id={user_id} already exists in this guild" - ) + raise ValueError(f"Member with id={user_id} already exists in this guild") user = users_map[user_id] member = MagicMock_( spec=discord.Member, - id=user_id, name=user.name, discriminator=user.discriminator, - guild=guild, display_name=display_name, **kwargs + id=user_id, + name=user.name, + discriminator=user.discriminator, + guild=guild, + display_name=display_name, + **kwargs, ) guild.members.append(member) # noinspection PyUnresolvedReferences @@ -280,21 +297,20 @@ def message() -> discord.Message: # noinspection PyPropertyAccess @pytest.fixture() async def bot( - users, users_map, channels, channels_map, guilds, guilds_map, - new_id + users, users_map, channels, channels_map, guilds, guilds_map, new_id ) -> commands.Bot: patchers = [] # Patch in some mocks for bot attributes that tests may need to work # with (otherwise they're unsettable) - for attr in ('users', 'guilds'): + for attr in ("users", "guilds"): p = mock.patch(f"discord.ext.commands.Bot.{attr}") patchers.append(p) p.start() # Create a dummy bot that will never actually connect but will help # with invocation - bot = commands.Bot(command_prefix='') + bot = commands.Bot(command_prefix="") @functools.wraps(mock.patch.object) def patch(attr, *, target=bot, **kwargs): @@ -310,27 +326,32 @@ def patch(attr, *, target=bot, **kwargs): # This function checks if message author is the self bot and skips # context creation (meaning we won't get command invocation), so # we will bypass it - patch('_skip_check', return_value=False) + patch("_skip_check", return_value=False) # This connection (discord.state.ConnectionState) object has a `user` # field which is accessed by the client's `user` property. The # _skip_check function is called with `client.user.id` which doesn't # exist (since we aren't connecting) and raises an AttributeError, so # we need to patch it in. - connection_mock = patch('_connection') + connection_mock = patch("_connection") client_uid = new_id() connection_mock.user = __create_mock_user( - users, users_map, guilds, client_uid, discord.ClientUser, - id=client_uid, name='Bot' + users, + users_map, + guilds, + client_uid, + discord.ClientUser, + id=client_uid, + name="Bot", ) - get_user = patch('get_user') + get_user = patch("get_user") get_user.side_effect = lambda id: users_map.get(id, None) - get_channel = patch('get_channel') + get_channel = patch("get_channel") get_channel.side_effect = lambda id: channels_map.get(id, None) - get_guild = patch('get_guild') + get_guild = patch("get_guild") get_guild.side_effect = lambda id: guilds_map.get(id, None) bot.guilds = guilds @@ -381,6 +402,7 @@ async def f(message_content: str) -> list[discord.Embed]: """ send = await invoke_cmd(message_content) return get_embeds(send) + return f @@ -399,7 +421,7 @@ async def f(message_content: str) -> mock.AsyncMock: message.content = message_content message.channel.send = mock.AsyncMock() - for listener in bot.extra_events.get('on_message', []): + for listener in bot.extra_events.get("on_message", []): await listener(message) return message.channel.send @@ -418,6 +440,7 @@ async def f(message_content: str) -> list[str]: """ send = await dispatch_msg(message_content) return get_contents(send) + return f @@ -433,6 +456,7 @@ async def f(message_content: str) -> list[discord.Embed]: """ send = await dispatch_msg(message_content) return get_embeds(send) + return f @@ -446,13 +470,11 @@ async def database() -> DatabaseSQLite: """Create, connect, and patch in a database adapter""" # Connect to a dummy database - db = DatabaseSQLite(':memory:') + db = DatabaseSQLite(":memory:") await db.connect() # Bypass UserData cog lookup by patching in the database - patcher = mock.patch( - 'sandpiper.user_data.UserData.get_database', return_value=db - ) + patcher = mock.patch("sandpiper.user_data.UserData.get_database", return_value=db) patcher.start() yield db @@ -464,9 +486,7 @@ async def database() -> DatabaseSQLite: @pytest.fixture() def patch_localzone_utc() -> pytz.UTC: # Patch localzone to use UTC - patcher = mock.patch( - 'tzlocal.get_localzone', autospec=True - ) + patcher = mock.patch("tzlocal.get_localzone", autospec=True) mock_localzone = patcher.start() mock_localzone.return_value = pytz.UTC @@ -477,19 +497,17 @@ def patch_localzone_utc() -> pytz.UTC: @pytest.fixture() def patch_datetime() -> list[mock.MagicMock]: - patchers = patch_all_symbol_imports(dt, 'sandpiper.', 'test') + patchers = patch_all_symbol_imports(dt, "sandpiper.", "test") dt_mocks = [] for patcher in patchers: mock_datetime = patcher.start() dt_mocks.append(mock_datetime) - mock_datetime.datetime = mock.MagicMock( - spec=dt.datetime, wraps=dt.datetime) + mock_datetime.datetime = mock.MagicMock(spec=dt.datetime, wraps=dt.datetime) mock_datetime.date = mock.MagicMock(spec=dt.date, wraps=dt.date) mock_datetime.time = mock.MagicMock(spec=dt.time, wraps=dt.time) - mock_datetime.timedelta = mock.MagicMock( - spec=dt.timedelta, wraps=dt.timedelta) + mock_datetime.timedelta = mock.MagicMock(spec=dt.timedelta, wraps=dt.timedelta) yield dt_mocks @@ -499,7 +517,6 @@ def patch_datetime() -> list[mock.MagicMock]: @pytest.fixture() def patch_datetime_now(patch_datetime): - def f(static_datetime: dt.datetime) -> dt.datetime: for dt_mock in patch_datetime: dt_mock.datetime.now.return_value = static_datetime @@ -516,7 +533,7 @@ def fail_on_log_error(caplog): i = 0 exc_texts = [] - records = caplog.get_records('setup') + caplog.get_records('call') + records = caplog.get_records("setup") + caplog.get_records("call") for r in records: if not r.levelno == logging.ERROR: continue @@ -526,15 +543,13 @@ def fail_on_log_error(caplog): exc_texts.append( f"[Error {i}]\n{r.message}\n" f"(No traceback, but here's the logging location)\n" - f" File \"{r.pathname}\", line {r.lineno}" + f' File "{r.pathname}", line {r.lineno}' ) i += 1 if exc_texts: - exc_texts = '\n\n'.join(exc_texts) - pytest.fail( - f"Errors logged during testing:\n{exc_texts}", pytrace=False - ) + exc_texts = "\n\n".join(exc_texts) + pytest.fail(f"Errors logged during testing:\n{exc_texts}", pytrace=False) # endregion diff --git a/sandpiper/tests/helpers/discord.py b/sandpiper/tests/helpers/discord.py index 590a788..49b2136 100644 --- a/sandpiper/tests/helpers/discord.py +++ b/sandpiper/tests/helpers/discord.py @@ -6,9 +6,13 @@ from .misc import assert_in, assert_one_if_list __all__ = [ - 'get_contents', 'get_embeds', - 'assert_success', 'assert_warning', 'assert_error', 'assert_info', - 'assert_no_reply' + "get_contents", + "get_embeds", + "assert_success", + "assert_warning", + "assert_error", + "assert_info", + "assert_no_reply", ] # region Misc helper functions @@ -21,7 +25,8 @@ def get_contents(mock_: mock.Mock) -> list[str]: """ # noinspection PyUnboundLocalVariable return [ - content for call in mock_.call_args_list + content + for call in mock_.call_args_list if len(call.args) > 0 and (content := call.args[0]) is not None ] @@ -32,8 +37,7 @@ def get_embeds(mock_: mock.Mock) -> list[discord.Embed]: :return: a list of embeds sent in each message """ return [ - embed for call in mock_.call_args_list - if (embed := call.kwargs.get('embed')) + embed for call in mock_.call_args_list if (embed := call.kwargs.get("embed")) ] @@ -42,55 +46,47 @@ def get_embeds(mock_: mock.Mock) -> list[discord.Embed]: # region Embed assertions -def assert_success( - embed: Union[discord.Embed, list[discord.Embed]], *substrings: str -): +def assert_success(embed: Union[discord.Embed, list[discord.Embed]], *substrings: str): """ Assert ``embed`` is a success embed and that its description contains each substring in ``substrings``. """ __tracebackhide__ = True embed = assert_one_if_list(embed) - assert 'Success' in embed.title + assert "Success" in embed.title assert_in(embed.description, *substrings) -def assert_warning( - embed: Union[discord.Embed, list[discord.Embed]], *substrings: str -): +def assert_warning(embed: Union[discord.Embed, list[discord.Embed]], *substrings: str): """ Assert ``embed`` is a warning embed and that its description contains each substring in ``substrings``. """ __tracebackhide__ = True embed = assert_one_if_list(embed) - assert 'Warning' in embed.title + assert "Warning" in embed.title assert_in(embed.description, *substrings) -def assert_error( - embed: Union[discord.Embed, list[discord.Embed]], *substrings: str -): +def assert_error(embed: Union[discord.Embed, list[discord.Embed]], *substrings: str): """ Assert ``embed`` is an error embed and that its description contains each substring in ``substrings``. """ __tracebackhide__ = True embed = assert_one_if_list(embed) - assert 'Error' in embed.title + assert "Error" in embed.title assert_in(embed.description, *substrings) -def assert_info( - embed: Union[discord.Embed, list[discord.Embed]], *substrings: str -): +def assert_info(embed: Union[discord.Embed, list[discord.Embed]], *substrings: str): """ Assert ``embed`` is an info embed and that its description contains each substring in ``substrings``. """ __tracebackhide__ = True embed = assert_one_if_list(embed) - assert 'Info' in embed.title + assert "Info" in embed.title assert_in(embed.description, *substrings) diff --git a/sandpiper/tests/helpers/misc.py b/sandpiper/tests/helpers/misc.py index b19a596..ff0bd20 100644 --- a/sandpiper/tests/helpers/misc.py +++ b/sandpiper/tests/helpers/misc.py @@ -3,13 +3,9 @@ import re from typing import NoReturn, TypeVar, Union -__all__ = [ - 'assert_in', 'assert_regex', - 'assert_one_if_list', - 'assert_count_equal' -] +__all__ = ["assert_in", "assert_regex", "assert_one_if_list", "assert_count_equal"] -V = TypeVar('V') +V = TypeVar("V") def assert_in(str_: str, *substrings: str) -> NoReturn: @@ -29,9 +25,9 @@ def assert_regex(str_: str, *patterns: str) -> NoReturn: """ __tracebackhide__ = True for pattern in patterns: - assert re.search(pattern, str_), ( - f'Pattern "{pattern}" did not match any part of "{str_}"' - ) + assert re.search( + pattern, str_ + ), f'Pattern "{pattern}" did not match any part of "{str_}"' def assert_one_if_list(x: Union[list[V], V]) -> V: diff --git a/sandpiper/tests/helpers/mocking.py b/sandpiper/tests/helpers/mocking.py index e2d88d9..b51540e 100644 --- a/sandpiper/tests/helpers/mocking.py +++ b/sandpiper/tests/helpers/mocking.py @@ -5,9 +5,9 @@ import pytest __all__ = [ - 'MagicMock_', - 'isinstance_mock_supported', - 'patch_all_symbol_imports', + "MagicMock_", + "isinstance_mock_supported", + "patch_all_symbol_imports", ] @@ -19,8 +19,8 @@ class MagicMock_(mock.MagicMock): def __init__(self, *args, _name_: Optional[str] = None, **kwargs): if _name_ is None: - _name_ = '' - name_attr = kwargs.pop('name', None) + _name_ = "" + name_attr = kwargs.pop("name", None) super().__init__(*args, name=_name_, **kwargs) self.name = name_attr @@ -38,8 +38,9 @@ def isinstance_mock_supported(__obj, __class_or_tuple): def patch_all_symbol_imports( - target_symbol: Any, match_prefix: Optional[str] = None, - skip_substring: Optional[str] = None + target_symbol: Any, + match_prefix: Optional[str] = None, + skip_substring: Optional[str] = None, ): """ Iterate through every visible module (in sys.modules) that starts with @@ -79,13 +80,8 @@ def patch_all_symbol_imports( # Iterate through all currently imported modules # Make a copy in case it changes for module in list(sys.modules.values()): - name_matches = ( - match_prefix is None - or module.__name__.startswith(match_prefix) - ) - should_skip = ( - skip_substring is not None and skip_substring in module.__name__ - ) + name_matches = match_prefix is None or module.__name__.startswith(match_prefix) + should_skip = skip_substring is not None and skip_substring in module.__name__ if not name_matches or should_skip: continue @@ -94,8 +90,8 @@ def patch_all_symbol_imports( for local_name, local in list(module.__dict__.items()): if local is target_symbol: # Patch this symbol local to the module - patchers.append(mock.patch( - f'{module.__name__}.{local_name}', autospec=True - )) + patchers.append( + mock.patch(f"{module.__name__}.{local_name}", autospec=True) + ) return patchers diff --git a/sandpiper/tests/test_bios.py b/sandpiper/tests/test_bios.py index 09493ce..dff5bd9 100644 --- a/sandpiper/tests/test_bios.py +++ b/sandpiper/tests/test_bios.py @@ -30,10 +30,10 @@ def bot(bot) -> commands.Bot: async def greg(database, new_id) -> int: """Make a dummy Greg user in the database and return his user ID""" uid = new_id() - await database.set_preferred_name(uid, 'Greg') - await database.set_pronouns(uid, 'He/Him') + await database.set_preferred_name(uid, "Greg") + await database.set_pronouns(uid, "He/Him") await database.set_birthday(uid, dt.date(2000, 2, 14)) - await database.set_timezone(uid, pytz.timezone('America/New_York')) + await database.set_timezone(uid, pytz.timezone("America/New_York")) return uid @@ -66,13 +66,14 @@ def send_in_guild(new_id, make_guild, message): message.guild = guild -@pytest.mark.usefixtures('apply_new_user_id') +@pytest.mark.usefixtures("apply_new_user_id") class TestPrivacy: - @staticmethod async def _assert( - embeds: list[discord.Embed], message: discord.Message, - db_meth: T_DatabaseMethod, privacy: PrivacyType + embeds: list[discord.Embed], + message: discord.Message, + db_meth: T_DatabaseMethod, + privacy: PrivacyType, ): """ Use the database method ``db_meth`` to assert the command successfully @@ -84,8 +85,10 @@ async def _assert( @staticmethod async def _assert_all( - embeds: list[discord.Embed], message: discord.Message, - db: DatabaseSQLite, privacy: PrivacyType + embeds: list[discord.Embed], + message: discord.Message, + db: DatabaseSQLite, + privacy: PrivacyType, ): """ Assert that every database field is ``privacy``. @@ -102,65 +105,60 @@ async def _assert_all( # region Name async def test_name_public(self, database, message, invoke_cmd_get_embeds): - embeds = await invoke_cmd_get_embeds('privacy name public') + embeds = await invoke_cmd_get_embeds("privacy name public") await self._assert( - embeds, message, database.get_privacy_preferred_name, - PrivacyType.PUBLIC + embeds, message, database.get_privacy_preferred_name, PrivacyType.PUBLIC ) async def test_name_private(self, database, message, invoke_cmd_get_embeds): - embeds = await invoke_cmd_get_embeds('privacy name private') + embeds = await invoke_cmd_get_embeds("privacy name private") await self._assert( - embeds, message, database.get_privacy_preferred_name, - PrivacyType.PRIVATE + embeds, message, database.get_privacy_preferred_name, PrivacyType.PRIVATE ) async def test_name_cycle(self, database, message, invoke_cmd_get_embeds): - embeds = await invoke_cmd_get_embeds('privacy name private') + embeds = await invoke_cmd_get_embeds("privacy name private") await self._assert( - embeds, message, database.get_privacy_preferred_name, - PrivacyType.PRIVATE + embeds, message, database.get_privacy_preferred_name, PrivacyType.PRIVATE ) - embeds = await invoke_cmd_get_embeds('privacy name public') + embeds = await invoke_cmd_get_embeds("privacy name public") await self._assert( - embeds, message, database.get_privacy_preferred_name, - PrivacyType.PUBLIC + embeds, message, database.get_privacy_preferred_name, PrivacyType.PUBLIC ) - embeds = await invoke_cmd_get_embeds('privacy name private') + embeds = await invoke_cmd_get_embeds("privacy name private") await self._assert( - embeds, message, database.get_privacy_preferred_name, - PrivacyType.PRIVATE + embeds, message, database.get_privacy_preferred_name, PrivacyType.PRIVATE ) # endregion # region Pronouns async def test_pronouns_public(self, database, message, invoke_cmd_get_embeds): - embeds = await invoke_cmd_get_embeds('privacy pronouns public') + embeds = await invoke_cmd_get_embeds("privacy pronouns public") await self._assert( embeds, message, database.get_privacy_pronouns, PrivacyType.PUBLIC ) async def test_pronouns_private(self, database, message, invoke_cmd_get_embeds): - embeds = await invoke_cmd_get_embeds('privacy pronouns private') + embeds = await invoke_cmd_get_embeds("privacy pronouns private") await self._assert( embeds, message, database.get_privacy_pronouns, PrivacyType.PRIVATE ) async def test_pronouns_cycle(self, database, message, invoke_cmd_get_embeds): - embeds = await invoke_cmd_get_embeds('privacy pronouns private') + embeds = await invoke_cmd_get_embeds("privacy pronouns private") await self._assert( embeds, message, database.get_privacy_pronouns, PrivacyType.PRIVATE ) - embeds = await invoke_cmd_get_embeds('privacy pronouns public') + embeds = await invoke_cmd_get_embeds("privacy pronouns public") await self._assert( embeds, message, database.get_privacy_pronouns, PrivacyType.PUBLIC ) - embeds = await invoke_cmd_get_embeds('privacy pronouns private') + embeds = await invoke_cmd_get_embeds("privacy pronouns private") await self._assert( embeds, message, database.get_privacy_pronouns, PrivacyType.PRIVATE ) @@ -169,9 +167,9 @@ async def test_pronouns_cycle(self, database, message, invoke_cmd_get_embeds): # region Birthday async def test_birthday_private_age_private( - self, database, message, invoke_cmd_get_embeds + self, database, message, invoke_cmd_get_embeds ): - embeds = await invoke_cmd_get_embeds('privacy birthday private') + embeds = await invoke_cmd_get_embeds("privacy birthday private") await self._assert( embeds, message, database.get_privacy_birthday, PrivacyType.PRIVATE ) @@ -181,10 +179,10 @@ async def test_birthday_private_age_private( assert BirthdayExplanations.age_is_private not in desc async def test_birthday_private_age_public( - self, database, message, invoke_cmd_get_embeds + self, database, message, invoke_cmd_get_embeds ): await database.set_privacy_age(message.author.id, PrivacyType.PUBLIC) - embeds = await invoke_cmd_get_embeds('privacy birthday private') + embeds = await invoke_cmd_get_embeds("privacy birthday private") await self._assert( embeds, message, database.get_privacy_birthday, PrivacyType.PRIVATE ) @@ -194,9 +192,9 @@ async def test_birthday_private_age_public( assert BirthdayExplanations.age_is_public not in desc async def test_birthday_public_age_private( - self, database, message, invoke_cmd_get_embeds + self, database, message, invoke_cmd_get_embeds ): - embeds = await invoke_cmd_get_embeds('privacy birthday public') + embeds = await invoke_cmd_get_embeds("privacy birthday public") await self._assert( embeds, message, database.get_privacy_birthday, PrivacyType.PUBLIC ) @@ -206,10 +204,10 @@ async def test_birthday_public_age_private( assert BirthdayExplanations.age_is_private in desc async def test_birthday_public_age_public( - self, database, message, invoke_cmd_get_embeds + self, database, message, invoke_cmd_get_embeds ): await database.set_privacy_age(message.author.id, PrivacyType.PUBLIC) - embeds = await invoke_cmd_get_embeds('privacy birthday public') + embeds = await invoke_cmd_get_embeds("privacy birthday public") await self._assert( embeds, message, database.get_privacy_birthday, PrivacyType.PUBLIC ) @@ -219,17 +217,17 @@ async def test_birthday_public_age_public( assert BirthdayExplanations.age_is_public in desc async def test_birthday_cycle(self, database, message, invoke_cmd_get_embeds): - embeds = await invoke_cmd_get_embeds('privacy birthday private') + embeds = await invoke_cmd_get_embeds("privacy birthday private") await self._assert( embeds, message, database.get_privacy_birthday, PrivacyType.PRIVATE ) - embeds = await invoke_cmd_get_embeds('privacy birthday public') + embeds = await invoke_cmd_get_embeds("privacy birthday public") await self._assert( embeds, message, database.get_privacy_birthday, PrivacyType.PUBLIC ) - embeds = await invoke_cmd_get_embeds('privacy birthday private') + embeds = await invoke_cmd_get_embeds("privacy birthday private") await self._assert( embeds, message, database.get_privacy_birthday, PrivacyType.PRIVATE ) @@ -238,9 +236,9 @@ async def test_birthday_cycle(self, database, message, invoke_cmd_get_embeds): # region Age async def test_age_private_birthday_private( - self, database, message, invoke_cmd_get_embeds + self, database, message, invoke_cmd_get_embeds ): - embeds = await invoke_cmd_get_embeds('privacy age private') + embeds = await invoke_cmd_get_embeds("privacy age private") await self._assert( embeds, message, database.get_privacy_age, PrivacyType.PRIVATE ) @@ -250,10 +248,10 @@ async def test_age_private_birthday_private( assert BirthdayExplanations.birthday_is_private not in desc async def test_age_private_birthday_public( - self, database, message, invoke_cmd_get_embeds + self, database, message, invoke_cmd_get_embeds ): await database.set_privacy_birthday(message.author.id, PrivacyType.PUBLIC) - embeds = await invoke_cmd_get_embeds('privacy age private') + embeds = await invoke_cmd_get_embeds("privacy age private") await self._assert( embeds, message, database.get_privacy_age, PrivacyType.PRIVATE ) @@ -262,8 +260,10 @@ async def test_age_private_birthday_public( assert BirthdayExplanations.age_is_private in desc assert BirthdayExplanations.birthday_is_public not in desc - async def test_age_public_birthday_private(self, database, message, invoke_cmd_get_embeds): - embeds = await invoke_cmd_get_embeds('privacy age public') + async def test_age_public_birthday_private( + self, database, message, invoke_cmd_get_embeds + ): + embeds = await invoke_cmd_get_embeds("privacy age public") await self._assert( embeds, message, database.get_privacy_age, PrivacyType.PUBLIC ) @@ -272,9 +272,11 @@ async def test_age_public_birthday_private(self, database, message, invoke_cmd_g assert BirthdayExplanations.age_is_public not in desc assert BirthdayExplanations.birthday_is_private not in desc - async def test_age_public_birthday_public(self, database, message, invoke_cmd_get_embeds): + async def test_age_public_birthday_public( + self, database, message, invoke_cmd_get_embeds + ): await database.set_privacy_birthday(message.author.id, PrivacyType.PUBLIC) - embeds = await invoke_cmd_get_embeds('privacy age public') + embeds = await invoke_cmd_get_embeds("privacy age public") await self._assert( embeds, message, database.get_privacy_age, PrivacyType.PUBLIC ) @@ -284,17 +286,17 @@ async def test_age_public_birthday_public(self, database, message, invoke_cmd_ge assert BirthdayExplanations.birthday_is_public not in desc async def test_age_cycle(self, database, message, invoke_cmd_get_embeds): - embeds = await invoke_cmd_get_embeds('privacy age private') + embeds = await invoke_cmd_get_embeds("privacy age private") await self._assert( embeds, message, database.get_privacy_age, PrivacyType.PRIVATE ) - embeds = await invoke_cmd_get_embeds('privacy age public') + embeds = await invoke_cmd_get_embeds("privacy age public") await self._assert( embeds, message, database.get_privacy_age, PrivacyType.PUBLIC ) - embeds = await invoke_cmd_get_embeds('privacy age private') + embeds = await invoke_cmd_get_embeds("privacy age private") await self._assert( embeds, message, database.get_privacy_age, PrivacyType.PRIVATE ) @@ -303,29 +305,29 @@ async def test_age_cycle(self, database, message, invoke_cmd_get_embeds): # region Timezone async def test_timezone_public(self, database, message, invoke_cmd_get_embeds): - embeds = await invoke_cmd_get_embeds('privacy timezone public') + embeds = await invoke_cmd_get_embeds("privacy timezone public") await self._assert( embeds, message, database.get_privacy_timezone, PrivacyType.PUBLIC ) async def test_timezone_private(self, database, message, invoke_cmd_get_embeds): - embeds = await invoke_cmd_get_embeds('privacy timezone private') + embeds = await invoke_cmd_get_embeds("privacy timezone private") await self._assert( embeds, message, database.get_privacy_timezone, PrivacyType.PRIVATE ) async def test_timezone_cycle(self, database, message, invoke_cmd_get_embeds): - embeds = await invoke_cmd_get_embeds('privacy timezone private') + embeds = await invoke_cmd_get_embeds("privacy timezone private") await self._assert( embeds, message, database.get_privacy_timezone, PrivacyType.PRIVATE ) - embeds = await invoke_cmd_get_embeds('privacy timezone public') + embeds = await invoke_cmd_get_embeds("privacy timezone public") await self._assert( embeds, message, database.get_privacy_timezone, PrivacyType.PUBLIC ) - embeds = await invoke_cmd_get_embeds('privacy timezone private') + embeds = await invoke_cmd_get_embeds("privacy timezone private") await self._assert( embeds, message, database.get_privacy_timezone, PrivacyType.PRIVATE ) @@ -334,97 +336,101 @@ async def test_timezone_cycle(self, database, message, invoke_cmd_get_embeds): # region All async def test_all_public(self, database, message, invoke_cmd_get_embeds): - embeds = await invoke_cmd_get_embeds('privacy all public') + embeds = await invoke_cmd_get_embeds("privacy all public") await self._assert_all(embeds, message, database, PrivacyType.PUBLIC) desc = embeds[0].description assert BirthdayExplanations.birthday_is_public in desc assert BirthdayExplanations.age_is_public in desc async def test_all_private(self, database, message, invoke_cmd_get_embeds): - embeds = await invoke_cmd_get_embeds('privacy all private') + embeds = await invoke_cmd_get_embeds("privacy all private") await self._assert_all(embeds, message, database, PrivacyType.PRIVATE) desc = embeds[0].description assert BirthdayExplanations.birthday_is_private in desc assert BirthdayExplanations.age_is_private not in desc async def test_all_cycle(self, database, message, invoke_cmd_get_embeds): - embeds = await invoke_cmd_get_embeds('privacy all private') + embeds = await invoke_cmd_get_embeds("privacy all private") await self._assert_all(embeds, message, database, PrivacyType.PRIVATE) - embeds = await invoke_cmd_get_embeds('privacy all public') + embeds = await invoke_cmd_get_embeds("privacy all public") await self._assert_all(embeds, message, database, PrivacyType.PUBLIC) - embeds = await invoke_cmd_get_embeds('privacy all private') + embeds = await invoke_cmd_get_embeds("privacy all private") await self._assert_all(embeds, message, database, PrivacyType.PRIVATE) # endregion -@pytest.mark.usefixtures('send_in_dms') +@pytest.mark.usefixtures("send_in_dms") class TestShow: - @pytest.fixture() def june_1st_2020_932_am( - self, patch_localzone_utc, patch_datetime_now + self, patch_localzone_utc, patch_datetime_now ) -> dt.datetime: yield patch_datetime_now(dt.datetime(2020, 6, 1, 9, 32)) async def test_name(self, invoke_as_greg, invoke_cmd_get_embeds): - embeds = await invoke_cmd_get_embeds('name show') - assert_info(embeds, 'Greg') + embeds = await invoke_cmd_get_embeds("name show") + assert_info(embeds, "Greg") async def test_pronouns(self, invoke_as_greg, invoke_cmd_get_embeds): - embeds = await invoke_cmd_get_embeds('pronouns show') - assert_info(embeds, 'He/Him') + embeds = await invoke_cmd_get_embeds("pronouns show") + assert_info(embeds, "He/Him") async def test_birthday(self, invoke_as_greg, invoke_cmd_get_embeds): - embeds = await invoke_cmd_get_embeds('birthday show') - assert_info(embeds, '2000-02-14') + embeds = await invoke_cmd_get_embeds("birthday show") + assert_info(embeds, "2000-02-14") async def test_age( - self, invoke_as_greg, invoke_cmd_get_embeds, - june_1st_2020_932_am + self, invoke_as_greg, invoke_cmd_get_embeds, june_1st_2020_932_am ): - embeds = await invoke_cmd_get_embeds('age show') + embeds = await invoke_cmd_get_embeds("age show") assert_info(embeds) - assert_regex(embeds[0].description, 'Age.+20') + assert_regex(embeds[0].description, "Age.+20") async def test_timezone(self, invoke_as_greg, invoke_cmd_get_embeds): - embeds = await invoke_cmd_get_embeds('timezone show') - assert_info(embeds, 'America/New_York') + embeds = await invoke_cmd_get_embeds("timezone show") + assert_info(embeds, "America/New_York") async def test_all( - self, invoke_as_greg, invoke_cmd_get_embeds, - june_1st_2020_932_am + self, invoke_as_greg, invoke_cmd_get_embeds, june_1st_2020_932_am ): - embeds = await invoke_cmd_get_embeds('bio show') + embeds = await invoke_cmd_get_embeds("bio show") assert_info(embeds) assert_regex( embeds[0].description, - 'Name.+Greg', 'Pronouns.+He/Him', 'Birthday.+2000-02-14', - 'Age.+20', 'Timezone.+America/New_York' + "Name.+Greg", + "Pronouns.+He/Him", + "Birthday.+2000-02-14", + "Age.+20", + "Timezone.+America/New_York", ) -@pytest.mark.usefixtures('send_in_dms', 'apply_new_user_id') +@pytest.mark.usefixtures("send_in_dms", "apply_new_user_id") class TestSet: - @staticmethod async def _assert_private( - embeds: list[discord.Embed], message: discord.Message, - db_meth: T_DatabaseMethod, expected_value, privacy_field_name: str + embeds: list[discord.Embed], + message: discord.Message, + db_meth: T_DatabaseMethod, + expected_value, + privacy_field_name: str, ): """ Assert the set succeeded and that the success embed tells the user how they can make this field public if they want. """ assert len(embeds) == 1 - assert_success(embeds[0], f'privacy {privacy_field_name} public') + assert_success(embeds[0], f"privacy {privacy_field_name} public") value = await db_meth(message.author.id) assert value == expected_value @staticmethod async def _assert_public( - embeds: list[discord.Embed], message: discord.Message, - db_meth: T_DatabaseMethod, expected_value + embeds: list[discord.Embed], + message: discord.Message, + db_meth: T_DatabaseMethod, + expected_value, ): """ Assert that the test succeeded. @@ -437,53 +443,48 @@ async def _assert_public( # region Name async def test_name_private(self, database, message, invoke_cmd_get_embeds): - embeds = await invoke_cmd_get_embeds(f'name set Greg') + embeds = await invoke_cmd_get_embeds(f"name set Greg") await self._assert_private( - embeds, message, database.get_preferred_name, 'Greg', 'name' + embeds, message, database.get_preferred_name, "Greg", "name" ) async def test_name_public(self, database, message, invoke_cmd_get_embeds): await database.set_privacy_preferred_name(message.author.id, PrivacyType.PUBLIC) - embeds = await invoke_cmd_get_embeds(f'name set Greg') - await self._assert_public( - embeds, message, database.get_preferred_name, 'Greg' - ) + embeds = await invoke_cmd_get_embeds(f"name set Greg") + await self._assert_public(embeds, message, database.get_preferred_name, "Greg") async def test_name_too_long_err(self, database, invoke_cmd_get_embeds): - with pytest.raises(commands.BadArgument, match='64 characters'): - await invoke_cmd_get_embeds('name set ' + 'a'*65) + with pytest.raises(commands.BadArgument, match="64 characters"): + await invoke_cmd_get_embeds("name set " + "a" * 65) # endregion # region Pronouns async def test_pronouns_private(self, database, message, invoke_cmd_get_embeds): - embeds = await invoke_cmd_get_embeds(f'pronouns set He/Him') + embeds = await invoke_cmd_get_embeds(f"pronouns set He/Him") await self._assert_private( - embeds, message, database.get_pronouns, 'He/Him', 'pronouns' + embeds, message, database.get_pronouns, "He/Him", "pronouns" ) async def test_pronouns_public(self, database, message, invoke_cmd_get_embeds): await database.set_privacy_pronouns(message.author.id, PrivacyType.PUBLIC) - embeds = await invoke_cmd_get_embeds(f'pronouns set He/Him') - await self._assert_public( - embeds, message, database.get_pronouns, 'He/Him' - ) + embeds = await invoke_cmd_get_embeds(f"pronouns set He/Him") + await self._assert_public(embeds, message, database.get_pronouns, "He/Him") async def test_pronouns_too_long_err(self, database, invoke_cmd_get_embeds): - with pytest.raises(commands.BadArgument, match='64 characters'): - await invoke_cmd_get_embeds('pronouns set ' + 'a'*65) + with pytest.raises(commands.BadArgument, match="64 characters"): + await invoke_cmd_get_embeds("pronouns set " + "a" * 65) # endregion # region Birthday async def test_birthday_private_age_private( - self, database, message, invoke_cmd_get_embeds + self, database, message, invoke_cmd_get_embeds ): await database.set_privacy_age(message.author.id, PrivacyType.PUBLIC) - embeds = await invoke_cmd_get_embeds(f'birthday set 2000-02-14') + embeds = await invoke_cmd_get_embeds(f"birthday set 2000-02-14") await self._assert_private( - embeds, message, database.get_birthday, dt.date(2000, 2, 14), - 'birthday' + embeds, message, database.get_birthday, dt.date(2000, 2, 14), "birthday" ) # Soft suggest changing birthday to public and say nothing about age desc = embeds[0].description @@ -491,13 +492,12 @@ async def test_birthday_private_age_private( assert BirthdayExplanations.age_is_private not in desc async def test_birthday_private_age_public( - self, database, message, invoke_cmd_get_embeds + self, database, message, invoke_cmd_get_embeds ): await database.set_privacy_age(message.author.id, PrivacyType.PUBLIC) - embeds = await invoke_cmd_get_embeds(f'birthday set 2000-02-14') + embeds = await invoke_cmd_get_embeds(f"birthday set 2000-02-14") await self._assert_private( - embeds, message, database.get_birthday, dt.date(2000, 2, 14), - 'birthday' + embeds, message, database.get_birthday, dt.date(2000, 2, 14), "birthday" ) # Soft suggest changing birthday to public and say nothing about age desc = embeds[0].description @@ -505,10 +505,10 @@ async def test_birthday_private_age_public( assert BirthdayExplanations.age_is_public not in desc async def test_birthday_public_age_private( - self, database, message, invoke_cmd_get_embeds + self, database, message, invoke_cmd_get_embeds ): await database.set_privacy_birthday(message.author.id, PrivacyType.PUBLIC) - embeds = await invoke_cmd_get_embeds(f'birthday set 2000-02-14') + embeds = await invoke_cmd_get_embeds(f"birthday set 2000-02-14") await self._assert_public( embeds, message, database.get_birthday, dt.date(2000, 2, 14) ) @@ -518,11 +518,11 @@ async def test_birthday_public_age_private( assert BirthdayExplanations.age_is_private in desc async def test_birthday_public_age_public( - self, database, message, invoke_cmd_get_embeds + self, database, message, invoke_cmd_get_embeds ): await database.set_privacy_age(message.author.id, PrivacyType.PUBLIC) await database.set_privacy_birthday(message.author.id, PrivacyType.PUBLIC) - embeds = await invoke_cmd_get_embeds(f'birthday set 2000-02-14') + embeds = await invoke_cmd_get_embeds(f"birthday set 2000-02-14") await self._assert_public( embeds, message, database.get_birthday, dt.date(2000, 2, 14) ) @@ -535,37 +535,37 @@ async def test_birthday_public_age_public( # region Age async def test_age(self, database, message, invoke_cmd_get_embeds): - embeds = await invoke_cmd_get_embeds(f'age set 20') + embeds = await invoke_cmd_get_embeds(f"age set 20") assert_error(embeds) # endregion # region Timezone async def test_timezone_private(self, database, message, invoke_cmd_get_embeds): - embeds = await invoke_cmd_get_embeds(f'timezone set new york') + embeds = await invoke_cmd_get_embeds(f"timezone set new york") await self._assert_private( - embeds, message, database.get_timezone, - pytz.timezone('America/New_York'), 'timezone' + embeds, + message, + database.get_timezone, + pytz.timezone("America/New_York"), + "timezone", ) async def test_timezone_public(self, database, message, invoke_cmd_get_embeds): await database.set_privacy_timezone(message.author.id, PrivacyType.PUBLIC) - embeds = await invoke_cmd_get_embeds(f'timezone set new york') + embeds = await invoke_cmd_get_embeds(f"timezone set new york") await self._assert_public( - embeds, message, database.get_timezone, - pytz.timezone('America/New_York') + embeds, message, database.get_timezone, pytz.timezone("America/New_York") ) # endregion -@pytest.mark.usefixtures('send_in_dms') +@pytest.mark.usefixtures("send_in_dms") class TestDelete: - @staticmethod async def _assert( - embeds: list[discord.Embed], message: discord.Message, - db_meth: T_DatabaseMethod + embeds: list[discord.Embed], message: discord.Message, db_meth: T_DatabaseMethod ): """ Use the database method ``db_meth`` to assert the command successfully @@ -576,40 +576,34 @@ async def _assert( value = await db_meth(message.author.id) assert value is None - async def test_name( - self, database, message, invoke_as_greg, invoke_cmd_get_embeds - ): - embeds = await invoke_cmd_get_embeds('name delete') + async def test_name(self, database, message, invoke_as_greg, invoke_cmd_get_embeds): + embeds = await invoke_cmd_get_embeds("name delete") await self._assert(embeds, message, database.get_preferred_name) async def test_pronouns( - self, database, message, invoke_as_greg, invoke_cmd_get_embeds + self, database, message, invoke_as_greg, invoke_cmd_get_embeds ): - embeds = await invoke_cmd_get_embeds('pronouns delete') + embeds = await invoke_cmd_get_embeds("pronouns delete") await self._assert(embeds, message, database.get_pronouns) async def test_birthday( - self, database, message, invoke_as_greg, invoke_cmd_get_embeds + self, database, message, invoke_as_greg, invoke_cmd_get_embeds ): - embeds = await invoke_cmd_get_embeds('birthday delete') + embeds = await invoke_cmd_get_embeds("birthday delete") await self._assert(embeds, message, database.get_birthday) - async def test_age( - self, database, message, invoke_as_greg, invoke_cmd_get_embeds - ): - embeds = await invoke_cmd_get_embeds('age delete') - assert_error(embeds, 'birthday delete') + async def test_age(self, database, message, invoke_as_greg, invoke_cmd_get_embeds): + embeds = await invoke_cmd_get_embeds("age delete") + assert_error(embeds, "birthday delete") async def test_timezone( - self, database, message, invoke_as_greg, invoke_cmd_get_embeds + self, database, message, invoke_as_greg, invoke_cmd_get_embeds ): - embeds = await invoke_cmd_get_embeds('timezone delete') + embeds = await invoke_cmd_get_embeds("timezone delete") await self._assert(embeds, message, database.get_timezone) - async def test_all( - self, database, message, invoke_as_greg, invoke_cmd_get_embeds - ): - embeds = await invoke_cmd_get_embeds('bio delete') + async def test_all(self, database, message, invoke_as_greg, invoke_cmd_get_embeds): + embeds = await invoke_cmd_get_embeds("bio delete") assert_success(embeds) uid = message.author.id @@ -630,7 +624,6 @@ async def test_all( class TestWhois: - @pytest.fixture() def main_guild(self, new_id, make_guild) -> discord.Guild: return make_guild(new_id()) @@ -640,16 +633,16 @@ def secondary_guild(self, new_id, make_guild) -> discord.Guild: return make_guild(new_id()) @pytest.fixture() - def user_factory( - self, database, new_id, make_user, add_user_to_guild, main_guild - ): + def user_factory(self, database, new_id, make_user, add_user_to_guild, main_guild): async def f( - guild: Optional[discord.Guild], - discriminator: int, username: str, display_name: str, - preferred_name: Optional[str] = None, - privacy_preferred_name: Optional[PrivacyType] = None, - pronouns: Optional[str] = None, - privacy_pronouns: Optional[PrivacyType] = None + guild: Optional[discord.Guild], + discriminator: int, + username: str, + display_name: str, + preferred_name: Optional[str] = None, + privacy_preferred_name: Optional[PrivacyType] = None, + pronouns: Optional[str] = None, + privacy_pronouns: Optional[PrivacyType] = None, ) -> discord.User: if guild is None: @@ -676,7 +669,7 @@ async def f( @pytest.fixture() async def executor(self, main_guild, user_factory, message) -> discord.User: - u = await user_factory(None, 1000, 'Executor', '_executor_') + u = await user_factory(None, 1000, "Executor", "_executor_") message.author = u # noinspection PyDunderSlots,PyUnresolvedReferences message.guild = main_guild @@ -684,235 +677,267 @@ async def executor(self, main_guild, user_factory, message) -> discord.User: @pytest.fixture() async def multiple_display_names_user( - self, executor, user_factory, new_id, make_guild, - add_user_to_guild, secondary_guild + self, + executor, + user_factory, + new_id, + make_guild, + add_user_to_guild, + secondary_guild, ) -> discord.User: other_user = await user_factory( - None, 1001, 'Greg', '_Greg1_', '*NoPreferred*', - PrivacyType.PUBLIC + None, 1001, "Greg", "_Greg1_", "*NoPreferred*", PrivacyType.PUBLIC ) - add_user_to_guild(secondary_guild.id, other_user.id, '_Greg2_') + add_user_to_guild(secondary_guild.id, other_user.id, "_Greg2_") return other_user @pytest.fixture() async def executor_in_secondary_guild( - self, executor, secondary_guild, add_user_to_guild + self, executor, secondary_guild, add_user_to_guild ): # noinspection PyUnresolvedReferences - add_user_to_guild(secondary_guild.id, executor.id, '_NoDisplay_') + add_user_to_guild(secondary_guild.id, executor.id, "_NoDisplay_") # region Same guild async def test_username( - self, executor, user_factory, message, invoke_cmd_get_embeds + self, executor, user_factory, message, invoke_cmd_get_embeds ): other_user = await user_factory( - None, 1001, 'Greg', '_NoDisplay_', '*NoPreferred*' + None, 1001, "Greg", "_NoDisplay_", "*NoPreferred*" ) - embeds = await invoke_cmd_get_embeds('whois greg') - assert_info(embeds, 'Greg#1001') + embeds = await invoke_cmd_get_embeds("whois greg") + assert_info(embeds, "Greg#1001") async def test_username_with_discriminator( - self, executor, user_factory, message, invoke_cmd_get_embeds + self, executor, user_factory, message, invoke_cmd_get_embeds ): other_user = await user_factory( - None, 1001, 'Greg', '_NoDisplay_', '*NoPreferred*' + None, 1001, "Greg", "_NoDisplay_", "*NoPreferred*" ) other_user = await user_factory( - None, 1002, 'Greg', '_NoDisplay_', '*NoPreferred*' + None, 1002, "Greg", "_NoDisplay_", "*NoPreferred*" ) - embeds = await invoke_cmd_get_embeds('whois greg#1002') + embeds = await invoke_cmd_get_embeds("whois greg#1002") assert_info(embeds) - assert 'Greg#1001' not in embeds[0].description - assert 'Greg#1002' in embeds[0].description + assert "Greg#1001" not in embeds[0].description + assert "Greg#1002" in embeds[0].description async def test_display_name( - self, executor, user_factory, message, invoke_cmd_get_embeds + self, executor, user_factory, message, invoke_cmd_get_embeds ): - other_user = await user_factory( - None, 1001, 'NoUser', '_Greg_', '*NoPreferred*' - ) - embeds = await invoke_cmd_get_embeds('whois greg') - assert_info(embeds, '_Greg_') + other_user = await user_factory(None, 1001, "NoUser", "_Greg_", "*NoPreferred*") + embeds = await invoke_cmd_get_embeds("whois greg") + assert_info(embeds, "_Greg_") async def test_preferred_name_private( - self, executor, user_factory, message, invoke_cmd_get_embeds + self, executor, user_factory, message, invoke_cmd_get_embeds ): - other_user = await user_factory( - None, 1001, 'Greg', '_NoDisplay_', '*Greg*' - ) - embeds = await invoke_cmd_get_embeds('whois greg') - assert_info(embeds, 'Greg#1001') - assert '*Greg*' not in embeds[0].description + other_user = await user_factory(None, 1001, "Greg", "_NoDisplay_", "*Greg*") + embeds = await invoke_cmd_get_embeds("whois greg") + assert_info(embeds, "Greg#1001") + assert "*Greg*" not in embeds[0].description async def test_preferred_name_public( - self, executor, user_factory, message, invoke_cmd_get_embeds + self, executor, user_factory, message, invoke_cmd_get_embeds ): other_user = await user_factory( - None, 1001, 'NoUser', '_Greg_', '*NoPreferred*', - PrivacyType.PUBLIC + None, 1001, "NoUser", "_Greg_", "*NoPreferred*", PrivacyType.PUBLIC ) - embeds = await invoke_cmd_get_embeds('whois greg') - assert_info(embeds, '_Greg_') + embeds = await invoke_cmd_get_embeds("whois greg") + assert_info(embeds, "_Greg_") async def test_pronouns_private( - self, executor, user_factory, message, invoke_cmd_get_embeds + self, executor, user_factory, message, invoke_cmd_get_embeds ): other_user = await user_factory( - None, 1001, 'Greg', '_NoDisplay_', '*NoPreferred*', - None, 'He/Him', privacy_pronouns=PrivacyType.PRIVATE - ) - embeds = await invoke_cmd_get_embeds('whois greg') - assert_info(embeds, 'Greg#1001') - assert 'He/Him' not in embeds[0].description + None, + 1001, + "Greg", + "_NoDisplay_", + "*NoPreferred*", + None, + "He/Him", + privacy_pronouns=PrivacyType.PRIVATE, + ) + embeds = await invoke_cmd_get_embeds("whois greg") + assert_info(embeds, "Greg#1001") + assert "He/Him" not in embeds[0].description async def test_pronouns_public( - self, executor, user_factory, message, invoke_cmd_get_embeds + self, executor, user_factory, message, invoke_cmd_get_embeds ): other_user = await user_factory( - None, 1001, 'Greg', '_NoDisplay_', '*NoPreferred*', - None, 'He/Him', privacy_pronouns=PrivacyType.PUBLIC - ) - embeds = await invoke_cmd_get_embeds('whois greg') - assert_info(embeds, 'Greg#1001') - assert 'He/Him' in embeds[0].description + None, + 1001, + "Greg", + "_NoDisplay_", + "*NoPreferred*", + None, + "He/Him", + privacy_pronouns=PrivacyType.PUBLIC, + ) + embeds = await invoke_cmd_get_embeds("whois greg") + assert_info(embeds, "Greg#1001") + assert "He/Him" in embeds[0].description # endregion # region Don't show stuff from different guilds async def test_username_different_guild( - self, executor, user_factory, message, invoke_cmd_get_embeds, - new_id, make_guild, add_user_to_guild + self, + executor, + user_factory, + message, + invoke_cmd_get_embeds, + new_id, + make_guild, + add_user_to_guild, ): other_guild = make_guild(new_id()) other_user = await user_factory( - other_guild, 1001, 'Greg', '_NoDisplay_', '*NoPreferred*' + other_guild, 1001, "Greg", "_NoDisplay_", "*NoPreferred*" ) - embeds = await invoke_cmd_get_embeds('whois greg') - assert_error(embeds, 'No user') + embeds = await invoke_cmd_get_embeds("whois greg") + assert_error(embeds, "No user") # Sanity check that this user can actually be found # noinspection PyDunderSlots,PyUnresolvedReferences message.guild = other_guild # noinspection PyUnresolvedReferences - add_user_to_guild(other_guild.id, executor.id, '_NoDisplay_') - embeds = await invoke_cmd_get_embeds('whois greg') - assert_info(embeds, 'Greg') + add_user_to_guild(other_guild.id, executor.id, "_NoDisplay_") + embeds = await invoke_cmd_get_embeds("whois greg") + assert_info(embeds, "Greg") async def test_display_name_different_guild( - self, executor, user_factory, message, invoke_cmd_get_embeds, - new_id, make_guild, add_user_to_guild + self, + executor, + user_factory, + message, + invoke_cmd_get_embeds, + new_id, + make_guild, + add_user_to_guild, ): other_guild = make_guild(new_id()) other_user = await user_factory( - other_guild, 1001, 'NoUser', '_Greg_', '*NoPreferred*' + other_guild, 1001, "NoUser", "_Greg_", "*NoPreferred*" ) - embeds = await invoke_cmd_get_embeds('whois greg') - assert_error(embeds, 'No user') + embeds = await invoke_cmd_get_embeds("whois greg") + assert_error(embeds, "No user") # Sanity check that this user can actually be found # noinspection PyDunderSlots,PyUnresolvedReferences message.guild = other_guild # noinspection PyUnresolvedReferences - add_user_to_guild(other_guild.id, executor.id, '_NoDisplay_') - embeds = await invoke_cmd_get_embeds('whois greg') - assert_info(embeds, '_Greg_') + add_user_to_guild(other_guild.id, executor.id, "_NoDisplay_") + embeds = await invoke_cmd_get_embeds("whois greg") + assert_info(embeds, "_Greg_") async def test_preferred_name_different_guild( - self, executor, user_factory, message, invoke_cmd_get_embeds, - new_id, make_guild, add_user_to_guild + self, + executor, + user_factory, + message, + invoke_cmd_get_embeds, + new_id, + make_guild, + add_user_to_guild, ): other_guild = make_guild(new_id()) other_user = await user_factory( - other_guild, 1001, 'NoUser', '_NoDisplay_', '*Greg*', - PrivacyType.PUBLIC + other_guild, 1001, "NoUser", "_NoDisplay_", "*Greg*", PrivacyType.PUBLIC ) - embeds = await invoke_cmd_get_embeds('whois greg') - assert_error(embeds, 'No user') + embeds = await invoke_cmd_get_embeds("whois greg") + assert_error(embeds, "No user") # Sanity check that this user can actually be found # noinspection PyDunderSlots,PyUnresolvedReferences message.guild = other_guild # noinspection PyUnresolvedReferences - add_user_to_guild(other_guild.id, executor.id, '_NoDisplay_') - embeds = await invoke_cmd_get_embeds('whois greg') - assert_info(embeds, '*Greg*') + add_user_to_guild(other_guild.id, executor.id, "_NoDisplay_") + embeds = await invoke_cmd_get_embeds("whois greg") + assert_info(embeds, "*Greg*") # endregion # region Multiple display names async def test_multiple_display_names_in_main_guild( - self, executor, message, invoke_cmd_get_embeds, - multiple_display_names_user, executor_in_secondary_guild - ): - embeds = await invoke_cmd_get_embeds('whois greg') + self, + executor, + message, + invoke_cmd_get_embeds, + multiple_display_names_user, + executor_in_secondary_guild, + ): + embeds = await invoke_cmd_get_embeds("whois greg") assert_info(embeds) - assert '_Greg1_' in embeds[0].description - assert '_Greg2_' not in embeds[0].description + assert "_Greg1_" in embeds[0].description + assert "_Greg2_" not in embeds[0].description async def test_multiple_display_names_in_dms( - self, executor, message, invoke_cmd_get_embeds, - multiple_display_names_user, executor_in_secondary_guild + self, + executor, + message, + invoke_cmd_get_embeds, + multiple_display_names_user, + executor_in_secondary_guild, ): # noinspection PyDunderSlots,PyUnresolvedReferences message.guild = None - embeds = await invoke_cmd_get_embeds('whois greg') + embeds = await invoke_cmd_get_embeds("whois greg") assert_info(embeds) - assert '_Greg1_' in embeds[0].description - assert '_Greg2_' in embeds[0].description + assert "_Greg1_" in embeds[0].description + assert "_Greg2_" in embeds[0].description async def test_multiple_display_names_in_dms_only_main_guild( - self, executor, message, invoke_cmd_get_embeds, - multiple_display_names_user + self, executor, message, invoke_cmd_get_embeds, multiple_display_names_user ): # noinspection PyDunderSlots,PyUnresolvedReferences message.guild = None - embeds = await invoke_cmd_get_embeds('whois greg') + embeds = await invoke_cmd_get_embeds("whois greg") assert_info(embeds) - assert '_Greg1_' in embeds[0].description - assert '_Greg2_' not in embeds[0].description + assert "_Greg1_" in embeds[0].description + assert "_Greg2_" not in embeds[0].description # endregion # region Invalid things - async def test_no_user_found( - self, executor, invoke_cmd_get_embeds - ): - embeds = await invoke_cmd_get_embeds('whois gregothy') + async def test_no_user_found(self, executor, invoke_cmd_get_embeds): + embeds = await invoke_cmd_get_embeds("whois gregothy") assert_error(embeds) - async def test_below_minimum_characters( - self, executor, invoke_cmd_get_embeds - ): + async def test_below_minimum_characters(self, executor, invoke_cmd_get_embeds): with pytest.raises(commands.BadArgument): await invoke_cmd_get_embeds("whois e") # endregion -@pytest.mark.usefixtures('apply_new_user_id') +@pytest.mark.usefixtures("apply_new_user_id") class TestAllowPublicBioSetting: - @pytest.fixture(autouse=True) async def set_privacies_public(self, apply_new_user_id, database, message): - await database.set_privacy_preferred_name( - message.author.id, PrivacyType.PUBLIC - ) + await database.set_privacy_preferred_name(message.author.id, PrivacyType.PUBLIC) @pytest.fixture() def allow_public_bio_setting(self, bot): - bios: Bios = bot.get_cog('Bios') + bios: Bios = bot.get_cog("Bios") bios.allow_public_setting = True async def test_disallow_set( - self, database, message, invoke_cmd_get_embeds, send_in_guild + self, database, message, invoke_cmd_get_embeds, send_in_guild ): with pytest.raises(commands.PrivateMessageOnly): - await invoke_cmd_get_embeds(f'name set Greg') + await invoke_cmd_get_embeds(f"name set Greg") async def test_allow_set( - self, database, message, invoke_cmd_get_embeds, send_in_guild, - allow_public_bio_setting - ): - embeds = await invoke_cmd_get_embeds(f'name set Greg') + self, + database, + message, + invoke_cmd_get_embeds, + send_in_guild, + allow_public_bio_setting, + ): + embeds = await invoke_cmd_get_embeds(f"name set Greg") assert_success(embeds) diff --git a/sandpiper/tests/test_birthdays.py b/sandpiper/tests/test_birthdays.py index 85968ce..422661e 100644 --- a/sandpiper/tests/test_birthdays.py +++ b/sandpiper/tests/test_birthdays.py @@ -17,7 +17,7 @@ pytest.skip( "I struggled a lot with writing these tests, and I still can't get them to " "work. I will return to this in the future.", - allow_module_level=True + allow_module_level=True, ) pytestmark = pytest.mark.asyncio @@ -30,11 +30,12 @@ def asyncio_sleep(): @pytest.fixture() def birthdays_cog( - bot, message_templates_with_age, message_templates_no_age + bot, message_templates_with_age, message_templates_no_age ) -> Birthdays: cog = Birthdays( - bot, message_templates_no_age=message_templates_no_age, - message_templates_with_age=message_templates_with_age + bot, + message_templates_no_age=message_templates_no_age, + message_templates_with_age=message_templates_with_age, ) bot.add_cog(cog) cog.daily_loop.count = 1 @@ -52,14 +53,14 @@ def bot(bot, database) -> commands.Bot: @pytest.fixture() async def main_channel(database, main_guild, make_channel): - channel = make_channel(main_guild, name='the-birthday-channel') + channel = make_channel(main_guild, name="the-birthday-channel") await database.set_guild_birthday_channel(main_guild.id, channel.id) yield channel @pytest.fixture() def main_guild(make_guild): - return make_guild(name='Main Guild') + return make_guild(name="Main Guild") @pytest.fixture() @@ -79,7 +80,7 @@ async def sleep(time: int, *args, **kwargs): # This is used to skip the current loop await asyncio_sleep(0) - with mock.patch('asyncio.sleep') as mock_sleep: + with mock.patch("asyncio.sleep") as mock_sleep: mock_sleep.side_effect = sleep yield mock_sleep @@ -87,15 +88,15 @@ async def sleep(time: int, *args, **kwargs): @pytest.fixture(autouse=True) def patch_database_isinstance(): with mock.patch( - 'sandpiper.user_data.database_sqlite.isinstance', - wraps=isinstance_mock_supported + "sandpiper.user_data.database_sqlite.isinstance", + wraps=isinstance_mock_supported, ): yield @pytest.fixture() def patch_send_birthday_message_hook( - birthdays_cog, patch_asyncio_sleep, patch_datetime_now + birthdays_cog, patch_asyncio_sleep, patch_datetime_now ): """ Adds a hook to Birthdays.send_birthday_message. When it is called, the mock @@ -117,7 +118,7 @@ async def side_effect(*args, **kwargs): await orig_fn(*args, **kwargs) p = mock.patch.object( - birthdays_cog, 'send_birthday_message', side_effect=side_effect + birthdays_cog, "send_birthday_message", side_effect=side_effect ) patchers.append(p) p.start() @@ -130,24 +131,32 @@ async def side_effect(*args, **kwargs): @pytest.fixture(autouse=True) def patch_time( - patch_datetime, patch_localzone_utc, patch_database_isinstance + patch_datetime, patch_localzone_utc, patch_database_isinstance ) -> dt.datetime: pass @pytest.fixture() def run_birthdays_cog( - add_user_to_guild, bot, main_channel, main_guild, - patch_asyncio_sleep, patch_datetime_now, - patch_send_birthday_message_hook, run_daily_loop_once + add_user_to_guild, + bot, + main_channel, + main_guild, + patch_asyncio_sleep, + patch_datetime_now, + patch_send_birthday_message_hook, + run_daily_loop_once, ): async def f( - user: discord.User, birthday: dt.date, - now_when_scheduling: dt.datetime, now_when_sending: dt.datetime, - should_send=True, tz: TimezoneType = pytz.utc + user: discord.User, + birthday: dt.date, + now_when_scheduling: dt.datetime, + now_when_sending: dt.datetime, + should_send=True, + tz: TimezoneType = pytz.utc, ) -> Optional[str]: # Add bot to main_guild - add_user_to_guild(main_guild.id, bot.user.id, 'Bot') + add_user_to_guild(main_guild.id, bot.user.id, "Bot") # Set datetime.now before scheduling patch_datetime_now(now_when_scheduling) # Add hook to change datetime.now when the message is about to be @@ -160,20 +169,22 @@ async def f( # Ensure a message was sent to main_channel main_channel.send.assert_called_once() - birthday_this_year = tz.localize(dt.datetime( - now_when_scheduling.year, birthday.month, birthday.day, 0, 0 - )) + birthday_this_year = tz.localize( + dt.datetime( + now_when_scheduling.year, birthday.month, birthday.day, 0, 0 + ) + ) now_when_scheduling = pytz.utc.localize(now_when_scheduling) now_when_sending = pytz.utc.localize(now_when_sending) if birthday_this_year >= now_when_scheduling: # Assert send_birthday_message slept until the birthday midnight - time_delta = (now_when_sending - now_when_scheduling) + time_delta = now_when_sending - now_when_scheduling else: # Assert send_birthday_message slept a negative amount of time # since the birthday midnight occurred before the scheduling # time - time_delta = (birthday_this_year - now_when_scheduling) + time_delta = birthday_this_year - now_when_scheduling time_delta = time_delta.total_seconds() patch_asyncio_sleep.assert_called_with(time_delta) @@ -194,24 +205,25 @@ async def f(): await birthdays_cog.daily_loop.get_task() # Wait for the birthday sending task to finish await asyncio.gather(*birthdays_cog.tasks.values()) + return f @pytest.fixture() def user_factory(add_user_to_guild, database, make_user, new_id): async def f( - *, - guild: Optional[discord.Guild] = None, - display_name: Optional[str] = None, - name: Optional[str] = None, - pronouns: Optional[str] = None, - birthday: Optional[dt.date] = None, - timezone: Optional[TimezoneType] = None, - p_name: PrivacyType = PrivacyType.PUBLIC, - p_pronouns: PrivacyType = PrivacyType.PUBLIC, - p_birthday: PrivacyType = PrivacyType.PUBLIC, - p_age: PrivacyType = PrivacyType.PUBLIC, - p_timezone: PrivacyType = PrivacyType.PUBLIC, + *, + guild: Optional[discord.Guild] = None, + display_name: Optional[str] = None, + name: Optional[str] = None, + pronouns: Optional[str] = None, + birthday: Optional[dt.date] = None, + timezone: Optional[TimezoneType] = None, + p_name: PrivacyType = PrivacyType.PUBLIC, + p_pronouns: PrivacyType = PrivacyType.PUBLIC, + p_birthday: PrivacyType = PrivacyType.PUBLIC, + p_age: PrivacyType = PrivacyType.PUBLIC, + p_timezone: PrivacyType = PrivacyType.PUBLIC, ) -> discord.User: uid = new_id() @@ -238,223 +250,162 @@ async def f( class TestBirthdayInFuture: - - async def test_0_minutes( - self, main_guild, run_birthdays_cog, user_factory - ): + async def test_0_minutes(self, main_guild, run_birthdays_cog, user_factory): bday = dt.date(2000, 2, 14) user = await user_factory( - guild=main_guild, - birthday=bday, - timezone=pytz.timezone('UTC') + guild=main_guild, birthday=bday, timezone=pytz.timezone("UTC") ) msg = await run_birthdays_cog( - user, bday, + user, + bday, now_when_scheduling=dt.datetime(2020, 2, 14, 0, 0), - now_when_sending=dt.datetime(2020, 2, 14, 0, 0) - ) - assert_in( - msg, "name=Some member", "they=they", "age=20", f"ping=<@{user.id}>" + now_when_sending=dt.datetime(2020, 2, 14, 0, 0), ) + assert_in(msg, "name=Some member", "they=they", "age=20", f"ping=<@{user.id}>") - async def test_15_minutes( - self, main_guild, run_birthdays_cog, user_factory - ): + async def test_15_minutes(self, main_guild, run_birthdays_cog, user_factory): bday = dt.date(2000, 2, 14) user = await user_factory( - guild=main_guild, - birthday=bday, - timezone=pytz.timezone('UTC') + guild=main_guild, birthday=bday, timezone=pytz.timezone("UTC") ) msg = await run_birthdays_cog( - user, bday, + user, + bday, now_when_scheduling=dt.datetime(2020, 2, 13, 23, 45), - now_when_sending=dt.datetime(2020, 2, 14, 0, 0) - ) - assert_in( - msg, "name=Some member", "they=they", "age=20", f"ping=<@{user.id}>" + now_when_sending=dt.datetime(2020, 2, 14, 0, 0), ) + assert_in(msg, "name=Some member", "they=they", "age=20", f"ping=<@{user.id}>") async def test_23_hours_59_minutes( - self, main_guild, run_birthdays_cog, user_factory + self, main_guild, run_birthdays_cog, user_factory ): bday = dt.date(2000, 2, 14) user = await user_factory( - guild=main_guild, - birthday=bday, - timezone=pytz.timezone('UTC') + guild=main_guild, birthday=bday, timezone=pytz.timezone("UTC") ) msg = await run_birthdays_cog( - user, bday, + user, + bday, now_when_scheduling=dt.datetime(2020, 2, 13, 0, 1), - now_when_sending=dt.datetime(2020, 2, 14, 0, 0) - ) - assert_in( - msg, "name=Some member", "they=they", "age=20", f"ping=<@{user.id}>" + now_when_sending=dt.datetime(2020, 2, 14, 0, 0), ) + assert_in(msg, "name=Some member", "they=they", "age=20", f"ping=<@{user.id}>") async def test_24_hours_should_not_send( - self, main_guild, run_birthdays_cog, user_factory + self, main_guild, run_birthdays_cog, user_factory ): bday = dt.date(2000, 2, 14) user = await user_factory( - guild=main_guild, - birthday=bday, - timezone=pytz.timezone('UTC') + guild=main_guild, birthday=bday, timezone=pytz.timezone("UTC") ) await run_birthdays_cog( - user, bday, + user, + bday, now_when_scheduling=dt.datetime(2020, 2, 13, 0, 0), now_when_sending=dt.datetime(2020, 2, 14, 0, 0), - should_send=False + should_send=False, ) class TestBirthdayInPast: - - async def test_1_minute_UTC( - self, main_guild, run_birthdays_cog, user_factory - ): + async def test_1_minute_UTC(self, main_guild, run_birthdays_cog, user_factory): bday = dt.date(2000, 2, 14) now = dt.datetime(2020, 2, 14, 0, 1) user = await user_factory( - guild=main_guild, - birthday=bday, - timezone=pytz.timezone('UTC') + guild=main_guild, birthday=bday, timezone=pytz.timezone("UTC") ) msg = await run_birthdays_cog( - user, bday, + user, + bday, now_when_scheduling=now, now_when_sending=now, ) - assert_in( - msg, "name=Some member", "they=they", "age=20", f"ping=<@{user.id}>" - ) + assert_in(msg, "name=Some member", "they=they", "age=20", f"ping=<@{user.id}>") async def test_23_hours_59_minutes_UTC( - self, main_guild, run_birthdays_cog, user_factory + self, main_guild, run_birthdays_cog, user_factory ): bday = dt.date(2000, 2, 14) now = dt.datetime(2020, 2, 14, 23, 59) user = await user_factory( - guild=main_guild, - birthday=bday, - timezone=pytz.timezone('UTC') + guild=main_guild, birthday=bday, timezone=pytz.timezone("UTC") ) msg = await run_birthdays_cog( - user, bday, + user, + bday, now_when_scheduling=now, now_when_sending=now, ) - assert_in( - msg, "name=Some member", "they=they", "age=20", f"ping=<@{user.id}>" - ) + assert_in(msg, "name=Some member", "they=they", "age=20", f"ping=<@{user.id}>") - async def test_24_hours_UTC( - self, main_guild, run_birthdays_cog, user_factory - ): + async def test_24_hours_UTC(self, main_guild, run_birthdays_cog, user_factory): bday = dt.date(2000, 2, 14) now = dt.datetime(2020, 2, 15, 0, 0) user = await user_factory( - guild=main_guild, - birthday=bday, - timezone=pytz.timezone('UTC') + guild=main_guild, birthday=bday, timezone=pytz.timezone("UTC") ) await run_birthdays_cog( - user, bday, - now_when_scheduling=now, - now_when_sending=now, - should_send=False + user, bday, now_when_scheduling=now, now_when_sending=now, should_send=False ) async def test_23_hours_59_minutes_new_york( - self, main_guild, run_birthdays_cog, user_factory + self, main_guild, run_birthdays_cog, user_factory ): bday = dt.date(2000, 2, 14) - tz = pytz.timezone('America/New_York') + tz = pytz.timezone("America/New_York") # 5 hours behind now = dt.datetime(2020, 2, 15, 4, 59) - user = await user_factory( - guild=main_guild, - birthday=bday, - timezone=tz - ) + user = await user_factory(guild=main_guild, birthday=bday, timezone=tz) msg = await run_birthdays_cog( - user, bday, - now_when_scheduling=now, - now_when_sending=now, - tz=tz - ) - assert_in( - msg, "name=Some member", "they=they", "age=20", f"ping=<@{user.id}>" + user, bday, now_when_scheduling=now, now_when_sending=now, tz=tz ) + assert_in(msg, "name=Some member", "they=they", "age=20", f"ping=<@{user.id}>") - async def test_24_hours_new_york( - self, main_guild, run_birthdays_cog, user_factory - ): + async def test_24_hours_new_york(self, main_guild, run_birthdays_cog, user_factory): bday = dt.date(2000, 2, 14) - tz = pytz.timezone('America/New_York') + tz = pytz.timezone("America/New_York") # 5 hours behind now = dt.datetime(2020, 2, 15, 5, 0) - user = await user_factory( - guild=main_guild, - birthday=bday, - timezone=tz - ) + user = await user_factory(guild=main_guild, birthday=bday, timezone=tz) await run_birthdays_cog( - user, bday, + user, + bday, now_when_scheduling=now, now_when_sending=now, should_send=False, - tz=tz + tz=tz, ) class TestTimezones: - - async def test_new_york( - self, main_guild, run_birthdays_cog, user_factory - ): + async def test_new_york(self, main_guild, run_birthdays_cog, user_factory): bday = dt.date(2000, 2, 14) - tz = pytz.timezone('America/New_York') - user = await user_factory( - guild=main_guild, - birthday=bday, - timezone=tz - ) + tz = pytz.timezone("America/New_York") + user = await user_factory(guild=main_guild, birthday=bday, timezone=tz) msg = await run_birthdays_cog( - user, bday, + user, + bday, now_when_scheduling=dt.datetime(2020, 2, 14, 0, 0), now_when_sending=dt.datetime(2020, 2, 14, 5, 0), - tz=tz - ) - assert_in( - msg, "name=Some member", "they=they", "age=20", f"ping=<@{user.id}>" + tz=tz, ) + assert_in(msg, "name=Some member", "they=they", "age=20", f"ping=<@{user.id}>") - async def test_dubai( - self, main_guild, run_birthdays_cog, user_factory - ): + async def test_dubai(self, main_guild, run_birthdays_cog, user_factory): bday = dt.date(2000, 2, 14) - tz = pytz.timezone('Asia/Dubai') - user = await user_factory( - guild=main_guild, - birthday=bday, - timezone=tz - ) + tz = pytz.timezone("Asia/Dubai") + user = await user_factory(guild=main_guild, birthday=bday, timezone=tz) msg = await run_birthdays_cog( - user, bday, + user, + bday, now_when_scheduling=dt.datetime(2020, 2, 14, 0, 0), now_when_sending=dt.datetime(2020, 2, 13, 21, 0), - tz=tz - ) - assert_in( - msg, "name=Some member", "they=they", "age=20", f"ping=<@{user.id}>" + tz=tz, ) + assert_in(msg, "name=Some member", "they=they", "age=20", f"ping=<@{user.id}>") class TestPrivacies: - @pytest.fixture() def birthday(self): return dt.date(2000, 2, 14) @@ -472,8 +423,13 @@ async def main_user(self, birthday, main_guild, user_factory): ) async def test_preferred_name_private( - self, birthday, birthday_midnight, database, main_guild, main_user, - run_birthdays_cog + self, + birthday, + birthday_midnight, + database, + main_guild, + main_user, + run_birthdays_cog, ): """Use their server nickname""" display_name = "Display name" @@ -488,8 +444,13 @@ async def test_preferred_name_private( assert_in(msg, f"name={display_name}") async def test_preferred_name_public( - self, birthday, birthday_midnight, database, main_guild, main_user, - run_birthdays_cog + self, + birthday, + birthday_midnight, + database, + main_guild, + main_user, + run_birthdays_cog, ): """Use their preferred name""" display_name = "Display name" @@ -504,8 +465,7 @@ async def test_preferred_name_public( assert_in(msg, f"name={preferred_name}") async def test_pronouns_private( - self, birthday, birthday_midnight, database, main_user, - run_birthdays_cog + self, birthday, birthday_midnight, database, main_user, run_birthdays_cog ): """Use they/them""" await database.set_pronouns(main_user.id, "She/her") @@ -517,8 +477,7 @@ async def test_pronouns_private( assert_in(msg, "they=they") async def test_pronouns_public( - self, birthday, birthday_midnight, database, main_user, - run_birthdays_cog + self, birthday, birthday_midnight, database, main_user, run_birthdays_cog ): """Use their pronouns""" await database.set_pronouns(main_user.id, "She/her") @@ -530,33 +489,27 @@ async def test_pronouns_public( assert_in(msg, "they=she") async def test_birthday_private( - self, birthday, birthday_midnight, database, main_user, - run_birthdays_cog + self, birthday, birthday_midnight, database, main_user, run_birthdays_cog ): """Don't schedule OR send the birthday""" await database.set_privacy_birthday(main_user.id, PrivacyType.PRIVATE) msg = await run_birthdays_cog( - main_user, birthday, birthday_midnight, birthday_midnight, - should_send=False + main_user, birthday, birthday_midnight, birthday_midnight, should_send=False ) async def test_birthday_public( - self, birthday, birthday_midnight, database, main_user, - run_birthdays_cog + self, birthday, birthday_midnight, database, main_user, run_birthdays_cog ): """Schedule and send the birthday""" await database.set_privacy_birthday(main_user.id, PrivacyType.PUBLIC) msg = await run_birthdays_cog( - main_user, birthday, birthday_midnight, birthday_midnight, - should_send=True + main_user, birthday, birthday_midnight, birthday_midnight, should_send=True ) - async def test_age_private( - self, birthday, birthday_midnight, database, main_user, - run_birthdays_cog + self, birthday, birthday_midnight, database, main_user, run_birthdays_cog ): """Pick a template without age""" await database.set_privacy_age(main_user.id, PrivacyType.PRIVATE) @@ -567,8 +520,7 @@ async def test_age_private( assert "age=" not in msg async def test_age_public( - self, birthday, birthday_midnight, database, main_user, - run_birthdays_cog + self, birthday, birthday_midnight, database, main_user, run_birthdays_cog ): """Pick a template with age""" await database.set_privacy_age(main_user.id, PrivacyType.PUBLIC) @@ -579,11 +531,10 @@ async def test_age_public( assert_in(msg, "age=20") async def test_timezone_private( - self, birthday, birthday_midnight, database, main_user, - run_birthdays_cog + self, birthday, birthday_midnight, database, main_user, run_birthdays_cog ): """Schedule using UTC""" - await database.set_timezone(main_user.id, pytz.timezone('America/New_York')) + await database.set_timezone(main_user.id, pytz.timezone("America/New_York")) await database.set_privacy_timezone(main_user.id, PrivacyType.PRIVATE) msg = await run_birthdays_cog( @@ -591,17 +542,18 @@ async def test_timezone_private( ) async def test_timezone_public( - self, birthday, birthday_midnight, database, main_user, - run_birthdays_cog + self, birthday, birthday_midnight, database, main_user, run_birthdays_cog ): """Schedule using their timezone""" - await database.set_timezone(main_user.id, pytz.timezone('America/New_York')) + await database.set_timezone(main_user.id, pytz.timezone("America/New_York")) await database.set_privacy_timezone(main_user.id, PrivacyType.PUBLIC) # Should send at 5:00 UTC (0:00 New York) msg = await run_birthdays_cog( - main_user, birthday, birthday_midnight, - birthday_midnight + dt.timedelta(hours=5) + main_user, + birthday, + birthday_midnight, + birthday_midnight + dt.timedelta(hours=5), ) diff --git a/sandpiper/tests/test_common_time.py b/sandpiper/tests/test_common_time.py index 6301acb..6b0fd84 100644 --- a/sandpiper/tests/test_common_time.py +++ b/sandpiper/tests/test_common_time.py @@ -17,24 +17,18 @@ def setUp(self): def patch_time(self): # Patch datetime to use a static datetime - patcher = mock.patch('sandpiper.common.time.dt', autospec=True) + patcher = mock.patch("sandpiper.common.time.dt", autospec=True) mock_datetime = patcher.start() self.addCleanup(patcher.stop) mock_datetime.datetime.now.return_value = self.STATIC_NOW - mock_datetime.datetime.side_effect = ( - lambda *a, **kw: dt.datetime(*a, **kw) - ) - mock_datetime.date.side_effect = ( - lambda *a, **kw: dt.date(*a, **kw) - ) - mock_datetime.time.side_effect = ( - lambda *a, **kw: dt.time(*a, **kw) - ) + mock_datetime.datetime.side_effect = lambda *a, **kw: dt.datetime(*a, **kw) + mock_datetime.date.side_effect = lambda *a, **kw: dt.date(*a, **kw) + mock_datetime.time.side_effect = lambda *a, **kw: dt.time(*a, **kw) # Patch localzone to use UTC patcher = mock.patch( - 'sandpiper.common.time.tzlocal.get_localzone', autospec=True + "sandpiper.common.time.tzlocal.get_localzone", autospec=True ) mock_localzone = patcher.start() self.addCleanup(patcher.stop) @@ -42,8 +36,11 @@ def patch_time(self): mock_localzone.return_value = pytz.UTC def assert_time( - self, timestr: str, time: Optional[dt.time], tz: Optional[str], - definitely_time: Optional[bool] + self, + timestr: str, + time: Optional[dt.time], + tz: Optional[str], + definitely_time: Optional[bool], ): __tracebackhide__ = True if time is None: @@ -65,169 +62,85 @@ def assert_time( assert definitely_time_parsed is definitely_time def test_hour(self): - self.assert_time( - '5', dt.time(5, 0), None, False - ) - self.assert_time( - '13', dt.time(13, 0), None, False - ) - self.assert_time( - '5am', dt.time(5, 0), None, True - ) - self.assert_time( - '5 AM', dt.time(5, 0), None, True - ) - self.assert_time( - '5pm', dt.time(17, 0), None, True - ) - self.assert_time( - '5 PM', dt.time(17, 0), None, True - ) + self.assert_time("5", dt.time(5, 0), None, False) + self.assert_time("13", dt.time(13, 0), None, False) + self.assert_time("5am", dt.time(5, 0), None, True) + self.assert_time("5 AM", dt.time(5, 0), None, True) + self.assert_time("5pm", dt.time(17, 0), None, True) + self.assert_time("5 PM", dt.time(17, 0), None, True) def test_hour_invalid_times(self): - self.assert_time( - '24', None, None, False - ) + self.assert_time("24", None, None, False) def test_colon(self): - self.assert_time( - '5:30', dt.time(5, 30), None, True - ) - self.assert_time( - '05:30', dt.time(5, 30), None, True - ) - self.assert_time( - '5:30am', dt.time(5, 30), None, True - ) - self.assert_time( - '5:30 AM', dt.time(5, 30), None, True - ) - self.assert_time( - '5:30pm', dt.time(17, 30), None, True - ) - self.assert_time( - '5:30 PM', dt.time(17, 30), None, True - ) + self.assert_time("5:30", dt.time(5, 30), None, True) + self.assert_time("05:30", dt.time(5, 30), None, True) + self.assert_time("5:30am", dt.time(5, 30), None, True) + self.assert_time("5:30 AM", dt.time(5, 30), None, True) + self.assert_time("5:30pm", dt.time(17, 30), None, True) + self.assert_time("5:30 PM", dt.time(17, 30), None, True) def test_colon_not_real_times(self): - self.assert_time( - '24:00', None, None, None - ) - self.assert_time( - '1:60', None, None, None - ) + self.assert_time("24:00", None, None, None) + self.assert_time("1:60", None, None, None) def test_no_colon(self): - self.assert_time( - '05', dt.time(5, 0), None, False - ) - self.assert_time( - '530', dt.time(5, 30), None, False - ) - self.assert_time( - '0530', dt.time(5, 30), None, False - ) - self.assert_time( - '0530am', dt.time(5, 30), None, True - ) - self.assert_time( - '530am', dt.time(5, 30), None, True - ) - self.assert_time( - '530 AM', dt.time(5, 30), None, True - ) - self.assert_time( - '530pm', dt.time(17, 30), None, True - ) - self.assert_time( - '530 PM', dt.time(17, 30), None, True - ) + self.assert_time("05", dt.time(5, 0), None, False) + self.assert_time("530", dt.time(5, 30), None, False) + self.assert_time("0530", dt.time(5, 30), None, False) + self.assert_time("0530am", dt.time(5, 30), None, True) + self.assert_time("530am", dt.time(5, 30), None, True) + self.assert_time("530 AM", dt.time(5, 30), None, True) + self.assert_time("530pm", dt.time(17, 30), None, True) + self.assert_time("530 PM", dt.time(17, 30), None, True) def test_no_colon_invalid_times(self): - self.assert_time( - '53', None, None, None - ) - self.assert_time( - '2400', None, None, None - ) - self.assert_time( - '160', None, None, None - ) + self.assert_time("53", None, None, None) + self.assert_time("2400", None, None, None) + self.assert_time("160", None, None, None) def test_hour_timezone(self): - self.assert_time( - '5am new york', dt.time(5, 0), 'new york', True - ) - self.assert_time( - '5 AM new york', dt.time(5, 0), 'new york', True - ) - self.assert_time( - '5pm new york', dt.time(17, 0), 'new york', True - ) - self.assert_time( - '5 PM new york', dt.time(17, 0), 'new york', True - ) + self.assert_time("5am new york", dt.time(5, 0), "new york", True) + self.assert_time("5 AM new york", dt.time(5, 0), "new york", True) + self.assert_time("5pm new york", dt.time(17, 0), "new york", True) + self.assert_time("5 PM new york", dt.time(17, 0), "new york", True) def test_hour_timezone_ambiguous(self): # This is ambiguous with a unit definition - self.assert_time( - '5 new york', None, None, None - ) + self.assert_time("5 new york", None, None, None) def test_colon_timezone(self): - self.assert_time( - '5:30 new york', dt.time(5, 30), 'new york', True - ) - self.assert_time( - '5:30am new york', dt.time(5, 30), 'new york', True - ) - self.assert_time( - '5:30 AM new york', dt.time(5, 30), 'new york', True - ) - self.assert_time( - '5:30pm new york', dt.time(17, 30), 'new york', True - ) - self.assert_time( - '5:30 PM new york', dt.time(17, 30), 'new york', True - ) + self.assert_time("5:30 new york", dt.time(5, 30), "new york", True) + self.assert_time("5:30am new york", dt.time(5, 30), "new york", True) + self.assert_time("5:30 AM new york", dt.time(5, 30), "new york", True) + self.assert_time("5:30pm new york", dt.time(17, 30), "new york", True) + self.assert_time("5:30 PM new york", dt.time(17, 30), "new york", True) def test_no_colon_timezone(self): - self.assert_time( - '530am new york', dt.time(5, 30), 'new york', True - ) - self.assert_time( - '530 AM new york', dt.time(5, 30), 'new york', True - ) - self.assert_time( - '530pm new york', dt.time(17, 30), 'new york', True - ) - self.assert_time( - '530 PM new york', dt.time(17, 30), 'new york', True - ) + self.assert_time("530am new york", dt.time(5, 30), "new york", True) + self.assert_time("530 AM new york", dt.time(5, 30), "new york", True) + self.assert_time("530pm new york", dt.time(17, 30), "new york", True) + self.assert_time("530 PM new york", dt.time(17, 30), "new york", True) def test_no_colon_timezone_ambiguous(self): # Ambiguous with a unit definition - self.assert_time( - '530 new york', None, None, None - ) - self.assert_time( - '0530 new york', None, None, None - ) + self.assert_time("530 new york", None, None, None) + self.assert_time("0530 new york", None, None, None) def test_random_string(self): - self.assert_time('hello', None, None, None) + self.assert_time("hello", None, None, None) def test_keywords_basic(self): - self.assert_time('now', self.STATIC_NOW.time(), 'UTC', True) - self.assert_time('noon', dt.time(12, 0), None, True) - self.assert_time('midnight', dt.time(0, 0), None, True) + self.assert_time("now", self.STATIC_NOW.time(), "UTC", True) + self.assert_time("noon", dt.time(12, 0), None, True) + self.assert_time("midnight", dt.time(0, 0), None, True) def test_keywords_with_am(self): - self.assert_time('now am', self.STATIC_NOW.time(), 'UTC', True) - self.assert_time('noon am', dt.time(12, 0), 'am', True) - self.assert_time('midnight am', dt.time(0, 0), 'am', True) + self.assert_time("now am", self.STATIC_NOW.time(), "UTC", True) + self.assert_time("noon am", dt.time(12, 0), "am", True) + self.assert_time("midnight am", dt.time(0, 0), "am", True) def test_keywords_timezone(self): - self.assert_time('now new york', self.STATIC_NOW.time(), 'UTC', True) - self.assert_time('noon new york', dt.time(12, 0), 'new york', True) - self.assert_time('midnight new york', dt.time(0, 0), 'new york', True) + self.assert_time("now new york", self.STATIC_NOW.time(), "UTC", True) + self.assert_time("noon new york", dt.time(12, 0), "new york", True) + self.assert_time("midnight new york", dt.time(0, 0), "new york", True) diff --git a/sandpiper/tests/test_conversion.py b/sandpiper/tests/test_conversion.py index a915449..fb11173 100644 --- a/sandpiper/tests/test_conversion.py +++ b/sandpiper/tests/test_conversion.py @@ -26,11 +26,11 @@ def bot(bot) -> commands.Bot: class TestImperialShorthandRegex: - @staticmethod def _assert( - test_str: str, foot: Optional[Union[int, float]], - inch: Optional[Union[int, float]] + test_str: str, + foot: Optional[Union[int, float]], + inch: Optional[Union[int, float]], ): __tracebackhide__ = True match = imperial_shorthand_pattern.match(test_str) @@ -38,8 +38,8 @@ def _assert( if foot is None and inch is None: assert match is None else: - match_foot = match['foot'] - match_inch = match['inch'] + match_foot = match["foot"] + match_inch = match["inch"] # Coerce the matched strings into their expected types if foot is not None and match_foot is not None: match_foot = type(foot)(match_foot) @@ -56,10 +56,10 @@ def test_int_feet(self): self._assert(" 5'", None, None) def test_int_inches(self): - self._assert("1\"", None, 1) - self._assert("23\"", None, 23) - self._assert("-4\"", None, None) - self._assert(" 5\"", None, None) + self._assert('1"', None, 1) + self._assert('23"', None, 23) + self._assert('-4"', None, None) + self._assert(' 5"', None, None) def test_int_both(self): self._assert("1'2\"", 1, 2) @@ -75,9 +75,9 @@ def test_decimal_feet(self): self._assert(".4'", None, None) def test_decimal_inches(self): - self._assert("1.2\"", None, 1.2) - self._assert("0.3\"", None, 0.3) - self._assert(".4\"", None, 0.4) + self._assert('1.2"', None, 1.2) + self._assert('0.3"', None, 0.3) + self._assert('.4"', None, 0.4) def test_decimal_both(self): self._assert("1' 2.3\"", 1, 2.3) @@ -91,11 +91,8 @@ def test_other_garbage(self): class TestConversionStringRegex(unittest.TestCase): - @staticmethod - def _assert( - in_: str, quantity: Optional[str], out_unit: Optional[str] - ): + def _assert(in_: str, quantity: Optional[str], out_unit: Optional[str]): __tracebackhide__ = True match = conversion_pattern.match(in_) @@ -105,13 +102,11 @@ def _assert( assert match is None, "Pattern matched when it shouldn't have" return - assert match['quantity'] == quantity, ( - f"Matched quantity {match['quantity']} does not equal input " - f"{quantity}" + assert match["quantity"] == quantity, ( + f"Matched quantity {match['quantity']} does not equal input " f"{quantity}" ) - assert match['out_unit'] == out_unit, ( - f"Matched out unit {match['out_unit']} does not equal input " - f"{out_unit}" + assert match["out_unit"] == out_unit, ( + f"Matched out unit {match['out_unit']} does not equal input " f"{out_unit}" ) @staticmethod @@ -120,27 +115,26 @@ def assert_findall_len(in_: str, len_: int): assert len(conversion_pattern.findall(in_)) == len_ def test_simple(self): - self._assert('{5pm}', '5pm', None) - self._assert('{ 5 ft }', '5 ft', None) + self._assert("{5pm}", "5pm", None) + self._assert("{ 5 ft }", "5 ft", None) def test_specifier_with_out_unit(self): - self._assert('{5ft>m}', '5ft', 'm') - self._assert('{5ft > m}', '5ft', 'm') - self._assert('{5 ft > m}', '5 ft', 'm') - self._assert('{5 km > mi}', '5 km', 'mi') - self._assert('{ 5pm > new york}', '5pm', 'new york') - self._assert('{ 5pm > new york }', '5pm', 'new york') + self._assert("{5ft>m}", "5ft", "m") + self._assert("{5ft > m}", "5ft", "m") + self._assert("{5 ft > m}", "5 ft", "m") + self._assert("{5 km > mi}", "5 km", "mi") + self._assert("{ 5pm > new york}", "5pm", "new york") + self._assert("{ 5pm > new york }", "5pm", "new york") def test_specifier_no_out_unit(self): - self._assert('{5pm>}', None, None) - self._assert('{5pm >}', None, None) - self._assert('{5pm> }', None, None) - self._assert('{5pm > }', None, None) - self._assert('{8:00 > }', None, None) + self._assert("{5pm>}", None, None) + self._assert("{5pm >}", None, None) + self._assert("{5pm> }", None, None) + self._assert("{5pm > }", None, None) + self._assert("{8:00 > }", None, None) class TestUnitConversion: - @staticmethod def _assert(contents: list[str], *substrings: str): assert len(contents) == 1 @@ -150,29 +144,27 @@ async def test_two_way_temperature(self, dispatch_msg_get_contents): contents = await dispatch_msg_get_contents( "guys it's {30f} outside today, I'm so cold..." ) - self._assert(contents, '30.00 °F', '-1.11 °C') + self._assert(contents, "30.00 °F", "-1.11 °C") contents = await dispatch_msg_get_contents( "guys it's {-1.11c} outside today, I'm so cold..." ) - self._assert(contents, '30.00 °F', '-1.11 °C') + self._assert(contents, "30.00 °F", "-1.11 °C") async def test_two_way_mass(self, dispatch_msg_get_contents): contents = await dispatch_msg_get_contents( "I've been working out a lot lately and I've already lost {2 kg}!!" ) - self._assert(contents, '2.00 kg', '4.41 lb') + self._assert(contents, "2.00 kg", "4.41 lb") contents = await dispatch_msg_get_contents( "I've been working out a lot lately and I've already lost {4.41 lb}!!" ) - self._assert(contents, '2.00 kg', '4.41 lb') + self._assert(contents, "2.00 kg", "4.41 lb") async def test_two_way_distance(self, dispatch_msg_get_contents): - contents = await dispatch_msg_get_contents( - "Is that a {33ft} boat, TJ?" - ) - self._assert(contents, '33.00 ft', '10.06 m') + contents = await dispatch_msg_get_contents("Is that a {33ft} boat, TJ?") + self._assert(contents, "33.00 ft", "10.06 m") async def test_two_way_distance_multi(self, dispatch_msg_get_contents): contents = await dispatch_msg_get_contents( @@ -180,99 +172,85 @@ async def test_two_way_distance_multi(self, dispatch_msg_get_contents): "{2.5km} away, so he and I are gonna meet up and drive over to " "Lou." ) - self._assert(contents, '9.32 mi', '15.00 km', '1.55 mi', '2.50 km') + self._assert(contents, "9.32 mi", "15.00 km", "1.55 mi", "2.50 km") async def test_one_way_distance(self, dispatch_msg_get_contents): contents = await dispatch_msg_get_contents( "I was only {4 yards} away in geoguessr!!" ) - self._assert(contents, '4.00 yd', '3.66 m') + self._assert(contents, "4.00 yd", "3.66 m") async def test_one_way_mass(self, dispatch_msg_get_contents): contents = await dispatch_msg_get_contents( "I weigh around {9.3 stone}. whatever that means..." ) - self._assert(contents, '9.30 stone', '59.06 kg') + self._assert(contents, "9.30 stone", "59.06 kg") async def test_one_way_temperature(self, dispatch_msg_get_contents): - contents = await dispatch_msg_get_contents( - "any scientists in the chat?? {0 K}" - ) - self._assert(contents, '0.00 K', '-273.15 °C') + contents = await dispatch_msg_get_contents("any scientists in the chat?? {0 K}") + self._assert(contents, "0.00 K", "-273.15 °C") async def test_imperial_shorthand(self, dispatch_msg_get_contents): contents = await dispatch_msg_get_contents( "I think Jason is like {6' 2\"} tall" ) - self._assert(contents, '6.17 ft', '1.88 m') + self._assert(contents, "6.17 ft", "1.88 m") - contents = await dispatch_msg_get_contents( - "I'm only {5'11\"} though!" - ) - self._assert(contents, '5.92 ft', '1.80 m') + contents = await dispatch_msg_get_contents("I'm only {5'11\"} though!") + self._assert(contents, "5.92 ft", "1.80 m") async def test_explicit(self, dispatch_msg_get_contents): contents = await dispatch_msg_get_contents( "{-5 f > kelvin} it's too late for apologies, imperial system" ) - self._assert(contents, '-5.00 °F', '252.59 K') + self._assert(contents, "-5.00 °F", "252.59 K") - contents = await dispatch_msg_get_contents( - "how much is {9.3 stone > lbs}" - ) - self._assert(contents, '9.30 stone', '130.20 lb') + contents = await dispatch_msg_get_contents("how much is {9.3 stone > lbs}") + self._assert(contents, "9.30 stone", "130.20 lb") contents = await dispatch_msg_get_contents( "bc this is totally useful.. {5 mi > ft}" ) - self._assert(contents, '5.00 mi', '26400.00 ft') + self._assert(contents, "5.00 mi", "26400.00 ft") contents = await dispatch_msg_get_contents( "can't believe {3.000 hogshead > gallon} is even real" ) - self._assert(contents, '3.00 hogshead', '189.00 gal') + self._assert(contents, "3.00 hogshead", "189.00 gal") contents = await dispatch_msg_get_contents( "ma'am you forgot your spaces {5ft>yd}" ) - self._assert(contents, '5.00 ft', '1.67 yd') + self._assert(contents, "5.00 ft", "1.67 yd") async def test_unit_math(self, dispatch_msg_get_contents): contents = await dispatch_msg_get_contents( "I'm measuring wood planks, I need {2.3 ft + 5 in}" ) - self._assert(contents, '2.72 ft', '0.83 m') + self._assert(contents, "2.72 ft", "0.83 m") contents = await dispatch_msg_get_contents( "oops, I need that in inches {2.3 ft + 5 in > in}" ) - self._assert(contents, '2.72 ft', '32.60 in') + self._assert(contents, "2.72 ft", "32.60 in") contents = await dispatch_msg_get_contents( "my two favorite songs are a total of {5min+27s + 4min+34s > s}" ) - self._assert(contents, '10.02 min', '601.00 s') + self._assert(contents, "10.02 min", "601.00 s") async def test_dimensionless_math(self, dispatch_msg_get_contents): - contents = await dispatch_msg_get_contents( - "what's {2 + 7}?" - ) - self._assert(contents, '2 + 7', '9') + contents = await dispatch_msg_get_contents("what's {2 + 7}?") + self._assert(contents, "2 + 7", "9") - contents = await dispatch_msg_get_contents( - "how about {2.5 + 7.8}?" - ) - self._assert(contents, '2.5 + 7.8', '10.3') + contents = await dispatch_msg_get_contents("how about {2.5 + 7.8}?") + self._assert(contents, "2.5 + 7.8", "10.3") - contents = await dispatch_msg_get_contents( - "and {2 * 7}?" - ) - self._assert(contents, '2 * 7', '14') + contents = await dispatch_msg_get_contents("and {2 * 7}?") + self._assert(contents, "2 * 7", "14") async def test_unknown_unit(self, dispatch_msg_get_embeds): - embeds = await dispatch_msg_get_embeds( - "that's like {12.5 donuts} wide!" - ) + embeds = await dispatch_msg_get_embeds("that's like {12.5 donuts} wide!") assert_error(embeds, 'Unknown unit "donuts"') embeds = await dispatch_msg_get_embeds( @@ -285,19 +263,27 @@ async def test_unmapped_unit(self, dispatch_msg_get_embeds): "{5 hogshead} is a real unit, but not really useful enough to be " "mapped. fun name though" ) - assert_error(embeds, '{5 hogshead > otherunit}') + assert_error(embeds, "{5 hogshead > otherunit}") async def test_bad_conversion_string(self, dispatch_msg): - send = await dispatch_msg("oops I dropped my unit {5 ft >}",) + send = await dispatch_msg( + "oops I dropped my unit {5 ft >}", + ) assert_no_reply(send) - send = await dispatch_msg("oh crap not again {5 ft > }",) + send = await dispatch_msg( + "oh crap not again {5 ft > }", + ) assert_no_reply(send) - send = await dispatch_msg("okay this is just disgusting {5 >}",) + send = await dispatch_msg( + "okay this is just disgusting {5 >}", + ) assert_no_reply(send) - send = await dispatch_msg("dude! {5 > }",) + send = await dispatch_msg( + "dude! {5 > }", + ) assert_no_reply(send) @@ -309,7 +295,7 @@ class TestTimeConversion: @pytest.fixture(autouse=True) def june_1st_2020_932_am( - self, patch_localzone_utc, patch_datetime_now + self, patch_localzone_utc, patch_datetime_now ) -> dt.datetime: yield patch_datetime_now(dt.datetime(2020, 6, 1, 9, 32)) @@ -320,48 +306,43 @@ async def f(timezone: TimezoneType) -> discord.User: await database.set_timezone(user.id, timezone) await database.set_privacy_timezone(user.id, PrivacyType.PUBLIC) return user + yield f @pytest.fixture() def american_tz(self) -> TimezoneType: - yield pytz.timezone('America/New_York') + yield pytz.timezone("America/New_York") @pytest.fixture() def american_now(self, american_tz) -> dt.datetime: yield utc_now().astimezone(american_tz) @pytest.fixture() - async def american_user( - self, american_tz, make_user_with_timezone - ) -> discord.User: + async def american_user(self, american_tz, make_user_with_timezone) -> discord.User: yield await make_user_with_timezone(american_tz) @pytest.fixture() def british_tz(self) -> TimezoneType: - yield pytz.timezone('Europe/London') + yield pytz.timezone("Europe/London") @pytest.fixture() def british_now(self, british_tz) -> dt.datetime: yield utc_now().astimezone(british_tz) @pytest.fixture() - async def british_user( - self, british_tz, make_user_with_timezone - ) -> discord.User: + async def british_user(self, british_tz, make_user_with_timezone) -> discord.User: yield await make_user_with_timezone(british_tz) @pytest.fixture() def dutch_tz(self) -> TimezoneType: - yield pytz.timezone('Europe/Amsterdam') + yield pytz.timezone("Europe/Amsterdam") @pytest.fixture() def dutch_now(self, dutch_tz) -> dt.datetime: yield utc_now().astimezone(dutch_tz) @pytest.fixture() - async def dutch_user( - self, dutch_tz, make_user_with_timezone - ) -> discord.User: + async def dutch_user(self, dutch_tz, make_user_with_timezone) -> discord.User: yield await make_user_with_timezone(dutch_tz) @pytest.fixture(autouse=True) @@ -378,49 +359,43 @@ def _assert(contents: list[str], *patterns: str): # region Get user's timezone async def test_basic_hour_period( - self, message, dutch_user, dispatch_msg_get_contents + self, message, dutch_user, dispatch_msg_get_contents ): message.author = dutch_user - contents = await dispatch_msg_get_contents( - "do you guys wanna play at {9pm}?" - ) + contents = await dispatch_msg_get_contents("do you guys wanna play at {9pm}?") self._assert( contents, - r'Europe/Amsterdam.+9:00 PM', - r'Europe/London.+8:00 PM', - r'America/New_York.+3:00 PM' + r"Europe/Amsterdam.+9:00 PM", + r"Europe/London.+8:00 PM", + r"America/New_York.+3:00 PM", ) async def test_basic_no_colon_period( - self, message, american_user, dispatch_msg_get_contents + self, message, american_user, dispatch_msg_get_contents ): message.author = american_user - contents = await dispatch_msg_get_contents( - "I get off work at {330pm}" - ) + contents = await dispatch_msg_get_contents("I get off work at {330pm}") self._assert( contents, - r'Europe/Amsterdam.+9:30 PM', - r'Europe/London.+8:30 PM', - r'America/New_York.+3:30 PM' + r"Europe/Amsterdam.+9:30 PM", + r"Europe/London.+8:30 PM", + r"America/New_York.+3:30 PM", ) async def test_basic_no_colon( - self, message, american_user, dispatch_msg_get_contents + self, message, american_user, dispatch_msg_get_contents ): message.author = american_user - contents = await dispatch_msg_get_contents( - "In 24-hour time that's {1530}" - ) + contents = await dispatch_msg_get_contents("In 24-hour time that's {1530}") self._assert( contents, - r'Europe/Amsterdam.+9:30 PM', - r'Europe/London.+8:30 PM', - r'America/New_York.+3:30 PM' + r"Europe/Amsterdam.+9:30 PM", + r"Europe/London.+8:30 PM", + r"America/New_York.+3:30 PM", ) async def test_basic_hour_only( - self, message, british_user, dispatch_msg_get_contents + self, message, british_user, dispatch_msg_get_contents ): message.author = british_user contents = await dispatch_msg_get_contents( @@ -429,13 +404,13 @@ async def test_basic_hour_only( ) self._assert( contents, - r'Europe/Amsterdam.+6:00 AM', - r'Europe/London.+5:00 AM', - r'America/New_York.+12:00 AM' + r"Europe/Amsterdam.+6:00 AM", + r"Europe/London.+5:00 AM", + r"America/New_York.+12:00 AM", ) async def test_basic_multiple( - self, message, american_user, dispatch_msg_get_contents + self, message, american_user, dispatch_msg_get_contents ): message.author = american_user contents = await dispatch_msg_get_contents( @@ -443,43 +418,43 @@ async def test_basic_multiple( ) self._assert( contents, - r'Europe/Amsterdam.+8:00 PM.+11:45 PM', - r'Europe/London.+7:00 PM.+10:45 PM', - r'America/New_York.+2:00 PM.+5:45 PM' + r"Europe/Amsterdam.+8:00 PM.+11:45 PM", + r"Europe/London.+7:00 PM.+10:45 PM", + r"America/New_York.+2:00 PM.+5:45 PM", ) - async def test_basic_noon( - self, message, dutch_user, dispatch_msg_get_contents - ): + async def test_basic_noon(self, message, dutch_user, dispatch_msg_get_contents): message.author = dutch_user contents = await dispatch_msg_get_contents( "It's nearly {noon}. Time for lunch!" ) self._assert( contents, - r'Europe/Amsterdam.+12:00 PM', - r'Europe/London.+11:00 AM', - r'America/New_York.+6:00 AM' + r"Europe/Amsterdam.+12:00 PM", + r"Europe/London.+11:00 AM", + r"America/New_York.+6:00 AM", ) async def test_basic_midnight( - self, message, american_user, dispatch_msg_get_contents + self, message, american_user, dispatch_msg_get_contents ): message.author = american_user - contents = await dispatch_msg_get_contents( - "Dude, it's {midnight} :gobed:!" - ) + contents = await dispatch_msg_get_contents("Dude, it's {midnight} :gobed:!") self._assert( contents, - r'Europe/Amsterdam.+6:00 AM', - r'Europe/London.+5:00 AM', - r'America/New_York.+12:00 AM' + r"Europe/Amsterdam.+6:00 AM", + r"Europe/London.+5:00 AM", + r"America/New_York.+12:00 AM", ) async def test_basic_now_british( - self, message, british_user, - american_now, british_now, dutch_now, - dispatch_msg_get_contents + self, + message, + british_user, + american_now, + british_now, + dutch_now, + dispatch_msg_get_contents, ): message.author = british_user contents = await dispatch_msg_get_contents( @@ -487,15 +462,19 @@ async def test_basic_now_british( ) self._assert( contents, - r'Europe/Amsterdam.+' + dutch_now.strftime('%I:%M %p').lstrip('0'), - r'Europe/London.+' + british_now.strftime('%I:%M %p').lstrip('0'), - r'America/New_York.+' + american_now.strftime('%I:%M %p').lstrip('0') + r"Europe/Amsterdam.+" + dutch_now.strftime("%I:%M %p").lstrip("0"), + r"Europe/London.+" + british_now.strftime("%I:%M %p").lstrip("0"), + r"America/New_York.+" + american_now.strftime("%I:%M %p").lstrip("0"), ) async def test_basic_now_american( - self, message, american_user, - american_now, british_now, dutch_now, - dispatch_msg_get_contents + self, + message, + american_user, + american_now, + british_now, + dutch_now, + dispatch_msg_get_contents, ): message.author = american_user contents = await dispatch_msg_get_contents( @@ -503,9 +482,9 @@ async def test_basic_now_american( ) self._assert( contents, - r'Europe/Amsterdam.+' + dutch_now.strftime('%I:%M %p').lstrip('0'), - r'Europe/London.+' + british_now.strftime('%I:%M %p').lstrip('0'), - r'America/New_York.+' + american_now.strftime('%I:%M %p').lstrip('0') + r"Europe/Amsterdam.+" + dutch_now.strftime("%I:%M %p").lstrip("0"), + r"Europe/London.+" + british_now.strftime("%I:%M %p").lstrip("0"), + r"America/New_York.+" + american_now.strftime("%I:%M %p").lstrip("0"), ) # endregion @@ -513,8 +492,12 @@ async def test_basic_now_american( # region Specified input timezone async def test_in_basic( - self, message, american_user, british_user, dutch_user, - dispatch_msg_get_contents + self, + message, + american_user, + british_user, + dutch_user, + dispatch_msg_get_contents, ): message.author = american_user contents = await dispatch_msg_get_contents( @@ -522,50 +505,43 @@ async def test_in_basic( ) self._assert( contents, - r'Europe/Amsterdam.+7:00 PM', - r'Europe/London.+6:00 PM', - r'America/New_York.+1:00 PM' + r"Europe/Amsterdam.+7:00 PM", + r"Europe/London.+6:00 PM", + r"America/New_York.+1:00 PM", ) message.author = british_user - contents = await dispatch_msg_get_contents( - "aka {8pm helsinki}" - ) + contents = await dispatch_msg_get_contents("aka {8pm helsinki}") self._assert( contents, - r'Europe/Amsterdam.+7:00 PM', - r'Europe/London.+6:00 PM', - r'America/New_York.+1:00 PM' + r"Europe/Amsterdam.+7:00 PM", + r"Europe/London.+6:00 PM", + r"America/New_York.+1:00 PM", ) message.author = dutch_user - contents = await dispatch_msg_get_contents( - "aka {20:00 helsinki}" - ) + contents = await dispatch_msg_get_contents("aka {20:00 helsinki}") self._assert( contents, - r'Europe/Amsterdam.+7:00 PM', - r'Europe/London.+6:00 PM', - r'America/New_York.+1:00 PM' + r"Europe/Amsterdam.+7:00 PM", + r"Europe/London.+6:00 PM", + r"America/New_York.+1:00 PM", ) - async def test_in_multiple( - self, message, american_user, dispatch_msg_get_contents - ): + async def test_in_multiple(self, message, american_user, dispatch_msg_get_contents): message.author = american_user contents = await dispatch_msg_get_contents( "my flight took off at {7pm new york} and landed at {8 AM london}" ) self._assert( contents, - r'Europe/Amsterdam.+1:00 AM.+9:00 AM', - r'Europe/London.+12:00 AM.+8:00 AM', - r'America/New_York.+7:00 PM.+3:00 AM' + r"Europe/Amsterdam.+1:00 AM.+9:00 AM", + r"Europe/London.+12:00 AM.+8:00 AM", + r"America/New_York.+7:00 PM.+3:00 AM", ) async def test_in_keyword( - self, message, american_user, british_user, - dispatch_msg_get_contents + self, message, american_user, british_user, dispatch_msg_get_contents ): message.author = british_user contents = await dispatch_msg_get_contents( @@ -573,9 +549,9 @@ async def test_in_keyword( ) self._assert( contents, - r'Europe/Amsterdam.+9:00 PM', - r'Europe/London.+8:00 PM', - r'America/New_York.+3:00 PM' + r"Europe/Amsterdam.+9:00 PM", + r"Europe/London.+8:00 PM", + r"America/New_York.+3:00 PM", ) message.author = american_user @@ -584,27 +560,25 @@ async def test_in_keyword( ) self._assert( contents, - r'Europe/Amsterdam.+12:00 AM', - r'Europe/London.+11:00 PM', - r'America/New_York.+6:00 PM' + r"Europe/Amsterdam.+12:00 AM", + r"Europe/London.+11:00 PM", + r"America/New_York.+6:00 PM", ) - async def test_in_now( - self, message, american_user, dispatch_msg_get_contents - ): + async def test_in_now(self, message, american_user, dispatch_msg_get_contents): message.author = american_user contents = await dispatch_msg_get_contents( "{now amsterdam} is redundant but it shouldn't fail" ) self._assert( contents, - r'Europe/Amsterdam.+11:32 AM', - r'Europe/London.+10:32 AM', - r'America/New_York.+5:32 AM' + r"Europe/Amsterdam.+11:32 AM", + r"Europe/London.+10:32 AM", + r"America/New_York.+5:32 AM", ) async def test_in_ambiguous_with_unit( - self, message, american_user, dispatch_msg_get_embeds + self, message, american_user, dispatch_msg_get_embeds ): message.author = american_user embeds = await dispatch_msg_get_embeds( @@ -615,7 +589,7 @@ async def test_in_ambiguous_with_unit( assert_error(embeds, 'Unknown unit "helsinki"') async def test_in_unknown_timezone( - self, message, american_user, dispatch_msg_get_embeds + self, message, american_user, dispatch_msg_get_embeds ): message.author = american_user embeds = await dispatch_msg_get_embeds( @@ -627,102 +601,66 @@ async def test_in_unknown_timezone( # region Specified output timezone - async def test_out_basic( - self, message, american_user, dispatch_msg_get_contents - ): + async def test_out_basic(self, message, american_user, dispatch_msg_get_contents): message.author = american_user contents = await dispatch_msg_get_contents( "alex, I'm gonna restart the server at {11 > amsterdam}" ) - self._assert( - contents, - r'Europe/Amsterdam.+5:00 PM' - ) + self._assert(contents, r"Europe/Amsterdam.+5:00 PM") message.author = american_user contents = await dispatch_msg_get_contents( "or I can wait until {8pm > amsterdam}" ) - self._assert( - contents, - r'Europe/Amsterdam.+2:00 AM' - ) + self._assert(contents, r"Europe/Amsterdam.+2:00 AM") async def test_out_multiple( - self, message, american_user, dutch_user, - dispatch_msg_get_contents + self, message, american_user, dutch_user, dispatch_msg_get_contents ): message.author = american_user contents = await dispatch_msg_get_contents( "hey bruce I wanna show you something, I'm free between " "{11am > london} and {3 PM > Europe/London}" ) - self._assert( - contents, - r'Europe/London.+4:00 PM.+8:00 PM' - ) + self._assert(contents, r"Europe/London.+4:00 PM.+8:00 PM") message.author = dutch_user contents = await dispatch_msg_get_contents( "the game's releasing for americans at {1 PM > new york} and " "{1500 > london} for europeans" - r'America/New_York.+7:00 AM' - ) - self._assert( - contents, - r'Europe/London.+2:00 PM' + r"America/New_York.+7:00 AM" ) + self._assert(contents, r"Europe/London.+2:00 PM") - async def test_out_keyword( - self, message, american_user, dispatch_msg_get_contents - ): + async def test_out_keyword(self, message, american_user, dispatch_msg_get_contents): message.author = american_user contents = await dispatch_msg_get_contents( "the solar eclipse will be happening here while the hawaiians " "are sleeping! {noon > honolulu}" ) - self._assert( - contents, - r'Pacific/Honolulu.+6:00 AM' - ) + self._assert(contents, r"Pacific/Honolulu.+6:00 AM") message.author = american_user contents = await dispatch_msg_get_contents( - "and while I'm sleeping, they'll be eating dinner " - "{midnight > honolulu}" - ) - self._assert( - contents, - r'Pacific/Honolulu.+6:00 PM' + "and while I'm sleeping, they'll be eating dinner " "{midnight > honolulu}" ) + self._assert(contents, r"Pacific/Honolulu.+6:00 PM") - async def test_out_now( - self, message, american_user, dispatch_msg_get_contents - ): + async def test_out_now(self, message, american_user, dispatch_msg_get_contents): message.author = american_user contents = await dispatch_msg_get_contents( "what time is it in dubai? {now > dubai}" ) - self._assert( - contents, - r'Asia/Dubai.+1:32 PM' - ) + self._assert(contents, r"Asia/Dubai.+1:32 PM") async def test_out_unknown_timezone( - self, message, american_user, dispatch_msg_get_embeds + self, message, american_user, dispatch_msg_get_embeds ): message.author = american_user - embeds = await dispatch_msg_get_embeds( - "no timezone {8:00 > ZBNMBSAEFHJBGEWB}" - ) - assert_error( - embeds, - 'Timezone "ZBNMBSAEFHJBGEWB" not found' - ) + embeds = await dispatch_msg_get_embeds("no timezone {8:00 > ZBNMBSAEFHJBGEWB}") + assert_error(embeds, 'Timezone "ZBNMBSAEFHJBGEWB" not found') - async def test_out_empty( - self, message, american_user, dispatch_msg - ): + async def test_out_empty(self, message, american_user, dispatch_msg): message.author = american_user mock_ = await dispatch_msg("no timezone {8:00 > }") assert_no_reply(mock_) @@ -731,21 +669,16 @@ async def test_out_empty( # region Specified input and output timezone - async def test_in_out_basic( - self, message, dutch_user, dispatch_msg_get_contents - ): + async def test_in_out_basic(self, message, dutch_user, dispatch_msg_get_contents): message.author = dutch_user contents = await dispatch_msg_get_contents( "I've run out of interesting message ideas " "{5:00 pm honolulu > los angeles}" ) - self._assert( - contents, - r'America/Los_Angeles.+8:00 PM' - ) + self._assert(contents, r"America/Los_Angeles.+8:00 PM") async def test_in_out_multiple( - self, message, british_user, dispatch_msg_get_contents + self, message, british_user, dispatch_msg_get_contents ): message.author = british_user contents = await dispatch_msg_get_contents( @@ -756,69 +689,42 @@ async def test_in_out_multiple( ) self._assert( contents, - r'America/Los_Angeles.+8:00 PM.+10:00 PM', - r'Europe/London.+9:00 AM' + r"America/Los_Angeles.+8:00 PM.+10:00 PM", + r"Europe/London.+9:00 AM", ) - async def test_in_out_keyword( - self, message, dutch_user, dispatch_msg_get_contents - ): + async def test_in_out_keyword(self, message, dutch_user, dispatch_msg_get_contents): message.author = dutch_user contents = await dispatch_msg_get_contents( ":) {midnight los angeles > honolulu}" ) - self._assert( - contents, - r'Pacific/Honolulu.+9:00 PM' - ) + self._assert(contents, r"Pacific/Honolulu.+9:00 PM") message.author = dutch_user - contents = await dispatch_msg_get_contents( - ":D {noon london > new york}" - ) - self._assert( - contents, - r'America/New_York.+7:00 AM' - ) + contents = await dispatch_msg_get_contents(":D {noon london > new york}") + self._assert(contents, r"America/New_York.+7:00 AM") - async def test_in_out_now( - self, message, dutch_user, dispatch_msg_get_contents - ): + async def test_in_out_now(self, message, dutch_user, dispatch_msg_get_contents): message.author = dutch_user - contents = await dispatch_msg_get_contents( - ":O {now new york > amsterdam}" - ) - self._assert( - contents, - r'Europe/Amsterdam.+11:32 AM' - ) + contents = await dispatch_msg_get_contents(":O {now new york > amsterdam}") + self._assert(contents, r"Europe/Amsterdam.+11:32 AM") async def test_in_out_unknown_timezone( - self, message, dutch_user, dispatch_msg_get_embeds + self, message, dutch_user, dispatch_msg_get_embeds ): message.author = dutch_user - embeds = await dispatch_msg_get_embeds( - ":( {10 amsterdam > london}" - ) - assert_error( - embeds, - 'Unknown unit "amsterdam"' - ) + embeds = await dispatch_msg_get_embeds(":( {10 amsterdam > london}") + assert_error(embeds, 'Unknown unit "amsterdam"') # endregion # region Other - async def test_flags( - self, message, american_user, dispatch_msg_get_contents - ): + async def test_flags(self, message, american_user, dispatch_msg_get_contents): message.author = american_user contents = await dispatch_msg_get_contents( "you can see the country flags too! {12am}" ) - self._assert( - contents, - '🇺🇸', '🇬🇧', '🇳🇱' - ) + self._assert(contents, "🇺🇸", "🇬🇧", "🇳🇱") # endregion diff --git a/sandpiper/tests/test_database.py b/sandpiper/tests/test_database.py index 4c3b9d3..7b94f0c 100644 --- a/sandpiper/tests/test_database.py +++ b/sandpiper/tests/test_database.py @@ -17,36 +17,34 @@ def user_id(new_id) -> int: class TestConnection: - async def test_connected(self, database): assert (await database.connected()) is True async def test_disconnected(self): - database = DatabaseSQLite(':memory:') + database = DatabaseSQLite(":memory:") await database.connect() await database.disconnect() assert (await database.connected()) is False class TestSandpiper: - async def test_get_version(self, database): assert (await database.get_sandpiper_version()) is None async def test_set_get_version(self, database): - value = '1.6.0' + value = "1.6.0" await database.set_sandpiper_version(value) assert (await database.get_sandpiper_version()) == value class TestFullUser: - @pytest.fixture() def user_factory(self, database, new_id): async def f() -> int: uid = new_id() await database.create_user(uid) return uid + return f async def test_create(self, database, user_factory): @@ -80,7 +78,6 @@ async def test_create_multiple_delete_all(self, database, user_factory): class TestPreferredName: - async def test_get(self, database, user_id): await database.create_user(user_id) assert (await database.get_preferred_name(user_id)) is None @@ -90,7 +87,7 @@ async def test_get_no_user(self, database, user_id): await database.get_preferred_name(user_id) async def test_set_get(self, database, user_id): - value = 'Greg' + value = "Greg" await database.set_preferred_name(user_id, value) assert (await database.get_preferred_name(user_id)) == value @@ -104,7 +101,6 @@ async def test_privacy_set_get(self, database, user_id): class TestPronouns: - async def test_get(self, database, user_id): await database.create_user(user_id) assert (await database.get_pronouns(user_id)) is None @@ -114,7 +110,7 @@ async def test_get_no_user(self, database, user_id): await database.get_pronouns(user_id) async def test_set_get(self, database, user_id): - value = 'She/Her' + value = "She/Her" await database.set_pronouns(user_id, value) assert (await database.get_pronouns(user_id)) == value @@ -135,17 +131,16 @@ async def test_get_parsed_no_user(self, database, user_id): await database.get_pronouns_parsed(user_id) async def test_set_get_parsed(self, database, user_id): - value = 'She/they' + value = "She/they" await database.set_pronouns(user_id, value) - parsed_pronouns = (await database.get_pronouns_parsed(user_id)) + parsed_pronouns = await database.get_pronouns_parsed(user_id) assert parsed_pronouns == [ - Pronouns('she', 'her', 'her', 'hers', 'herself'), - Pronouns('they', 'them', 'their', 'theirs', 'themself') + Pronouns("she", "her", "her", "hers", "herself"), + Pronouns("they", "them", "their", "theirs", "themself"), ] class TestBirthday: - async def test_get(self, database, user_id): await database.create_user(user_id) assert (await database.get_birthday(user_id)) is None @@ -169,7 +164,6 @@ async def test_privacy_set_get(self, database, user_id): class TestAge: - async def test_privacy_get(self, database, user_id): assert (await database.get_privacy_age(user_id)) is None @@ -180,21 +174,19 @@ async def test_privacy_set_get(self, database, user_id): class TestCalculateAge: - @pytest.fixture() async def birthday(self) -> dt.date: yield dt.date(2000, 2, 14) @pytest.fixture() async def tz_new_york(self) -> TimezoneType: - yield pytz.timezone('America/New_York') + yield pytz.timezone("America/New_York") @pytest.fixture() def calculate_age(self, database, birthday, tz_new_york): def f(at_time: dt.datetime, tz: TimezoneType = pytz.UTC): - return database._calculate_age( - birthday, tz_new_york, tz.localize(at_time) - ) + return database._calculate_age(birthday, tz_new_york, tz.localize(at_time)) + return f async def test_jan_1(self, calculate_age): @@ -214,7 +206,6 @@ async def test_day_after_birthday(self, calculate_age): class TestTimezone: - async def test_get(self, database, user_id): await database.create_user(user_id) assert (await database.get_timezone(user_id)) is None @@ -224,7 +215,7 @@ async def test_get_no_user(self, database, user_id): await database.get_timezone(user_id) async def test_set_get(self, database, user_id): - value = pytz.timezone('America/New_York') + value = pytz.timezone("America/New_York") await database.set_timezone(user_id, value) assert (await database.get_timezone(user_id)) == value @@ -238,7 +229,6 @@ async def test_privacy_set_get(self, database, user_id): class TestBirthdayNotificationSent: - async def test_get(self, database, user_id): await database.create_user(user_id) assert (await database.get_last_birthday_notification(user_id)) is None @@ -254,7 +244,6 @@ async def test_set_get(self, database, user_id): class TestGuildBirthdayChannel: - async def test_get(self, database, user_id): assert (await database.get_guild_birthday_channel(user_id)) is None @@ -265,77 +254,76 @@ async def test_set_get(self, database, user_id, new_id): class TestFindUsersByPreferredName: - @pytest.fixture() def user_factory(self, database, new_id): async def f( - preferred_name: str, privacy: PrivacyType = PrivacyType.PUBLIC + preferred_name: str, privacy: PrivacyType = PrivacyType.PUBLIC ) -> int: uid = new_id() await database.set_preferred_name(uid, preferred_name) await database.set_privacy_preferred_name(uid, privacy) return uid + return f async def test_no_users(self, database): - found = await database.find_users_by_preferred_name('Name') + found = await database.find_users_by_preferred_name("Name") assert found == [] async def test_no_users_with_name(self, database, user_factory): - await user_factory('Greg') - found = await database.find_users_by_preferred_name('Alan') + await user_factory("Greg") + found = await database.find_users_by_preferred_name("Alan") assert found == [] async def test_basic(self, database, user_factory): - uid = await user_factory('Greg') - found = await database.find_users_by_preferred_name('Greg') - assert found == [(uid, 'Greg')] + uid = await user_factory("Greg") + found = await database.find_users_by_preferred_name("Greg") + assert found == [(uid, "Greg")] async def test_empty_string(self, database, user_factory): - await user_factory('Greg') - found = await database.find_users_by_preferred_name('') + await user_factory("Greg") + found = await database.find_users_by_preferred_name("") assert found == [] async def test_duplicate(self, database, user_factory): - uid1 = await user_factory('Fred') - uid2 = await user_factory('Fred') - found = await database.find_users_by_preferred_name('Fred') - assert_count_equal(found, [(uid1, 'Fred'), (uid2, 'Fred')]) + uid1 = await user_factory("Fred") + uid2 = await user_factory("Fred") + found = await database.find_users_by_preferred_name("Fred") + assert_count_equal(found, [(uid1, "Fred"), (uid2, "Fred")]) async def test_case_insensitive(self, database, user_factory): - uid1 = await user_factory('Ned') - uid2 = await user_factory('ned') - found = await database.find_users_by_preferred_name('ned') - assert_count_equal(found, [(uid1, 'Ned'), (uid2, 'ned')]) + uid1 = await user_factory("Ned") + uid2 = await user_factory("ned") + found = await database.find_users_by_preferred_name("ned") + assert_count_equal(found, [(uid1, "Ned"), (uid2, "ned")]) async def test_substring(self, database, user_factory): - uid1 = await user_factory('Pizzaman') - uid2 = await user_factory('Eat Pizza') - found = await database.find_users_by_preferred_name('Pizza') - assert_count_equal(found, [(uid1, 'Pizzaman'), (uid2, 'Eat Pizza')]) + uid1 = await user_factory("Pizzaman") + uid2 = await user_factory("Eat Pizza") + found = await database.find_users_by_preferred_name("Pizza") + assert_count_equal(found, [(uid1, "Pizzaman"), (uid2, "Eat Pizza")]) async def test_superstring(self, database, user_factory): - uid1 = await user_factory('Pizzaman') - uid2 = await user_factory('Eat Pizza') - found = await database.find_users_by_preferred_name('Pizzaman') - assert found == [(uid1, 'Pizzaman')] + uid1 = await user_factory("Pizzaman") + uid2 = await user_factory("Eat Pizza") + found = await database.find_users_by_preferred_name("Pizzaman") + assert found == [(uid1, "Pizzaman")] async def test_private(self, database, user_factory): - uid1 = await user_factory('Alan') - uid2 = await user_factory('Alan', PrivacyType.PRIVATE) - found = await database.find_users_by_preferred_name('Alan') - assert found == [(uid1, 'Alan')] + uid1 = await user_factory("Alan") + uid2 = await user_factory("Alan", PrivacyType.PRIVATE) + found = await database.find_users_by_preferred_name("Alan") + assert found == [(uid1, "Alan")] class TestGetBirthdaysRange: - @pytest.fixture() def user_factory(self, database, new_id): async def f( - birthday: Optional[dt.date], - last_notif: dt.datetime = None, - *, - privacy: PrivacyType = PrivacyType.PUBLIC + birthday: Optional[dt.date], + last_notif: dt.datetime = None, + *, + privacy: PrivacyType = PrivacyType.PUBLIC, ) -> tuple[int, dt.date]: uid = new_id() await database.create_user(uid) @@ -344,6 +332,7 @@ async def f( if last_notif is not None: await database.set_last_birthday_notification(uid, last_notif) return uid, birthday + return f @pytest.fixture() @@ -354,7 +343,7 @@ async def birthdays(self, user_factory) -> list[tuple[int, dt.date]]: await user_factory(dt.date(2004, 4, 5)), await user_factory(dt.date(2000, 4, 30)), await user_factory(dt.date(1999, 5, 2)), - await user_factory(dt.date(1998, 5, 27)) + await user_factory(dt.date(1998, 5, 27)), ) async def test_no_results(self, database, birthdays): @@ -404,10 +393,8 @@ async def test_year_wrap_to_same_month(self, database, birthdays): dt.date(2020, 4, 20), dt.date(2021, 4, 1) ) assert_count_equal( - result, [ - birthdays[3], birthdays[4], birthdays[5], - birthdays[0], birthdays[1] - ] + result, + [birthdays[3], birthdays[4], birthdays[5], birthdays[0], birthdays[1]], ) async def test_private(self, database, birthdays): @@ -428,16 +415,13 @@ async def test_none(self, database, birthdays, user_factory): async def birthdays_with_last_notif(self, user_factory): yield [ # UTC - await user_factory( - dt.date(2000, 1, 1), dt.datetime(2021, 1, 1, 0, 0)), + await user_factory(dt.date(2000, 1, 1), dt.datetime(2021, 1, 1, 0, 0)), # UTC no last notif await user_factory(dt.date(2000, 1, 2), None), # New York (UTC-5) - await user_factory( - dt.date(2001, 2, 2), dt.datetime(2021, 2, 2, 5, 0)), + await user_factory(dt.date(2001, 2, 2), dt.datetime(2021, 2, 2, 5, 0)), # Dubai (UTC+4) - await user_factory( - dt.date(2002, 2, 2), dt.datetime(2021, 2, 1, 20, 0)) + await user_factory(dt.date(2002, 2, 2), dt.datetime(2021, 2, 1, 20, 0)), ] async def test_last_birthday_notification_basic(self, database, user_factory): @@ -445,18 +429,15 @@ async def test_last_birthday_notification_basic(self, database, user_factory): await user_factory(dt.date(2000, 2, 14), dt.datetime(2020, 2, 14, 0, 0)), await user_factory(dt.date(2000, 2, 14), dt.datetime(2021, 2, 14, 0, 0)), await user_factory(dt.date(2000, 2, 14), dt.datetime(2021, 2, 14, 11, 0)), - await user_factory(dt.date(2000, 2, 15), dt.datetime(2021, 2, 15, 0, 0)) + await user_factory(dt.date(2000, 2, 15), dt.datetime(2021, 2, 15, 0, 0)), ] start = dt.date(2021, 1, 1) end = dt.date(2021, 12, 31) # Sanity check -- should return all - assert_count_equal( - await database.get_birthdays_range(start, end), birthdays - ) + assert_count_equal(await database.get_birthdays_range(start, end), birthdays) # Main assertion result = await database.get_birthdays_range( - start, end, - max_last_notification_time=dt.datetime(2021, 2, 14, 12, 0) + start, end, max_last_notification_time=dt.datetime(2021, 2, 14, 12, 0) ) assert_count_equal(result, birthdays[:3]) @@ -464,18 +445,15 @@ async def test_last_birthday_notification_none(self, database, user_factory): birthdays = [ await user_factory(dt.date(2000, 2, 14), None), await user_factory(dt.date(2000, 2, 14), dt.datetime(2021, 2, 14, 0, 0)), - await user_factory(dt.date(2000, 2, 15), dt.datetime(2021, 2, 15, 0, 0)) + await user_factory(dt.date(2000, 2, 15), dt.datetime(2021, 2, 15, 0, 0)), ] start = dt.date(2021, 1, 1) end = dt.date(2021, 12, 31) # Sanity check -- should return all - assert_count_equal( - await database.get_birthdays_range(start, end), birthdays - ) + assert_count_equal(await database.get_birthdays_range(start, end), birthdays) # Main assertion result = await database.get_birthdays_range( - start, end, - max_last_notification_time=dt.datetime(2021, 2, 14, 12, 0) + start, end, max_last_notification_time=dt.datetime(2021, 2, 14, 12, 0) ) assert_count_equal(result, birthdays[:2]) @@ -486,46 +464,42 @@ async def test_last_birthday_notification_inclusive(self, database, user_factory start = dt.date(2021, 1, 1) end = dt.date(2021, 12, 31) # Sanity check -- should return all - assert_count_equal( - await database.get_birthdays_range(start, end), birthdays - ) + assert_count_equal(await database.get_birthdays_range(start, end), birthdays) # Main assertion result = await database.get_birthdays_range( - start, end, - max_last_notification_time=dt.datetime(2021, 2, 14, 0, 0) + start, end, max_last_notification_time=dt.datetime(2021, 2, 14, 0, 0) ) assert_count_equal(result, birthdays) class TestGetAllTimezones: - @pytest.fixture() def user_factory(self, database, new_id): async def f( - timezone: TimezoneType, - privacy: PrivacyType = PrivacyType.PUBLIC + timezone: TimezoneType, privacy: PrivacyType = PrivacyType.PUBLIC ) -> int: uid = new_id() await database.set_timezone(uid, timezone) await database.set_privacy_timezone(uid, privacy) return uid + return f @pytest.fixture() def tz_london(self): - return pytz.timezone('Europe/London') + return pytz.timezone("Europe/London") @pytest.fixture() def tz_new_york(self): - return pytz.timezone('America/New_York') + return pytz.timezone("America/New_York") @pytest.fixture() def tz_denver(self): - return pytz.timezone('America/Denver') + return pytz.timezone("America/Denver") @pytest.fixture() def tz_boise(self): - return pytz.timezone('America/Boise') + return pytz.timezone("America/Boise") async def test_no_users(self, database, user_factory): timezones = await database.get_all_timezones() @@ -537,8 +511,7 @@ async def test_basic(self, database, user_factory, tz_london): assert timezones == [(uid, tz_london)] async def test_many( - self, database, user_factory, tz_london, tz_new_york, tz_denver, - tz_boise + self, database, user_factory, tz_london, tz_new_york, tz_denver, tz_boise ): uid1 = await user_factory(tz_london) uid2 = await user_factory(tz_new_york) @@ -546,10 +519,13 @@ async def test_many( uid4 = await user_factory(tz_boise) timezones = await database.get_all_timezones() assert_count_equal( - timezones, [ - (uid1, tz_london), (uid2, tz_new_york), (uid3, tz_denver), - (uid4, tz_boise) - ] + timezones, + [ + (uid1, tz_london), + (uid2, tz_new_york), + (uid3, tz_denver), + (uid4, tz_boise), + ], ) async def test_duplicate(self, database, user_factory, tz_london): @@ -558,9 +534,7 @@ async def test_duplicate(self, database, user_factory, tz_london): timezones = await database.get_all_timezones() assert_count_equal(timezones, [(uid1, tz_london), (uid2, tz_london)]) - async def test_private( - self, database, user_factory, tz_new_york, tz_denver - ): + async def test_private(self, database, user_factory, tz_new_york, tz_denver): uid1 = await user_factory(tz_new_york) uid2 = await user_factory(tz_denver, PrivacyType.PRIVATE) timezones = await database.get_all_timezones() @@ -568,10 +542,9 @@ async def test_private( class TestSnowflakes: - async def test_max_64_bit_int_user_id(self, database): uid = 0xFFFF_FFFF_FFFF_FFFF - await database.set_preferred_name(uid, 'Name') + await database.set_preferred_name(uid, "Name") async def test_max_64_bit_int_guild_id(self, database): gid = 0xFFFF_FFFF_FFFF_FFFF diff --git a/sandpiper/upgrades/cog.py b/sandpiper/upgrades/cog.py index 8b4b46f..f60127d 100644 --- a/sandpiper/upgrades/cog.py +++ b/sandpiper/upgrades/cog.py @@ -8,19 +8,18 @@ from sandpiper import __version__ as current_version from sandpiper.user_data import UserData -__all__ = ['Upgrades'] +__all__ = ["Upgrades"] logger = logging.getLogger(__name__) class Upgrades(commands.Cog): - def __init__(self, bot: commands.Bot): self.bot = bot - @commands.Cog.listener('on_ready') + @commands.Cog.listener("on_ready") async def do_upgrades(self): - user_data: Optional[UserData] = self.bot.get_cog('UserData') + user_data: Optional[UserData] = self.bot.get_cog("UserData") if user_data is None: logger.warning( f"Failed to get the UserData cog; skipping upgrade handlers." diff --git a/sandpiper/upgrades/upgrades.py b/sandpiper/upgrades/upgrades.py index 7ffc04c..d045212 100644 --- a/sandpiper/upgrades/upgrades.py +++ b/sandpiper/upgrades/upgrades.py @@ -7,16 +7,17 @@ from sandpiper.user_data import Database, UserData -__all__ = ['UpgradeHandler', 'do_upgrades'] +__all__ = ["UpgradeHandler", "do_upgrades"] logger = logging.getLogger(__name__) class UpgradeHandler(metaclass=ABCMeta): - def __init__( - self, bot: commands.Bot, previous_version: VersionInfo, - current_version: VersionInfo + self, + bot: commands.Bot, + previous_version: VersionInfo, + current_version: VersionInfo, ): """ :param bot: the running Discord bot @@ -34,7 +35,7 @@ def __str__(self): ) async def _get_database(self) -> Optional[Database]: - user_data: UserData = self.bot.get_cog('UserData') + user_data: UserData = self.bot.get_cog("UserData") if user_data is None: logger.warning("Failed to load the UserData cog") return None @@ -56,10 +57,12 @@ async def on_upgrade(self): async def do_upgrades( - bot: commands.Bot, previous_version: Optional[str], - current_version: str, upgrade_handlers: list[Type[UpgradeHandler]] + bot: commands.Bot, + previous_version: Optional[str], + current_version: str, + upgrade_handlers: list[Type[UpgradeHandler]], ): - previous_version = VersionInfo.parse(previous_version or '0.0.0') + previous_version = VersionInfo.parse(previous_version or "0.0.0") current_version = VersionInfo.parse(current_version) logger.info( f"Beginning application upgrade from {previous_version} to " diff --git a/sandpiper/upgrades/versions/__init__.py b/sandpiper/upgrades/versions/__init__.py index 3026e40..4263790 100644 --- a/sandpiper/upgrades/versions/__init__.py +++ b/sandpiper/upgrades/versions/__init__.py @@ -1,5 +1,3 @@ from .sandpiper_1_6_0 import Sandpiper_1_6_0 -all_upgrade_handlers = [ - Sandpiper_1_6_0 -] +all_upgrade_handlers = [Sandpiper_1_6_0] diff --git a/sandpiper/upgrades/versions/sandpiper_1_6_0.py b/sandpiper/upgrades/versions/sandpiper_1_6_0.py index 19e0c82..123d52d 100644 --- a/sandpiper/upgrades/versions/sandpiper_1_6_0.py +++ b/sandpiper/upgrades/versions/sandpiper_1_6_0.py @@ -12,9 +12,8 @@ class Sandpiper_1_6_0(UpgradeHandler): - def version(self) -> str: - return '1.6.0' + return "1.6.0" async def on_upgrade(self): db = await self._get_database() @@ -37,12 +36,14 @@ async def tell_about_birthday(self, user_id: int, db: Database): return # General info about the birthday announcements - mutual_guilds = listify([m.guild.name for m in find_user_in_mutual_guilds( - self.bot, self.bot.user.id, user_id - )], 2) - embed = SpecialEmbed( - title='Birthday announcements update 🥳', join='\n\n' + mutual_guilds = listify( + [ + m.guild.name + for m in find_user_in_mutual_guilds(self.bot, self.bot.user.id, user_id) + ], + 2, ) + embed = SpecialEmbed(title="Birthday announcements update 🥳", join="\n\n") embed.append( f"Hey!! I'm a bot from {mutual_guilds}. I've just gotten a " f"birthday announcement feature. I can announce your birthday " @@ -85,8 +86,7 @@ async def tell_about_age(self, user_id: int, db: Database): # something if age_privacy is PrivacyType.PUBLIC: logger.info( - f"User's age is public; switching it to private " - f"(user_id={user_id})" + f"User's age is public; switching it to private " f"(user_id={user_id})" ) await db.set_privacy_age(user_id, PrivacyType.PRIVATE) @@ -96,7 +96,7 @@ async def tell_about_age(self, user_id: int, db: Database): return # General info about age in the announcement - embed = SpecialEmbed(title='Age in birthday announcement', join='\n\n') + embed = SpecialEmbed(title="Age in birthday announcement", join="\n\n") embed.append( "I can also announce your new age in your birthday message " "if your age privacy is set to public!" diff --git a/sandpiper/user_data/__init__.py b/sandpiper/user_data/__init__.py index 5e29571..3662268 100644 --- a/sandpiper/user_data/__init__.py +++ b/sandpiper/user_data/__init__.py @@ -1,11 +1,15 @@ from __future__ import annotations __all__ = [ - 'UserData', 'DatabaseUnavailable', - 'Database', 'DatabaseError', 'UserNotInDatabase', - 'DatabaseSQLite', - 'PrivacyType', - 'Pronouns', 'common_pronouns' + "UserData", + "DatabaseUnavailable", + "Database", + "DatabaseError", + "UserNotInDatabase", + "DatabaseSQLite", + "PrivacyType", + "Pronouns", + "common_pronouns", ] import asyncio @@ -24,7 +28,7 @@ if typing.TYPE_CHECKING: from sandpiper import Sandpiper -DB_FILE = Path(__file__).parent.parent / 'sandpiper.db' +DB_FILE = Path(__file__).parent.parent / "sandpiper.db" def setup(bot: Sandpiper): @@ -33,12 +37,13 @@ def setup(bot: Sandpiper): asyncio.run_coroutine_threadsafe(db.connect(), bot.loop) user_data.set_database_adapter(db) bot.add_cog(user_data) - bot.add_listener(set_bot_user_id(bot, db), 'on_ready') + bot.add_listener(set_bot_user_id(bot, db), "on_ready") def set_bot_user_id(bot: Sandpiper, db: DatabaseSQLite): async def fn(): db.bot_user_id = bot.user.id + return fn @@ -53,6 +58,6 @@ async def do_disconnect(user_data: UserData): def teardown(bot: Bot): """Disconnects from the database""" - user_data: Optional[UserData] = bot.get_cog('UserData') + user_data: Optional[UserData] = bot.get_cog("UserData") if user_data is not None: asyncio.run_coroutine_threadsafe(do_disconnect(user_data), bot.loop) diff --git a/sandpiper/user_data/alembic/env.py b/sandpiper/user_data/alembic/env.py index 3ef9fcc..2ab3005 100644 --- a/sandpiper/user_data/alembic/env.py +++ b/sandpiper/user_data/alembic/env.py @@ -9,7 +9,7 @@ from alembic import context -sandpiper_root_dir = Path(__file__, '../../../..') +sandpiper_root_dir = Path(__file__, "../../../..") sys.path.insert(0, str(sandpiper_root_dir.absolute())) from sandpiper.user_data.models import Base diff --git a/sandpiper/user_data/alembic/versions/643337f23b38_add_sandpiper_meta_table.py b/sandpiper/user_data/alembic/versions/643337f23b38_add_sandpiper_meta_table.py index d5834e3..e2e3873 100644 --- a/sandpiper/user_data/alembic/versions/643337f23b38_add_sandpiper_meta_table.py +++ b/sandpiper/user_data/alembic/versions/643337f23b38_add_sandpiper_meta_table.py @@ -10,19 +10,19 @@ # revision identifiers, used by Alembic. -revision = '643337f23b38' -down_revision = '8bb04f2d0a43' +revision = "643337f23b38" +down_revision = "8bb04f2d0a43" branch_labels = None depends_on = None def upgrade(): op.create_table( - 'sandpiper_meta', - sa.Column('id', sa.Integer, primary_key=True), - sa.Column('version', sa.String) + "sandpiper_meta", + sa.Column("id", sa.Integer, primary_key=True), + sa.Column("version", sa.String), ) def downgrade(): - op.drop_table('sandpiper_meta') + op.drop_table("sandpiper_meta") diff --git a/sandpiper/user_data/alembic/versions/8bb04f2d0a43_add_birthday_notification_sent_column.py b/sandpiper/user_data/alembic/versions/8bb04f2d0a43_add_birthday_notification_sent_column.py index c0a6806..ca527f6 100644 --- a/sandpiper/user_data/alembic/versions/8bb04f2d0a43_add_birthday_notification_sent_column.py +++ b/sandpiper/user_data/alembic/versions/8bb04f2d0a43_add_birthday_notification_sent_column.py @@ -10,21 +10,23 @@ # revision identifiers, used by Alembic. -revision = '8bb04f2d0a43' -down_revision = 'ecce1f1e8760' +revision = "8bb04f2d0a43" +down_revision = "ecce1f1e8760" branch_labels = None depends_on = None def upgrade(): op.add_column( - 'users', + "users", sa.Column( - 'birthday_notification_sent', sa.Boolean, nullable=False, - server_default=sa.text('false') - ) + "birthday_notification_sent", + sa.Boolean, + nullable=False, + server_default=sa.text("false"), + ), ) def downgrade(): - op.drop_column('users', 'birthday_notification_sent') + op.drop_column("users", "birthday_notification_sent") diff --git a/sandpiper/user_data/alembic/versions/91e18fdd475a_init.py b/sandpiper/user_data/alembic/versions/91e18fdd475a_init.py index a6821bf..8c4328b 100644 --- a/sandpiper/user_data/alembic/versions/91e18fdd475a_init.py +++ b/sandpiper/user_data/alembic/versions/91e18fdd475a_init.py @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. -revision = '91e18fdd475a' +revision = "91e18fdd475a" down_revision = None branch_labels = None depends_on = None @@ -18,24 +18,23 @@ def upgrade(): op.create_table( - 'user_data', - sa.Column('user_id', sa.BigInteger, primary_key=True), - sa.Column('preferred_name', sa.String), - sa.Column('pronouns', sa.String), - sa.Column('birthday', sa.Date), - sa.Column('timezone', sa.String), - sa.Column('privacy_preferred_name', sa.SmallInteger), - sa.Column('privacy_pronouns', sa.SmallInteger), - sa.Column('privacy_birthday', sa.SmallInteger), - sa.Column('privacy_age', sa.SmallInteger), - sa.Column('privacy_timezone', sa.SmallInteger) + "user_data", + sa.Column("user_id", sa.BigInteger, primary_key=True), + sa.Column("preferred_name", sa.String), + sa.Column("pronouns", sa.String), + sa.Column("birthday", sa.Date), + sa.Column("timezone", sa.String), + sa.Column("privacy_preferred_name", sa.SmallInteger), + sa.Column("privacy_pronouns", sa.SmallInteger), + sa.Column("privacy_birthday", sa.SmallInteger), + sa.Column("privacy_age", sa.SmallInteger), + sa.Column("privacy_timezone", sa.SmallInteger), ) op.create_index( - 'index_users_preferred_name', 'user_data', ['preferred_name'], - unique=False + "index_users_preferred_name", "user_data", ["preferred_name"], unique=False ) def downgrade(): - op.drop_index('index_users_preferred_name', table_name='user_data') - op.drop_table('user_data') + op.drop_index("index_users_preferred_name", table_name="user_data") + op.drop_table("user_data") diff --git a/sandpiper/user_data/alembic/versions/a2d3dc3c170e_add_guilds_table.py b/sandpiper/user_data/alembic/versions/a2d3dc3c170e_add_guilds_table.py index ae33a49..8d10c3e 100644 --- a/sandpiper/user_data/alembic/versions/a2d3dc3c170e_add_guilds_table.py +++ b/sandpiper/user_data/alembic/versions/a2d3dc3c170e_add_guilds_table.py @@ -10,19 +10,19 @@ # revision identifiers, used by Alembic. -revision = 'a2d3dc3c170e' -down_revision = 'e9cf36d27e9d' +revision = "a2d3dc3c170e" +down_revision = "e9cf36d27e9d" branch_labels = None depends_on = None def upgrade(): op.create_table( - 'guilds', - sa.Column('guild_id', sa.BigInteger, primary_key=True), - sa.Column('birthday_channel', sa.BigInteger), + "guilds", + sa.Column("guild_id", sa.BigInteger, primary_key=True), + sa.Column("birthday_channel", sa.BigInteger), ) def downgrade(): - op.drop_table('guilds') + op.drop_table("guilds") diff --git a/sandpiper/user_data/alembic/versions/deb518f1a216_add_server_default_value_for_privacies_.py b/sandpiper/user_data/alembic/versions/deb518f1a216_add_server_default_value_for_privacies_.py index 3b14f93..9e92301 100644 --- a/sandpiper/user_data/alembic/versions/deb518f1a216_add_server_default_value_for_privacies_.py +++ b/sandpiper/user_data/alembic/versions/deb518f1a216_add_server_default_value_for_privacies_.py @@ -11,22 +11,22 @@ # revision identifiers, used by Alembic. -revision = 'deb518f1a216' -down_revision = 'a2d3dc3c170e' +revision = "deb518f1a216" +down_revision = "a2d3dc3c170e" branch_labels = None depends_on = None DEFAULT_PRIVACY = 0 # == PrivacyType.PRIVATE privacy_cols = ( - 'privacy_preferred_name', - 'privacy_pronouns', - 'privacy_birthday', - 'privacy_age', - 'privacy_timezone' + "privacy_preferred_name", + "privacy_pronouns", + "privacy_birthday", + "privacy_age", + "privacy_timezone", ) # Build a little fake table with our privacy columns so we can update them users = sa.table( - 'users', + "users", *[sa.column(col, sa.SmallInteger) for col in privacy_cols], ) @@ -37,7 +37,7 @@ def upgrade(): # op.execute wasn't working conn: Connection = op.get_bind() - with op.batch_alter_table('users') as batch_op: + with op.batch_alter_table("users") as batch_op: for col in privacy_cols: # Replace all null fields with the new default value of 0 (which # was already being returned programmatically as the default) @@ -51,14 +51,13 @@ def upgrade(): # new table and copy all data over because SQLite doesn't have much # functionality in terms of alter column :( batch_op.alter_column( - col, nullable=False, - server_default=sa.text(str(DEFAULT_PRIVACY)) + col, nullable=False, server_default=sa.text(str(DEFAULT_PRIVACY)) ) def downgrade(): # The replacement of null fields with the new default is destructive but # backwards-compatible, so we will not be doing anything about that change - with op.batch_alter_table('users') as batch_op: + with op.batch_alter_table("users") as batch_op: for col in privacy_cols: batch_op.alter_column(col, nullable=True, server_default=None) diff --git a/sandpiper/user_data/alembic/versions/e9cf36d27e9d_rename_user_data_table_to_users.py b/sandpiper/user_data/alembic/versions/e9cf36d27e9d_rename_user_data_table_to_users.py index 0ea35ec..1d1c013 100644 --- a/sandpiper/user_data/alembic/versions/e9cf36d27e9d_rename_user_data_table_to_users.py +++ b/sandpiper/user_data/alembic/versions/e9cf36d27e9d_rename_user_data_table_to_users.py @@ -10,15 +10,15 @@ # revision identifiers, used by Alembic. -revision = 'e9cf36d27e9d' -down_revision = '91e18fdd475a' +revision = "e9cf36d27e9d" +down_revision = "91e18fdd475a" branch_labels = None depends_on = None def upgrade(): - op.rename_table('user_data', 'users') + op.rename_table("user_data", "users") def downgrade(): - op.rename_table('users', 'user_data') + op.rename_table("users", "user_data") diff --git a/sandpiper/user_data/alembic/versions/eaa603d93189_rename_birthday_notification_sent_to_.py b/sandpiper/user_data/alembic/versions/eaa603d93189_rename_birthday_notification_sent_to_.py index 5ae53e1..3896576 100644 --- a/sandpiper/user_data/alembic/versions/eaa603d93189_rename_birthday_notification_sent_to_.py +++ b/sandpiper/user_data/alembic/versions/eaa603d93189_rename_birthday_notification_sent_to_.py @@ -10,26 +10,28 @@ # revision identifiers, used by Alembic. -revision = 'eaa603d93189' -down_revision = '643337f23b38' +revision = "eaa603d93189" +down_revision = "643337f23b38" branch_labels = None depends_on = None def upgrade(): - with op.batch_alter_table('users') as batch_op: - batch_op.drop_column('birthday_notification_sent') + with op.batch_alter_table("users") as batch_op: + batch_op.drop_column("birthday_notification_sent") batch_op.add_column( - sa.Column('last_birthday_notification', sa.DateTime, nullable=True) + sa.Column("last_birthday_notification", sa.DateTime, nullable=True) ) def downgrade(): - with op.batch_alter_table('users') as batch_op: - batch_op.drop_column('last_birthday_notification') + with op.batch_alter_table("users") as batch_op: + batch_op.drop_column("last_birthday_notification") batch_op.add_column( sa.Column( - 'birthday_notification_sent', sa.Boolean, nullable=False, - server_default=sa.text('false') + "birthday_notification_sent", + sa.Boolean, + nullable=False, + server_default=sa.text("false"), ) ) diff --git a/sandpiper/user_data/alembic/versions/ecce1f1e8760_promote_snowflake_fields_from_integers_.py b/sandpiper/user_data/alembic/versions/ecce1f1e8760_promote_snowflake_fields_from_integers_.py index 2835b9a..3aefdce 100644 --- a/sandpiper/user_data/alembic/versions/ecce1f1e8760_promote_snowflake_fields_from_integers_.py +++ b/sandpiper/user_data/alembic/versions/ecce1f1e8760_promote_snowflake_fields_from_integers_.py @@ -10,8 +10,8 @@ # revision identifiers, used by Alembic. -revision = 'ecce1f1e8760' -down_revision = 'deb518f1a216' +revision = "ecce1f1e8760" +down_revision = "deb518f1a216" branch_labels = None depends_on = None @@ -27,18 +27,18 @@ def upgrade(): represent the decimal form of the highest 64-bit unsigned int) """ - with op.batch_alter_table('users') as batch_op: - batch_op.alter_column('user_id', type_=sa.String(20)) + with op.batch_alter_table("users") as batch_op: + batch_op.alter_column("user_id", type_=sa.String(20)) - with op.batch_alter_table('guilds') as batch_op: - batch_op.alter_column('guild_id', type_=sa.String(20)) - batch_op.alter_column('birthday_channel', type_=sa.String(20)) + with op.batch_alter_table("guilds") as batch_op: + batch_op.alter_column("guild_id", type_=sa.String(20)) + batch_op.alter_column("birthday_channel", type_=sa.String(20)) def downgrade(): - with op.batch_alter_table('users') as batch_op: - batch_op.alter_column('user_id', type_=sa.BigInteger) + with op.batch_alter_table("users") as batch_op: + batch_op.alter_column("user_id", type_=sa.BigInteger) - with op.batch_alter_table('guilds') as batch_op: - batch_op.alter_column('guild_id', type_=sa.BigInteger) - batch_op.alter_column('birthday_channel', type_=sa.BigInteger) + with op.batch_alter_table("guilds") as batch_op: + batch_op.alter_column("guild_id", type_=sa.BigInteger) + batch_op.alter_column("birthday_channel", type_=sa.BigInteger) diff --git a/sandpiper/user_data/alembic_utils.py b/sandpiper/user_data/alembic_utils.py index d2727f4..8fe0760 100644 --- a/sandpiper/user_data/alembic_utils.py +++ b/sandpiper/user_data/alembic_utils.py @@ -11,11 +11,11 @@ from sandpiper.user_data.models import Base -__all__ = ['get_current_heads', 'stamp', 'upgrade'] +__all__ = ["get_current_heads", "stamp", "upgrade"] logger = logging.getLogger(__name__) -config_path = Path(__file__, '../alembic.ini').resolve().absolute() +config_path = Path(__file__, "../alembic.ini").resolve().absolute() config = Config(str(config_path)) script = ScriptDirectory.from_config(config) context = EnvironmentContext(config, script) @@ -37,6 +37,7 @@ async def get_current_heads(engine: AsyncEngine) -> tuple[str]: def fn(connection: AsyncConnection): migration_ctx = MigrationContext.configure(connection) return migration_ctx.get_current_heads() + return await _run_sync(engine, fn) @@ -44,6 +45,7 @@ async def stamp(engine: AsyncEngine, revision: str): def fn(connection: AsyncConnection): migration_ctx = MigrationContext.configure(connection) return migration_ctx.stamp(script, revision) + await _run_sync(engine, fn) @@ -52,9 +54,7 @@ def do_upgrade(rev, context): return script._upgrade_revs(revision, rev) def fn(connection: AsyncConnection): - context.configure( - connection, target_metadata=target_metadata, fn=do_upgrade - ) + context.configure(connection, target_metadata=target_metadata, fn=do_upgrade) with context.begin_transaction(): context.run_migrations() diff --git a/sandpiper/user_data/cog.py b/sandpiper/user_data/cog.py index 4bf8343..16304ac 100644 --- a/sandpiper/user_data/cog.py +++ b/sandpiper/user_data/cog.py @@ -4,9 +4,9 @@ from .database import Database -__all__ = ['UserData', 'DatabaseUnavailable'] +__all__ = ["UserData", "DatabaseUnavailable"] -logger = logging.getLogger('sandpiper.user_data') +logger = logging.getLogger("sandpiper.user_data") class DatabaseUnavailable(Exception): @@ -32,8 +32,8 @@ async def get_database(self): :raises DatabaseUnavailable: if database is not set or connected """ if self._database is None: - raise DatabaseUnavailable('Database adapter is not set.') + raise DatabaseUnavailable("Database adapter is not set.") if not await self._database.connected(): - raise DatabaseUnavailable('Database is not connected.') + raise DatabaseUnavailable("Database is not connected.") await self._database.ready() return self._database diff --git a/sandpiper/user_data/database.py b/sandpiper/user_data/database.py index 134a025..debbf86 100644 --- a/sandpiper/user_data/database.py +++ b/sandpiper/user_data/database.py @@ -9,9 +9,10 @@ from sandpiper.common.time import TimezoneType, utc_now __all__ = [ - 'DEFAULT_PRIVACY', - 'DatabaseError', 'UserNotInDatabase', - 'Database', + "DEFAULT_PRIVACY", + "DatabaseError", + "UserNotInDatabase", + "Database", ] DEFAULT_PRIVACY = PrivacyType.PRIVATE @@ -26,7 +27,6 @@ class UserNotInDatabase(DatabaseError): class Database(metaclass=ABCMeta): - @abstractmethod async def connect(self): pass @@ -77,27 +77,19 @@ async def get_preferred_name(self, user_id: int) -> Optional[str]: pass @abstractmethod - async def set_preferred_name( - self, user_id: int, new_preferred_name: Optional[str] - ): + async def set_preferred_name(self, user_id: int, new_preferred_name: Optional[str]): pass @abstractmethod - async def get_privacy_preferred_name( - self, user_id: int - ) -> Optional[PrivacyType]: + async def get_privacy_preferred_name(self, user_id: int) -> Optional[PrivacyType]: pass @abstractmethod - async def set_privacy_preferred_name( - self, user_id: int, new_privacy: PrivacyType - ): + async def set_privacy_preferred_name(self, user_id: int, new_privacy: PrivacyType): pass @abstractmethod - async def find_users_by_preferred_name( - self, name: str - ) -> list[tuple[int, str]]: + async def find_users_by_preferred_name(self, name: str) -> list[tuple[int, str]]: pass # endregion @@ -116,9 +108,7 @@ async def get_privacy_pronouns(self, user_id: int) -> Optional[PrivacyType]: pass @abstractmethod - async def set_privacy_pronouns( - self, user_id: int, new_privacy: PrivacyType - ): + async def set_privacy_pronouns(self, user_id: int, new_privacy: PrivacyType): pass async def get_pronouns_parsed(self, user_id: int) -> list[Pronouns]: @@ -139,9 +129,7 @@ async def get_birthday(self, user_id: int) -> Optional[dt.date]: pass @abstractmethod - async def set_birthday( - self, user_id: int, new_birthday: Optional[dt.date] - ): + async def set_birthday(self, user_id: int, new_birthday: Optional[dt.date]): pass @abstractmethod @@ -149,16 +137,16 @@ async def get_privacy_birthday(self, user_id: int) -> Optional[PrivacyType]: pass @abstractmethod - async def set_privacy_birthday( - self, user_id: int, new_privacy: PrivacyType - ): + async def set_privacy_birthday(self, user_id: int, new_privacy: PrivacyType): pass @abstractmethod async def get_birthdays_range( - self, start: dt.date, end: dt.date, - max_last_notification_time: Optional[dt.date] = None - ) -> list[tuple[Annotated[int, 'user_id'], dt.date]]: + self, + start: dt.date, + end: dt.date, + max_last_notification_time: Optional[dt.date] = None, + ) -> list[tuple[Annotated[int, "user_id"], dt.date]]: """ Get a list of (user_id, birthday) for all users with birthdays between `start` and `end`, inclusive. If `start` is later than `end`, the check @@ -181,14 +169,12 @@ async def get_birthdays_range( # region Age @staticmethod - def _calculate_age( - birthday: dt.date, tz: TimezoneType, at_time: dt.datetime - ): + def _calculate_age(birthday: dt.date, tz: TimezoneType, at_time: dt.datetime): # The user's birthday in `at_time`'s year at midnight (localized to # their timezone) - birthday_this_year = tz.localize(dt.datetime( - at_time.year, birthday.month, birthday.day, 0, 0 - )) + birthday_this_year = tz.localize( + dt.datetime(at_time.year, birthday.month, birthday.day, 0, 0) + ) year_diff = at_time.year - birthday.year if at_time < birthday_this_year: # They haven't reached their birthday for this year @@ -226,9 +212,7 @@ async def get_timezone(self, user_id: int) -> Optional[TimezoneType]: pass @abstractmethod - async def set_timezone( - self, user_id: int, new_timezone: Optional[TimezoneType] - ): + async def set_timezone(self, user_id: int, new_timezone: Optional[TimezoneType]): pass @abstractmethod @@ -236,9 +220,7 @@ async def get_privacy_timezone(self, user_id: int) -> Optional[PrivacyType]: pass @abstractmethod - async def set_privacy_timezone( - self, user_id: int, new_privacy: PrivacyType - ): + async def set_privacy_timezone(self, user_id: int, new_privacy: PrivacyType): pass @abstractmethod @@ -253,23 +235,19 @@ async def get_last_birthday_notification(self, user_id: int) -> dt.datetime: pass @abstractmethod - async def set_last_birthday_notification( - self, user_id: int, new_date: dt.datetime - ): + async def set_last_birthday_notification(self, user_id: int, new_date: dt.datetime): pass # endregion # region Guild settings @abstractmethod - async def get_guild_birthday_channel( - self, guild_id: int - ) -> Optional[int]: + async def get_guild_birthday_channel(self, guild_id: int) -> Optional[int]: pass @abstractmethod async def set_guild_birthday_channel( - self, guild_id: int, new_birthday_channel: Optional[int] + self, guild_id: int, new_birthday_channel: Optional[int] ): pass diff --git a/sandpiper/user_data/database_sqlite.py b/sandpiper/user_data/database_sqlite.py index beebf4c..4bba0ba 100644 --- a/sandpiper/user_data/database_sqlite.py +++ b/sandpiper/user_data/database_sqlite.py @@ -9,7 +9,10 @@ import sqlalchemy as sa from sqlalchemy.exc import NoResultFound from sqlalchemy.ext.asyncio import ( - AsyncConnection, AsyncEngine, AsyncSession, create_async_engine + AsyncConnection, + AsyncEngine, + AsyncSession, + create_async_engine, ) from sqlalchemy.orm import sessionmaker @@ -51,9 +54,10 @@ async def connect(self): self._engine = create_async_engine( f"sqlite+aiosqlite:///{self.db_path}", echo=False, future=True ) - self._session_maker = cast(T_Sessionmaker, sessionmaker( - self._engine, expire_on_commit=False, class_=AsyncSession - )) + self._session_maker = cast( + T_Sessionmaker, + sessionmaker(self._engine, expire_on_commit=False, class_=AsyncSession), + ) await self._do_upgrades() @@ -79,28 +83,30 @@ async def _do_upgrades(self): revision = await alembic_utils.get_current_heads(self._engine) if revision: logger.info("Performing Alembic upgrade to head (may be a no-op)") - await alembic_utils.upgrade(self._engine, 'head') + await alembic_utils.upgrade(self._engine, "head") return logger.info("Database has no Alembic version") async with self._engine.begin() as conn: # Check the sqlite meta table for table definitions conn: AsyncConnection - no_tables = (await conn.execute(sa.text( - "SELECT 1 FROM sqlite_master LIMIT 1" - ))).first() is None - user_data_table_exists = (await conn.execute(sa.text( - "SELECT 1 FROM sqlite_master WHERE name = 'user_data' LIMIT 1" - ))).first() is not None + no_tables = ( + await conn.execute(sa.text("SELECT 1 FROM sqlite_master LIMIT 1")) + ).first() is None + user_data_table_exists = ( + await conn.execute( + sa.text( + "SELECT 1 FROM sqlite_master WHERE name = 'user_data' LIMIT 1" + ) + ) + ).first() is not None if no_tables: # This database is empty, we can create all and stamp as head - logger.info( - "Empty database; creating all and stamping as Alembic head" - ) + logger.info("Empty database; creating all and stamping as Alembic head") async with self._engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) - await alembic_utils.stamp(self._engine, 'head') + await alembic_utils.stamp(self._engine, "head") elif user_data_table_exists: # The database already existed but was not tracked yet by Alembic. @@ -110,9 +116,9 @@ async def _do_upgrades(self): "user_data table found; stamping with the appropriate " "Alembic revision to continue with upgrades" ) - await alembic_utils.stamp(self._engine, '91e18fdd475a') + await alembic_utils.stamp(self._engine, "91e18fdd475a") logger.info("Upgrading to head") - await alembic_utils.upgrade(self._engine, 'head') + await alembic_utils.upgrade(self._engine, "head") else: err_msg = ( @@ -131,9 +137,11 @@ async def _do_upgrades(self): @staticmethod async def _get_sandpiper_meta(session: AsyncSession) -> SandpiperMeta: try: - return (await session.execute( - sa.select(SandpiperMeta).where(SandpiperMeta.id == 0) - )).scalar_one() + return ( + await session.execute( + sa.select(SandpiperMeta).where(SandpiperMeta.id == 0) + ) + ).scalar_one() except NoResultFound: sandpiper_meta = SandpiperMeta(id=0) session.add(sandpiper_meta) @@ -141,12 +149,12 @@ async def _get_sandpiper_meta(session: AsyncSession) -> SandpiperMeta: @staticmethod async def _get_user( - session: AsyncSession, user_id: int, create_if_missing=True + session: AsyncSession, user_id: int, create_if_missing=True ) -> Optional[User]: try: - return (await session.execute( - sa.select(User).where(User.user_id == user_id) - )).scalar_one() + return ( + await session.execute(sa.select(User).where(User.user_id == user_id)) + ).scalar_one() except NoResultFound: if not create_if_missing: return None @@ -157,38 +165,37 @@ async def _get_user( @staticmethod async def _get_guild(session: AsyncSession, guild_id: int) -> Guild: try: - return (await session.execute( - sa.select(Guild).where(Guild.guild_id == guild_id) - )).scalar_one() + return ( + await session.execute( + sa.select(Guild).where(Guild.guild_id == guild_id) + ) + ).scalar_one() except NoResultFound: guild = Guild(guild_id=guild_id) session.add(guild) return guild - async def _get_user_field( - self, field_name: str, user_id: int - ) -> Optional[Any]: + async def _get_user_field(self, field_name: str, user_id: int) -> Optional[Any]: logger.info(f"Getting {field_name} (user_id={user_id})") async with self._session_maker() as session, session.begin(): try: - return (await session.execute( - sa.select(getattr(User, field_name)) - .where(User.user_id == user_id) - )).scalar_one() + return ( + await session.execute( + sa.select(getattr(User, field_name)).where( + User.user_id == user_id + ) + ) + ).scalar_one() except NoResultFound: raise UserNotInDatabase async def _set_user_field(self, field_name: str, user_id: int, value: Any): - logger.info( - f"Setting {field_name} (user_id={user_id}, new_value={value})" - ) + logger.info(f"Setting {field_name} (user_id={user_id}, new_value={value})") async with self._session_maker() as session, session.begin(): if value is None: # When using the delete command, it sends None. We don't want # to create a new user if they're just trying to delete data - user = await self._get_user( - session, user_id, create_if_missing=False - ) + user = await self._get_user(session, user_id, create_if_missing=False) if user is None: raise UserNotInDatabase else: @@ -196,20 +203,21 @@ async def _set_user_field(self, field_name: str, user_id: int, value: Any): setattr(user, field_name, value) async def _get_user_privacy_field( - self, field_name: str, user_id: int + self, field_name: str, user_id: int ) -> Optional[PrivacyType]: - logger.info( - f"Getting {field_name} privacy (user_id={user_id})" - ) + logger.info(f"Getting {field_name} privacy (user_id={user_id})") async with self._session_maker() as session, session.begin(): - privacy = (await session.execute( - sa.select(getattr(User, f"privacy_{field_name}")) - .where(User.user_id == user_id)) + privacy = ( + await session.execute( + sa.select(getattr(User, f"privacy_{field_name}")).where( + User.user_id == user_id + ) + ) ).scalar() return PrivacyType(privacy) if privacy is not None else None async def _set_user_privacy_field( - self, field_name: str, user_id: int, new_privacy: PrivacyType + self, field_name: str, user_id: int, new_privacy: PrivacyType ): logger.info( f"Setting {field_name} privacy (user_id={user_id} " @@ -225,15 +233,14 @@ async def _set_user_privacy_field( async def get_sandpiper_version(self) -> str: logger.info(f"Getting Sandpiper version") async with self._session_maker() as session, session.begin(): - return (await session.execute( - sa.select(SandpiperMeta.version) - .where(SandpiperMeta.id == 0) - )).scalar() + return ( + await session.execute( + sa.select(SandpiperMeta.version).where(SandpiperMeta.id == 0) + ) + ).scalar() async def set_sandpiper_version(self, new_version: str): - logger.info( - f"Setting Sandpiper version (new_value={new_version})" - ) + logger.info(f"Setting Sandpiper version (new_value={new_version})") async with self._session_maker() as session, session.begin(): sandpiper_meta = await self._get_sandpiper_meta(session) sandpiper_meta.version = new_version @@ -250,98 +257,76 @@ async def create_user(self, user_id: int): async def delete_user(self, user_id: int): logger.info(f"Deleting user (user_id={user_id})") async with self._session_maker() as session, session.begin(): - await session.execute( - sa.delete(User).where(User.user_id == user_id) - ) + await session.execute(sa.delete(User).where(User.user_id == user_id)) async def get_all_user_ids(self) -> list[int]: logger.info(f"Getting all user IDs") async with self._session_maker() as session, session.begin(): - return (await session.execute( - sa.select(User.user_id) - )).scalars().all() + return (await session.execute(sa.select(User.user_id))).scalars().all() # endregion # region Preferred name async def get_preferred_name(self, user_id: int) -> Optional[str]: - return await self._get_user_field('preferred_name', user_id) + return await self._get_user_field("preferred_name", user_id) - async def set_preferred_name( - self, user_id: int, new_preferred_name: Optional[str] - ): - await self._set_user_field( - 'preferred_name', user_id, new_preferred_name - ) + async def set_preferred_name(self, user_id: int, new_preferred_name: Optional[str]): + await self._set_user_field("preferred_name", user_id, new_preferred_name) - async def get_privacy_preferred_name( - self, user_id: int - ) -> Optional[PrivacyType]: - return await self._get_user_privacy_field('preferred_name', user_id) + async def get_privacy_preferred_name(self, user_id: int) -> Optional[PrivacyType]: + return await self._get_user_privacy_field("preferred_name", user_id) - async def set_privacy_preferred_name( - self, user_id: int, new_privacy: PrivacyType - ): - await self._set_user_privacy_field( - 'preferred_name', user_id, new_privacy - ) + async def set_privacy_preferred_name(self, user_id: int, new_privacy: PrivacyType): + await self._set_user_privacy_field("preferred_name", user_id, new_privacy) - async def find_users_by_preferred_name( - self, name: str - ) -> list[tuple[int, str]]: + async def find_users_by_preferred_name(self, name: str) -> list[tuple[int, str]]: logger.info(f"Finding users by preferred name (name={name})") - if name == '': + if name == "": logger.info("Skipping empty string") return [] async with self._session_maker() as session, session.begin(): - return (await session.execute( - sa.select(User.user_id, User.preferred_name) - .where(User.preferred_name.like(f'%{name}%')) - .where(User.privacy_preferred_name == PrivacyType.PUBLIC) - )).all() + return ( + await session.execute( + sa.select(User.user_id, User.preferred_name) + .where(User.preferred_name.like(f"%{name}%")) + .where(User.privacy_preferred_name == PrivacyType.PUBLIC) + ) + ).all() # endregion # region Pronouns async def get_pronouns(self, user_id: int) -> Optional[str]: - return await self._get_user_field('pronouns', user_id) + return await self._get_user_field("pronouns", user_id) async def set_pronouns(self, user_id: int, new_pronouns: Optional[str]): - await self._set_user_field('pronouns', user_id, new_pronouns) + await self._set_user_field("pronouns", user_id, new_pronouns) async def get_privacy_pronouns(self, user_id: int) -> Optional[PrivacyType]: - return await self._get_user_privacy_field('pronouns', user_id) + return await self._get_user_privacy_field("pronouns", user_id) - async def set_privacy_pronouns( - self, user_id: int, new_privacy: PrivacyType - ): - await self._set_user_privacy_field('pronouns', user_id, new_privacy) + async def set_privacy_pronouns(self, user_id: int, new_privacy: PrivacyType): + await self._set_user_privacy_field("pronouns", user_id, new_privacy) # endregion # region Birthday async def get_birthday(self, user_id: int) -> Optional[dt.date]: - return await self._get_user_field('birthday', user_id) + return await self._get_user_field("birthday", user_id) - async def set_birthday( - self, user_id: int, new_birthday: Optional[dt.date] - ): - await self._set_user_field('birthday', user_id, new_birthday) + async def set_birthday(self, user_id: int, new_birthday: Optional[dt.date]): + await self._set_user_field("birthday", user_id, new_birthday) async def get_privacy_birthday(self, user_id: int) -> Optional[PrivacyType]: - return await self._get_user_privacy_field('birthday', user_id) + return await self._get_user_privacy_field("birthday", user_id) - async def set_privacy_birthday( - self, user_id: int, new_privacy: PrivacyType - ): - await self._set_user_privacy_field('birthday', user_id, new_privacy) + async def set_privacy_birthday(self, user_id: int, new_privacy: PrivacyType): + await self._set_user_privacy_field("birthday", user_id, new_privacy) @staticmethod def _birthday_range_predicate(start: dt.date, end: dt.date): - wrap = ( - (32 * start.month + start.day) > (32 * end.month + end.day) - ) + wrap = (32 * start.month + start.day) > (32 * end.month + end.day) def f(d: dt.date) -> bool: if wrap: @@ -374,9 +359,11 @@ def f(d: dt.date) -> bool: return f async def get_birthdays_range( - self, start: dt.date, end: dt.date, - max_last_notification_time: Optional[dt.date] = None - ) -> list[tuple[Annotated[int, 'user_id'], dt.date]]: + self, + start: dt.date, + end: dt.date, + max_last_notification_time: Optional[dt.date] = None, + ) -> list[tuple[Annotated[int, "user_id"], dt.date]]: logger.info( f"Getting all birthdays between {start.day}-{start.month} and " f"{end.day}-{end.month}" @@ -397,43 +384,41 @@ async def get_birthdays_range( ) birthdays_unfiltered = (await session.execute(stmt)).all() - return list(filter( - lambda r: self._birthday_range_predicate(start, end)(r[1]), - birthdays_unfiltered - )) + return list( + filter( + lambda r: self._birthday_range_predicate(start, end)(r[1]), + birthdays_unfiltered, + ) + ) # endregion # region Age async def get_privacy_age(self, user_id: int) -> Optional[PrivacyType]: - return await self._get_user_privacy_field('age', user_id) + return await self._get_user_privacy_field("age", user_id) async def set_privacy_age(self, user_id: int, new_privacy: PrivacyType): - await self._set_user_privacy_field('age', user_id, new_privacy) + await self._set_user_privacy_field("age", user_id, new_privacy) # endregion # region Timezone async def get_timezone(self, user_id: int) -> Optional[TimezoneType]: - tz_name = await self._get_user_field('timezone', user_id) + tz_name = await self._get_user_field("timezone", user_id) if tz_name: return pytz.timezone(tz_name) return None - async def set_timezone( - self, user_id: int, new_timezone: Optional[TimezoneType] - ): + async def set_timezone(self, user_id: int, new_timezone: Optional[TimezoneType]): if new_timezone: new_timezone = new_timezone.zone - await self._set_user_field('timezone', user_id, new_timezone) + await self._set_user_field("timezone", user_id, new_timezone) async def get_privacy_timezone(self, user_id: int) -> Optional[PrivacyType]: - return await self._get_user_privacy_field('timezone', user_id) + return await self._get_user_privacy_field("timezone", user_id) - async def set_privacy_timezone( - self, user_id: int, new_privacy: PrivacyType - ): - await self._set_user_privacy_field('timezone', user_id, new_privacy) + async def set_privacy_timezone(self, user_id: int, new_privacy: PrivacyType): + await self._set_user_privacy_field("timezone", user_id, new_privacy) async def get_all_timezones(self) -> list[tuple[int, TimezoneType]]: logger.info(f"Getting all user timezones") @@ -452,30 +437,25 @@ async def get_all_timezones(self) -> list[tuple[int, TimezoneType]]: # region Other user stuff async def get_last_birthday_notification(self, user_id: int) -> dt.datetime: - return await self._get_user_field('last_birthday_notification', user_id) + return await self._get_user_field("last_birthday_notification", user_id) - async def set_last_birthday_notification( - self, user_id: int, new_date: dt.datetime - ): - await self._set_user_field( - 'last_birthday_notification', user_id, new_date - ) + async def set_last_birthday_notification(self, user_id: int, new_date: dt.datetime): + await self._set_user_field("last_birthday_notification", user_id, new_date) # endregion # region Guilds - async def get_guild_birthday_channel( - self, guild_id: int - ) -> Optional[str]: + async def get_guild_birthday_channel(self, guild_id: int) -> Optional[str]: logger.info(f"Getting guild birthday_channel (guild_id={guild_id})") async with self._session_maker() as session, session.begin(): - return (await session.execute( - sa.select(Guild.birthday_channel) - .where(Guild.guild_id == guild_id) - )).scalar() + return ( + await session.execute( + sa.select(Guild.birthday_channel).where(Guild.guild_id == guild_id) + ) + ).scalar() async def set_guild_birthday_channel( - self, guild_id: int, new_birthday_channel: Optional[int] + self, guild_id: int, new_birthday_channel: Optional[int] ): logger.info( f"Setting guild birthday_channel (guild_id={guild_id}, " diff --git a/sandpiper/user_data/models/_types.py b/sandpiper/user_data/models/_types.py index 424eb50..54c9779 100644 --- a/sandpiper/user_data/models/_types.py +++ b/sandpiper/user_data/models/_types.py @@ -1,15 +1,15 @@ import sqlalchemy.types as types -__all__ = ['Snowflake'] +__all__ = ["Snowflake"] class Snowflake(types.TypeDecorator): - ''' + """ SQLite only stores (signed) int64, so we need to coerce Snowflakes to strings for storage. 20 == the number of digits in (1<<64) - 1 - ''' + """ impl = types.String(20) diff --git a/sandpiper/user_data/models/guild.py b/sandpiper/user_data/models/guild.py index f647184..10e9803 100644 --- a/sandpiper/user_data/models/guild.py +++ b/sandpiper/user_data/models/guild.py @@ -5,8 +5,8 @@ class Guild(Base): - __tablename__ = 'guilds' - __mapper_args__ = {'eager_defaults': True} + __tablename__ = "guilds" + __mapper_args__ = {"eager_defaults": True} guild_id = Column(Snowflake, primary_key=True) birthday_channel = Column(Snowflake) diff --git a/sandpiper/user_data/models/sandpiper_meta.py b/sandpiper/user_data/models/sandpiper_meta.py index 75c681a..a0aff4e 100644 --- a/sandpiper/user_data/models/sandpiper_meta.py +++ b/sandpiper/user_data/models/sandpiper_meta.py @@ -5,8 +5,8 @@ class SandpiperMeta(Base): - __tablename__ = 'sandpiper_meta' - __mapper_args__ = {'eager_defaults': True} + __tablename__ = "sandpiper_meta" + __mapper_args__ = {"eager_defaults": True} # Not necessarily required, but the ORM wants a primary key column, so this # is just a dummy column for now diff --git a/sandpiper/user_data/models/user.py b/sandpiper/user_data/models/user.py index 324da51..c4537be 100644 --- a/sandpiper/user_data/models/user.py +++ b/sandpiper/user_data/models/user.py @@ -9,11 +9,9 @@ class User(Base): - __tablename__ = 'users' - __table_args__ = ( - Index('index_users_preferred_name', 'preferred_name'), - ) - __mapper_args__ = {'eager_defaults': True} + __tablename__ = "users" + __table_args__ = (Index("index_users_preferred_name", "preferred_name"),) + __mapper_args__ = {"eager_defaults": True} user_id = Column(Snowflake, primary_key=True) preferred_name = Column(sa.String) @@ -22,24 +20,19 @@ class User(Base): timezone = Column(sa.String) privacy_preferred_name = Column( - sa.SmallInteger, nullable=False, - server_default=sa.text(str(DEFAULT_PRIVACY)) + sa.SmallInteger, nullable=False, server_default=sa.text(str(DEFAULT_PRIVACY)) ) privacy_pronouns = Column( - sa.SmallInteger, nullable=False, - server_default=sa.text(str(DEFAULT_PRIVACY)) + sa.SmallInteger, nullable=False, server_default=sa.text(str(DEFAULT_PRIVACY)) ) privacy_birthday = Column( - sa.SmallInteger, nullable=False, - server_default=sa.text(str(DEFAULT_PRIVACY)) + sa.SmallInteger, nullable=False, server_default=sa.text(str(DEFAULT_PRIVACY)) ) privacy_age = Column( - sa.SmallInteger, nullable=False, - server_default=sa.text(str(DEFAULT_PRIVACY)) + sa.SmallInteger, nullable=False, server_default=sa.text(str(DEFAULT_PRIVACY)) ) privacy_timezone = Column( - sa.SmallInteger, nullable=False, - server_default=sa.text(str(DEFAULT_PRIVACY)) + sa.SmallInteger, nullable=False, server_default=sa.text(str(DEFAULT_PRIVACY)) ) last_birthday_notification = Column(sa.DateTime, nullable=True) diff --git a/sandpiper/user_data/pronouns.py b/sandpiper/user_data/pronouns.py index f41b304..f9e4f30 100644 --- a/sandpiper/user_data/pronouns.py +++ b/sandpiper/user_data/pronouns.py @@ -2,15 +2,15 @@ from dataclasses import astuple, dataclass import re -__all__ = ['Pronouns', 'common_pronouns'] +__all__ = ["Pronouns", "common_pronouns"] -_slashed_group_pattern = re.compile(r'[a-zA-Z]+(?: *[/\\] *[a-zA-Z]+)*') +_slashed_group_pattern = re.compile(r"[a-zA-Z]+(?: *[/\\] *[a-zA-Z]+)*") @dataclass class Pronouns: - subjective: str = 'they' + subjective: str = "they" objective: str = None determiner: str = None possessive: str = None @@ -18,26 +18,26 @@ class Pronouns: def __post_init__(self): if self.objective is None: - if self.subjective == 'they': - self.objective = 'them' + if self.subjective == "they": + self.objective = "them" else: self.objective = self.subjective if self.determiner is None: - if self.subjective == 'they': - self.determiner = 'their' + if self.subjective == "they": + self.determiner = "their" else: self.determiner = f"{self.subjective}s" if self.possessive is None: - if self.subjective == 'they': - self.possessive = 'theirs' + if self.subjective == "they": + self.possessive = "theirs" else: self.possessive = f"{self.subjective}s" if self.reflexive is None: - if self.subjective == 'they': - self.reflexive = 'themself' + if self.subjective == "they": + self.reflexive = "themself" else: self.reflexive = f"{self.subjective}self" @@ -51,18 +51,18 @@ def __contains__(self, pronoun: str): ) def __str__(self): - return '/'.join(self.to_tuple()) + return "/".join(self.to_tuple()) @property def to_be_conjugation(self): # Conjugation following Elverson (ey/em) pronouns can be either is or are - if self.subjective == 'they': - return 'are' - return 'is' + if self.subjective == "they": + return "are" + return "is" @property def subjective_to_be_contraction(self): - if self.subjective == 'they': + if self.subjective == "they": return "they're" return f"{self.subjective}'s" @@ -80,7 +80,7 @@ def parse(cls, string: str) -> list[Pronouns]: They/he Xe/xem/xyr/xyrs/xemself """ - if string == '': + if string == "": return [Pronouns()] out = [] @@ -88,7 +88,7 @@ def parse(cls, string: str) -> list[Pronouns]: for slashed_group in _slashed_group_pattern.finditer(string): # Iterate through groups of slashed pronouns ("she/her they/them") first = True - split = iter(re.split(r' *[\\/] *', slashed_group.group())) + split = iter(re.split(r" *[\\/] *", slashed_group.group())) for pronoun in map(lambda x: x.lower(), split): pronoun = pronoun # Infer set of pronouns from this one @@ -117,25 +117,25 @@ def parse(cls, string: str) -> list[Pronouns]: common_pronouns = { - 'they': Pronouns('they', 'them', 'their', 'theirs', 'themself'), - 'she': Pronouns('she', 'her', 'her', 'hers', 'herself'), - 'he': Pronouns('he', 'him', 'his', 'his', 'himself'), - 'it': Pronouns('it', 'it', 'its', 'its', 'itself'), - 'one': Pronouns('one', 'one', "one's", "one's", 'oneself'), - 'thon': Pronouns('thon', 'thon', 'thons', "thon's", 'thonself'), - 'ae': Pronouns('ae', 'aer', 'aer', 'aers', 'aerself'), - 'co': Pronouns('co', 'co', 'cos', "co's", 'coself'), - 've': Pronouns('ve', 'ver', 'vis', 'vers', 'verself'), - 'vi': Pronouns('vi', 'vir', 'vis', 'virs', 'virself'), - 'xe': Pronouns('xe', 'xem', 'xyr', 'xyrs', 'xemself'), - 'per': Pronouns('per', 'per', 'per', 'pers', 'perself'), # person - 'ey': Pronouns('ey', 'em', 'eir', 'eirs', 'emself'), # Elverson - 'hu': Pronouns('hu', 'hum', 'hus', 'hus', 'huself'), # humanist + "they": Pronouns("they", "them", "their", "theirs", "themself"), + "she": Pronouns("she", "her", "her", "hers", "herself"), + "he": Pronouns("he", "him", "his", "his", "himself"), + "it": Pronouns("it", "it", "its", "its", "itself"), + "one": Pronouns("one", "one", "one's", "one's", "oneself"), + "thon": Pronouns("thon", "thon", "thons", "thon's", "thonself"), + "ae": Pronouns("ae", "aer", "aer", "aers", "aerself"), + "co": Pronouns("co", "co", "cos", "co's", "coself"), + "ve": Pronouns("ve", "ver", "vis", "vers", "verself"), + "vi": Pronouns("vi", "vir", "vis", "virs", "virself"), + "xe": Pronouns("xe", "xem", "xyr", "xyrs", "xemself"), + "per": Pronouns("per", "per", "per", "pers", "perself"), # person + "ey": Pronouns("ey", "em", "eir", "eirs", "emself"), # Elverson + "hu": Pronouns("hu", "hum", "hus", "hus", "huself"), # humanist # Conflicts with e below, so it's disambiguated - 'e_spivak': Pronouns('e', 'em', 'eir', 'eirs', 'emself'), # Spivak - 'ze': Pronouns('ze', 'zir', 'zir', 'zirs', 'zirself'), - 'fae': Pronouns('fae', 'faer', 'faer', 'faers', 'faerself'), - 'e': Pronouns('e', 'em', 'es', 'ems', 'emself'), + "e_spivak": Pronouns("e", "em", "eir", "eirs", "emself"), # Spivak + "ze": Pronouns("ze", "zir", "zir", "zirs", "zirself"), + "fae": Pronouns("fae", "faer", "faer", "faers", "faerself"), + "e": Pronouns("e", "em", "es", "ems", "emself"), } diff --git a/sandpiper/user_data/test_pronouns.py b/sandpiper/user_data/test_pronouns.py index bac12bb..bf99e61 100644 --- a/sandpiper/user_data/test_pronouns.py +++ b/sandpiper/user_data/test_pronouns.py @@ -2,103 +2,85 @@ class TestOneClass: - def test_one_case(self): - pronouns = Pronouns.parse('He') - assert pronouns == [ - Pronouns('he', 'him', 'his', 'his', 'himself') - ] + pronouns = Pronouns.parse("He") + assert pronouns == [Pronouns("he", "him", "his", "his", "himself")] def test_two_cases(self): - pronouns = Pronouns.parse('She/her') - assert pronouns == [ - Pronouns('she', 'her', 'her', 'hers', 'herself') - ] + pronouns = Pronouns.parse("She/her") + assert pronouns == [Pronouns("she", "her", "her", "hers", "herself")] def test_three_cases(self): - pronouns = Pronouns.parse('They/them/their') - assert pronouns == [ - Pronouns('they', 'them', 'their', 'theirs', 'themself') - ] + pronouns = Pronouns.parse("They/them/their") + assert pronouns == [Pronouns("they", "them", "their", "theirs", "themself")] def test_five_cases(self): - pronouns = Pronouns.parse('Xe/xem/xyr/xyrs/xemself') - assert pronouns == [ - Pronouns('xe', 'xem', 'xyr', 'xyrs', 'xemself') - ] + pronouns = Pronouns.parse("Xe/xem/xyr/xyrs/xemself") + assert pronouns == [Pronouns("xe", "xem", "xyr", "xyrs", "xemself")] class TestTwoClasses: - def test_one_case(self): - pronouns = Pronouns.parse('They/he') + pronouns = Pronouns.parse("They/he") assert pronouns == [ - Pronouns('they', 'them', 'their', 'theirs', 'themself'), - Pronouns('he', 'him', 'his', 'his', 'himself') + Pronouns("they", "them", "their", "theirs", "themself"), + Pronouns("he", "him", "his", "his", "himself"), ] def test_two_cases(self): - pronouns = Pronouns.parse('She/her he/him') + pronouns = Pronouns.parse("She/her he/him") assert pronouns == [ - Pronouns('she', 'her', 'her', 'hers', 'herself'), - Pronouns('he', 'him', 'his', 'his', 'himself') + Pronouns("she", "her", "her", "hers", "herself"), + Pronouns("he", "him", "his", "his", "himself"), ] class TestUniqueClass: - def test_one_case(self): - pronouns = Pronouns.parse('Pup') - assert pronouns == [ - Pronouns('pup', 'pup', 'pups', 'pups', 'pupself') - ] + pronouns = Pronouns.parse("Pup") + assert pronouns == [Pronouns("pup", "pup", "pups", "pups", "pupself")] def test_three_cases(self): - pronouns = Pronouns.parse('Unique/missing/some') + pronouns = Pronouns.parse("Unique/missing/some") assert pronouns == [ - Pronouns('unique', 'missing', 'some', 'uniques', 'uniqueself') + Pronouns("unique", "missing", "some", "uniques", "uniqueself") ] def test_five_cases(self): - pronouns = Pronouns.parse('Unique/but/not/missing/any') - assert pronouns == [ - Pronouns('unique', 'but', 'not', 'missing', 'any') - ] + pronouns = Pronouns.parse("Unique/but/not/missing/any") + assert pronouns == [Pronouns("unique", "but", "not", "missing", "any")] def test_five_cases_mixed_with_known(self): - pronouns = Pronouns.parse('He/pup/she') + pronouns = Pronouns.parse("He/pup/she") assert pronouns == [ - Pronouns('he', 'him', 'his', 'his', 'himself'), - Pronouns('pup', 'pup', 'pups', 'pups', 'pupself'), - Pronouns('she', 'her', 'her', 'hers', 'herself') + Pronouns("he", "him", "his", "his", "himself"), + Pronouns("pup", "pup", "pups", "pups", "pupself"), + Pronouns("she", "her", "her", "hers", "herself"), ] class TestMisc: - def test_no_args(self): - pronouns = Pronouns.parse('') - assert pronouns == [ - Pronouns('they', 'them', 'their', 'theirs', 'themself') - ] + pronouns = Pronouns.parse("") + assert pronouns == [Pronouns("they", "them", "their", "theirs", "themself")] def test_backslash(self): - pronouns = Pronouns.parse('she\\he') + pronouns = Pronouns.parse("she\\he") assert pronouns == [ - Pronouns('she', 'her', 'her', 'hers', 'herself'), - Pronouns('he', 'him', 'his', 'his', 'himself') + Pronouns("she", "her", "her", "hers", "herself"), + Pronouns("he", "him", "his", "his", "himself"), ] def test_spaces(self): - pronouns = Pronouns.parse('she / he') + pronouns = Pronouns.parse("she / he") assert pronouns == [ - Pronouns('she', 'her', 'her', 'hers', 'herself'), - Pronouns('he', 'him', 'his', 'his', 'himself') + Pronouns("she", "her", "her", "hers", "herself"), + Pronouns("he", "him", "his", "his", "himself"), ] def test_repeats(self): - pronouns = Pronouns.parse('She/her he/him she/her') + pronouns = Pronouns.parse("She/her he/him she/her") assert pronouns == [ - Pronouns('she', 'her', 'her', 'hers', 'herself'), - Pronouns('he', 'him', 'his', 'his', 'himself') + Pronouns("she", "her", "her", "hers", "herself"), + Pronouns("he", "him", "his", "his", "himself"), ] diff --git a/yappi_profile_tests.py b/yappi_profile_tests.py index eca97af..112ecc0 100644 --- a/yappi_profile_tests.py +++ b/yappi_profile_tests.py @@ -1,63 +1,57 @@ -import sys -from typing import Optional - -import pytest -import yappi - - -def print_all(stats, out=sys.stdout, limit: Optional[int] = 10): - if stats.empty(): - return - columns = [ - ('name', 128), - ('ncall', 10), - ('ttot', 8), - ('tsub', 8), - ('tavg', 8) - ] - for name, length in columns: - # noinspection PyStringFormat - print(f"%-{length+2}s" % name, end='', file=out) - print('', file=out) - - columns = {idx: i for idx, i in enumerate(columns)} - if limit: - stats = stats[:limit] - for stat in stats: - stat._print(out, columns) - - -def filter_callback(fn_stat: yappi.YFuncStat): - return ( - 'pytest' not in fn_stat.module - and 'pluggy' not in fn_stat.module - and 'mock' not in fn_stat.module - ) - - -def main( - test_module: str, test_name: Optional[str] = None, - sort_type: str = 'ttot' -): - yappi.set_clock_type('WALL') - with yappi.run(): - pytest_args = ['--pyargs', test_module] - if test_name is not None: - pytest_args += ['-k', test_name] - pytest.main(pytest_args) - fn_stats = yappi.get_func_stats(filter_callback=filter_callback) - fn_stats.sort(sort_type) - print('\n==== Profiling stats ====\n') - print_all(fn_stats, limit=40) - - -if __name__ == '__main__': - if len(sys.argv) == 2 and sys.argv[1] == '-h': - print( - "Usage: " - "yappi_profile_tests.py [test_module] [test_name] [sort_type]" - ) - elif 2 <= len(sys.argv) <= 4: - main(*sys.argv[1:]) - else: - main('sandpiper') +import sys +from typing import Optional + +import pytest +import yappi + + +def print_all(stats, out=sys.stdout, limit: Optional[int] = 10): + if stats.empty(): + return + columns = [ + ("name", 128), + ("ncall", 10), + ("ttot", 8), + ("tsub", 8), + ("tavg", 8), + ] + for name, length in columns: + # noinspection PyStringFormat + print(f"%-{length+2}s" % name, end="", file=out) + print("", file=out) + + columns = {idx: i for idx, i in enumerate(columns)} + if limit: + stats = stats[:limit] + for stat in stats: + stat._print(out, columns) + + +def filter_callback(fn_stat: yappi.YFuncStat): + return ( + "pytest" not in fn_stat.module + and "pluggy" not in fn_stat.module + and "mock" not in fn_stat.module + ) + + +def main(test_module: str, test_name: Optional[str] = None, sort_type: str = "ttot"): + yappi.set_clock_type("WALL") + with yappi.run(): + pytest_args = ["--pyargs", test_module] + if test_name is not None: + pytest_args += ["-k", test_name] + pytest.main(pytest_args) + fn_stats = yappi.get_func_stats(filter_callback=filter_callback) + fn_stats.sort(sort_type) + print("\n==== Profiling stats ====\n") + print_all(fn_stats, limit=40) + + +if __name__ == "__main__": + if len(sys.argv) == 2 and sys.argv[1] == "-h": + print("Usage: " "yappi_profile_tests.py [test_module] [test_name] [sort_type]") + elif 2 <= len(sys.argv) <= 4: + main(*sys.argv[1:]) + else: + main("sandpiper") From e6fcd6473afb7b3c098779e45ea725559481e847 Mon Sep 17 00:00:00 2001 From: Phanabani Date: Thu, 10 Nov 2022 23:48:10 -0500 Subject: [PATCH 07/33] Remove redundant parentheses. --- sandpiper/bios/cog.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sandpiper/bios/cog.py b/sandpiper/bios/cog.py index 39cb7a4..883fa81 100644 --- a/sandpiper/bios/cog.py +++ b/sandpiper/bios/cog.py @@ -5,12 +5,12 @@ from discord.ext.commands import BadArgument import discord.ext.commands as commands -from .strings import * from sandpiper.birthdays import Birthdays from sandpiper.common.discord import * from sandpiper.common.embeds import * from sandpiper.common.time import format_date, fuzzy_match_timezone from sandpiper.user_data import * +from .strings import * __all__ = ["Bios"] @@ -231,7 +231,7 @@ async def privacy_name(self, ctx: commands.Context, new_privacy: privacy_handler @privacy.command( name="pronouns", brief="Set pronouns privacy.", - help=("Set the privacy of your pronouns to either 'private' or 'public'."), + help="Set the privacy of your pronouns to either 'private' or 'public'.", example="privacy pronouns public", ) async def privacy_pronouns( @@ -246,7 +246,7 @@ async def privacy_pronouns( @privacy.command( name="birthday", brief="Set birthday privacy.", - help=("Set the privacy of your birthday to either 'private' or 'public'."), + help="Set the privacy of your birthday to either 'private' or 'public'.", example="privacy birthday public", ) async def privacy_birthday( @@ -275,7 +275,7 @@ async def privacy_birthday( @privacy.command( name="age", brief="Set age privacy.", - help=("Set the privacy of your age to either 'private' or 'public'."), + help="Set the privacy of your age to either 'private' or 'public'.", example="privacy age public", ) async def privacy_age(self, ctx: commands.Context, new_privacy: privacy_handler): @@ -298,7 +298,7 @@ async def privacy_age(self, ctx: commands.Context, new_privacy: privacy_handler) @privacy.command( name="timezone", brief="Set timezone privacy.", - help=("Set the privacy of your timezone to either 'private' or 'public'."), + help="Set the privacy of your timezone to either 'private' or 'public'.", example="privacy timezone public", ) async def privacy_timezone( @@ -688,7 +688,7 @@ async def server(self, ctx: commands.Context): name="birthday_channel", invoke_without_command=False, brief="Birthday notification channel commands", - help=("Commands for managing the birthday notification channel"), + help="Commands for managing the birthday notification channel", ) async def server_birthday_channel(self, ctx: commands.Context): pass From 5973d025411252ec1993bcce00e649bb0d4f1095 Mon Sep 17 00:00:00 2001 From: Phanabani Date: Thu, 10 Nov 2022 23:48:31 -0500 Subject: [PATCH 08/33] Remove unused imports. --- sandpiper/common/test_misc.py | 2 -- .../versions/e9cf36d27e9d_rename_user_data_table_to_users.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/sandpiper/common/test_misc.py b/sandpiper/common/test_misc.py index 3fc6c41..8d1415d 100644 --- a/sandpiper/common/test_misc.py +++ b/sandpiper/common/test_misc.py @@ -1,5 +1,3 @@ -import pytest - from sandpiper.common.misc import listify diff --git a/sandpiper/user_data/alembic/versions/e9cf36d27e9d_rename_user_data_table_to_users.py b/sandpiper/user_data/alembic/versions/e9cf36d27e9d_rename_user_data_table_to_users.py index 1d1c013..ffd84a1 100644 --- a/sandpiper/user_data/alembic/versions/e9cf36d27e9d_rename_user_data_table_to_users.py +++ b/sandpiper/user_data/alembic/versions/e9cf36d27e9d_rename_user_data_table_to_users.py @@ -6,8 +6,6 @@ """ from alembic import op -import sqlalchemy as sa - # revision identifiers, used by Alembic. revision = "e9cf36d27e9d" From bc7c85da044b67e9401081af95f83c6f1971aff7 Mon Sep 17 00:00:00 2001 From: Phanabani Date: Fri, 11 Nov 2022 00:27:12 -0500 Subject: [PATCH 09/33] Fix table formatting. --- docs/config.md | 4 +- docs/default_unit_mappings.md | 118 +++++++++++++++++----------------- 2 files changed, 60 insertions(+), 62 deletions(-) diff --git a/docs/config.md b/docs/config.md index f44eda0..9729276 100644 --- a/docs/config.md +++ b/docs/config.md @@ -11,8 +11,8 @@ optional. Fields in the root JSON object. -| Key | Type | Value | -|-------------|--------|-------------------------------------------------------------------------------------------| +| Key | Type | Value | +|-------------|----------|-------------------------------------------------------------------------------------------| | `bot_token` | `string` | Your [Discord bot's](https://discord.com/developers/docs/topics/oauth2#bots) access token | ### bot diff --git a/docs/default_unit_mappings.md b/docs/default_unit_mappings.md index e4cec3b..d9b3b0b 100644 --- a/docs/default_unit_mappings.md +++ b/docs/default_unit_mappings.md @@ -9,97 +9,95 @@ map only one way for simplicity (Kelvin -> Celsius). The units in each row will convert to each other. ### Length -Metric | Imperial ------- | -------- -Kilometre `km` | Mile `mi` -Metre `m` | Foot `ft or '` -Centimetre `cm` | Inch `in or "` +| Metric | Imperial | +|-----------------|----------------| +| Kilometre `km` | Mile `mi` | +| Metre `m` | Foot `ft or '` | +| Centimetre `cm` | Inch `in or "` | ### Area -Metric | Imperial ------- | -------- -Hectare `ha` | Acre `acre` +| Metric | Imperial | +|--------------|-------------| +| Hectare `ha` | Acre `acre` | ### Speed -Metric | Imperial ------- | -------- -Kilometre per hour `kph or km/h` | Mile per hour `mph or mi/h` +| Metric | Imperial | +|----------------------------------|-----------------------------| +| Kilometre per hour `kph or km/h` | Mile per hour `mph or mi/h` | ### Mass -Metric | Imperial ------- | -------- -Gram `g` | Ounce `oz` -Kilogram `kg` | Pound `lb or lbs` +| Metric | Imperial | +|---------------|-------------------| +| Gram `g` | Ounce `oz` | +| Kilogram `kg` | Pound `lb or lbs` | ### Volume -Metric | Imperial ------- | -------- -Litre `L` | Gallon (US) `gal` -Millilitre `ml` | Cup `cup` +| Metric | Imperial | +|-----------------|-------------------| +| Litre `L` | Gallon (US) `gal` | +| Millilitre `ml` | Cup `cup` | ### Pressure -Metric | Imperial ------- | -------- -Pascal `pascal` | Pound\[-force] per square inch `psi` +| Metric | Imperial | +|-----------------|--------------------------------------| +| Pascal `pascal` | Pound\[-force] per square inch `psi` | ### Temperature -Metric | Imperial ------- | -------- -Celsius `C or degC or °C` | Fahrenheit `F or degF or °F` +| Metric | Imperial | +|---------------------------|------------------------------| +| Celsius `C or degC or °C` | Fahrenheit `F or degF or °F` | ### Energy -Metric | Imperial ------- | -------- -Joule `J` | Foot pound `ft_lb or foot_pound` +| Metric | Imperial | +|-----------|----------------------------------| +| Joule `J` | Foot pound `ft_lb or foot_pound` | ### Angle -Radian | Degree ------- | -------- -Radian `rad` | Degree `deg` - - +| Radian | Degree | +|--------------|--------------| +| Radian `rad` | Degree `deg` | ## One-way Only the units on the left will map to the unit on the right. ### Length -From | To ----- | --- -Yard `yd` | Metre `m` +| From | To | +|-----------|-----------| +| Yard `yd` | Metre `m` | ### Speed -From | To ----- | --- -Metre per second `mps or m/s` | Kilometre per hour `kph or km/h` -Foot per second `ft/s` | Mile per hour `mph or mi/h` +| From | To | +|-------------------------------|----------------------------------| +| Metre per second `mps or m/s` | Kilometre per hour `kph or km/h` | +| Foot per second `ft/s` | Mile per hour `mph or mi/h` | ### Mass -From | To ----- | --- -Stone `stone` | Kilogram `kg` +| From | To | +|---------------|---------------| +| Stone `stone` | Kilogram `kg` | ### Volume -From | To ----- | --- -Pint (US) `pint` | Litre `L` -Fluid ounce (US) `floz` | Millilitre `ml` +| From | To | +|-------------------------|-----------------| +| Pint (US) `pint` | Litre `L` | +| Fluid ounce (US) `floz` | Millilitre `ml` | ### Pressure -From | To ----- | --- -Atmosphere `atm` | Pound\[-force] per square inch `psi` -Bar `bar` | Pound\[-force] force per square inch `psi` +| From | To | +|------------------|--------------------------------------------| +| Atmosphere `atm` | Pound\[-force] per square inch `psi` | +| Bar `bar` | Pound\[-force] force per square inch `psi` | ### Temperature -From | To ----- | --- -Kelvin `K` | Celsius `C or degC or °C` +| From | To | +|------------|---------------------------| +| Kelvin `K` | Celsius `C or degC or °C` | ### Time -From | To ----- | --- -Second `s or sec` | Minute `min` -Minute `min` | Hour `h or hr` -Hour `h or hr` | Day `day` -Day `day` | Week `week` +| From | To | +|-------------------|----------------| +| Second `s or sec` | Minute `min` | +| Minute `min` | Hour `h or hr` | +| Hour `h or hr` | Day `day` | +| Day `day` | Week `week` | From e20ada4e54d5776f48b41494e9ea2e89374f00d0 Mon Sep 17 00:00:00 2001 From: Phanabani Date: Fri, 11 Nov 2022 01:05:45 -0500 Subject: [PATCH 10/33] Upgrade discord.py to v2. --- poetry.lock | 277 ++++++++++++++++++++++++++++++++++++++----------- pyproject.toml | 2 +- 2 files changed, 217 insertions(+), 62 deletions(-) diff --git a/poetry.lock b/poetry.lock index 9d60915..2adc89c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,21 +1,33 @@ [[package]] name = "aiohttp" -version = "3.7.4.post0" +version = "3.8.3" description = "Async http client/server framework (asyncio)" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -async-timeout = ">=3.0,<4.0" +aiosignal = ">=1.1.2" +async-timeout = ">=4.0.0a3,<5.0" attrs = ">=17.3.0" -chardet = ">=2.0,<5.0" +charset-normalizer = ">=2.0,<3.0" +frozenlist = ">=1.1.1" multidict = ">=4.5,<7.0" -typing-extensions = ">=3.6.5" yarl = ">=1.0,<2.0" [package.extras] -speedups = ["aiodns", "brotlipy", "cchardet"] +speedups = ["Brotli", "aiodns", "cchardet"] + +[[package]] +name = "aiosignal" +version = "1.3.1" +description = "aiosignal: a list of registered asynchronous callbacks" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +frozenlist = ">=1.1.0" [[package]] name = "aiosqlite" @@ -53,11 +65,11 @@ python-versions = "*" [[package]] name = "async-timeout" -version = "3.0.1" +version = "4.0.2" description = "Timeout context manager for asyncio programs" category = "main" optional = false -python-versions = ">=3.5.3" +python-versions = ">=3.6" [[package]] name = "atomicwrites" @@ -98,12 +110,15 @@ optional = false python-versions = ">=3.6" [[package]] -name = "chardet" -version = "4.0.0" -description = "Universal encoding detector for Python 2 and 3" +name = "charset-normalizer" +version = "2.1.1" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.6.0" + +[package.extras] +unicode-backport = ["unicodedata2"] [[package]] name = "colorama" @@ -134,18 +149,28 @@ python-versions = ">=3.5" [[package]] name = "discord-py" -version = "1.7.3" +version = "2.0.1" description = "A Python wrapper for the Discord API" category = "main" optional = false -python-versions = ">=3.5.3" +python-versions = ">=3.8.0" [package.dependencies] -aiohttp = ">=3.6.0,<3.8.0" +aiohttp = ">=3.7.4,<4" [package.extras] -docs = ["sphinx (==3.0.3)", "sphinxcontrib-trio (==1.1.2)", "sphinxcontrib-websupport"] -voice = ["PyNaCl (>=1.3.0,<1.5)"] +docs = ["sphinx (==4.4.0)", "sphinxcontrib-trio (==1.1.2)", "sphinxcontrib-websupport", "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)"] +voice = ["PyNaCl (>=1.3.0,<1.6)"] + +[[package]] +name = "frozenlist" +version = "1.3.3" +description = "A list-like structure which implements collections.abc.MutableSequence" +category = "main" +optional = false +python-versions = ">=3.7" [[package]] name = "fuzzywuzzy" @@ -673,47 +698,101 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "4151a86ee8c266c9502024fe74751b3def7547afea99a529cd217e5ae8de11c6" +content-hash = "5e2ce1961c5c95d5e914c969f9e1536176c3087d05d36ee62f9f9370f940ea50" [metadata.files] aiohttp = [ - {file = "aiohttp-3.7.4.post0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:3cf75f7cdc2397ed4442594b935a11ed5569961333d49b7539ea741be2cc79d5"}, - {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:4b302b45040890cea949ad092479e01ba25911a15e648429c7c5aae9650c67a8"}, - {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:fe60131d21b31fd1a14bd43e6bb88256f69dfc3188b3a89d736d6c71ed43ec95"}, - {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:393f389841e8f2dfc86f774ad22f00923fdee66d238af89b70ea314c4aefd290"}, - {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:c6e9dcb4cb338d91a73f178d866d051efe7c62a7166653a91e7d9fb18274058f"}, - {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:5df68496d19f849921f05f14f31bd6ef53ad4b00245da3195048c69934521809"}, - {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:0563c1b3826945eecd62186f3f5c7d31abb7391fedc893b7e2b26303b5a9f3fe"}, - {file = "aiohttp-3.7.4.post0-cp36-cp36m-win32.whl", hash = "sha256:3d78619672183be860b96ed96f533046ec97ca067fd46ac1f6a09cd9b7484287"}, - {file = "aiohttp-3.7.4.post0-cp36-cp36m-win_amd64.whl", hash = "sha256:f705e12750171c0ab4ef2a3c76b9a4024a62c4103e3a55dd6f99265b9bc6fcfc"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:230a8f7e24298dea47659251abc0fd8b3c4e38a664c59d4b89cca7f6c09c9e87"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2e19413bf84934d651344783c9f5e22dee452e251cfd220ebadbed2d9931dbf0"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e4b2b334e68b18ac9817d828ba44d8fcb391f6acb398bcc5062b14b2cbeac970"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:d012ad7911653a906425d8473a1465caa9f8dea7fcf07b6d870397b774ea7c0f"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:40eced07f07a9e60e825554a31f923e8d3997cfc7fb31dbc1328c70826e04cde"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:209b4a8ee987eccc91e2bd3ac36adee0e53a5970b8ac52c273f7f8fd4872c94c"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:14762875b22d0055f05d12abc7f7d61d5fd4fe4642ce1a249abdf8c700bf1fd8"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-win32.whl", hash = "sha256:7615dab56bb07bff74bc865307aeb89a8bfd9941d2ef9d817b9436da3a0ea54f"}, - {file = "aiohttp-3.7.4.post0-cp37-cp37m-win_amd64.whl", hash = "sha256:d9e13b33afd39ddeb377eff2c1c4f00544e191e1d1dee5b6c51ddee8ea6f0cf5"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:547da6cacac20666422d4882cfcd51298d45f7ccb60a04ec27424d2f36ba3eaf"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:af9aa9ef5ba1fd5b8c948bb11f44891968ab30356d65fd0cc6707d989cd521df"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:64322071e046020e8797117b3658b9c2f80e3267daec409b350b6a7a05041213"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:bb437315738aa441251214dad17428cafda9cdc9729499f1d6001748e1d432f4"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:e54962802d4b8b18b6207d4a927032826af39395a3bd9196a5af43fc4e60b009"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:a00bb73540af068ca7390e636c01cbc4f644961896fa9363154ff43fd37af2f5"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:79ebfc238612123a713a457d92afb4096e2148be17df6c50fb9bf7a81c2f8013"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-win32.whl", hash = "sha256:515dfef7f869a0feb2afee66b957cc7bbe9ad0cdee45aec7fdc623f4ecd4fb16"}, - {file = "aiohttp-3.7.4.post0-cp38-cp38-win_amd64.whl", hash = "sha256:114b281e4d68302a324dd33abb04778e8557d88947875cbf4e842c2c01a030c5"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:7b18b97cf8ee5452fa5f4e3af95d01d84d86d32c5e2bfa260cf041749d66360b"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:15492a6368d985b76a2a5fdd2166cddfea5d24e69eefed4630cbaae5c81d89bd"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bdb230b4943891321e06fc7def63c7aace16095be7d9cf3b1e01be2f10fba439"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:cffe3ab27871bc3ea47df5d8f7013945712c46a3cc5a95b6bee15887f1675c22"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:f881853d2643a29e643609da57b96d5f9c9b93f62429dcc1cbb413c7d07f0e1a"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:a5ca29ee66f8343ed336816c553e82d6cade48a3ad702b9ffa6125d187e2dedb"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:17c073de315745a1510393a96e680d20af8e67e324f70b42accbd4cb3315c9fb"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-win32.whl", hash = "sha256:932bb1ea39a54e9ea27fc9232163059a0b8855256f4052e776357ad9add6f1c9"}, - {file = "aiohttp-3.7.4.post0-cp39-cp39-win_amd64.whl", hash = "sha256:02f46fc0e3c5ac58b80d4d56eb0a7c7d97fcef69ace9326289fb9f1955e65cfe"}, - {file = "aiohttp-3.7.4.post0.tar.gz", hash = "sha256:493d3299ebe5f5a7c66b9819eacdcfbbaaf1a8e84911ddffcdc48888497afecf"}, + {file = "aiohttp-3.8.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ba71c9b4dcbb16212f334126cc3d8beb6af377f6703d9dc2d9fb3874fd667ee9"}, + {file = "aiohttp-3.8.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d24b8bb40d5c61ef2d9b6a8f4528c2f17f1c5d2d31fed62ec860f6006142e83e"}, + {file = "aiohttp-3.8.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f88df3a83cf9df566f171adba39d5bd52814ac0b94778d2448652fc77f9eb491"}, + {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97decbb3372d4b69e4d4c8117f44632551c692bb1361b356a02b97b69e18a62"}, + {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:309aa21c1d54b8ef0723181d430347d7452daaff93e8e2363db8e75c72c2fb2d"}, + {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad5383a67514e8e76906a06741febd9126fc7c7ff0f599d6fcce3e82b80d026f"}, + {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20acae4f268317bb975671e375493dbdbc67cddb5f6c71eebdb85b34444ac46b"}, + {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05a3c31c6d7cd08c149e50dc7aa2568317f5844acd745621983380597f027a18"}, + {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d6f76310355e9fae637c3162936e9504b4767d5c52ca268331e2756e54fd4ca5"}, + {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:256deb4b29fe5e47893fa32e1de2d73c3afe7407738bd3c63829874661d4822d"}, + {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:5c59fcd80b9049b49acd29bd3598cada4afc8d8d69bd4160cd613246912535d7"}, + {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:059a91e88f2c00fe40aed9031b3606c3f311414f86a90d696dd982e7aec48142"}, + {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2feebbb6074cdbd1ac276dbd737b40e890a1361b3cc30b74ac2f5e24aab41f7b"}, + {file = "aiohttp-3.8.3-cp310-cp310-win32.whl", hash = "sha256:5bf651afd22d5f0c4be16cf39d0482ea494f5c88f03e75e5fef3a85177fecdeb"}, + {file = "aiohttp-3.8.3-cp310-cp310-win_amd64.whl", hash = "sha256:653acc3880459f82a65e27bd6526e47ddf19e643457d36a2250b85b41a564715"}, + {file = "aiohttp-3.8.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:86fc24e58ecb32aee09f864cb11bb91bc4c1086615001647dbfc4dc8c32f4008"}, + {file = "aiohttp-3.8.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75e14eac916f024305db517e00a9252714fce0abcb10ad327fb6dcdc0d060f1d"}, + {file = "aiohttp-3.8.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d1fde0f44029e02d02d3993ad55ce93ead9bb9b15c6b7ccd580f90bd7e3de476"}, + {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ab94426ddb1ecc6a0b601d832d5d9d421820989b8caa929114811369673235c"}, + {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89d2e02167fa95172c017732ed7725bc8523c598757f08d13c5acca308e1a061"}, + {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:02f9a2c72fc95d59b881cf38a4b2be9381b9527f9d328771e90f72ac76f31ad8"}, + {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c7149272fb5834fc186328e2c1fa01dda3e1fa940ce18fded6d412e8f2cf76d"}, + {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:512bd5ab136b8dc0ffe3fdf2dfb0c4b4f49c8577f6cae55dca862cd37a4564e2"}, + {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7018ecc5fe97027214556afbc7c502fbd718d0740e87eb1217b17efd05b3d276"}, + {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:88c70ed9da9963d5496d38320160e8eb7e5f1886f9290475a881db12f351ab5d"}, + {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:da22885266bbfb3f78218dc40205fed2671909fbd0720aedba39b4515c038091"}, + {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:e65bc19919c910127c06759a63747ebe14f386cda573d95bcc62b427ca1afc73"}, + {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:08c78317e950e0762c2983f4dd58dc5e6c9ff75c8a0efeae299d363d439c8e34"}, + {file = "aiohttp-3.8.3-cp311-cp311-win32.whl", hash = "sha256:45d88b016c849d74ebc6f2b6e8bc17cabf26e7e40c0661ddd8fae4c00f015697"}, + {file = "aiohttp-3.8.3-cp311-cp311-win_amd64.whl", hash = "sha256:96372fc29471646b9b106ee918c8eeb4cca423fcbf9a34daa1b93767a88a2290"}, + {file = "aiohttp-3.8.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c971bf3786b5fad82ce5ad570dc6ee420f5b12527157929e830f51c55dc8af77"}, + {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff25f48fc8e623d95eca0670b8cc1469a83783c924a602e0fbd47363bb54aaca"}, + {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e381581b37db1db7597b62a2e6b8b57c3deec95d93b6d6407c5b61ddc98aca6d"}, + {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:db19d60d846283ee275d0416e2a23493f4e6b6028825b51290ac05afc87a6f97"}, + {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25892c92bee6d9449ffac82c2fe257f3a6f297792cdb18ad784737d61e7a9a85"}, + {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:398701865e7a9565d49189f6c90868efaca21be65c725fc87fc305906be915da"}, + {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4a4fbc769ea9b6bd97f4ad0b430a6807f92f0e5eb020f1e42ece59f3ecfc4585"}, + {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:b29bfd650ed8e148f9c515474a6ef0ba1090b7a8faeee26b74a8ff3b33617502"}, + {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:1e56b9cafcd6531bab5d9b2e890bb4937f4165109fe98e2b98ef0dcfcb06ee9d"}, + {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ec40170327d4a404b0d91855d41bfe1fe4b699222b2b93e3d833a27330a87a6d"}, + {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2df5f139233060578d8c2c975128fb231a89ca0a462b35d4b5fcf7c501ebdbe1"}, + {file = "aiohttp-3.8.3-cp36-cp36m-win32.whl", hash = "sha256:f973157ffeab5459eefe7b97a804987876dd0a55570b8fa56b4e1954bf11329b"}, + {file = "aiohttp-3.8.3-cp36-cp36m-win_amd64.whl", hash = "sha256:437399385f2abcd634865705bdc180c8314124b98299d54fe1d4c8990f2f9494"}, + {file = "aiohttp-3.8.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:09e28f572b21642128ef31f4e8372adb6888846f32fecb288c8b0457597ba61a"}, + {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f3553510abdbec67c043ca85727396ceed1272eef029b050677046d3387be8d"}, + {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e168a7560b7c61342ae0412997b069753f27ac4862ec7867eff74f0fe4ea2ad9"}, + {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:db4c979b0b3e0fa7e9e69ecd11b2b3174c6963cebadeecfb7ad24532ffcdd11a"}, + {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e164e0a98e92d06da343d17d4e9c4da4654f4a4588a20d6c73548a29f176abe2"}, + {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8a78079d9a39ca9ca99a8b0ac2fdc0c4d25fc80c8a8a82e5c8211509c523363"}, + {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:21b30885a63c3f4ff5b77a5d6caf008b037cb521a5f33eab445dc566f6d092cc"}, + {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4b0f30372cef3fdc262f33d06e7b411cd59058ce9174ef159ad938c4a34a89da"}, + {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:8135fa153a20d82ffb64f70a1b5c2738684afa197839b34cc3e3c72fa88d302c"}, + {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:ad61a9639792fd790523ba072c0555cd6be5a0baf03a49a5dd8cfcf20d56df48"}, + {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:978b046ca728073070e9abc074b6299ebf3501e8dee5e26efacb13cec2b2dea0"}, + {file = "aiohttp-3.8.3-cp37-cp37m-win32.whl", hash = "sha256:0d2c6d8c6872df4a6ec37d2ede71eff62395b9e337b4e18efd2177de883a5033"}, + {file = "aiohttp-3.8.3-cp37-cp37m-win_amd64.whl", hash = "sha256:21d69797eb951f155026651f7e9362877334508d39c2fc37bd04ff55b2007091"}, + {file = "aiohttp-3.8.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ca9af5f8f5812d475c5259393f52d712f6d5f0d7fdad9acdb1107dd9e3cb7eb"}, + {file = "aiohttp-3.8.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d90043c1882067f1bd26196d5d2db9aa6d268def3293ed5fb317e13c9413ea4"}, + {file = "aiohttp-3.8.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d737fc67b9a970f3234754974531dc9afeea11c70791dcb7db53b0cf81b79784"}, + {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebf909ea0a3fc9596e40d55d8000702a85e27fd578ff41a5500f68f20fd32e6c"}, + {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5835f258ca9f7c455493a57ee707b76d2d9634d84d5d7f62e77be984ea80b849"}, + {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da37dcfbf4b7f45d80ee386a5f81122501ec75672f475da34784196690762f4b"}, + {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87f44875f2804bc0511a69ce44a9595d5944837a62caecc8490bbdb0e18b1342"}, + {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:527b3b87b24844ea7865284aabfab08eb0faf599b385b03c2aa91fc6edd6e4b6"}, + {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d5ba88df9aa5e2f806650fcbeedbe4f6e8736e92fc0e73b0400538fd25a4dd96"}, + {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e7b8813be97cab8cb52b1375f41f8e6804f6507fe4660152e8ca5c48f0436017"}, + {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:2dea10edfa1a54098703cb7acaa665c07b4e7568472a47f4e64e6319d3821ccf"}, + {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:713d22cd9643ba9025d33c4af43943c7a1eb8547729228de18d3e02e278472b6"}, + {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2d252771fc85e0cf8da0b823157962d70639e63cb9b578b1dec9868dd1f4f937"}, + {file = "aiohttp-3.8.3-cp38-cp38-win32.whl", hash = "sha256:66bd5f950344fb2b3dbdd421aaa4e84f4411a1a13fca3aeb2bcbe667f80c9f76"}, + {file = "aiohttp-3.8.3-cp38-cp38-win_amd64.whl", hash = "sha256:84b14f36e85295fe69c6b9789b51a0903b774046d5f7df538176516c3e422446"}, + {file = "aiohttp-3.8.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16c121ba0b1ec2b44b73e3a8a171c4f999b33929cd2397124a8c7fcfc8cd9e06"}, + {file = "aiohttp-3.8.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8d6aaa4e7155afaf994d7924eb290abbe81a6905b303d8cb61310a2aba1c68ba"}, + {file = "aiohttp-3.8.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43046a319664a04b146f81b40e1545d4c8ac7b7dd04c47e40bf09f65f2437346"}, + {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:599418aaaf88a6d02a8c515e656f6faf3d10618d3dd95866eb4436520096c84b"}, + {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92a2964319d359f494f16011e23434f6f8ef0434acd3cf154a6b7bec511e2fb7"}, + {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73a4131962e6d91109bca6536416aa067cf6c4efb871975df734f8d2fd821b37"}, + {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598adde339d2cf7d67beaccda3f2ce7c57b3b412702f29c946708f69cf8222aa"}, + {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75880ed07be39beff1881d81e4a907cafb802f306efd6d2d15f2b3c69935f6fb"}, + {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a0239da9fbafd9ff82fd67c16704a7d1bccf0d107a300e790587ad05547681c8"}, + {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4e3a23ec214e95c9fe85a58470b660efe6534b83e6cbe38b3ed52b053d7cb6ad"}, + {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:47841407cc89a4b80b0c52276f3cc8138bbbfba4b179ee3acbd7d77ae33f7ac4"}, + {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:54d107c89a3ebcd13228278d68f1436d3f33f2dd2af5415e3feaeb1156e1a62c"}, + {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c37c5cce780349d4d51739ae682dec63573847a2a8dcb44381b174c3d9c8d403"}, + {file = "aiohttp-3.8.3-cp39-cp39-win32.whl", hash = "sha256:f178d2aadf0166be4df834c4953da2d7eef24719e8aec9a65289483eeea9d618"}, + {file = "aiohttp-3.8.3-cp39-cp39-win_amd64.whl", hash = "sha256:88e5be56c231981428f4f506c68b6a46fa25c4123a2e86d156c58a8369d31ab7"}, + {file = "aiohttp-3.8.3.tar.gz", hash = "sha256:3828fb41b7203176b82fe5d699e0d845435f2374750a44b480ea6b930f6be269"}, +] +aiosignal = [ + {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, + {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, ] aiosqlite = [ {file = "aiosqlite-0.17.0-py3-none-any.whl", hash = "sha256:6c49dc6d3405929b1d08eeccc72306d3677503cc5e5e43771efc1e00232e8231"}, @@ -728,8 +807,8 @@ appnope = [ {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, ] async-timeout = [ - {file = "async-timeout-3.0.1.tar.gz", hash = "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f"}, - {file = "async_timeout-3.0.1-py3-none-any.whl", hash = "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"}, + {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, + {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, ] atomicwrites = [ {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, @@ -746,9 +825,9 @@ certifi = [ {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, ] -chardet = [ - {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, - {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, +charset-normalizer = [ + {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, + {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, ] colorama = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, @@ -811,8 +890,84 @@ decorator = [ {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, ] discord-py = [ - {file = "discord.py-1.7.3-py3-none-any.whl", hash = "sha256:c6f64db136de0e18e090f6752ea68bdd4ab0a61b82dfe7acecefa22d6477bb0c"}, - {file = "discord.py-1.7.3.tar.gz", hash = "sha256:462cd0fe307aef8b29cbfa8dd613e548ae4b2cb581d46da9ac0d46fb6ea19408"}, + {file = "discord.py-2.0.1-py3-none-any.whl", hash = "sha256:aeb186348bf011708b085b2715cf92bbb72c692eb4f59c4c0b488130cc4c4b7e"}, + {file = "discord.py-2.0.1.tar.gz", hash = "sha256:309146476e986cb8faf038cd5d604d4b3834ef15c2d34df697ce5064bf5cd779"}, +] +frozenlist = [ + {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff8bf625fe85e119553b5383ba0fb6aa3d0ec2ae980295aaefa552374926b3f4"}, + {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dfbac4c2dfcc082fcf8d942d1e49b6aa0766c19d3358bd86e2000bf0fa4a9cf0"}, + {file = "frozenlist-1.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b1c63e8d377d039ac769cd0926558bb7068a1f7abb0f003e3717ee003ad85530"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fdfc24dcfce5b48109867c13b4cb15e4660e7bd7661741a391f821f23dfdca7"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c926450857408e42f0bbc295e84395722ce74bae69a3b2aa2a65fe22cb14b99"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1841e200fdafc3d51f974d9d377c079a0694a8f06de2e67b48150328d66d5483"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f470c92737afa7d4c3aacc001e335062d582053d4dbe73cda126f2d7031068dd"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:783263a4eaad7c49983fe4b2e7b53fa9770c136c270d2d4bbb6d2192bf4d9caf"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:924620eef691990dfb56dc4709f280f40baee568c794b5c1885800c3ecc69816"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ae4dc05c465a08a866b7a1baf360747078b362e6a6dbeb0c57f234db0ef88ae0"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:bed331fe18f58d844d39ceb398b77d6ac0b010d571cba8267c2e7165806b00ce"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:02c9ac843e3390826a265e331105efeab489ffaf4dd86384595ee8ce6d35ae7f"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9545a33965d0d377b0bc823dcabf26980e77f1b6a7caa368a365a9497fb09420"}, + {file = "frozenlist-1.3.3-cp310-cp310-win32.whl", hash = "sha256:d5cd3ab21acbdb414bb6c31958d7b06b85eeb40f66463c264a9b343a4e238642"}, + {file = "frozenlist-1.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:b756072364347cb6aa5b60f9bc18e94b2f79632de3b0190253ad770c5df17db1"}, + {file = "frozenlist-1.3.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b4395e2f8d83fbe0c627b2b696acce67868793d7d9750e90e39592b3626691b7"}, + {file = "frozenlist-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14143ae966a6229350021384870458e4777d1eae4c28d1a7aa47f24d030e6678"}, + {file = "frozenlist-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5d8860749e813a6f65bad8285a0520607c9500caa23fea6ee407e63debcdbef6"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23d16d9f477bb55b6154654e0e74557040575d9d19fe78a161bd33d7d76808e8"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb82dbba47a8318e75f679690190c10a5e1f447fbf9df41cbc4c3afd726d88cb"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9309869032abb23d196cb4e4db574232abe8b8be1339026f489eeb34a4acfd91"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a97b4fe50b5890d36300820abd305694cb865ddb7885049587a5678215782a6b"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c188512b43542b1e91cadc3c6c915a82a5eb95929134faf7fd109f14f9892ce4"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:303e04d422e9b911a09ad499b0368dc551e8c3cd15293c99160c7f1f07b59a48"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0771aed7f596c7d73444c847a1c16288937ef988dc04fb9f7be4b2aa91db609d"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:66080ec69883597e4d026f2f71a231a1ee9887835902dbe6b6467d5a89216cf6"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:41fe21dc74ad3a779c3d73a2786bdf622ea81234bdd4faf90b8b03cad0c2c0b4"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f20380df709d91525e4bee04746ba612a4df0972c1b8f8e1e8af997e678c7b81"}, + {file = "frozenlist-1.3.3-cp311-cp311-win32.whl", hash = "sha256:f30f1928162e189091cf4d9da2eac617bfe78ef907a761614ff577ef4edfb3c8"}, + {file = "frozenlist-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:a6394d7dadd3cfe3f4b3b186e54d5d8504d44f2d58dcc89d693698e8b7132b32"}, + {file = "frozenlist-1.3.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8df3de3a9ab8325f94f646609a66cbeeede263910c5c0de0101079ad541af332"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0693c609e9742c66ba4870bcee1ad5ff35462d5ffec18710b4ac89337ff16e27"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd4210baef299717db0a600d7a3cac81d46ef0e007f88c9335db79f8979c0d3d"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:394c9c242113bfb4b9aa36e2b80a05ffa163a30691c7b5a29eba82e937895d5e"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6327eb8e419f7d9c38f333cde41b9ae348bec26d840927332f17e887a8dcb70d"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e24900aa13212e75e5b366cb9065e78bbf3893d4baab6052d1aca10d46d944c"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3843f84a6c465a36559161e6c59dce2f2ac10943040c2fd021cfb70d58c4ad56"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:84610c1502b2461255b4c9b7d5e9c48052601a8957cd0aea6ec7a7a1e1fb9420"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:c21b9aa40e08e4f63a2f92ff3748e6b6c84d717d033c7b3438dd3123ee18f70e"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:efce6ae830831ab6a22b9b4091d411698145cb9b8fc869e1397ccf4b4b6455cb"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:40de71985e9042ca00b7953c4f41eabc3dc514a2d1ff534027f091bc74416401"}, + {file = "frozenlist-1.3.3-cp37-cp37m-win32.whl", hash = "sha256:180c00c66bde6146a860cbb81b54ee0df350d2daf13ca85b275123bbf85de18a"}, + {file = "frozenlist-1.3.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9bbbcedd75acdfecf2159663b87f1bb5cfc80e7cd99f7ddd9d66eb98b14a8411"}, + {file = "frozenlist-1.3.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:034a5c08d36649591be1cbb10e09da9f531034acfe29275fc5454a3b101ce41a"}, + {file = "frozenlist-1.3.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba64dc2b3b7b158c6660d49cdb1d872d1d0bf4e42043ad8d5006099479a194e5"}, + {file = "frozenlist-1.3.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:47df36a9fe24054b950bbc2db630d508cca3aa27ed0566c0baf661225e52c18e"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:008a054b75d77c995ea26629ab3a0c0d7281341f2fa7e1e85fa6153ae29ae99c"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:841ea19b43d438a80b4de62ac6ab21cfe6827bb8a9dc62b896acc88eaf9cecba"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e235688f42b36be2b6b06fc37ac2126a73b75fb8d6bc66dd632aa35286238703"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca713d4af15bae6e5d79b15c10c8522859a9a89d3b361a50b817c98c2fb402a2"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ac5995f2b408017b0be26d4a1d7c61bce106ff3d9e3324374d66b5964325448"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a4ae8135b11652b08a8baf07631d3ebfe65a4c87909dbef5fa0cdde440444ee4"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4ea42116ceb6bb16dbb7d526e242cb6747b08b7710d9782aa3d6732bd8d27649"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:810860bb4bdce7557bc0febb84bbd88198b9dbc2022d8eebe5b3590b2ad6c842"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ee78feb9d293c323b59a6f2dd441b63339a30edf35abcb51187d2fc26e696d13"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0af2e7c87d35b38732e810befb9d797a99279cbb85374d42ea61c1e9d23094b3"}, + {file = "frozenlist-1.3.3-cp38-cp38-win32.whl", hash = "sha256:899c5e1928eec13fd6f6d8dc51be23f0d09c5281e40d9cf4273d188d9feeaf9b"}, + {file = "frozenlist-1.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:7f44e24fa70f6fbc74aeec3e971f60a14dde85da364aa87f15d1be94ae75aeef"}, + {file = "frozenlist-1.3.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2b07ae0c1edaa0a36339ec6cce700f51b14a3fc6545fdd32930d2c83917332cf"}, + {file = "frozenlist-1.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ebb86518203e12e96af765ee89034a1dbb0c3c65052d1b0c19bbbd6af8a145e1"}, + {file = "frozenlist-1.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5cf820485f1b4c91e0417ea0afd41ce5cf5965011b3c22c400f6d144296ccbc0"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c11e43016b9024240212d2a65043b70ed8dfd3b52678a1271972702d990ac6d"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8fa3c6e3305aa1146b59a09b32b2e04074945ffcfb2f0931836d103a2c38f936"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:352bd4c8c72d508778cf05ab491f6ef36149f4d0cb3c56b1b4302852255d05d5"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65a5e4d3aa679610ac6e3569e865425b23b372277f89b5ef06cf2cdaf1ebf22b"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e2c1185858d7e10ff045c496bbf90ae752c28b365fef2c09cf0fa309291669"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f163d2fd041c630fed01bc48d28c3ed4a3b003c00acd396900e11ee5316b56bb"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:05cdb16d09a0832eedf770cb7bd1fe57d8cf4eaf5aced29c4e41e3f20b30a784"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:8bae29d60768bfa8fb92244b74502b18fae55a80eac13c88eb0b496d4268fd2d"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:eedab4c310c0299961ac285591acd53dc6723a1ebd90a57207c71f6e0c2153ab"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3bbdf44855ed8f0fbcd102ef05ec3012d6a4fd7c7562403f76ce6a52aeffb2b1"}, + {file = "frozenlist-1.3.3-cp39-cp39-win32.whl", hash = "sha256:efa568b885bca461f7c7b9e032655c0c143d305bf01c30caf6db2854a4532b38"}, + {file = "frozenlist-1.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:cfe33efc9cb900a4c46f91a5ceba26d6df370ffddd9ca386eb1d4f0ad97b9ea9"}, + {file = "frozenlist-1.3.3.tar.gz", hash = "sha256:58bcc55721e8a90b88332d6cd441261ebb22342e238296bb330968952fbb3a6a"}, ] fuzzywuzzy = [ {file = "fuzzywuzzy-0.18.0-py2.py3-none-any.whl", hash = "sha256:928244b28db720d1e0ee7587acf660ea49d7e4c632569cad4f1cd7e68a5f0993"}, diff --git a/pyproject.toml b/pyproject.toml index 8669d61..7731b42 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ python = "^3.10" aiosqlite = "^0.17.0" alembic = "^1.7.5" certifi = "*" -"discord.py" = "^1.7" +"discord.py" = "^2" fuzzywuzzy = {extras = ["speedup"], version = "^0.18.0"} pytz = "*" regex = "^2021.11.10" From 1e9b3b8cddd2fa66d4e15d5b298d915444450edb Mon Sep 17 00:00:00 2001 From: Phanabani Date: Fri, 11 Nov 2022 01:23:47 -0500 Subject: [PATCH 11/33] Remove certifi. --- poetry.lock | 14 +------------- pyproject.toml | 1 - sandpiper/__init__.py | 9 --------- 3 files changed, 1 insertion(+), 23 deletions(-) diff --git a/poetry.lock b/poetry.lock index 9d60915..8b44873 100644 --- a/poetry.lock +++ b/poetry.lock @@ -89,14 +89,6 @@ category = "dev" optional = false python-versions = "*" -[[package]] -name = "certifi" -version = "2022.9.24" -description = "Python package for providing Mozilla's CA Bundle." -category = "main" -optional = false -python-versions = ">=3.6" - [[package]] name = "chardet" version = "4.0.0" @@ -673,7 +665,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "4151a86ee8c266c9502024fe74751b3def7547afea99a529cd217e5ae8de11c6" +content-hash = "d66a5bea0e3d3ea5dbde5c572014e55eaa61dd120f7a5aea673ff06258508e74" [metadata.files] aiohttp = [ @@ -742,10 +734,6 @@ backcall = [ {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, ] -certifi = [ - {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, - {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, -] chardet = [ {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, diff --git a/pyproject.toml b/pyproject.toml index 8669d61..8dc6b83 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,6 @@ readme = "README.md" python = "^3.10" aiosqlite = "^0.17.0" alembic = "^1.7.5" -certifi = "*" "discord.py" = "^1.7" fuzzywuzzy = {extras = ["speedup"], version = "^0.18.0"} pytz = "*" diff --git a/sandpiper/__init__.py b/sandpiper/__init__.py index 9fa9bd7..a110439 100644 --- a/sandpiper/__init__.py +++ b/sandpiper/__init__.py @@ -1,12 +1,3 @@ -import sys - -if sys.platform == "win32": - # Temporary fix for expired certificates on Windows - import certifi - import os - - os.environ["SSL_CERT_FILE"] = certifi.where() - import logging logger = logging.getLogger(__name__) From 778ee72d379292cbd7e34f4cd6d04ac60af7649d Mon Sep 17 00:00:00 2001 From: Phanabani Date: Fri, 11 Nov 2022 01:27:34 -0500 Subject: [PATCH 12/33] Lock poetry following merge. --- poetry.lock | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2adc89c..5cbab9e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -101,14 +101,6 @@ category = "dev" optional = false python-versions = "*" -[[package]] -name = "certifi" -version = "2022.9.24" -description = "Python package for providing Mozilla's CA Bundle." -category = "main" -optional = false -python-versions = ">=3.6" - [[package]] name = "charset-normalizer" version = "2.1.1" @@ -698,7 +690,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "5e2ce1961c5c95d5e914c969f9e1536176c3087d05d36ee62f9f9370f940ea50" +content-hash = "3a77fe6cd930127bfadd6655326f3c6ad8fa8804760d879df310b36999919393" [metadata.files] aiohttp = [ @@ -821,10 +813,6 @@ backcall = [ {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, ] -certifi = [ - {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, - {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, -] charset-normalizer = [ {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, From f9cedf20f0c0d605d15db9ac93ba81a4504010c4 Mon Sep 17 00:00:00 2001 From: Phanabani Date: Fri, 11 Nov 2022 01:47:39 -0500 Subject: [PATCH 13/33] Suppress discordpy's default log handler. --- sandpiper/sandpiper.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sandpiper/sandpiper.py b/sandpiper/sandpiper.py index 7a0524a..068b8c4 100644 --- a/sandpiper/sandpiper.py +++ b/sandpiper/sandpiper.py @@ -34,6 +34,7 @@ def get_prefix(bot: commands.Bot, msg: discord.Message) -> str: intents=intents, allowed_mentions=allowed_mentions, activity=activity, + log_handler=None, # Bot params command_prefix=get_prefix, description=config.description, From 8481c411303413de9b7deb12e2cbefcffa5d914f Mon Sep 17 00:00:00 2001 From: Phanabani Date: Fri, 11 Nov 2022 01:48:28 -0500 Subject: [PATCH 14/33] Add `setup_hook` to run async setup. --- sandpiper/sandpiper.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sandpiper/sandpiper.py b/sandpiper/sandpiper.py index 068b8c4..b8b4b58 100644 --- a/sandpiper/sandpiper.py +++ b/sandpiper/sandpiper.py @@ -59,6 +59,9 @@ async def noprefix_notify(ctx: commands.Context, *, rest: str): self.load_extension("sandpiper.conversion") self.load_extension("sandpiper.upgrades") + async def setup_hook(self) -> None: + self.loop.set_debug(True) + async def on_connect(self): logger.info("Client connected") @@ -116,5 +119,4 @@ def run_bot(): # Run bot sandpiper = Sandpiper(config.bot) - sandpiper.loop.set_debug(True) sandpiper.run(bot_token) From 9028919e05cc1f1c6d56b517979513e015198329 Mon Sep 17 00:00:00 2001 From: Phanabani Date: Fri, 11 Nov 2022 02:07:09 -0500 Subject: [PATCH 15/33] Make extension loading async. --- sandpiper/sandpiper.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/sandpiper/sandpiper.py b/sandpiper/sandpiper.py index b8b4b58..122313f 100644 --- a/sandpiper/sandpiper.py +++ b/sandpiper/sandpiper.py @@ -53,15 +53,16 @@ async def noprefix_notify(ctx: commands.Context, *, rest: str): self.modules_config = config.modules - self.load_extension("sandpiper.user_data") - self.load_extension("sandpiper.bios") - self.load_extension("sandpiper.birthdays") - self.load_extension("sandpiper.conversion") - self.load_extension("sandpiper.upgrades") - async def setup_hook(self) -> None: self.loop.set_debug(True) + await self.load_extension("sandpiper.user_data") + + await self.load_extension("sandpiper.bios") + await self.load_extension("sandpiper.birthdays") + await self.load_extension("sandpiper.conversion") + await self.load_extension("sandpiper.upgrades") + async def on_connect(self): logger.info("Client connected") From 94d7eea2b049704334b2b2403dc2c91f09b86b11 Mon Sep 17 00:00:00 2001 From: Phanabani Date: Fri, 11 Nov 2022 02:07:19 -0500 Subject: [PATCH 16/33] Make extension setup async. --- sandpiper/bios/__init__.py | 6 +++--- sandpiper/birthdays/__init__.py | 6 +++--- sandpiper/conversion/__init__.py | 4 ++-- sandpiper/upgrades/__init__.py | 4 ++-- sandpiper/user_data/__init__.py | 6 +++--- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/sandpiper/bios/__init__.py b/sandpiper/bios/__init__.py index 5a7c636..7c11e06 100644 --- a/sandpiper/bios/__init__.py +++ b/sandpiper/bios/__init__.py @@ -1,8 +1,8 @@ -from .cog import Bios from sandpiper import Sandpiper +from .cog import Bios -def setup(bot: Sandpiper): +async def setup(bot: Sandpiper): config = bot.modules_config.bios bios = Bios(bot, allow_public_setting=config.allow_public_setting) - bot.add_cog(bios) + await bot.add_cog(bios) diff --git a/sandpiper/birthdays/__init__.py b/sandpiper/birthdays/__init__.py index e3c7c2c..41044f9 100644 --- a/sandpiper/birthdays/__init__.py +++ b/sandpiper/birthdays/__init__.py @@ -1,8 +1,8 @@ -from .cog import Birthdays from sandpiper import Sandpiper +from .cog import Birthdays -def setup(bot: Sandpiper): +async def setup(bot: Sandpiper): config = bot.modules_config.birthdays birthdays = Birthdays( bot, @@ -11,4 +11,4 @@ def setup(bot: Sandpiper): past_birthdays_day_range=config.past_birthdays_day_range, upcoming_birthdays_day_range=config.upcoming_birthdays_day_range, ) - bot.add_cog(birthdays) + await bot.add_cog(birthdays) diff --git a/sandpiper/conversion/__init__.py b/sandpiper/conversion/__init__.py index abe73da..17e6a3d 100644 --- a/sandpiper/conversion/__init__.py +++ b/sandpiper/conversion/__init__.py @@ -3,5 +3,5 @@ from .cog import Conversion -def setup(bot: Bot): - bot.add_cog(Conversion(bot)) +async def setup(bot: Bot): + await bot.add_cog(Conversion(bot)) diff --git a/sandpiper/upgrades/__init__.py b/sandpiper/upgrades/__init__.py index afcfbd0..1a0097c 100644 --- a/sandpiper/upgrades/__init__.py +++ b/sandpiper/upgrades/__init__.py @@ -3,5 +3,5 @@ from .cog import Upgrades -def setup(bot: Bot): - bot.add_cog(Upgrades(bot)) +async def setup(bot: Bot): + await bot.add_cog(Upgrades(bot)) diff --git a/sandpiper/user_data/__init__.py b/sandpiper/user_data/__init__.py index 3662268..7b990e3 100644 --- a/sandpiper/user_data/__init__.py +++ b/sandpiper/user_data/__init__.py @@ -19,7 +19,7 @@ from discord.ext.commands import Bot -from .cog import UserData, DatabaseUnavailable +from .cog import DatabaseUnavailable, UserData from .database import * from .database_sqlite import DatabaseSQLite from .enums import PrivacyType @@ -31,12 +31,12 @@ DB_FILE = Path(__file__).parent.parent / "sandpiper.db" -def setup(bot: Sandpiper): +async def setup(bot: Sandpiper): user_data = UserData(bot) db = DatabaseSQLite(DB_FILE) asyncio.run_coroutine_threadsafe(db.connect(), bot.loop) user_data.set_database_adapter(db) - bot.add_cog(user_data) + await bot.add_cog(user_data) bot.add_listener(set_bot_user_id(bot, db), "on_ready") From 92a95c2f295b34b69360d714e26f472bc6d3e020 Mon Sep 17 00:00:00 2001 From: Phanabani Date: Fri, 11 Nov 2022 02:07:41 -0500 Subject: [PATCH 17/33] Add `message_content` intent. --- sandpiper/sandpiper.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sandpiper/sandpiper.py b/sandpiper/sandpiper.py index 122313f..a728ca4 100644 --- a/sandpiper/sandpiper.py +++ b/sandpiper/sandpiper.py @@ -24,7 +24,9 @@ def get_prefix(bot: commands.Bot, msg: discord.Message) -> str: return "" return commands.when_mentioned_or(config.command_prefix)(bot, msg) - intents = discord.Intents(guilds=True, members=True, messages=True) + intents = discord.Intents( + guilds=True, members=True, messages=True, message_content=True + ) allowed_mentions = discord.AllowedMentions(users=True) activity = discord.Game(f"{config.command_prefix}help") From 4c34053273b2407f7b5c1c872e208bddfbc68ab6 Mon Sep 17 00:00:00 2001 From: Phanabani Date: Fri, 11 Nov 2022 02:07:54 -0500 Subject: [PATCH 18/33] Fix incorrect type annotation. --- sandpiper/sandpiper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sandpiper/sandpiper.py b/sandpiper/sandpiper.py index a728ca4..bc0bc40 100644 --- a/sandpiper/sandpiper.py +++ b/sandpiper/sandpiper.py @@ -18,7 +18,7 @@ class Sandpiper(commands.Bot): def __init__(self, config: SandpiperConfig._Bot): # noinspection PyUnusedLocal - def get_prefix(bot: commands.Bot, msg: discord.Message) -> str: + def get_prefix(bot: commands.Bot, msg: discord.Message) -> str | list[str]: """Allows prefix-less command invocation in DMs""" if isinstance(msg.channel, discord.DMChannel): return "" From 5b6096d866d79587c12a0b5f0297c448eb191e46 Mon Sep 17 00:00:00 2001 From: Phanabani Date: Fri, 11 Nov 2022 02:18:33 -0500 Subject: [PATCH 19/33] Use poetry in pm2 config. --- ecosystem.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ecosystem.config.js b/ecosystem.config.js index f037989..c233463 100644 --- a/ecosystem.config.js +++ b/ecosystem.config.js @@ -12,7 +12,7 @@ let shell; let args; -const command = 'pipenv run python -m sandpiper'; +const command = 'poetry run python -m sandpiper'; switch (process.platform) { case 'win32': From 349ce8c44a1fcb638674f5497f92c7755109195b Mon Sep 17 00:00:00 2001 From: Phanabani Date: Sat, 12 Nov 2022 22:06:05 -0500 Subject: [PATCH 20/33] Update patching to work with discordpy v2. --- sandpiper/tests/conftest.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/sandpiper/tests/conftest.py b/sandpiper/tests/conftest.py index 9a1353d..c692747 100644 --- a/sandpiper/tests/conftest.py +++ b/sandpiper/tests/conftest.py @@ -310,7 +310,7 @@ async def bot( # Create a dummy bot that will never actually connect but will help # with invocation - bot = commands.Bot(command_prefix="") + bot = commands.Bot(command_prefix="", intents=discord.Intents.all()) @functools.wraps(mock.patch.object) def patch(attr, *, target=bot, **kwargs): @@ -321,12 +321,23 @@ def patch(attr, *, target=bot, **kwargs): # I don't need the extreme verbosity of this right now, but for some reason # when I set it to False, it shows errors that don't get shown if the # method is never called at all... - bot.loop.set_debug(False) + async with bot: + bot.loop.set_debug(False) - # This function checks if message author is the self bot and skips + # In `get_context`, if the message author is the self bot, it skips # context creation (meaning we won't get command invocation), so - # we will bypass it - patch("_skip_check", return_value=False) + # we will bypass this by making the user IDs always unequal + get_context_orig = bot.get_context + + async def get_context_wrapper(self, *args, **kwargs): + prev_id = bot.user.id + bot.user.id = -1 # We'll never otherwise use negative IDs since they're invalid + ctx = await get_context_orig(self, *args, **kwargs) + bot.user.id = prev_id + return ctx + + get_context = patch("get_context") + get_context.side_effect = get_context_wrapper # This connection (discord.state.ConnectionState) object has a `user` # field which is accessed by the client's `user` property. The From d5fd108e3b4e320f3d3404e0b43bc4ba867bae24 Mon Sep 17 00:00:00 2001 From: Phanabani Date: Sat, 12 Nov 2022 22:06:59 -0500 Subject: [PATCH 21/33] Fix `add_cog` not being awaited. --- sandpiper/tests/test_bios.py | 6 +++--- sandpiper/tests/test_birthdays.py | 10 +++++----- sandpiper/tests/test_conversion.py | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/sandpiper/tests/test_bios.py b/sandpiper/tests/test_bios.py index dff5bd9..1f49a52 100644 --- a/sandpiper/tests/test_bios.py +++ b/sandpiper/tests/test_bios.py @@ -19,10 +19,10 @@ @pytest.fixture() -def bot(bot) -> commands.Bot: +async def bot(bot) -> commands.Bot: """Add a Bios cog to a bot and return the bot""" - bot.add_cog(Bios(bot)) - bot.add_cog(UserData(bot)) + await bot.add_cog(Bios(bot)) + await bot.add_cog(UserData(bot)) return bot diff --git a/sandpiper/tests/test_birthdays.py b/sandpiper/tests/test_birthdays.py index 422661e..f9744ce 100644 --- a/sandpiper/tests/test_birthdays.py +++ b/sandpiper/tests/test_birthdays.py @@ -29,7 +29,7 @@ def asyncio_sleep(): @pytest.fixture() -def birthdays_cog( +async def birthdays_cog( bot, message_templates_with_age, message_templates_no_age ) -> Birthdays: cog = Birthdays( @@ -37,17 +37,17 @@ def birthdays_cog( message_templates_no_age=message_templates_no_age, message_templates_with_age=message_templates_with_age, ) - bot.add_cog(cog) + await bot.add_cog(cog) cog.daily_loop.count = 1 cog.daily_loop.cancel() return cog @pytest.fixture() -def bot(bot, database) -> commands.Bot: +async def bot(bot, database) -> commands.Bot: """Add a Bios cog to a bot and return the bot""" - bot.add_cog(UserData(bot)) - bot.loop.set_debug(True) + await bot.add_cog(UserData(bot)) + await bot.loop.set_debug(True) return bot diff --git a/sandpiper/tests/test_conversion.py b/sandpiper/tests/test_conversion.py index fb11173..0ec405d 100644 --- a/sandpiper/tests/test_conversion.py +++ b/sandpiper/tests/test_conversion.py @@ -19,9 +19,9 @@ @pytest.fixture() -def bot(bot) -> commands.Bot: - bot.add_cog(Conversion(bot)) - bot.add_cog(UserData(bot)) +async def bot(bot) -> commands.Bot: + await bot.add_cog(Conversion(bot)) + await bot.add_cog(UserData(bot)) return bot From 3634f2b5989e236b9192b197e025f9c4803a6817 Mon Sep 17 00:00:00 2001 From: Phanabani Date: Sat, 12 Nov 2022 22:07:27 -0500 Subject: [PATCH 22/33] Fix `Embed.Empty` being deprecated. --- sandpiper/common/embeds.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sandpiper/common/embeds.py b/sandpiper/common/embeds.py index ca31aeb..dc39db2 100644 --- a/sandpiper/common/embeds.py +++ b/sandpiper/common/embeds.py @@ -73,7 +73,7 @@ async def send(self, messageable: discord.abc.Messageable): :param messageable: a messageable interface to send the embed to """ - desc = discord.Embed.Empty + desc = None if self.message_parts: desc = self.join_str.join(self.message_parts) From 62dfc9061280f99c013af32eb09f060791876dcb Mon Sep 17 00:00:00 2001 From: Phanabani Date: Sun, 13 Nov 2022 01:32:43 -0500 Subject: [PATCH 23/33] Optimize imports. --- sandpiper/birthdays/cog.py | 2 +- sandpiper/birthdays/message.py | 1 - sandpiper/common/__init__.py | 6 +----- sandpiper/common/discord.py | 2 +- sandpiper/config.py | 1 + sandpiper/conversion/cog.py | 4 ++-- sandpiper/conversion/time_conversion.py | 2 +- sandpiper/conversion/unit_conversion.py | 3 +-- sandpiper/help.py | 2 +- sandpiper/piperfig/parser.py | 2 +- sandpiper/piperfig/test_transformers.py | 4 ++-- sandpiper/tests/conftest.py | 3 ++- sandpiper/tests/helpers/__init__.py | 4 +--- sandpiper/tests/test_bios.py | 4 ++-- sandpiper/tests/test_birthdays.py | 4 ++-- sandpiper/tests/test_conversion.py | 4 ++-- sandpiper/tests/test_database.py | 2 +- sandpiper/upgrades/cog.py | 4 ++-- sandpiper/upgrades/versions/sandpiper_1_6_0.py | 2 +- sandpiper/user_data/alembic/env.py | 6 ++---- sandpiper/user_data/database.py | 2 +- sandpiper/user_data/database_sqlite.py | 2 +- sandpiper/user_data/models/sandpiper_meta.py | 2 +- sandpiper/user_data/models/user.py | 2 +- sandpiper/user_data/pronouns.py | 1 + 25 files changed, 32 insertions(+), 39 deletions(-) diff --git a/sandpiper/birthdays/cog.py b/sandpiper/birthdays/cog.py index 3e42d25..a1808ce 100644 --- a/sandpiper/birthdays/cog.py +++ b/sandpiper/birthdays/cog.py @@ -12,7 +12,7 @@ from sandpiper.birthdays.message import format_birthday_message from sandpiper.common.discord import AutoOrder, cheap_user_hash from sandpiper.common.time import sort_dates_no_year, utc_now -from sandpiper.user_data import UserData, Database, PrivacyType, common_pronouns +from sandpiper.user_data import Database, PrivacyType, UserData, common_pronouns __all__ = ["Birthdays"] diff --git a/sandpiper/birthdays/message.py b/sandpiper/birthdays/message.py index 81e4342..2f717bd 100644 --- a/sandpiper/birthdays/message.py +++ b/sandpiper/birthdays/message.py @@ -4,7 +4,6 @@ from sandpiper.user_data import Pronouns, common_pronouns - base_ordinal_suffix = "th" ordinal_suffixes = {1: "st", 2: "nd", 3: "rd"} diff --git a/sandpiper/common/__init__.py b/sandpiper/common/__init__.py index e945c87..577a87e 100644 --- a/sandpiper/common/__init__.py +++ b/sandpiper/common/__init__.py @@ -1,5 +1 @@ -from . import discord -from . import embeds -from . import IANA -from . import misc -from . import time +from . import IANA, discord, embeds, misc, time diff --git a/sandpiper/common/discord.py b/sandpiper/common/discord.py index 50d6086..35231d0 100644 --- a/sandpiper/common/discord.py +++ b/sandpiper/common/discord.py @@ -5,8 +5,8 @@ import discord from discord.ext.commands import BadArgument, Command -from .time import parse_date from sandpiper.user_data.enums import PrivacyType +from .time import parse_date __all__ = [ "AutoOrder", diff --git a/sandpiper/config.py b/sandpiper/config.py index 38c4fdf..9e7f322 100644 --- a/sandpiper/config.py +++ b/sandpiper/config.py @@ -1,4 +1,5 @@ from __future__ import annotations + from functools import cached_property import logging from logging.handlers import TimedRotatingFileHandler diff --git a/sandpiper/conversion/cog.py b/sandpiper/conversion/cog.py index f363620..b380e96 100644 --- a/sandpiper/conversion/cog.py +++ b/sandpiper/conversion/cog.py @@ -1,13 +1,13 @@ from decimal import Decimal import logging -import regex from typing import NoReturn import discord import discord.ext.commands as commands +import regex -from sandpiper.common.embeds import * from sandpiper.common.IANA import get_country_flag_emoji_from_timezone +from sandpiper.common.embeds import * from sandpiper.common.misc import RuntimeMessages from sandpiper.common.time import time_format from sandpiper.conversion.time_conversion import * diff --git a/sandpiper/conversion/time_conversion.py b/sandpiper/conversion/time_conversion.py index 79b8937..bee5c0a 100644 --- a/sandpiper/conversion/time_conversion.py +++ b/sandpiper/conversion/time_conversion.py @@ -6,8 +6,8 @@ import discord -from sandpiper.common.time import * from sandpiper.common.misc import RuntimeMessages +from sandpiper.common.time import * from sandpiper.user_data import Database __all__ = ("UserTimezoneUnset", "TimezoneNotFound", "convert_time_to_user_timezones") diff --git a/sandpiper/conversion/unit_conversion.py b/sandpiper/conversion/unit_conversion.py index a4d10df..531d55b 100644 --- a/sandpiper/conversion/unit_conversion.py +++ b/sandpiper/conversion/unit_conversion.py @@ -3,8 +3,7 @@ import re from typing import Union -from pint import UndefinedUnitError as PintUndefinedUnitError -from pint import UnitRegistry, Unit +from pint import UndefinedUnitError as PintUndefinedUnitError, Unit, UnitRegistry from pint.quantity import Quantity from sandpiper.common.misc import RuntimeMessages diff --git a/sandpiper/help.py b/sandpiper/help.py index e9963a9..0436b56 100644 --- a/sandpiper/help.py +++ b/sandpiper/help.py @@ -1,7 +1,7 @@ from collections.abc import Iterable import itertools -from discord.ext.commands import DefaultHelpCommand, Group, Command, Cog +from discord.ext.commands import Cog, Command, DefaultHelpCommand, Group __all__ = ["HelpCommand"] diff --git a/sandpiper/piperfig/parser.py b/sandpiper/piperfig/parser.py index 374f649..8830755 100644 --- a/sandpiper/piperfig/parser.py +++ b/sandpiper/piperfig/parser.py @@ -6,8 +6,8 @@ # noinspection PyPep8Naming from typing import ( - Any, Annotated, + Any, Literal, NoReturn, TextIO, diff --git a/sandpiper/piperfig/test_transformers.py b/sandpiper/piperfig/test_transformers.py index 55dea65..02e10e6 100644 --- a/sandpiper/piperfig/test_transformers.py +++ b/sandpiper/piperfig/test_transformers.py @@ -1,7 +1,7 @@ -from textwrap import dedent -import unittest.mock as mock from pathlib import Path +from textwrap import dedent from typing import Annotated +import unittest.mock as mock import pytest diff --git a/sandpiper/tests/conftest.py b/sandpiper/tests/conftest.py index c692747..0ea8eca 100644 --- a/sandpiper/tests/conftest.py +++ b/sandpiper/tests/conftest.py @@ -9,9 +9,10 @@ import pytest import pytz +from sandpiper.user_data import DatabaseSQLite from .helpers.discord import * from .helpers.mocking import MagicMock_, patch_all_symbol_imports -from sandpiper.user_data import DatabaseSQLite + # region Discord arrange fixtures diff --git a/sandpiper/tests/helpers/__init__.py b/sandpiper/tests/helpers/__init__.py index a7e0660..8cd7dc8 100644 --- a/sandpiper/tests/helpers/__init__.py +++ b/sandpiper/tests/helpers/__init__.py @@ -1,3 +1 @@ -from . import discord -from . import misc -from . import mocking +from . import discord, misc, mocking diff --git a/sandpiper/tests/test_bios.py b/sandpiper/tests/test_bios.py index 1f49a52..2325332 100644 --- a/sandpiper/tests/test_bios.py +++ b/sandpiper/tests/test_bios.py @@ -7,11 +7,11 @@ import pytest import pytz -from .helpers.discord import * -from .helpers.misc import * from sandpiper.bios import Bios from sandpiper.bios.strings import BirthdayExplanations from sandpiper.user_data import * +from .helpers.discord import * +from .helpers.misc import * pytestmark = pytest.mark.asyncio diff --git a/sandpiper/tests/test_birthdays.py b/sandpiper/tests/test_birthdays.py index f9744ce..01eb084 100644 --- a/sandpiper/tests/test_birthdays.py +++ b/sandpiper/tests/test_birthdays.py @@ -8,11 +8,11 @@ import pytest import pytz -from .helpers.misc import * -from .helpers.mocking import * from sandpiper.birthdays import Birthdays from sandpiper.common.time import TimezoneType from sandpiper.user_data import PrivacyType, UserData +from .helpers.misc import * +from .helpers.mocking import * pytest.skip( "I struggled a lot with writing these tests, and I still can't get them to " diff --git a/sandpiper/tests/test_conversion.py b/sandpiper/tests/test_conversion.py index 0ec405d..d3a2626 100644 --- a/sandpiper/tests/test_conversion.py +++ b/sandpiper/tests/test_conversion.py @@ -7,13 +7,13 @@ import pytest import pytz -from .helpers.discord import * -from .helpers.misc import * from sandpiper.common.time import TimezoneType, utc_now from sandpiper.conversion.cog import Conversion, conversion_pattern from sandpiper.conversion.unit_conversion import imperial_shorthand_pattern from sandpiper.user_data import UserData from sandpiper.user_data.enums import PrivacyType +from .helpers.discord import * +from .helpers.misc import * pytestmark = pytest.mark.asyncio diff --git a/sandpiper/tests/test_database.py b/sandpiper/tests/test_database.py index 7b94f0c..6452c74 100644 --- a/sandpiper/tests/test_database.py +++ b/sandpiper/tests/test_database.py @@ -4,9 +4,9 @@ import pytest import pytz -from .helpers.misc import * from sandpiper.common.time import TimezoneType from sandpiper.user_data import * +from .helpers.misc import * pytestmark = pytest.mark.asyncio diff --git a/sandpiper/upgrades/cog.py b/sandpiper/upgrades/cog.py index f60127d..0327686 100644 --- a/sandpiper/upgrades/cog.py +++ b/sandpiper/upgrades/cog.py @@ -3,10 +3,10 @@ import discord.ext.commands as commands -from .versions import all_upgrade_handlers -from .upgrades import do_upgrades from sandpiper import __version__ as current_version from sandpiper.user_data import UserData +from .upgrades import do_upgrades +from .versions import all_upgrade_handlers __all__ = ["Upgrades"] diff --git a/sandpiper/upgrades/versions/sandpiper_1_6_0.py b/sandpiper/upgrades/versions/sandpiper_1_6_0.py index 123d52d..2870889 100644 --- a/sandpiper/upgrades/versions/sandpiper_1_6_0.py +++ b/sandpiper/upgrades/versions/sandpiper_1_6_0.py @@ -2,11 +2,11 @@ import discord -from ..upgrades import UpgradeHandler from sandpiper.common.discord import find_user_in_mutual_guilds from sandpiper.common.embeds import * from sandpiper.common.misc import listify from sandpiper.user_data import Database, PrivacyType +from ..upgrades import UpgradeHandler logger = logging.getLogger(__name__) diff --git a/sandpiper/user_data/alembic/env.py b/sandpiper/user_data/alembic/env.py index 2ab3005..6fe8c66 100644 --- a/sandpiper/user_data/alembic/env.py +++ b/sandpiper/user_data/alembic/env.py @@ -3,11 +3,9 @@ from pathlib import Path import sys -from sqlalchemy import engine_from_config -from sqlalchemy import pool -from sqlalchemy.ext.asyncio import AsyncEngine - from alembic import context +from sqlalchemy import engine_from_config, pool +from sqlalchemy.ext.asyncio import AsyncEngine sandpiper_root_dir = Path(__file__, "../../../..") sys.path.insert(0, str(sandpiper_root_dir.absolute())) diff --git a/sandpiper/user_data/database.py b/sandpiper/user_data/database.py index debbf86..6d3b5cb 100644 --- a/sandpiper/user_data/database.py +++ b/sandpiper/user_data/database.py @@ -4,9 +4,9 @@ import pytz +from sandpiper.common.time import TimezoneType, utc_now from .enums import PrivacyType from .pronouns import Pronouns -from sandpiper.common.time import TimezoneType, utc_now __all__ = [ "DEFAULT_PRIVACY", diff --git a/sandpiper/user_data/database_sqlite.py b/sandpiper/user_data/database_sqlite.py index 4bba0ba..18f7614 100644 --- a/sandpiper/user_data/database_sqlite.py +++ b/sandpiper/user_data/database_sqlite.py @@ -16,11 +16,11 @@ ) from sqlalchemy.orm import sessionmaker +from sandpiper.common.time import TimezoneType from . import alembic_utils as alembic_utils from .database import * from .enums import PrivacyType from .models import Base, Guild, SandpiperMeta, User -from sandpiper.common.time import TimezoneType logger = logging.getLogger(__name__) diff --git a/sandpiper/user_data/models/sandpiper_meta.py b/sandpiper/user_data/models/sandpiper_meta.py index a0aff4e..9ed5584 100644 --- a/sandpiper/user_data/models/sandpiper_meta.py +++ b/sandpiper/user_data/models/sandpiper_meta.py @@ -1,5 +1,5 @@ -import sqlalchemy as sa from sqlalchemy import Column +import sqlalchemy as sa from .base import Base diff --git a/sandpiper/user_data/models/user.py b/sandpiper/user_data/models/user.py index c4537be..f822a57 100644 --- a/sandpiper/user_data/models/user.py +++ b/sandpiper/user_data/models/user.py @@ -1,5 +1,5 @@ -import sqlalchemy as sa from sqlalchemy import Column, Index +import sqlalchemy as sa from ._types import Snowflake from .base import Base diff --git a/sandpiper/user_data/pronouns.py b/sandpiper/user_data/pronouns.py index f9e4f30..ad3a24f 100644 --- a/sandpiper/user_data/pronouns.py +++ b/sandpiper/user_data/pronouns.py @@ -1,4 +1,5 @@ from __future__ import annotations + from dataclasses import astuple, dataclass import re From e9fcc499ab8925036d1550fe99e25f6b3fb69efb Mon Sep 17 00:00:00 2001 From: Phanabani Date: Sun, 13 Nov 2022 01:37:46 -0500 Subject: [PATCH 24/33] Minor change. --- sandpiper/piperfig/parser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sandpiper/piperfig/parser.py b/sandpiper/piperfig/parser.py index 8830755..cc13a65 100644 --- a/sandpiper/piperfig/parser.py +++ b/sandpiper/piperfig/parser.py @@ -1,10 +1,10 @@ +__all__ = ["ConfigSchema"] + from functools import cached_property from io import TextIOBase import json import sys from types import MethodType - -# noinspection PyPep8Naming from typing import ( Annotated, Any, From 3339e9d1373dcce0d57b804f9b9cd1fb9c354378 Mon Sep 17 00:00:00 2001 From: Phanabani Date: Sun, 13 Nov 2022 01:40:45 -0500 Subject: [PATCH 25/33] Move `__all__` above imports. --- sandpiper/bios/cog.py | 4 ++-- sandpiper/bios/strings.py | 16 ++++++++-------- sandpiper/birthdays/cog.py | 4 ++-- sandpiper/common/IANA/database.py | 16 ++++++++-------- sandpiper/common/discord.py | 20 ++++++++++---------- sandpiper/common/embeds.py | 8 ++++---- sandpiper/common/misc.py | 4 ++-- sandpiper/common/time.py | 18 +++++++++--------- sandpiper/config.py | 4 ++-- sandpiper/conversion/time_conversion.py | 4 ++-- sandpiper/conversion/unit_conversion.py | 4 ++-- sandpiper/help.py | 4 ++-- sandpiper/piperfig/exceptions.py | 8 ++++---- sandpiper/piperfig/misc.py | 4 ++-- sandpiper/piperfig/parser.py | 2 -- sandpiper/piperfig/transformers.py | 16 ++++++++-------- sandpiper/sandpiper.py | 4 ++-- sandpiper/tests/helpers/discord.py | 15 ++++++++------- sandpiper/tests/helpers/misc.py | 4 ++-- sandpiper/tests/helpers/mocking.py | 12 ++++++------ sandpiper/upgrades/cog.py | 4 ++-- sandpiper/upgrades/upgrades.py | 4 ++-- sandpiper/user_data/alembic_utils.py | 4 ++-- sandpiper/user_data/cog.py | 4 ++-- sandpiper/user_data/database.py | 14 +++++++------- sandpiper/user_data/models/_types.py | 4 ++-- sandpiper/user_data/pronouns.py | 4 ++-- 27 files changed, 104 insertions(+), 105 deletions(-) diff --git a/sandpiper/bios/cog.py b/sandpiper/bios/cog.py index 883fa81..1fb0e11 100644 --- a/sandpiper/bios/cog.py +++ b/sandpiper/bios/cog.py @@ -1,3 +1,5 @@ +__all__ = ["Bios"] + import logging from typing import Optional @@ -12,8 +14,6 @@ from sandpiper.user_data import * from .strings import * -__all__ = ["Bios"] - logger = logging.getLogger("sandpiper.bios") diff --git a/sandpiper/bios/strings.py b/sandpiper/bios/strings.py index 96333d1..1deba59 100644 --- a/sandpiper/bios/strings.py +++ b/sandpiper/bios/strings.py @@ -1,3 +1,11 @@ +__all__ = [ + "PrivacyExplanation", + "BirthdayExplanations", + "info_str", + "user_info_str", + "user_names_str", +] + from typing import Any, Optional import discord @@ -7,14 +15,6 @@ from sandpiper.common.misc import join from sandpiper.user_data import Database, PrivacyType -__all__ = [ - "PrivacyExplanation", - "BirthdayExplanations", - "info_str", - "user_info_str", - "user_names_str", -] - privacy_emojis = {PrivacyType.PRIVATE: "⛔", PrivacyType.PUBLIC: "✅"} diff --git a/sandpiper/birthdays/cog.py b/sandpiper/birthdays/cog.py index a1808ce..de4a777 100644 --- a/sandpiper/birthdays/cog.py +++ b/sandpiper/birthdays/cog.py @@ -1,3 +1,5 @@ +__all__ = ["Birthdays"] + import asyncio import datetime as dt import logging @@ -14,8 +16,6 @@ from sandpiper.common.time import sort_dates_no_year, utc_now from sandpiper.user_data import Database, PrivacyType, UserData, common_pronouns -__all__ = ["Birthdays"] - logger = logging.getLogger("sandpiper.birthdays") diff --git a/sandpiper/common/IANA/database.py b/sandpiper/common/IANA/database.py index 6c43d57..e1ba3c5 100644 --- a/sandpiper/common/IANA/database.py +++ b/sandpiper/common/IANA/database.py @@ -1,17 +1,17 @@ -from operator import setitem -from pathlib import Path -from typing import Callable, NoReturn, Union - -from sandpiper.common.time import TimezoneType - -__all__ = ( +__all__ = [ "DEFAULT_FLAG", "country_code_to_country_name", "timezone_to_country_code", "to_regional_indicator", "get_country_flag_emoji", "get_country_flag_emoji_from_timezone", -) +] + +from operator import setitem +from pathlib import Path +from typing import Callable, NoReturn, Union + +from sandpiper.common.time import TimezoneType DEFAULT_FLAG = ":flag_white:" diff --git a/sandpiper/common/discord.py b/sandpiper/common/discord.py index 35231d0..07c2fe7 100644 --- a/sandpiper/common/discord.py +++ b/sandpiper/common/discord.py @@ -1,13 +1,3 @@ -from datetime import date -import logging -from typing import Optional - -import discord -from discord.ext.commands import BadArgument, Command - -from sandpiper.user_data.enums import PrivacyType -from .time import parse_date - __all__ = [ "AutoOrder", "date_handler", @@ -18,6 +8,16 @@ "find_users_by_username", ] +from datetime import date +import logging +from typing import Optional + +import discord +from discord.ext.commands import BadArgument, Command + +from sandpiper.user_data.enums import PrivacyType +from .time import parse_date + logger = logging.getLogger("sandpiper.common.discord") diff --git a/sandpiper/common/embeds.py b/sandpiper/common/embeds.py index dc39db2..8daa406 100644 --- a/sandpiper/common/embeds.py +++ b/sandpiper/common/embeds.py @@ -1,7 +1,3 @@ -from typing import Optional, Union - -import discord - __all__ = [ "SimpleEmbed", "SuccessEmbed", @@ -11,6 +7,10 @@ "SpecialEmbed", ] +from typing import Optional, Union + +import discord + T_Field = tuple[str, str, bool] diff --git a/sandpiper/common/misc.py b/sandpiper/common/misc.py index f33c3ad..785dcad 100644 --- a/sandpiper/common/misc.py +++ b/sandpiper/common/misc.py @@ -1,8 +1,8 @@ +__all__ = ["join", "prune", "listify", "RuntimeMessages"] + from collections.abc import Iterable, Sequence from typing import Optional, Union -__all__ = ["join", "prune", "listify", "RuntimeMessages"] - def join(*fragments, sep=""): return sep.join(str(f) for f in fragments if f) diff --git a/sandpiper/common/time.py b/sandpiper/common/time.py index d553a14..bcafde9 100644 --- a/sandpiper/common/time.py +++ b/sandpiper/common/time.py @@ -1,12 +1,3 @@ -from dataclasses import dataclass -import datetime as dt -import re -from typing import Optional, Union, cast - -from fuzzywuzzy import fuzz, process as fuzzy_process -import pytz -import tzlocal - __all__ = [ "TimezoneType", "no_zeropad", @@ -22,6 +13,15 @@ "fuzzy_match_timezone", ] +from dataclasses import dataclass +import datetime as dt +import re +from typing import Optional, Union, cast + +from fuzzywuzzy import fuzz, process as fuzzy_process +import pytz +import tzlocal + TimezoneType = Union[pytz.tzinfo.StaticTzInfo, pytz.tzinfo.DstTzInfo] time_pattern = re.compile( diff --git a/sandpiper/config.py b/sandpiper/config.py index 9e7f322..88abb89 100644 --- a/sandpiper/config.py +++ b/sandpiper/config.py @@ -1,5 +1,7 @@ from __future__ import annotations +__all__ = ["SandpiperConfig"] + from functools import cached_property import logging from logging.handlers import TimedRotatingFileHandler @@ -9,8 +11,6 @@ from sandpiper.common.paths import MODULE_PATH from sandpiper.piperfig import * -__all__ = ("SandpiperConfig",) - class SandpiperConfig(ConfigSchema): diff --git a/sandpiper/conversion/time_conversion.py b/sandpiper/conversion/time_conversion.py index bee5c0a..515f7a4 100644 --- a/sandpiper/conversion/time_conversion.py +++ b/sandpiper/conversion/time_conversion.py @@ -1,3 +1,5 @@ +__all__ = ["UserTimezoneUnset", "TimezoneNotFound", "convert_time_to_user_timezones"] + from collections import defaultdict from collections.abc import Iterable import datetime as dt @@ -10,8 +12,6 @@ from sandpiper.common.time import * from sandpiper.user_data import Database -__all__ = ("UserTimezoneUnset", "TimezoneNotFound", "convert_time_to_user_timezones") - logger = logging.getLogger("sandpiper.conversion.time_conversion") T_ConvertedTimes = list[tuple[str, list[dt.datetime]]] diff --git a/sandpiper/conversion/unit_conversion.py b/sandpiper/conversion/unit_conversion.py index 531d55b..b20a8b8 100644 --- a/sandpiper/conversion/unit_conversion.py +++ b/sandpiper/conversion/unit_conversion.py @@ -1,3 +1,5 @@ +__all__ = ["convert_measurement"] + from decimal import Decimal import logging import re @@ -9,8 +11,6 @@ from sandpiper.common.misc import RuntimeMessages from sandpiper.conversion.unit_map import UnitMap -__all__ = ["convert_measurement"] - logger = logging.getLogger("sandpiper.conversion.unit_conversion") ureg = UnitRegistry( diff --git a/sandpiper/help.py b/sandpiper/help.py index 0436b56..f2809c9 100644 --- a/sandpiper/help.py +++ b/sandpiper/help.py @@ -1,10 +1,10 @@ +__all__ = ["HelpCommand"] + from collections.abc import Iterable import itertools from discord.ext.commands import Cog, Command, DefaultHelpCommand, Group -__all__ = ["HelpCommand"] - def sort_commands_key(c: Command): try: diff --git a/sandpiper/piperfig/exceptions.py b/sandpiper/piperfig/exceptions.py index ff71d2b..91ca7cb 100644 --- a/sandpiper/piperfig/exceptions.py +++ b/sandpiper/piperfig/exceptions.py @@ -1,11 +1,11 @@ -from typing import Any - -__all__ = ( +__all__ = [ "ConfigSchemaError", "ConfigParsingError", "MissingFieldError", "ParsingError", -) +] + +from typing import Any class ConfigSchemaError(Exception): diff --git a/sandpiper/piperfig/misc.py b/sandpiper/piperfig/misc.py index 0bf116a..672c4e6 100644 --- a/sandpiper/piperfig/misc.py +++ b/sandpiper/piperfig/misc.py @@ -1,6 +1,6 @@ -from typing import NoReturn, Union +__all__ = ["qualified", "typecheck"] -__all__ = ("qualified", "typecheck") +from typing import NoReturn, Union def qualified(parent: str, name: str) -> str: diff --git a/sandpiper/piperfig/parser.py b/sandpiper/piperfig/parser.py index cc13a65..3b3d892 100644 --- a/sandpiper/piperfig/parser.py +++ b/sandpiper/piperfig/parser.py @@ -20,8 +20,6 @@ from .misc import * from .transformers import * -__all__ = ("ConfigSchema",) - NoDefault = object() diff --git a/sandpiper/piperfig/transformers.py b/sandpiper/piperfig/transformers.py index 4562a4d..a77d38c 100644 --- a/sandpiper/piperfig/transformers.py +++ b/sandpiper/piperfig/transformers.py @@ -1,17 +1,17 @@ -from abc import ABCMeta, abstractmethod -from pathlib import Path -from typing import Any, Optional, Type, TypeVar, overload - -from .misc import typecheck - -__all__ = ( +__all__ = [ "do_transformations", "do_transformations_back", "ConfigTransformer", "FromType", "Bounded", "MaybeRelativePath", -) +] + +from abc import ABCMeta, abstractmethod +from pathlib import Path +from typing import Any, Optional, Type, TypeVar, overload + +from .misc import typecheck V1 = TypeVar("V1") V2 = TypeVar("V2") diff --git a/sandpiper/sandpiper.py b/sandpiper/sandpiper.py index bc0bc40..32c0fb8 100644 --- a/sandpiper/sandpiper.py +++ b/sandpiper/sandpiper.py @@ -1,3 +1,5 @@ +__all__ = ["Sandpiper", "run_bot"] + import logging from pathlib import Path import sys @@ -8,8 +10,6 @@ from .config import SandpiperConfig from .help import HelpCommand -__all__ = ("Sandpiper", "run_bot") - logger = logging.getLogger("sandpiper") diff --git a/sandpiper/tests/helpers/discord.py b/sandpiper/tests/helpers/discord.py index 49b2136..022d748 100644 --- a/sandpiper/tests/helpers/discord.py +++ b/sandpiper/tests/helpers/discord.py @@ -1,10 +1,3 @@ -from typing import NoReturn, Union -from unittest import mock - -import discord - -from .misc import assert_in, assert_one_if_list - __all__ = [ "get_contents", "get_embeds", @@ -15,6 +8,14 @@ "assert_no_reply", ] +from typing import NoReturn, Union +from unittest import mock + +import discord + +from .misc import assert_in, assert_one_if_list + + # region Misc helper functions diff --git a/sandpiper/tests/helpers/misc.py b/sandpiper/tests/helpers/misc.py index ff0bd20..d89203a 100644 --- a/sandpiper/tests/helpers/misc.py +++ b/sandpiper/tests/helpers/misc.py @@ -1,10 +1,10 @@ +__all__ = ["assert_in", "assert_regex", "assert_one_if_list", "assert_count_equal"] + from collections import Counter from collections.abc import Iterable import re from typing import NoReturn, TypeVar, Union -__all__ = ["assert_in", "assert_regex", "assert_one_if_list", "assert_count_equal"] - V = TypeVar("V") diff --git a/sandpiper/tests/helpers/mocking.py b/sandpiper/tests/helpers/mocking.py index b51540e..1b57cfa 100644 --- a/sandpiper/tests/helpers/mocking.py +++ b/sandpiper/tests/helpers/mocking.py @@ -1,15 +1,15 @@ -import sys -from typing import Any, Optional -from unittest import mock - -import pytest - __all__ = [ "MagicMock_", "isinstance_mock_supported", "patch_all_symbol_imports", ] +import sys +from typing import Any, Optional +from unittest import mock + +import pytest + class MagicMock_(mock.MagicMock): """ diff --git a/sandpiper/upgrades/cog.py b/sandpiper/upgrades/cog.py index 0327686..b022020 100644 --- a/sandpiper/upgrades/cog.py +++ b/sandpiper/upgrades/cog.py @@ -1,3 +1,5 @@ +__all__ = ["Upgrades"] + import logging from typing import Optional @@ -8,8 +10,6 @@ from .upgrades import do_upgrades from .versions import all_upgrade_handlers -__all__ = ["Upgrades"] - logger = logging.getLogger(__name__) diff --git a/sandpiper/upgrades/upgrades.py b/sandpiper/upgrades/upgrades.py index d045212..d486623 100644 --- a/sandpiper/upgrades/upgrades.py +++ b/sandpiper/upgrades/upgrades.py @@ -1,3 +1,5 @@ +__all__ = ["UpgradeHandler", "do_upgrades"] + from abc import ABCMeta, abstractmethod import logging from typing import Optional, Type @@ -7,8 +9,6 @@ from sandpiper.user_data import Database, UserData -__all__ = ["UpgradeHandler", "do_upgrades"] - logger = logging.getLogger(__name__) diff --git a/sandpiper/user_data/alembic_utils.py b/sandpiper/user_data/alembic_utils.py index 8fe0760..a851ed8 100644 --- a/sandpiper/user_data/alembic_utils.py +++ b/sandpiper/user_data/alembic_utils.py @@ -1,3 +1,5 @@ +__all__ = ["get_current_heads", "stamp", "upgrade"] + from collections.abc import Callable import logging from pathlib import Path @@ -11,8 +13,6 @@ from sandpiper.user_data.models import Base -__all__ = ["get_current_heads", "stamp", "upgrade"] - logger = logging.getLogger(__name__) config_path = Path(__file__, "../alembic.ini").resolve().absolute() diff --git a/sandpiper/user_data/cog.py b/sandpiper/user_data/cog.py index 16304ac..4bfdac8 100644 --- a/sandpiper/user_data/cog.py +++ b/sandpiper/user_data/cog.py @@ -1,11 +1,11 @@ +__all__ = ["UserData", "DatabaseUnavailable"] + import logging import discord.ext.commands as commands from .database import Database -__all__ = ["UserData", "DatabaseUnavailable"] - logger = logging.getLogger("sandpiper.user_data") diff --git a/sandpiper/user_data/database.py b/sandpiper/user_data/database.py index 6d3b5cb..04a8f27 100644 --- a/sandpiper/user_data/database.py +++ b/sandpiper/user_data/database.py @@ -1,3 +1,10 @@ +__all__ = [ + "DEFAULT_PRIVACY", + "DatabaseError", + "UserNotInDatabase", + "Database", +] + from abc import ABCMeta, abstractmethod import datetime as dt from typing import Annotated, Optional @@ -8,13 +15,6 @@ from .enums import PrivacyType from .pronouns import Pronouns -__all__ = [ - "DEFAULT_PRIVACY", - "DatabaseError", - "UserNotInDatabase", - "Database", -] - DEFAULT_PRIVACY = PrivacyType.PRIVATE diff --git a/sandpiper/user_data/models/_types.py b/sandpiper/user_data/models/_types.py index 54c9779..402bde1 100644 --- a/sandpiper/user_data/models/_types.py +++ b/sandpiper/user_data/models/_types.py @@ -1,7 +1,7 @@ -import sqlalchemy.types as types - __all__ = ["Snowflake"] +import sqlalchemy.types as types + class Snowflake(types.TypeDecorator): """ diff --git a/sandpiper/user_data/pronouns.py b/sandpiper/user_data/pronouns.py index ad3a24f..9f47792 100644 --- a/sandpiper/user_data/pronouns.py +++ b/sandpiper/user_data/pronouns.py @@ -1,10 +1,10 @@ from __future__ import annotations +__all__ = ["Pronouns", "common_pronouns"] + from dataclasses import astuple, dataclass import re -__all__ = ["Pronouns", "common_pronouns"] - _slashed_group_pattern = re.compile(r"[a-zA-Z]+(?: *[/\\] *[a-zA-Z]+)*") From e7cb2d88b1e6027dc6d2a18d37b92c27507a5889 Mon Sep 17 00:00:00 2001 From: Phanabani Date: Sun, 13 Nov 2022 02:02:05 -0500 Subject: [PATCH 26/33] Change pipenv instructions to Poetry. --- README.md | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 0bf3a26..89523e5 100644 --- a/README.md +++ b/README.md @@ -33,32 +33,32 @@ Her current features include: ## Install -To get started, clone the repo. +### Prerequisites -```shell script -git clone https://github.com/phanabani/sandpiper.git -cd sandpiper -``` +- [Poetry](https://python-poetry.org/docs/#installation) – dependency manager +- (Optional) pyenv – Python version manager + - [pyenv](https://github.com/pyenv/pyenv) (Linux, Mac) + - [pyenv-win](https://github.com/pyenv-win/pyenv-win) (Windows) +- (Optional) [PM2](https://pm2.keymetrics.io/docs/usage/quick-start) – process manager -[Install Pipenv](https://pipenv.pypa.io/en/latest/install/#installing-pipenv). -After installing for the first time, you may have to do some extra steps to -be able to use the `pipenv` command in your shell. Read the note in the -Pipenv installation page for more info. +### Install Sandpiper + +To get started, clone the repo. ```shell -python -m pip install --user pipenv +git clone https://github.com/phanabani/sandpiper.git +cd sandpiper ``` -Install the dependencies with Pipenv. Sandpiper requires Python 3.9+. +Install the dependencies with Poetry. Sandpiper requires Python 3.10. ```shell -# If Python 3.9 is your default version: -pipenv sync +# If you're using pyenv, run the following to init a Poetry environment using +# the correct Python version +poetry env use $(pyenv which python) -# If Python 3.9 is NOT your default version, you should specify the path to -# your Python 3.9 executable -pipenv sync --python "/usr/bin/python3.9" -pipenv sync --python "C:\Miniconda3\envs\python39\python.exe" +# Install dependencies +poetry install --no-root --no-dev ``` ## Usage @@ -80,10 +80,10 @@ See [config](#config) for more info. #### Basic -In the top level directory, simply run Sandpiper as a Python module with Pipenv. +In the top level directory, simply run Sandpiper as a Python module with Poetry. ```shell script -pipenv run python -m sandpiper +poetry run python -m sandpiper ``` #### With PM2 @@ -326,11 +326,11 @@ message: ### Installation -Follow the installation steps in [install](#install) and use Pipenv to +Follow the installation steps in [install](#install) and use Poetry to install the development dependencies: ```bash -pipenv sync --dev +poetry install --no-root ``` ### Testing @@ -338,16 +338,16 @@ pipenv sync --dev #### Run unit tests ```bash -pipenv run python -m pytest --pyargs sandpiper +poetry run python -m pytest --pyargs sandpiper # or run tests with profiling (--profile-svg to generate svg image): -pipenv run python -m pytest --pyargs --profile sandpiper +poetry run python -m pytest --pyargs --profile sandpiper ``` #### Run tests with code coverage ```bash -pipenv run coverage run -m pytest --pyargs sandpiper -pipenv run coverage html +poetry run coverage run -m pytest --pyargs sandpiper +poetry run coverage html ``` Open `htmlcov/index.html` to view the coverage report. From 7c00fc7177f41851737e3538855f492d7ec39fd4 Mon Sep 17 00:00:00 2001 From: Phanabani Date: Sun, 13 Nov 2022 02:03:20 -0500 Subject: [PATCH 27/33] Add info about the message content privileged intent. --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 89523e5..df15788 100644 --- a/README.md +++ b/README.md @@ -108,9 +108,15 @@ Sandpiper requires the following permissions to run normally: These correspond to the permission integer `19456`. -Sandpiper also requires the [server members privileged intent](https://discord.com/developers/docs/topics/gateway#privileged-intents) -to allow for Discord username/server nickname lookup in the `whois` command. -You can enable it on the bot page of your application (https:\/\/discord.com/developers/applications//bot). +Sandpiper also requires the following [privileged intents](https://discord.com/developers/docs/topics/gateway#privileged-intents): + +- Server members + - For Discord username/server nickname lookup in the `whois` command +- Message content + - For running commands (will be replaced by slash commands) + - For searching messages for unit/time conversion strings + +You can enable these on the bot page of your application (`https://discord.com/developers/applications/CLIENT_ID/bot`). ## Config From 767f0188b28549250474d91ca94c968d17e5b855 Mon Sep 17 00:00:00 2001 From: Phanabani Date: Sun, 13 Nov 2022 02:05:55 -0500 Subject: [PATCH 28/33] Add planned features. --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index df15788..6db9503 100644 --- a/README.md +++ b/README.md @@ -368,6 +368,11 @@ Open `htmlcov/index.html` to view the coverage report. - [X] Timezone - [X] Time conversion - [X] Birthday notifications +- [X] Thread support +- [ ] Slash commands +- [ ] Conversion editing/deletion +- [ ] Time conversion images +- [ ] Currency conversions ## Inspiration From 28fd4edb83440644923a8a08aca7e52f8932ab01 Mon Sep 17 00:00:00 2001 From: Phanabani Date: Sun, 13 Nov 2022 02:42:39 -0500 Subject: [PATCH 29/33] Add CHANGELOG.md. --- CHANGELOG.md | 141 +++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 5 ++ 2 files changed, 146 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8514131 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,141 @@ +# Changelog + +Hi! Here is Sandpiper's version history. Look at her journey! c: + + +## Table of contents + +- [2.0.0 - Thread support](#200---thread-support) +- [1.6.0 - Birthday notifications](#160---birthday-notifications) +- [1.5.0 - Better conversions and test coverage](#150---better-conversions-and-test-coverage) +- [1.4.1](#141) +- [1.4.0 - Better help messages and various improvements](#140---better-help-messages-and-various-improvements) +- [1.3.0 - Async database and unit testing](#130---async-database-and-unit-testing) +- [1.2.2](#122) +- [1.2.1](#121) +- [1.2.0 - Time conversion](#120---time-conversion) +- [1.1.0 - User bios](#110---user-bios) +- [1.0.0 - Unit conversion](#100---unit-conversion) + + +## 2.0.0 - Thread support + +### Feature + +- Add support for Discord threads + +### Change (internal) +- Upgrade to Python 3.10 +- Use Poetry +- Reformat with black +- Migrate to discord.py v2 +- Optimize imports in all files + +### Fix (internal) + +- Fix flaky unit tests for discord.py v2 + + +## 1.6.0 - Birthday notifications + +### Feature + +- Add birthday notifications +- Add `birthdays upcoming` command + + +## 1.5.0 - Better conversions and test coverage + +### Feature + +- Add the ability to specify and input and/or output timezone for time conversions +- Add a ton of new default unit conversion mappings + +### Change (internal) + +- Massively upgrade the conversions test suite to test many more conversion scenarios and allow for more easily adding tests in the future + + +## 1.4.1 + +### Feature + +- Add "noon", "now", and "midnight" keywords for time conversion + +### Change + +- Allow time strings to be written without a colon separating hour and minutes (1430 = 14:30) + + +## 1.4.0 - Better help messages and various improvements + +Help messages are now more meaningful! One shouldn't need to come to the repo +anymore to figure out how to use a command. Various bugfixes and quality of +life changes are also included. + +### Feature + +- Improve help messages + + +## 1.3.0 - Async database and unit testing + +Switched to an asynchronous API for the database and added unit tests to ensure +functionality remains stable after large changes like this. + +### Feature (internal) + +- Add unit tests + +### Change (internal) + +- Use an async database API + + +## 1.2.2 + +Fixed an issue where null timezones weren't handled. + +### Fix + +- Fix null timezones not being handled + + +## 1.2.1 + +Fixes an issue where duplicate timezones are printed in time conversion output +if multiple users share the timezone. + +### Fix + +- Fix duplicate timezones showing in timezone list + + +## 1.2.0 - Time conversion + +Added time conversion. Users can now convert a time to all timezones of users +in a server. + +### Feature + +- Add time conversion + + +## 1.1.0 - User bios + +Added user bios. Users can store some personal information in Sandpiper so that +others can get to know them better. + +### Feature + +- Add user bios commands + + +## 1.0.0 - Unit conversion + +Sandpiper's debut! Currently featuring unit conversion between imperial and +metric measurements. + +### Feature + +- Add unit conversion diff --git a/README.md b/README.md index 6db9503..3d10446 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ Her current features include: - [Bios](#bios) - [Birthday notifications](#birthday-notifications) - [Developers](#developers) +- [Changelog](#changelog) - [Planned features](#planned-features) - [Inspiration](#inspiration) - [License](#license) @@ -358,6 +359,10 @@ poetry run coverage html Open `htmlcov/index.html` to view the coverage report. +## Changelog + +Check out Sandpiper's version history in [CHANGELOG.md](CHANGELOG.md)! + ## Planned features - [X] Unit conversion From 24914066801f64658e523f419d61165090d09957 Mon Sep 17 00:00:00 2001 From: Phanabani Date: Sun, 13 Nov 2022 03:04:59 -0500 Subject: [PATCH 30/33] Use Poetry in GitHub action. --- .github/workflows/python-app.yml | 35 ++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 0a8f11e..906e5bf 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -3,11 +3,7 @@ name: Python application -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] +on: [push, pull_request] jobs: build: @@ -15,23 +11,32 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Set up Python 3.9 - uses: actions/setup-python@v2 + - uses: actions/checkout@v3 + + - name: Set up Python 3.10 + uses: actions/setup-python@v4 with: - python-version: 3.9 + python-version: 3.10 + - name: Install dependencies run: | python -m pip install --upgrade pip - pip install pipenv - pipenv install flake8 pytest - if [ -f Pipfile.lock ]; then pipenv sync --dev; fi + + python -m pip install --user pipx + python -m pipx ensurepath + + pipx install poetry + + poetry install flake8 + if [ -f poetry.lock ]; then poetry install --no-root; fi + - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names - pipenv run flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + poetry run flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - pipenv run flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + poetry run flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with pytest run: | - pipenv run pytest + poetry run pytest From bbe6ecc832fb6673900a524bc5a22cddadc6c9fd Mon Sep 17 00:00:00 2001 From: Phanabani Date: Sun, 13 Nov 2022 03:07:52 -0500 Subject: [PATCH 31/33] Fix Python version not quoted. --- .github/workflows/python-app.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 906e5bf..50a742a 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Python 3.10 uses: actions/setup-python@v4 with: - python-version: 3.10 + python-version: '3.10' - name: Install dependencies run: | From ed5c8bb8be1d8a10705a93a8cd423542e5ca923d Mon Sep 17 00:00:00 2001 From: Phanabani Date: Sun, 13 Nov 2022 03:09:58 -0500 Subject: [PATCH 32/33] Fix Poetry command. --- .github/workflows/python-app.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 50a742a..a6014ca 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -27,7 +27,7 @@ jobs: pipx install poetry - poetry install flake8 + poetry add flake8 if [ -f poetry.lock ]; then poetry install --no-root; fi - name: Lint with flake8 From 17d5653dce3e8f76884a3289e3893ab04537cc43 Mon Sep 17 00:00:00 2001 From: Phanabani Date: Sun, 13 Nov 2022 03:13:29 -0500 Subject: [PATCH 33/33] Add flake8 so GitHub action doesn't need to resolve dependencies. --- .github/workflows/python-app.yml | 1 - poetry.lock | 55 +++++++++++++++++++++++++++++++- pyproject.toml | 3 ++ 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index a6014ca..63aacfe 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -27,7 +27,6 @@ jobs: pipx install poetry - poetry add flake8 if [ -f poetry.lock ]; then poetry install --no-root; fi - name: Lint with flake8 diff --git a/poetry.lock b/poetry.lock index 5cbab9e..0019632 100644 --- a/poetry.lock +++ b/poetry.lock @@ -156,6 +156,19 @@ 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)"] voice = ["PyNaCl (>=1.3.0,<1.6)"] +[[package]] +name = "flake8" +version = "5.0.4" +description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.9.0,<2.10.0" +pyflakes = ">=2.5.0,<2.6.0" + [[package]] name = "frozenlist" version = "1.3.3" @@ -308,6 +321,14 @@ python-versions = ">=3.5" [package.dependencies] traitlets = "*" +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +category = "dev" +optional = false +python-versions = ">=3.6" + [[package]] name = "multidict" version = "6.0.2" @@ -421,6 +442,22 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +[[package]] +name = "pycodestyle" +version = "2.9.1" +description = "Python style guide checker" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "pyflakes" +version = "2.5.0" +description = "passive checker of Python programs" +category = "dev" +optional = false +python-versions = ">=3.6" + [[package]] name = "pygments" version = "2.13.0" @@ -690,7 +727,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "3a77fe6cd930127bfadd6655326f3c6ad8fa8804760d879df310b36999919393" +content-hash = "caf212eb8cfe19636bcdc0cefd50d7a3dd5ae0b08083e395c7293ca669964d42" [metadata.files] aiohttp = [ @@ -881,6 +918,10 @@ discord-py = [ {file = "discord.py-2.0.1-py3-none-any.whl", hash = "sha256:aeb186348bf011708b085b2715cf92bbb72c692eb4f59c4c0b488130cc4c4b7e"}, {file = "discord.py-2.0.1.tar.gz", hash = "sha256:309146476e986cb8faf038cd5d604d4b3834ef15c2d34df697ce5064bf5cd779"}, ] +flake8 = [ + {file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"}, + {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"}, +] frozenlist = [ {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff8bf625fe85e119553b5383ba0fb6aa3d0ec2ae980295aaefa552374926b3f4"}, {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dfbac4c2dfcc082fcf8d942d1e49b6aa0766c19d3358bd86e2000bf0fa4a9cf0"}, @@ -1194,6 +1235,10 @@ matplotlib-inline = [ {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"}, {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"}, ] +mccabe = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] multidict = [ {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2"}, {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3"}, @@ -1295,6 +1340,14 @@ py = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] +pycodestyle = [ + {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"}, + {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"}, +] +pyflakes = [ + {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, + {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, +] pygments = [ {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"}, {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"}, diff --git a/pyproject.toml b/pyproject.toml index 48a6b86..2b580d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,9 @@ pytest-asyncio = "^0.16.0" pytest-profiling = "^1.7.0" yappi = "^1.3.3" +[tool.poetry.group.dev.dependencies] +flake8 = "^5.0.4" + [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api"