From a2e1d521bed512d8a0dd9b0bd000afc558984fd5 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 26 Jul 2023 10:41:32 +0200 Subject: [PATCH 001/143] feat(endpoint): add initial features --- poetry.lock | 473 +++++++++++++++++++++ pyproject.toml | 18 + src/zulip_write_only_proxy/__init__.py | 0 src/zulip_write_only_proxy/main.py | 38 ++ src/zulip_write_only_proxy/zulip_client.py | 14 + tests/__init__.py | 0 6 files changed, 543 insertions(+) create mode 100644 poetry.lock create mode 100644 pyproject.toml create mode 100644 src/zulip_write_only_proxy/__init__.py create mode 100644 src/zulip_write_only_proxy/main.py create mode 100644 src/zulip_write_only_proxy/zulip_client.py create mode 100644 tests/__init__.py diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 00000000..93d9b306 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,473 @@ +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. + +[[package]] +name = "annotated-types" +version = "0.5.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.7" +files = [ + {file = "annotated_types-0.5.0-py3-none-any.whl", hash = "sha256:58da39888f92c276ad970249761ebea80ba544b77acddaa1a4d6cf78287d45fd"}, + {file = "annotated_types-0.5.0.tar.gz", hash = "sha256:47cdc3490d9ac1506ce92c7aaa76c579dc3509ff11e098fc867e5130ab7be802"}, +] + +[[package]] +name = "anyio" +version = "3.7.1" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.7" +files = [ + {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, + {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, +] + +[package.dependencies] +idna = ">=2.8" +sniffio = ">=1.1" + +[package.extras] +doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"] +test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (<0.22)"] + +[[package]] +name = "certifi" +version = "2023.7.22" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.2.0" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"}, + {file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, +] + +[[package]] +name = "click" +version = "8.1.6" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.6-py3-none-any.whl", hash = "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"}, + {file = "click-8.1.6.tar.gz", hash = "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "distro" +version = "1.8.0" +description = "Distro - an OS platform information API" +optional = false +python-versions = ">=3.6" +files = [ + {file = "distro-1.8.0-py3-none-any.whl", hash = "sha256:99522ca3e365cac527b44bde033f64c6945d90eb9f769703caaec52b09bbd3ff"}, + {file = "distro-1.8.0.tar.gz", hash = "sha256:02e111d1dc6a50abb8eed6bf31c3e48ed8b0830d1ea2a1b78c61765c2513fdd8"}, +] + +[[package]] +name = "fastapi" +version = "0.100.0" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +optional = false +python-versions = ">=3.7" +files = [ + {file = "fastapi-0.100.0-py3-none-any.whl", hash = "sha256:271662daf986da8fa98dc2b7c7f61c4abdfdccfb4786d79ed8b2878f172c6d5f"}, + {file = "fastapi-0.100.0.tar.gz", hash = "sha256:acb5f941ea8215663283c10018323ba7ea737c571b67fc7e88e9469c7eb1d12e"}, +] + +[package.dependencies] +pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<3.0.0" +starlette = ">=0.27.0,<0.28.0" +typing-extensions = ">=4.5.0" + +[package.extras] +all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] + +[[package]] +name = "matrix-client" +version = "0.4.0" +description = "Client-Server SDK for Matrix" +optional = false +python-versions = "*" +files = [ + {file = "matrix_client-0.4.0-py2.py3-none-any.whl", hash = "sha256:20cb42fb644879858c3fdd348d1c349c33676f11d1597f820abfd0fc0e009cb1"}, + {file = "matrix_client-0.4.0.tar.gz", hash = "sha256:0678af40f2cb2f0928a908a410c029747d40cb961ac5a3f1bd05aa35563c3156"}, +] + +[package.dependencies] +requests = ">=2.22,<3.0" +urllib3 = ">=1.21,<2.0" + +[package.extras] +doc = ["Sphinx (>=1.7.6,<2.dev0)", "sphinx-rtd-theme (>=0.1.9,<0.2.dev0)", "sphinxcontrib-napoleon (>=0.5.3,<0.6.dev0)"] +e2e = ["canonicaljson (>=1.1,<2.0)", "python-olm (>=3.1,<4.0)"] +test = ["pytest (>=4.6,<6.0.0)", "responses (>=0.10.6,<0.11.dev0)"] + +[[package]] +name = "pydantic" +version = "2.1.1" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic-2.1.1-py3-none-any.whl", hash = "sha256:43bdbf359d6304c57afda15c2b95797295b702948082d4c23851ce752f21da70"}, + {file = "pydantic-2.1.1.tar.gz", hash = "sha256:22d63db5ce4831afd16e7c58b3192d3faf8f79154980d9397d9867254310ba4b"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.4.0" +typing-extensions = ">=4.6.1" + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.4.0" +description = "" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic_core-2.4.0-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:2ca4687dd996bde7f3c420def450797feeb20dcee2b9687023e3323c73fc14a2"}, + {file = "pydantic_core-2.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:782fced7d61469fd1231b184a80e4f2fa7ad54cd7173834651a453f96f29d673"}, + {file = "pydantic_core-2.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6213b471b68146af97b8551294e59e7392c2117e28ffad9c557c65087f4baee3"}, + {file = "pydantic_core-2.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63797499a219d8e81eb4e0c42222d0a4c8ec896f5c76751d4258af95de41fdf1"}, + {file = "pydantic_core-2.4.0-cp310-cp310-manylinux_2_24_armv7l.whl", hash = "sha256:0455876d575a35defc4da7e0a199596d6c773e20d3d42fa1fc29f6aa640369ed"}, + {file = "pydantic_core-2.4.0-cp310-cp310-manylinux_2_24_ppc64le.whl", hash = "sha256:8c938c96294d983dcf419b54dba2d21056959c22911d41788efbf949a29ae30d"}, + {file = "pydantic_core-2.4.0-cp310-cp310-manylinux_2_24_s390x.whl", hash = "sha256:878a5017d93e776c379af4e7b20f173c82594d94fa073059bcc546789ad50bf8"}, + {file = "pydantic_core-2.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:69159afc2f2dc43285725f16143bc5df3c853bc1cb7df6021fce7ef1c69e8171"}, + {file = "pydantic_core-2.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:54df7df399b777c1fd144f541c95d351b3aa110535a6810a6a569905d106b6f3"}, + {file = "pydantic_core-2.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e412607ca89a0ced10758dfb8f9adcc365ce4c1c377e637c01989a75e9a9ec8a"}, + {file = "pydantic_core-2.4.0-cp310-none-win32.whl", hash = "sha256:853f103e2b9a58832fdd08a587a51de8b552ae90e1a5d167f316b7eabf8d7dde"}, + {file = "pydantic_core-2.4.0-cp310-none-win_amd64.whl", hash = "sha256:3ba2c9c94a9176f6321a879c8b864d7c5b12d34f549a4c216c72ce213d7d953c"}, + {file = "pydantic_core-2.4.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:a8b7acd04896e8f161e1500dc5f218017db05c1d322f054e89cbd089ce5d0071"}, + {file = "pydantic_core-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:16468bd074fa4567592d3255bf25528ed41e6b616d69bf07096bdb5b66f947d1"}, + {file = "pydantic_core-2.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cba5ad5eef02c86a1f3da00544cbc59a510d596b27566479a7cd4d91c6187a11"}, + {file = "pydantic_core-2.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7206e41e04b443016e930e01685bab7a308113c0b251b3f906942c8d4b48fcb"}, + {file = "pydantic_core-2.4.0-cp311-cp311-manylinux_2_24_armv7l.whl", hash = "sha256:c1375025f0bfc9155286ebae8eecc65e33e494c90025cda69e247c3ccd2bab00"}, + {file = "pydantic_core-2.4.0-cp311-cp311-manylinux_2_24_ppc64le.whl", hash = "sha256:3534118289e33130ed3f1cc487002e8d09b9f359be48b02e9cd3de58ce58fba9"}, + {file = "pydantic_core-2.4.0-cp311-cp311-manylinux_2_24_s390x.whl", hash = "sha256:94d2b36a74623caab262bf95f0e365c2c058396082bd9d6a9e825657d0c1e7fa"}, + {file = "pydantic_core-2.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:af24ad4fbaa5e4a2000beae0c3b7fd1c78d7819ab90f9370a1cfd8998e3f8a3c"}, + {file = "pydantic_core-2.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bf10963d8aed8bbe0165b41797c9463d4c5c8788ae6a77c68427569be6bead41"}, + {file = "pydantic_core-2.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:68199ada7c310ddb8c76efbb606a0de656b40899388a7498954f423e03fc38be"}, + {file = "pydantic_core-2.4.0-cp311-none-win32.whl", hash = "sha256:6f855bcc96ed3dd56da7373cfcc9dcbabbc2073cac7f65c185772d08884790ce"}, + {file = "pydantic_core-2.4.0-cp311-none-win_amd64.whl", hash = "sha256:de39eb3bab93a99ddda1ac1b9aa331b944d8bcc4aa9141148f7fd8ee0299dafc"}, + {file = "pydantic_core-2.4.0-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:f773b39780323a0499b53ebd91a28ad11cde6705605d98d999dfa08624caf064"}, + {file = "pydantic_core-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a297c0d6c61963c5c3726840677b798ca5b7dfc71bc9c02b9a4af11d23236008"}, + {file = "pydantic_core-2.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:546064c55264156b973b5e65e5fafbe5e62390902ce3cf6b4005765505e8ff56"}, + {file = "pydantic_core-2.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36ba9e728588588f0196deaf6751b9222492331b5552f865a8ff120869d372e0"}, + {file = "pydantic_core-2.4.0-cp312-cp312-manylinux_2_24_armv7l.whl", hash = "sha256:57a53a75010c635b3ad6499e7721eaa3b450e03f6862afe2dbef9c8f66e46ec8"}, + {file = "pydantic_core-2.4.0-cp312-cp312-manylinux_2_24_ppc64le.whl", hash = "sha256:4b262bbc13022f2097c48a21adcc360a81d83dc1d854c11b94953cd46d7d3c07"}, + {file = "pydantic_core-2.4.0-cp312-cp312-manylinux_2_24_s390x.whl", hash = "sha256:01947ad728f426fa07fcb26457ebf90ce29320259938414bc0edd1476e75addb"}, + {file = "pydantic_core-2.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b2799c2eaf182769889761d4fb4d78b82bc47dae833799fedbf69fc7de306faa"}, + {file = "pydantic_core-2.4.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a08fd490ba36d1fbb2cd5dcdcfb9f3892deb93bd53456724389135712b5fc735"}, + {file = "pydantic_core-2.4.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1e8a7c62d15a5c4b307271e4252d76ebb981d6251c6ecea4daf203ef0179ea4f"}, + {file = "pydantic_core-2.4.0-cp312-none-win32.whl", hash = "sha256:9206c14a67c38de7b916e486ae280017cf394fa4b1aa95cfe88621a4e1d79725"}, + {file = "pydantic_core-2.4.0-cp312-none-win_amd64.whl", hash = "sha256:884235507549a6b2d3c4113fb1877ae263109e787d9e0eb25c35982ab28d0399"}, + {file = "pydantic_core-2.4.0-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:4cbe929efa77a806e8f1a97793f2dc3ea3475ae21a9ed0f37c21320fe93f6f50"}, + {file = "pydantic_core-2.4.0-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:9137289de8fe845c246a8c3482dd0cb40338846ba683756d8f489a4bd8fddcae"}, + {file = "pydantic_core-2.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5d8e764b5646623e57575f624f8ebb8f7a9f7fd1fae682ef87869ca5fec8dcf"}, + {file = "pydantic_core-2.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fba0aff4c407d0274e43697e785bcac155ad962be57518d1c711f45e72da70f"}, + {file = "pydantic_core-2.4.0-cp37-cp37m-manylinux_2_24_armv7l.whl", hash = "sha256:30527d173e826f2f7651f91c821e337073df1555e3b5a0b7b1e2c39e26e50678"}, + {file = "pydantic_core-2.4.0-cp37-cp37m-manylinux_2_24_ppc64le.whl", hash = "sha256:bd7d1dde70ff3e09e4bc7a1cbb91a7a538add291bfd5b3e70ef1e7b45192440f"}, + {file = "pydantic_core-2.4.0-cp37-cp37m-manylinux_2_24_s390x.whl", hash = "sha256:72f1216ca8cef7b8adacd4c4c6b89c3b0c4f97503197f5284c80f36d6e4edd30"}, + {file = "pydantic_core-2.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b013c7861a7c7bfcec48fd709513fea6f9f31727e7a0a93ca0dd12e056740717"}, + {file = "pydantic_core-2.4.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:478f5f6d7e32bd4a04d102160efb2d389432ecf095fe87c555c0a6fc4adfc1a4"}, + {file = "pydantic_core-2.4.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d9610b47b5fe4aacbbba6a9cb5f12cbe864eec99dbfed5710bd32ef5dd8a5d5b"}, + {file = "pydantic_core-2.4.0-cp37-none-win32.whl", hash = "sha256:ff246c0111076c8022f9ba325c294f2cb5983403506989253e04dbae565e019b"}, + {file = "pydantic_core-2.4.0-cp37-none-win_amd64.whl", hash = "sha256:d0c2b713464a8e263a243ae7980d81ce2de5ac59a9f798a282e44350b42dc516"}, + {file = "pydantic_core-2.4.0-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:12ef6838245569fd60a179fade81ca4b90ae2fa0ef355d616f519f7bb27582db"}, + {file = "pydantic_core-2.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:49db206eb8fdc4b4f30e6e3e410584146d813c151928f94ec0db06c4f2595538"}, + {file = "pydantic_core-2.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a507d7fa44688bbac76af6521e488b3da93de155b9cba6f2c9b7833ce243d59"}, + {file = "pydantic_core-2.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffe18407a4d000c568182ce5388bbbedeb099896904e43fc14eee76cfae6dec5"}, + {file = "pydantic_core-2.4.0-cp38-cp38-manylinux_2_24_armv7l.whl", hash = "sha256:fa8e48001b39d54d97d7b380a0669fa99fc0feeb972e35a2d677ba59164a9a22"}, + {file = "pydantic_core-2.4.0-cp38-cp38-manylinux_2_24_ppc64le.whl", hash = "sha256:394f12a2671ff8c4dfa2e85be6c08be0651ad85bc1e6aa9c77c21671baaf28cd"}, + {file = "pydantic_core-2.4.0-cp38-cp38-manylinux_2_24_s390x.whl", hash = "sha256:2f9ea0355f90db2a76af530245fa42f04d98f752a1236ed7c6809ec484560d5b"}, + {file = "pydantic_core-2.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:61d4e713f467abcdd59b47665d488bb898ad3dd47ce7446522a50e0cbd8e8279"}, + {file = "pydantic_core-2.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:453862ab268f6326b01f067ed89cb3a527d34dc46f6f4eeec46a15bbc706d0da"}, + {file = "pydantic_core-2.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:56a85fa0dab1567bd0cac10f0c3837b03e8a0d939e6a8061a3a420acd97e9421"}, + {file = "pydantic_core-2.4.0-cp38-none-win32.whl", hash = "sha256:0d726108c1c0380b88b6dd4db559f0280e0ceda9e077f46ff90bc85cd4d03e77"}, + {file = "pydantic_core-2.4.0-cp38-none-win_amd64.whl", hash = "sha256:047580388644c473b934d27849f8ed8dbe45df0adb72104e78b543e13bf69762"}, + {file = "pydantic_core-2.4.0-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:867d3eea954bea807cabba83cfc939c889a18576d66d197c60025b15269d7cc0"}, + {file = "pydantic_core-2.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:664402ef0c238a7f8a46efb101789d5f2275600fb18114446efec83cfadb5b66"}, + {file = "pydantic_core-2.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64e8012ad60a5f0da09ed48725e6e923d1be25f2f091a640af6079f874663813"}, + {file = "pydantic_core-2.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac2b680de398f293b68183317432b3d67ab3faeba216aec18de0c395cb5e3060"}, + {file = "pydantic_core-2.4.0-cp39-cp39-manylinux_2_24_armv7l.whl", hash = "sha256:8efc1be43b036c2b6bcfb1451df24ee0ddcf69c31351003daf2699ed93f5687b"}, + {file = "pydantic_core-2.4.0-cp39-cp39-manylinux_2_24_ppc64le.whl", hash = "sha256:d93aedbc4614cc21b9ab0d0c4ccd7143354c1f7cffbbe96ae5216ad21d1b21b5"}, + {file = "pydantic_core-2.4.0-cp39-cp39-manylinux_2_24_s390x.whl", hash = "sha256:af788b64e13d52fc3600a68b16d31fa8d8573e3ff2fc9a38f8a60b8d94d1f012"}, + {file = "pydantic_core-2.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:97c6349c81cee2e69ef59eba6e6c08c5936e6b01c2d50b9e4ac152217845ae09"}, + {file = "pydantic_core-2.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cc086ddb6dc654a15deeed1d1f2bcb1cb924ebd70df9dca738af19f64229b06c"}, + {file = "pydantic_core-2.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e953353180bec330c3b830891d260b6f8e576e2d18db3c78d314e56bb2276066"}, + {file = "pydantic_core-2.4.0-cp39-none-win32.whl", hash = "sha256:6feb4b64d11d5420e517910d60a907d08d846cacaf4e029668725cd21d16743c"}, + {file = "pydantic_core-2.4.0-cp39-none-win_amd64.whl", hash = "sha256:153a61ac4030fa019b70b31fb7986461119230d3ba0ab661c757cfea652f4332"}, + {file = "pydantic_core-2.4.0-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:3fcf529382b282a30b466bd7af05be28e22aa620e016135ac414f14e1ee6b9e1"}, + {file = "pydantic_core-2.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2edef05b63d82568b877002dc4cb5cc18f8929b59077120192df1e03e0c633f8"}, + {file = "pydantic_core-2.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da055a1b0bfa8041bb2ff586b2cb0353ed03944a3472186a02cc44a557a0e661"}, + {file = "pydantic_core-2.4.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:77dadc764cf7c5405e04866181c5bd94a447372a9763e473abb63d1dfe9b7387"}, + {file = "pydantic_core-2.4.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:a4ea23b07f29487a7bef2a869f68c7ee0e05424d81375ce3d3de829314c6b5ec"}, + {file = "pydantic_core-2.4.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:382f0baa044d674ad59455a5eff83d7965572b745cc72df35c52c2ce8c731d37"}, + {file = "pydantic_core-2.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:08f89697625e453421401c7f661b9d1eb4c9e4c0a12fd256eeb55b06994ac6af"}, + {file = "pydantic_core-2.4.0-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:43a405ce520b45941df9ff55d0cd09762017756a7b413bbad3a6e8178e64a2c2"}, + {file = "pydantic_core-2.4.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:584a7a818c84767af16ce8bda5d4f7fedb37d3d231fc89928a192f567e4ef685"}, + {file = "pydantic_core-2.4.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04922fea7b13cd480586fa106345fe06e43220b8327358873c22d8dfa7a711c7"}, + {file = "pydantic_core-2.4.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17156abac20a9feed10feec867fddd91a80819a485b0107fe61f09f2117fe5f3"}, + {file = "pydantic_core-2.4.0-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4e562cc63b04636cde361fd47569162f1daa94c759220ff202a8129902229114"}, + {file = "pydantic_core-2.4.0-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:90f3785146f701e053bb6b9e8f53acce2c919aca91df88bd4975be0cb926eb41"}, + {file = "pydantic_core-2.4.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:e40b1e97edd3dc127aa53d8a5e539a3d0c227d71574d3f9ac1af02d58218a122"}, + {file = "pydantic_core-2.4.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:b27f3e67f6e031f6620655741b7d0d6bebea8b25d415924b3e8bfef2dd7bd841"}, + {file = "pydantic_core-2.4.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be86c2eb12fb0f846262ace9d8f032dc6978b8cb26a058920ecb723dbcb87d05"}, + {file = "pydantic_core-2.4.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4665f7ed345012a8d2eddf4203ef145f5f56a291d010382d235b94e91813f88a"}, + {file = "pydantic_core-2.4.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:79262be5a292d1df060f29b9a7cdd66934801f987a817632d7552534a172709a"}, + {file = "pydantic_core-2.4.0-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:5fd905a69ac74eaba5041e21a1e8b1a479dab2b41c93bdcc4c1cede3c12a8d86"}, + {file = "pydantic_core-2.4.0-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:2ad538b7e07343001934417cdc8584623b4d8823c5b8b258e75ec8d327cec969"}, + {file = "pydantic_core-2.4.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:dd2429f7635ad4857b5881503f9c310be7761dc681c467a9d27787b674d1250a"}, + {file = "pydantic_core-2.4.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:efff8b6761a1f6e45cebd1b7a6406eb2723d2d5710ff0d1b624fe11313693989"}, + {file = "pydantic_core-2.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32a1e0352558cd7ccc014ffe818c7d87b15ec6145875e2cc5fa4bb7351a1033d"}, + {file = "pydantic_core-2.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a027f41c5008571314861744d83aff75a34cf3a07022e0be32b214a5bc93f7f1"}, + {file = "pydantic_core-2.4.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1927f0e15d190f11f0b8344373731e28fd774c6d676d8a6cfadc95c77214a48b"}, + {file = "pydantic_core-2.4.0-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:7aa82d483d5fb867d4fb10a138ffd57b0f1644e99f2f4f336e48790ada9ada5e"}, + {file = "pydantic_core-2.4.0-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b85778308bf945e9b33ac604e6793df9b07933108d20bdf53811bc7c2798a4af"}, + {file = "pydantic_core-2.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3ded19dcaefe2f6706d81e0db787b59095f4ad0fbadce1edffdf092294c8a23f"}, + {file = "pydantic_core-2.4.0.tar.gz", hash = "sha256:ec3473c9789cc00c7260d840c3db2c16dbfc816ca70ec87a00cddfa3e1a1cdd5"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "sniffio" +version = "1.3.0" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, + {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, +] + +[[package]] +name = "starlette" +version = "0.27.0" +description = "The little ASGI library that shines." +optional = false +python-versions = ">=3.7" +files = [ + {file = "starlette-0.27.0-py3-none-any.whl", hash = "sha256:918416370e846586541235ccd38a474c08b80443ed31c578a418e2209b3eef91"}, + {file = "starlette-0.27.0.tar.gz", hash = "sha256:6a6b0d042acb8d469a01eba54e9cda6cbd24ac602c4cd016723117d6a7e73b75"}, +] + +[package.dependencies] +anyio = ">=3.4.0,<5" + +[package.extras] +full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] + +[[package]] +name = "typing-extensions" +version = "4.7.1" +description = "Backported and Experimental Type Hints for Python 3.7+" +optional = false +python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, + {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, +] + +[[package]] +name = "urllib3" +version = "1.26.16" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "urllib3-1.26.16-py2.py3-none-any.whl", hash = "sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f"}, + {file = "urllib3-1.26.16.tar.gz", hash = "sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "uvicorn" +version = "0.23.1" +description = "The lightning-fast ASGI server." +optional = false +python-versions = ">=3.8" +files = [ + {file = "uvicorn-0.23.1-py3-none-any.whl", hash = "sha256:1d55d46b83ee4ce82b4e82f621f2050adb3eb7b5481c13f9af1744951cae2f1f"}, + {file = "uvicorn-0.23.1.tar.gz", hash = "sha256:da9b0c8443b2d7ee9db00a345f1eee6db7317432c9d4400f5049cc8d358383be"}, +] + +[package.dependencies] +click = ">=7.0" +h11 = ">=0.8" + +[package.extras] +standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] + +[[package]] +name = "zulip" +version = "0.8.2" +description = "Bindings for the Zulip message API" +optional = false +python-versions = ">=3.6" +files = [ + {file = "zulip-0.8.2-py3-none-any.whl", hash = "sha256:22ba357f55c538120eef02781faeb46a256aad4f7a1b5419889f765766cda0a5"}, + {file = "zulip-0.8.2.tar.gz", hash = "sha256:7e3d5097feb22e912dda187cdabdebf5b2022cba95b32a43793e27abe9c52e6f"}, +] + +[package.dependencies] +click = "*" +distro = "*" +matrix-client = "*" +requests = {version = ">=0.12.1", extras = ["security"]} +typing-extensions = ">=3.7" + +[metadata] +lock-version = "2.0" +python-versions = "^3.11" +content-hash = "b18dba2f04b2f52e85a022265b2ae091f4bd733c79d6163ed9fb023b40925479" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..0b531cf4 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,18 @@ +[tool.poetry] +name = "zulip-write-only-proxy" +version = "0.1.0" +description = "" +authors = ["Your Name "] +readme = "README.md" +packages = [{include = "zulip_write_only_proxy", from = "src"}] + +[tool.poetry.dependencies] +python = "^3.11" +fastapi = "^0.100.0" +uvicorn = "^0.23.1" +zulip = "^0.8.2" + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/src/zulip_write_only_proxy/__init__.py b/src/zulip_write_only_proxy/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py new file mode 100644 index 00000000..20152c43 --- /dev/null +++ b/src/zulip_write_only_proxy/main.py @@ -0,0 +1,38 @@ +from contextlib import asynccontextmanager + +import fastapi +import uvicorn +import zulip + +from . import zulip_client + + +@asynccontextmanager +async def lifespan(app: fastapi.FastAPI): + zulip_client.setup() + yield + + +app = fastapi.FastAPI(title="Zulip Write Only Proxy", lifespan=lifespan) + + +@app.post("/message") +def post_message( + stream: str, + topic: str, + content: str, + client: zulip.Client = fastapi.Depends(zulip_client.get_client), +): + request = { + "type": "stream", + "to": stream, + "topic": topic, + "content": content, + } + return client.send_message(request) + + +if __name__ == "__main__": + uvicorn.run( + "zulip_write_only_proxy.main:app", + ) diff --git a/src/zulip_write_only_proxy/zulip_client.py b/src/zulip_write_only_proxy/zulip_client.py new file mode 100644 index 00000000..2597474d --- /dev/null +++ b/src/zulip_write_only_proxy/zulip_client.py @@ -0,0 +1,14 @@ +from pathlib import Path + +import zulip + +ZULIP_CLIENT: zulip.Client + + +def setup(): + global ZULIP_CLIENT + ZULIP_CLIENT = zulip.Client(config_file=str(Path(__file__).parent / "zuliprc")) + + +async def get_client(): + return ZULIP_CLIENT diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b From 9ccbc76cfa2d4f48068d44627c4f106f770a8eb7 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 26 Jul 2023 11:29:23 +0200 Subject: [PATCH 002/143] feat(endpoint): allow for image uploads --- poetry.lock | 16 +++++++++++++++- pyproject.toml | 1 + src/zulip_write_only_proxy/main.py | 21 ++++++++++++++++++--- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index 93d9b306..633a0fe6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -355,6 +355,20 @@ files = [ [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +[[package]] +name = "python-multipart" +version = "0.0.6" +description = "A streaming multipart parser for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "python_multipart-0.0.6-py3-none-any.whl", hash = "sha256:ee698bab5ef148b0a760751c261902cd096e57e10558e11aca17646b74ee1c18"}, + {file = "python_multipart-0.0.6.tar.gz", hash = "sha256:e9925a80bb668529f1b67c7fdb0a5dacdd7cbfc6fb0bff3ea443fe22bdd62132"}, +] + +[package.extras] +dev = ["atomicwrites (==1.2.1)", "attrs (==19.2.0)", "coverage (==6.5.0)", "hatch", "invoke (==1.7.3)", "more-itertools (==4.3.0)", "pbr (==4.3.0)", "pluggy (==1.0.0)", "py (==1.11.0)", "pytest (==7.2.0)", "pytest-cov (==4.0.0)", "pytest-timeout (==2.1.0)", "pyyaml (==5.1)"] + [[package]] name = "requests" version = "2.31.0" @@ -470,4 +484,4 @@ typing-extensions = ">=3.7" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "b18dba2f04b2f52e85a022265b2ae091f4bd733c79d6163ed9fb023b40925479" +content-hash = "7c0beac2393f5506f81ffc120e6603a2ec443b07a03baacca118f9f2a7f920a0" diff --git a/pyproject.toml b/pyproject.toml index 0b531cf4..00538df6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,7 @@ python = "^3.11" fastapi = "^0.100.0" uvicorn = "^0.23.1" zulip = "^0.8.2" +python-multipart = "^0.0.6" [build-system] diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index 20152c43..38248a5f 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -1,4 +1,7 @@ from contextlib import asynccontextmanager +from io import FileIO +from tempfile import SpooledTemporaryFile +from typing import Annotated import fastapi import uvicorn @@ -18,17 +21,29 @@ async def lifespan(app: fastapi.FastAPI): @app.post("/message") def post_message( - stream: str, - topic: str, - content: str, + stream: str = "DAMNIT!", + topic: str = "test-read-only-thing", + content: str = "test message", client: zulip.Client = fastapi.Depends(zulip_client.get_client), + image: fastapi.UploadFile = fastapi.File(None), ): + if image: + # Some screwing around to get the spooled tmp file to act more like a real file + # since Zulip needs it to have a filename + + f: SpooledTemporaryFile = image.file # type: ignore + f._file.name = image.filename # type: ignore + result = client.upload_file(f) + print(result) + content += f" []({result['uri']})" + request = { "type": "stream", "to": stream, "topic": topic, "content": content, } + return client.send_message(request) From 0c7bca41e11a7da6f23e7a30b3e5169a6eba82e3 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 26 Jul 2023 12:03:26 +0200 Subject: [PATCH 003/143] feat(endpoint): add very rudimentary authentication --- .gitignore | 1 + poetry.lock | 78 +++++++++++++++++++++++- pyproject.toml | 3 + src/zulip_write_only_proxy/cli.py | 21 +++++++ src/zulip_write_only_proxy/main.py | 11 ++-- src/zulip_write_only_proxy/model.py | 20 ++++++ src/zulip_write_only_proxy/repository.py | 38 ++++++++++++ src/zulip_write_only_proxy/service.py | 19 ++++++ 8 files changed, 183 insertions(+), 8 deletions(-) create mode 100644 src/zulip_write_only_proxy/cli.py create mode 100644 src/zulip_write_only_proxy/model.py create mode 100644 src/zulip_write_only_proxy/repository.py create mode 100644 src/zulip_write_only_proxy/service.py diff --git a/.gitignore b/.gitignore index eeba6f0b..eddb1a45 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ zuliprc +proposals.json # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/poetry.lock b/poetry.lock index 633a0fe6..78f3244d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -223,6 +223,61 @@ doc = ["Sphinx (>=1.7.6,<2.dev0)", "sphinx-rtd-theme (>=0.1.9,<0.2.dev0)", "sphi e2e = ["canonicaljson (>=1.1,<2.0)", "python-olm (>=3.1,<4.0)"] test = ["pytest (>=4.6,<6.0.0)", "responses (>=0.10.6,<0.11.dev0)"] +[[package]] +name = "orjson" +version = "3.9.2" +description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" +optional = false +python-versions = ">=3.7" +files = [ + {file = "orjson-3.9.2-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7323e4ca8322b1ecb87562f1ec2491831c086d9faa9a6c6503f489dadbed37d7"}, + {file = "orjson-3.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1272688ea1865f711b01ba479dea2d53e037ea00892fd04196b5875f7021d9d3"}, + {file = "orjson-3.9.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0b9a26f1d1427a9101a1e8910f2e2df1f44d3d18ad5480ba031b15d5c1cb282e"}, + {file = "orjson-3.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6a5ca55b0d8f25f18b471e34abaee4b175924b6cd62f59992945b25963443141"}, + {file = "orjson-3.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:877872db2c0f41fbe21f852ff642ca842a43bc34895b70f71c9d575df31fffb4"}, + {file = "orjson-3.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a39c2529d75373b7167bf84c814ef9b8f3737a339c225ed6c0df40736df8748"}, + {file = "orjson-3.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:84ebd6fdf138eb0eb4280045442331ee71c0aab5e16397ba6645f32f911bfb37"}, + {file = "orjson-3.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5a60a1cfcfe310547a1946506dd4f1ed0a7d5bd5b02c8697d9d5dcd8d2e9245e"}, + {file = "orjson-3.9.2-cp310-none-win_amd64.whl", hash = "sha256:c290c4f81e8fd0c1683638802c11610b2f722b540f8e5e858b6914b495cf90c8"}, + {file = "orjson-3.9.2-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:02ef014f9a605e84b675060785e37ec9c0d2347a04f1307a9d6840ab8ecd6f55"}, + {file = "orjson-3.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:992af54265ada1c1579500d6594ed73fe333e726de70d64919cf37f93defdd06"}, + {file = "orjson-3.9.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a40958f7af7c6d992ee67b2da4098dca8b770fc3b4b3834d540477788bfa76d3"}, + {file = "orjson-3.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93864dec3e3dd058a2dbe488d11ac0345214a6a12697f53a63e34de7d28d4257"}, + {file = "orjson-3.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16fdf5a82df80c544c3c91516ab3882cd1ac4f1f84eefeafa642e05cef5f6699"}, + {file = "orjson-3.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:275b5a18fd9ed60b2720543d3ddac170051c43d680e47d04ff5203d2c6d8ebf1"}, + {file = "orjson-3.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b9aea6dcb99fcbc9f6d1dd84fca92322fda261da7fb014514bb4689c7c2097a8"}, + {file = "orjson-3.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7d74ae0e101d17c22ef67b741ba356ab896fc0fa64b301c2bf2bb0a4d874b190"}, + {file = "orjson-3.9.2-cp311-none-win_amd64.whl", hash = "sha256:6320b28e7bdb58c3a3a5efffe04b9edad3318d82409e84670a9b24e8035a249d"}, + {file = "orjson-3.9.2-cp37-cp37m-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:368e9cc91ecb7ac21f2aa475e1901204110cf3e714e98649c2502227d248f947"}, + {file = "orjson-3.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58e9e70f0dcd6a802c35887f306b555ff7a214840aad7de24901fc8bd9cf5dde"}, + {file = "orjson-3.9.2-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:00c983896c2e01c94c0ef72fd7373b2aa06d0c0eed0342c4884559f812a6835b"}, + {file = "orjson-3.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ee743e8890b16c87a2f89733f983370672272b61ee77429c0a5899b2c98c1a7"}, + {file = "orjson-3.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7b065942d362aad4818ff599d2f104c35a565c2cbcbab8c09ec49edba91da75"}, + {file = "orjson-3.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e46e9c5b404bb9e41d5555762fd410d5466b7eb1ec170ad1b1609cbebe71df21"}, + {file = "orjson-3.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8170157288714678ffd64f5de33039e1164a73fd8b6be40a8a273f80093f5c4f"}, + {file = "orjson-3.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e3e2f087161947dafe8319ea2cfcb9cea4bb9d2172ecc60ac3c9738f72ef2909"}, + {file = "orjson-3.9.2-cp37-none-win_amd64.whl", hash = "sha256:d7de3dbbe74109ae598692113cec327fd30c5a30ebca819b21dfa4052f7b08ef"}, + {file = "orjson-3.9.2-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:8cd4385c59bbc1433cad4a80aca65d2d9039646a9c57f8084897549b55913b17"}, + {file = "orjson-3.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a74036aab1a80c361039290cdbc51aa7adc7ea13f56e5ef94e9be536abd227bd"}, + {file = "orjson-3.9.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1aaa46d7d4ae55335f635eadc9be0bd9bcf742e6757209fc6dc697e390010adc"}, + {file = "orjson-3.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e52c67ed6bb368083aa2078ea3ccbd9721920b93d4b06c43eb4e20c4c860046"}, + {file = "orjson-3.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a6cdfcf9c7dd4026b2b01fdff56986251dc0cc1e980c690c79eec3ae07b36e7"}, + {file = "orjson-3.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1882a70bb69595b9ec5aac0040a819e94d2833fe54901e2b32f5e734bc259a8b"}, + {file = "orjson-3.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:fc05e060d452145ab3c0b5420769e7356050ea311fc03cb9d79c481982917cca"}, + {file = "orjson-3.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f8bc2c40d9bb26efefb10949d261a47ca196772c308babc538dd9f4b73e8d386"}, + {file = "orjson-3.9.2-cp38-none-win_amd64.whl", hash = "sha256:3164fc20a585ec30a9aff33ad5de3b20ce85702b2b2a456852c413e3f0d7ab09"}, + {file = "orjson-3.9.2-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7a6ccadf788531595ed4728aa746bc271955448d2460ff0ef8e21eb3f2a281ba"}, + {file = "orjson-3.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3245d230370f571c945f69aab823c279a868dc877352817e22e551de155cb06c"}, + {file = "orjson-3.9.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:205925b179550a4ee39b8418dd4c94ad6b777d165d7d22614771c771d44f57bd"}, + {file = "orjson-3.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0325fe2d69512187761f7368c8cda1959bcb75fc56b8e7a884e9569112320e57"}, + {file = "orjson-3.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:806704cd58708acc66a064a9a58e3be25cf1c3f9f159e8757bd3f515bfabdfa1"}, + {file = "orjson-3.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03fb36f187a0c19ff38f6289418863df8b9b7880cdbe279e920bef3a09d8dab1"}, + {file = "orjson-3.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:20925d07a97c49c6305bff1635318d9fc1804aa4ccacb5fb0deb8a910e57d97a"}, + {file = "orjson-3.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:eebfed53bec5674e981ebe8ed2cf00b3f7bcda62d634733ff779c264307ea505"}, + {file = "orjson-3.9.2-cp39-none-win_amd64.whl", hash = "sha256:869b961df5fcedf6c79f4096119b35679b63272362e9b745e668f0391a892d39"}, + {file = "orjson-3.9.2.tar.gz", hash = "sha256:24257c8f641979bf25ecd3e27251b5cc194cdd3a6e96004aac8446f5e63d9664"}, +] + [[package]] name = "pydantic" version = "2.1.1" @@ -418,6 +473,27 @@ anyio = ">=3.4.0,<5" [package.extras] full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] +[[package]] +name = "typer" +version = "0.9.0" +description = "Typer, build great CLIs. Easy to code. Based on Python type hints." +optional = false +python-versions = ">=3.6" +files = [ + {file = "typer-0.9.0-py3-none-any.whl", hash = "sha256:5d96d986a21493606a358cae4461bd8cdf83cbf33a5aa950ae629ca3b51467ee"}, + {file = "typer-0.9.0.tar.gz", hash = "sha256:50922fd79aea2f4751a8e0408ff10d2662bd0c8bbfa84755a699f3bada2978b2"}, +] + +[package.dependencies] +click = ">=7.1.1,<9.0.0" +typing-extensions = ">=3.7.4.3" + +[package.extras] +all = ["colorama (>=0.4.3,<0.5.0)", "rich (>=10.11.0,<14.0.0)", "shellingham (>=1.3.0,<2.0.0)"] +dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "pre-commit (>=2.17.0,<3.0.0)"] +doc = ["cairosvg (>=2.5.2,<3.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pillow (>=9.3.0,<10.0.0)"] +test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "pytest (>=4.4.0,<8.0.0)", "pytest-cov (>=2.10.0,<5.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "pytest-xdist (>=1.32.0,<4.0.0)", "rich (>=10.11.0,<14.0.0)", "shellingham (>=1.3.0,<2.0.0)"] + [[package]] name = "typing-extensions" version = "4.7.1" @@ -484,4 +560,4 @@ typing-extensions = ">=3.7" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "7c0beac2393f5506f81ffc120e6603a2ec443b07a03baacca118f9f2a7f920a0" +content-hash = "5f1a332a8389886447ff672facb5b2854b02a94cf6a8166d306faa548d92c6c1" diff --git a/pyproject.toml b/pyproject.toml index 00538df6..544fc742 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,9 @@ fastapi = "^0.100.0" uvicorn = "^0.23.1" zulip = "^0.8.2" python-multipart = "^0.0.6" +orjson = "^3.9.2" +pydantic = "^2.1.1" +typer = "^0.9.0" [build-system] diff --git a/src/zulip_write_only_proxy/cli.py b/src/zulip_write_only_proxy/cli.py new file mode 100644 index 00000000..1cb93af0 --- /dev/null +++ b/src/zulip_write_only_proxy/cli.py @@ -0,0 +1,21 @@ +import typer + +from . import service + +app = typer.Typer() + + +@app.command() +def create(proposal_no: int): + proposal = service.create_proposal(proposal_no) + typer.echo(proposal) + + +@app.command() +def list(): + proposals = service.list_proposals() + typer.echo(proposals) + + +if __name__ == "__main__": + app() diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index 38248a5f..5125db44 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -1,13 +1,11 @@ from contextlib import asynccontextmanager -from io import FileIO from tempfile import SpooledTemporaryFile -from typing import Annotated import fastapi import uvicorn import zulip -from . import zulip_client +from . import zulip_client, service @asynccontextmanager @@ -21,8 +19,7 @@ async def lifespan(app: fastapi.FastAPI): @app.post("/message") def post_message( - stream: str = "DAMNIT!", - topic: str = "test-read-only-thing", + proposal: service.Proposal = fastapi.Depends(service.get_proposal), content: str = "test message", client: zulip.Client = fastapi.Depends(zulip_client.get_client), image: fastapi.UploadFile = fastapi.File(None), @@ -39,8 +36,8 @@ def post_message( request = { "type": "stream", - "to": stream, - "topic": topic, + "to": proposal.stream, + "topic": proposal.topic, "content": content, } diff --git a/src/zulip_write_only_proxy/model.py b/src/zulip_write_only_proxy/model.py new file mode 100644 index 00000000..48fa0cd0 --- /dev/null +++ b/src/zulip_write_only_proxy/model.py @@ -0,0 +1,20 @@ +import secrets +from typing import Self + +from pydantic import BaseModel + + +class Proposal(BaseModel): + token: str + proposal_no: int + stream: str + topic: str + + @classmethod + def create(cls, proposal_no: int) -> Self: + return cls( + token=secrets.token_urlsafe(), + proposal_no=proposal_no, + stream=f"some-pattern-{proposal_no}", + topic=f"some-pattern-{proposal_no}", + ) diff --git a/src/zulip_write_only_proxy/repository.py b/src/zulip_write_only_proxy/repository.py new file mode 100644 index 00000000..c0b15ff3 --- /dev/null +++ b/src/zulip_write_only_proxy/repository.py @@ -0,0 +1,38 @@ +from pathlib import Path +from pydantic import BaseModel, field_validator + +import orjson +from .model import Proposal + + +class ProposalRepository(BaseModel): + path: Path + + def get(self, token: str) -> Proposal: + with self.path.open("rb") as f: + data = orjson.loads(f.read()) + return Proposal(token=token, **data[token]) + + def put(self, proposal: Proposal) -> None: + with self.path.open("rb") as f: + data = orjson.loads(f.read()) + if proposal.proposal_no in data.values(): + raise ValueError("Proposal already exists") + + data[proposal.token] = proposal.model_dump(exclude={"token"}) + + with self.path.open("wb") as f: + f.write(orjson.dumps(data, option=orjson.OPT_INDENT_2)) + + def list(self): + with self.path.open("rb") as f: + data = orjson.loads(f.read()) + return [Proposal(token=token, **entry) for entry, token in data.items()] + + @field_validator("path") + @classmethod + def check_path(cls, v: Path) -> Path: + if not v.exists(): + v.touch() + v.write_text("{}") + return v diff --git a/src/zulip_write_only_proxy/service.py b/src/zulip_write_only_proxy/service.py new file mode 100644 index 00000000..9007579d --- /dev/null +++ b/src/zulip_write_only_proxy/service.py @@ -0,0 +1,19 @@ +from pathlib import Path +from .repository import ProposalRepository +from .model import Proposal + +REPOSITORY = ProposalRepository(path=Path(__file__).parent / "proposals.json") + + +def create_proposal(proposal_no: int) -> Proposal: + proposal = Proposal.create(proposal_no) + REPOSITORY.put(proposal) + return proposal + + +def get_proposal(token: str) -> Proposal: + return REPOSITORY.get(token) + + +def list_proposals() -> list[Proposal]: + return REPOSITORY.list() From 93749d1924d42ec7ba8b42f3581eff138171ded8 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 26 Jul 2023 12:26:34 +0200 Subject: [PATCH 004/143] chore: add docstrings, add cli as script --- pyproject.toml | 5 ++++- src/zulip_write_only_proxy/cli.py | 5 ++++- src/zulip_write_only_proxy/main.py | 9 +++------ src/zulip_write_only_proxy/repository.py | 2 ++ 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 544fc742..b3b0b801 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,10 @@ version = "0.1.0" description = "" authors = ["Your Name "] readme = "README.md" -packages = [{include = "zulip_write_only_proxy", from = "src"}] +packages = [{ include = "zulip_write_only_proxy", from = "src" }] + +[tool.poetry.scripts] +damnit-zulip = "zulip_write_only_proxy.cli:app" [tool.poetry.dependencies] python = "^3.11" diff --git a/src/zulip_write_only_proxy/cli.py b/src/zulip_write_only_proxy/cli.py index 1cb93af0..26b53bdb 100644 --- a/src/zulip_write_only_proxy/cli.py +++ b/src/zulip_write_only_proxy/cli.py @@ -1,18 +1,21 @@ import typer +import uvicorn -from . import service +from . import service, main app = typer.Typer() @app.command() def create(proposal_no: int): + """Create a new entry for a proposal with the given number.""" proposal = service.create_proposal(proposal_no) typer.echo(proposal) @app.command() def list(): + """List all proposal entries.""" proposals = service.list_proposals() typer.echo(proposals) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index 5125db44..246e979e 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -20,18 +20,17 @@ async def lifespan(app: fastapi.FastAPI): @app.post("/message") def post_message( proposal: service.Proposal = fastapi.Depends(service.get_proposal), - content: str = "test message", + content: str = fastapi.Query(...), client: zulip.Client = fastapi.Depends(zulip_client.get_client), image: fastapi.UploadFile = fastapi.File(None), ): if image: # Some screwing around to get the spooled tmp file to act more like a real file # since Zulip needs it to have a filename - f: SpooledTemporaryFile = image.file # type: ignore f._file.name = image.filename # type: ignore + result = client.upload_file(f) - print(result) content += f" []({result['uri']})" request = { @@ -45,6 +44,4 @@ def post_message( if __name__ == "__main__": - uvicorn.run( - "zulip_write_only_proxy.main:app", - ) + uvicorn.run("zulip_write_only_proxy.main:app") diff --git a/src/zulip_write_only_proxy/repository.py b/src/zulip_write_only_proxy/repository.py index c0b15ff3..b8844324 100644 --- a/src/zulip_write_only_proxy/repository.py +++ b/src/zulip_write_only_proxy/repository.py @@ -6,6 +6,8 @@ class ProposalRepository(BaseModel): + """A basic file/JSON-based repository for storing proposal entries.""" + path: Path def get(self, token: str) -> Proposal: From 73787f950234acdb329b7aaaacfe069f0fabb62f Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 26 Jul 2023 12:31:42 +0200 Subject: [PATCH 005/143] style: sort imports --- src/zulip_write_only_proxy/cli.py | 2 +- src/zulip_write_only_proxy/main.py | 2 +- src/zulip_write_only_proxy/repository.py | 3 ++- src/zulip_write_only_proxy/service.py | 3 ++- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/zulip_write_only_proxy/cli.py b/src/zulip_write_only_proxy/cli.py index 26b53bdb..869b891a 100644 --- a/src/zulip_write_only_proxy/cli.py +++ b/src/zulip_write_only_proxy/cli.py @@ -1,7 +1,7 @@ import typer import uvicorn -from . import service, main +from . import service app = typer.Typer() diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index 246e979e..6a051129 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -5,7 +5,7 @@ import uvicorn import zulip -from . import zulip_client, service +from . import service, zulip_client @asynccontextmanager diff --git a/src/zulip_write_only_proxy/repository.py b/src/zulip_write_only_proxy/repository.py index b8844324..4eb106d4 100644 --- a/src/zulip_write_only_proxy/repository.py +++ b/src/zulip_write_only_proxy/repository.py @@ -1,7 +1,8 @@ from pathlib import Path -from pydantic import BaseModel, field_validator import orjson +from pydantic import BaseModel, field_validator + from .model import Proposal diff --git a/src/zulip_write_only_proxy/service.py b/src/zulip_write_only_proxy/service.py index 9007579d..2d53d728 100644 --- a/src/zulip_write_only_proxy/service.py +++ b/src/zulip_write_only_proxy/service.py @@ -1,6 +1,7 @@ from pathlib import Path -from .repository import ProposalRepository + from .model import Proposal +from .repository import ProposalRepository REPOSITORY = ProposalRepository(path=Path(__file__).parent / "proposals.json") From 941b1b053a5b2351591112686d34321ccefc1081 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 26 Jul 2023 13:54:26 +0200 Subject: [PATCH 006/143] refactor: rename classes/methods to maybe make more sense --- src/zulip_write_only_proxy/cli.py | 4 ++-- src/zulip_write_only_proxy/main.py | 14 +++++++------- src/zulip_write_only_proxy/model.py | 2 +- src/zulip_write_only_proxy/repository.py | 16 ++++++++-------- src/zulip_write_only_proxy/service.py | 14 +++++++------- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/zulip_write_only_proxy/cli.py b/src/zulip_write_only_proxy/cli.py index 869b891a..0bdce8e5 100644 --- a/src/zulip_write_only_proxy/cli.py +++ b/src/zulip_write_only_proxy/cli.py @@ -8,14 +8,14 @@ @app.command() def create(proposal_no: int): - """Create a new entry for a proposal with the given number.""" + """Create a new scoped client for a proposal.""" proposal = service.create_proposal(proposal_no) typer.echo(proposal) @app.command() def list(): - """List all proposal entries.""" + """List all scoped clients.""" proposals = service.list_proposals() typer.echo(proposals) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index 6a051129..bdb5a34f 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -18,10 +18,10 @@ async def lifespan(app: fastapi.FastAPI): @app.post("/message") -def post_message( - proposal: service.Proposal = fastapi.Depends(service.get_proposal), +def send_message( + scoped_client: service.ScopedClient = fastapi.Depends(service.get_proposal), content: str = fastapi.Query(...), - client: zulip.Client = fastapi.Depends(zulip_client.get_client), + zulip_client: zulip.Client = fastapi.Depends(zulip_client.get_client), image: fastapi.UploadFile = fastapi.File(None), ): if image: @@ -30,17 +30,17 @@ def post_message( f: SpooledTemporaryFile = image.file # type: ignore f._file.name = image.filename # type: ignore - result = client.upload_file(f) + result = zulip_client.upload_file(f) content += f" []({result['uri']})" request = { "type": "stream", - "to": proposal.stream, - "topic": proposal.topic, + "to": scoped_client.stream, + "topic": scoped_client.topic, "content": content, } - return client.send_message(request) + return zulip_client.send_message(request) if __name__ == "__main__": diff --git a/src/zulip_write_only_proxy/model.py b/src/zulip_write_only_proxy/model.py index 48fa0cd0..6738ccc5 100644 --- a/src/zulip_write_only_proxy/model.py +++ b/src/zulip_write_only_proxy/model.py @@ -4,7 +4,7 @@ from pydantic import BaseModel -class Proposal(BaseModel): +class ScopedClient(BaseModel): token: str proposal_no: int stream: str diff --git a/src/zulip_write_only_proxy/repository.py b/src/zulip_write_only_proxy/repository.py index 4eb106d4..bf43e4a6 100644 --- a/src/zulip_write_only_proxy/repository.py +++ b/src/zulip_write_only_proxy/repository.py @@ -3,24 +3,24 @@ import orjson from pydantic import BaseModel, field_validator -from .model import Proposal +from .model import ScopedClient -class ProposalRepository(BaseModel): - """A basic file/JSON-based repository for storing proposal entries.""" +class JSONRepository(BaseModel): + """A basic file/JSON-based repository for storing client entries.""" path: Path - def get(self, token: str) -> Proposal: + def get(self, token: str) -> ScopedClient: with self.path.open("rb") as f: data = orjson.loads(f.read()) - return Proposal(token=token, **data[token]) + return ScopedClient(token=token, **data[token]) - def put(self, proposal: Proposal) -> None: + def put(self, proposal: ScopedClient) -> None: with self.path.open("rb") as f: data = orjson.loads(f.read()) if proposal.proposal_no in data.values(): - raise ValueError("Proposal already exists") + raise ValueError("Client already exists") data[proposal.token] = proposal.model_dump(exclude={"token"}) @@ -30,7 +30,7 @@ def put(self, proposal: Proposal) -> None: def list(self): with self.path.open("rb") as f: data = orjson.loads(f.read()) - return [Proposal(token=token, **entry) for entry, token in data.items()] + return [ScopedClient(token=token, **entry) for entry, token in data.items()] @field_validator("path") @classmethod diff --git a/src/zulip_write_only_proxy/service.py b/src/zulip_write_only_proxy/service.py index 2d53d728..d45b423e 100644 --- a/src/zulip_write_only_proxy/service.py +++ b/src/zulip_write_only_proxy/service.py @@ -1,20 +1,20 @@ from pathlib import Path -from .model import Proposal -from .repository import ProposalRepository +from .model import ScopedClient +from .repository import JSONRepository -REPOSITORY = ProposalRepository(path=Path(__file__).parent / "proposals.json") +REPOSITORY = JSONRepository(path=Path(__file__).parent / "clients.json") -def create_proposal(proposal_no: int) -> Proposal: - proposal = Proposal.create(proposal_no) +def create_proposal(proposal_no: int) -> ScopedClient: + proposal = ScopedClient.create(proposal_no) REPOSITORY.put(proposal) return proposal -def get_proposal(token: str) -> Proposal: +def get_proposal(token: str) -> ScopedClient: return REPOSITORY.get(token) -def list_proposals() -> list[Proposal]: +def list_proposals() -> list[ScopedClient]: return REPOSITORY.list() From cefd962f0ab480d17f904d2ff05c79d3d041cda1 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 26 Jul 2023 14:09:28 +0200 Subject: [PATCH 007/143] feat: Zulip client setup in service, add message/upload methods to model --- src/zulip_write_only_proxy/main.py | 20 ++++++-------------- src/zulip_write_only_proxy/model.py | 22 ++++++++++++++++++++-- src/zulip_write_only_proxy/service.py | 11 +++++++++++ src/zulip_write_only_proxy/zulip_client.py | 14 -------------- 4 files changed, 37 insertions(+), 30 deletions(-) delete mode 100644 src/zulip_write_only_proxy/zulip_client.py diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index bdb5a34f..3fc9e2e5 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -3,14 +3,13 @@ import fastapi import uvicorn -import zulip -from . import service, zulip_client +from . import service @asynccontextmanager async def lifespan(app: fastapi.FastAPI): - zulip_client.setup() + service.setup() yield @@ -19,9 +18,8 @@ async def lifespan(app: fastapi.FastAPI): @app.post("/message") def send_message( - scoped_client: service.ScopedClient = fastapi.Depends(service.get_proposal), + client: service.ScopedClient = fastapi.Depends(service.get_proposal), content: str = fastapi.Query(...), - zulip_client: zulip.Client = fastapi.Depends(zulip_client.get_client), image: fastapi.UploadFile = fastapi.File(None), ): if image: @@ -30,17 +28,11 @@ def send_message( f: SpooledTemporaryFile = image.file # type: ignore f._file.name = image.filename # type: ignore - result = zulip_client.upload_file(f) - content += f" []({result['uri']})" + result = client.upload_image(f) - request = { - "type": "stream", - "to": scoped_client.stream, - "topic": scoped_client.topic, - "content": content, - } + content += f" []({result['uri']})" - return zulip_client.send_message(request) + return client.send_message(content) if __name__ == "__main__": diff --git a/src/zulip_write_only_proxy/model.py b/src/zulip_write_only_proxy/model.py index 6738ccc5..164726b4 100644 --- a/src/zulip_write_only_proxy/model.py +++ b/src/zulip_write_only_proxy/model.py @@ -1,7 +1,8 @@ import secrets -from typing import Self +from typing import IO, Any, Self -from pydantic import BaseModel +import zulip +from pydantic import BaseModel, Field class ScopedClient(BaseModel): @@ -10,6 +11,10 @@ class ScopedClient(BaseModel): stream: str topic: str + _client: zulip.Client = Field( + init_var=None, default=None + ) # Injected by service.setup + @classmethod def create(cls, proposal_no: int) -> Self: return cls( @@ -18,3 +23,16 @@ def create(cls, proposal_no: int) -> Self: stream=f"some-pattern-{proposal_no}", topic=f"some-pattern-{proposal_no}", ) + + def upload_image(self, image: IO[Any]): + return self._client.upload_file(image) + + def send_message(self, content: str): + request = { + "type": "stream", + "to": self.stream, + "topic": self.topic, + "content": content, + } + + return self._client.send_message(request) diff --git a/src/zulip_write_only_proxy/service.py b/src/zulip_write_only_proxy/service.py index d45b423e..2079d257 100644 --- a/src/zulip_write_only_proxy/service.py +++ b/src/zulip_write_only_proxy/service.py @@ -1,5 +1,7 @@ from pathlib import Path +import zulip + from .model import ScopedClient from .repository import JSONRepository @@ -18,3 +20,12 @@ def get_proposal(token: str) -> ScopedClient: def list_proposals() -> list[ScopedClient]: return REPOSITORY.list() + + +def setup(): + if ScopedClient._client is not None: + print("Client already set up") + return + + zulip_client = zulip.Client(config_file=str(Path(__file__).parent / "zuliprc")) + ScopedClient._client = zulip_client diff --git a/src/zulip_write_only_proxy/zulip_client.py b/src/zulip_write_only_proxy/zulip_client.py deleted file mode 100644 index 2597474d..00000000 --- a/src/zulip_write_only_proxy/zulip_client.py +++ /dev/null @@ -1,14 +0,0 @@ -from pathlib import Path - -import zulip - -ZULIP_CLIENT: zulip.Client - - -def setup(): - global ZULIP_CLIENT - ZULIP_CLIENT = zulip.Client(config_file=str(Path(__file__).parent / "zuliprc")) - - -async def get_client(): - return ZULIP_CLIENT From c9282c4cce343ba54d5e81660daf6e2d443ee70d Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 26 Jul 2023 14:49:24 +0200 Subject: [PATCH 008/143] refactor: more renaming, proposal to client, token to key --- .gitignore | 2 +- src/zulip_write_only_proxy/cli.py | 8 ++++---- src/zulip_write_only_proxy/model.py | 22 +++++++++++++--------- src/zulip_write_only_proxy/repository.py | 12 ++++++------ src/zulip_write_only_proxy/service.py | 14 +++++++------- 5 files changed, 31 insertions(+), 27 deletions(-) diff --git a/.gitignore b/.gitignore index eddb1a45..547bdd09 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ zuliprc -proposals.json +clients.json # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/src/zulip_write_only_proxy/cli.py b/src/zulip_write_only_proxy/cli.py index 0bdce8e5..310abcab 100644 --- a/src/zulip_write_only_proxy/cli.py +++ b/src/zulip_write_only_proxy/cli.py @@ -9,15 +9,15 @@ @app.command() def create(proposal_no: int): """Create a new scoped client for a proposal.""" - proposal = service.create_proposal(proposal_no) - typer.echo(proposal) + client = service.create_client(proposal_no) + typer.echo(client) @app.command() def list(): """List all scoped clients.""" - proposals = service.list_proposals() - typer.echo(proposals) + client = service.list_clients() + typer.echo(client) if __name__ == "__main__": diff --git a/src/zulip_write_only_proxy/model.py b/src/zulip_write_only_proxy/model.py index 164726b4..d77e88f9 100644 --- a/src/zulip_write_only_proxy/model.py +++ b/src/zulip_write_only_proxy/model.py @@ -2,26 +2,30 @@ from typing import IO, Any, Self import zulip -from pydantic import BaseModel, Field +from pydantic import BaseModel class ScopedClient(BaseModel): - token: str + key: str + proposal_no: int stream: str topic: str - _client: zulip.Client = Field( - init_var=None, default=None - ) # Injected by service.setup + _client: zulip.Client = None # type: ignore @classmethod - def create(cls, proposal_no: int) -> Self: + def create( + cls, + proposal_no: int, + stream: str | None = None, + topic: str | None = None, + ) -> Self: return cls( - token=secrets.token_urlsafe(), + key=secrets.token_urlsafe(), proposal_no=proposal_no, - stream=f"some-pattern-{proposal_no}", - topic=f"some-pattern-{proposal_no}", + stream=stream or f"some-pattern-{proposal_no}", + topic=topic or f"some-pattern-{proposal_no}", ) def upload_image(self, image: IO[Any]): diff --git a/src/zulip_write_only_proxy/repository.py b/src/zulip_write_only_proxy/repository.py index bf43e4a6..4e4f955f 100644 --- a/src/zulip_write_only_proxy/repository.py +++ b/src/zulip_write_only_proxy/repository.py @@ -11,18 +11,18 @@ class JSONRepository(BaseModel): path: Path - def get(self, token: str) -> ScopedClient: + def get(self, key: str) -> ScopedClient: with self.path.open("rb") as f: data = orjson.loads(f.read()) - return ScopedClient(token=token, **data[token]) + return ScopedClient(key=key, **data[key]) - def put(self, proposal: ScopedClient) -> None: + def put(self, client: ScopedClient) -> None: with self.path.open("rb") as f: data = orjson.loads(f.read()) - if proposal.proposal_no in data.values(): + if client.proposal_no in data.values(): raise ValueError("Client already exists") - data[proposal.token] = proposal.model_dump(exclude={"token"}) + data[client.key] = client.model_dump(exclude={"token"}) with self.path.open("wb") as f: f.write(orjson.dumps(data, option=orjson.OPT_INDENT_2)) @@ -30,7 +30,7 @@ def put(self, proposal: ScopedClient) -> None: def list(self): with self.path.open("rb") as f: data = orjson.loads(f.read()) - return [ScopedClient(token=token, **entry) for entry, token in data.items()] + return [ScopedClient(key=key, **value) for key, value in data.items()] @field_validator("path") @classmethod diff --git a/src/zulip_write_only_proxy/service.py b/src/zulip_write_only_proxy/service.py index 2079d257..9053f3d6 100644 --- a/src/zulip_write_only_proxy/service.py +++ b/src/zulip_write_only_proxy/service.py @@ -8,17 +8,17 @@ REPOSITORY = JSONRepository(path=Path(__file__).parent / "clients.json") -def create_proposal(proposal_no: int) -> ScopedClient: - proposal = ScopedClient.create(proposal_no) - REPOSITORY.put(proposal) - return proposal +def create_client(proposal_no: int) -> ScopedClient: + client = ScopedClient.create(proposal_no) + REPOSITORY.put(client) + return client -def get_proposal(token: str) -> ScopedClient: - return REPOSITORY.get(token) +def get_client(key: str) -> ScopedClient: + return REPOSITORY.get(key) -def list_proposals() -> list[ScopedClient]: +def list_clients() -> list[ScopedClient]: return REPOSITORY.list() From d397b88f698e54f82dc4dd8c9d1a7aa8e6a70064 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 26 Jul 2023 14:49:54 +0200 Subject: [PATCH 009/143] fix(endpoint): use proper header auth --- src/zulip_write_only_proxy/main.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index 3fc9e2e5..1228c4d3 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -3,6 +3,7 @@ import fastapi import uvicorn +from fastapi.security import APIKeyHeader from . import service @@ -15,10 +16,19 @@ async def lifespan(app: fastapi.FastAPI): app = fastapi.FastAPI(title="Zulip Write Only Proxy", lifespan=lifespan) +api_key_header = APIKeyHeader(name="X-API-key") -@app.post("/message") + +def get_client(key: str = fastapi.Security(api_key_header)) -> service.ScopedClient: + try: + return service.get_client(key) + except KeyError as e: + raise fastapi.HTTPException(status_code=404, detail="Key not found") from e + + +@app.post("/message", tags=["User"]) def send_message( - client: service.ScopedClient = fastapi.Depends(service.get_proposal), + client=fastapi.Depends(get_client), content: str = fastapi.Query(...), image: fastapi.UploadFile = fastapi.File(None), ): @@ -36,4 +46,4 @@ def send_message( if __name__ == "__main__": - uvicorn.run("zulip_write_only_proxy.main:app") + uvicorn.run(app="zulip_write_only_proxy.main:app") From 1b12f77c2a5d38951137e62a4d9993c01eba7b34 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 26 Jul 2023 14:59:21 +0200 Subject: [PATCH 010/143] feat(cli): allow specifying client stream/topic in CLI --- src/zulip_write_only_proxy/cli.py | 10 ++++++++-- src/zulip_write_only_proxy/service.py | 6 ++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/zulip_write_only_proxy/cli.py b/src/zulip_write_only_proxy/cli.py index 310abcab..426f4d89 100644 --- a/src/zulip_write_only_proxy/cli.py +++ b/src/zulip_write_only_proxy/cli.py @@ -1,3 +1,5 @@ +from typing import Annotated, Optional + import typer import uvicorn @@ -7,9 +9,13 @@ @app.command() -def create(proposal_no: int): +def create( + proposal_no: Annotated[int, typer.Argument()], + stream: Annotated[Optional[str], typer.Argument()] = None, + topic: Annotated[Optional[str], typer.Argument()] = None, +): """Create a new scoped client for a proposal.""" - client = service.create_client(proposal_no) + client = service.create_client(proposal_no, stream, topic) typer.echo(client) diff --git a/src/zulip_write_only_proxy/service.py b/src/zulip_write_only_proxy/service.py index 9053f3d6..4706239a 100644 --- a/src/zulip_write_only_proxy/service.py +++ b/src/zulip_write_only_proxy/service.py @@ -8,8 +8,10 @@ REPOSITORY = JSONRepository(path=Path(__file__).parent / "clients.json") -def create_client(proposal_no: int) -> ScopedClient: - client = ScopedClient.create(proposal_no) +def create_client( + proposal_no: int, stream: str | None = None, topic: str | None = None +) -> ScopedClient: + client = ScopedClient.create(proposal_no, stream, topic) REPOSITORY.put(client) return client From c73798612166e62751d7c70b9c3c50a3cea43c99 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 26 Jul 2023 15:18:46 +0200 Subject: [PATCH 011/143] build(docker): add Dockerfile --- Dockerfile | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..4f501251 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.11 + +WORKDIR /app + +RUN python3 -m pip install --upgrade poetry pip + +COPY ./poetry.lock ./pyproject.toml ./README.md /app +COPY ./src /app/src + +RUN poetry config virtualenvs.create false --local +RUN poetry install + +CMD ["uvicorn", "zulip_write_only_proxy.main:app", "--host", "0.0.0.0", "--port", "8009"] From eea35167bfa709a643ea48c50e60a0827a697f5a Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 26 Jul 2023 15:22:23 +0200 Subject: [PATCH 012/143] feat: store clients/zuliprc in cwd for easy config/start --- src/zulip_write_only_proxy/service.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zulip_write_only_proxy/service.py b/src/zulip_write_only_proxy/service.py index 4706239a..2763bc9e 100644 --- a/src/zulip_write_only_proxy/service.py +++ b/src/zulip_write_only_proxy/service.py @@ -5,7 +5,7 @@ from .model import ScopedClient from .repository import JSONRepository -REPOSITORY = JSONRepository(path=Path(__file__).parent / "clients.json") +REPOSITORY = JSONRepository(path=Path.cwd() / "clients.json") def create_client( @@ -29,5 +29,5 @@ def setup(): print("Client already set up") return - zulip_client = zulip.Client(config_file=str(Path(__file__).parent / "zuliprc")) + zulip_client = zulip.Client(config_file=str(Path.cwd() / "zuliprc")) ScopedClient._client = zulip_client From 6fcab7c43f652eab98e2a630099a90fb11de15de Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 26 Jul 2023 15:24:11 +0200 Subject: [PATCH 013/143] chore(docker): use default port 8000 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 4f501251..f99c01d6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,4 +10,4 @@ COPY ./src /app/src RUN poetry config virtualenvs.create false --local RUN poetry install -CMD ["uvicorn", "zulip_write_only_proxy.main:app", "--host", "0.0.0.0", "--port", "8009"] +CMD ["uvicorn", "zulip_write_only_proxy.main:app", "--host", "0.0.0.0", "--port", "8000"] From 355631b55cd002aaf101fce918ad5aa57a9e2123 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 26 Jul 2023 15:32:27 +0200 Subject: [PATCH 014/143] feat: better exception handling --- src/zulip_write_only_proxy/repository.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/zulip_write_only_proxy/repository.py b/src/zulip_write_only_proxy/repository.py index 4e4f955f..a3617162 100644 --- a/src/zulip_write_only_proxy/repository.py +++ b/src/zulip_write_only_proxy/repository.py @@ -19,10 +19,11 @@ def get(self, key: str) -> ScopedClient: def put(self, client: ScopedClient) -> None: with self.path.open("rb") as f: data = orjson.loads(f.read()) - if client.proposal_no in data.values(): - raise ValueError("Client already exists") + proposal_nos = [value["proposal_no"] for value in data.values()] + if client.proposal_no in proposal_nos: + raise ValueError(f"Client already exists for {client.proposal_no=}") - data[client.key] = client.model_dump(exclude={"token"}) + data[client.key] = client.model_dump(exclude={"key"}) with self.path.open("wb") as f: f.write(orjson.dumps(data, option=orjson.OPT_INDENT_2)) From 4476091df69668862717c0a8c8b601e6ab72e1c2 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 26 Jul 2023 15:32:29 +0200 Subject: [PATCH 015/143] docs: add readme --- README.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/README.md b/README.md index e69de29b..1d1e75a7 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,49 @@ +# Zulip Write Only Proxy + +## Quick Start + +For docker: + +```sh +# Build image +docker build . --tag zwop + +# Start server in background +docker run --rm -v ./zuliprc:/app/zuliprc -v ./clients.json:/app/clients.json -p 8080:8000 -d zwop +``` + +For CLI: + +```sh +# Normal venv: +python3 -m venv .venv +source .venv/bin/activate + +python3 -m pip install . + +# Poetry: +poetry install +poetry shell + +damnit-zulip --help +``` + +To create a client for proposal 2222: + +```sh +damnit-zulip create 2222 +``` + +Default configuration is something like: + +```json +{ + "t425dYYQAVAT9AZiPx5fe0nrLzQSPpjZW-54EdxOUPQ": { + "proposal_no": 222, + "stream": "some-pattern-222", + "topic": "some-pattern-222" + } +} +``` + +Can stream/topic edited manually or set via CLI at creation time. From 8b3473db4caa2ca94c22e7867b8d4382e39991e2 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 26 Jul 2023 16:04:09 +0200 Subject: [PATCH 016/143] chore: update Zulip client injection --- src/zulip_write_only_proxy/model.py | 4 ++-- src/zulip_write_only_proxy/service.py | 14 +++++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/zulip_write_only_proxy/model.py b/src/zulip_write_only_proxy/model.py index d77e88f9..548f52a9 100644 --- a/src/zulip_write_only_proxy/model.py +++ b/src/zulip_write_only_proxy/model.py @@ -2,7 +2,7 @@ from typing import IO, Any, Self import zulip -from pydantic import BaseModel +from pydantic import BaseModel, PrivateAttr class ScopedClient(BaseModel): @@ -12,7 +12,7 @@ class ScopedClient(BaseModel): stream: str topic: str - _client: zulip.Client = None # type: ignore + _client: zulip.Client = PrivateAttr() @classmethod def create( diff --git a/src/zulip_write_only_proxy/service.py b/src/zulip_write_only_proxy/service.py index 2763bc9e..cd6b3768 100644 --- a/src/zulip_write_only_proxy/service.py +++ b/src/zulip_write_only_proxy/service.py @@ -1,6 +1,8 @@ from pathlib import Path import zulip +from pydantic.fields import ModelPrivateAttr +from pydantic_core import PydanticUndefined from .model import ScopedClient from .repository import JSONRepository @@ -25,9 +27,11 @@ def list_clients() -> list[ScopedClient]: def setup(): - if ScopedClient._client is not None: - print("Client already set up") - return + if not isinstance(ScopedClient._client, ModelPrivateAttr): + raise RuntimeError("ScopedClient.client is not a ModelPrivateAttr") - zulip_client = zulip.Client(config_file=str(Path.cwd() / "zuliprc")) - ScopedClient._client = zulip_client + client_default = ScopedClient._client.default + + if client_default is None or client_default is PydanticUndefined: + zulip_client = zulip.Client(config_file=str(Path.cwd() / "zuliprc")) + ScopedClient._client.default = zulip_client From b79d2eb2585242b9c6c47c9a6ce59a95cb79f542 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 26 Jul 2023 16:40:42 +0200 Subject: [PATCH 017/143] fix: newline before image URI insertion --- src/zulip_write_only_proxy/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index 1228c4d3..1f2aa088 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -40,7 +40,7 @@ def send_message( result = client.upload_image(f) - content += f" []({result['uri']})" + content += f"\n[]({result['uri']})" return client.send_message(content) From fde64f21165e4664e562ed075cd57fdea0120f32 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 26 Jul 2023 16:55:00 +0200 Subject: [PATCH 018/143] feat(endpoint): add upload image endpoint --- src/zulip_write_only_proxy/main.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index 1f2aa088..504c622c 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -1,7 +1,10 @@ from contextlib import asynccontextmanager +from dataclasses import dataclass from tempfile import SpooledTemporaryFile +from typing import NamedTuple import fastapi +from pydantic import BaseModel import uvicorn from fastapi.security import APIKeyHeader @@ -45,5 +48,23 @@ def send_message( return client.send_message(content) +# This should not be here +class UploadImageResponse(BaseModel): + uri: str + msg: str + result: str = "success" + + +@app.post("/upload_image", tags=["User"], response_model=UploadImageResponse) +def upload_image( + client=fastapi.Depends(get_client), + image: fastapi.UploadFile = fastapi.File(None), +): + f: SpooledTemporaryFile = image.file # type: ignore + f._file.name = image.filename # type: ignore + + return client.upload_image(f) + + if __name__ == "__main__": uvicorn.run(app="zulip_write_only_proxy.main:app") From df81dc1aeb8dc95de48d1524f8e131a6688a0245 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 26 Jul 2023 17:07:27 +0200 Subject: [PATCH 019/143] chore(docker): use buildkit caching --- Dockerfile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index f99c01d6..4e4048a1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,12 +2,15 @@ FROM python:3.11 WORKDIR /app -RUN python3 -m pip install --upgrade poetry pip +RUN --mount=type=cache,target=/root/.cache \ + python3 -m pip install --upgrade poetry pip COPY ./poetry.lock ./pyproject.toml ./README.md /app COPY ./src /app/src RUN poetry config virtualenvs.create false --local -RUN poetry install + +RUN --mount=type=cache,target=/root/.cache\ + poetry install CMD ["uvicorn", "zulip_write_only_proxy.main:app", "--host", "0.0.0.0", "--port", "8000"] From f2ef5e37ee925d91e6f7518bd2aa4d6bb95d4e46 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 26 Jul 2023 17:07:37 +0200 Subject: [PATCH 020/143] chore(docker): add compose file for easier pull/up/down --- README.md | 5 +---- docker-compose.yml | 9 +++++++++ 2 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 docker-compose.yml diff --git a/README.md b/README.md index 1d1e75a7..2699f8fe 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,8 @@ For docker: ```sh -# Build image -docker build . --tag zwop - # Start server in background -docker run --rm -v ./zuliprc:/app/zuliprc -v ./clients.json:/app/clients.json -p 8080:8000 -d zwop +docker compose up -d ``` For CLI: diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..7e93e22f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,9 @@ +services: + + zwop: + build: ./ + ports: + - "8080:8000" + volumes: + - ./clients.json:/app/clients.json + - ./zuliprc:/app/zuliprc From 47b719d3cc5105a1104b17b422e7347bea5c87bd Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 26 Jul 2023 17:52:07 +0200 Subject: [PATCH 021/143] build(deps): lower Python version to ^3.9 --- poetry.lock | 21 +++++++++++++++++++-- pyproject.toml | 2 +- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 78f3244d..05349959 100644 --- a/poetry.lock +++ b/poetry.lock @@ -23,6 +23,7 @@ files = [ ] [package.dependencies] +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" @@ -162,6 +163,20 @@ files = [ {file = "distro-1.8.0.tar.gz", hash = "sha256:02e111d1dc6a50abb8eed6bf31c3e48ed8b0830d1ea2a1b78c61765c2513fdd8"}, ] +[[package]] +name = "exceptiongroup" +version = "1.1.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.1.2-py3-none-any.whl", hash = "sha256:e346e69d186172ca7cf029c8c1d16235aa0e04035e5750b4b95039e65204328f"}, + {file = "exceptiongroup-1.1.2.tar.gz", hash = "sha256:12c3e887d6485d16943a309616de20ae5582633e0a2eda17f4e10fd61c1e8af5"}, +] + +[package.extras] +test = ["pytest (>=6)"] + [[package]] name = "fastapi" version = "0.100.0" @@ -469,6 +484,7 @@ files = [ [package.dependencies] anyio = ">=3.4.0,<5" +typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} [package.extras] full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] @@ -535,6 +551,7 @@ files = [ [package.dependencies] click = ">=7.0" h11 = ">=0.8" +typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} [package.extras] standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] @@ -559,5 +576,5 @@ typing-extensions = ">=3.7" [metadata] lock-version = "2.0" -python-versions = "^3.11" -content-hash = "5f1a332a8389886447ff672facb5b2854b02a94cf6a8166d306faa548d92c6c1" +python-versions = "^3.9" +content-hash = "37e3dc344a19258dbe91d1f7ed5fad3827044962e6e0e3f857b14ba0d3867ed6" diff --git a/pyproject.toml b/pyproject.toml index b3b0b801..69896f9c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ packages = [{ include = "zulip_write_only_proxy", from = "src" }] damnit-zulip = "zulip_write_only_proxy.cli:app" [tool.poetry.dependencies] -python = "^3.11" +python = "^3.9" fastapi = "^0.100.0" uvicorn = "^0.23.1" zulip = "^0.8.2" From a12c2f196c7255866716c3d591a96762d71f46be Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 26 Jul 2023 17:59:11 +0200 Subject: [PATCH 022/143] chore(deps): make compatible with Python 3.9 --- src/zulip_write_only_proxy/main.py | 2 -- src/zulip_write_only_proxy/model.py | 5 ++++- src/zulip_write_only_proxy/service.py | 2 ++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index 504c622c..53166fe5 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -1,7 +1,5 @@ from contextlib import asynccontextmanager -from dataclasses import dataclass from tempfile import SpooledTemporaryFile -from typing import NamedTuple import fastapi from pydantic import BaseModel diff --git a/src/zulip_write_only_proxy/model.py b/src/zulip_write_only_proxy/model.py index 548f52a9..115e23dd 100644 --- a/src/zulip_write_only_proxy/model.py +++ b/src/zulip_write_only_proxy/model.py @@ -1,8 +1,11 @@ +from __future__ import annotations + import secrets -from typing import IO, Any, Self +from typing import IO, Any import zulip from pydantic import BaseModel, PrivateAttr +from typing_extensions import Self class ScopedClient(BaseModel): diff --git a/src/zulip_write_only_proxy/service.py b/src/zulip_write_only_proxy/service.py index cd6b3768..3e9326e0 100644 --- a/src/zulip_write_only_proxy/service.py +++ b/src/zulip_write_only_proxy/service.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from pathlib import Path import zulip From 63a42a189a09d17b8c7991c9653c99ecc1ceb68a Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 26 Jul 2023 18:07:16 +0200 Subject: [PATCH 023/143] chore(docker): use .env to set port --- .gitignore | 1 + docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 547bdd09..e6a98ef4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.env zuliprc clients.json diff --git a/docker-compose.yml b/docker-compose.yml index 7e93e22f..be43f610 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,7 +3,7 @@ services: zwop: build: ./ ports: - - "8080:8000" + - "${PORT:-8000}:8000" volumes: - ./clients.json:/app/clients.json - ./zuliprc:/app/zuliprc From 5bcc1c844364e17156a1da639844057a2d2a661a Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 26 Jul 2023 18:48:38 +0200 Subject: [PATCH 024/143] docs: reference Zulip docs, drop response validation --- src/zulip_write_only_proxy/main.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index 53166fe5..0b8c8d11 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -2,9 +2,9 @@ from tempfile import SpooledTemporaryFile import fastapi -from pydantic import BaseModel import uvicorn from fastapi.security import APIKeyHeader +from pydantic import BaseModel from . import service @@ -27,7 +27,14 @@ def get_client(key: str = fastapi.Security(api_key_header)) -> service.ScopedCli raise fastapi.HTTPException(status_code=404, detail="Key not found") from e -@app.post("/message", tags=["User"]) +send_message_docs_url = "https://zulip.com/api/send-message#response" + + +@app.post( + "/message", + tags=["User"], + response_description=f"See {send_message_docs_url}", +) def send_message( client=fastapi.Depends(get_client), content: str = fastapi.Query(...), @@ -53,10 +60,17 @@ class UploadImageResponse(BaseModel): result: str = "success" -@app.post("/upload_image", tags=["User"], response_model=UploadImageResponse) +upload_file_docs_url = "https://zulip.com/api/upload-file#response" + + +@app.post( + "/upload_image", + tags=["User"], + response_description=f"See {upload_file_docs_url}", +) def upload_image( client=fastapi.Depends(get_client), - image: fastapi.UploadFile = fastapi.File(None), + image: fastapi.UploadFile = fastapi.File(...), ): f: SpooledTemporaryFile = image.file # type: ignore f._file.name = image.filename # type: ignore From 839365925f3ceafd773a27cda8895c48aac679c4 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 2 Aug 2023 10:25:37 +0200 Subject: [PATCH 025/143] feat(endpoint): allow topic to be set by API call --- src/zulip_write_only_proxy/cli.py | 3 +-- src/zulip_write_only_proxy/main.py | 7 ++++--- src/zulip_write_only_proxy/model.py | 7 ++----- src/zulip_write_only_proxy/service.py | 6 ++---- 4 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/zulip_write_only_proxy/cli.py b/src/zulip_write_only_proxy/cli.py index 426f4d89..cb6a75b2 100644 --- a/src/zulip_write_only_proxy/cli.py +++ b/src/zulip_write_only_proxy/cli.py @@ -12,10 +12,9 @@ def create( proposal_no: Annotated[int, typer.Argument()], stream: Annotated[Optional[str], typer.Argument()] = None, - topic: Annotated[Optional[str], typer.Argument()] = None, ): """Create a new scoped client for a proposal.""" - client = service.create_client(proposal_no, stream, topic) + client = service.create_client(proposal_no, stream) typer.echo(client) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index 0b8c8d11..0e42d4a3 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -6,7 +6,7 @@ from fastapi.security import APIKeyHeader from pydantic import BaseModel -from . import service +from . import service, model @asynccontextmanager @@ -36,7 +36,8 @@ def get_client(key: str = fastapi.Security(api_key_header)) -> service.ScopedCli response_description=f"See {send_message_docs_url}", ) def send_message( - client=fastapi.Depends(get_client), + client: model.ScopedClient = fastapi.Depends(get_client), + topic: str = fastapi.Query(...), content: str = fastapi.Query(...), image: fastapi.UploadFile = fastapi.File(None), ): @@ -50,7 +51,7 @@ def send_message( content += f"\n[]({result['uri']})" - return client.send_message(content) + return client.send_message(topic, content) # This should not be here diff --git a/src/zulip_write_only_proxy/model.py b/src/zulip_write_only_proxy/model.py index 115e23dd..04c2ec6f 100644 --- a/src/zulip_write_only_proxy/model.py +++ b/src/zulip_write_only_proxy/model.py @@ -13,7 +13,6 @@ class ScopedClient(BaseModel): proposal_no: int stream: str - topic: str _client: zulip.Client = PrivateAttr() @@ -22,23 +21,21 @@ def create( cls, proposal_no: int, stream: str | None = None, - topic: str | None = None, ) -> Self: return cls( key=secrets.token_urlsafe(), proposal_no=proposal_no, stream=stream or f"some-pattern-{proposal_no}", - topic=topic or f"some-pattern-{proposal_no}", ) def upload_image(self, image: IO[Any]): return self._client.upload_file(image) - def send_message(self, content: str): + def send_message(self, topic: str, content: str): request = { "type": "stream", "to": self.stream, - "topic": self.topic, + "topic": topic, "content": content, } diff --git a/src/zulip_write_only_proxy/service.py b/src/zulip_write_only_proxy/service.py index 3e9326e0..96b6db4c 100644 --- a/src/zulip_write_only_proxy/service.py +++ b/src/zulip_write_only_proxy/service.py @@ -12,10 +12,8 @@ REPOSITORY = JSONRepository(path=Path.cwd() / "clients.json") -def create_client( - proposal_no: int, stream: str | None = None, topic: str | None = None -) -> ScopedClient: - client = ScopedClient.create(proposal_no, stream, topic) +def create_client(proposal_no: int, stream: str | None = None) -> ScopedClient: + client = ScopedClient.create(proposal_no, stream) REPOSITORY.put(client) return client From 3a13c0c50e0b6428c4cb3534e6ebfae56015601a Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 2 Aug 2023 11:49:55 +0200 Subject: [PATCH 026/143] feat: add Admin client, use locking for client.json writes --- src/zulip_write_only_proxy/cli.py | 7 ++++ src/zulip_write_only_proxy/main.py | 27 ++++++++---- src/zulip_write_only_proxy/model.py | 22 +++++++++- src/zulip_write_only_proxy/repository.py | 52 ++++++++++++++++++------ src/zulip_write_only_proxy/service.py | 10 ++++- 5 files changed, 94 insertions(+), 24 deletions(-) diff --git a/src/zulip_write_only_proxy/cli.py b/src/zulip_write_only_proxy/cli.py index cb6a75b2..4ed187a3 100644 --- a/src/zulip_write_only_proxy/cli.py +++ b/src/zulip_write_only_proxy/cli.py @@ -18,6 +18,13 @@ def create( typer.echo(client) +@app.command() +def create_admin(): + """Create a new scoped client for a proposal.""" + client = service.create_admin() + typer.echo(client) + + @app.command() def list(): """List all scoped clients.""" diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index 0e42d4a3..bfe6c78c 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -1,12 +1,13 @@ from contextlib import asynccontextmanager from tempfile import SpooledTemporaryFile +from typing import Union import fastapi import uvicorn from fastapi.security import APIKeyHeader from pydantic import BaseModel -from . import service, model +from . import model, service @asynccontextmanager @@ -20,20 +21,20 @@ async def lifespan(app: fastapi.FastAPI): api_key_header = APIKeyHeader(name="X-API-key") -def get_client(key: str = fastapi.Security(api_key_header)) -> service.ScopedClient: +def get_client(key: str = fastapi.Security(api_key_header)) -> model.Client: try: return service.get_client(key) except KeyError as e: raise fastapi.HTTPException(status_code=404, detail="Key not found") from e -send_message_docs_url = "https://zulip.com/api/send-message#response" +send_msg_docs_url = "https://zulip.com/api/send-message#response" @app.post( "/message", tags=["User"], - response_description=f"See {send_message_docs_url}", + response_description=f"See {send_msg_docs_url}", ) def send_message( client: model.ScopedClient = fastapi.Depends(get_client), @@ -61,16 +62,16 @@ class UploadImageResponse(BaseModel): result: str = "success" -upload_file_docs_url = "https://zulip.com/api/upload-file#response" +upload_f_docs_url = "https://zulip.com/api/upload-file#response" @app.post( "/upload_image", tags=["User"], - response_description=f"See {upload_file_docs_url}", + response_description=f"See {upload_f_docs_url}", ) def upload_image( - client=fastapi.Depends(get_client), + client: model.ScopedClient = fastapi.Depends(get_client), image: fastapi.UploadFile = fastapi.File(...), ): f: SpooledTemporaryFile = image.file # type: ignore @@ -79,5 +80,17 @@ def upload_image( return client.upload_image(f) +@app.post("/create_client", tags=["Admin"]) +def create_client( + admin_client: model.AdminClient = fastapi.Depends(get_client), + proposal_no: int = fastapi.Query(...), + stream: Union[str, None] = fastapi.Query(None), +): + try: + return service.create_client(proposal_no, stream) + except ValueError as e: + raise fastapi.HTTPException(status_code=400, detail=str(e)) from e + + if __name__ == "__main__": uvicorn.run(app="zulip_write_only_proxy.main:app") diff --git a/src/zulip_write_only_proxy/model.py b/src/zulip_write_only_proxy/model.py index 04c2ec6f..3ce2c66a 100644 --- a/src/zulip_write_only_proxy/model.py +++ b/src/zulip_write_only_proxy/model.py @@ -1,10 +1,10 @@ from __future__ import annotations import secrets -from typing import IO, Any +from typing import IO, Any, Union import zulip -from pydantic import BaseModel, PrivateAttr +from pydantic import BaseModel, PrivateAttr, validator from typing_extensions import Self @@ -40,3 +40,21 @@ def send_message(self, topic: str, content: str): } return self._client.send_message(request) + + +class AdminClient(BaseModel): + key: str + admin: bool + + @classmethod + def create(cls) -> Self: + return cls(key=secrets.token_urlsafe(), admin=True) + + @validator("admin") + def check_admin(cls, v: bool) -> bool: + if not v: + raise ValueError("Admin client must be admin") + return v + + +Client = Union[ScopedClient, AdminClient] diff --git a/src/zulip_write_only_proxy/repository.py b/src/zulip_write_only_proxy/repository.py index a3617162..b356f6ed 100644 --- a/src/zulip_write_only_proxy/repository.py +++ b/src/zulip_write_only_proxy/repository.py @@ -1,9 +1,12 @@ +import threading from pathlib import Path import orjson from pydantic import BaseModel, field_validator -from .model import ScopedClient +from . import model + +file_lock = threading.Lock() class JSONRepository(BaseModel): @@ -11,27 +14,50 @@ class JSONRepository(BaseModel): path: Path - def get(self, key: str) -> ScopedClient: + def get(self, key: str) -> model.Client: with self.path.open("rb") as f: data = orjson.loads(f.read()) - return ScopedClient(key=key, **data[key]) + client_data = data[key] - def put(self, client: ScopedClient) -> None: - with self.path.open("rb") as f: - data = orjson.loads(f.read()) - proposal_nos = [value["proposal_no"] for value in data.values()] - if client.proposal_no in proposal_nos: - raise ValueError(f"Client already exists for {client.proposal_no=}") + if client_data["admin"]: + return model.AdminClient(key=key, **client_data) + + return model.ScopedClient(key=key, **client_data) + + def put(self, client: model.ScopedClient) -> None: + with file_lock: + with self.path.open("rb") as f: + data = orjson.loads(f.read()) + proposal_nos = [value.get("proposal_no") for value in data.values()] + if client.proposal_no in proposal_nos: + reversed_data = { + value.get("proposal_no"): {"key": key, **value} + for key, value in data.items() + } + + raise ValueError( + f"Client already exists for {client.proposal_no=}: " + f"{reversed_data[client.proposal_no]}" + ) + + data[client.key] = client.model_dump(exclude={"key"}) + + with self.path.open("wb") as f: + f.write(orjson.dumps(data, option=orjson.OPT_INDENT_2)) - data[client.key] = client.model_dump(exclude={"key"}) + def put_admin(self, client: model.AdminClient) -> None: + with file_lock: + with self.path.open("rb") as f: + data = orjson.loads(f.read()) + data[client.key] = client.model_dump(exclude={"key"}) - with self.path.open("wb") as f: - f.write(orjson.dumps(data, option=orjson.OPT_INDENT_2)) + with self.path.open("wb") as f: + f.write(orjson.dumps(data, option=orjson.OPT_INDENT_2)) def list(self): with self.path.open("rb") as f: data = orjson.loads(f.read()) - return [ScopedClient(key=key, **value) for key, value in data.items()] + return [model.ScopedClient(key=key, **value) for key, value in data.items()] @field_validator("path") @classmethod diff --git a/src/zulip_write_only_proxy/service.py b/src/zulip_write_only_proxy/service.py index 96b6db4c..d33b6ec2 100644 --- a/src/zulip_write_only_proxy/service.py +++ b/src/zulip_write_only_proxy/service.py @@ -6,7 +6,7 @@ from pydantic.fields import ModelPrivateAttr from pydantic_core import PydanticUndefined -from .model import ScopedClient +from .model import AdminClient, Client, ScopedClient from .repository import JSONRepository REPOSITORY = JSONRepository(path=Path.cwd() / "clients.json") @@ -18,7 +18,13 @@ def create_client(proposal_no: int, stream: str | None = None) -> ScopedClient: return client -def get_client(key: str) -> ScopedClient: +def create_admin() -> AdminClient: + client = AdminClient.create() + REPOSITORY.put_admin(client) + return client + + +def get_client(key: str) -> Client: return REPOSITORY.get(key) From 92c3d3e1db00f9f7c5a6d42424b36bd7ed582554 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 2 Aug 2023 11:50:59 +0200 Subject: [PATCH 027/143] style: more consistent imports --- src/zulip_write_only_proxy/service.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/zulip_write_only_proxy/service.py b/src/zulip_write_only_proxy/service.py index d33b6ec2..c9f3ba4c 100644 --- a/src/zulip_write_only_proxy/service.py +++ b/src/zulip_write_only_proxy/service.py @@ -6,38 +6,37 @@ from pydantic.fields import ModelPrivateAttr from pydantic_core import PydanticUndefined -from .model import AdminClient, Client, ScopedClient -from .repository import JSONRepository +from . import model, repository -REPOSITORY = JSONRepository(path=Path.cwd() / "clients.json") +REPOSITORY = repository.JSONRepository(path=Path.cwd() / "clients.json") -def create_client(proposal_no: int, stream: str | None = None) -> ScopedClient: - client = ScopedClient.create(proposal_no, stream) +def create_client(proposal_no: int, stream: str | None = None) -> model.ScopedClient: + client = model.ScopedClient.create(proposal_no, stream) REPOSITORY.put(client) return client -def create_admin() -> AdminClient: - client = AdminClient.create() +def create_admin() -> model.AdminClient: + client = model.AdminClient.create() REPOSITORY.put_admin(client) return client -def get_client(key: str) -> Client: +def get_client(key: str) -> model.Client: return REPOSITORY.get(key) -def list_clients() -> list[ScopedClient]: +def list_clients() -> list[model.ScopedClient]: return REPOSITORY.list() def setup(): - if not isinstance(ScopedClient._client, ModelPrivateAttr): + if not isinstance(model.ScopedClient._client, ModelPrivateAttr): raise RuntimeError("ScopedClient.client is not a ModelPrivateAttr") - client_default = ScopedClient._client.default + client_default = model.ScopedClient._client.default if client_default is None or client_default is PydanticUndefined: zulip_client = zulip.Client(config_file=str(Path.cwd() / "zuliprc")) - ScopedClient._client.default = zulip_client + model.ScopedClient._client.default = zulip_client From 1046ae6a5ef8df2a75190230bb2a7863e1a3eab0 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 2 Aug 2023 11:56:33 +0200 Subject: [PATCH 028/143] docs: update usage documentation --- README.md | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 87 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 2699f8fe..d5054823 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,88 @@ # Zulip Write Only Proxy -## Quick Start +## Usage + +### Text Only + +Using requests (synchronous): + +```python +import requests + +url = "http://exfldadev01.desy.de:8089/message" +params = { + "topic": "test-read-only-thing-2", + "content": "I recommend muting this topic." +} +headers = { + "accept": "application/json", + "X-API-key": "GXXQoc8YlXJv2VDksX2Y7NzQAWdkdNeZ5fFvBLrCe6A", + "Content-Type": "multipart/form-data" +} + +response = requests.post(url, params=params, headers=headers) +``` + +Using httpx (async): + +```python +import httpx + +url = "http://exfldadev01.desy.de:8089/message" +params = { + "topic": "test-read-only-thing-2", + "content": "I recommend muting this topic." +} +headers = { + "accept": "application/json", + "X-API-key": "GXXQoc8YlXJv2VDksX2Y7NzQAWdkdNeZ5fFvBLrCe6A", + "Content-Type": "multipart/form-data" +} + +async with httpx.AsyncClient() as client: + response = await client.post(url, params=params, headers=headers) +``` + +### Image/File Upload + +Using requests (synchronous): + +```python +import requests + +url = 'http://exfldadev01.desy.de:8089/message' +headers = { + 'accept': 'application/json', + 'X-API-key': 'DQBMXmA6wmxsQLq4A27GErqD2pARI4IooOciNcmq3ng', +} +params = { + 'content': f'Bonk bonk', +} +files = {'image': open('./downloads/recursion.jpg', 'rb')} + +response = requests.post(url, headers=headers, params=params, files=files) +``` + +Using httpx (async): + +```python +import httpx + +url = 'http://exfldadev01.desy.de:8089/message' +headers = { + 'accept': 'application/json', + 'X-API-key': 'DQBMXmA6wmxsQLq4A27GErqD2pARI4IooOciNcmq3ng', +} +params = { + 'content': f'Bonk bonk', +} +files = {'image': open('./downloads/recursion.jpg', 'rb')} + +async with httpx.AsyncClient() as client: + response = await client.post(url, headers=headers, params=params, files=files) +``` + +## Server Setup and Development For docker: @@ -28,7 +110,7 @@ damnit-zulip --help To create a client for proposal 2222: ```sh -damnit-zulip create 2222 +damnit-zulip create 2222 "proposal 2222 stream" ``` Default configuration is something like: @@ -36,11 +118,10 @@ Default configuration is something like: ```json { "t425dYYQAVAT9AZiPx5fe0nrLzQSPpjZW-54EdxOUPQ": { - "proposal_no": 222, - "stream": "some-pattern-222", - "topic": "some-pattern-222" + "proposal_no": 2222, + "stream": "proposal 2222 stream", } } ``` -Can stream/topic edited manually or set via CLI at creation time. +Stream/topic can be edited manually in the JSON file or set via CLI at creation time. From 8fa741c06cc3fc3b269f97f890578b34d529376f Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 2 Aug 2023 12:17:53 +0200 Subject: [PATCH 029/143] fix: oversight on admin check --- src/zulip_write_only_proxy/repository.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zulip_write_only_proxy/repository.py b/src/zulip_write_only_proxy/repository.py index b356f6ed..fad3d93b 100644 --- a/src/zulip_write_only_proxy/repository.py +++ b/src/zulip_write_only_proxy/repository.py @@ -19,7 +19,7 @@ def get(self, key: str) -> model.Client: data = orjson.loads(f.read()) client_data = data[key] - if client_data["admin"]: + if client_data.get("admin"): return model.AdminClient(key=key, **client_data) return model.ScopedClient(key=key, **client_data) From 58d6f18863929567b4bfd39df3f31a984f99c3d3 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 2 Aug 2023 12:18:02 +0200 Subject: [PATCH 030/143] feat(endpoint): add list topics endpoint --- src/zulip_write_only_proxy/main.py | 17 +++++++++++++++++ src/zulip_write_only_proxy/model.py | 10 ++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index bfe6c78c..159a2f14 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -80,6 +80,23 @@ def upload_image( return client.upload_image(f) +get_topics_docs_url = "https://zulip.com/api/get-stream-topics#response" + + +@app.get( + "/get_topics", + tags=["User"], + response_description=f"See {get_topics_docs_url}", +) +def get_topics( + client: model.ScopedClient = fastapi.Depends(get_client), +): + try: + return client.list_topics() + except RuntimeError as e: + raise fastapi.HTTPException(status_code=400, detail=str(e)) from e + + @app.post("/create_client", tags=["Admin"]) def create_client( admin_client: model.AdminClient = fastapi.Depends(get_client), diff --git a/src/zulip_write_only_proxy/model.py b/src/zulip_write_only_proxy/model.py index 3ce2c66a..09aa8abd 100644 --- a/src/zulip_write_only_proxy/model.py +++ b/src/zulip_write_only_proxy/model.py @@ -31,6 +31,16 @@ def create( def upload_image(self, image: IO[Any]): return self._client.upload_file(image) + def list_topics(self): + stream = self._client.get_stream_id(self.stream) + if stream["result"] != "success": + raise RuntimeError( + f"Failed to get stream id for {self.stream}. Is bot added to stream?\n" + f"Response was {stream}" + ) + stream_id = stream["stream_id"] + return self._client.get_stream_topics(stream_id) + def send_message(self, topic: str, content: str): request = { "type": "stream", From 6293878eb5bdd32169e9872a47a6eb74834873c9 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 2 Aug 2023 12:20:04 +0200 Subject: [PATCH 031/143] refactor: rename to plural --- src/zulip_write_only_proxy/cli.py | 8 +++---- src/zulip_write_only_proxy/main.py | 18 +++++++-------- .../{model.py => models.py} | 0 .../{repository.py => repositories.py} | 14 ++++++------ .../{service.py => services.py} | 22 +++++++++---------- 5 files changed, 31 insertions(+), 31 deletions(-) rename src/zulip_write_only_proxy/{model.py => models.py} (100%) rename src/zulip_write_only_proxy/{repository.py => repositories.py} (82%) rename src/zulip_write_only_proxy/{service.py => services.py} (53%) diff --git a/src/zulip_write_only_proxy/cli.py b/src/zulip_write_only_proxy/cli.py index 4ed187a3..4dd8b6f4 100644 --- a/src/zulip_write_only_proxy/cli.py +++ b/src/zulip_write_only_proxy/cli.py @@ -3,7 +3,7 @@ import typer import uvicorn -from . import service +from . import services app = typer.Typer() @@ -14,21 +14,21 @@ def create( stream: Annotated[Optional[str], typer.Argument()] = None, ): """Create a new scoped client for a proposal.""" - client = service.create_client(proposal_no, stream) + client = services.create_client(proposal_no, stream) typer.echo(client) @app.command() def create_admin(): """Create a new scoped client for a proposal.""" - client = service.create_admin() + client = services.create_admin() typer.echo(client) @app.command() def list(): """List all scoped clients.""" - client = service.list_clients() + client = services.list_clients() typer.echo(client) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index 159a2f14..d4555294 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -7,12 +7,12 @@ from fastapi.security import APIKeyHeader from pydantic import BaseModel -from . import model, service +from . import models, services @asynccontextmanager async def lifespan(app: fastapi.FastAPI): - service.setup() + services.setup() yield @@ -21,9 +21,9 @@ async def lifespan(app: fastapi.FastAPI): api_key_header = APIKeyHeader(name="X-API-key") -def get_client(key: str = fastapi.Security(api_key_header)) -> model.Client: +def get_client(key: str = fastapi.Security(api_key_header)) -> models.Client: try: - return service.get_client(key) + return services.get_client(key) except KeyError as e: raise fastapi.HTTPException(status_code=404, detail="Key not found") from e @@ -37,7 +37,7 @@ def get_client(key: str = fastapi.Security(api_key_header)) -> model.Client: response_description=f"See {send_msg_docs_url}", ) def send_message( - client: model.ScopedClient = fastapi.Depends(get_client), + client: models.ScopedClient = fastapi.Depends(get_client), topic: str = fastapi.Query(...), content: str = fastapi.Query(...), image: fastapi.UploadFile = fastapi.File(None), @@ -71,7 +71,7 @@ class UploadImageResponse(BaseModel): response_description=f"See {upload_f_docs_url}", ) def upload_image( - client: model.ScopedClient = fastapi.Depends(get_client), + client: models.ScopedClient = fastapi.Depends(get_client), image: fastapi.UploadFile = fastapi.File(...), ): f: SpooledTemporaryFile = image.file # type: ignore @@ -89,7 +89,7 @@ def upload_image( response_description=f"See {get_topics_docs_url}", ) def get_topics( - client: model.ScopedClient = fastapi.Depends(get_client), + client: models.ScopedClient = fastapi.Depends(get_client), ): try: return client.list_topics() @@ -99,12 +99,12 @@ def get_topics( @app.post("/create_client", tags=["Admin"]) def create_client( - admin_client: model.AdminClient = fastapi.Depends(get_client), + admin_client: models.AdminClient = fastapi.Depends(get_client), proposal_no: int = fastapi.Query(...), stream: Union[str, None] = fastapi.Query(None), ): try: - return service.create_client(proposal_no, stream) + return services.create_client(proposal_no, stream) except ValueError as e: raise fastapi.HTTPException(status_code=400, detail=str(e)) from e diff --git a/src/zulip_write_only_proxy/model.py b/src/zulip_write_only_proxy/models.py similarity index 100% rename from src/zulip_write_only_proxy/model.py rename to src/zulip_write_only_proxy/models.py diff --git a/src/zulip_write_only_proxy/repository.py b/src/zulip_write_only_proxy/repositories.py similarity index 82% rename from src/zulip_write_only_proxy/repository.py rename to src/zulip_write_only_proxy/repositories.py index fad3d93b..3e97384f 100644 --- a/src/zulip_write_only_proxy/repository.py +++ b/src/zulip_write_only_proxy/repositories.py @@ -4,7 +4,7 @@ import orjson from pydantic import BaseModel, field_validator -from . import model +from . import models file_lock = threading.Lock() @@ -14,17 +14,17 @@ class JSONRepository(BaseModel): path: Path - def get(self, key: str) -> model.Client: + def get(self, key: str) -> models.Client: with self.path.open("rb") as f: data = orjson.loads(f.read()) client_data = data[key] if client_data.get("admin"): - return model.AdminClient(key=key, **client_data) + return models.AdminClient(key=key, **client_data) - return model.ScopedClient(key=key, **client_data) + return models.ScopedClient(key=key, **client_data) - def put(self, client: model.ScopedClient) -> None: + def put(self, client: models.ScopedClient) -> None: with file_lock: with self.path.open("rb") as f: data = orjson.loads(f.read()) @@ -45,7 +45,7 @@ def put(self, client: model.ScopedClient) -> None: with self.path.open("wb") as f: f.write(orjson.dumps(data, option=orjson.OPT_INDENT_2)) - def put_admin(self, client: model.AdminClient) -> None: + def put_admin(self, client: models.AdminClient) -> None: with file_lock: with self.path.open("rb") as f: data = orjson.loads(f.read()) @@ -57,7 +57,7 @@ def put_admin(self, client: model.AdminClient) -> None: def list(self): with self.path.open("rb") as f: data = orjson.loads(f.read()) - return [model.ScopedClient(key=key, **value) for key, value in data.items()] + return [models.ScopedClient(key=key, **value) for key, value in data.items()] @field_validator("path") @classmethod diff --git a/src/zulip_write_only_proxy/service.py b/src/zulip_write_only_proxy/services.py similarity index 53% rename from src/zulip_write_only_proxy/service.py rename to src/zulip_write_only_proxy/services.py index c9f3ba4c..c22de7e6 100644 --- a/src/zulip_write_only_proxy/service.py +++ b/src/zulip_write_only_proxy/services.py @@ -6,37 +6,37 @@ from pydantic.fields import ModelPrivateAttr from pydantic_core import PydanticUndefined -from . import model, repository +from . import models, repositories -REPOSITORY = repository.JSONRepository(path=Path.cwd() / "clients.json") +REPOSITORY = repositories.JSONRepository(path=Path.cwd() / "clients.json") -def create_client(proposal_no: int, stream: str | None = None) -> model.ScopedClient: - client = model.ScopedClient.create(proposal_no, stream) +def create_client(proposal_no: int, stream: str | None = None) -> models.ScopedClient: + client = models.ScopedClient.create(proposal_no, stream) REPOSITORY.put(client) return client -def create_admin() -> model.AdminClient: - client = model.AdminClient.create() +def create_admin() -> models.AdminClient: + client = models.AdminClient.create() REPOSITORY.put_admin(client) return client -def get_client(key: str) -> model.Client: +def get_client(key: str) -> models.Client: return REPOSITORY.get(key) -def list_clients() -> list[model.ScopedClient]: +def list_clients() -> list[models.ScopedClient]: return REPOSITORY.list() def setup(): - if not isinstance(model.ScopedClient._client, ModelPrivateAttr): + if not isinstance(models.ScopedClient._client, ModelPrivateAttr): raise RuntimeError("ScopedClient.client is not a ModelPrivateAttr") - client_default = model.ScopedClient._client.default + client_default = models.ScopedClient._client.default if client_default is None or client_default is PydanticUndefined: zulip_client = zulip.Client(config_file=str(Path.cwd() / "zuliprc")) - model.ScopedClient._client.default = zulip_client + models.ScopedClient._client.default = zulip_client From 9ab1e3477678ca483ceac4a3e6acef95255d2f1e Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 2 Aug 2023 12:47:13 +0200 Subject: [PATCH 032/143] chore(deps): use pydantic 2.0 field validator decorator --- src/zulip_write_only_proxy/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zulip_write_only_proxy/models.py b/src/zulip_write_only_proxy/models.py index 09aa8abd..c2ef5363 100644 --- a/src/zulip_write_only_proxy/models.py +++ b/src/zulip_write_only_proxy/models.py @@ -4,7 +4,7 @@ from typing import IO, Any, Union import zulip -from pydantic import BaseModel, PrivateAttr, validator +from pydantic import BaseModel, PrivateAttr, field_validator from typing_extensions import Self @@ -60,7 +60,7 @@ class AdminClient(BaseModel): def create(cls) -> Self: return cls(key=secrets.token_urlsafe(), admin=True) - @validator("admin") + @field_validator("admin") def check_admin(cls, v: bool) -> bool: if not v: raise ValueError("Admin client must be admin") From 86f94db5aa8e341be1a95ca33cf6c6b9d94c9b75 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 2 Aug 2023 12:48:25 +0200 Subject: [PATCH 033/143] style: formatting improvements --- src/zulip_write_only_proxy/main.py | 12 ++++++------ src/zulip_write_only_proxy/repositories.py | 4 +++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index d4555294..2a89a0d2 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -28,13 +28,13 @@ def get_client(key: str = fastapi.Security(api_key_header)) -> models.Client: raise fastapi.HTTPException(status_code=404, detail="Key not found") from e -send_msg_docs_url = "https://zulip.com/api/send-message#response" +_docs_url = "https://zulip.com/api/send-message#response" @app.post( "/message", tags=["User"], - response_description=f"See {send_msg_docs_url}", + response_description=f"See {_docs_url}", ) def send_message( client: models.ScopedClient = fastapi.Depends(get_client), @@ -62,13 +62,13 @@ class UploadImageResponse(BaseModel): result: str = "success" -upload_f_docs_url = "https://zulip.com/api/upload-file#response" +_docs_url = "https://zulip.com/api/upload-file#response" @app.post( "/upload_image", tags=["User"], - response_description=f"See {upload_f_docs_url}", + response_description=f"See {_docs_url}", ) def upload_image( client: models.ScopedClient = fastapi.Depends(get_client), @@ -80,13 +80,13 @@ def upload_image( return client.upload_image(f) -get_topics_docs_url = "https://zulip.com/api/get-stream-topics#response" +_docs_url = "https://zulip.com/api/get-stream-topics#response" @app.get( "/get_topics", tags=["User"], - response_description=f"See {get_topics_docs_url}", + response_description=f"See {_docs_url}", ) def get_topics( client: models.ScopedClient = fastapi.Depends(get_client), diff --git a/src/zulip_write_only_proxy/repositories.py b/src/zulip_write_only_proxy/repositories.py index 3e97384f..60b6d0a9 100644 --- a/src/zulip_write_only_proxy/repositories.py +++ b/src/zulip_write_only_proxy/repositories.py @@ -57,7 +57,9 @@ def put_admin(self, client: models.AdminClient) -> None: def list(self): with self.path.open("rb") as f: data = orjson.loads(f.read()) - return [models.ScopedClient(key=key, **value) for key, value in data.items()] + return [ + models.ScopedClient(key=key, **value) for key, value in data.items() + ] @field_validator("path") @classmethod From caf7a3f854c6590d5a8cec0ce2dcb1257a651b2a Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 2 Aug 2023 12:49:13 +0200 Subject: [PATCH 034/143] build(add): add pytest --- poetry.lock | 72 +++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 5 +++- 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/poetry.lock b/poetry.lock index 05349959..5b4f4454 100644 --- a/poetry.lock +++ b/poetry.lock @@ -218,6 +218,17 @@ files = [ {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + [[package]] name = "matrix-client" version = "0.4.0" @@ -293,6 +304,32 @@ files = [ {file = "orjson-3.9.2.tar.gz", hash = "sha256:24257c8f641979bf25ecd3e27251b5cc194cdd3a6e96004aac8446f5e63d9664"}, ] +[[package]] +name = "packaging" +version = "23.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, + {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, +] + +[[package]] +name = "pluggy" +version = "1.2.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, + {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + [[package]] name = "pydantic" version = "2.1.1" @@ -425,6 +462,28 @@ files = [ [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +[[package]] +name = "pytest" +version = "7.4.0" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, + {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + [[package]] name = "python-multipart" version = "0.0.6" @@ -489,6 +548,17 @@ typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\"" [package.extras] full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + [[package]] name = "typer" version = "0.9.0" @@ -577,4 +647,4 @@ typing-extensions = ">=3.7" [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "37e3dc344a19258dbe91d1f7ed5fad3827044962e6e0e3f857b14ba0d3867ed6" +content-hash = "d4ddaf676e4ae6f494cd95a0ee11350f3748539182f8c06db2ec4c07ab683530" diff --git a/pyproject.toml b/pyproject.toml index 69896f9c..d915ccb2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ name = "zulip-write-only-proxy" version = "0.1.0" description = "" -authors = ["Your Name "] +authors = ["Robert Rosca"] readme = "README.md" packages = [{ include = "zulip_write_only_proxy", from = "src" }] @@ -20,6 +20,9 @@ pydantic = "^2.1.1" typer = "^0.9.0" +[tool.poetry.group.dev.dependencies] +pytest = "^7.4.0" + [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" From 9eeca36dee895816ecd6c92fa43d94b37457a964 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 2 Aug 2023 12:57:01 +0200 Subject: [PATCH 035/143] fix: store files in config directory to deal with docker mount --- config/.gitkeep | 0 docker-compose.yml | 3 +-- src/zulip_write_only_proxy/services.py | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) create mode 100644 config/.gitkeep diff --git a/config/.gitkeep b/config/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/docker-compose.yml b/docker-compose.yml index be43f610..5f650415 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,5 +5,4 @@ services: ports: - "${PORT:-8000}:8000" volumes: - - ./clients.json:/app/clients.json - - ./zuliprc:/app/zuliprc + - ./config:/app/config diff --git a/src/zulip_write_only_proxy/services.py b/src/zulip_write_only_proxy/services.py index c22de7e6..da40be62 100644 --- a/src/zulip_write_only_proxy/services.py +++ b/src/zulip_write_only_proxy/services.py @@ -8,7 +8,7 @@ from . import models, repositories -REPOSITORY = repositories.JSONRepository(path=Path.cwd() / "clients.json") +REPOSITORY = repositories.JSONRepository(path=Path.cwd() / "config" / "clients.json") def create_client(proposal_no: int, stream: str | None = None) -> models.ScopedClient: @@ -38,5 +38,5 @@ def setup(): client_default = models.ScopedClient._client.default if client_default is None or client_default is PydanticUndefined: - zulip_client = zulip.Client(config_file=str(Path.cwd() / "zuliprc")) + zulip_client = zulip.Client(config_file=str(Path.cwd() / "config" / "zuliprc")) models.ScopedClient._client.default = zulip_client From fbd8a03ff6b6facef727f06a8c1b49860a24a72a Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 2 Aug 2023 13:11:05 +0200 Subject: [PATCH 036/143] refactor: remove redundant response model --- src/zulip_write_only_proxy/main.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index 2a89a0d2..d3121464 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -5,7 +5,6 @@ import fastapi import uvicorn from fastapi.security import APIKeyHeader -from pydantic import BaseModel from . import models, services @@ -55,13 +54,6 @@ def send_message( return client.send_message(topic, content) -# This should not be here -class UploadImageResponse(BaseModel): - uri: str - msg: str - result: str = "success" - - _docs_url = "https://zulip.com/api/upload-file#response" From 7f55125cdab2ee19717d5033127985894878abf4 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 2 Aug 2023 16:41:14 +0200 Subject: [PATCH 037/143] fix: update response for invalid key --- src/zulip_write_only_proxy/main.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index d3121464..684aa48a 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -24,7 +24,7 @@ def get_client(key: str = fastapi.Security(api_key_header)) -> models.Client: try: return services.get_client(key) except KeyError as e: - raise fastapi.HTTPException(status_code=404, detail="Key not found") from e + raise fastapi.HTTPException(status_code=401, detail="Unauthorized") from e _docs_url = "https://zulip.com/api/send-message#response" @@ -99,7 +99,3 @@ def create_client( return services.create_client(proposal_no, stream) except ValueError as e: raise fastapi.HTTPException(status_code=400, detail=str(e)) from e - - -if __name__ == "__main__": - uvicorn.run(app="zulip_write_only_proxy.main:app") From 51a24e30a5a3e51e4c85546aa073636b11927fa4 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 2 Aug 2023 16:41:26 +0200 Subject: [PATCH 038/143] fix: deal with mix of client/admins in repo list --- src/zulip_write_only_proxy/repositories.py | 17 ++++++++++++++--- src/zulip_write_only_proxy/services.py | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/zulip_write_only_proxy/repositories.py b/src/zulip_write_only_proxy/repositories.py index 60b6d0a9..0c6c529d 100644 --- a/src/zulip_write_only_proxy/repositories.py +++ b/src/zulip_write_only_proxy/repositories.py @@ -54,13 +54,24 @@ def put_admin(self, client: models.AdminClient) -> None: with self.path.open("wb") as f: f.write(orjson.dumps(data, option=orjson.OPT_INDENT_2)) - def list(self): + def list(self) -> list[models.Client]: with self.path.open("rb") as f: data = orjson.loads(f.read()) - return [ - models.ScopedClient(key=key, **value) for key, value in data.items() + + clients = [ + models.ScopedClient(key=key, **value) + for key, value in data.items() + if not value.get("admin") + ] + + admins = [ + models.AdminClient(key=key, **value) + for key, value in data.items() + if value.get("admin") ] + return clients + admins + @field_validator("path") @classmethod def check_path(cls, v: Path) -> Path: diff --git a/src/zulip_write_only_proxy/services.py b/src/zulip_write_only_proxy/services.py index da40be62..b9e131d2 100644 --- a/src/zulip_write_only_proxy/services.py +++ b/src/zulip_write_only_proxy/services.py @@ -27,7 +27,7 @@ def get_client(key: str) -> models.Client: return REPOSITORY.get(key) -def list_clients() -> list[models.ScopedClient]: +def list_clients() -> list[models.Client]: return REPOSITORY.list() From 24bedd3f37c6af35310947f03c639caaab15d56a Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 2 Aug 2023 16:42:14 +0200 Subject: [PATCH 039/143] refactor: use factory to return same object instead of default Default actually returns a deep copy of the object --- src/zulip_write_only_proxy/services.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zulip_write_only_proxy/services.py b/src/zulip_write_only_proxy/services.py index b9e131d2..a7687aa7 100644 --- a/src/zulip_write_only_proxy/services.py +++ b/src/zulip_write_only_proxy/services.py @@ -39,4 +39,4 @@ def setup(): if client_default is None or client_default is PydanticUndefined: zulip_client = zulip.Client(config_file=str(Path.cwd() / "config" / "zuliprc")) - models.ScopedClient._client.default = zulip_client + models.ScopedClient._client.default_factory = lambda: zulip_client From e20d81408f70c36df076023976bb9322df62134e Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 2 Aug 2023 16:42:23 +0200 Subject: [PATCH 040/143] test: add initial tests --- poetry.lock | 128 +++++++++++++++++++++++++++++-- pyproject.toml | 12 ++- tests/conftest.py | 62 +++++++++++++++ tests/test_fastapi.py | 151 +++++++++++++++++++++++++++++++++++++ tests/test_models.py | 76 +++++++++++++++++++ tests/test_repositories.py | 86 +++++++++++++++++++++ tests/test_services.py | 57 ++++++++++++++ 7 files changed, 562 insertions(+), 10 deletions(-) create mode 100644 tests/conftest.py create mode 100644 tests/test_fastapi.py create mode 100644 tests/test_models.py create mode 100644 tests/test_repositories.py create mode 100644 tests/test_services.py diff --git a/poetry.lock b/poetry.lock index 5b4f4454..35df23fb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -179,13 +179,13 @@ test = ["pytest (>=6)"] [[package]] name = "fastapi" -version = "0.100.0" +version = "0.100.1" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.7" files = [ - {file = "fastapi-0.100.0-py3-none-any.whl", hash = "sha256:271662daf986da8fa98dc2b7c7f61c4abdfdccfb4786d79ed8b2878f172c6d5f"}, - {file = "fastapi-0.100.0.tar.gz", hash = "sha256:acb5f941ea8215663283c10018323ba7ea737c571b67fc7e88e9469c7eb1d12e"}, + {file = "fastapi-0.100.1-py3-none-any.whl", hash = "sha256:ec6dd52bfc4eff3063cfcd0713b43c87640fefb2687bbbe3d8a08d94049cdf32"}, + {file = "fastapi-0.100.1.tar.gz", hash = "sha256:522700d7a469e4a973d92321ab93312448fbe20fca9c8da97effc7e7bc56df23"}, ] [package.dependencies] @@ -207,6 +207,50 @@ files = [ {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, ] +[[package]] +name = "httpcore" +version = "0.17.3" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.7" +files = [ + {file = "httpcore-0.17.3-py3-none-any.whl", hash = "sha256:c2789b767ddddfa2a5782e3199b2b7f6894540b17b16ec26b2c4d8e103510b87"}, + {file = "httpcore-0.17.3.tar.gz", hash = "sha256:a6f30213335e34c1ade7be6ec7c47f19f50c56db36abef1a9dfa3815b1cb3888"}, +] + +[package.dependencies] +anyio = ">=3.0,<5.0" +certifi = "*" +h11 = ">=0.13,<0.15" +sniffio = "==1.*" + +[package.extras] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + +[[package]] +name = "httpx" +version = "0.24.1" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.7" +files = [ + {file = "httpx-0.24.1-py3-none-any.whl", hash = "sha256:06781eb9ac53cde990577af654bd990a4949de37a28bdb4a230d434f3a30b9bd"}, + {file = "httpx-0.24.1.tar.gz", hash = "sha256:5853a43053df830c20f8110c5e69fe44d035d850b2dfe795e196f00fdb774bdd"}, +] + +[package.dependencies] +certifi = "*" +httpcore = ">=0.15.0,<0.18.0" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + [[package]] name = "idna" version = "3.4" @@ -315,6 +359,17 @@ files = [ {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, ] +[[package]] +name = "pastel" +version = "0.2.1" +description = "Bring colors to your terminal." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pastel-0.2.1-py2.py3-none-any.whl", hash = "sha256:4349225fcdf6c2bb34d483e523475de5bb04a5c10ef711263452cb37d7dd4364"}, + {file = "pastel-0.2.1.tar.gz", hash = "sha256:e6581ac04e973cac858828c6202c1e1e81fee1dc7de7683f3e1ffe0bfd8a573d"}, +] + [[package]] name = "pluggy" version = "1.2.0" @@ -330,6 +385,24 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "poethepoet" +version = "0.21.1" +description = "A task runner that works well with poetry." +optional = false +python-versions = ">=3.8" +files = [ + {file = "poethepoet-0.21.1-py3-none-any.whl", hash = "sha256:c79b2aefc889c4490db9d64ecc1896db78700aa212b8c5f0bca4aee0395e7714"}, + {file = "poethepoet-0.21.1.tar.gz", hash = "sha256:39d754f53714545a23cfc94b122deaf3548218b6e35b453c30973c5598719e9f"}, +] + +[package.dependencies] +pastel = ">=0.2.1,<0.3.0" +tomli = ">=1.2.2" + +[package.extras] +poetry-plugin = ["poetry (>=1.0,<2.0)"] + [[package]] name = "pydantic" version = "2.1.1" @@ -519,6 +592,31 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "slipcover" +version = "0.3.2" +description = "Near Zero-Overhead Python Code Coverage" +optional = false +python-versions = ">=3.8,<3.12" +files = [ + {file = "slipcover-0.3.2-cp310-cp310-macosx_11_7_universal2.whl", hash = "sha256:2ef0d99f81dada328223089bd3b2f94d6f2076211ce634633d6eeee59676452a"}, + {file = "slipcover-0.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d10a02e1dc198c4111baa7c74a7754d8a1fce28ff1e16aeb3fe7ca15390c0372"}, + {file = "slipcover-0.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:dbe90e413297c0782e5a50f6507ef22974ce0c36481af80d1edf2589978e7f5f"}, + {file = "slipcover-0.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:49c7e78411c883e913ffc30b71929e0af4dd4cd4dc055b7ca7052eaab97d83a4"}, + {file = "slipcover-0.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f75e027cffdae84f25e9f8a8c7d1b1dc65fe6ce4d1bf5c5703fe062e6bd57ff3"}, + {file = "slipcover-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:f7e004343b14ccc203af663ed10971f4516aa239ecca8b1c99c2ad47fcb9c7ae"}, + {file = "slipcover-0.3.2-cp38-cp38-macosx_11_7_universal2.whl", hash = "sha256:fcdca53eae06c7518887d553c4ca7dc62ea80407344c958a58d903729e8b6662"}, + {file = "slipcover-0.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eda2d1dfe550857cc097a0cd2f2934c26ec196ac27dd791cd7a7834bf4b203a"}, + {file = "slipcover-0.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:71a6045d634a42346af44b7d7f09eb2a994d45e7fa46e54200a79faa9af7970d"}, + {file = "slipcover-0.3.2-cp39-cp39-macosx_11_7_universal2.whl", hash = "sha256:0e755c0e26a852fd1b786a9837a7d5f723e3d135637e57d4ba101b3790fd9435"}, + {file = "slipcover-0.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a537f16279e4e99c8b9ab38c86e656474c0cd1cb94997c8c519bc9777e8e2dc"}, + {file = "slipcover-0.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:52523889926a3a9a16323dab8bec5097bc512302b4af23eee42c5be2ebdd1653"}, + {file = "slipcover-0.3.2.tar.gz", hash = "sha256:fd095928a24521bf54614778fb7d469121953928f53e8fe752570cf10888c475"}, +] + +[package.dependencies] +tabulate = "*" + [[package]] name = "sniffio" version = "1.3.0" @@ -548,6 +646,20 @@ typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\"" [package.extras] full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] +[[package]] +name = "tabulate" +version = "0.9.0" +description = "Pretty-print tabular data" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, + {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, +] + +[package.extras] +widechars = ["wcwidth"] + [[package]] name = "tomli" version = "2.0.1" @@ -609,13 +721,13 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "uvicorn" -version = "0.23.1" +version = "0.23.2" description = "The lightning-fast ASGI server." optional = false python-versions = ">=3.8" files = [ - {file = "uvicorn-0.23.1-py3-none-any.whl", hash = "sha256:1d55d46b83ee4ce82b4e82f621f2050adb3eb7b5481c13f9af1744951cae2f1f"}, - {file = "uvicorn-0.23.1.tar.gz", hash = "sha256:da9b0c8443b2d7ee9db00a345f1eee6db7317432c9d4400f5049cc8d358383be"}, + {file = "uvicorn-0.23.2-py3-none-any.whl", hash = "sha256:1f9be6558f01239d4fdf22ef8126c39cb1ad0addf76c40e760549d2c2f43ab53"}, + {file = "uvicorn-0.23.2.tar.gz", hash = "sha256:4d3cc12d7727ba72b64d12d3cc7743124074c0a69f7b201512fc50c3e3f1569a"}, ] [package.dependencies] @@ -646,5 +758,5 @@ typing-extensions = ">=3.7" [metadata] lock-version = "2.0" -python-versions = "^3.9" -content-hash = "d4ddaf676e4ae6f494cd95a0ee11350f3748539182f8c06db2ec4c07ab683530" +python-versions = ">=3.9,<3.12" +content-hash = "c122051357bcfcf780b8a8f79bd51a856b0dfa839598407dd00dbd370e776804" diff --git a/pyproject.toml b/pyproject.toml index d915ccb2..0957f333 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ packages = [{ include = "zulip_write_only_proxy", from = "src" }] damnit-zulip = "zulip_write_only_proxy.cli:app" [tool.poetry.dependencies] -python = "^3.9" +python = ">=3.9,<3.12" fastapi = "^0.100.0" uvicorn = "^0.23.1" zulip = "^0.8.2" @@ -19,9 +19,17 @@ orjson = "^3.9.2" pydantic = "^2.1.1" typer = "^0.9.0" +[tool.poetry.group.test.dependencies] +slipcover = "^0.3.2" +pytest = "^7.4.0" +httpx = "^0.24.1" [tool.poetry.group.dev.dependencies] -pytest = "^7.4.0" +poethepoet = "^0.21.1" + +[tool.poe.tasks] +test = "python3 -m slipcover --source src -m pytest" +serve.script = "my_app.service:run(debug=True)" [build-system] requires = ["poetry-core"] diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..15c44bde --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,62 @@ +import tempfile +from pathlib import Path +from unittest.mock import MagicMock, patch + +import orjson +import pytest +from fastapi.testclient import TestClient + +from zulip_write_only_proxy.models import ScopedClient +from zulip_write_only_proxy.repositories import JSONRepository + + +@pytest.fixture +def repository(): + with tempfile.NamedTemporaryFile(delete=False) as f: + data = { + "client1": { + "stream": "Test Stream 1", + "proposal_no": 1, + }, + "client2": { + "stream": "Test Stream 2", + "proposal_no": 2, + }, + "admin1": { + "admin": True, + }, + } + + f.write(orjson.dumps(data)) + + path = Path(f.name) + + repository = JSONRepository(path=path) + + with patch("zulip_write_only_proxy.services.REPOSITORY", repository): + yield repository + + path.unlink() + + +# @pytest.fixture +# def scoped_client(): +# client = ScopedClient.create(1234, stream="Test Stream") +# client._client = MagicMock() +# return client + + +@pytest.fixture +def zulip_client(): + with patch("zulip.Client", new_callable=MagicMock) as mock_class: + mock_class.return_value = mock_class + yield mock_class + + +@pytest.fixture +def fastapi_client(repository, zulip_client): + from zulip_write_only_proxy import services, main + + services.setup() + + yield TestClient(main.app, headers={"X-API-key": "client1"}) diff --git a/tests/test_fastapi.py b/tests/test_fastapi.py new file mode 100644 index 00000000..5f7e61bb --- /dev/null +++ b/tests/test_fastapi.py @@ -0,0 +1,151 @@ +import io +from unittest.mock import MagicMock + +from zulip_write_only_proxy import models, services + + +def test_send_message(fastapi_client, zulip_client): + zulip_client.send_message = MagicMock(return_value={"result": "success"}) + + response = fastapi_client.post( + "/message", + params={"topic": "Test Topic", "content": "Test Content"}, + ) + + assert response.status_code == 200 + assert response.json() == {"result": "success"} + + # Check that the zulip client was called with the expected arguments + zulip_request = { + "type": "stream", + "to": "Test Stream 1", + "topic": "Test Topic", + "content": "Test Content", + } + zulip_client.send_message.assert_called_once_with(zulip_request) + + +def test_send_message_invalid_key(fastapi_client): + # Call the API endpoint with an invalid key + response = fastapi_client.post( + "/message", + headers={"X-API-key": "invalid_key"}, + params={"topic": "Test Topic", "content": "Test Content"}, + ) + + # Check that the response has a 404 status code and the expected error message + assert response.status_code == 404 + assert response.json() == {"detail": "Key not found"} + + +def test_send_message_with_image(fastapi_client): + # Mock the services module + services.send_message = MagicMock(return_value={"result": "success"}) + + # Call the API endpoint with valid data and an image + image = io.BytesIO(b"test image data") + response = fastapi_client.post( + "/message", + headers={"X-API-key": "test_key"}, + params={"topic": "Test Topic", "content": "Test Content"}, + files={"image": ("test.jpg", image)}, + ) + + # Check that the response has a 200 status code and the expected result + assert response.status_code == 200 + assert response.json() == {"result": "success"} + + # Check that the services module was called with the expected arguments + services.send_message.assert_called_once_with( + models.ScopedClient.create("test_key"), "Test Topic", "Test Content", image + ) + + +def test_upload_image(fastapi_client): + # Mock the services module + services.upload_image = MagicMock(return_value={"uri": "/foo/bar.jpg"}) + + # Call the API endpoint with valid data + image = io.BytesIO(b"test image data") + response = fastapi_client.post( + "/upload_image", + headers={"X-API-key": "test_key"}, + files={"image": ("test.jpg", image)}, + ) + + # Check that the response has a 200 status code and the expected result + assert response.status_code == 200 + assert response.json() == {"uri": "/foo/bar.jpg"} + + # Check that the services module was called with the expected arguments + services.upload_image.assert_called_once_with( + models.ScopedClient.create("test_key"), image + ) + + +def test_get_topics(fastapi_client): + # Mock the services module + services.list_topics = MagicMock(return_value=["Topic 1", "Topic 2"]) + + # Call the API endpoint with valid data + response = fastapi_client.get("/get_topics", headers={"X-API-key": "test_key"}) + + # Check that the response has a 200 status code and the expected result + assert response.status_code == 200 + assert response.json() == ["Topic 1", "Topic 2"] + + # Check that the services module was called with the expected arguments + services.list_topics.assert_called_once_with(models.ScopedClient.create("test_key")) + + +def test_get_topics_error(fastapi_client): + # Mock the services module to raise an error + services.list_topics = MagicMock(side_effect=RuntimeError("Test Error")) + + # Call the API endpoint with valid data + response = fastapi_client.get("/get_topics", headers={"X-API-key": "test_key"}) + + # Check that the response has a 400 status code and the expected error message + assert response.status_code == 400 + assert response.json() == {"detail": "Test Error"} + + # Check that the services module was called with the expected arguments + services.list_topics.assert_called_once_with(models.ScopedClient.create("test_key")) + + +def test_create_client(fastapi_client): + # Mock the services module + services.create_client = MagicMock(return_value={"result": "success"}) + + # Call the API endpoint with valid data + response = fastapi_client.post( + "/create_client", + headers={"X-API-key": "admin_key"}, + params={"proposal_no": 1234, "stream": "Test Stream"}, + ) + + # Check that the response has a 200 status code and the expected result + assert response.status_code == 200 + assert response.json() == {"result": "success"} + + # Check that the services module was called with the expected arguments + services.create_client.assert_called_once_with(1234, "Test Stream") + + +def test_create_client_error(fastapi_client): + # Mock the services module to raise an error + services.create_client = MagicMock(side_effect=ValueError("Test Error")) + + # Call the API endpoint with invalid data + response = fastapi_client.post( + "/create_client", + headers={"X-API-key": "admin_key"}, + params={"proposal_no": 1234, "stream": "Test Stream"}, + ) + + # Check that the response has a 400 status code and the expected error message + assert response.status_code == 400 + assert response.json() == {"detail": "Test Error"} + + # Check that the services module was called with the expected arguments + services.create_client.assert_called_once_with(1234, "Test Stream") diff --git a/tests/test_models.py b/tests/test_models.py new file mode 100644 index 00000000..1fb24c1c --- /dev/null +++ b/tests/test_models.py @@ -0,0 +1,76 @@ +import io +from unittest.mock import MagicMock + +import pytest + +from zulip_write_only_proxy.models import AdminClient + + +def test_upload_image(scoped_client): + scoped_client._client.upload_file = MagicMock(return_value={"uri": "/foo/bar.jpg"}) + + image = io.BytesIO(b"test image data") + + result = scoped_client.upload_image(image) + + scoped_client._client.upload_file.assert_called_once_with(image) + + assert result == {"uri": "/foo/bar.jpg"} + + +def test_list_topics(scoped_client): + scoped_client._client.get_stream_id = MagicMock( + return_value={"result": "success", "stream_id": 123} + ) + scoped_client._client.get_stream_topics = MagicMock( + return_value=["Topic 1", "Topic 2"] + ) + + result = scoped_client.list_topics() + + scoped_client._client.get_stream_id.assert_called_once_with("Test Stream") + + scoped_client._client.get_stream_topics.assert_called_once_with(123) + + assert result == ["Topic 1", "Topic 2"] + + +def test_list_topics_raises(scoped_client): + scoped_client._client.get_stream_id = MagicMock(return_value={"result": "failure"}) + + with pytest.raises(RuntimeError): + scoped_client.list_topics() + + +def test_send_message(scoped_client): + scoped_client._client.send_message = MagicMock(return_value={"result": "success"}) + + result = scoped_client.send_message("Test Topic", "Test Content") + + scoped_client._client.send_message.assert_called_once_with( + { + "type": "stream", + "to": "Test Stream", + "topic": "Test Topic", + "content": "Test Content", + } + ) + + assert result == {"result": "success"} + + +def test_admin_client_create(): + client = AdminClient.create() + + assert client.key is not None + + assert client.admin is True + + +def test_admin_client_init(): + result = AdminClient(key="", admin=True) + + assert result.admin + + with pytest.raises(ValueError): + AdminClient(key="", admin=False) diff --git a/tests/test_repositories.py b/tests/test_repositories.py new file mode 100644 index 00000000..074a17ce --- /dev/null +++ b/tests/test_repositories.py @@ -0,0 +1,86 @@ +import tempfile +from pathlib import Path + +import pytest + +from zulip_write_only_proxy.models import AdminClient, ScopedClient +from zulip_write_only_proxy.repositories import JSONRepository + + +def test_file_creation(): + with tempfile.TemporaryDirectory() as f: + path = Path(f) / "test.json" + + assert not path.exists() + + repository = JSONRepository(path=path) + + assert path.exists() + + assert len(repository.list()) == 0 + + +def test_get_scoped_client(repository): + result = repository.get("client1") + + assert isinstance(result, ScopedClient) + assert result.stream == "Test Stream 1" + assert result.proposal_no == 1 + + with pytest.raises(KeyError): + repository.get("invalid") + + +def test_get_admin_client(repository): + result = repository.get("admin1") + + assert isinstance(result, AdminClient) + assert result.admin is True + + +def test_put_scoped_client(repository): + client = ScopedClient( + key="client3", + stream="Test Stream 3", + proposal_no=3, + ) + + repository.put(client) + + result = repository.get(client.key) + + assert isinstance(result, ScopedClient) + assert result.stream == "Test Stream 3" + assert result.proposal_no == 3 + + with pytest.raises(ValueError): + repository.put(client) + + +def test_put_admin_client(repository): + client = AdminClient(key="admin2", admin=True) + + repository.put_admin(client) + + result = repository.get(client.key) + + assert isinstance(result, AdminClient) + assert result.admin is True + + +def test_list_clients(repository): + result = repository.list() + + assert len(result) == 3 + + assert isinstance(result[0], ScopedClient) + assert result[0].stream == "Test Stream 1" + assert result[0].proposal_no == 1 + + assert isinstance(result[1], ScopedClient) + assert result[1].stream == "Test Stream 2" + assert result[1].proposal_no == 2 + + assert isinstance(result[2], AdminClient) + assert result[2].key == "admin1" + assert result[2].admin is True diff --git a/tests/test_services.py b/tests/test_services.py new file mode 100644 index 00000000..b6aa57a1 --- /dev/null +++ b/tests/test_services.py @@ -0,0 +1,57 @@ +from unittest.mock import patch + +import pytest + +from zulip_write_only_proxy.models import AdminClient, ScopedClient +from zulip_write_only_proxy.services import ( + create_admin, + create_client, + get_client, + list_clients, + setup, +) + + +def test_create_client(repository): + result = create_client(proposal_no=3) + assert isinstance(result, ScopedClient) + + +def test_create_admin(repository): + result = create_admin() + assert isinstance(result, AdminClient) + + +def test_get_client(repository): + result = get_client("client1") + + assert isinstance(result, ScopedClient) + assert result.stream == "Test Stream 1" + assert result.proposal_no == 1 + + with pytest.raises(KeyError): + get_client("invalid") + + +def test_list_clients(repository): + result = list_clients() + assert len(result) == 3 + + +def test_setup(zulip_client): + from pydantic.fields import ModelPrivateAttr + from pydantic_core import PydanticUndefinedType + + with patch.object(ScopedClient, "_client", ModelPrivateAttr()): + assert isinstance(ScopedClient._client, ModelPrivateAttr) + assert isinstance(ScopedClient._client.default, PydanticUndefinedType) + + setup() + + assert not isinstance(ScopedClient._client.default, PydanticUndefinedType) + + +def test_setup_raises(zulip_client): + with patch.object(ScopedClient, "_client", 1234): + with pytest.raises(RuntimeError): + setup() From 545653c14be831b88e86c1a392b8a5ab234f75bc Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 2 Aug 2023 17:14:22 +0200 Subject: [PATCH 041/143] feat: add bot to stream when client is created --- src/zulip_write_only_proxy/models.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/zulip_write_only_proxy/models.py b/src/zulip_write_only_proxy/models.py index c2ef5363..2f9e5e73 100644 --- a/src/zulip_write_only_proxy/models.py +++ b/src/zulip_write_only_proxy/models.py @@ -22,12 +22,16 @@ def create( proposal_no: int, stream: str | None = None, ) -> Self: - return cls( + self = cls( key=secrets.token_urlsafe(), proposal_no=proposal_no, stream=stream or f"some-pattern-{proposal_no}", ) + self._client.add_subscriptions(streams=[{"name": self.stream}]) + + return self + def upload_image(self, image: IO[Any]): return self._client.upload_file(image) From a57a861ce16004de7eb1e68001256c3adc696cc9 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 2 Aug 2023 17:14:54 +0200 Subject: [PATCH 042/143] feat: use poe task to serve --- pyproject.toml | 2 +- src/zulip_write_only_proxy/cli.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0957f333..69fc221f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ poethepoet = "^0.21.1" [tool.poe.tasks] test = "python3 -m slipcover --source src -m pytest" -serve.script = "my_app.service:run(debug=True)" +serve = "uvicorn zulip_write_only_proxy.main:app --reload" [build-system] requires = ["poetry-core"] diff --git a/src/zulip_write_only_proxy/cli.py b/src/zulip_write_only_proxy/cli.py index 4dd8b6f4..a6a3acea 100644 --- a/src/zulip_write_only_proxy/cli.py +++ b/src/zulip_write_only_proxy/cli.py @@ -1,7 +1,6 @@ from typing import Annotated, Optional import typer -import uvicorn from . import services From 8e72e3dfd225db2ef4e9c9b8175f6290747c0d4c Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Thu, 3 Aug 2023 09:47:12 +0200 Subject: [PATCH 043/143] test: update tests for default factory ScopedClient._client --- tests/conftest.py | 8 +++----- tests/test_services.py | 5 ++++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 15c44bde..da72158f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -39,11 +39,9 @@ def repository(): path.unlink() -# @pytest.fixture -# def scoped_client(): -# client = ScopedClient.create(1234, stream="Test Stream") -# client._client = MagicMock() -# return client +@pytest.fixture +def scoped_client(): + return ScopedClient.create(1234, stream="Test Stream") @pytest.fixture diff --git a/tests/test_services.py b/tests/test_services.py index b6aa57a1..7469a527 100644 --- a/tests/test_services.py +++ b/tests/test_services.py @@ -45,10 +45,13 @@ def test_setup(zulip_client): with patch.object(ScopedClient, "_client", ModelPrivateAttr()): assert isinstance(ScopedClient._client, ModelPrivateAttr) assert isinstance(ScopedClient._client.default, PydanticUndefinedType) + assert ScopedClient._client.default_factory is None setup() - assert not isinstance(ScopedClient._client.default, PydanticUndefinedType) + assert not isinstance( + ScopedClient._client.default_factory, PydanticUndefinedType + ) def test_setup_raises(zulip_client): From d0fe6ee948726bfc7194f652edc734e214db90a3 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Thu, 3 Aug 2023 12:15:23 +0200 Subject: [PATCH 044/143] feat(endpoint): add `/me` endpoint to get details on what stream you use --- src/zulip_write_only_proxy/main.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index 684aa48a..50811806 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -3,7 +3,6 @@ from typing import Union import fastapi -import uvicorn from fastapi.security import APIKeyHeader from . import models, services @@ -24,7 +23,7 @@ def get_client(key: str = fastapi.Security(api_key_header)) -> models.Client: try: return services.get_client(key) except KeyError as e: - raise fastapi.HTTPException(status_code=401, detail="Unauthorized") from e + raise fastapi.HTTPException(status_code=401, detail="Unauthorised") from e _docs_url = "https://zulip.com/api/send-message#response" @@ -89,6 +88,13 @@ def get_topics( raise fastapi.HTTPException(status_code=400, detail=str(e)) from e +@app.get("/me", tags=["User"]) +def get_me( + client: models.ScopedClient = fastapi.Depends(get_client), +) -> models.ScopedClient: + return client + + @app.post("/create_client", tags=["Admin"]) def create_client( admin_client: models.AdminClient = fastapi.Depends(get_client), From 68d7e81814885eb9a5dcf8deeca320f9365027dc Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Thu, 3 Aug 2023 12:16:45 +0200 Subject: [PATCH 045/143] feat(model): change key field to SecretStr --- src/zulip_write_only_proxy/models.py | 10 +++++----- src/zulip_write_only_proxy/repositories.py | 14 +++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/zulip_write_only_proxy/models.py b/src/zulip_write_only_proxy/models.py index 2f9e5e73..99379f18 100644 --- a/src/zulip_write_only_proxy/models.py +++ b/src/zulip_write_only_proxy/models.py @@ -4,12 +4,12 @@ from typing import IO, Any, Union import zulip -from pydantic import BaseModel, PrivateAttr, field_validator +from pydantic import BaseModel, PrivateAttr, SecretStr, field_validator from typing_extensions import Self class ScopedClient(BaseModel): - key: str + key: SecretStr proposal_no: int stream: str @@ -23,7 +23,7 @@ def create( stream: str | None = None, ) -> Self: self = cls( - key=secrets.token_urlsafe(), + key=SecretStr(secrets.token_urlsafe()), proposal_no=proposal_no, stream=stream or f"some-pattern-{proposal_no}", ) @@ -57,12 +57,12 @@ def send_message(self, topic: str, content: str): class AdminClient(BaseModel): - key: str + key: SecretStr admin: bool @classmethod def create(cls) -> Self: - return cls(key=secrets.token_urlsafe(), admin=True) + return cls(key=SecretStr(secrets.token_urlsafe()), admin=True) @field_validator("admin") def check_admin(cls, v: bool) -> bool: diff --git a/src/zulip_write_only_proxy/repositories.py b/src/zulip_write_only_proxy/repositories.py index 0c6c529d..4200d02e 100644 --- a/src/zulip_write_only_proxy/repositories.py +++ b/src/zulip_write_only_proxy/repositories.py @@ -2,7 +2,7 @@ from pathlib import Path import orjson -from pydantic import BaseModel, field_validator +from pydantic import BaseModel, SecretStr, field_validator from . import models @@ -20,14 +20,14 @@ def get(self, key: str) -> models.Client: client_data = data[key] if client_data.get("admin"): - return models.AdminClient(key=key, **client_data) + return models.AdminClient(key=SecretStr(key), **client_data) - return models.ScopedClient(key=key, **client_data) + return models.ScopedClient(key=SecretStr(key), **client_data) def put(self, client: models.ScopedClient) -> None: with file_lock: with self.path.open("rb") as f: - data = orjson.loads(f.read()) + data: dict[str, dict] = orjson.loads(f.read()) proposal_nos = [value.get("proposal_no") for value in data.values()] if client.proposal_no in proposal_nos: reversed_data = { @@ -40,7 +40,7 @@ def put(self, client: models.ScopedClient) -> None: f"{reversed_data[client.proposal_no]}" ) - data[client.key] = client.model_dump(exclude={"key"}) + data[client.key.get_secret_value()] = client.model_dump(exclude={"key"}) with self.path.open("wb") as f: f.write(orjson.dumps(data, option=orjson.OPT_INDENT_2)) @@ -48,8 +48,8 @@ def put(self, client: models.ScopedClient) -> None: def put_admin(self, client: models.AdminClient) -> None: with file_lock: with self.path.open("rb") as f: - data = orjson.loads(f.read()) - data[client.key] = client.model_dump(exclude={"key"}) + data: dict[str, dict] = orjson.loads(f.read()) + data[client.key.get_secret_value()] = client.model_dump(exclude={"key"}) with self.path.open("wb") as f: f.write(orjson.dumps(data, option=orjson.OPT_INDENT_2)) From 4b9566f4051e023a928673b2788eb76ab1220f4e Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Thu, 3 Aug 2023 12:17:06 +0200 Subject: [PATCH 046/143] test: add more tests --- tests/test_fastapi.py | 140 ++++++++++++++++++++----------------- tests/test_repositories.py | 20 +++--- 2 files changed, 87 insertions(+), 73 deletions(-) diff --git a/tests/test_fastapi.py b/tests/test_fastapi.py index 5f7e61bb..a6d8bf03 100644 --- a/tests/test_fastapi.py +++ b/tests/test_fastapi.py @@ -5,7 +5,8 @@ def test_send_message(fastapi_client, zulip_client): - zulip_client.send_message = MagicMock(return_value={"result": "success"}) + zulip_response = {"id": 42, "msg": "", "result": "success"} + zulip_client.send_message = MagicMock(return_value=zulip_response) response = fastapi_client.post( "/message", @@ -13,7 +14,7 @@ def test_send_message(fastapi_client, zulip_client): ) assert response.status_code == 200 - assert response.json() == {"result": "success"} + assert response.json() == zulip_response # Check that the zulip client was called with the expected arguments zulip_request = { @@ -25,111 +26,124 @@ def test_send_message(fastapi_client, zulip_client): zulip_client.send_message.assert_called_once_with(zulip_request) -def test_send_message_invalid_key(fastapi_client): - # Call the API endpoint with an invalid key +def test_send_message_unauthorised(fastapi_client): response = fastapi_client.post( "/message", headers={"X-API-key": "invalid_key"}, params={"topic": "Test Topic", "content": "Test Content"}, ) - # Check that the response has a 404 status code and the expected error message - assert response.status_code == 404 - assert response.json() == {"detail": "Key not found"} + assert response.status_code == 401 + assert response.json() == {"detail": "Unauthorised"} -def test_send_message_with_image(fastapi_client): - # Mock the services module - services.send_message = MagicMock(return_value={"result": "success"}) +def test_send_message_with_image(fastapi_client, zulip_client): + zulip_response_msg = {"id": 42, "msg": "", "result": "success"} + zulip_response_file = { + "msg": "", + "result": "success", + "uri": "/user_uploads/1/4e/m2A3MSqFnWRLUf9SaPzQ0Up_/zulip.txt", + } + + zulip_client.send_message = MagicMock(return_value=zulip_response_msg) + zulip_client.upload_file = MagicMock(return_value=zulip_response_file) - # Call the API endpoint with valid data and an image image = io.BytesIO(b"test image data") response = fastapi_client.post( "/message", - headers={"X-API-key": "test_key"}, params={"topic": "Test Topic", "content": "Test Content"}, files={"image": ("test.jpg", image)}, ) - # Check that the response has a 200 status code and the expected result assert response.status_code == 200 - assert response.json() == {"result": "success"} + assert response.json() == zulip_response_msg - # Check that the services module was called with the expected arguments - services.send_message.assert_called_once_with( - models.ScopedClient.create("test_key"), "Test Topic", "Test Content", image - ) + # Check that the zulip client was called with the expected arguments + zulip_request_msg = { + "type": "stream", + "to": "Test Stream 1", + "topic": "Test Topic", + "content": f"Test Content\n[]({zulip_response_file['uri']})", + } + zulip_client.send_message.assert_called_once_with(zulip_request_msg) + + zulip_client.upload_file.assert_called_once() # Call is made with spooled temp file object + uploaded_image = zulip_client.upload_file.call_args.args[0] + assert uploaded_image.name == "test.jpg" -def test_upload_image(fastapi_client): - # Mock the services module - services.upload_image = MagicMock(return_value={"uri": "/foo/bar.jpg"}) +def test_upload_image(fastapi_client, zulip_client): + zulip_response_file = { + "msg": "", + "result": "success", + "uri": "/user_uploads/1/4e/m2A3MSqFnWRLUf9SaPzQ0Up_/zulip.txt", + } + zulip_client.upload_file = MagicMock(return_value=zulip_response_file) - # Call the API endpoint with valid data image = io.BytesIO(b"test image data") response = fastapi_client.post( "/upload_image", - headers={"X-API-key": "test_key"}, files={"image": ("test.jpg", image)}, ) - # Check that the response has a 200 status code and the expected result assert response.status_code == 200 - assert response.json() == {"uri": "/foo/bar.jpg"} - - # Check that the services module was called with the expected arguments - services.upload_image.assert_called_once_with( - models.ScopedClient.create("test_key"), image - ) - - -def test_get_topics(fastapi_client): - # Mock the services module - services.list_topics = MagicMock(return_value=["Topic 1", "Topic 2"]) + assert response.json() == zulip_response_file + + zulip_client.upload_file.assert_called_once() # Call is made with spooled temp file object + uploaded_image = zulip_client.upload_file.call_args.args[0] + assert uploaded_image.name == "test.jpg" + + +def test_get_topics(fastapi_client, zulip_client): + zulip_response_id = {"msg": "", "result": "success", "stream_id": 15} + zulip_response_topics = { + "msg": "", + "result": "success", + "topics": [ + {"max_id": 26, "name": "Denmark3"}, + {"max_id": 23, "name": "Denmark1"}, + {"max_id": 6, "name": "Denmark2"}, + ], + } + zulip_client.get_stream_id = MagicMock(return_value=zulip_response_id) + zulip_client.get_stream_topics = MagicMock(return_value=zulip_response_topics) - # Call the API endpoint with valid data - response = fastapi_client.get("/get_topics", headers={"X-API-key": "test_key"}) + response = fastapi_client.get("/get_topics") - # Check that the response has a 200 status code and the expected result assert response.status_code == 200 - assert response.json() == ["Topic 1", "Topic 2"] + assert response.json() == zulip_response_topics - # Check that the services module was called with the expected arguments - services.list_topics.assert_called_once_with(models.ScopedClient.create("test_key")) +def test_get_topics_error(fastapi_client, zulip_client): + zulip_response_id = {"result": "error"} + zulip_client.get_stream_id = MagicMock(return_value=zulip_response_id) -def test_get_topics_error(fastapi_client): - # Mock the services module to raise an error - services.list_topics = MagicMock(side_effect=RuntimeError("Test Error")) + response = fastapi_client.get("/get_topics") - # Call the API endpoint with valid data - response = fastapi_client.get("/get_topics", headers={"X-API-key": "test_key"}) - - # Check that the response has a 400 status code and the expected error message assert response.status_code == 400 - assert response.json() == {"detail": "Test Error"} - - # Check that the services module was called with the expected arguments - services.list_topics.assert_called_once_with(models.ScopedClient.create("test_key")) + assert response.json() == { + "detail": ( + "Failed to get stream id for Test Stream 1. Is bot added to stream?\n" + f"Response was {zulip_response_id}" + ) + } -def test_create_client(fastapi_client): - # Mock the services module - services.create_client = MagicMock(return_value={"result": "success"}) +def test_create_client(fastapi_client, zulip_client): + zulip_client.create_client = MagicMock(return_value={"result": "success"}) - # Call the API endpoint with valid data response = fastapi_client.post( "/create_client", - headers={"X-API-key": "admin_key"}, + headers={"X-API-key": "admin1"}, params={"proposal_no": 1234, "stream": "Test Stream"}, ) - # Check that the response has a 200 status code and the expected result assert response.status_code == 200 - assert response.json() == {"result": "success"} - - # Check that the services module was called with the expected arguments - services.create_client.assert_called_once_with(1234, "Test Stream") + assert response.json() == { + "key": "**********", + "proposal_no": 1234, + "stream": "Test Stream", + } def test_create_client_error(fastapi_client): @@ -139,7 +153,7 @@ def test_create_client_error(fastapi_client): # Call the API endpoint with invalid data response = fastapi_client.post( "/create_client", - headers={"X-API-key": "admin_key"}, + headers={"X-API-key": "admin1"}, params={"proposal_no": 1234, "stream": "Test Stream"}, ) diff --git a/tests/test_repositories.py b/tests/test_repositories.py index 074a17ce..690759ee 100644 --- a/tests/test_repositories.py +++ b/tests/test_repositories.py @@ -20,7 +20,7 @@ def test_file_creation(): assert len(repository.list()) == 0 -def test_get_scoped_client(repository): +def test_get_scoped_client(repository: JSONRepository): result = repository.get("client1") assert isinstance(result, ScopedClient) @@ -31,23 +31,23 @@ def test_get_scoped_client(repository): repository.get("invalid") -def test_get_admin_client(repository): +def test_get_admin_client(repository: JSONRepository): result = repository.get("admin1") assert isinstance(result, AdminClient) assert result.admin is True -def test_put_scoped_client(repository): +def test_put_scoped_client(repository: JSONRepository): client = ScopedClient( - key="client3", + key="client3", # type: ignore stream="Test Stream 3", proposal_no=3, ) repository.put(client) - result = repository.get(client.key) + result = repository.get(client.key.get_secret_value()) assert isinstance(result, ScopedClient) assert result.stream == "Test Stream 3" @@ -57,18 +57,18 @@ def test_put_scoped_client(repository): repository.put(client) -def test_put_admin_client(repository): - client = AdminClient(key="admin2", admin=True) +def test_put_admin_client(repository: JSONRepository): + client = AdminClient(key="admin2", admin=True) # type: ignore repository.put_admin(client) - result = repository.get(client.key) + result = repository.get(client.key.get_secret_value()) assert isinstance(result, AdminClient) assert result.admin is True -def test_list_clients(repository): +def test_list_clients(repository: JSONRepository): result = repository.list() assert len(result) == 3 @@ -82,5 +82,5 @@ def test_list_clients(repository): assert result[1].proposal_no == 2 assert isinstance(result[2], AdminClient) - assert result[2].key == "admin1" + assert result[2].key.get_secret_value() == "admin1" assert result[2].admin is True From 00e602b18bf6fafdbfe6824f36e50bd9d7ddf568 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Thu, 3 Aug 2023 13:32:42 +0200 Subject: [PATCH 047/143] build(add): add ruff for linting/analysis --- poetry.lock | 85 +++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 30 ++++++++++++++++++ 2 files changed, 114 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 35df23fb..269b3340 100644 --- a/poetry.lock +++ b/poetry.lock @@ -293,6 +293,63 @@ doc = ["Sphinx (>=1.7.6,<2.dev0)", "sphinx-rtd-theme (>=0.1.9,<0.2.dev0)", "sphi e2e = ["canonicaljson (>=1.1,<2.0)", "python-olm (>=3.1,<4.0)"] test = ["pytest (>=4.6,<6.0.0)", "responses (>=0.10.6,<0.11.dev0)"] +[[package]] +name = "mypy" +version = "1.4.1" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8"}, + {file = "mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878"}, + {file = "mypy-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd"}, + {file = "mypy-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc"}, + {file = "mypy-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1"}, + {file = "mypy-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462"}, + {file = "mypy-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258"}, + {file = "mypy-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2"}, + {file = "mypy-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7"}, + {file = "mypy-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01"}, + {file = "mypy-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b"}, + {file = "mypy-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b"}, + {file = "mypy-1.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7"}, + {file = "mypy-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9"}, + {file = "mypy-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042"}, + {file = "mypy-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3"}, + {file = "mypy-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6"}, + {file = "mypy-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f"}, + {file = "mypy-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc"}, + {file = "mypy-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828"}, + {file = "mypy-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3"}, + {file = "mypy-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816"}, + {file = "mypy-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c"}, + {file = "mypy-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f"}, + {file = "mypy-1.4.1-py3-none-any.whl", hash = "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4"}, + {file = "mypy-1.4.1.tar.gz", hash = "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.1.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +python2 = ["typed-ast (>=1.4.0,<2)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + [[package]] name = "orjson" version = "3.9.2" @@ -592,6 +649,32 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "ruff" +version = "0.0.282" +description = "An extremely fast Python linter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.0.282-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:01b76309ddab16eb258dabc5e86e73e6542f59f3ea6b4ab886ecbcfc80ce062c"}, + {file = "ruff-0.0.282-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:e177cbb6dc0b1dbef5e999900d798b73e33602abf9b6c62d5d2cbe101026d931"}, + {file = "ruff-0.0.282-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5374b40b6d860d334d28678a53a92f0bf04b53acdf0395900361ad54ce71cd1d"}, + {file = "ruff-0.0.282-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d1ccbceb44e94fe2205b63996166e98a513a19ed23ec01d7193b7494b94ba30d"}, + {file = "ruff-0.0.282-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eee9c8c50bc77eb9c0811c91d9d67ff39fe4f394c2f44ada37dac6d45e50c9f1"}, + {file = "ruff-0.0.282-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:826e4de98e91450a6fe699a4e4a7cf33b9a90a2c5c270dc5b202241c37359ff8"}, + {file = "ruff-0.0.282-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d99758f8bbcb8f8da99acabf711ffad5e7a015247adf27211100b3586777fd56"}, + {file = "ruff-0.0.282-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3f30c9958ab9cb02bf0c574c629e87c19454cbbdb82750e49e3d1559a5a8f216"}, + {file = "ruff-0.0.282-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47a7a9366ab8e4ee20df9339bef172eec7b2e9e123643bf3ede005058f5b114e"}, + {file = "ruff-0.0.282-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1f05f5e6d6df6f8b1974c08f963c33f0a4d8cfa15cba12d35ca3ece8e9be5b1f"}, + {file = "ruff-0.0.282-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0710ea2cadc504b96c1d94c414a7802369d0fff2ab7c94460344bba69135cb40"}, + {file = "ruff-0.0.282-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2ca52536e1c7603fe4cbb5ad9dc141df47c3200df782f5ec559364716ea27f96"}, + {file = "ruff-0.0.282-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:aab9ed5bfba6b0a2242a7ec9a72858c802ceeaf0076fe72b2ad455639275f22c"}, + {file = "ruff-0.0.282-py3-none-win32.whl", hash = "sha256:f51bbb64f8f29e444c16d21b269ba82e25f8d536beda3df7c9fe1816297e508e"}, + {file = "ruff-0.0.282-py3-none-win_amd64.whl", hash = "sha256:bd25085c42ebaffe336ed7bda8a0ae7b6c454a5f386ec8b2299503f79bd12bdf"}, + {file = "ruff-0.0.282-py3-none-win_arm64.whl", hash = "sha256:f03fba9621533d67d7ab995847467d78b9337e3697779ef2cea6f1deaee5fbef"}, + {file = "ruff-0.0.282.tar.gz", hash = "sha256:ef677c26bae756e4c98af6d8972da83caea550bc92ffef97a6e939ca5b24ad06"}, +] + [[package]] name = "slipcover" version = "0.3.2" @@ -759,4 +842,4 @@ typing-extensions = ">=3.7" [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.12" -content-hash = "c122051357bcfcf780b8a8f79bd51a856b0dfa839598407dd00dbd370e776804" +content-hash = "8b7baf117451c5314450966a4f61db60ad4add365538fdf4fd43f023f18b906f" diff --git a/pyproject.toml b/pyproject.toml index 69fc221f..19934cd3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,9 +27,39 @@ httpx = "^0.24.1" [tool.poetry.group.dev.dependencies] poethepoet = "^0.21.1" +[tool.poetry.group.lint.dependencies] +ruff = "^0.0.282" +mypy = "^1.4.1" + +[tool.black] +line-length = 120 +target-version = ["py39", "py310", "py311", "py312"] + +[tool.ruff] +line-length = 120 +extend-select = [ + "I", # isort + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "PGH", # pygrep-hooks + "RUF", # ruff + "W", # pycodestyle + "YTT", # flake8-2020 +] +extend-ignore = ["B018", "B019"] +src = ["src"] +target-version = "py39" + +[tool.ruff.mccabe] +max-complexity = 10 + +[tool.ruff.isort] +known-first-party = ["zulip_write_only_proxy"] + [tool.poe.tasks] test = "python3 -m slipcover --source src -m pytest" serve = "uvicorn zulip_write_only_proxy.main:app --reload" +lint = "ruff check ./src ./tests" [build-system] requires = ["poetry-core"] From da59a582698820e4a79b8195e697cafd48365638 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Thu, 3 Aug 2023 13:33:04 +0200 Subject: [PATCH 048/143] build(add): add pyright, update lint command --- poetry.lock | 50 +++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 7 ++++++- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/poetry.lock b/poetry.lock index 269b3340..ff325e3c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -350,6 +350,20 @@ files = [ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] +[[package]] +name = "nodeenv" +version = "1.8.0" +description = "Node.js virtual environment builder" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +files = [ + {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, + {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, +] + +[package.dependencies] +setuptools = "*" + [[package]] name = "orjson" version = "3.9.2" @@ -592,6 +606,24 @@ files = [ [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +[[package]] +name = "pyright" +version = "1.1.320" +description = "Command line wrapper for pyright" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyright-1.1.320-py3-none-any.whl", hash = "sha256:cdf739f7827374575cefb134311457d08c189793c3da3097c023debec9acaedb"}, + {file = "pyright-1.1.320.tar.gz", hash = "sha256:94223e3b82d0b4c99c5125bab4d628ec749ce0b5b2ae0471fd4921dd4eff460d"}, +] + +[package.dependencies] +nodeenv = ">=1.6.0" + +[package.extras] +all = ["twine (>=3.4.1)"] +dev = ["twine (>=3.4.1)"] + [[package]] name = "pytest" version = "7.4.0" @@ -675,6 +707,22 @@ files = [ {file = "ruff-0.0.282.tar.gz", hash = "sha256:ef677c26bae756e4c98af6d8972da83caea550bc92ffef97a6e939ca5b24ad06"}, ] +[[package]] +name = "setuptools" +version = "68.0.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, + {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, +] + +[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-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "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-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "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 = "slipcover" version = "0.3.2" @@ -842,4 +890,4 @@ typing-extensions = ">=3.7" [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.12" -content-hash = "8b7baf117451c5314450966a4f61db60ad4add365538fdf4fd43f023f18b906f" +content-hash = "bb3744ef933ea6a1f45cc7f317353c8a23bc4432aa2ae8f6a2870132e4f5f5d1" diff --git a/pyproject.toml b/pyproject.toml index 19934cd3..d1f03666 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,7 @@ poethepoet = "^0.21.1" [tool.poetry.group.lint.dependencies] ruff = "^0.0.282" mypy = "^1.4.1" +pyright = "^1.1.320" [tool.black] line-length = 120 @@ -59,7 +60,11 @@ known-first-party = ["zulip_write_only_proxy"] [tool.poe.tasks] test = "python3 -m slipcover --source src -m pytest" serve = "uvicorn zulip_write_only_proxy.main:app --reload" -lint = "ruff check ./src ./tests" +lint = [ + { cmd = "ruff check ./src ./tests" }, + { cmd = "mypy ./src ./tests" }, + { cmd = "pyright ./src ./tests" }, +] [build-system] requires = ["poetry-core"] From 8fae04a5639c8575dff2068e42b9dad9a206817d Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Thu, 3 Aug 2023 13:33:20 +0200 Subject: [PATCH 049/143] refactor: use FastAPI Annotated style instead of kwarg style --- src/zulip_write_only_proxy/main.py | 34 +++++++++++++++--------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index 50811806..8d44ab1c 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -1,6 +1,6 @@ from contextlib import asynccontextmanager from tempfile import SpooledTemporaryFile -from typing import Union +from typing import Annotated, Union import fastapi from fastapi.security import APIKeyHeader @@ -19,7 +19,7 @@ async def lifespan(app: fastapi.FastAPI): api_key_header = APIKeyHeader(name="X-API-key") -def get_client(key: str = fastapi.Security(api_key_header)) -> models.Client: +def get_client(key: Annotated[str, fastapi.Security(api_key_header)]) -> models.Client: try: return services.get_client(key) except KeyError as e: @@ -35,16 +35,16 @@ def get_client(key: str = fastapi.Security(api_key_header)) -> models.Client: response_description=f"See {_docs_url}", ) def send_message( - client: models.ScopedClient = fastapi.Depends(get_client), - topic: str = fastapi.Query(...), - content: str = fastapi.Query(...), - image: fastapi.UploadFile = fastapi.File(None), + client: Annotated[models.ScopedClient, fastapi.Depends(get_client)], + topic: Annotated[str, fastapi.Query(...)], + content: Annotated[str, fastapi.Query(...)], + image: Annotated[fastapi.UploadFile, fastapi.File(None)], ): if image: # Some screwing around to get the spooled tmp file to act more like a real file # since Zulip needs it to have a filename - f: SpooledTemporaryFile = image.file # type: ignore - f._file.name = image.filename # type: ignore + f: SpooledTemporaryFile = image.file # type: ignore[assignment] + f._file.name = image.filename # type: ignore[attr-defined] result = client.upload_image(f) @@ -62,11 +62,11 @@ def send_message( response_description=f"See {_docs_url}", ) def upload_image( - client: models.ScopedClient = fastapi.Depends(get_client), - image: fastapi.UploadFile = fastapi.File(...), + client: Annotated[models.ScopedClient, fastapi.Depends(get_client)], + image: Annotated[fastapi.UploadFile, fastapi.File(...)], ): - f: SpooledTemporaryFile = image.file # type: ignore - f._file.name = image.filename # type: ignore + f: SpooledTemporaryFile = image.file # type: ignore[assignment] + f._file.name = image.filename # type: ignore[attr-defined] return client.upload_image(f) @@ -80,7 +80,7 @@ def upload_image( response_description=f"See {_docs_url}", ) def get_topics( - client: models.ScopedClient = fastapi.Depends(get_client), + client: Annotated[models.ScopedClient, fastapi.Depends(get_client)], ): try: return client.list_topics() @@ -90,16 +90,16 @@ def get_topics( @app.get("/me", tags=["User"]) def get_me( - client: models.ScopedClient = fastapi.Depends(get_client), + client: Annotated[models.ScopedClient, fastapi.Depends(get_client)], ) -> models.ScopedClient: return client @app.post("/create_client", tags=["Admin"]) def create_client( - admin_client: models.AdminClient = fastapi.Depends(get_client), - proposal_no: int = fastapi.Query(...), - stream: Union[str, None] = fastapi.Query(None), + admin_client: Annotated[models.AdminClient, fastapi.Depends(get_client)], + proposal_no: Annotated[int, fastapi.Query(...)], + stream: Annotated[Union[str, None], fastapi.Query(None)], ): try: return services.create_client(proposal_no, stream) From 2b15cc3910eaca3be333fe5c1fd774503aa00d3b Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Thu, 3 Aug 2023 13:33:30 +0200 Subject: [PATCH 050/143] fix: lint/type issues --- tests/conftest.py | 2 +- tests/test_fastapi.py | 2 +- tests/test_models.py | 4 ++-- tests/test_repositories.py | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index da72158f..cf815dd7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -53,7 +53,7 @@ def zulip_client(): @pytest.fixture def fastapi_client(repository, zulip_client): - from zulip_write_only_proxy import services, main + from zulip_write_only_proxy import main, services services.setup() diff --git a/tests/test_fastapi.py b/tests/test_fastapi.py index a6d8bf03..a87047d9 100644 --- a/tests/test_fastapi.py +++ b/tests/test_fastapi.py @@ -1,7 +1,7 @@ import io from unittest.mock import MagicMock -from zulip_write_only_proxy import models, services +from zulip_write_only_proxy import services def test_send_message(fastapi_client, zulip_client): diff --git a/tests/test_models.py b/tests/test_models.py index 1fb24c1c..a89f9b8c 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -68,9 +68,9 @@ def test_admin_client_create(): def test_admin_client_init(): - result = AdminClient(key="", admin=True) + result = AdminClient(key="", admin=True) # type: ignore[assignment] assert result.admin with pytest.raises(ValueError): - AdminClient(key="", admin=False) + AdminClient(key="", admin=False) # type: ignore[assignment] diff --git a/tests/test_repositories.py b/tests/test_repositories.py index 690759ee..6b7e4e65 100644 --- a/tests/test_repositories.py +++ b/tests/test_repositories.py @@ -40,7 +40,7 @@ def test_get_admin_client(repository: JSONRepository): def test_put_scoped_client(repository: JSONRepository): client = ScopedClient( - key="client3", # type: ignore + key="client3", # type: ignore[arg-type] stream="Test Stream 3", proposal_no=3, ) @@ -58,7 +58,7 @@ def test_put_scoped_client(repository: JSONRepository): def test_put_admin_client(repository: JSONRepository): - client = AdminClient(key="admin2", admin=True) # type: ignore + client = AdminClient(key="admin2", admin=True) # type: ignore[arg-type] repository.put_admin(client) From 3eebbe3274b55e02fe34f1c70d0db44610af364b Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Thu, 3 Aug 2023 13:45:29 +0200 Subject: [PATCH 051/143] build: switch to pytest-cov --- poetry.lock | 134 ++++++++++++++++++++++++++++++++++--------------- pyproject.toml | 4 +- 2 files changed, 96 insertions(+), 42 deletions(-) diff --git a/poetry.lock b/poetry.lock index ff325e3c..75600029 100644 --- a/poetry.lock +++ b/poetry.lock @@ -152,6 +152,81 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "coverage" +version = "7.2.7" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"}, + {file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"}, + {file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"}, + {file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"}, + {file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"}, + {file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"}, + {file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"}, + {file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"}, + {file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"}, + {file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"}, + {file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"}, + {file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"}, + {file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"}, + {file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"}, + {file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"}, + {file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"}, + {file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"}, + {file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"}, + {file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"}, + {file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"}, + {file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"}, + {file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"}, + {file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"}, + {file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + [[package]] name = "distro" version = "1.8.0" @@ -646,6 +721,24 @@ tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-cov" +version = "4.1.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, + {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + [[package]] name = "python-multipart" version = "0.0.6" @@ -723,31 +816,6 @@ docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-g testing = ["build[virtualenv]", "filelock (>=3.4.0)", "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-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "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 = "slipcover" -version = "0.3.2" -description = "Near Zero-Overhead Python Code Coverage" -optional = false -python-versions = ">=3.8,<3.12" -files = [ - {file = "slipcover-0.3.2-cp310-cp310-macosx_11_7_universal2.whl", hash = "sha256:2ef0d99f81dada328223089bd3b2f94d6f2076211ce634633d6eeee59676452a"}, - {file = "slipcover-0.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d10a02e1dc198c4111baa7c74a7754d8a1fce28ff1e16aeb3fe7ca15390c0372"}, - {file = "slipcover-0.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:dbe90e413297c0782e5a50f6507ef22974ce0c36481af80d1edf2589978e7f5f"}, - {file = "slipcover-0.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:49c7e78411c883e913ffc30b71929e0af4dd4cd4dc055b7ca7052eaab97d83a4"}, - {file = "slipcover-0.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f75e027cffdae84f25e9f8a8c7d1b1dc65fe6ce4d1bf5c5703fe062e6bd57ff3"}, - {file = "slipcover-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:f7e004343b14ccc203af663ed10971f4516aa239ecca8b1c99c2ad47fcb9c7ae"}, - {file = "slipcover-0.3.2-cp38-cp38-macosx_11_7_universal2.whl", hash = "sha256:fcdca53eae06c7518887d553c4ca7dc62ea80407344c958a58d903729e8b6662"}, - {file = "slipcover-0.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eda2d1dfe550857cc097a0cd2f2934c26ec196ac27dd791cd7a7834bf4b203a"}, - {file = "slipcover-0.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:71a6045d634a42346af44b7d7f09eb2a994d45e7fa46e54200a79faa9af7970d"}, - {file = "slipcover-0.3.2-cp39-cp39-macosx_11_7_universal2.whl", hash = "sha256:0e755c0e26a852fd1b786a9837a7d5f723e3d135637e57d4ba101b3790fd9435"}, - {file = "slipcover-0.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a537f16279e4e99c8b9ab38c86e656474c0cd1cb94997c8c519bc9777e8e2dc"}, - {file = "slipcover-0.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:52523889926a3a9a16323dab8bec5097bc512302b4af23eee42c5be2ebdd1653"}, - {file = "slipcover-0.3.2.tar.gz", hash = "sha256:fd095928a24521bf54614778fb7d469121953928f53e8fe752570cf10888c475"}, -] - -[package.dependencies] -tabulate = "*" - [[package]] name = "sniffio" version = "1.3.0" @@ -777,20 +845,6 @@ typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\"" [package.extras] full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] -[[package]] -name = "tabulate" -version = "0.9.0" -description = "Pretty-print tabular data" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, - {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, -] - -[package.extras] -widechars = ["wcwidth"] - [[package]] name = "tomli" version = "2.0.1" @@ -890,4 +944,4 @@ typing-extensions = ">=3.7" [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.12" -content-hash = "bb3744ef933ea6a1f45cc7f317353c8a23bc4432aa2ae8f6a2870132e4f5f5d1" +content-hash = "b08ba6cdd2611addd83269efe303769abd8b7cca37de24886eeb4c4f3131a92e" diff --git a/pyproject.toml b/pyproject.toml index d1f03666..a813bf3f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,9 +20,9 @@ pydantic = "^2.1.1" typer = "^0.9.0" [tool.poetry.group.test.dependencies] -slipcover = "^0.3.2" pytest = "^7.4.0" httpx = "^0.24.1" +pytest-cov = "^4.1.0" [tool.poetry.group.dev.dependencies] poethepoet = "^0.21.1" @@ -58,7 +58,7 @@ max-complexity = 10 known-first-party = ["zulip_write_only_proxy"] [tool.poe.tasks] -test = "python3 -m slipcover --source src -m pytest" +test = "python3 -m pytest --cov=zulip_write_only_proxy --cov-report=term-missing --cov-report xml --cov-branch" serve = "uvicorn zulip_write_only_proxy.main:app --reload" lint = [ { cmd = "ruff check ./src ./tests" }, From 644e4ca7956ee5ca8b8f710bbbb47739976908af Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Thu, 3 Aug 2023 13:45:37 +0200 Subject: [PATCH 052/143] fix: incorrect annotation --- src/zulip_write_only_proxy/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index 8d44ab1c..bc9501f7 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -38,7 +38,7 @@ def send_message( client: Annotated[models.ScopedClient, fastapi.Depends(get_client)], topic: Annotated[str, fastapi.Query(...)], content: Annotated[str, fastapi.Query(...)], - image: Annotated[fastapi.UploadFile, fastapi.File(None)], + image: Annotated[Union[fastapi.UploadFile, None], fastapi.File()] = None, ): if image: # Some screwing around to get the spooled tmp file to act more like a real file @@ -99,7 +99,7 @@ def get_me( def create_client( admin_client: Annotated[models.AdminClient, fastapi.Depends(get_client)], proposal_no: Annotated[int, fastapi.Query(...)], - stream: Annotated[Union[str, None], fastapi.Query(None)], + stream: Annotated[Union[str, None], fastapi.Query()] = None, ): try: return services.create_client(proposal_no, stream) From d54a4dd70b71f8425eca7ee0a8936a6468e20e71 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Thu, 3 Aug 2023 14:10:25 +0200 Subject: [PATCH 053/143] build: split up lint tasks --- pyproject.toml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a813bf3f..e060f4ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,13 +58,14 @@ max-complexity = 10 known-first-party = ["zulip_write_only_proxy"] [tool.poe.tasks] -test = "python3 -m pytest --cov=zulip_write_only_proxy --cov-report=term-missing --cov-report xml --cov-branch" serve = "uvicorn zulip_write_only_proxy.main:app --reload" -lint = [ - { cmd = "ruff check ./src ./tests" }, - { cmd = "mypy ./src ./tests" }, - { cmd = "pyright ./src ./tests" }, -] + +test = "python3 -m pytest --cov=zulip_write_only_proxy --cov-report=term-missing --cov-report xml --cov-branch" + +lint = ["ruff", "mypy", "pyright"] +ruff = "ruff check ./src ./tests" +mypy = "mypy ./src ./tests" +pyright = "pyright ./src ./tests" [build-system] requires = ["poetry-core"] From 932a3ceb285877cac8cadc41a7b7cce27f932a05 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Thu, 3 Aug 2023 14:10:48 +0200 Subject: [PATCH 054/143] ci(test): create tests.yml --- .github/workflows/tests.yml | 44 +++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..8945f174 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,44 @@ +name: Tests + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +jobs: + tests: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.8", "3.9", "3.10"] + steps: + - name: Checkout project + uses: actions/checkout@v3 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + + - name: Set up Poetry + run: | + python -m pip install --upgrade pip poetry + poetry config virtualenvs.create true --local + poetry config virtualenvs.in-project true --local + + - name: Use venv cache + uses: actions/cache@v3 + with: + path: ./.venv + key: venv-${{ hashFiles('poetry.lock') }} + + - name: Install project + run: poetry install + + - name: Run tests + run: poe test From 099422a74a84eaf097b14c526d2eb57504544425 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Thu, 3 Aug 2023 14:13:17 +0200 Subject: [PATCH 055/143] ci(lint): create lint.yml --- .github/workflows/lint.yml | 50 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 .github/workflows/lint.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..d178aaf8 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,50 @@ +name: lint + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +jobs: + lint: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.8", "3.9", "3.10"] + steps: + - name: Checkout project + uses: actions/checkout@v3 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + + - name: Set up Poetry + run: | + python -m pip install --upgrade pip poetry + poetry config virtualenvs.create true --local + poetry config virtualenvs.in-project true --local + + - name: Use venv cache + uses: actions/cache@v3 + with: + path: ./.venv + key: venv-${{ hashFiles('poetry.lock') }} + + - name: Install project + run: poetry install + + - name: Lint - ruff + run: poe ruff + + - name: Lint - mypy + run: poe mypy + + - name: Lint - pyright + run: poe pyright From f57b51155dda399369c0e3dd1b66e639aed070a1 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Thu, 3 Aug 2023 14:14:22 +0200 Subject: [PATCH 056/143] ci(style): consistent action names --- .github/workflows/lint.yml | 68 ++++++++++++++++++------------------- .github/workflows/test.yml | 44 ++++++++++++++++++++++++ .github/workflows/tests.yml | 44 ------------------------ 3 files changed, 78 insertions(+), 78 deletions(-) create mode 100644 .github/workflows/test.yml delete mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d178aaf8..27eac1bd 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,10 +1,9 @@ -name: lint +name: Lint on: push: branches: [main] pull_request: - workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }} @@ -15,36 +14,37 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10"] + python-version: ["3.9"] steps: - - name: Checkout project - uses: actions/checkout@v3 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 - with: - python-version: ${{ matrix.python-version }} - - - name: Set up Poetry - run: | - python -m pip install --upgrade pip poetry - poetry config virtualenvs.create true --local - poetry config virtualenvs.in-project true --local - - - name: Use venv cache - uses: actions/cache@v3 - with: - path: ./.venv - key: venv-${{ hashFiles('poetry.lock') }} - - - name: Install project - run: poetry install - - - name: Lint - ruff - run: poe ruff - - - name: Lint - mypy - run: poe mypy - - - name: Lint - pyright - run: poe pyright + - name: Checkout project + uses: actions/checkout@v3 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + + - name: Set up Poetry + run: | + python -m pip install --upgrade pip poetry + poetry config virtualenvs.create true --local + poetry config virtualenvs.in-project true --local + + - name: Use venv cache + uses: actions/cache@v3 + with: + path: ./.venv + key: venv-${{ hashFiles('poetry.lock') }}-${{ matrix.python-version }} + + - name: Install project + run: | + poetry --no-ansi --no-interaction install + + - name: Lint - ruff + run: poetry run poe ruff + + - name: Lint - mypy + run: poetry run poe mypy + + - name: Lint - pyright + run: poetry run poe pyright diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..eae0fac4 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,44 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.9", "3.11", "3.12-dev"] + steps: + - name: Checkout project + uses: actions/checkout@v3 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + + - name: Set up Poetry + run: | + python -m pip install --upgrade pip poetry + poetry config virtualenvs.create true --local + poetry config virtualenvs.in-project true --local + + - name: Use venv cache + uses: actions/cache@v3 + with: + path: ./.venv + key: venv-${{ hashFiles('poetry.lock') }}-${{ matrix.python-version }} + + - name: Install project + run: | + poetry --no-ansi --no-interaction install + + - name: Lint - ruff + run: poetry run poe test diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index 8945f174..00000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Tests - -on: - push: - branches: [main] - pull_request: - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }} - cancel-in-progress: true - -jobs: - tests: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.8", "3.9", "3.10"] - steps: - - name: Checkout project - uses: actions/checkout@v3 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 - with: - python-version: ${{ matrix.python-version }} - - - name: Set up Poetry - run: | - python -m pip install --upgrade pip poetry - poetry config virtualenvs.create true --local - poetry config virtualenvs.in-project true --local - - - name: Use venv cache - uses: actions/cache@v3 - with: - path: ./.venv - key: venv-${{ hashFiles('poetry.lock') }} - - - name: Install project - run: poetry install - - - name: Run tests - run: poe test From 8c6bbc1b2150cd1332115be9f9c91469de800fcc Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Thu, 3 Aug 2023 20:02:28 +0200 Subject: [PATCH 057/143] fix: test issue when no zuliprc file present --- tests/conftest.py | 2 +- tests/test_fastapi.py | 27 ++++++++++++--------------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index cf815dd7..a48d00cc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -44,7 +44,7 @@ def scoped_client(): return ScopedClient.create(1234, stream="Test Stream") -@pytest.fixture +@pytest.fixture(autouse=True) def zulip_client(): with patch("zulip.Client", new_callable=MagicMock) as mock_class: mock_class.return_value = mock_class diff --git a/tests/test_fastapi.py b/tests/test_fastapi.py index a87047d9..8ed9883d 100644 --- a/tests/test_fastapi.py +++ b/tests/test_fastapi.py @@ -1,5 +1,5 @@ import io -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch from zulip_write_only_proxy import services @@ -147,19 +147,16 @@ def test_create_client(fastapi_client, zulip_client): def test_create_client_error(fastapi_client): - # Mock the services module to raise an error - services.create_client = MagicMock(side_effect=ValueError("Test Error")) - - # Call the API endpoint with invalid data - response = fastapi_client.post( - "/create_client", - headers={"X-API-key": "admin1"}, - params={"proposal_no": 1234, "stream": "Test Stream"}, - ) + with patch("zulip_write_only_proxy.services.create_client", MagicMock(side_effect=ValueError("Test Error"))): + # Call the API endpoint with invalid data + response = fastapi_client.post( + "/create_client", + headers={"X-API-key": "admin1"}, + params={"proposal_no": 1234, "stream": "Test Stream"}, + ) - # Check that the response has a 400 status code and the expected error message - assert response.status_code == 400 - assert response.json() == {"detail": "Test Error"} + assert response.status_code == 400 + assert response.json() == {"detail": "Test Error"} - # Check that the services module was called with the expected arguments - services.create_client.assert_called_once_with(1234, "Test Stream") + # Check that the services module was called with the expected arguments + services.create_client.assert_called_once_with(1234, "Test Stream") From 868631f76512b9e08c24b545c8c695068ac62340 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Thu, 3 Aug 2023 20:02:53 +0200 Subject: [PATCH 058/143] refactor: import module not funcs, add annotations --- tests/test_services.py | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/tests/test_services.py b/tests/test_services.py index 7469a527..74807c99 100644 --- a/tests/test_services.py +++ b/tests/test_services.py @@ -1,44 +1,39 @@ -from unittest.mock import patch +from unittest.mock import MagicMock, patch import pytest from zulip_write_only_proxy.models import AdminClient, ScopedClient -from zulip_write_only_proxy.services import ( - create_admin, - create_client, - get_client, - list_clients, - setup, -) +from zulip_write_only_proxy.repositories import JSONRepository +from zulip_write_only_proxy import services -def test_create_client(repository): - result = create_client(proposal_no=3) +def test_create_client(repository: JSONRepository): + result = services.create_client(proposal_no=3) assert isinstance(result, ScopedClient) -def test_create_admin(repository): - result = create_admin() +def test_create_admin(repository: JSONRepository): + result = services.create_admin() assert isinstance(result, AdminClient) -def test_get_client(repository): - result = get_client("client1") +def test_get_client(repository: JSONRepository): + result = services.get_client("client1") assert isinstance(result, ScopedClient) assert result.stream == "Test Stream 1" assert result.proposal_no == 1 with pytest.raises(KeyError): - get_client("invalid") + services.get_client("invalid") -def test_list_clients(repository): - result = list_clients() +def test_list_clients(repository: JSONRepository): + result = services.list_clients() assert len(result) == 3 -def test_setup(zulip_client): +def test_setup(zulip_client: MagicMock): from pydantic.fields import ModelPrivateAttr from pydantic_core import PydanticUndefinedType @@ -47,14 +42,14 @@ def test_setup(zulip_client): assert isinstance(ScopedClient._client.default, PydanticUndefinedType) assert ScopedClient._client.default_factory is None - setup() + services.setup() assert not isinstance( ScopedClient._client.default_factory, PydanticUndefinedType ) -def test_setup_raises(zulip_client): +def test_setup_raises(zulip_client: MagicMock): with patch.object(ScopedClient, "_client", 1234): with pytest.raises(RuntimeError): - setup() + services.setup() From 4d630884d74a9202952ad0907f3cac7508366b57 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Thu, 3 Aug 2023 20:04:02 +0200 Subject: [PATCH 059/143] style: fix formatting --- tests/test_services.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_services.py b/tests/test_services.py index 74807c99..82970b02 100644 --- a/tests/test_services.py +++ b/tests/test_services.py @@ -2,9 +2,9 @@ import pytest +from zulip_write_only_proxy import services from zulip_write_only_proxy.models import AdminClient, ScopedClient from zulip_write_only_proxy.repositories import JSONRepository -from zulip_write_only_proxy import services def test_create_client(repository: JSONRepository): From e311214200c818ff7b4aa53515089b7cabdd7c90 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Thu, 3 Aug 2023 20:10:49 +0200 Subject: [PATCH 060/143] ci(test): upload test coverage --- .github/workflows/test.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index eae0fac4..463d2dc5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -40,5 +40,10 @@ jobs: run: | poetry --no-ansi --no-interaction install - - name: Lint - ruff + - name: Test run: poetry run poe test + + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v3 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} From 435729d88d9b03168c6478a3a6c133a1ecf95c0c Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Fri, 4 Aug 2023 10:25:52 +0200 Subject: [PATCH 061/143] ci(docker): add ci to build and push docker image --- .github/workflows/build-image.yml | 59 +++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 .github/workflows/build-image.yml diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml new file mode 100644 index 00000000..e56bff5b --- /dev/null +++ b/.github/workflows/build-image.yml @@ -0,0 +1,59 @@ +name: Build Image + +on: + push: + branches: + - "**" + tags: + - "v**" + pull_request: + branches: + - "main" + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push-image: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Log in to the Container registry + uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=sha + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Build and push Docker image + uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 + with: + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} From 489f2c7aac4b14533bfa4be0952db8dcbc4e0851 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Fri, 4 Aug 2023 11:28:47 +0200 Subject: [PATCH 062/143] chore(deploy): configure auto update and load balancer --- docker-compose.yml | 28 +++++++++++++++++++++++++--- nginx.conf | 11 +++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 nginx.conf diff --git a/docker-compose.yml b/docker-compose.yml index 5f650415..bdffb051 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,30 @@ services: zwop: - build: ./ - ports: - - "${PORT:-8000}:8000" + image: ghcr.io/robertrosca/zulip-write-only-proxy:feat-initial-dev + deploy: + replicas: 2 + update_config: + parallelism: 1 + delay: 10s + order: start-first + monitor: 1s + failure_action: pause volumes: - ./config:/app/config + labels: + - "com.centurylinklabs.watchtower.enable=true" + - "com.centurylinklabs.watchtower.scope=zwop" + + nginx: + image: nginx:latest + ports: + - "${PORT:-8000}:80" + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf + + watchtower: + image: containrrr/watchtower + volumes: + - /var/run/docker.sock:/var/run/docker.sock + command: --interval 60 --label-enable --scope zwop --rolling-restart diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 00000000..440a2ac0 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,11 @@ +events {} + +http { + server { + listen 80; + + location / { + proxy_pass http://zwop:8000; + } + } +} From c3d943d7be4ea8a54d5a23640f045b2bda28ff41 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Fri, 4 Aug 2023 11:38:17 +0200 Subject: [PATCH 063/143] chore(deploy): remove watchtower, use compose 'update_config' --- docker-compose.yml | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index bdffb051..d3e3c628 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,3 +1,5 @@ +version: "3.8" + services: zwop: @@ -7,14 +9,10 @@ services: update_config: parallelism: 1 delay: 10s - order: start-first monitor: 1s failure_action: pause volumes: - ./config:/app/config - labels: - - "com.centurylinklabs.watchtower.enable=true" - - "com.centurylinklabs.watchtower.scope=zwop" nginx: image: nginx:latest @@ -22,9 +20,3 @@ services: - "${PORT:-8000}:80" volumes: - ./nginx.conf:/etc/nginx/nginx.conf - - watchtower: - image: containrrr/watchtower - volumes: - - /var/run/docker.sock:/var/run/docker.sock - command: --interval 60 --label-enable --scope zwop --rolling-restart From 78e2221203fdc18b791e26cec7c310f5eb24f5b4 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Fri, 4 Aug 2023 11:39:20 +0200 Subject: [PATCH 064/143] feat(endpoint): add health endpoint --- Dockerfile | 2 ++ src/zulip_write_only_proxy/main.py | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/Dockerfile b/Dockerfile index 4e4048a1..4a7367cf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,3 +14,5 @@ RUN --mount=type=cache,target=/root/.cache\ poetry install CMD ["uvicorn", "zulip_write_only_proxy.main:app", "--host", "0.0.0.0", "--port", "8000"] + +HEALTHCHECK CMD curl --fail http://localhost:8000/health || exit 1 diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index bc9501f7..58ee1a95 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -94,6 +94,10 @@ def get_me( ) -> models.ScopedClient: return client +@app.get("/health") +def healthcheck(): + return "OK" + @app.post("/create_client", tags=["Admin"]) def create_client( From 398c0542dcc03dafcf438889cadd7d13aea50779 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Fri, 4 Aug 2023 11:50:57 +0200 Subject: [PATCH 065/143] chore(deploy): change docker compose update config --- docker-compose.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index d3e3c628..ed4384d0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,8 +9,10 @@ services: update_config: parallelism: 1 delay: 10s - monitor: 1s - failure_action: pause + failure_action: rollback + order: start-first + monitor: 5s + max_failure_ratio: 0 volumes: - ./config:/app/config From cb30aa4f71102ca66b9bcb2dadc60d7b1137c106 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Fri, 4 Aug 2023 11:57:15 +0200 Subject: [PATCH 066/143] docs: add deployment section --- README.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d5054823..545d7c49 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ async with httpx.AsyncClient() as client: response = await client.post(url, headers=headers, params=params, files=files) ``` -## Server Setup and Development +## Development Setup For docker: @@ -125,3 +125,17 @@ Default configuration is something like: ``` Stream/topic can be edited manually in the JSON file or set via CLI at creation time. + +## Deployment Setup + +Deployment is similar to development with `docker compose`, but instead a docker stack is used to allow for better scaling and update configuration. + +Deployment is done with: + +```sh +docker stack deploy -c docker-compose.yml zwop +``` + +To update the stack, use the same command. This will pull in the latest image and perform a rolling restart of the service, which will first start the new container, wait for a successful health check, and then stop the old container. + +A cron job runs the deployment command every minute to check for updates. From 1d5c56e3337a067919dbac1f545922f587120b54 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Fri, 4 Aug 2023 12:45:37 +0200 Subject: [PATCH 067/143] chore(deploy): add nginx healthcheck --- docker-compose.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index ed4384d0..d25e10ae 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,3 +22,5 @@ services: - "${PORT:-8000}:80" volumes: - ./nginx.conf:/etc/nginx/nginx.conf + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost/health"] From aa7e3452c8b836e581e57315d9d66d068a87cb7b Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Fri, 4 Aug 2023 13:03:02 +0200 Subject: [PATCH 068/143] chore(deploy): pin nginx version --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index d25e10ae..dce1a5c4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,7 +17,7 @@ services: - ./config:/app/config nginx: - image: nginx:latest + image: nginx:1.25.1 ports: - "${PORT:-8000}:80" volumes: From 681b3705afab5ed61f6e4591a358d3ddd21eb7bd Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Fri, 4 Aug 2023 13:51:01 +0200 Subject: [PATCH 069/143] ci(docker): do not build image on pr trigger --- .github/workflows/build-image.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml index e56bff5b..d54aebbc 100644 --- a/.github/workflows/build-image.yml +++ b/.github/workflows/build-image.yml @@ -6,9 +6,6 @@ on: - "**" tags: - "v**" - pull_request: - branches: - - "main" env: REGISTRY: ghcr.io From 26fea8b8b1d7579ec7e8cabcd10a556e0a49a062 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Fri, 4 Aug 2023 14:03:09 +0200 Subject: [PATCH 070/143] build(add): add commitizen --- .pre-commit-config.yaml | 8 ++ poetry.lock | 277 +++++++++++++++++++++++++++++++++++++++- pyproject.toml | 9 ++ 3 files changed, 293 insertions(+), 1 deletion(-) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..a5dfcbcf --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,8 @@ +repos: +- hooks: + - id: commitizen + - id: commitizen-branch + stages: + - push + repo: https://github.com/commitizen-tools/commitizen + rev: v2.42.1 diff --git a/poetry.lock b/poetry.lock index 75600029..55233a9d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -32,6 +32,20 @@ doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd- test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] trio = ["trio (<0.22)"] +[[package]] +name = "argcomplete" +version = "3.1.1" +description = "Bash tab completion for argparse" +optional = false +python-versions = ">=3.6" +files = [ + {file = "argcomplete-3.1.1-py3-none-any.whl", hash = "sha256:35fa893a88deea85ea7b20d241100e64516d6af6d7b0ae2bed1d263d26f70948"}, + {file = "argcomplete-3.1.1.tar.gz", hash = "sha256:6c4c563f14f01440aaffa3eae13441c5db2357b5eec639abe7c0b15334627dff"}, +] + +[package.extras] +test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] + [[package]] name = "certifi" version = "2023.7.22" @@ -152,6 +166,30 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "commitizen" +version = "3.6.0" +description = "Python commitizen client tool" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "commitizen-3.6.0-py3-none-any.whl", hash = "sha256:4414724b306b252d08a5ae6701401c65cd53554d446c0cc4cedcae7ab8591a5a"}, + {file = "commitizen-3.6.0.tar.gz", hash = "sha256:979f659f9fc071c675f41796bb7c56a827aacc2e312db4ec3920951211a72ce3"}, +] + +[package.dependencies] +argcomplete = ">=1.12.1,<3.2" +charset-normalizer = ">=2.1.0,<4" +colorama = ">=0.4.1,<0.5.0" +decli = ">=0.6.0,<0.7.0" +importlib_metadata = ">=4.13,<7" +jinja2 = ">=2.10.3" +packaging = ">=19" +pyyaml = ">=3.08" +questionary = ">=1.4.0,<2.0.0" +termcolor = ">=1.1,<3" +tomlkit = ">=0.5.3,<1.0.0" + [[package]] name = "coverage" version = "7.2.7" @@ -227,6 +265,17 @@ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.1 [package.extras] toml = ["tomli"] +[[package]] +name = "decli" +version = "0.6.1" +description = "Minimal, easy-to-use, declarative cli tool" +optional = false +python-versions = ">=3.7" +files = [ + {file = "decli-0.6.1-py3-none-any.whl", hash = "sha256:7815ac58617764e1a200d7cadac6315fcaacc24d727d182f9878dd6378ccf869"}, + {file = "decli-0.6.1.tar.gz", hash = "sha256:ed88ccb947701e8e5509b7945fda56e150e2ac74a69f25d47ac85ef30ab0c0f0"}, +] + [[package]] name = "distro" version = "1.8.0" @@ -337,6 +386,25 @@ files = [ {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] +[[package]] +name = "importlib-metadata" +version = "6.8.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, + {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, +] + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] + [[package]] name = "iniconfig" version = "2.0.0" @@ -348,6 +416,82 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "jinja2" +version = "3.1.2" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "markupsafe" +version = "2.1.3" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, + {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, +] + [[package]] name = "matrix-client" version = "0.4.0" @@ -549,6 +693,20 @@ tomli = ">=1.2.2" [package.extras] poetry-plugin = ["poetry (>=1.0,<2.0)"] +[[package]] +name = "prompt-toolkit" +version = "3.0.39" +description = "Library for building powerful interactive command lines in Python" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "prompt_toolkit-3.0.39-py3-none-any.whl", hash = "sha256:9dffbe1d8acf91e3de75f3b544e4842382fc06c6babe903ac9acb74dc6e08d88"}, + {file = "prompt_toolkit-3.0.39.tar.gz", hash = "sha256:04505ade687dc26dc4284b1ad19a83be2f2afe83e7a828ace0c72f3a1df72aac"}, +] + +[package.dependencies] +wcwidth = "*" + [[package]] name = "pydantic" version = "2.1.1" @@ -753,6 +911,72 @@ files = [ [package.extras] dev = ["atomicwrites (==1.2.1)", "attrs (==19.2.0)", "coverage (==6.5.0)", "hatch", "invoke (==1.7.3)", "more-itertools (==4.3.0)", "pbr (==4.3.0)", "pluggy (==1.0.0)", "py (==1.11.0)", "pytest (==7.2.0)", "pytest-cov (==4.0.0)", "pytest-timeout (==2.1.0)", "pyyaml (==5.1)"] +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + +[[package]] +name = "questionary" +version = "1.10.0" +description = "Python library to build pretty command line user prompts ⭐️" +optional = false +python-versions = ">=3.6,<4.0" +files = [ + {file = "questionary-1.10.0-py3-none-any.whl", hash = "sha256:fecfcc8cca110fda9d561cb83f1e97ecbb93c613ff857f655818839dac74ce90"}, + {file = "questionary-1.10.0.tar.gz", hash = "sha256:600d3aefecce26d48d97eee936fdb66e4bc27f934c3ab6dd1e292c4f43946d90"}, +] + +[package.dependencies] +prompt_toolkit = ">=2.0,<4.0" + +[package.extras] +docs = ["Sphinx (>=3.3,<4.0)", "sphinx-autobuild (>=2020.9.1,<2021.0.0)", "sphinx-autodoc-typehints (>=1.11.1,<2.0.0)", "sphinx-copybutton (>=0.3.1,<0.4.0)", "sphinx-rtd-theme (>=0.5.0,<0.6.0)"] + [[package]] name = "requests" version = "2.31.0" @@ -845,6 +1069,20 @@ typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\"" [package.extras] full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] +[[package]] +name = "termcolor" +version = "2.3.0" +description = "ANSI color formatting for output in terminal" +optional = false +python-versions = ">=3.7" +files = [ + {file = "termcolor-2.3.0-py3-none-any.whl", hash = "sha256:3afb05607b89aed0ffe25202399ee0867ad4d3cb4180d98aaf8eefa6a5f7d475"}, + {file = "termcolor-2.3.0.tar.gz", hash = "sha256:b5b08f68937f138fe92f6c089b99f1e2da0ae56c52b78bf7075fd95420fd9a5a"}, +] + +[package.extras] +tests = ["pytest", "pytest-cov"] + [[package]] name = "tomli" version = "2.0.1" @@ -856,6 +1094,17 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] +[[package]] +name = "tomlkit" +version = "0.12.1" +description = "Style preserving TOML library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomlkit-0.12.1-py3-none-any.whl", hash = "sha256:712cbd236609acc6a3e2e97253dfc52d4c2082982a88f61b640ecf0817eab899"}, + {file = "tomlkit-0.12.1.tar.gz", hash = "sha256:38e1ff8edb991273ec9f6181244a6a391ac30e9f5098e7535640ea6be97a7c86"}, +] + [[package]] name = "typer" version = "0.9.0" @@ -923,6 +1172,32 @@ typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} [package.extras] standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] +[[package]] +name = "wcwidth" +version = "0.2.6" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = "*" +files = [ + {file = "wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"}, + {file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"}, +] + +[[package]] +name = "zipp" +version = "3.16.2" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zipp-3.16.2-py3-none-any.whl", hash = "sha256:679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0"}, + {file = "zipp-3.16.2.tar.gz", hash = "sha256:ebc15946aa78bd63458992fc81ec3b6f7b1e92d51c35e6de1c3804e73b799147"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] + [[package]] name = "zulip" version = "0.8.2" @@ -944,4 +1219,4 @@ typing-extensions = ">=3.7" [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.12" -content-hash = "b08ba6cdd2611addd83269efe303769abd8b7cca37de24886eeb4c4f3131a92e" +content-hash = "08d8dea4b3436bebf7f62214827e5f0250131592ced10f2ea7ddbd228f050bd5" diff --git a/pyproject.toml b/pyproject.toml index e060f4ae..d426fe52 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,7 @@ pytest-cov = "^4.1.0" [tool.poetry.group.dev.dependencies] poethepoet = "^0.21.1" +commitizen = "^3.6.0" [tool.poetry.group.lint.dependencies] ruff = "^0.0.282" @@ -67,6 +68,14 @@ ruff = "ruff check ./src ./tests" mypy = "mypy ./src ./tests" pyright = "pyright ./src ./tests" + +[tool.commitizen] +name = "cz_conventional_commits" +tag_format = "v$version" +version_scheme = "pep440" +version_provider = "poetry" +update_changelog_on_bump = true +major_version_zero = true [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" From 1d2af5f0cccfb0f6be6dc951afbbc6826f2f347f Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Mon, 7 Aug 2023 12:26:29 +0200 Subject: [PATCH 071/143] chore: add black task, add format task, set lint to check only --- pyproject.toml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d426fe52..73c5af77 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,7 +35,7 @@ pyright = "^1.1.320" [tool.black] line-length = 120 -target-version = ["py39", "py310", "py311", "py312"] +target-version = ["py39", "py310", "py311"] [tool.ruff] line-length = 120 @@ -63,12 +63,13 @@ serve = "uvicorn zulip_write_only_proxy.main:app --reload" test = "python3 -m pytest --cov=zulip_write_only_proxy --cov-report=term-missing --cov-report xml --cov-branch" -lint = ["ruff", "mypy", "pyright"] +lint = ["ruff", "black --check", "mypy", "pyright"] +format = ["ruff --fix", "black"] ruff = "ruff check ./src ./tests" +black = "black ./src ./tests" mypy = "mypy ./src ./tests" pyright = "pyright ./src ./tests" - [tool.commitizen] name = "cz_conventional_commits" tag_format = "v$version" @@ -76,6 +77,7 @@ version_scheme = "pep440" version_provider = "poetry" update_changelog_on_bump = true major_version_zero = true + [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" From 18d79dde7a5365af589a5b5115822549dac7b73f Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Mon, 7 Aug 2023 12:26:54 +0200 Subject: [PATCH 072/143] ci: continue on error for lint checks --- .github/workflows/lint.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 27eac1bd..996c983d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -41,10 +41,17 @@ jobs: poetry --no-ansi --no-interaction install - name: Lint - ruff + continue-on-error: true run: poetry run poe ruff + - name: Lint - ruff + continue-on-error: true + run: poetry run poe black + - name: Lint - mypy + continue-on-error: true run: poetry run poe mypy - name: Lint - pyright + continue-on-error: true run: poetry run poe pyright From 8397b0d68ed7a7e3b036ef4b53ed3e6d5f5d1737 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Mon, 7 Aug 2023 12:31:39 +0200 Subject: [PATCH 073/143] build(deps): add black to lint group --- poetry.lock | 74 +++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 55233a9d..5f83531f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -46,6 +46,52 @@ files = [ [package.extras] test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] +[[package]] +name = "black" +version = "23.7.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-23.7.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:5c4bc552ab52f6c1c506ccae05681fab58c3f72d59ae6e6639e8885e94fe2587"}, + {file = "black-23.7.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:552513d5cd5694590d7ef6f46e1767a4df9af168d449ff767b13b084c020e63f"}, + {file = "black-23.7.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:86cee259349b4448adb4ef9b204bb4467aae74a386bce85d56ba4f5dc0da27be"}, + {file = "black-23.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:501387a9edcb75d7ae8a4412bb8749900386eaef258f1aefab18adddea1936bc"}, + {file = "black-23.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb074d8b213749fa1d077d630db0d5f8cc3b2ae63587ad4116e8a436e9bbe995"}, + {file = "black-23.7.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b5b0ee6d96b345a8b420100b7d71ebfdd19fab5e8301aff48ec270042cd40ac2"}, + {file = "black-23.7.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:893695a76b140881531062d48476ebe4a48f5d1e9388177e175d76234ca247cd"}, + {file = "black-23.7.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:c333286dc3ddca6fdff74670b911cccedacb4ef0a60b34e491b8a67c833b343a"}, + {file = "black-23.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831d8f54c3a8c8cf55f64d0422ee875eecac26f5f649fb6c1df65316b67c8926"}, + {file = "black-23.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:7f3bf2dec7d541b4619b8ce526bda74a6b0bffc480a163fed32eb8b3c9aed8ad"}, + {file = "black-23.7.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:f9062af71c59c004cd519e2fb8f5d25d39e46d3af011b41ab43b9c74e27e236f"}, + {file = "black-23.7.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:01ede61aac8c154b55f35301fac3e730baf0c9cf8120f65a9cd61a81cfb4a0c3"}, + {file = "black-23.7.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:327a8c2550ddc573b51e2c352adb88143464bb9d92c10416feb86b0f5aee5ff6"}, + {file = "black-23.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1c6022b86f83b632d06f2b02774134def5d4d4f1dac8bef16d90cda18ba28a"}, + {file = "black-23.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:27eb7a0c71604d5de083757fbdb245b1a4fae60e9596514c6ec497eb63f95320"}, + {file = "black-23.7.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:8417dbd2f57b5701492cd46edcecc4f9208dc75529bcf76c514864e48da867d9"}, + {file = "black-23.7.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:47e56d83aad53ca140da0af87678fb38e44fd6bc0af71eebab2d1f59b1acf1d3"}, + {file = "black-23.7.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:25cc308838fe71f7065df53aedd20327969d05671bac95b38fdf37ebe70ac087"}, + {file = "black-23.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:642496b675095d423f9b8448243336f8ec71c9d4d57ec17bf795b67f08132a91"}, + {file = "black-23.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:ad0014efc7acf0bd745792bd0d8857413652979200ab924fbf239062adc12491"}, + {file = "black-23.7.0-py3-none-any.whl", hash = "sha256:9fd59d418c60c0348505f2ddf9609c1e1de8e7493eab96198fc89d9f865e7a96"}, + {file = "black-23.7.0.tar.gz", hash = "sha256:022a582720b0d9480ed82576c920a8c1dde97cc38ff11d8d8859b3bd6ca9eedb"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + [[package]] name = "certifi" version = "2023.7.22" @@ -660,6 +706,32 @@ files = [ {file = "pastel-0.2.1.tar.gz", hash = "sha256:e6581ac04e973cac858828c6202c1e1e81fee1dc7de7683f3e1ffe0bfd8a573d"}, ] +[[package]] +name = "pathspec" +version = "0.11.2" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, +] + +[[package]] +name = "platformdirs" +version = "3.10.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"}, + {file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"}, +] + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] + [[package]] name = "pluggy" version = "1.2.0" @@ -1219,4 +1291,4 @@ typing-extensions = ">=3.7" [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.12" -content-hash = "08d8dea4b3436bebf7f62214827e5f0250131592ced10f2ea7ddbd228f050bd5" +content-hash = "36f420145078a9640dec4edab4761899ba716b277924b369ec45cd088c4c1485" diff --git a/pyproject.toml b/pyproject.toml index 73c5af77..1049fb9c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,7 @@ commitizen = "^3.6.0" ruff = "^0.0.282" mypy = "^1.4.1" pyright = "^1.1.320" +black = "^23.7.0" [tool.black] line-length = 120 From 0c4e5ec8e3f09f8789302363c349ce79d08cceaa Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Mon, 7 Aug 2023 12:32:24 +0200 Subject: [PATCH 074/143] ci: replace continue on error with if always Continue on error marks workflow as passed even if step fails. Idea is to have all steps keep running even if one fails, but still mark the workflow as failed. --- .github/workflows/lint.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 996c983d..9875aad5 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -41,17 +41,17 @@ jobs: poetry --no-ansi --no-interaction install - name: Lint - ruff - continue-on-error: true + if: always() run: poetry run poe ruff - name: Lint - ruff - continue-on-error: true + if: always() run: poetry run poe black - name: Lint - mypy - continue-on-error: true + if: always() run: poetry run poe mypy - name: Lint - pyright - continue-on-error: true + if: always() run: poetry run poe pyright From 79d9fce4341b1895dac1b994d1f6e49e1b99e256 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Mon, 7 Aug 2023 12:34:12 +0200 Subject: [PATCH 075/143] ci: rename lint black step, add check flag --- .github/workflows/lint.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 9875aad5..ce4e2de0 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -44,9 +44,9 @@ jobs: if: always() run: poetry run poe ruff - - name: Lint - ruff + - name: Lint - black if: always() - run: poetry run poe black + run: poetry run poe black --check - name: Lint - mypy if: always() From 38c4196945669e5d8cfed885191f46be3235c620 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Mon, 7 Aug 2023 12:37:40 +0200 Subject: [PATCH 076/143] style: set line length to 88 --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1049fb9c..4aecc4c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,11 +35,11 @@ pyright = "^1.1.320" black = "^23.7.0" [tool.black] -line-length = 120 +line-length = 88 target-version = ["py39", "py310", "py311"] [tool.ruff] -line-length = 120 +line-length = 88 extend-select = [ "I", # isort "B", # flake8-bugbear From 5c6f76a75ba45ecdc9b882c6d8eff1484bedc273 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Mon, 7 Aug 2023 12:39:18 +0200 Subject: [PATCH 077/143] style: reformat --- src/zulip_write_only_proxy/main.py | 1 + tests/test_fastapi.py | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index 58ee1a95..d4754917 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -94,6 +94,7 @@ def get_me( ) -> models.ScopedClient: return client + @app.get("/health") def healthcheck(): return "OK" diff --git a/tests/test_fastapi.py b/tests/test_fastapi.py index 8ed9883d..f5397de8 100644 --- a/tests/test_fastapi.py +++ b/tests/test_fastapi.py @@ -67,7 +67,8 @@ def test_send_message_with_image(fastapi_client, zulip_client): } zulip_client.send_message.assert_called_once_with(zulip_request_msg) - zulip_client.upload_file.assert_called_once() # Call is made with spooled temp file object + # Call is made with spooled temp file object + zulip_client.upload_file.assert_called_once() uploaded_image = zulip_client.upload_file.call_args.args[0] assert uploaded_image.name == "test.jpg" @@ -89,7 +90,8 @@ def test_upload_image(fastapi_client, zulip_client): assert response.status_code == 200 assert response.json() == zulip_response_file - zulip_client.upload_file.assert_called_once() # Call is made with spooled temp file object + # Call is made with spooled temp file object + zulip_client.upload_file.assert_called_once() uploaded_image = zulip_client.upload_file.call_args.args[0] assert uploaded_image.name == "test.jpg" @@ -147,7 +149,10 @@ def test_create_client(fastapi_client, zulip_client): def test_create_client_error(fastapi_client): - with patch("zulip_write_only_proxy.services.create_client", MagicMock(side_effect=ValueError("Test Error"))): + with patch( + "zulip_write_only_proxy.services.create_client", + MagicMock(side_effect=ValueError("Test Error")), + ): # Call the API endpoint with invalid data response = fastapi_client.post( "/create_client", From 1077c707531ec458a4e3bab88f324e1b7fa80b1c Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Thu, 10 Aug 2023 10:33:38 +0200 Subject: [PATCH 078/143] feat(endpoint): deprecate `upload_image`, rename to `upload_file` Done to keep in line with native Zulip API endpoints `upload_image` deprecated, will be removed in next commit --- src/zulip_write_only_proxy/main.py | 23 ++++++++++++++++++----- src/zulip_write_only_proxy/models.py | 4 ++-- tests/test_fastapi.py | 4 ++-- tests/test_models.py | 4 ++-- 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index d4754917..2abc9771 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -46,7 +46,7 @@ def send_message( f: SpooledTemporaryFile = image.file # type: ignore[assignment] f._file.name = image.filename # type: ignore[attr-defined] - result = client.upload_image(f) + result = client.upload_file(f) content += f"\n[]({result['uri']})" @@ -56,19 +56,32 @@ def send_message( _docs_url = "https://zulip.com/api/upload-file#response" +@app.post( + "/upload_file", + tags=["User"], + response_description=f"See {_docs_url}", +) +def upload_file( + client: Annotated[models.ScopedClient, fastapi.Depends(get_client)], + file: Annotated[fastapi.UploadFile, fastapi.File(...)], +): + f: SpooledTemporaryFile = file.file # type: ignore[assignment] + f._file.name = file.filename # type: ignore[attr-defined] + + return client.upload_file(f) + + @app.post( "/upload_image", tags=["User"], response_description=f"See {_docs_url}", + deprecated=True, ) def upload_image( client: Annotated[models.ScopedClient, fastapi.Depends(get_client)], image: Annotated[fastapi.UploadFile, fastapi.File(...)], ): - f: SpooledTemporaryFile = image.file # type: ignore[assignment] - f._file.name = image.filename # type: ignore[attr-defined] - - return client.upload_image(f) + return upload_file(client, image) _docs_url = "https://zulip.com/api/get-stream-topics#response" diff --git a/src/zulip_write_only_proxy/models.py b/src/zulip_write_only_proxy/models.py index 99379f18..7de7fe93 100644 --- a/src/zulip_write_only_proxy/models.py +++ b/src/zulip_write_only_proxy/models.py @@ -32,8 +32,8 @@ def create( return self - def upload_image(self, image: IO[Any]): - return self._client.upload_file(image) + def upload_file(self, file: IO[Any]): + return self._client.upload_file(file) def list_topics(self): stream = self._client.get_stream_id(self.stream) diff --git a/tests/test_fastapi.py b/tests/test_fastapi.py index f5397de8..f657df36 100644 --- a/tests/test_fastapi.py +++ b/tests/test_fastapi.py @@ -73,7 +73,7 @@ def test_send_message_with_image(fastapi_client, zulip_client): assert uploaded_image.name == "test.jpg" -def test_upload_image(fastapi_client, zulip_client): +def test_upload_file(fastapi_client, zulip_client): zulip_response_file = { "msg": "", "result": "success", @@ -83,7 +83,7 @@ def test_upload_image(fastapi_client, zulip_client): image = io.BytesIO(b"test image data") response = fastapi_client.post( - "/upload_image", + "/upload_file", files={"image": ("test.jpg", image)}, ) diff --git a/tests/test_models.py b/tests/test_models.py index a89f9b8c..289445d9 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -6,12 +6,12 @@ from zulip_write_only_proxy.models import AdminClient -def test_upload_image(scoped_client): +def test_upload_file(scoped_client): scoped_client._client.upload_file = MagicMock(return_value={"uri": "/foo/bar.jpg"}) image = io.BytesIO(b"test image data") - result = scoped_client.upload_image(image) + result = scoped_client.upload_file(image) scoped_client._client.upload_file.assert_called_once_with(image) From bd9d23f2cb188a3553362aa4b881740051169a7f Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Thu, 10 Aug 2023 10:51:39 +0200 Subject: [PATCH 079/143] feat(endpoint): remove `upload_image` Removes deprecated `upload_image` endpoint --- src/zulip_write_only_proxy/main.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index 2abc9771..d9378bdb 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -71,19 +71,6 @@ def upload_file( return client.upload_file(f) -@app.post( - "/upload_image", - tags=["User"], - response_description=f"See {_docs_url}", - deprecated=True, -) -def upload_image( - client: Annotated[models.ScopedClient, fastapi.Depends(get_client)], - image: Annotated[fastapi.UploadFile, fastapi.File(...)], -): - return upload_file(client, image) - - _docs_url = "https://zulip.com/api/get-stream-topics#response" From 18c8bc99311690d5f29c58317c610b655ec3cf8f Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Thu, 10 Aug 2023 11:04:59 +0200 Subject: [PATCH 080/143] build: add makefile for easier up/down with stacks --- Makefile | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..b879edc3 --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +up: + docker compes config | docker stack deploy -c - zwop + +down: + docker stack rm zwop From f06c5e0b934a672a1957682fc71debbc5d300c4f Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Thu, 10 Aug 2023 11:05:20 +0200 Subject: [PATCH 081/143] docs(deploy): update documentation w/ make commands --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 545d7c49..0100b6c6 100644 --- a/README.md +++ b/README.md @@ -130,12 +130,16 @@ Stream/topic can be edited manually in the JSON file or set via CLI at creation Deployment is similar to development with `docker compose`, but instead a docker stack is used to allow for better scaling and update configuration. -Deployment is done with: +To quickly bring the service up or down run `make up` or `make down`. + +Bringing up the service runs: ```sh -docker stack deploy -c docker-compose.yml zwop +docker compose config | docker stack deploy -c - zwop ``` To update the stack, use the same command. This will pull in the latest image and perform a rolling restart of the service, which will first start the new container, wait for a successful health check, and then stop the old container. A cron job runs the deployment command every minute to check for updates. + +NB: There is an outstanding issue with `docker stack deploy` where it [does not load `.env` files](https://github.com/moby/moby/issues/29133) in the same way that `docker compose up` does. This is solved by running `docker compose config` to generate a compose-compliant file (with env vars subsituted) and piping that to `docker stack deploy`. From c53ce7d72fa75150514fbd56124931facd16ed24 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Thu, 10 Aug 2023 11:11:09 +0200 Subject: [PATCH 082/143] test: replace some references to image with generic file --- tests/test_fastapi.py | 8 ++++---- tests/test_models.py | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/test_fastapi.py b/tests/test_fastapi.py index f657df36..5bb11ebb 100644 --- a/tests/test_fastapi.py +++ b/tests/test_fastapi.py @@ -81,10 +81,10 @@ def test_upload_file(fastapi_client, zulip_client): } zulip_client.upload_file = MagicMock(return_value=zulip_response_file) - image = io.BytesIO(b"test image data") + file = io.BytesIO(b"test file data") response = fastapi_client.post( "/upload_file", - files={"image": ("test.jpg", image)}, + files={"file": ("test.jpg", file)}, ) assert response.status_code == 200 @@ -92,8 +92,8 @@ def test_upload_file(fastapi_client, zulip_client): # Call is made with spooled temp file object zulip_client.upload_file.assert_called_once() - uploaded_image = zulip_client.upload_file.call_args.args[0] - assert uploaded_image.name == "test.jpg" + uploaded_file = zulip_client.upload_file.call_args.args[0] + assert uploaded_file.name == "test.jpg" def test_get_topics(fastapi_client, zulip_client): diff --git a/tests/test_models.py b/tests/test_models.py index 289445d9..44a22641 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -9,11 +9,11 @@ def test_upload_file(scoped_client): scoped_client._client.upload_file = MagicMock(return_value={"uri": "/foo/bar.jpg"}) - image = io.BytesIO(b"test image data") + file = io.BytesIO(b"test file data") - result = scoped_client.upload_file(image) + result = scoped_client.upload_file(file) - scoped_client._client.upload_file.assert_called_once_with(image) + scoped_client._client.upload_file.assert_called_once_with(file) assert result == {"uri": "/foo/bar.jpg"} From 5be14e2edfe0dcc8a54a5419d4d293dd6a9f63ae Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Thu, 10 Aug 2023 11:11:34 +0200 Subject: [PATCH 083/143] build(deps): update pyright --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 5f83531f..69a9db98 100644 --- a/poetry.lock +++ b/poetry.lock @@ -913,13 +913,13 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pyright" -version = "1.1.320" +version = "1.1.321" description = "Command line wrapper for pyright" optional = false python-versions = ">=3.7" files = [ - {file = "pyright-1.1.320-py3-none-any.whl", hash = "sha256:cdf739f7827374575cefb134311457d08c189793c3da3097c023debec9acaedb"}, - {file = "pyright-1.1.320.tar.gz", hash = "sha256:94223e3b82d0b4c99c5125bab4d628ec749ce0b5b2ae0471fd4921dd4eff460d"}, + {file = "pyright-1.1.321-py3-none-any.whl", hash = "sha256:bc8ca2001b9f19b1cf75eb5ace71d833f9266b3981a5d88dac3b9bbcff8d9281"}, + {file = "pyright-1.1.321.tar.gz", hash = "sha256:f62d54fac611138a407bb302103700eeb37a147ce1a6e915148be9ca5961572a"}, ] [package.dependencies] From 6437abea9cbaa654ea301a35ea997a7bbc14aa6a Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Thu, 10 Aug 2023 13:13:02 +0200 Subject: [PATCH 084/143] ci: create reusable composite action for project setup --- .github/setup/action.yml | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .github/setup/action.yml diff --git a/.github/setup/action.yml b/.github/setup/action.yml new file mode 100644 index 00000000..4eba538d --- /dev/null +++ b/.github/setup/action.yml @@ -0,0 +1,36 @@ +name: Setup Step + +description: | + This step sets up Python and Poetry, and caches the virtual environment. + It is intended to be used as a step in a workflow that runs Python code. + +inputs: + python-version: + required: true + description: "Python version to use with `actions/setup-python`" + +runs: + using: "composite" + steps: + - name: Set up Python ${{ inputs.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ inputs.python-version }} + + - name: Set up Poetry + shell: bash + run: | + python -m pip install --upgrade pip poetry + poetry config virtualenvs.create true --local + poetry config virtualenvs.in-project true --local + + - name: Use venv cache + uses: actions/cache@v3 + with: + path: ./.venv + key: venv-${{ hashFiles('poetry.lock') }}-${{ inputs.python-version }} + + - name: Install project + shell: bash + run: | + poetry --no-ansi --no-interaction install From c3e5d89ba1a07d876006f6e07b2a3d37916c9465 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Thu, 10 Aug 2023 13:13:45 +0200 Subject: [PATCH 085/143] ci: use reusable actions for lint and test --- .github/workflows/lint.yml | 19 +------------------ .github/workflows/test.yml | 19 +------------------ 2 files changed, 2 insertions(+), 36 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ce4e2de0..cb767136 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -19,27 +19,10 @@ jobs: - name: Checkout project uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + - uses: ./.github/setup with: python-version: ${{ matrix.python-version }} - - name: Set up Poetry - run: | - python -m pip install --upgrade pip poetry - poetry config virtualenvs.create true --local - poetry config virtualenvs.in-project true --local - - - name: Use venv cache - uses: actions/cache@v3 - with: - path: ./.venv - key: venv-${{ hashFiles('poetry.lock') }}-${{ matrix.python-version }} - - - name: Install project - run: | - poetry --no-ansi --no-interaction install - - name: Lint - ruff if: always() run: poetry run poe ruff diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 463d2dc5..50e75fb8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,27 +19,10 @@ jobs: - name: Checkout project uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + - uses: ./.github/setup with: python-version: ${{ matrix.python-version }} - - name: Set up Poetry - run: | - python -m pip install --upgrade pip poetry - poetry config virtualenvs.create true --local - poetry config virtualenvs.in-project true --local - - - name: Use venv cache - uses: actions/cache@v3 - with: - path: ./.venv - key: venv-${{ hashFiles('poetry.lock') }}-${{ matrix.python-version }} - - - name: Install project - run: | - poetry --no-ansi --no-interaction install - - name: Test run: poetry run poe test From 3d87c3ed3e651881bdf6df04825494d9f82282bf Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Thu, 10 Aug 2023 13:15:13 +0200 Subject: [PATCH 086/143] ci: do not build on all branches, set workflow call/dispatch --- .github/workflows/build-image.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml index d54aebbc..d55da795 100644 --- a/.github/workflows/build-image.yml +++ b/.github/workflows/build-image.yml @@ -2,10 +2,10 @@ name: Build Image on: push: - branches: - - "**" tags: - "v**" + workflow_call: + workflow_dispatch: env: REGISTRY: ghcr.io From f8309bb2e5536d20a8490acdca0735ff43f6a4cc Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Thu, 10 Aug 2023 13:15:37 +0200 Subject: [PATCH 087/143] ci: build image after successful tests --- .github/workflows/test.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 50e75fb8..3c61ef6f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,3 +30,11 @@ jobs: uses: codecov/codecov-action@v3 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + build-image: + needs: test + uses: ./.github/workflows/build-image.yml + permissions: + contents: read + packages: write + secrets: inherit From b3db1282b0d31be69751aa5414e5e9ad45793886 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Mon, 14 Aug 2023 10:50:52 +0200 Subject: [PATCH 088/143] feat(endpoint): rename `/message` to `/send_message`, deprecate `/message` --- src/zulip_write_only_proxy/main.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index d9378bdb..c7d59cd8 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -30,7 +30,7 @@ def get_client(key: Annotated[str, fastapi.Security(api_key_header)]) -> models. @app.post( - "/message", + "/send_message", tags=["User"], response_description=f"See {_docs_url}", ) @@ -53,6 +53,16 @@ def send_message( return client.send_message(topic, content) +@app.post( + "/message", + tags=["User"], + response_description=f"See {_docs_url}", + deprecated=True, +) +def message(res: Annotated[dict, fastapi.Depends(send_message)]): + return res + + _docs_url = "https://zulip.com/api/upload-file#response" From 9dd2a41c6077b6299a5a2732b487a0dd0148bcc6 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Mon, 14 Aug 2023 10:59:56 +0200 Subject: [PATCH 089/143] feat(endpoint): add `/update_message` endpoint and model methods --- src/zulip_write_only_proxy/main.py | 18 ++++++++++++++++++ src/zulip_write_only_proxy/models.py | 23 +++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index c7d59cd8..4aefd1f0 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -63,6 +63,24 @@ def message(res: Annotated[dict, fastapi.Depends(send_message)]): return res +_docs_url = "https://zulip.com/api/update-message#response" + + +@app.patch( + "/update_message", + tags=["User"], + response_description=f"See {_docs_url}", +) +def update_message( + client: Annotated[models.ScopedClient, fastapi.Depends(get_client)], + message_id: Annotated[int, fastapi.Query(...)], + topic: Annotated[str, fastapi.Query(...)], + propagate_mode: Annotated[models.PropagateMode, fastapi.Query(...)], + content: Annotated[str, fastapi.Query(...)], +): + return client.update_message(message_id, topic, propagate_mode, content) + + _docs_url = "https://zulip.com/api/upload-file#response" diff --git a/src/zulip_write_only_proxy/models.py b/src/zulip_write_only_proxy/models.py index 7de7fe93..d830d3be 100644 --- a/src/zulip_write_only_proxy/models.py +++ b/src/zulip_write_only_proxy/models.py @@ -1,5 +1,6 @@ from __future__ import annotations +import enum import secrets from typing import IO, Any, Union @@ -8,6 +9,12 @@ from typing_extensions import Self +class PropagateMode(str, enum.Enum): + change_one = "change_one" + change_all = "change_all" + change_later = "change_later" + + class ScopedClient(BaseModel): key: SecretStr @@ -55,6 +62,22 @@ def send_message(self, topic: str, content: str): return self._client.send_message(request) + def update_message( + self, + message_id: int, + topic: str, + propagate_mode: PropagateMode, + content: str, + ): + request = { + "message_id": message_id, + "topic": topic, + "propagate_mode": propagate_mode.value, + "content": content, + } + + return self._client.update_message(request) + class AdminClient(BaseModel): key: SecretStr From d66c6e65a1a98699e7866dd25926c2bd73399efc Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Mon, 14 Aug 2023 11:10:52 +0200 Subject: [PATCH 090/143] fix(deploy): add `git pull` to make up, fix compose typo --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b879edc3..f99743de 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ up: - docker compes config | docker stack deploy -c - zwop + git pull + docker compose config | docker stack deploy -c - zwop down: docker stack rm zwop From 1f26a0eac09e79ccda83954f7cf2aba4910a160f Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Mon, 14 Aug 2023 11:12:48 +0200 Subject: [PATCH 091/143] fix(deploy): remove quotes around nginx port --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index dce1a5c4..84c11c04 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,7 +19,7 @@ services: nginx: image: nginx:1.25.1 ports: - - "${PORT:-8000}:80" + - ${PORT:-8000}:80 volumes: - ./nginx.conf:/etc/nginx/nginx.conf healthcheck: From 224c06b1538c5b13012d89052973cbe7e7bf62b0 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Mon, 14 Aug 2023 11:26:31 +0200 Subject: [PATCH 092/143] chore(deploy): erorr on missing port env var --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 84c11c04..7d35f5cc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,7 +19,7 @@ services: nginx: image: nginx:1.25.1 ports: - - ${PORT:-8000}:80 + - ${PORT:?err}:80 volumes: - ./nginx.conf:/etc/nginx/nginx.conf healthcheck: From fb6a9027cf75525d784c05e8039c45be62d3ed0d Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Mon, 14 Aug 2023 11:27:30 +0200 Subject: [PATCH 093/143] build(deploy): parse compose config with sed before pipe to stack deploy various interoperability problems between compose spec and stack spec, see https://github.com/docker/cli/issues/1073 --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f99743de..84fb4fa5 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ up: git pull - docker compose config | docker stack deploy -c - zwop + # See: https://github.com/docker/cli/issues/1073 + docker compose config | sed '/published:/ s/"//g' | sed "/name:/d" | docker stack deploy -c - zwop down: docker stack rm zwop From 9a12078ab0156a683117728554c89bcee48fe5b9 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Mon, 14 Aug 2023 12:14:55 +0200 Subject: [PATCH 094/143] ci(deploy): set push to true --- .github/workflows/build-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml index d55da795..e8cc6967 100644 --- a/.github/workflows/build-image.yml +++ b/.github/workflows/build-image.yml @@ -51,6 +51,6 @@ jobs: - name: Build and push Docker image uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 with: - push: ${{ github.event_name != 'pull_request' }} + push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} From f74ae96236a3d124c3b7047da9bbbc9033c83fb8 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Mon, 14 Aug 2023 12:34:38 +0200 Subject: [PATCH 095/143] fix(deploy): set head sha on pr event --- .github/workflows/build-image.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml index e8cc6967..9cbe1c5e 100644 --- a/.github/workflows/build-image.yml +++ b/.github/workflows/build-image.yml @@ -41,6 +41,8 @@ jobs: type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} type=sha + env: + DOCKER_METADATA_PR_HEAD_SHA: true - name: Set up QEMU uses: docker/setup-qemu-action@v2 From ca6995ec1f831ee81e55bc257d313e3b96753a68 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Mon, 14 Aug 2023 12:37:53 +0200 Subject: [PATCH 096/143] fix(deploy): use pr number instead of branch name --- .github/workflows/build-image.yml | 2 -- docker-compose.yml | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml index 9cbe1c5e..e8cc6967 100644 --- a/.github/workflows/build-image.yml +++ b/.github/workflows/build-image.yml @@ -41,8 +41,6 @@ jobs: type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} type=sha - env: - DOCKER_METADATA_PR_HEAD_SHA: true - name: Set up QEMU uses: docker/setup-qemu-action@v2 diff --git a/docker-compose.yml b/docker-compose.yml index 7d35f5cc..9d05128d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,7 +3,7 @@ version: "3.8" services: zwop: - image: ghcr.io/robertrosca/zulip-write-only-proxy:feat-initial-dev + image: ghcr.io/robertrosca/zulip-write-only-proxy:pr-1 deploy: replicas: 2 update_config: From 1fb42465910d758964317a76895f9472b2333154 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Tue, 15 Aug 2023 11:20:25 +0200 Subject: [PATCH 097/143] chore(deploy): update image url to pull from european-xfel --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 9d05128d..cea086fa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,7 +3,7 @@ version: "3.8" services: zwop: - image: ghcr.io/robertrosca/zulip-write-only-proxy:pr-1 + image: ghcr.io/european-xfel/zulip-write-only-proxy:pr-1 deploy: replicas: 2 update_config: From 231d58e36580f41eeaa390b48977b0a9a448dc43 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Sun, 20 Aug 2023 14:22:21 +0200 Subject: [PATCH 098/143] chore(deploy): remove `replicas: 2` since `order: start-first` achieves desired effect --- docker-compose.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index cea086fa..d4f371fb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,6 @@ services: zwop: image: ghcr.io/european-xfel/zulip-write-only-proxy:pr-1 deploy: - replicas: 2 update_config: parallelism: 1 delay: 10s From 619a9eb28bfe7abdeb0b89812354732d05e79240 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 20 Sep 2023 11:04:32 +0200 Subject: [PATCH 099/143] feat(endpoint): remove deprecated '/message' endpoint old '/message' endpoint no longer works --- src/zulip_write_only_proxy/main.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index 4aefd1f0..bbe68822 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -53,16 +53,6 @@ def send_message( return client.send_message(topic, content) -@app.post( - "/message", - tags=["User"], - response_description=f"See {_docs_url}", - deprecated=True, -) -def message(res: Annotated[dict, fastapi.Depends(send_message)]): - return res - - _docs_url = "https://zulip.com/api/update-message#response" From 9743052d0f9437e17130f515e213999f3f98f195 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 20 Sep 2023 11:06:47 +0200 Subject: [PATCH 100/143] feat(endpoint): make message contents a body parameter message contents no longer query, api calls need to be changed see https://github.com/European-XFEL/DAMNIT/pull/76#pullrequestreview-1612583411 --- src/zulip_write_only_proxy/main.py | 4 ++-- tests/test_fastapi.py | 15 +++++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index bbe68822..68673c3a 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -37,7 +37,7 @@ def get_client(key: Annotated[str, fastapi.Security(api_key_header)]) -> models. def send_message( client: Annotated[models.ScopedClient, fastapi.Depends(get_client)], topic: Annotated[str, fastapi.Query(...)], - content: Annotated[str, fastapi.Query(...)], + content: Annotated[str, fastapi.Body(...)], image: Annotated[Union[fastapi.UploadFile, None], fastapi.File()] = None, ): if image: @@ -66,7 +66,7 @@ def update_message( message_id: Annotated[int, fastapi.Query(...)], topic: Annotated[str, fastapi.Query(...)], propagate_mode: Annotated[models.PropagateMode, fastapi.Query(...)], - content: Annotated[str, fastapi.Query(...)], + content: Annotated[str, fastapi.Body(...)], ): return client.update_message(message_id, topic, propagate_mode, content) diff --git a/tests/test_fastapi.py b/tests/test_fastapi.py index 5bb11ebb..e21a7b66 100644 --- a/tests/test_fastapi.py +++ b/tests/test_fastapi.py @@ -1,16 +1,21 @@ import io +from typing import TYPE_CHECKING from unittest.mock import MagicMock, patch from zulip_write_only_proxy import services +if TYPE_CHECKING: + from fastapi.testclient import TestClient -def test_send_message(fastapi_client, zulip_client): + +def test_send_message(fastapi_client: "TestClient", zulip_client): zulip_response = {"id": 42, "msg": "", "result": "success"} zulip_client.send_message = MagicMock(return_value=zulip_response) response = fastapi_client.post( "/message", - params={"topic": "Test Topic", "content": "Test Content"}, + params={"topic": "Test Topic"}, + data={"content": "Test Content"}, ) assert response.status_code == 200 @@ -30,7 +35,8 @@ def test_send_message_unauthorised(fastapi_client): response = fastapi_client.post( "/message", headers={"X-API-key": "invalid_key"}, - params={"topic": "Test Topic", "content": "Test Content"}, + params={"topic": "Test Topic"}, + data={"content": "Test Content"}, ) assert response.status_code == 401 @@ -51,8 +57,9 @@ def test_send_message_with_image(fastapi_client, zulip_client): image = io.BytesIO(b"test image data") response = fastapi_client.post( "/message", - params={"topic": "Test Topic", "content": "Test Content"}, + params={"topic": "Test Topic"}, files={"image": ("test.jpg", image)}, + data={"content": "Test Content"}, ) assert response.status_code == 200 From c66b2a8c808713e5ef55a90e21951644a7b14eec Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Wed, 20 Sep 2023 11:07:46 +0200 Subject: [PATCH 101/143] test: update tests after removing deprecated endpoint --- tests/test_fastapi.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_fastapi.py b/tests/test_fastapi.py index e21a7b66..fd5f6895 100644 --- a/tests/test_fastapi.py +++ b/tests/test_fastapi.py @@ -13,7 +13,7 @@ def test_send_message(fastapi_client: "TestClient", zulip_client): zulip_client.send_message = MagicMock(return_value=zulip_response) response = fastapi_client.post( - "/message", + "/send_message", params={"topic": "Test Topic"}, data={"content": "Test Content"}, ) @@ -33,7 +33,7 @@ def test_send_message(fastapi_client: "TestClient", zulip_client): def test_send_message_unauthorised(fastapi_client): response = fastapi_client.post( - "/message", + "/send_message", headers={"X-API-key": "invalid_key"}, params={"topic": "Test Topic"}, data={"content": "Test Content"}, @@ -56,7 +56,7 @@ def test_send_message_with_image(fastapi_client, zulip_client): image = io.BytesIO(b"test image data") response = fastapi_client.post( - "/message", + "/send_message", params={"topic": "Test Topic"}, files={"image": ("test.jpg", image)}, data={"content": "Test Content"}, From 4e0da104c7fe19a6fff8094c132bec753773f737 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Fri, 22 Sep 2023 12:02:38 +0200 Subject: [PATCH 102/143] feat: remane `list_topics` to `get_stream_topics` aligns closer to Zulip API endpoint renamed --- src/zulip_write_only_proxy/main.py | 4 ++-- src/zulip_write_only_proxy/models.py | 2 +- tests/test_models.py | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index 68673c3a..122361ec 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -97,11 +97,11 @@ def upload_file( tags=["User"], response_description=f"See {_docs_url}", ) -def get_topics( +def get_stream_topics( client: Annotated[models.ScopedClient, fastapi.Depends(get_client)], ): try: - return client.list_topics() + return client.get_stream_topics() except RuntimeError as e: raise fastapi.HTTPException(status_code=400, detail=str(e)) from e diff --git a/src/zulip_write_only_proxy/models.py b/src/zulip_write_only_proxy/models.py index d830d3be..d3059268 100644 --- a/src/zulip_write_only_proxy/models.py +++ b/src/zulip_write_only_proxy/models.py @@ -42,7 +42,7 @@ def create( def upload_file(self, file: IO[Any]): return self._client.upload_file(file) - def list_topics(self): + def get_stream_topics(self): stream = self._client.get_stream_id(self.stream) if stream["result"] != "success": raise RuntimeError( diff --git a/tests/test_models.py b/tests/test_models.py index 44a22641..675b9184 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -18,7 +18,7 @@ def test_upload_file(scoped_client): assert result == {"uri": "/foo/bar.jpg"} -def test_list_topics(scoped_client): +def test_get_stream_topics(scoped_client): scoped_client._client.get_stream_id = MagicMock( return_value={"result": "success", "stream_id": 123} ) @@ -26,7 +26,7 @@ def test_list_topics(scoped_client): return_value=["Topic 1", "Topic 2"] ) - result = scoped_client.list_topics() + result = scoped_client.get_stream_topics() scoped_client._client.get_stream_id.assert_called_once_with("Test Stream") @@ -35,11 +35,11 @@ def test_list_topics(scoped_client): assert result == ["Topic 1", "Topic 2"] -def test_list_topics_raises(scoped_client): +def test_get_stream_topics_raises(scoped_client): scoped_client._client.get_stream_id = MagicMock(return_value={"result": "failure"}) with pytest.raises(RuntimeError): - scoped_client.list_topics() + scoped_client.get_stream_topics() def test_send_message(scoped_client): From 78848dd7245057254f1209a04b074aa48608a163 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Fri, 22 Sep 2023 12:11:19 +0200 Subject: [PATCH 103/143] feat: `get_stream_topics` return zulip response instead of custom error --- src/zulip_write_only_proxy/main.py | 5 +---- src/zulip_write_only_proxy/models.py | 9 +++++---- tests/test_fastapi.py | 9 ++------- tests/test_models.py | 8 +++++--- 4 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index 122361ec..46d50e37 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -100,10 +100,7 @@ def upload_file( def get_stream_topics( client: Annotated[models.ScopedClient, fastapi.Depends(get_client)], ): - try: - return client.get_stream_topics() - except RuntimeError as e: - raise fastapi.HTTPException(status_code=400, detail=str(e)) from e + return client.get_stream_topics() @app.get("/me", tags=["User"]) diff --git a/src/zulip_write_only_proxy/models.py b/src/zulip_write_only_proxy/models.py index d3059268..bb76fa59 100644 --- a/src/zulip_write_only_proxy/models.py +++ b/src/zulip_write_only_proxy/models.py @@ -1,6 +1,7 @@ from __future__ import annotations import enum +import logging import secrets from typing import IO, Any, Union @@ -8,6 +9,8 @@ from pydantic import BaseModel, PrivateAttr, SecretStr, field_validator from typing_extensions import Self +log = logging.getLogger(__name__) + class PropagateMode(str, enum.Enum): change_one = "change_one" @@ -45,10 +48,8 @@ def upload_file(self, file: IO[Any]): def get_stream_topics(self): stream = self._client.get_stream_id(self.stream) if stream["result"] != "success": - raise RuntimeError( - f"Failed to get stream id for {self.stream}. Is bot added to stream?\n" - f"Response was {stream}" - ) + log.error(f"failed to get stream id for {self.stream}", extra=stream) + return stream stream_id = stream["stream_id"] return self._client.get_stream_topics(stream_id) diff --git a/tests/test_fastapi.py b/tests/test_fastapi.py index fd5f6895..fa01a8d5 100644 --- a/tests/test_fastapi.py +++ b/tests/test_fastapi.py @@ -129,13 +129,8 @@ def test_get_topics_error(fastapi_client, zulip_client): response = fastapi_client.get("/get_topics") - assert response.status_code == 400 - assert response.json() == { - "detail": ( - "Failed to get stream id for Test Stream 1. Is bot added to stream?\n" - f"Response was {zulip_response_id}" - ) - } + assert response.status_code == 200 + assert response.json() == {"result": "error"} def test_create_client(fastapi_client, zulip_client): diff --git a/tests/test_models.py b/tests/test_models.py index 675b9184..780fa281 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -35,11 +35,13 @@ def test_get_stream_topics(scoped_client): assert result == ["Topic 1", "Topic 2"] -def test_get_stream_topics_raises(scoped_client): +def test_get_stream_topics_log(scoped_client, caplog: pytest.LogCaptureFixture): scoped_client._client.get_stream_id = MagicMock(return_value={"result": "failure"}) - with pytest.raises(RuntimeError): - scoped_client.get_stream_topics() + scoped_client.get_stream_topics() + + assert len(caplog.records) == 1 + assert caplog.records[0].message == "failed to get stream id for Test Stream" def test_send_message(scoped_client): From 195d606dff4228b9993abd08e518769dc2317a3c Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Fri, 22 Sep 2023 12:17:20 +0200 Subject: [PATCH 104/143] docs: document poe commands --- README.md | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0100b6c6..fee0a358 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,9 @@ async with httpx.AsyncClient() as client: response = await client.post(url, headers=headers, params=params, files=files) ``` -## Development Setup +## Development + +### Setup For docker: @@ -104,7 +106,8 @@ python3 -m pip install . poetry install poetry shell -damnit-zulip --help +# Start the server: +poe serve ``` To create a client for proposal 2222: @@ -126,6 +129,38 @@ Default configuration is something like: Stream/topic can be edited manually in the JSON file or set via CLI at creation time. +### Tasks + +This project uses `poe` as a task runner for various commands (see [Poe the Poet](https://github.com/nat-n/poethepoet)). + +List available commands with `poe`: + +```sh +$ poe + +CONFIGURED TASKS + serve + test + lint + format + ruff + black + mypy + pyright +``` + +Run a task with `poe `: + +```sh +poe lint # Run linters - only checks, no code changes + +poe format # Run formatters - changes files in place + +poe test # Run tests + +poe serve # Run the server +``` + ## Deployment Setup Deployment is similar to development with `docker compose`, but instead a docker stack is used to allow for better scaling and update configuration. From 19a3f64d8c691079f2769f5be431838994fe958e Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Fri, 6 Oct 2023 12:56:05 +0200 Subject: [PATCH 105/143] docs: simplify readme, update endpoints --- README.md | 91 +++++++++++++++++++++++++------------------------------ 1 file changed, 42 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index fee0a358..f6fa4b97 100644 --- a/README.md +++ b/README.md @@ -2,25 +2,18 @@ ## Usage -### Text Only +### Client + +Recommended clients are `requests` for synchronous code and `httpx` for asynchronous code. Using requests (synchronous): ```python import requests -url = "http://exfldadev01.desy.de:8089/message" -params = { - "topic": "test-read-only-thing-2", - "content": "I recommend muting this topic." -} -headers = { - "accept": "application/json", - "X-API-key": "GXXQoc8YlXJv2VDksX2Y7NzQAWdkdNeZ5fFvBLrCe6A", - "Content-Type": "multipart/form-data" -} +base_url = "http://exfldadev01.desy.de:8089" -response = requests.post(url, params=params, headers=headers) +response = requests.post(f"{base_url}/endpoint", data=data, ...) ``` Using httpx (async): @@ -28,58 +21,58 @@ Using httpx (async): ```python import httpx -url = "http://exfldadev01.desy.de:8089/message" -params = { - "topic": "test-read-only-thing-2", - "content": "I recommend muting this topic." -} -headers = { - "accept": "application/json", - "X-API-key": "GXXQoc8YlXJv2VDksX2Y7NzQAWdkdNeZ5fFvBLrCe6A", - "Content-Type": "multipart/form-data" -} +base_url = "http://exfldadev01.desy.de:8089" async with httpx.AsyncClient() as client: - response = await client.post(url, params=params, headers=headers) + response = await client.post(f"{base_url}/endpoint", ...) ``` -### Image/File Upload +### Authentication -Using requests (synchronous): +Authentication is done by including the token in the header, for example: ```python -import requests - -url = 'http://exfldadev01.desy.de:8089/message' headers = { - 'accept': 'application/json', - 'X-API-key': 'DQBMXmA6wmxsQLq4A27GErqD2pARI4IooOciNcmq3ng', -} -params = { - 'content': f'Bonk bonk', + "accept": "application/json", + "X-API-key": "token", + "Content-Type": "multipart/form-data" } -files = {'image': open('./downloads/recursion.jpg', 'rb')} +``` + +This header should be included in all requests. You can create a client which always has the header with: + +```python +import requests # or httpx -response = requests.post(url, headers=headers, params=params, files=files) +client = requests.Session() +client.headers.update({"X-API-key": "token"}) ``` -Using httpx (async): +### Endpoints + +Full API documentation is available by going to the `/docs` page (e.g. ). A few examples of basic usage are provided below. + +#### Sending a message with text ```python -import httpx +response = client.post( + f"{base_url}/send_message", + params={"topic": "test-read-only-thing-2"} + data={"content": "I recommend muting this topic."}, +) +``` -url = 'http://exfldadev01.desy.de:8089/message' -headers = { - 'accept': 'application/json', - 'X-API-key': 'DQBMXmA6wmxsQLq4A27GErqD2pARI4IooOciNcmq3ng', -} -params = { - 'content': f'Bonk bonk', -} -files = {'image': open('./downloads/recursion.jpg', 'rb')} +#### Sending a message with text and an image -async with httpx.AsyncClient() as client: - response = await client.post(url, headers=headers, params=params, files=files) +`/send_message` supports sending a file with a message. An inline link to the file will be included at the end of the text message. + +```python +response = client.post( + f"{base_url}/send_message", + params={"topic": "test-read-only-thing-2"} + data={'content': f'Interesting plot!'}, + files={'image': open('./downloads/recursion.jpg', 'rb')}, +) ``` ## Development @@ -161,7 +154,7 @@ poe test # Run tests poe serve # Run the server ``` -## Deployment Setup +## Deployment Deployment is similar to development with `docker compose`, but instead a docker stack is used to allow for better scaling and update configuration. From 67ed2574683c8be317a1cd3dbe5dbda3d22d7114 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Fri, 6 Oct 2023 12:56:32 +0200 Subject: [PATCH 106/143] build: bump pyright --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 69a9db98..59253e61 100644 --- a/poetry.lock +++ b/poetry.lock @@ -913,13 +913,13 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pyright" -version = "1.1.321" +version = "1.1.329" description = "Command line wrapper for pyright" optional = false python-versions = ">=3.7" files = [ - {file = "pyright-1.1.321-py3-none-any.whl", hash = "sha256:bc8ca2001b9f19b1cf75eb5ace71d833f9266b3981a5d88dac3b9bbcff8d9281"}, - {file = "pyright-1.1.321.tar.gz", hash = "sha256:f62d54fac611138a407bb302103700eeb37a147ce1a6e915148be9ca5961572a"}, + {file = "pyright-1.1.329-py3-none-any.whl", hash = "sha256:c16f88a7ac14ddd0513e62fec56d69c37e3c6b412161ad16aa23a9c7e3dabaf4"}, + {file = "pyright-1.1.329.tar.gz", hash = "sha256:5baf82ff5ecb8c8b3ac400e8536348efbde0b94a09d83d5b440c0d143fd151a8"}, ] [package.dependencies] From eed510f9208eaa16411f82bee6af15d23adafc3a Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Tue, 10 Oct 2023 15:14:44 +0200 Subject: [PATCH 107/143] fix(cli): run services setup before creating client --- src/zulip_write_only_proxy/cli.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/zulip_write_only_proxy/cli.py b/src/zulip_write_only_proxy/cli.py index a6a3acea..071dc5dd 100644 --- a/src/zulip_write_only_proxy/cli.py +++ b/src/zulip_write_only_proxy/cli.py @@ -13,6 +13,7 @@ def create( stream: Annotated[Optional[str], typer.Argument()] = None, ): """Create a new scoped client for a proposal.""" + services.setup() client = services.create_client(proposal_no, stream) typer.echo(client) From 746b1233253577e4e89215037c5f60dc6cbcc7d5 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Tue, 10 Oct 2023 15:15:51 +0200 Subject: [PATCH 108/143] fix(models): do not log stream as `extra` the `stream` dict contains a `msg` key which causes issues with stdlib logging --- src/zulip_write_only_proxy/models.py | 5 ++++- tests/test_models.py | 13 +++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/zulip_write_only_proxy/models.py b/src/zulip_write_only_proxy/models.py index bb76fa59..b8fb2799 100644 --- a/src/zulip_write_only_proxy/models.py +++ b/src/zulip_write_only_proxy/models.py @@ -48,7 +48,10 @@ def upload_file(self, file: IO[Any]): def get_stream_topics(self): stream = self._client.get_stream_id(self.stream) if stream["result"] != "success": - log.error(f"failed to get stream id for {self.stream}", extra=stream) + log.error( + f"failed to get stream id for {self.stream}, " + f"zulip api response: {stream}" + ) return stream stream_id = stream["stream_id"] return self._client.get_stream_topics(stream_id) diff --git a/tests/test_models.py b/tests/test_models.py index 780fa281..3185aa98 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -36,12 +36,21 @@ def test_get_stream_topics(scoped_client): def test_get_stream_topics_log(scoped_client, caplog: pytest.LogCaptureFixture): - scoped_client._client.get_stream_id = MagicMock(return_value={"result": "failure"}) + scoped_client._client.get_stream_id = MagicMock( + return_value={ + "code": "BAD_REQUEST", + "msg": "Invalid stream name 'nonexistent'", + "result": "error", + } + ) scoped_client.get_stream_topics() assert len(caplog.records) == 1 - assert caplog.records[0].message == "failed to get stream id for Test Stream" + assert caplog.records[0].message == ( + "failed to get stream id for Test Stream, zulip api response: " + f"{scoped_client._client.get_stream_id.return_value}" + ) def test_send_message(scoped_client): From 271ae8deefa79e67481594bbbe0e84c2d40bad30 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Thu, 12 Oct 2023 16:53:03 +0200 Subject: [PATCH 109/143] fix: rename `get_topics` to `get_stream_topics` --- src/zulip_write_only_proxy/main.py | 2 +- tests/test_fastapi.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index 46d50e37..f5ca1b6e 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -93,7 +93,7 @@ def upload_file( @app.get( - "/get_topics", + "/get_stream_topics", tags=["User"], response_description=f"See {_docs_url}", ) diff --git a/tests/test_fastapi.py b/tests/test_fastapi.py index fa01a8d5..2ff54c29 100644 --- a/tests/test_fastapi.py +++ b/tests/test_fastapi.py @@ -103,7 +103,7 @@ def test_upload_file(fastapi_client, zulip_client): assert uploaded_file.name == "test.jpg" -def test_get_topics(fastapi_client, zulip_client): +def test_get_stream_topics(fastapi_client, zulip_client): zulip_response_id = {"msg": "", "result": "success", "stream_id": 15} zulip_response_topics = { "msg": "", @@ -117,17 +117,17 @@ def test_get_topics(fastapi_client, zulip_client): zulip_client.get_stream_id = MagicMock(return_value=zulip_response_id) zulip_client.get_stream_topics = MagicMock(return_value=zulip_response_topics) - response = fastapi_client.get("/get_topics") + response = fastapi_client.get("/get_stream_topics") assert response.status_code == 200 assert response.json() == zulip_response_topics -def test_get_topics_error(fastapi_client, zulip_client): +def test_get_stream_topics_error(fastapi_client, zulip_client): zulip_response_id = {"result": "error"} zulip_client.get_stream_id = MagicMock(return_value=zulip_response_id) - response = fastapi_client.get("/get_topics") + response = fastapi_client.get("/get_stream_topics") assert response.status_code == 200 assert response.json() == {"result": "error"} From 0345ddcbafa5b2791a3f9577ca1004bf2771f52d Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Mon, 16 Oct 2023 10:55:42 +0200 Subject: [PATCH 110/143] feat(endpoint): make topic/content conditionally required for update message --- src/zulip_write_only_proxy/main.py | 15 +++++-- src/zulip_write_only_proxy/models.py | 12 ++++-- tests/test_fastapi.py | 60 ++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 7 deletions(-) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index f5ca1b6e..3891f267 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -64,11 +64,20 @@ def send_message( def update_message( client: Annotated[models.ScopedClient, fastapi.Depends(get_client)], message_id: Annotated[int, fastapi.Query(...)], - topic: Annotated[str, fastapi.Query(...)], propagate_mode: Annotated[models.PropagateMode, fastapi.Query(...)], - content: Annotated[str, fastapi.Body(...)], + content: Annotated[str | None, fastapi.Body(media_type="text/plain")] = None, + topic: Annotated[str | None, fastapi.Query()] = None, ): - return client.update_message(message_id, topic, propagate_mode, content) + if content or topic: + return client.update_message(topic, content, message_id, propagate_mode) + else: + raise fastapi.HTTPException( + status_code=400, + detail=( + "Either content (update message text) or topic (rename message topic) " + "must be provided" + ), + ) _docs_url = "https://zulip.com/api/upload-file#response" diff --git a/src/zulip_write_only_proxy/models.py b/src/zulip_write_only_proxy/models.py index b8fb2799..5ed72ccf 100644 --- a/src/zulip_write_only_proxy/models.py +++ b/src/zulip_write_only_proxy/models.py @@ -68,18 +68,22 @@ def send_message(self, topic: str, content: str): def update_message( self, + topic: str | None, + content: str | None, message_id: int, - topic: str, propagate_mode: PropagateMode, - content: str, ): request = { "message_id": message_id, - "topic": topic, "propagate_mode": propagate_mode.value, - "content": content, } + if topic: + request["topic"] = topic + + if content: + request["content"] = content + return self._client.update_message(request) diff --git a/tests/test_fastapi.py b/tests/test_fastapi.py index 2ff54c29..0dd7d8eb 100644 --- a/tests/test_fastapi.py +++ b/tests/test_fastapi.py @@ -80,6 +80,66 @@ def test_send_message_with_image(fastapi_client, zulip_client): assert uploaded_image.name == "test.jpg" +def test_update_message_move_topic(fastapi_client: "TestClient", zulip_client): + zulip_response = {"msg": "", "result": "success"} + zulip_client.update_message = MagicMock(return_value=zulip_response) + + response = fastapi_client.patch( + "/update_message", + params={"message_id": 42, "propagate_mode": "change_one", "topic": "New Topic"}, + ) + + assert response.status_code == 200 + assert response.json() == zulip_response + + # Check that the zulip client was called with the expected arguments + zulip_request = { + "message_id": 42, + "propagate_mode": "change_one", + "topic": "New Topic", + } + zulip_client.update_message.assert_called_once_with(zulip_request) + + +def test_update_message_content(fastapi_client: "TestClient", zulip_client): + zulip_response = {"msg": "", "result": "success"} + zulip_client.update_message = MagicMock(return_value=zulip_response) + + response = fastapi_client.patch( + "/update_message", + params={"message_id": 42, "propagate_mode": "change_one"}, + headers={"Content-Type": "text/plain"}, + content="Test Content", + ) + + assert response.status_code == 200 + assert response.json() == zulip_response + + # Check that the zulip client was called with the expected arguments + zulip_request = { + "message_id": 42, + "propagate_mode": "change_one", + "content": "Test Content", + } + zulip_client.update_message.assert_called_once_with(zulip_request) + + +def test_update_message_missing_args(fastapi_client: "TestClient", zulip_client): + response = fastapi_client.patch( + "/update_message", + params={"message_id": 42, "propagate_mode": "change_one"}, + headers={"Content-Type": "text/plain"}, + ) + + assert response.status_code == 400 + assert response.json() == { + "detail": ( + "Either content (update message text) or topic (rename message topic) must " + "be provided" + ) + } + + def test_upload_file(fastapi_client, zulip_client): zulip_response_file = { "msg": "", From da051788b86aa24d79e03c7acad1d00e5ef40dc1 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Mon, 16 Oct 2023 10:56:12 +0200 Subject: [PATCH 111/143] test: exclude cli module from coverage --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 4aecc4c6..7dd8cb79 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,6 +71,10 @@ black = "black ./src ./tests" mypy = "mypy ./src ./tests" pyright = "pyright ./src ./tests" +[tool.coverage.run] +# Exclude CLI from coverage as it is not really required anymore +omit = ["src/zulip_write_only_proxy/cli.py"] + [tool.commitizen] name = "cz_conventional_commits" tag_format = "v$version" From 702c8b3234ae45b2293a10a0d51aa61065d3d509 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Mon, 16 Oct 2023 10:56:34 +0200 Subject: [PATCH 112/143] build(deps): update pyright --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 59253e61..de514f99 100644 --- a/poetry.lock +++ b/poetry.lock @@ -913,13 +913,13 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pyright" -version = "1.1.329" +version = "1.1.331" description = "Command line wrapper for pyright" optional = false python-versions = ">=3.7" files = [ - {file = "pyright-1.1.329-py3-none-any.whl", hash = "sha256:c16f88a7ac14ddd0513e62fec56d69c37e3c6b412161ad16aa23a9c7e3dabaf4"}, - {file = "pyright-1.1.329.tar.gz", hash = "sha256:5baf82ff5ecb8c8b3ac400e8536348efbde0b94a09d83d5b440c0d143fd151a8"}, + {file = "pyright-1.1.331-py3-none-any.whl", hash = "sha256:d200a01794e7f2a04d5042a6c3abee36ce92780287d3037edfc3604d45488f0e"}, + {file = "pyright-1.1.331.tar.gz", hash = "sha256:c3e7b86154cac86c3bd61ea0f963143d001c201e246825aaabdddfcce5d04293"}, ] [package.dependencies] From 8fd34b1aa4d36c5ec5a071a144de10b474c7def4 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Mon, 16 Oct 2023 16:23:41 +0200 Subject: [PATCH 113/143] fix: add `from __future__ import annotations` --- src/zulip_write_only_proxy/main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index 3891f267..445ed7bd 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from contextlib import asynccontextmanager from tempfile import SpooledTemporaryFile from typing import Annotated, Union From add555786fc33461385a76250f151e890a57ed61 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Fri, 27 Oct 2023 14:49:16 +0200 Subject: [PATCH 114/143] test: remove python 3.9 from test matrix --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3c61ef6f..c9fdaa84 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.9", "3.11", "3.12-dev"] + python-version: ["3.11", "3.12-dev"] steps: - name: Checkout project uses: actions/checkout@v3 From d5ca16ba8319e7e86ec16cc70fdda20f2ede02a0 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Fri, 27 Oct 2023 14:56:02 +0200 Subject: [PATCH 115/143] build: bump python, fastapi, pydantic, ruff --- poetry.lock | 907 ++++++++++++++++++++++++------------------------- pyproject.toml | 12 +- 2 files changed, 452 insertions(+), 467 deletions(-) diff --git a/poetry.lock b/poetry.lock index de514f99..343cf125 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,13 +2,13 @@ [[package]] name = "annotated-types" -version = "0.5.0" +version = "0.6.0" description = "Reusable constraint types to use with typing.Annotated" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "annotated_types-0.5.0-py3-none-any.whl", hash = "sha256:58da39888f92c276ad970249761ebea80ba544b77acddaa1a4d6cf78287d45fd"}, - {file = "annotated_types-0.5.0.tar.gz", hash = "sha256:47cdc3490d9ac1506ce92c7aaa76c579dc3509ff11e098fc867e5130ab7be802"}, + {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, + {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, ] [[package]] @@ -23,7 +23,6 @@ files = [ ] [package.dependencies] -exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" @@ -34,13 +33,13 @@ trio = ["trio (<0.22)"] [[package]] name = "argcomplete" -version = "3.1.1" +version = "3.1.2" description = "Bash tab completion for argparse" optional = false python-versions = ">=3.6" files = [ - {file = "argcomplete-3.1.1-py3-none-any.whl", hash = "sha256:35fa893a88deea85ea7b20d241100e64516d6af6d7b0ae2bed1d263d26f70948"}, - {file = "argcomplete-3.1.1.tar.gz", hash = "sha256:6c4c563f14f01440aaffa3eae13441c5db2357b5eec639abe7c0b15334627dff"}, + {file = "argcomplete-3.1.2-py3-none-any.whl", hash = "sha256:d97c036d12a752d1079f190bc1521c545b941fda89ad85d15afa909b4d1b9a99"}, + {file = "argcomplete-3.1.2.tar.gz", hash = "sha256:d5d1e5efd41435260b8f85673b74ea2e883affcbec9f4230c582689e8e78251b"}, ] [package.extras] @@ -48,33 +47,29 @@ test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] [[package]] name = "black" -version = "23.7.0" +version = "23.10.1" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" files = [ - {file = "black-23.7.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:5c4bc552ab52f6c1c506ccae05681fab58c3f72d59ae6e6639e8885e94fe2587"}, - {file = "black-23.7.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:552513d5cd5694590d7ef6f46e1767a4df9af168d449ff767b13b084c020e63f"}, - {file = "black-23.7.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:86cee259349b4448adb4ef9b204bb4467aae74a386bce85d56ba4f5dc0da27be"}, - {file = "black-23.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:501387a9edcb75d7ae8a4412bb8749900386eaef258f1aefab18adddea1936bc"}, - {file = "black-23.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb074d8b213749fa1d077d630db0d5f8cc3b2ae63587ad4116e8a436e9bbe995"}, - {file = "black-23.7.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b5b0ee6d96b345a8b420100b7d71ebfdd19fab5e8301aff48ec270042cd40ac2"}, - {file = "black-23.7.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:893695a76b140881531062d48476ebe4a48f5d1e9388177e175d76234ca247cd"}, - {file = "black-23.7.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:c333286dc3ddca6fdff74670b911cccedacb4ef0a60b34e491b8a67c833b343a"}, - {file = "black-23.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831d8f54c3a8c8cf55f64d0422ee875eecac26f5f649fb6c1df65316b67c8926"}, - {file = "black-23.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:7f3bf2dec7d541b4619b8ce526bda74a6b0bffc480a163fed32eb8b3c9aed8ad"}, - {file = "black-23.7.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:f9062af71c59c004cd519e2fb8f5d25d39e46d3af011b41ab43b9c74e27e236f"}, - {file = "black-23.7.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:01ede61aac8c154b55f35301fac3e730baf0c9cf8120f65a9cd61a81cfb4a0c3"}, - {file = "black-23.7.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:327a8c2550ddc573b51e2c352adb88143464bb9d92c10416feb86b0f5aee5ff6"}, - {file = "black-23.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1c6022b86f83b632d06f2b02774134def5d4d4f1dac8bef16d90cda18ba28a"}, - {file = "black-23.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:27eb7a0c71604d5de083757fbdb245b1a4fae60e9596514c6ec497eb63f95320"}, - {file = "black-23.7.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:8417dbd2f57b5701492cd46edcecc4f9208dc75529bcf76c514864e48da867d9"}, - {file = "black-23.7.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:47e56d83aad53ca140da0af87678fb38e44fd6bc0af71eebab2d1f59b1acf1d3"}, - {file = "black-23.7.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:25cc308838fe71f7065df53aedd20327969d05671bac95b38fdf37ebe70ac087"}, - {file = "black-23.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:642496b675095d423f9b8448243336f8ec71c9d4d57ec17bf795b67f08132a91"}, - {file = "black-23.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:ad0014efc7acf0bd745792bd0d8857413652979200ab924fbf239062adc12491"}, - {file = "black-23.7.0-py3-none-any.whl", hash = "sha256:9fd59d418c60c0348505f2ddf9609c1e1de8e7493eab96198fc89d9f865e7a96"}, - {file = "black-23.7.0.tar.gz", hash = "sha256:022a582720b0d9480ed82576c920a8c1dde97cc38ff11d8d8859b3bd6ca9eedb"}, + {file = "black-23.10.1-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:ec3f8e6234c4e46ff9e16d9ae96f4ef69fa328bb4ad08198c8cee45bb1f08c69"}, + {file = "black-23.10.1-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:1b917a2aa020ca600483a7b340c165970b26e9029067f019e3755b56e8dd5916"}, + {file = "black-23.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c74de4c77b849e6359c6f01987e94873c707098322b91490d24296f66d067dc"}, + {file = "black-23.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:7b4d10b0f016616a0d93d24a448100adf1699712fb7a4efd0e2c32bbb219b173"}, + {file = "black-23.10.1-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b15b75fc53a2fbcac8a87d3e20f69874d161beef13954747e053bca7a1ce53a0"}, + {file = "black-23.10.1-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:e293e4c2f4a992b980032bbd62df07c1bcff82d6964d6c9496f2cd726e246ace"}, + {file = "black-23.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d56124b7a61d092cb52cce34182a5280e160e6aff3137172a68c2c2c4b76bcb"}, + {file = "black-23.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:3f157a8945a7b2d424da3335f7ace89c14a3b0625e6593d21139c2d8214d55ce"}, + {file = "black-23.10.1-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:cfcce6f0a384d0da692119f2d72d79ed07c7159879d0bb1bb32d2e443382bf3a"}, + {file = "black-23.10.1-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:33d40f5b06be80c1bbce17b173cda17994fbad096ce60eb22054da021bf933d1"}, + {file = "black-23.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:840015166dbdfbc47992871325799fd2dc0dcf9395e401ada6d88fe11498abad"}, + {file = "black-23.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:037e9b4664cafda5f025a1728c50a9e9aedb99a759c89f760bd83730e76ba884"}, + {file = "black-23.10.1-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:7cb5936e686e782fddb1c73f8aa6f459e1ad38a6a7b0e54b403f1f05a1507ee9"}, + {file = "black-23.10.1-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:7670242e90dc129c539e9ca17665e39a146a761e681805c54fbd86015c7c84f7"}, + {file = "black-23.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ed45ac9a613fb52dad3b61c8dea2ec9510bf3108d4db88422bacc7d1ba1243d"}, + {file = "black-23.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:6d23d7822140e3fef190734216cefb262521789367fbdc0b3f22af6744058982"}, + {file = "black-23.10.1-py3-none-any.whl", hash = "sha256:d431e6739f727bb2e0495df64a6c7a5310758e87505f5f8cde9ff6c0f2d7e4fe"}, + {file = "black-23.10.1.tar.gz", hash = "sha256:1f8ce316753428ff68749c65a5f7844631aa18c8679dfd3ca9dc1a289979c258"}, ] [package.dependencies] @@ -83,8 +78,6 @@ mypy-extensions = ">=0.4.3" packaging = ">=22.0" pathspec = ">=0.9.0" platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} [package.extras] colorama = ["colorama (>=0.4.3)"] @@ -105,97 +98,112 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.2.0" +version = "3.3.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"}, - {file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, + {file = "charset-normalizer-3.3.1.tar.gz", hash = "sha256:d9137a876020661972ca6eec0766d81aef8a5627df628b664b234b73396e727e"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8aee051c89e13565c6bd366813c386939f8e928af93c29fda4af86d25b73d8f8"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:352a88c3df0d1fa886562384b86f9a9e27563d4704ee0e9d56ec6fcd270ea690"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:223b4d54561c01048f657fa6ce41461d5ad8ff128b9678cfe8b2ecd951e3f8a2"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f861d94c2a450b974b86093c6c027888627b8082f1299dfd5a4bae8e2292821"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1171ef1fc5ab4693c5d151ae0fdad7f7349920eabbaca6271f95969fa0756c2d"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28f512b9a33235545fbbdac6a330a510b63be278a50071a336afc1b78781b147"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0e842112fe3f1a4ffcf64b06dc4c61a88441c2f02f373367f7b4c1aa9be2ad5"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f9bc2ce123637a60ebe819f9fccc614da1bcc05798bbbaf2dd4ec91f3e08846"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f194cce575e59ffe442c10a360182a986535fd90b57f7debfaa5c845c409ecc3"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9a74041ba0bfa9bc9b9bb2cd3238a6ab3b7618e759b41bd15b5f6ad958d17605"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b578cbe580e3b41ad17b1c428f382c814b32a6ce90f2d8e39e2e635d49e498d1"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6db3cfb9b4fcecb4390db154e75b49578c87a3b9979b40cdf90d7e4b945656e1"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:debb633f3f7856f95ad957d9b9c781f8e2c6303ef21724ec94bea2ce2fcbd056"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-win32.whl", hash = "sha256:87071618d3d8ec8b186d53cb6e66955ef2a0e4fa63ccd3709c0c90ac5a43520f"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:e372d7dfd154009142631de2d316adad3cc1c36c32a38b16a4751ba78da2a397"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae4070f741f8d809075ef697877fd350ecf0b7c5837ed68738607ee0a2c572cf"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:58e875eb7016fd014c0eea46c6fa92b87b62c0cb31b9feae25cbbe62c919f54d"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dbd95e300367aa0827496fe75a1766d198d34385a58f97683fe6e07f89ca3e3c"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de0b4caa1c8a21394e8ce971997614a17648f94e1cd0640fbd6b4d14cab13a72"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:985c7965f62f6f32bf432e2681173db41336a9c2611693247069288bcb0c7f8b"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a15c1fe6d26e83fd2e5972425a772cca158eae58b05d4a25a4e474c221053e2d"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae55d592b02c4349525b6ed8f74c692509e5adffa842e582c0f861751701a673"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be4d9c2770044a59715eb57c1144dedea7c5d5ae80c68fb9959515037cde2008"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:851cf693fb3aaef71031237cd68699dded198657ec1e76a76eb8be58c03a5d1f"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:31bbaba7218904d2eabecf4feec0d07469284e952a27400f23b6628439439fa7"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:871d045d6ccc181fd863a3cd66ee8e395523ebfbc57f85f91f035f50cee8e3d4"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:501adc5eb6cd5f40a6f77fbd90e5ab915c8fd6e8c614af2db5561e16c600d6f3"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f5fb672c396d826ca16a022ac04c9dce74e00a1c344f6ad1a0fdc1ba1f332213"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-win32.whl", hash = "sha256:bb06098d019766ca16fc915ecaa455c1f1cd594204e7f840cd6258237b5079a8"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:8af5a8917b8af42295e86b64903156b4f110a30dca5f3b5aedea123fbd638bff"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7ae8e5142dcc7a49168f4055255dbcced01dc1714a90a21f87448dc8d90617d1"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5b70bab78accbc672f50e878a5b73ca692f45f5b5e25c8066d748c09405e6a55"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ceca5876032362ae73b83347be8b5dbd2d1faf3358deb38c9c88776779b2e2f"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34d95638ff3613849f473afc33f65c401a89f3b9528d0d213c7037c398a51296"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9edbe6a5bf8b56a4a84533ba2b2f489d0046e755c29616ef8830f9e7d9cf5728"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6a02a3c7950cafaadcd46a226ad9e12fc9744652cc69f9e5534f98b47f3bbcf"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10b8dd31e10f32410751b3430996f9807fc4d1587ca69772e2aa940a82ab571a"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edc0202099ea1d82844316604e17d2b175044f9bcb6b398aab781eba957224bd"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b891a2f68e09c5ef989007fac11476ed33c5c9994449a4e2c3386529d703dc8b"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:71ef3b9be10070360f289aea4838c784f8b851be3ba58cf796262b57775c2f14"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:55602981b2dbf8184c098bc10287e8c245e351cd4fdcad050bd7199d5a8bf514"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:46fb9970aa5eeca547d7aa0de5d4b124a288b42eaefac677bde805013c95725c"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:520b7a142d2524f999447b3a0cf95115df81c4f33003c51a6ab637cbda9d0bf4"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-win32.whl", hash = "sha256:8ec8ef42c6cd5856a7613dcd1eaf21e5573b2185263d87d27c8edcae33b62a61"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:baec8148d6b8bd5cee1ae138ba658c71f5b03e0d69d5907703e3e1df96db5e41"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63a6f59e2d01310f754c270e4a257426fe5a591dc487f1983b3bbe793cf6bac6"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d6bfc32a68bc0933819cfdfe45f9abc3cae3877e1d90aac7259d57e6e0f85b1"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f3100d86dcd03c03f7e9c3fdb23d92e32abbca07e7c13ebd7ddfbcb06f5991f"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39b70a6f88eebe239fa775190796d55a33cfb6d36b9ffdd37843f7c4c1b5dc67"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e12f8ee80aa35e746230a2af83e81bd6b52daa92a8afaef4fea4a2ce9b9f4fa"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b6cefa579e1237ce198619b76eaa148b71894fb0d6bcf9024460f9bf30fd228"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:61f1e3fb621f5420523abb71f5771a204b33c21d31e7d9d86881b2cffe92c47c"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4f6e2a839f83a6a76854d12dbebde50e4b1afa63e27761549d006fa53e9aa80e"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:1ec937546cad86d0dce5396748bf392bb7b62a9eeb8c66efac60e947697f0e58"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:82ca51ff0fc5b641a2d4e1cc8c5ff108699b7a56d7f3ad6f6da9dbb6f0145b48"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:633968254f8d421e70f91c6ebe71ed0ab140220469cf87a9857e21c16687c034"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-win32.whl", hash = "sha256:c0c72d34e7de5604df0fde3644cc079feee5e55464967d10b24b1de268deceb9"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:63accd11149c0f9a99e3bc095bbdb5a464862d77a7e309ad5938fbc8721235ae"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5a3580a4fdc4ac05f9e53c57f965e3594b2f99796231380adb2baaab96e22761"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2465aa50c9299d615d757c1c888bc6fef384b7c4aec81c05a0172b4400f98557"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cb7cd68814308aade9d0c93c5bd2ade9f9441666f8ba5aa9c2d4b389cb5e2a45"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91e43805ccafa0a91831f9cd5443aa34528c0c3f2cc48c4cb3d9a7721053874b"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:854cc74367180beb327ab9d00f964f6d91da06450b0855cbbb09187bcdb02de5"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c15070ebf11b8b7fd1bfff7217e9324963c82dbdf6182ff7050519e350e7ad9f"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c4c99f98fc3a1835af8179dcc9013f93594d0670e2fa80c83aa36346ee763d2"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fb765362688821404ad6cf86772fc54993ec11577cd5a92ac44b4c2ba52155b"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dced27917823df984fe0c80a5c4ad75cf58df0fbfae890bc08004cd3888922a2"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a66bcdf19c1a523e41b8e9d53d0cedbfbac2e93c649a2e9502cb26c014d0980c"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ecd26be9f112c4f96718290c10f4caea6cc798459a3a76636b817a0ed7874e42"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3f70fd716855cd3b855316b226a1ac8bdb3caf4f7ea96edcccc6f484217c9597"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:17a866d61259c7de1bdadef418a37755050ddb4b922df8b356503234fff7932c"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-win32.whl", hash = "sha256:548eefad783ed787b38cb6f9a574bd8664468cc76d1538215d510a3cd41406cb"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:45f053a0ece92c734d874861ffe6e3cc92150e32136dd59ab1fb070575189c97"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bc791ec3fd0c4309a753f95bb6c749ef0d8ea3aea91f07ee1cf06b7b02118f2f"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0c8c61fb505c7dad1d251c284e712d4e0372cef3b067f7ddf82a7fa82e1e9a93"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2c092be3885a1b7899cd85ce24acedc1034199d6fca1483fa2c3a35c86e43041"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2000c54c395d9e5e44c99dc7c20a64dc371f777faf8bae4919ad3e99ce5253e"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4cb50a0335382aac15c31b61d8531bc9bb657cfd848b1d7158009472189f3d62"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c30187840d36d0ba2893bc3271a36a517a717f9fd383a98e2697ee890a37c273"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe81b35c33772e56f4b6cf62cf4aedc1762ef7162a31e6ac7fe5e40d0149eb67"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0bf89afcbcf4d1bb2652f6580e5e55a840fdf87384f6063c4a4f0c95e378656"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:06cf46bdff72f58645434d467bf5228080801298fbba19fe268a01b4534467f5"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3c66df3f41abee950d6638adc7eac4730a306b022570f71dd0bd6ba53503ab57"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd805513198304026bd379d1d516afbf6c3c13f4382134a2c526b8b854da1c2e"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:9505dc359edb6a330efcd2be825fdb73ee3e628d9010597aa1aee5aa63442e97"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:31445f38053476a0c4e6d12b047b08ced81e2c7c712e5a1ad97bc913256f91b2"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-win32.whl", hash = "sha256:bd28b31730f0e982ace8663d108e01199098432a30a4c410d06fe08fdb9e93f4"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:555fe186da0068d3354cdf4bbcbc609b0ecae4d04c921cc13e209eece7720727"}, + {file = "charset_normalizer-3.3.1-py3-none-any.whl", hash = "sha256:800561453acdecedaac137bf09cd719c7a440b6800ec182f077bb8e7025fb708"}, ] [[package]] name = "click" -version = "8.1.6" +version = "8.1.7" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.6-py3-none-any.whl", hash = "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"}, - {file = "click-8.1.6.tar.gz", hash = "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd"}, + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, ] [package.dependencies] @@ -214,13 +222,13 @@ files = [ [[package]] name = "commitizen" -version = "3.6.0" +version = "3.12.0" description = "Python commitizen client tool" optional = false -python-versions = ">=3.7,<4.0" +python-versions = ">=3.8" files = [ - {file = "commitizen-3.6.0-py3-none-any.whl", hash = "sha256:4414724b306b252d08a5ae6701401c65cd53554d446c0cc4cedcae7ab8591a5a"}, - {file = "commitizen-3.6.0.tar.gz", hash = "sha256:979f659f9fc071c675f41796bb7c56a827aacc2e312db4ec3920951211a72ce3"}, + {file = "commitizen-3.12.0-py3-none-any.whl", hash = "sha256:082f4733409bc4f01f987467295f8393ceb16b42cc648cf2f5a7a754c6d594db"}, + {file = "commitizen-3.12.0.tar.gz", hash = "sha256:7c313f1f85f45c9acf1a70f1637deab5c388150ae8660a0037ac260e77bb1492"}, ] [package.dependencies] @@ -232,82 +240,71 @@ importlib_metadata = ">=4.13,<7" jinja2 = ">=2.10.3" packaging = ">=19" pyyaml = ">=3.08" -questionary = ">=1.4.0,<2.0.0" +questionary = ">=2.0,<3.0" termcolor = ">=1.1,<3" tomlkit = ">=0.5.3,<1.0.0" [[package]] name = "coverage" -version = "7.2.7" +version = "7.3.2" description = "Code coverage measurement for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"}, - {file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"}, - {file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"}, - {file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"}, - {file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"}, - {file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"}, - {file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"}, - {file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"}, - {file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"}, - {file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"}, - {file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"}, - {file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"}, - {file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"}, - {file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"}, - {file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"}, - {file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"}, - {file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"}, - {file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"}, - {file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"}, - {file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"}, - {file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"}, - {file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"}, - {file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"}, - {file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"}, + {file = "coverage-7.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d872145f3a3231a5f20fd48500274d7df222e291d90baa2026cc5152b7ce86bf"}, + {file = "coverage-7.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:310b3bb9c91ea66d59c53fa4989f57d2436e08f18fb2f421a1b0b6b8cc7fffda"}, + {file = "coverage-7.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47d39359e2c3779c5331fc740cf4bce6d9d680a7b4b4ead97056a0ae07cb49a"}, + {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa72dbaf2c2068404b9870d93436e6d23addd8bbe9295f49cbca83f6e278179c"}, + {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beaa5c1b4777f03fc63dfd2a6bd820f73f036bfb10e925fce067b00a340d0f3f"}, + {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dbc1b46b92186cc8074fee9d9fbb97a9dd06c6cbbef391c2f59d80eabdf0faa6"}, + {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:315a989e861031334d7bee1f9113c8770472db2ac484e5b8c3173428360a9148"}, + {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d1bc430677773397f64a5c88cb522ea43175ff16f8bfcc89d467d974cb2274f9"}, + {file = "coverage-7.3.2-cp310-cp310-win32.whl", hash = "sha256:a889ae02f43aa45032afe364c8ae84ad3c54828c2faa44f3bfcafecb5c96b02f"}, + {file = "coverage-7.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0ba320de3fb8c6ec16e0be17ee1d3d69adcda99406c43c0409cb5c41788a611"}, + {file = "coverage-7.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ac8c802fa29843a72d32ec56d0ca792ad15a302b28ca6203389afe21f8fa062c"}, + {file = "coverage-7.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:89a937174104339e3a3ffcf9f446c00e3a806c28b1841c63edb2b369310fd074"}, + {file = "coverage-7.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e267e9e2b574a176ddb983399dec325a80dbe161f1a32715c780b5d14b5f583a"}, + {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2443cbda35df0d35dcfb9bf8f3c02c57c1d6111169e3c85fc1fcc05e0c9f39a3"}, + {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4175e10cc8dda0265653e8714b3174430b07c1dca8957f4966cbd6c2b1b8065a"}, + {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf38419fb1a347aaf63481c00f0bdc86889d9fbf3f25109cf96c26b403fda1"}, + {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5c913b556a116b8d5f6ef834038ba983834d887d82187c8f73dec21049abd65c"}, + {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1981f785239e4e39e6444c63a98da3a1db8e971cb9ceb50a945ba6296b43f312"}, + {file = "coverage-7.3.2-cp311-cp311-win32.whl", hash = "sha256:43668cabd5ca8258f5954f27a3aaf78757e6acf13c17604d89648ecc0cc66640"}, + {file = "coverage-7.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10c39c0452bf6e694511c901426d6b5ac005acc0f78ff265dbe36bf81f808a2"}, + {file = "coverage-7.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4cbae1051ab791debecc4a5dcc4a1ff45fc27b91b9aee165c8a27514dd160836"}, + {file = "coverage-7.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12d15ab5833a997716d76f2ac1e4b4d536814fc213c85ca72756c19e5a6b3d63"}, + {file = "coverage-7.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7bba973ebee5e56fe9251300c00f1579652587a9f4a5ed8404b15a0471f216"}, + {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe494faa90ce6381770746077243231e0b83ff3f17069d748f645617cefe19d4"}, + {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6e9589bd04d0461a417562649522575d8752904d35c12907d8c9dfeba588faf"}, + {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d51ac2a26f71da1b57f2dc81d0e108b6ab177e7d30e774db90675467c847bbdf"}, + {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99b89d9f76070237975b315b3d5f4d6956ae354a4c92ac2388a5695516e47c84"}, + {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fa28e909776dc69efb6ed975a63691bc8172b64ff357e663a1bb06ff3c9b589a"}, + {file = "coverage-7.3.2-cp312-cp312-win32.whl", hash = "sha256:289fe43bf45a575e3ab10b26d7b6f2ddb9ee2dba447499f5401cfb5ecb8196bb"}, + {file = "coverage-7.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dbc3ed60e8659bc59b6b304b43ff9c3ed858da2839c78b804973f613d3e92ed"}, + {file = "coverage-7.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f94b734214ea6a36fe16e96a70d941af80ff3bfd716c141300d95ebc85339738"}, + {file = "coverage-7.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af3d828d2c1cbae52d34bdbb22fcd94d1ce715d95f1a012354a75e5913f1bda2"}, + {file = "coverage-7.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630b13e3036e13c7adc480ca42fa7afc2a5d938081d28e20903cf7fd687872e2"}, + {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9eacf273e885b02a0273bb3a2170f30e2d53a6d53b72dbe02d6701b5296101c"}, + {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f17966e861ff97305e0801134e69db33b143bbfb36436efb9cfff6ec7b2fd9"}, + {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b4275802d16882cf9c8b3d057a0839acb07ee9379fa2749eca54efbce1535b82"}, + {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72c0cfa5250f483181e677ebc97133ea1ab3eb68645e494775deb6a7f6f83901"}, + {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb536f0dcd14149425996821a168f6e269d7dcd2c273a8bff8201e79f5104e76"}, + {file = "coverage-7.3.2-cp38-cp38-win32.whl", hash = "sha256:307adb8bd3abe389a471e649038a71b4eb13bfd6b7dd9a129fa856f5c695cf92"}, + {file = "coverage-7.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:88ed2c30a49ea81ea3b7f172e0269c182a44c236eb394718f976239892c0a27a"}, + {file = "coverage-7.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b631c92dfe601adf8f5ebc7fc13ced6bb6e9609b19d9a8cd59fa47c4186ad1ce"}, + {file = "coverage-7.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d3d9df4051c4a7d13036524b66ecf7a7537d14c18a384043f30a303b146164e9"}, + {file = "coverage-7.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7363d3b6a1119ef05015959ca24a9afc0ea8a02c687fe7e2d557705375c01f"}, + {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f11cc3c967a09d3695d2a6f03fb3e6236622b93be7a4b5dc09166a861be6d25"}, + {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:149de1d2401ae4655c436a3dced6dd153f4c3309f599c3d4bd97ab172eaf02d9"}, + {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3a4006916aa6fee7cd38db3bfc95aa9c54ebb4ffbfc47c677c8bba949ceba0a6"}, + {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9028a3871280110d6e1aa2df1afd5ef003bab5fb1ef421d6dc748ae1c8ef2ebc"}, + {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f805d62aec8eb92bab5b61c0f07329275b6f41c97d80e847b03eb894f38d083"}, + {file = "coverage-7.3.2-cp39-cp39-win32.whl", hash = "sha256:d1c88ec1a7ff4ebca0219f5b1ef863451d828cccf889c173e1253aa84b1e07ce"}, + {file = "coverage-7.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4767da59464bb593c07afceaddea61b154136300881844768037fd5e859353f"}, + {file = "coverage-7.3.2-pp38.pp39.pp310-none-any.whl", hash = "sha256:ae97af89f0fbf373400970c0a21eef5aa941ffeed90aee43650b81f7d7f47637"}, + {file = "coverage-7.3.2.tar.gz", hash = "sha256:be32ad29341b0170e795ca590e1c07e81fc061cb5b10c74ce7203491484404ef"}, ] -[package.dependencies] -tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} - [package.extras] toml = ["tomli"] @@ -333,35 +330,22 @@ files = [ {file = "distro-1.8.0.tar.gz", hash = "sha256:02e111d1dc6a50abb8eed6bf31c3e48ed8b0830d1ea2a1b78c61765c2513fdd8"}, ] -[[package]] -name = "exceptiongroup" -version = "1.1.2" -description = "Backport of PEP 654 (exception groups)" -optional = false -python-versions = ">=3.7" -files = [ - {file = "exceptiongroup-1.1.2-py3-none-any.whl", hash = "sha256:e346e69d186172ca7cf029c8c1d16235aa0e04035e5750b4b95039e65204328f"}, - {file = "exceptiongroup-1.1.2.tar.gz", hash = "sha256:12c3e887d6485d16943a309616de20ae5582633e0a2eda17f4e10fd61c1e8af5"}, -] - -[package.extras] -test = ["pytest (>=6)"] - [[package]] name = "fastapi" -version = "0.100.1" +version = "0.104.0" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "fastapi-0.100.1-py3-none-any.whl", hash = "sha256:ec6dd52bfc4eff3063cfcd0713b43c87640fefb2687bbbe3d8a08d94049cdf32"}, - {file = "fastapi-0.100.1.tar.gz", hash = "sha256:522700d7a469e4a973d92321ab93312448fbe20fca9c8da97effc7e7bc56df23"}, + {file = "fastapi-0.104.0-py3-none-any.whl", hash = "sha256:456482c1178fb7beb2814b88e1885bc49f9a81f079665016feffe3e1c6a7663e"}, + {file = "fastapi-0.104.0.tar.gz", hash = "sha256:9c44de45693ae037b0c6914727a29c49a40668432b67c859a87851fc6a7b74c6"}, ] [package.dependencies] -pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<3.0.0" +anyio = ">=3.7.1,<4.0.0" +pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" starlette = ">=0.27.0,<0.28.0" -typing-extensions = ">=4.5.0" +typing-extensions = ">=4.8.0" [package.extras] all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] @@ -560,48 +544,47 @@ test = ["pytest (>=4.6,<6.0.0)", "responses (>=0.10.6,<0.11.dev0)"] [[package]] name = "mypy" -version = "1.4.1" +version = "1.6.1" description = "Optional static typing for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8"}, - {file = "mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878"}, - {file = "mypy-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd"}, - {file = "mypy-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc"}, - {file = "mypy-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1"}, - {file = "mypy-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462"}, - {file = "mypy-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258"}, - {file = "mypy-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2"}, - {file = "mypy-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7"}, - {file = "mypy-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01"}, - {file = "mypy-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b"}, - {file = "mypy-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b"}, - {file = "mypy-1.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7"}, - {file = "mypy-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9"}, - {file = "mypy-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042"}, - {file = "mypy-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3"}, - {file = "mypy-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6"}, - {file = "mypy-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f"}, - {file = "mypy-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc"}, - {file = "mypy-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828"}, - {file = "mypy-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3"}, - {file = "mypy-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816"}, - {file = "mypy-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c"}, - {file = "mypy-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f"}, - {file = "mypy-1.4.1-py3-none-any.whl", hash = "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4"}, - {file = "mypy-1.4.1.tar.gz", hash = "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b"}, + {file = "mypy-1.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e5012e5cc2ac628177eaac0e83d622b2dd499e28253d4107a08ecc59ede3fc2c"}, + {file = "mypy-1.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d8fbb68711905f8912e5af474ca8b78d077447d8f3918997fecbf26943ff3cbb"}, + {file = "mypy-1.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a1ad938fee7d2d96ca666c77b7c494c3c5bd88dff792220e1afbebb2925b5e"}, + {file = "mypy-1.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b96ae2c1279d1065413965c607712006205a9ac541895004a1e0d4f281f2ff9f"}, + {file = "mypy-1.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:40b1844d2e8b232ed92e50a4bd11c48d2daa351f9deee6c194b83bf03e418b0c"}, + {file = "mypy-1.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:81af8adaa5e3099469e7623436881eff6b3b06db5ef75e6f5b6d4871263547e5"}, + {file = "mypy-1.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8c223fa57cb154c7eab5156856c231c3f5eace1e0bed9b32a24696b7ba3c3245"}, + {file = "mypy-1.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8032e00ce71c3ceb93eeba63963b864bf635a18f6c0c12da6c13c450eedb183"}, + {file = "mypy-1.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4c46b51de523817a0045b150ed11b56f9fff55f12b9edd0f3ed35b15a2809de0"}, + {file = "mypy-1.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:19f905bcfd9e167159b3d63ecd8cb5e696151c3e59a1742e79bc3bcb540c42c7"}, + {file = "mypy-1.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:82e469518d3e9a321912955cc702d418773a2fd1e91c651280a1bda10622f02f"}, + {file = "mypy-1.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d4473c22cc296425bbbce7e9429588e76e05bc7342da359d6520b6427bf76660"}, + {file = "mypy-1.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59a0d7d24dfb26729e0a068639a6ce3500e31d6655df8557156c51c1cb874ce7"}, + {file = "mypy-1.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cfd13d47b29ed3bbaafaff7d8b21e90d827631afda134836962011acb5904b71"}, + {file = "mypy-1.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:eb4f18589d196a4cbe5290b435d135dee96567e07c2b2d43b5c4621b6501531a"}, + {file = "mypy-1.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:41697773aa0bf53ff917aa077e2cde7aa50254f28750f9b88884acea38a16169"}, + {file = "mypy-1.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7274b0c57737bd3476d2229c6389b2ec9eefeb090bbaf77777e9d6b1b5a9d143"}, + {file = "mypy-1.6.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbaf4662e498c8c2e352da5f5bca5ab29d378895fa2d980630656178bd607c46"}, + {file = "mypy-1.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bb8ccb4724f7d8601938571bf3f24da0da791fe2db7be3d9e79849cb64e0ae85"}, + {file = "mypy-1.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:68351911e85145f582b5aa6cd9ad666c8958bcae897a1bfda8f4940472463c45"}, + {file = "mypy-1.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:49ae115da099dcc0922a7a895c1eec82c1518109ea5c162ed50e3b3594c71208"}, + {file = "mypy-1.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b27958f8c76bed8edaa63da0739d76e4e9ad4ed325c814f9b3851425582a3cd"}, + {file = "mypy-1.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:925cd6a3b7b55dfba252b7c4561892311c5358c6b5a601847015a1ad4eb7d332"}, + {file = "mypy-1.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8f57e6b6927a49550da3d122f0cb983d400f843a8a82e65b3b380d3d7259468f"}, + {file = "mypy-1.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:a43ef1c8ddfdb9575691720b6352761f3f53d85f1b57d7745701041053deff30"}, + {file = "mypy-1.6.1-py3-none-any.whl", hash = "sha256:4cbe68ef919c28ea561165206a2dcb68591c50f3bcf777932323bc208d949cf1"}, + {file = "mypy-1.6.1.tar.gz", hash = "sha256:4d01c00d09a0be62a4ca3f933e315455bde83f37f892ba4b08ce92f3cf44bcc1"}, ] [package.dependencies] mypy-extensions = ">=1.0.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = ">=4.1.0" [package.extras] dmypy = ["psutil (>=4.0)"] install-types = ["pip"] -python2 = ["typed-ast (>=1.4.0,<2)"] reports = ["lxml"] [[package]] @@ -631,68 +614,72 @@ setuptools = "*" [[package]] name = "orjson" -version = "3.9.2" +version = "3.9.10" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "orjson-3.9.2-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7323e4ca8322b1ecb87562f1ec2491831c086d9faa9a6c6503f489dadbed37d7"}, - {file = "orjson-3.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1272688ea1865f711b01ba479dea2d53e037ea00892fd04196b5875f7021d9d3"}, - {file = "orjson-3.9.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0b9a26f1d1427a9101a1e8910f2e2df1f44d3d18ad5480ba031b15d5c1cb282e"}, - {file = "orjson-3.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6a5ca55b0d8f25f18b471e34abaee4b175924b6cd62f59992945b25963443141"}, - {file = "orjson-3.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:877872db2c0f41fbe21f852ff642ca842a43bc34895b70f71c9d575df31fffb4"}, - {file = "orjson-3.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a39c2529d75373b7167bf84c814ef9b8f3737a339c225ed6c0df40736df8748"}, - {file = "orjson-3.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:84ebd6fdf138eb0eb4280045442331ee71c0aab5e16397ba6645f32f911bfb37"}, - {file = "orjson-3.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5a60a1cfcfe310547a1946506dd4f1ed0a7d5bd5b02c8697d9d5dcd8d2e9245e"}, - {file = "orjson-3.9.2-cp310-none-win_amd64.whl", hash = "sha256:c290c4f81e8fd0c1683638802c11610b2f722b540f8e5e858b6914b495cf90c8"}, - {file = "orjson-3.9.2-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:02ef014f9a605e84b675060785e37ec9c0d2347a04f1307a9d6840ab8ecd6f55"}, - {file = "orjson-3.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:992af54265ada1c1579500d6594ed73fe333e726de70d64919cf37f93defdd06"}, - {file = "orjson-3.9.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a40958f7af7c6d992ee67b2da4098dca8b770fc3b4b3834d540477788bfa76d3"}, - {file = "orjson-3.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93864dec3e3dd058a2dbe488d11ac0345214a6a12697f53a63e34de7d28d4257"}, - {file = "orjson-3.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16fdf5a82df80c544c3c91516ab3882cd1ac4f1f84eefeafa642e05cef5f6699"}, - {file = "orjson-3.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:275b5a18fd9ed60b2720543d3ddac170051c43d680e47d04ff5203d2c6d8ebf1"}, - {file = "orjson-3.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b9aea6dcb99fcbc9f6d1dd84fca92322fda261da7fb014514bb4689c7c2097a8"}, - {file = "orjson-3.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7d74ae0e101d17c22ef67b741ba356ab896fc0fa64b301c2bf2bb0a4d874b190"}, - {file = "orjson-3.9.2-cp311-none-win_amd64.whl", hash = "sha256:6320b28e7bdb58c3a3a5efffe04b9edad3318d82409e84670a9b24e8035a249d"}, - {file = "orjson-3.9.2-cp37-cp37m-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:368e9cc91ecb7ac21f2aa475e1901204110cf3e714e98649c2502227d248f947"}, - {file = "orjson-3.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58e9e70f0dcd6a802c35887f306b555ff7a214840aad7de24901fc8bd9cf5dde"}, - {file = "orjson-3.9.2-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:00c983896c2e01c94c0ef72fd7373b2aa06d0c0eed0342c4884559f812a6835b"}, - {file = "orjson-3.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ee743e8890b16c87a2f89733f983370672272b61ee77429c0a5899b2c98c1a7"}, - {file = "orjson-3.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7b065942d362aad4818ff599d2f104c35a565c2cbcbab8c09ec49edba91da75"}, - {file = "orjson-3.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e46e9c5b404bb9e41d5555762fd410d5466b7eb1ec170ad1b1609cbebe71df21"}, - {file = "orjson-3.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8170157288714678ffd64f5de33039e1164a73fd8b6be40a8a273f80093f5c4f"}, - {file = "orjson-3.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e3e2f087161947dafe8319ea2cfcb9cea4bb9d2172ecc60ac3c9738f72ef2909"}, - {file = "orjson-3.9.2-cp37-none-win_amd64.whl", hash = "sha256:d7de3dbbe74109ae598692113cec327fd30c5a30ebca819b21dfa4052f7b08ef"}, - {file = "orjson-3.9.2-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:8cd4385c59bbc1433cad4a80aca65d2d9039646a9c57f8084897549b55913b17"}, - {file = "orjson-3.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a74036aab1a80c361039290cdbc51aa7adc7ea13f56e5ef94e9be536abd227bd"}, - {file = "orjson-3.9.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1aaa46d7d4ae55335f635eadc9be0bd9bcf742e6757209fc6dc697e390010adc"}, - {file = "orjson-3.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e52c67ed6bb368083aa2078ea3ccbd9721920b93d4b06c43eb4e20c4c860046"}, - {file = "orjson-3.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a6cdfcf9c7dd4026b2b01fdff56986251dc0cc1e980c690c79eec3ae07b36e7"}, - {file = "orjson-3.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1882a70bb69595b9ec5aac0040a819e94d2833fe54901e2b32f5e734bc259a8b"}, - {file = "orjson-3.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:fc05e060d452145ab3c0b5420769e7356050ea311fc03cb9d79c481982917cca"}, - {file = "orjson-3.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f8bc2c40d9bb26efefb10949d261a47ca196772c308babc538dd9f4b73e8d386"}, - {file = "orjson-3.9.2-cp38-none-win_amd64.whl", hash = "sha256:3164fc20a585ec30a9aff33ad5de3b20ce85702b2b2a456852c413e3f0d7ab09"}, - {file = "orjson-3.9.2-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7a6ccadf788531595ed4728aa746bc271955448d2460ff0ef8e21eb3f2a281ba"}, - {file = "orjson-3.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3245d230370f571c945f69aab823c279a868dc877352817e22e551de155cb06c"}, - {file = "orjson-3.9.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:205925b179550a4ee39b8418dd4c94ad6b777d165d7d22614771c771d44f57bd"}, - {file = "orjson-3.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0325fe2d69512187761f7368c8cda1959bcb75fc56b8e7a884e9569112320e57"}, - {file = "orjson-3.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:806704cd58708acc66a064a9a58e3be25cf1c3f9f159e8757bd3f515bfabdfa1"}, - {file = "orjson-3.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03fb36f187a0c19ff38f6289418863df8b9b7880cdbe279e920bef3a09d8dab1"}, - {file = "orjson-3.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:20925d07a97c49c6305bff1635318d9fc1804aa4ccacb5fb0deb8a910e57d97a"}, - {file = "orjson-3.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:eebfed53bec5674e981ebe8ed2cf00b3f7bcda62d634733ff779c264307ea505"}, - {file = "orjson-3.9.2-cp39-none-win_amd64.whl", hash = "sha256:869b961df5fcedf6c79f4096119b35679b63272362e9b745e668f0391a892d39"}, - {file = "orjson-3.9.2.tar.gz", hash = "sha256:24257c8f641979bf25ecd3e27251b5cc194cdd3a6e96004aac8446f5e63d9664"}, + {file = "orjson-3.9.10-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c18a4da2f50050a03d1da5317388ef84a16013302a5281d6f64e4a3f406aabc4"}, + {file = "orjson-3.9.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5148bab4d71f58948c7c39d12b14a9005b6ab35a0bdf317a8ade9a9e4d9d0bd5"}, + {file = "orjson-3.9.10-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4cf7837c3b11a2dfb589f8530b3cff2bd0307ace4c301e8997e95c7468c1378e"}, + {file = "orjson-3.9.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c62b6fa2961a1dcc51ebe88771be5319a93fd89bd247c9ddf732bc250507bc2b"}, + {file = "orjson-3.9.10-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:deeb3922a7a804755bbe6b5be9b312e746137a03600f488290318936c1a2d4dc"}, + {file = "orjson-3.9.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1234dc92d011d3554d929b6cf058ac4a24d188d97be5e04355f1b9223e98bbe9"}, + {file = "orjson-3.9.10-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:06ad5543217e0e46fd7ab7ea45d506c76f878b87b1b4e369006bdb01acc05a83"}, + {file = "orjson-3.9.10-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4fd72fab7bddce46c6826994ce1e7de145ae1e9e106ebb8eb9ce1393ca01444d"}, + {file = "orjson-3.9.10-cp310-none-win32.whl", hash = "sha256:b5b7d4a44cc0e6ff98da5d56cde794385bdd212a86563ac321ca64d7f80c80d1"}, + {file = "orjson-3.9.10-cp310-none-win_amd64.whl", hash = "sha256:61804231099214e2f84998316f3238c4c2c4aaec302df12b21a64d72e2a135c7"}, + {file = "orjson-3.9.10-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:cff7570d492bcf4b64cc862a6e2fb77edd5e5748ad715f487628f102815165e9"}, + {file = "orjson-3.9.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed8bc367f725dfc5cabeed1ae079d00369900231fbb5a5280cf0736c30e2adf7"}, + {file = "orjson-3.9.10-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c812312847867b6335cfb264772f2a7e85b3b502d3a6b0586aa35e1858528ab1"}, + {file = "orjson-3.9.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9edd2856611e5050004f4722922b7b1cd6268da34102667bd49d2a2b18bafb81"}, + {file = "orjson-3.9.10-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:674eb520f02422546c40401f4efaf8207b5e29e420c17051cddf6c02783ff5ca"}, + {file = "orjson-3.9.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d0dc4310da8b5f6415949bd5ef937e60aeb0eb6b16f95041b5e43e6200821fb"}, + {file = "orjson-3.9.10-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e99c625b8c95d7741fe057585176b1b8783d46ed4b8932cf98ee145c4facf499"}, + {file = "orjson-3.9.10-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ec6f18f96b47299c11203edfbdc34e1b69085070d9a3d1f302810cc23ad36bf3"}, + {file = "orjson-3.9.10-cp311-none-win32.whl", hash = "sha256:ce0a29c28dfb8eccd0f16219360530bc3cfdf6bf70ca384dacd36e6c650ef8e8"}, + {file = "orjson-3.9.10-cp311-none-win_amd64.whl", hash = "sha256:cf80b550092cc480a0cbd0750e8189247ff45457e5a023305f7ef1bcec811616"}, + {file = "orjson-3.9.10-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:602a8001bdf60e1a7d544be29c82560a7b49319a0b31d62586548835bbe2c862"}, + {file = "orjson-3.9.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f295efcd47b6124b01255d1491f9e46f17ef40d3d7eabf7364099e463fb45f0f"}, + {file = "orjson-3.9.10-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:92af0d00091e744587221e79f68d617b432425a7e59328ca4c496f774a356071"}, + {file = "orjson-3.9.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5a02360e73e7208a872bf65a7554c9f15df5fe063dc047f79738998b0506a14"}, + {file = "orjson-3.9.10-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:858379cbb08d84fe7583231077d9a36a1a20eb72f8c9076a45df8b083724ad1d"}, + {file = "orjson-3.9.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666c6fdcaac1f13eb982b649e1c311c08d7097cbda24f32612dae43648d8db8d"}, + {file = "orjson-3.9.10-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3fb205ab52a2e30354640780ce4587157a9563a68c9beaf52153e1cea9aa0921"}, + {file = "orjson-3.9.10-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7ec960b1b942ee3c69323b8721df2a3ce28ff40e7ca47873ae35bfafeb4555ca"}, + {file = "orjson-3.9.10-cp312-none-win_amd64.whl", hash = "sha256:3e892621434392199efb54e69edfff9f699f6cc36dd9553c5bf796058b14b20d"}, + {file = "orjson-3.9.10-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:8b9ba0ccd5a7f4219e67fbbe25e6b4a46ceef783c42af7dbc1da548eb28b6531"}, + {file = "orjson-3.9.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e2ecd1d349e62e3960695214f40939bbfdcaeaaa62ccc638f8e651cf0970e5f"}, + {file = "orjson-3.9.10-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7f433be3b3f4c66016d5a20e5b4444ef833a1f802ced13a2d852c637f69729c1"}, + {file = "orjson-3.9.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4689270c35d4bb3102e103ac43c3f0b76b169760aff8bcf2d401a3e0e58cdb7f"}, + {file = "orjson-3.9.10-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4bd176f528a8151a6efc5359b853ba3cc0e82d4cd1fab9c1300c5d957dc8f48c"}, + {file = "orjson-3.9.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a2ce5ea4f71681623f04e2b7dadede3c7435dfb5e5e2d1d0ec25b35530e277b"}, + {file = "orjson-3.9.10-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:49f8ad582da6e8d2cf663c4ba5bf9f83cc052570a3a767487fec6af839b0e777"}, + {file = "orjson-3.9.10-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2a11b4b1a8415f105d989876a19b173f6cdc89ca13855ccc67c18efbd7cbd1f8"}, + {file = "orjson-3.9.10-cp38-none-win32.whl", hash = "sha256:a353bf1f565ed27ba71a419b2cd3db9d6151da426b61b289b6ba1422a702e643"}, + {file = "orjson-3.9.10-cp38-none-win_amd64.whl", hash = "sha256:e28a50b5be854e18d54f75ef1bb13e1abf4bc650ab9d635e4258c58e71eb6ad5"}, + {file = "orjson-3.9.10-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ee5926746232f627a3be1cc175b2cfad24d0170d520361f4ce3fa2fd83f09e1d"}, + {file = "orjson-3.9.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a73160e823151f33cdc05fe2cea557c5ef12fdf276ce29bb4f1c571c8368a60"}, + {file = "orjson-3.9.10-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c338ed69ad0b8f8f8920c13f529889fe0771abbb46550013e3c3d01e5174deef"}, + {file = "orjson-3.9.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5869e8e130e99687d9e4be835116c4ebd83ca92e52e55810962446d841aba8de"}, + {file = "orjson-3.9.10-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2c1e559d96a7f94a4f581e2a32d6d610df5840881a8cba8f25e446f4d792df3"}, + {file = "orjson-3.9.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a3a3a72c9811b56adf8bcc829b010163bb2fc308877e50e9910c9357e78521"}, + {file = "orjson-3.9.10-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7f8fb7f5ecf4f6355683ac6881fd64b5bb2b8a60e3ccde6ff799e48791d8f864"}, + {file = "orjson-3.9.10-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c943b35ecdf7123b2d81d225397efddf0bce2e81db2f3ae633ead38e85cd5ade"}, + {file = "orjson-3.9.10-cp39-none-win32.whl", hash = "sha256:fb0b361d73f6b8eeceba47cd37070b5e6c9de5beaeaa63a1cb35c7e1a73ef088"}, + {file = "orjson-3.9.10-cp39-none-win_amd64.whl", hash = "sha256:b90f340cb6397ec7a854157fac03f0c82b744abdd1c0941a024c3c29d1340aff"}, + {file = "orjson-3.9.10.tar.gz", hash = "sha256:9ebbdbd6a046c304b1845e96fbcc5559cd296b4dfd3ad2509e33c4d9ce07d6a1"}, ] [[package]] name = "packaging" -version = "23.1" +version = "23.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, - {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] [[package]] @@ -719,13 +706,13 @@ files = [ [[package]] name = "platformdirs" -version = "3.10.0" +version = "3.11.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"}, - {file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"}, + {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"}, + {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"}, ] [package.extras] @@ -734,13 +721,13 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co [[package]] name = "pluggy" -version = "1.2.0" +version = "1.3.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, - {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, + {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, + {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, ] [package.extras] @@ -767,13 +754,13 @@ poetry-plugin = ["poetry (>=1.0,<2.0)"] [[package]] name = "prompt-toolkit" -version = "3.0.39" +version = "3.0.36" description = "Library for building powerful interactive command lines in Python" optional = false -python-versions = ">=3.7.0" +python-versions = ">=3.6.2" files = [ - {file = "prompt_toolkit-3.0.39-py3-none-any.whl", hash = "sha256:9dffbe1d8acf91e3de75f3b544e4842382fc06c6babe903ac9acb74dc6e08d88"}, - {file = "prompt_toolkit-3.0.39.tar.gz", hash = "sha256:04505ade687dc26dc4284b1ad19a83be2f2afe83e7a828ace0c72f3a1df72aac"}, + {file = "prompt_toolkit-3.0.36-py3-none-any.whl", hash = "sha256:aa64ad242a462c5ff0363a7b9cfe696c20d55d9fc60c11fd8e632d064804d305"}, + {file = "prompt_toolkit-3.0.36.tar.gz", hash = "sha256:3e163f254bef5a03b146397d7c1963bd3e2812f0964bb9a24e6ec761fd28db63"}, ] [package.dependencies] @@ -781,18 +768,18 @@ wcwidth = "*" [[package]] name = "pydantic" -version = "2.1.1" +version = "2.4.2" description = "Data validation using Python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-2.1.1-py3-none-any.whl", hash = "sha256:43bdbf359d6304c57afda15c2b95797295b702948082d4c23851ce752f21da70"}, - {file = "pydantic-2.1.1.tar.gz", hash = "sha256:22d63db5ce4831afd16e7c58b3192d3faf8f79154980d9397d9867254310ba4b"}, + {file = "pydantic-2.4.2-py3-none-any.whl", hash = "sha256:bc3ddf669d234f4220e6e1c4d96b061abe0998185a8d7855c0126782b7abc8c1"}, + {file = "pydantic-2.4.2.tar.gz", hash = "sha256:94f336138093a5d7f426aac732dcfe7ab4eb4da243c88f891d65deb4a2556ee7"}, ] [package.dependencies] annotated-types = ">=0.4.0" -pydantic-core = "2.4.0" +pydantic-core = "2.10.1" typing-extensions = ">=4.6.1" [package.extras] @@ -800,112 +787,117 @@ email = ["email-validator (>=2.0.0)"] [[package]] name = "pydantic-core" -version = "2.4.0" +version = "2.10.1" description = "" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic_core-2.4.0-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:2ca4687dd996bde7f3c420def450797feeb20dcee2b9687023e3323c73fc14a2"}, - {file = "pydantic_core-2.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:782fced7d61469fd1231b184a80e4f2fa7ad54cd7173834651a453f96f29d673"}, - {file = "pydantic_core-2.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6213b471b68146af97b8551294e59e7392c2117e28ffad9c557c65087f4baee3"}, - {file = "pydantic_core-2.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63797499a219d8e81eb4e0c42222d0a4c8ec896f5c76751d4258af95de41fdf1"}, - {file = "pydantic_core-2.4.0-cp310-cp310-manylinux_2_24_armv7l.whl", hash = "sha256:0455876d575a35defc4da7e0a199596d6c773e20d3d42fa1fc29f6aa640369ed"}, - {file = "pydantic_core-2.4.0-cp310-cp310-manylinux_2_24_ppc64le.whl", hash = "sha256:8c938c96294d983dcf419b54dba2d21056959c22911d41788efbf949a29ae30d"}, - {file = "pydantic_core-2.4.0-cp310-cp310-manylinux_2_24_s390x.whl", hash = "sha256:878a5017d93e776c379af4e7b20f173c82594d94fa073059bcc546789ad50bf8"}, - {file = "pydantic_core-2.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:69159afc2f2dc43285725f16143bc5df3c853bc1cb7df6021fce7ef1c69e8171"}, - {file = "pydantic_core-2.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:54df7df399b777c1fd144f541c95d351b3aa110535a6810a6a569905d106b6f3"}, - {file = "pydantic_core-2.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e412607ca89a0ced10758dfb8f9adcc365ce4c1c377e637c01989a75e9a9ec8a"}, - {file = "pydantic_core-2.4.0-cp310-none-win32.whl", hash = "sha256:853f103e2b9a58832fdd08a587a51de8b552ae90e1a5d167f316b7eabf8d7dde"}, - {file = "pydantic_core-2.4.0-cp310-none-win_amd64.whl", hash = "sha256:3ba2c9c94a9176f6321a879c8b864d7c5b12d34f549a4c216c72ce213d7d953c"}, - {file = "pydantic_core-2.4.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:a8b7acd04896e8f161e1500dc5f218017db05c1d322f054e89cbd089ce5d0071"}, - {file = "pydantic_core-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:16468bd074fa4567592d3255bf25528ed41e6b616d69bf07096bdb5b66f947d1"}, - {file = "pydantic_core-2.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cba5ad5eef02c86a1f3da00544cbc59a510d596b27566479a7cd4d91c6187a11"}, - {file = "pydantic_core-2.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7206e41e04b443016e930e01685bab7a308113c0b251b3f906942c8d4b48fcb"}, - {file = "pydantic_core-2.4.0-cp311-cp311-manylinux_2_24_armv7l.whl", hash = "sha256:c1375025f0bfc9155286ebae8eecc65e33e494c90025cda69e247c3ccd2bab00"}, - {file = "pydantic_core-2.4.0-cp311-cp311-manylinux_2_24_ppc64le.whl", hash = "sha256:3534118289e33130ed3f1cc487002e8d09b9f359be48b02e9cd3de58ce58fba9"}, - {file = "pydantic_core-2.4.0-cp311-cp311-manylinux_2_24_s390x.whl", hash = "sha256:94d2b36a74623caab262bf95f0e365c2c058396082bd9d6a9e825657d0c1e7fa"}, - {file = "pydantic_core-2.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:af24ad4fbaa5e4a2000beae0c3b7fd1c78d7819ab90f9370a1cfd8998e3f8a3c"}, - {file = "pydantic_core-2.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bf10963d8aed8bbe0165b41797c9463d4c5c8788ae6a77c68427569be6bead41"}, - {file = "pydantic_core-2.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:68199ada7c310ddb8c76efbb606a0de656b40899388a7498954f423e03fc38be"}, - {file = "pydantic_core-2.4.0-cp311-none-win32.whl", hash = "sha256:6f855bcc96ed3dd56da7373cfcc9dcbabbc2073cac7f65c185772d08884790ce"}, - {file = "pydantic_core-2.4.0-cp311-none-win_amd64.whl", hash = "sha256:de39eb3bab93a99ddda1ac1b9aa331b944d8bcc4aa9141148f7fd8ee0299dafc"}, - {file = "pydantic_core-2.4.0-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:f773b39780323a0499b53ebd91a28ad11cde6705605d98d999dfa08624caf064"}, - {file = "pydantic_core-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a297c0d6c61963c5c3726840677b798ca5b7dfc71bc9c02b9a4af11d23236008"}, - {file = "pydantic_core-2.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:546064c55264156b973b5e65e5fafbe5e62390902ce3cf6b4005765505e8ff56"}, - {file = "pydantic_core-2.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36ba9e728588588f0196deaf6751b9222492331b5552f865a8ff120869d372e0"}, - {file = "pydantic_core-2.4.0-cp312-cp312-manylinux_2_24_armv7l.whl", hash = "sha256:57a53a75010c635b3ad6499e7721eaa3b450e03f6862afe2dbef9c8f66e46ec8"}, - {file = "pydantic_core-2.4.0-cp312-cp312-manylinux_2_24_ppc64le.whl", hash = "sha256:4b262bbc13022f2097c48a21adcc360a81d83dc1d854c11b94953cd46d7d3c07"}, - {file = "pydantic_core-2.4.0-cp312-cp312-manylinux_2_24_s390x.whl", hash = "sha256:01947ad728f426fa07fcb26457ebf90ce29320259938414bc0edd1476e75addb"}, - {file = "pydantic_core-2.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b2799c2eaf182769889761d4fb4d78b82bc47dae833799fedbf69fc7de306faa"}, - {file = "pydantic_core-2.4.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a08fd490ba36d1fbb2cd5dcdcfb9f3892deb93bd53456724389135712b5fc735"}, - {file = "pydantic_core-2.4.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1e8a7c62d15a5c4b307271e4252d76ebb981d6251c6ecea4daf203ef0179ea4f"}, - {file = "pydantic_core-2.4.0-cp312-none-win32.whl", hash = "sha256:9206c14a67c38de7b916e486ae280017cf394fa4b1aa95cfe88621a4e1d79725"}, - {file = "pydantic_core-2.4.0-cp312-none-win_amd64.whl", hash = "sha256:884235507549a6b2d3c4113fb1877ae263109e787d9e0eb25c35982ab28d0399"}, - {file = "pydantic_core-2.4.0-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:4cbe929efa77a806e8f1a97793f2dc3ea3475ae21a9ed0f37c21320fe93f6f50"}, - {file = "pydantic_core-2.4.0-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:9137289de8fe845c246a8c3482dd0cb40338846ba683756d8f489a4bd8fddcae"}, - {file = "pydantic_core-2.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5d8e764b5646623e57575f624f8ebb8f7a9f7fd1fae682ef87869ca5fec8dcf"}, - {file = "pydantic_core-2.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fba0aff4c407d0274e43697e785bcac155ad962be57518d1c711f45e72da70f"}, - {file = "pydantic_core-2.4.0-cp37-cp37m-manylinux_2_24_armv7l.whl", hash = "sha256:30527d173e826f2f7651f91c821e337073df1555e3b5a0b7b1e2c39e26e50678"}, - {file = "pydantic_core-2.4.0-cp37-cp37m-manylinux_2_24_ppc64le.whl", hash = "sha256:bd7d1dde70ff3e09e4bc7a1cbb91a7a538add291bfd5b3e70ef1e7b45192440f"}, - {file = "pydantic_core-2.4.0-cp37-cp37m-manylinux_2_24_s390x.whl", hash = "sha256:72f1216ca8cef7b8adacd4c4c6b89c3b0c4f97503197f5284c80f36d6e4edd30"}, - {file = "pydantic_core-2.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b013c7861a7c7bfcec48fd709513fea6f9f31727e7a0a93ca0dd12e056740717"}, - {file = "pydantic_core-2.4.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:478f5f6d7e32bd4a04d102160efb2d389432ecf095fe87c555c0a6fc4adfc1a4"}, - {file = "pydantic_core-2.4.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d9610b47b5fe4aacbbba6a9cb5f12cbe864eec99dbfed5710bd32ef5dd8a5d5b"}, - {file = "pydantic_core-2.4.0-cp37-none-win32.whl", hash = "sha256:ff246c0111076c8022f9ba325c294f2cb5983403506989253e04dbae565e019b"}, - {file = "pydantic_core-2.4.0-cp37-none-win_amd64.whl", hash = "sha256:d0c2b713464a8e263a243ae7980d81ce2de5ac59a9f798a282e44350b42dc516"}, - {file = "pydantic_core-2.4.0-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:12ef6838245569fd60a179fade81ca4b90ae2fa0ef355d616f519f7bb27582db"}, - {file = "pydantic_core-2.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:49db206eb8fdc4b4f30e6e3e410584146d813c151928f94ec0db06c4f2595538"}, - {file = "pydantic_core-2.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a507d7fa44688bbac76af6521e488b3da93de155b9cba6f2c9b7833ce243d59"}, - {file = "pydantic_core-2.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffe18407a4d000c568182ce5388bbbedeb099896904e43fc14eee76cfae6dec5"}, - {file = "pydantic_core-2.4.0-cp38-cp38-manylinux_2_24_armv7l.whl", hash = "sha256:fa8e48001b39d54d97d7b380a0669fa99fc0feeb972e35a2d677ba59164a9a22"}, - {file = "pydantic_core-2.4.0-cp38-cp38-manylinux_2_24_ppc64le.whl", hash = "sha256:394f12a2671ff8c4dfa2e85be6c08be0651ad85bc1e6aa9c77c21671baaf28cd"}, - {file = "pydantic_core-2.4.0-cp38-cp38-manylinux_2_24_s390x.whl", hash = "sha256:2f9ea0355f90db2a76af530245fa42f04d98f752a1236ed7c6809ec484560d5b"}, - {file = "pydantic_core-2.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:61d4e713f467abcdd59b47665d488bb898ad3dd47ce7446522a50e0cbd8e8279"}, - {file = "pydantic_core-2.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:453862ab268f6326b01f067ed89cb3a527d34dc46f6f4eeec46a15bbc706d0da"}, - {file = "pydantic_core-2.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:56a85fa0dab1567bd0cac10f0c3837b03e8a0d939e6a8061a3a420acd97e9421"}, - {file = "pydantic_core-2.4.0-cp38-none-win32.whl", hash = "sha256:0d726108c1c0380b88b6dd4db559f0280e0ceda9e077f46ff90bc85cd4d03e77"}, - {file = "pydantic_core-2.4.0-cp38-none-win_amd64.whl", hash = "sha256:047580388644c473b934d27849f8ed8dbe45df0adb72104e78b543e13bf69762"}, - {file = "pydantic_core-2.4.0-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:867d3eea954bea807cabba83cfc939c889a18576d66d197c60025b15269d7cc0"}, - {file = "pydantic_core-2.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:664402ef0c238a7f8a46efb101789d5f2275600fb18114446efec83cfadb5b66"}, - {file = "pydantic_core-2.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64e8012ad60a5f0da09ed48725e6e923d1be25f2f091a640af6079f874663813"}, - {file = "pydantic_core-2.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac2b680de398f293b68183317432b3d67ab3faeba216aec18de0c395cb5e3060"}, - {file = "pydantic_core-2.4.0-cp39-cp39-manylinux_2_24_armv7l.whl", hash = "sha256:8efc1be43b036c2b6bcfb1451df24ee0ddcf69c31351003daf2699ed93f5687b"}, - {file = "pydantic_core-2.4.0-cp39-cp39-manylinux_2_24_ppc64le.whl", hash = "sha256:d93aedbc4614cc21b9ab0d0c4ccd7143354c1f7cffbbe96ae5216ad21d1b21b5"}, - {file = "pydantic_core-2.4.0-cp39-cp39-manylinux_2_24_s390x.whl", hash = "sha256:af788b64e13d52fc3600a68b16d31fa8d8573e3ff2fc9a38f8a60b8d94d1f012"}, - {file = "pydantic_core-2.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:97c6349c81cee2e69ef59eba6e6c08c5936e6b01c2d50b9e4ac152217845ae09"}, - {file = "pydantic_core-2.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cc086ddb6dc654a15deeed1d1f2bcb1cb924ebd70df9dca738af19f64229b06c"}, - {file = "pydantic_core-2.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e953353180bec330c3b830891d260b6f8e576e2d18db3c78d314e56bb2276066"}, - {file = "pydantic_core-2.4.0-cp39-none-win32.whl", hash = "sha256:6feb4b64d11d5420e517910d60a907d08d846cacaf4e029668725cd21d16743c"}, - {file = "pydantic_core-2.4.0-cp39-none-win_amd64.whl", hash = "sha256:153a61ac4030fa019b70b31fb7986461119230d3ba0ab661c757cfea652f4332"}, - {file = "pydantic_core-2.4.0-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:3fcf529382b282a30b466bd7af05be28e22aa620e016135ac414f14e1ee6b9e1"}, - {file = "pydantic_core-2.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2edef05b63d82568b877002dc4cb5cc18f8929b59077120192df1e03e0c633f8"}, - {file = "pydantic_core-2.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da055a1b0bfa8041bb2ff586b2cb0353ed03944a3472186a02cc44a557a0e661"}, - {file = "pydantic_core-2.4.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:77dadc764cf7c5405e04866181c5bd94a447372a9763e473abb63d1dfe9b7387"}, - {file = "pydantic_core-2.4.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:a4ea23b07f29487a7bef2a869f68c7ee0e05424d81375ce3d3de829314c6b5ec"}, - {file = "pydantic_core-2.4.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:382f0baa044d674ad59455a5eff83d7965572b745cc72df35c52c2ce8c731d37"}, - {file = "pydantic_core-2.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:08f89697625e453421401c7f661b9d1eb4c9e4c0a12fd256eeb55b06994ac6af"}, - {file = "pydantic_core-2.4.0-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:43a405ce520b45941df9ff55d0cd09762017756a7b413bbad3a6e8178e64a2c2"}, - {file = "pydantic_core-2.4.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:584a7a818c84767af16ce8bda5d4f7fedb37d3d231fc89928a192f567e4ef685"}, - {file = "pydantic_core-2.4.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04922fea7b13cd480586fa106345fe06e43220b8327358873c22d8dfa7a711c7"}, - {file = "pydantic_core-2.4.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17156abac20a9feed10feec867fddd91a80819a485b0107fe61f09f2117fe5f3"}, - {file = "pydantic_core-2.4.0-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4e562cc63b04636cde361fd47569162f1daa94c759220ff202a8129902229114"}, - {file = "pydantic_core-2.4.0-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:90f3785146f701e053bb6b9e8f53acce2c919aca91df88bd4975be0cb926eb41"}, - {file = "pydantic_core-2.4.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:e40b1e97edd3dc127aa53d8a5e539a3d0c227d71574d3f9ac1af02d58218a122"}, - {file = "pydantic_core-2.4.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:b27f3e67f6e031f6620655741b7d0d6bebea8b25d415924b3e8bfef2dd7bd841"}, - {file = "pydantic_core-2.4.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be86c2eb12fb0f846262ace9d8f032dc6978b8cb26a058920ecb723dbcb87d05"}, - {file = "pydantic_core-2.4.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4665f7ed345012a8d2eddf4203ef145f5f56a291d010382d235b94e91813f88a"}, - {file = "pydantic_core-2.4.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:79262be5a292d1df060f29b9a7cdd66934801f987a817632d7552534a172709a"}, - {file = "pydantic_core-2.4.0-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:5fd905a69ac74eaba5041e21a1e8b1a479dab2b41c93bdcc4c1cede3c12a8d86"}, - {file = "pydantic_core-2.4.0-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:2ad538b7e07343001934417cdc8584623b4d8823c5b8b258e75ec8d327cec969"}, - {file = "pydantic_core-2.4.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:dd2429f7635ad4857b5881503f9c310be7761dc681c467a9d27787b674d1250a"}, - {file = "pydantic_core-2.4.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:efff8b6761a1f6e45cebd1b7a6406eb2723d2d5710ff0d1b624fe11313693989"}, - {file = "pydantic_core-2.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32a1e0352558cd7ccc014ffe818c7d87b15ec6145875e2cc5fa4bb7351a1033d"}, - {file = "pydantic_core-2.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a027f41c5008571314861744d83aff75a34cf3a07022e0be32b214a5bc93f7f1"}, - {file = "pydantic_core-2.4.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1927f0e15d190f11f0b8344373731e28fd774c6d676d8a6cfadc95c77214a48b"}, - {file = "pydantic_core-2.4.0-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:7aa82d483d5fb867d4fb10a138ffd57b0f1644e99f2f4f336e48790ada9ada5e"}, - {file = "pydantic_core-2.4.0-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b85778308bf945e9b33ac604e6793df9b07933108d20bdf53811bc7c2798a4af"}, - {file = "pydantic_core-2.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3ded19dcaefe2f6706d81e0db787b59095f4ad0fbadce1edffdf092294c8a23f"}, - {file = "pydantic_core-2.4.0.tar.gz", hash = "sha256:ec3473c9789cc00c7260d840c3db2c16dbfc816ca70ec87a00cddfa3e1a1cdd5"}, + {file = "pydantic_core-2.10.1-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:d64728ee14e667ba27c66314b7d880b8eeb050e58ffc5fec3b7a109f8cddbd63"}, + {file = "pydantic_core-2.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:48525933fea744a3e7464c19bfede85df4aba79ce90c60b94d8b6e1eddd67096"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef337945bbd76cce390d1b2496ccf9f90b1c1242a3a7bc242ca4a9fc5993427a"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1392e0638af203cee360495fd2cfdd6054711f2db5175b6e9c3c461b76f5175"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0675ba5d22de54d07bccde38997e780044dcfa9a71aac9fd7d4d7a1d2e3e65f7"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:128552af70a64660f21cb0eb4876cbdadf1a1f9d5de820fed6421fa8de07c893"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f6e6aed5818c264412ac0598b581a002a9f050cb2637a84979859e70197aa9e"}, + {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ecaac27da855b8d73f92123e5f03612b04c5632fd0a476e469dfc47cd37d6b2e"}, + {file = "pydantic_core-2.10.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b3c01c2fb081fced3bbb3da78510693dc7121bb893a1f0f5f4b48013201f362e"}, + {file = "pydantic_core-2.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:92f675fefa977625105708492850bcbc1182bfc3e997f8eecb866d1927c98ae6"}, + {file = "pydantic_core-2.10.1-cp310-none-win32.whl", hash = "sha256:420a692b547736a8d8703c39ea935ab5d8f0d2573f8f123b0a294e49a73f214b"}, + {file = "pydantic_core-2.10.1-cp310-none-win_amd64.whl", hash = "sha256:0880e239827b4b5b3e2ce05e6b766a7414e5f5aedc4523be6b68cfbc7f61c5d0"}, + {file = "pydantic_core-2.10.1-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:073d4a470b195d2b2245d0343569aac7e979d3a0dcce6c7d2af6d8a920ad0bea"}, + {file = "pydantic_core-2.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:600d04a7b342363058b9190d4e929a8e2e715c5682a70cc37d5ded1e0dd370b4"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39215d809470f4c8d1881758575b2abfb80174a9e8daf8f33b1d4379357e417c"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eeb3d3d6b399ffe55f9a04e09e635554012f1980696d6b0aca3e6cf42a17a03b"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7a7902bf75779bc12ccfc508bfb7a4c47063f748ea3de87135d433a4cca7a2f"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3625578b6010c65964d177626fde80cf60d7f2e297d56b925cb5cdeda6e9925a"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:caa48fc31fc7243e50188197b5f0c4228956f97b954f76da157aae7f67269ae8"}, + {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:07ec6d7d929ae9c68f716195ce15e745b3e8fa122fc67698ac6498d802ed0fa4"}, + {file = "pydantic_core-2.10.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e6f31a17acede6a8cd1ae2d123ce04d8cca74056c9d456075f4f6f85de055607"}, + {file = "pydantic_core-2.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d8f1ebca515a03e5654f88411420fea6380fc841d1bea08effb28184e3d4899f"}, + {file = "pydantic_core-2.10.1-cp311-none-win32.whl", hash = "sha256:6db2eb9654a85ada248afa5a6db5ff1cf0f7b16043a6b070adc4a5be68c716d6"}, + {file = "pydantic_core-2.10.1-cp311-none-win_amd64.whl", hash = "sha256:4a5be350f922430997f240d25f8219f93b0c81e15f7b30b868b2fddfc2d05f27"}, + {file = "pydantic_core-2.10.1-cp311-none-win_arm64.whl", hash = "sha256:5fdb39f67c779b183b0c853cd6b45f7db84b84e0571b3ef1c89cdb1dfc367325"}, + {file = "pydantic_core-2.10.1-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:b1f22a9ab44de5f082216270552aa54259db20189e68fc12484873d926426921"}, + {file = "pydantic_core-2.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8572cadbf4cfa95fb4187775b5ade2eaa93511f07947b38f4cd67cf10783b118"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db9a28c063c7c00844ae42a80203eb6d2d6bbb97070cfa00194dff40e6f545ab"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e2a35baa428181cb2270a15864ec6286822d3576f2ed0f4cd7f0c1708472aff"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05560ab976012bf40f25d5225a58bfa649bb897b87192a36c6fef1ab132540d7"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6495008733c7521a89422d7a68efa0a0122c99a5861f06020ef5b1f51f9ba7c"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ac492c686defc8e6133e3a2d9eaf5261b3df26b8ae97450c1647286750b901"}, + {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8282bab177a9a3081fd3d0a0175a07a1e2bfb7fcbbd949519ea0980f8a07144d"}, + {file = "pydantic_core-2.10.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:aafdb89fdeb5fe165043896817eccd6434aee124d5ee9b354f92cd574ba5e78f"}, + {file = "pydantic_core-2.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f6defd966ca3b187ec6c366604e9296f585021d922e666b99c47e78738b5666c"}, + {file = "pydantic_core-2.10.1-cp312-none-win32.whl", hash = "sha256:7c4d1894fe112b0864c1fa75dffa045720a194b227bed12f4be7f6045b25209f"}, + {file = "pydantic_core-2.10.1-cp312-none-win_amd64.whl", hash = "sha256:5994985da903d0b8a08e4935c46ed8daf5be1cf217489e673910951dc533d430"}, + {file = "pydantic_core-2.10.1-cp312-none-win_arm64.whl", hash = "sha256:0d8a8adef23d86d8eceed3e32e9cca8879c7481c183f84ed1a8edc7df073af94"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:9badf8d45171d92387410b04639d73811b785b5161ecadabf056ea14d62d4ede"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:ebedb45b9feb7258fac0a268a3f6bec0a2ea4d9558f3d6f813f02ff3a6dc6698"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfe1090245c078720d250d19cb05d67e21a9cd7c257698ef139bc41cf6c27b4f"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e357571bb0efd65fd55f18db0a2fb0ed89d0bb1d41d906b138f088933ae618bb"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b3dcd587b69bbf54fc04ca157c2323b8911033e827fffaecf0cafa5a892a0904"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c120c9ce3b163b985a3b966bb701114beb1da4b0468b9b236fc754783d85aa3"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15d6bca84ffc966cc9976b09a18cf9543ed4d4ecbd97e7086f9ce9327ea48891"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cabb9710f09d5d2e9e2748c3e3e20d991a4c5f96ed8f1132518f54ab2967221"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:82f55187a5bebae7d81d35b1e9aaea5e169d44819789837cdd4720d768c55d15"}, + {file = "pydantic_core-2.10.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1d40f55222b233e98e3921df7811c27567f0e1a4411b93d4c5c0f4ce131bc42f"}, + {file = "pydantic_core-2.10.1-cp37-none-win32.whl", hash = "sha256:14e09ff0b8fe6e46b93d36a878f6e4a3a98ba5303c76bb8e716f4878a3bee92c"}, + {file = "pydantic_core-2.10.1-cp37-none-win_amd64.whl", hash = "sha256:1396e81b83516b9d5c9e26a924fa69164156c148c717131f54f586485ac3c15e"}, + {file = "pydantic_core-2.10.1-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:6835451b57c1b467b95ffb03a38bb75b52fb4dc2762bb1d9dbed8de31ea7d0fc"}, + {file = "pydantic_core-2.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b00bc4619f60c853556b35f83731bd817f989cba3e97dc792bb8c97941b8053a"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fa467fd300a6f046bdb248d40cd015b21b7576c168a6bb20aa22e595c8ffcdd"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d99277877daf2efe074eae6338453a4ed54a2d93fb4678ddfe1209a0c93a2468"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa7db7558607afeccb33c0e4bf1c9a9a835e26599e76af6fe2fcea45904083a6"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aad7bd686363d1ce4ee930ad39f14e1673248373f4a9d74d2b9554f06199fb58"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:443fed67d33aa85357464f297e3d26e570267d1af6fef1c21ca50921d2976302"}, + {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:042462d8d6ba707fd3ce9649e7bf268633a41018d6a998fb5fbacb7e928a183e"}, + {file = "pydantic_core-2.10.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ecdbde46235f3d560b18be0cb706c8e8ad1b965e5c13bbba7450c86064e96561"}, + {file = "pydantic_core-2.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ed550ed05540c03f0e69e6d74ad58d026de61b9eaebebbaaf8873e585cbb18de"}, + {file = "pydantic_core-2.10.1-cp38-none-win32.whl", hash = "sha256:8cdbbd92154db2fec4ec973d45c565e767ddc20aa6dbaf50142676484cbff8ee"}, + {file = "pydantic_core-2.10.1-cp38-none-win_amd64.whl", hash = "sha256:9f6f3e2598604956480f6c8aa24a3384dbf6509fe995d97f6ca6103bb8c2534e"}, + {file = "pydantic_core-2.10.1-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:655f8f4c8d6a5963c9a0687793da37b9b681d9ad06f29438a3b2326d4e6b7970"}, + {file = "pydantic_core-2.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e570ffeb2170e116a5b17e83f19911020ac79d19c96f320cbfa1fa96b470185b"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64322bfa13e44c6c30c518729ef08fda6026b96d5c0be724b3c4ae4da939f875"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:485a91abe3a07c3a8d1e082ba29254eea3e2bb13cbbd4351ea4e5a21912cc9b0"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7c2b8eb9fc872e68b46eeaf835e86bccc3a58ba57d0eedc109cbb14177be531"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a5cb87bdc2e5f620693148b5f8f842d293cae46c5f15a1b1bf7ceeed324a740c"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25bd966103890ccfa028841a8f30cebcf5875eeac8c4bde4fe221364c92f0c9a"}, + {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f323306d0556351735b54acbf82904fe30a27b6a7147153cbe6e19aaaa2aa429"}, + {file = "pydantic_core-2.10.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0c27f38dc4fbf07b358b2bc90edf35e82d1703e22ff2efa4af4ad5de1b3833e7"}, + {file = "pydantic_core-2.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f1365e032a477c1430cfe0cf2856679529a2331426f8081172c4a74186f1d595"}, + {file = "pydantic_core-2.10.1-cp39-none-win32.whl", hash = "sha256:a1c311fd06ab3b10805abb72109f01a134019739bd3286b8ae1bc2fc4e50c07a"}, + {file = "pydantic_core-2.10.1-cp39-none-win_amd64.whl", hash = "sha256:ae8a8843b11dc0b03b57b52793e391f0122e740de3df1474814c700d2622950a"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d43002441932f9a9ea5d6f9efaa2e21458221a3a4b417a14027a1d530201ef1b"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fcb83175cc4936a5425dde3356f079ae03c0802bbdf8ff82c035f8a54b333521"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:962ed72424bf1f72334e2f1e61b68f16c0e596f024ca7ac5daf229f7c26e4208"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2cf5bb4dd67f20f3bbc1209ef572a259027c49e5ff694fa56bed62959b41e1f9"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e544246b859f17373bed915182ab841b80849ed9cf23f1f07b73b7c58baee5fb"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c0877239307b7e69d025b73774e88e86ce82f6ba6adf98f41069d5b0b78bd1bf"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:53df009d1e1ba40f696f8995683e067e3967101d4bb4ea6f667931b7d4a01357"}, + {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a1254357f7e4c82e77c348dabf2d55f1d14d19d91ff025004775e70a6ef40ada"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:524ff0ca3baea164d6d93a32c58ac79eca9f6cf713586fdc0adb66a8cdeab96a"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f0ac9fb8608dbc6eaf17956bf623c9119b4db7dbb511650910a82e261e6600f"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:320f14bd4542a04ab23747ff2c8a778bde727158b606e2661349557f0770711e"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:63974d168b6233b4ed6a0046296803cb13c56637a7b8106564ab575926572a55"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:417243bf599ba1f1fef2bb8c543ceb918676954734e2dcb82bf162ae9d7bd514"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dda81e5ec82485155a19d9624cfcca9be88a405e2857354e5b089c2a982144b2"}, + {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:14cfbb00959259e15d684505263d5a21732b31248a5dd4941f73a3be233865b9"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:631cb7415225954fdcc2a024119101946793e5923f6c4d73a5914d27eb3d3a05"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:bec7dd208a4182e99c5b6c501ce0b1f49de2802448d4056091f8e630b28e9a52"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:149b8a07712f45b332faee1a2258d8ef1fb4a36f88c0c17cb687f205c5dc6e7d"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d966c47f9dd73c2d32a809d2be529112d509321c5310ebf54076812e6ecd884"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7eb037106f5c6b3b0b864ad226b0b7ab58157124161d48e4b30c4a43fef8bc4b"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:154ea7c52e32dce13065dbb20a4a6f0cc012b4f667ac90d648d36b12007fa9f7"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e562617a45b5a9da5be4abe72b971d4f00bf8555eb29bb91ec2ef2be348cd132"}, + {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:f23b55eb5464468f9e0e9a9935ce3ed2a870608d5f534025cd5536bca25b1402"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:e9121b4009339b0f751955baf4543a0bfd6bc3f8188f8056b1a25a2d45099934"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:0523aeb76e03f753b58be33b26540880bac5aa54422e4462404c432230543f33"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e0e2959ef5d5b8dc9ef21e1a305a21a36e254e6a34432d00c72a92fdc5ecda5"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da01bec0a26befab4898ed83b362993c844b9a607a86add78604186297eb047e"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f2e9072d71c1f6cfc79a36d4484c82823c560e6f5599c43c1ca6b5cdbd54f881"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f36a3489d9e28fe4b67be9992a23029c3cec0babc3bd9afb39f49844a8c721c5"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f64f82cc3443149292b32387086d02a6c7fb39b8781563e0ca7b8d7d9cf72bd7"}, + {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b4a6db486ac8e99ae696e09efc8b2b9fea67b63c8f88ba7a1a16c24a057a0776"}, + {file = "pydantic_core-2.10.1.tar.gz", hash = "sha256:0f8682dbdd2f67f8e1edddcbffcc29f60a6182b4901c367fc8c1c40d30bb0a82"}, ] [package.dependencies] @@ -913,13 +905,13 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pyright" -version = "1.1.331" +version = "1.1.333" description = "Command line wrapper for pyright" optional = false python-versions = ">=3.7" files = [ - {file = "pyright-1.1.331-py3-none-any.whl", hash = "sha256:d200a01794e7f2a04d5042a6c3abee36ce92780287d3037edfc3604d45488f0e"}, - {file = "pyright-1.1.331.tar.gz", hash = "sha256:c3e7b86154cac86c3bd61ea0f963143d001c201e246825aaabdddfcce5d04293"}, + {file = "pyright-1.1.333-py3-none-any.whl", hash = "sha256:f0a7b7b0cac11c396b17ef3cf6c8527aca1269edaf5cf8203eed7d6dd1ef52aa"}, + {file = "pyright-1.1.333.tar.gz", hash = "sha256:1c49b0029048120c4378f3baf6c1dcbbfb221678bb69654fe773c514430ac53c"}, ] [package.dependencies] @@ -931,22 +923,20 @@ dev = ["twine (>=3.4.1)"] [[package]] name = "pytest" -version = "7.4.0" +version = "7.4.3" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, - {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, + {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, + {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, ] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] @@ -1034,20 +1024,17 @@ files = [ [[package]] name = "questionary" -version = "1.10.0" +version = "2.0.1" description = "Python library to build pretty command line user prompts ⭐️" optional = false -python-versions = ">=3.6,<4.0" +python-versions = ">=3.8" files = [ - {file = "questionary-1.10.0-py3-none-any.whl", hash = "sha256:fecfcc8cca110fda9d561cb83f1e97ecbb93c613ff857f655818839dac74ce90"}, - {file = "questionary-1.10.0.tar.gz", hash = "sha256:600d3aefecce26d48d97eee936fdb66e4bc27f934c3ab6dd1e292c4f43946d90"}, + {file = "questionary-2.0.1-py3-none-any.whl", hash = "sha256:8ab9a01d0b91b68444dff7f6652c1e754105533f083cbe27597c8110ecc230a2"}, + {file = "questionary-2.0.1.tar.gz", hash = "sha256:bcce898bf3dbb446ff62830c86c5c6fb9a22a54146f0f5597d3da43b10d8fc8b"}, ] [package.dependencies] -prompt_toolkit = ">=2.0,<4.0" - -[package.extras] -docs = ["Sphinx (>=3.3,<4.0)", "sphinx-autobuild (>=2020.9.1,<2021.0.0)", "sphinx-autodoc-typehints (>=1.11.1,<2.0.0)", "sphinx-copybutton (>=0.3.1,<0.4.0)", "sphinx-rtd-theme (>=0.5.0,<0.6.0)"] +prompt_toolkit = ">=2.0,<=3.0.36" [[package]] name = "requests" @@ -1072,45 +1059,45 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "ruff" -version = "0.0.282" +version = "0.1.3" description = "An extremely fast Python linter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.0.282-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:01b76309ddab16eb258dabc5e86e73e6542f59f3ea6b4ab886ecbcfc80ce062c"}, - {file = "ruff-0.0.282-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:e177cbb6dc0b1dbef5e999900d798b73e33602abf9b6c62d5d2cbe101026d931"}, - {file = "ruff-0.0.282-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5374b40b6d860d334d28678a53a92f0bf04b53acdf0395900361ad54ce71cd1d"}, - {file = "ruff-0.0.282-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d1ccbceb44e94fe2205b63996166e98a513a19ed23ec01d7193b7494b94ba30d"}, - {file = "ruff-0.0.282-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eee9c8c50bc77eb9c0811c91d9d67ff39fe4f394c2f44ada37dac6d45e50c9f1"}, - {file = "ruff-0.0.282-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:826e4de98e91450a6fe699a4e4a7cf33b9a90a2c5c270dc5b202241c37359ff8"}, - {file = "ruff-0.0.282-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d99758f8bbcb8f8da99acabf711ffad5e7a015247adf27211100b3586777fd56"}, - {file = "ruff-0.0.282-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3f30c9958ab9cb02bf0c574c629e87c19454cbbdb82750e49e3d1559a5a8f216"}, - {file = "ruff-0.0.282-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47a7a9366ab8e4ee20df9339bef172eec7b2e9e123643bf3ede005058f5b114e"}, - {file = "ruff-0.0.282-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1f05f5e6d6df6f8b1974c08f963c33f0a4d8cfa15cba12d35ca3ece8e9be5b1f"}, - {file = "ruff-0.0.282-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0710ea2cadc504b96c1d94c414a7802369d0fff2ab7c94460344bba69135cb40"}, - {file = "ruff-0.0.282-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2ca52536e1c7603fe4cbb5ad9dc141df47c3200df782f5ec559364716ea27f96"}, - {file = "ruff-0.0.282-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:aab9ed5bfba6b0a2242a7ec9a72858c802ceeaf0076fe72b2ad455639275f22c"}, - {file = "ruff-0.0.282-py3-none-win32.whl", hash = "sha256:f51bbb64f8f29e444c16d21b269ba82e25f8d536beda3df7c9fe1816297e508e"}, - {file = "ruff-0.0.282-py3-none-win_amd64.whl", hash = "sha256:bd25085c42ebaffe336ed7bda8a0ae7b6c454a5f386ec8b2299503f79bd12bdf"}, - {file = "ruff-0.0.282-py3-none-win_arm64.whl", hash = "sha256:f03fba9621533d67d7ab995847467d78b9337e3697779ef2cea6f1deaee5fbef"}, - {file = "ruff-0.0.282.tar.gz", hash = "sha256:ef677c26bae756e4c98af6d8972da83caea550bc92ffef97a6e939ca5b24ad06"}, + {file = "ruff-0.1.3-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:b46d43d51f7061652eeadb426a9e3caa1e0002470229ab2fc19de8a7b0766901"}, + {file = "ruff-0.1.3-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:b8afeb9abd26b4029c72adc9921b8363374f4e7edb78385ffaa80278313a15f9"}, + {file = "ruff-0.1.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca3cf365bf32e9ba7e6db3f48a4d3e2c446cd19ebee04f05338bc3910114528b"}, + {file = "ruff-0.1.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4874c165f96c14a00590dcc727a04dca0cfd110334c24b039458c06cf78a672e"}, + {file = "ruff-0.1.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eec2dd31eed114e48ea42dbffc443e9b7221976554a504767ceaee3dd38edeb8"}, + {file = "ruff-0.1.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:dc3ec4edb3b73f21b4aa51337e16674c752f1d76a4a543af56d7d04e97769613"}, + {file = "ruff-0.1.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e3de9ed2e39160800281848ff4670e1698037ca039bda7b9274f849258d26ce"}, + {file = "ruff-0.1.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c595193881922cc0556a90f3af99b1c5681f0c552e7a2a189956141d8666fe8"}, + {file = "ruff-0.1.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f75e670d529aa2288cd00fc0e9b9287603d95e1536d7a7e0cafe00f75e0dd9d"}, + {file = "ruff-0.1.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:76dd49f6cd945d82d9d4a9a6622c54a994689d8d7b22fa1322983389b4892e20"}, + {file = "ruff-0.1.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:918b454bc4f8874a616f0d725590277c42949431ceb303950e87fef7a7d94cb3"}, + {file = "ruff-0.1.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d8859605e729cd5e53aa38275568dbbdb4fe882d2ea2714c5453b678dca83784"}, + {file = "ruff-0.1.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0b6c55f5ef8d9dd05b230bb6ab80bc4381ecb60ae56db0330f660ea240cb0d4a"}, + {file = "ruff-0.1.3-py3-none-win32.whl", hash = "sha256:3e7afcbdcfbe3399c34e0f6370c30f6e529193c731b885316c5a09c9e4317eef"}, + {file = "ruff-0.1.3-py3-none-win_amd64.whl", hash = "sha256:7a18df6638cec4a5bd75350639b2bb2a2366e01222825562c7346674bdceb7ea"}, + {file = "ruff-0.1.3-py3-none-win_arm64.whl", hash = "sha256:12fd53696c83a194a2db7f9a46337ce06445fb9aa7d25ea6f293cf75b21aca9f"}, + {file = "ruff-0.1.3.tar.gz", hash = "sha256:3ba6145369a151401d5db79f0a47d50e470384d0d89d0d6f7fab0b589ad07c34"}, ] [[package]] name = "setuptools" -version = "68.0.0" +version = "68.2.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, - {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, + {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, + {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, ] [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-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "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-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "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"] +docs = ["furo", "jaraco.packaging (>=9.3)", "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-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "sniffio" @@ -1136,7 +1123,6 @@ files = [ [package.dependencies] anyio = ">=3.4.0,<5" -typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} [package.extras] full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] @@ -1200,28 +1186,28 @@ test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6. [[package]] name = "typing-extensions" -version = "4.7.1" -description = "Backported and Experimental Type Hints for Python 3.7+" +version = "4.8.0" +description = "Backported and Experimental Type Hints for Python 3.8+" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, - {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, + {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, + {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, ] [[package]] name = "urllib3" -version = "1.26.16" +version = "1.26.18" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ - {file = "urllib3-1.26.16-py2.py3-none-any.whl", hash = "sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f"}, - {file = "urllib3-1.26.16.tar.gz", hash = "sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14"}, + {file = "urllib3-1.26.18-py2.py3-none-any.whl", hash = "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07"}, + {file = "urllib3-1.26.18.tar.gz", hash = "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] @@ -1239,35 +1225,34 @@ files = [ [package.dependencies] click = ">=7.0" h11 = ">=0.8" -typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} [package.extras] standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] [[package]] name = "wcwidth" -version = "0.2.6" +version = "0.2.8" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = "*" files = [ - {file = "wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"}, - {file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"}, + {file = "wcwidth-0.2.8-py2.py3-none-any.whl", hash = "sha256:77f719e01648ed600dfa5402c347481c0992263b81a027344f3e1ba25493a704"}, + {file = "wcwidth-0.2.8.tar.gz", hash = "sha256:8705c569999ffbb4f6a87c6d1b80f324bd6db952f5eb0b95bc07517f4c1813d4"}, ] [[package]] name = "zipp" -version = "3.16.2" +version = "3.17.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.16.2-py3-none-any.whl", hash = "sha256:679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0"}, - {file = "zipp-3.16.2.tar.gz", hash = "sha256:ebc15946aa78bd63458992fc81ec3b6f7b1e92d51c35e6de1c3804e73b799147"}, + {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, + {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] [[package]] @@ -1290,5 +1275,5 @@ typing-extensions = ">=3.7" [metadata] lock-version = "2.0" -python-versions = ">=3.9,<3.12" -content-hash = "36f420145078a9640dec4edab4761899ba716b277924b369ec45cd088c4c1485" +python-versions = ">=3.11,<3.12" +content-hash = "ed3cbc138222a794d331565d9115dbe63cc8a55b024f9404692813fae1679972" diff --git a/pyproject.toml b/pyproject.toml index 7dd8cb79..7f494fc5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,13 +10,13 @@ packages = [{ include = "zulip_write_only_proxy", from = "src" }] damnit-zulip = "zulip_write_only_proxy.cli:app" [tool.poetry.dependencies] -python = ">=3.9,<3.12" -fastapi = "^0.100.0" +python = ">=3.11,<3.12" +fastapi = "^0.104" uvicorn = "^0.23.1" zulip = "^0.8.2" python-multipart = "^0.0.6" orjson = "^3.9.2" -pydantic = "^2.1.1" +pydantic = "^2.4.2" typer = "^0.9.0" [tool.poetry.group.test.dependencies] @@ -29,14 +29,14 @@ poethepoet = "^0.21.1" commitizen = "^3.6.0" [tool.poetry.group.lint.dependencies] -ruff = "^0.0.282" +ruff = "^0.1.0" mypy = "^1.4.1" pyright = "^1.1.320" black = "^23.7.0" [tool.black] line-length = 88 -target-version = ["py39", "py310", "py311"] +target-version = ["py311", "py312"] [tool.ruff] line-length = 88 @@ -51,7 +51,7 @@ extend-select = [ ] extend-ignore = ["B018", "B019"] src = ["src"] -target-version = "py39" +target-version = "py311" [tool.ruff.mccabe] max-complexity = 10 From d34b54984c08a7b55793a5fb9a37092c9aa4c4a9 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Fri, 27 Oct 2023 15:13:38 +0200 Subject: [PATCH 116/143] test(endpoint): add tests for `/me` endpoint --- tests/test_fastapi.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/tests/test_fastapi.py b/tests/test_fastapi.py index 0dd7d8eb..41323bf5 100644 --- a/tests/test_fastapi.py +++ b/tests/test_fastapi.py @@ -2,7 +2,9 @@ from typing import TYPE_CHECKING from unittest.mock import MagicMock, patch -from zulip_write_only_proxy import services +import pytest + +from zulip_write_only_proxy import models, services if TYPE_CHECKING: from fastapi.testclient import TestClient @@ -227,3 +229,22 @@ def test_create_client_error(fastapi_client): # Check that the services module was called with the expected arguments services.create_client.assert_called_once_with(1234, "Test Stream") + + +@pytest.mark.parametrize( + "client_type,kwargs", + [(models.AdminClient, {}), (models.ScopedClient, {"proposal_no": 0})], +) +def test_get_me(client_type, kwargs, fastapi_client, zulip_client): + client = client_type.create(**kwargs) + + with patch( + "zulip_write_only_proxy.services.get_client", + MagicMock(return_value=client), + ): + response = fastapi_client.get( + "/me", + headers={"X-API-key": client.key.get_secret_value()}, + ) + + assert response.status_code == 200 From 7372aa11c7936bf8100d7c3f0f62fe1e586d7147 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Fri, 27 Oct 2023 15:14:39 +0200 Subject: [PATCH 117/143] fix(endpoint): fix `/me` endpoint response type missmatch --- src/zulip_write_only_proxy/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index 445ed7bd..fbab4ca5 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -116,8 +116,8 @@ def get_stream_topics( @app.get("/me", tags=["User"]) def get_me( - client: Annotated[models.ScopedClient, fastapi.Depends(get_client)], -) -> models.ScopedClient: + client: Annotated[models.Client, fastapi.Depends(get_client)], +) -> models.Client: return client From 48078247f9089d68ba761f884cffceae6e2c991b Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Fri, 27 Oct 2023 15:17:03 +0200 Subject: [PATCH 118/143] fix(endpoint): update mypy ignore categories --- src/zulip_write_only_proxy/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index fbab4ca5..a980d1ed 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -46,7 +46,7 @@ def send_message( # Some screwing around to get the spooled tmp file to act more like a real file # since Zulip needs it to have a filename f: SpooledTemporaryFile = image.file # type: ignore[assignment] - f._file.name = image.filename # type: ignore[attr-defined] + f._file.name = image.filename # type: ignore[misc, assignment] result = client.upload_file(f) @@ -95,7 +95,7 @@ def upload_file( file: Annotated[fastapi.UploadFile, fastapi.File(...)], ): f: SpooledTemporaryFile = file.file # type: ignore[assignment] - f._file.name = file.filename # type: ignore[attr-defined] + f._file.name = file.filename # type: ignore[misc, assignment] return client.upload_file(f) From 26790051775ef9e43a5247cc0040ac09df07ec8a Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Fri, 27 Oct 2023 16:09:45 +0200 Subject: [PATCH 119/143] feat(endpoint): explicitly exclude `Client.key` from `/me` response --- src/zulip_write_only_proxy/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index a980d1ed..a49b04df 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -114,9 +114,9 @@ def get_stream_topics( return client.get_stream_topics() -@app.get("/me", tags=["User"]) +@app.get("/me", tags=["User"], response_model_exclude={"key"}) def get_me( - client: Annotated[models.Client, fastapi.Depends(get_client)], + client: Annotated[models.Client, fastapi.Depends(get_client)] ) -> models.Client: return client From a75dc1c94dbc420e65d454d29a722a4785be973b Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Fri, 27 Oct 2023 16:10:09 +0200 Subject: [PATCH 120/143] feat(endpoint): tag `/health` as `Admin` --- src/zulip_write_only_proxy/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index a49b04df..f91b38f8 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -121,7 +121,7 @@ def get_me( return client -@app.get("/health") +@app.get("/health", tags=["Admin"]) def healthcheck(): return "OK" From fb84c379fcd90c09e0c4a9a148db7bf13e023ac2 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Fri, 27 Oct 2023 16:11:36 +0200 Subject: [PATCH 121/143] fix(endpoint): return client key on `/create_client` call perviously returned the obfuscated `SecretStr` --- src/zulip_write_only_proxy/main.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index f91b38f8..9fe774eb 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -126,13 +126,20 @@ def healthcheck(): return "OK" +class ScopedClientWithKey(models.ScopedClient): + key: str # type: ignore[assignment] + + @app.post("/create_client", tags=["Admin"]) def create_client( admin_client: Annotated[models.AdminClient, fastapi.Depends(get_client)], proposal_no: Annotated[int, fastapi.Query(...)], stream: Annotated[Union[str, None], fastapi.Query()] = None, -): +) -> ScopedClientWithKey: try: - return services.create_client(proposal_no, stream) + client = services.create_client(proposal_no, stream) + dump = client.model_dump() + dump["key"] = client.key.get_secret_value() + return ScopedClientWithKey(**dump) except ValueError as e: raise fastapi.HTTPException(status_code=400, detail=str(e)) from e From 7050660724160a07685a0ab830c8a95eb1841a30 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Fri, 27 Oct 2023 16:16:38 +0200 Subject: [PATCH 122/143] test(endpoint): test that `/create_client` returns exposed secret --- tests/test_fastapi.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/test_fastapi.py b/tests/test_fastapi.py index 41323bf5..29d7a68f 100644 --- a/tests/test_fastapi.py +++ b/tests/test_fastapi.py @@ -198,15 +198,19 @@ def test_get_stream_topics_error(fastapi_client, zulip_client): def test_create_client(fastapi_client, zulip_client): zulip_client.create_client = MagicMock(return_value={"result": "success"}) - response = fastapi_client.post( - "/create_client", - headers={"X-API-key": "admin1"}, - params={"proposal_no": 1234, "stream": "Test Stream"}, - ) + with patch( + "secrets.token_urlsafe", + MagicMock(return_value="exposed-secret"), + ): + response = fastapi_client.post( + "/create_client", + headers={"X-API-key": "admin1"}, + params={"proposal_no": 1234, "stream": "Test Stream"}, + ) assert response.status_code == 200 assert response.json() == { - "key": "**********", + "key": "exposed-secret", "proposal_no": 1234, "stream": "Test Stream", } From aa6088388aabd4fb674127b3caef1726518c3725 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Fri, 27 Oct 2023 16:19:24 +0200 Subject: [PATCH 123/143] ci: bump python version for lint workflow to 3.11 --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index cb767136..5c68c0dd 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.9"] + python-version: ["3.11"] steps: - name: Checkout project uses: actions/checkout@v3 From cdf7a679241f9e1072ca31ffef2e83f54a484d99 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Fri, 27 Oct 2023 16:20:22 +0200 Subject: [PATCH 124/143] build: bump max python version to 3.13 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7f494fc5..a8b064d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ packages = [{ include = "zulip_write_only_proxy", from = "src" }] damnit-zulip = "zulip_write_only_proxy.cli:app" [tool.poetry.dependencies] -python = ">=3.11,<3.12" +python = ">=3.11,<3.13" fastapi = "^0.104" uvicorn = "^0.23.1" zulip = "^0.8.2" From cef3faa5ef96a7d84716464be6aff641c211a4ed Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Tue, 7 Nov 2023 14:22:59 +0100 Subject: [PATCH 125/143] build(deploy): use port 8080 as standard --- Dockerfile | 4 ++-- nginx.conf | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4a7367cf..20d09501 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,6 @@ RUN poetry config virtualenvs.create false --local RUN --mount=type=cache,target=/root/.cache\ poetry install -CMD ["uvicorn", "zulip_write_only_proxy.main:app", "--host", "0.0.0.0", "--port", "8000"] +CMD ["uvicorn", "zulip_write_only_proxy.main:app", "--host", "0.0.0.0", "--port", "8080"] -HEALTHCHECK CMD curl --fail http://localhost:8000/health || exit 1 +HEALTHCHECK CMD curl --fail http://localhost:8080/health || exit 1 diff --git a/nginx.conf b/nginx.conf index 440a2ac0..3af964b5 100644 --- a/nginx.conf +++ b/nginx.conf @@ -5,7 +5,7 @@ http { listen 80; location / { - proxy_pass http://zwop:8000; + proxy_pass http://zwop:8080; } } } From 8c12aa778386e83ab8b8caf6eae0e24a58041c76 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Tue, 7 Nov 2023 14:23:37 +0100 Subject: [PATCH 126/143] build(deploy): allow setting image and tag via env vars --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index d4f371fb..a6a2bbb0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,7 +3,7 @@ version: "3.8" services: zwop: - image: ghcr.io/european-xfel/zulip-write-only-proxy:pr-1 + image: ${IMAGE:-ghcr.io/european-xfel/zulip-write-only-proxy}:${TAG:?error} deploy: update_config: parallelism: 1 From 3bda45b4a9883830bc93bf6a96784c9dbffd5895 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Tue, 7 Nov 2023 14:24:12 +0100 Subject: [PATCH 127/143] build(dev): add 'dev-docker' target to Makefile --- Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile b/Makefile index 84fb4fa5..cece21b7 100644 --- a/Makefile +++ b/Makefile @@ -5,3 +5,7 @@ up: down: docker stack rm zwop + +dev-docker: + docker build . --tag zwop:dev + docker run -it --rm -v $(PWD):/app -p 8080:8080 zwop:dev From cfdd9aea91d5a5da1bb9af78eb017747ae68dfa0 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Tue, 7 Nov 2023 14:25:16 +0100 Subject: [PATCH 128/143] build: use poetry-dynamic-versioning --- .gitignore | 1 + pyproject.toml | 24 +++++++++++++++++++++--- src/zulip_write_only_proxy/__init__.py | 7 +++++++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index e6a98ef4..1f44628c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .env zuliprc clients.json +src/zulip_write_only_proxy/_version.py # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/pyproject.toml b/pyproject.toml index a8b064d5..1a9819d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "zulip-write-only-proxy" -version = "0.1.0" +version = "0.0.0" description = "" authors = ["Robert Rosca"] readme = "README.md" @@ -83,6 +83,24 @@ version_provider = "poetry" update_changelog_on_bump = true major_version_zero = true +[tool.poetry-dynamic-versioning] +enable = true +dirty = true +style = "semver" + +[tool.poetry-dynamic-versioning.substitution] +folders = [ + { path = "src" } +] + +[tool.poetry-dynamic-versioning.files."src/zulip_write_only_proxy/_version.py"] +persistent-substitution = true +initial-content = """ + # These version placeholders will be replaced later during substitution. + __version__ = "0.0.0" + __version_tuple__ = (0, 0, 0) +""" + [build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" +requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"] +build-backend = "poetry_dynamic_versioning.backend" diff --git a/src/zulip_write_only_proxy/__init__.py b/src/zulip_write_only_proxy/__init__.py index e69de29b..21958600 100644 --- a/src/zulip_write_only_proxy/__init__.py +++ b/src/zulip_write_only_proxy/__init__.py @@ -0,0 +1,7 @@ +try: + from ._version import __version__, __version_tuple__ +except ImportError: + __version__ = "unknown" + __version_tuple__ = (0, 0, 0) + +__all__ = ["__version__", "__version_tuple__"] From 632612ddbe5ac4d32ae87748e9df85c188b8b2bf Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Tue, 7 Nov 2023 14:37:25 +0100 Subject: [PATCH 129/143] feat(endpoint): include version info in health response --- src/zulip_write_only_proxy/main.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index 9fe774eb..5945511e 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -123,7 +123,13 @@ def get_me( @app.get("/health", tags=["Admin"]) def healthcheck(): - return "OK" + return { + "status": "OK", + "dirty": "dirty" in _version.__version__, + "dev": "+" in _version.__version__, + "version": _version.__version__, + "version_tuple": _version.__version_tuple__, + } class ScopedClientWithKey(models.ScopedClient): From e8e130389ae05da2cefd97224312d79fba566098 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Tue, 7 Nov 2023 14:38:11 +0100 Subject: [PATCH 130/143] feat(logging): exclude '/health' endpoint calls from logs --- src/zulip_write_only_proxy/_logging.py | 7 +++++++ src/zulip_write_only_proxy/main.py | 4 +++- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 src/zulip_write_only_proxy/_logging.py diff --git a/src/zulip_write_only_proxy/_logging.py b/src/zulip_write_only_proxy/_logging.py new file mode 100644 index 00000000..fa095e37 --- /dev/null +++ b/src/zulip_write_only_proxy/_logging.py @@ -0,0 +1,7 @@ +import logging + + +class EndpointFilter(logging.Filter): + def filter(self, record: logging.LogRecord) -> bool: + # Exclude health check requests from the logs + return record.getMessage().find("/health") == -1 diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index 5945511e..49b34918 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -1,18 +1,20 @@ from __future__ import annotations from contextlib import asynccontextmanager +import logging from tempfile import SpooledTemporaryFile from typing import Annotated, Union import fastapi from fastapi.security import APIKeyHeader -from . import models, services +from . import models, services, _version, _logging @asynccontextmanager async def lifespan(app: fastapi.FastAPI): services.setup() + logging.getLogger("uvicorn.access").addFilter(_logging.EndpointFilter()) yield From 18d2fc04efd3ff639c437fbebc2fe7aed8cfb99b Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Tue, 7 Nov 2023 14:38:43 +0100 Subject: [PATCH 131/143] docs: add todo section --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index f6fa4b97..596fe366 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,14 @@ poe test # Run tests poe serve # Run the server ``` +### Todo + +Tentative list of things to do in the future: + +- [ ] Query MyMdC for the stream name given a proposal number +- [ ] Query MyMdC for the list of topics and pass that on instead of trying to get them with the Zulip API +- [ ] Improve logging (structlog/loguru? sentry?) + ## Deployment Deployment is similar to development with `docker compose`, but instead a docker stack is used to allow for better scaling and update configuration. From 008fbcd6bb47294ac077e13be8e79331b501fe3f Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Tue, 7 Nov 2023 14:39:16 +0100 Subject: [PATCH 132/143] docs: add client creation section --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 596fe366..b162a23f 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,19 @@ response = client.post( ) ``` +#### Creating a Client + +If you have an admin token you can create a new client for a proposal and stream. This will create a new token for the client and return it: + +```python +response = client.post( + f"{base_url}/create_client", + params={"proposal_no": 1234, "stream": "proposal 1234 stream"}, +) +``` + +Note that admin tokens can only create clients, they cannot post to a stream as they have no associated stream. + ## Development ### Setup From 5e985a925f272e728e8f34e32be1786e39c8a545 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Tue, 7 Nov 2023 14:39:54 +0100 Subject: [PATCH 133/143] docs: update deployment section --- README.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index b162a23f..abcdef2a 100644 --- a/README.md +++ b/README.md @@ -179,16 +179,17 @@ Tentative list of things to do in the future: Deployment is similar to development with `docker compose`, but instead a docker stack is used to allow for better scaling and update configuration. -To quickly bring the service up or down run `make up` or `make down`. - -Bringing up the service runs: +There are two required environment variables, the port to run on (`PORT`), and the tag to use for the image (`TAG`). These should be set in an `.env` file: -```sh -docker compose config | docker stack deploy -c - zwop +```env +PORT=8089 +TAG=0.1.0 ``` -To update the stack, use the same command. This will pull in the latest image and perform a rolling restart of the service, which will first start the new container, wait for a successful health check, and then stop the old container. +To quickly bring the service up or down run `make up` or `make down`. + +To update, bump up the tag run `make up` again. This will pull in the latest image and perform a rolling restart of the service, which will first start the new container, wait for a successful health check, and then stop the old container. -A cron job runs the deployment command every minute to check for updates. +For development use there is an additional variable `IMAGE` which can be set to the name of a local image to use instead of pulling from the registry. This is useful for testing changes. If you recently used `make dev-docker` that would have build an image tagged `zwop:dev` which you could then use by setting `IMAGE=zwop:dev` in the `.env` file (note that this will not reflect code changes since the last image build). -NB: There is an outstanding issue with `docker stack deploy` where it [does not load `.env` files](https://github.com/moby/moby/issues/29133) in the same way that `docker compose up` does. This is solved by running `docker compose config` to generate a compose-compliant file (with env vars subsituted) and piping that to `docker stack deploy`. +NB: There is an outstanding issue with `docker stack deploy` where it [does not load `.env` files](https://github.com/moby/moby/issues/29133) in the same way that `docker compose up` does. This is solved by running `docker compose config` to generate a compose-compliant file (with env vars substituted), making a few changes via `sed`, and piping that to `docker stack deploy -`. Check the `Makefile` to see the exact command. From fc72e4c1c08975d18e89ed8ed89c4c49c4b80797 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Tue, 7 Nov 2023 14:40:18 +0100 Subject: [PATCH 134/143] docs: update development section --- README.md | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index abcdef2a..f18b0c98 100644 --- a/README.md +++ b/README.md @@ -92,37 +92,35 @@ Note that admin tokens can only create clients, they cannot post to a stream as ### Setup +For development you can either use docker or install the package locally via Poetry. + For docker: ```sh -# Start server in background -docker compose up -d -``` +# Build the image: +docker build . --tag zwop:dev -For CLI: +# Start the server, with the current directory mounted as a volume: +docker run -it --rm -v $(PWD):/app -p 8080:8080 zwop:dev -```sh -# Normal venv: -python3 -m venv .venv -source .venv/bin/activate +# Or use make, which does the same thing: +make dev-docker +``` -python3 -m pip install . +For a direct install with Poetry: -# Poetry: +```sh +# Install and activate poetry install poetry shell -# Start the server: +# Start the server using poe, see next section for more details on poe tasks poe serve ``` -To create a client for proposal 2222: - -```sh -damnit-zulip create 2222 "proposal 2222 stream" -``` +### Client Configuration -Default configuration is something like: +The configuration is very basic and is stored in a JSON file. The client token is used as a key and the value is a dictionary containing the proposal number and stream name. ```json { @@ -133,7 +131,7 @@ Default configuration is something like: } ``` -Stream/topic can be edited manually in the JSON file or set via CLI at creation time. +Stream/topic can be edited manually in the JSON file or set at creation time. ### Tasks From 9dbbcee335b3acfadc2627b4752f164123729061 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Tue, 7 Nov 2023 14:40:50 +0100 Subject: [PATCH 135/143] docs: replace exfldadev01 with localhost --- README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f18b0c98..57eb2069 100644 --- a/README.md +++ b/README.md @@ -4,16 +4,16 @@ ### Client -Recommended clients are `requests` for synchronous code and `httpx` for asynchronous code. +Recommended clients are `requests` for synchronous code and `httpx` for asynchronous code. These examples are written for a server running locally at but the actual URL will depend on the deployment. Using requests (synchronous): ```python import requests -base_url = "http://exfldadev01.desy.de:8089" +base_url = "http://localhost:8080" -response = requests.post(f"{base_url}/endpoint", data=data, ...) +response = requests.post(f"{base_url}/{endpoint}", data=data, ...) ``` Using httpx (async): @@ -21,12 +21,14 @@ Using httpx (async): ```python import httpx -base_url = "http://exfldadev01.desy.de:8089" +base_url = "http://localhost:8080" async with httpx.AsyncClient() as client: - response = await client.post(f"{base_url}/endpoint", ...) + response = await client.post(f"{base_url}/{endpoint}", ...) ``` +Replace `endpoint` with the desired endpoint (e.g. `send_message`). + ### Authentication Authentication is done by including the token in the header, for example: @@ -50,7 +52,7 @@ client.headers.update({"X-API-key": "token"}) ### Endpoints -Full API documentation is available by going to the `/docs` page (e.g. ). A few examples of basic usage are provided below. +Full API documentation is available by going to the `/docs` page (e.g. ). These examples are just intended to show basic usage and may not represent the current state of the API, check the API docs for the latest information. #### Sending a message with text From 7cdd57b76bbea56c81ceb9f953b23868de836877 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Tue, 7 Nov 2023 14:43:17 +0100 Subject: [PATCH 136/143] style: format, fix mypy error --- src/zulip_write_only_proxy/__init__.py | 2 +- src/zulip_write_only_proxy/main.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/zulip_write_only_proxy/__init__.py b/src/zulip_write_only_proxy/__init__.py index 21958600..b93dfb2c 100644 --- a/src/zulip_write_only_proxy/__init__.py +++ b/src/zulip_write_only_proxy/__init__.py @@ -2,6 +2,6 @@ from ._version import __version__, __version_tuple__ except ImportError: __version__ = "unknown" - __version_tuple__ = (0, 0, 0) + __version_tuple__ = (0, 0, 0) # type: ignore[assignment] __all__ = ["__version__", "__version_tuple__"] diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index 49b34918..9142933f 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -1,14 +1,14 @@ from __future__ import annotations -from contextlib import asynccontextmanager import logging +from contextlib import asynccontextmanager from tempfile import SpooledTemporaryFile from typing import Annotated, Union import fastapi from fastapi.security import APIKeyHeader -from . import models, services, _version, _logging +from . import _logging, _version, models, services @asynccontextmanager From 6f8e0690dc8c493f9a6823b44f26f00973258e2a Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Tue, 7 Nov 2023 14:43:45 +0100 Subject: [PATCH 137/143] build: update lockfile --- poetry.lock | 288 ++++++++++++++++++++++++++-------------------------- 1 file changed, 144 insertions(+), 144 deletions(-) diff --git a/poetry.lock b/poetry.lock index 343cf125..e631465a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "annotated-types" @@ -33,13 +33,13 @@ trio = ["trio (<0.22)"] [[package]] name = "argcomplete" -version = "3.1.2" +version = "3.1.4" description = "Bash tab completion for argparse" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "argcomplete-3.1.2-py3-none-any.whl", hash = "sha256:d97c036d12a752d1079f190bc1521c545b941fda89ad85d15afa909b4d1b9a99"}, - {file = "argcomplete-3.1.2.tar.gz", hash = "sha256:d5d1e5efd41435260b8f85673b74ea2e883affcbec9f4230c582689e8e78251b"}, + {file = "argcomplete-3.1.4-py3-none-any.whl", hash = "sha256:fbe56f8cda08aa9a04b307d8482ea703e96a6a801611acb4be9bf3942017989f"}, + {file = "argcomplete-3.1.4.tar.gz", hash = "sha256:72558ba729e4c468572609817226fb0a6e7e9a0a7d477b882be168c0b4a62b94"}, ] [package.extras] @@ -98,101 +98,101 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.3.1" +version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.3.1.tar.gz", hash = "sha256:d9137a876020661972ca6eec0766d81aef8a5627df628b664b234b73396e727e"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8aee051c89e13565c6bd366813c386939f8e928af93c29fda4af86d25b73d8f8"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:352a88c3df0d1fa886562384b86f9a9e27563d4704ee0e9d56ec6fcd270ea690"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:223b4d54561c01048f657fa6ce41461d5ad8ff128b9678cfe8b2ecd951e3f8a2"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f861d94c2a450b974b86093c6c027888627b8082f1299dfd5a4bae8e2292821"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1171ef1fc5ab4693c5d151ae0fdad7f7349920eabbaca6271f95969fa0756c2d"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28f512b9a33235545fbbdac6a330a510b63be278a50071a336afc1b78781b147"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0e842112fe3f1a4ffcf64b06dc4c61a88441c2f02f373367f7b4c1aa9be2ad5"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f9bc2ce123637a60ebe819f9fccc614da1bcc05798bbbaf2dd4ec91f3e08846"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f194cce575e59ffe442c10a360182a986535fd90b57f7debfaa5c845c409ecc3"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9a74041ba0bfa9bc9b9bb2cd3238a6ab3b7618e759b41bd15b5f6ad958d17605"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b578cbe580e3b41ad17b1c428f382c814b32a6ce90f2d8e39e2e635d49e498d1"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6db3cfb9b4fcecb4390db154e75b49578c87a3b9979b40cdf90d7e4b945656e1"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:debb633f3f7856f95ad957d9b9c781f8e2c6303ef21724ec94bea2ce2fcbd056"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-win32.whl", hash = "sha256:87071618d3d8ec8b186d53cb6e66955ef2a0e4fa63ccd3709c0c90ac5a43520f"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:e372d7dfd154009142631de2d316adad3cc1c36c32a38b16a4751ba78da2a397"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae4070f741f8d809075ef697877fd350ecf0b7c5837ed68738607ee0a2c572cf"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:58e875eb7016fd014c0eea46c6fa92b87b62c0cb31b9feae25cbbe62c919f54d"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dbd95e300367aa0827496fe75a1766d198d34385a58f97683fe6e07f89ca3e3c"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de0b4caa1c8a21394e8ce971997614a17648f94e1cd0640fbd6b4d14cab13a72"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:985c7965f62f6f32bf432e2681173db41336a9c2611693247069288bcb0c7f8b"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a15c1fe6d26e83fd2e5972425a772cca158eae58b05d4a25a4e474c221053e2d"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae55d592b02c4349525b6ed8f74c692509e5adffa842e582c0f861751701a673"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be4d9c2770044a59715eb57c1144dedea7c5d5ae80c68fb9959515037cde2008"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:851cf693fb3aaef71031237cd68699dded198657ec1e76a76eb8be58c03a5d1f"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:31bbaba7218904d2eabecf4feec0d07469284e952a27400f23b6628439439fa7"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:871d045d6ccc181fd863a3cd66ee8e395523ebfbc57f85f91f035f50cee8e3d4"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:501adc5eb6cd5f40a6f77fbd90e5ab915c8fd6e8c614af2db5561e16c600d6f3"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f5fb672c396d826ca16a022ac04c9dce74e00a1c344f6ad1a0fdc1ba1f332213"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-win32.whl", hash = "sha256:bb06098d019766ca16fc915ecaa455c1f1cd594204e7f840cd6258237b5079a8"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:8af5a8917b8af42295e86b64903156b4f110a30dca5f3b5aedea123fbd638bff"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7ae8e5142dcc7a49168f4055255dbcced01dc1714a90a21f87448dc8d90617d1"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5b70bab78accbc672f50e878a5b73ca692f45f5b5e25c8066d748c09405e6a55"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ceca5876032362ae73b83347be8b5dbd2d1faf3358deb38c9c88776779b2e2f"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34d95638ff3613849f473afc33f65c401a89f3b9528d0d213c7037c398a51296"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9edbe6a5bf8b56a4a84533ba2b2f489d0046e755c29616ef8830f9e7d9cf5728"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6a02a3c7950cafaadcd46a226ad9e12fc9744652cc69f9e5534f98b47f3bbcf"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10b8dd31e10f32410751b3430996f9807fc4d1587ca69772e2aa940a82ab571a"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edc0202099ea1d82844316604e17d2b175044f9bcb6b398aab781eba957224bd"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b891a2f68e09c5ef989007fac11476ed33c5c9994449a4e2c3386529d703dc8b"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:71ef3b9be10070360f289aea4838c784f8b851be3ba58cf796262b57775c2f14"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:55602981b2dbf8184c098bc10287e8c245e351cd4fdcad050bd7199d5a8bf514"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:46fb9970aa5eeca547d7aa0de5d4b124a288b42eaefac677bde805013c95725c"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:520b7a142d2524f999447b3a0cf95115df81c4f33003c51a6ab637cbda9d0bf4"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-win32.whl", hash = "sha256:8ec8ef42c6cd5856a7613dcd1eaf21e5573b2185263d87d27c8edcae33b62a61"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:baec8148d6b8bd5cee1ae138ba658c71f5b03e0d69d5907703e3e1df96db5e41"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63a6f59e2d01310f754c270e4a257426fe5a591dc487f1983b3bbe793cf6bac6"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d6bfc32a68bc0933819cfdfe45f9abc3cae3877e1d90aac7259d57e6e0f85b1"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f3100d86dcd03c03f7e9c3fdb23d92e32abbca07e7c13ebd7ddfbcb06f5991f"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39b70a6f88eebe239fa775190796d55a33cfb6d36b9ffdd37843f7c4c1b5dc67"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e12f8ee80aa35e746230a2af83e81bd6b52daa92a8afaef4fea4a2ce9b9f4fa"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b6cefa579e1237ce198619b76eaa148b71894fb0d6bcf9024460f9bf30fd228"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:61f1e3fb621f5420523abb71f5771a204b33c21d31e7d9d86881b2cffe92c47c"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4f6e2a839f83a6a76854d12dbebde50e4b1afa63e27761549d006fa53e9aa80e"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:1ec937546cad86d0dce5396748bf392bb7b62a9eeb8c66efac60e947697f0e58"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:82ca51ff0fc5b641a2d4e1cc8c5ff108699b7a56d7f3ad6f6da9dbb6f0145b48"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:633968254f8d421e70f91c6ebe71ed0ab140220469cf87a9857e21c16687c034"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-win32.whl", hash = "sha256:c0c72d34e7de5604df0fde3644cc079feee5e55464967d10b24b1de268deceb9"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:63accd11149c0f9a99e3bc095bbdb5a464862d77a7e309ad5938fbc8721235ae"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5a3580a4fdc4ac05f9e53c57f965e3594b2f99796231380adb2baaab96e22761"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2465aa50c9299d615d757c1c888bc6fef384b7c4aec81c05a0172b4400f98557"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cb7cd68814308aade9d0c93c5bd2ade9f9441666f8ba5aa9c2d4b389cb5e2a45"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91e43805ccafa0a91831f9cd5443aa34528c0c3f2cc48c4cb3d9a7721053874b"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:854cc74367180beb327ab9d00f964f6d91da06450b0855cbbb09187bcdb02de5"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c15070ebf11b8b7fd1bfff7217e9324963c82dbdf6182ff7050519e350e7ad9f"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c4c99f98fc3a1835af8179dcc9013f93594d0670e2fa80c83aa36346ee763d2"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fb765362688821404ad6cf86772fc54993ec11577cd5a92ac44b4c2ba52155b"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dced27917823df984fe0c80a5c4ad75cf58df0fbfae890bc08004cd3888922a2"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a66bcdf19c1a523e41b8e9d53d0cedbfbac2e93c649a2e9502cb26c014d0980c"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ecd26be9f112c4f96718290c10f4caea6cc798459a3a76636b817a0ed7874e42"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3f70fd716855cd3b855316b226a1ac8bdb3caf4f7ea96edcccc6f484217c9597"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:17a866d61259c7de1bdadef418a37755050ddb4b922df8b356503234fff7932c"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-win32.whl", hash = "sha256:548eefad783ed787b38cb6f9a574bd8664468cc76d1538215d510a3cd41406cb"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:45f053a0ece92c734d874861ffe6e3cc92150e32136dd59ab1fb070575189c97"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bc791ec3fd0c4309a753f95bb6c749ef0d8ea3aea91f07ee1cf06b7b02118f2f"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0c8c61fb505c7dad1d251c284e712d4e0372cef3b067f7ddf82a7fa82e1e9a93"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2c092be3885a1b7899cd85ce24acedc1034199d6fca1483fa2c3a35c86e43041"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2000c54c395d9e5e44c99dc7c20a64dc371f777faf8bae4919ad3e99ce5253e"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4cb50a0335382aac15c31b61d8531bc9bb657cfd848b1d7158009472189f3d62"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c30187840d36d0ba2893bc3271a36a517a717f9fd383a98e2697ee890a37c273"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe81b35c33772e56f4b6cf62cf4aedc1762ef7162a31e6ac7fe5e40d0149eb67"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0bf89afcbcf4d1bb2652f6580e5e55a840fdf87384f6063c4a4f0c95e378656"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:06cf46bdff72f58645434d467bf5228080801298fbba19fe268a01b4534467f5"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3c66df3f41abee950d6638adc7eac4730a306b022570f71dd0bd6ba53503ab57"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd805513198304026bd379d1d516afbf6c3c13f4382134a2c526b8b854da1c2e"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:9505dc359edb6a330efcd2be825fdb73ee3e628d9010597aa1aee5aa63442e97"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:31445f38053476a0c4e6d12b047b08ced81e2c7c712e5a1ad97bc913256f91b2"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-win32.whl", hash = "sha256:bd28b31730f0e982ace8663d108e01199098432a30a4c410d06fe08fdb9e93f4"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:555fe186da0068d3354cdf4bbcbc609b0ecae4d04c921cc13e209eece7720727"}, - {file = "charset_normalizer-3.3.1-py3-none-any.whl", hash = "sha256:800561453acdecedaac137bf09cd719c7a440b6800ec182f077bb8e7025fb708"}, + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] [[package]] @@ -332,13 +332,13 @@ files = [ [[package]] name = "fastapi" -version = "0.104.0" +version = "0.104.1" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" files = [ - {file = "fastapi-0.104.0-py3-none-any.whl", hash = "sha256:456482c1178fb7beb2814b88e1885bc49f9a81f079665016feffe3e1c6a7663e"}, - {file = "fastapi-0.104.0.tar.gz", hash = "sha256:9c44de45693ae037b0c6914727a29c49a40668432b67c859a87851fc6a7b74c6"}, + {file = "fastapi-0.104.1-py3-none-any.whl", hash = "sha256:752dc31160cdbd0436bb93bad51560b57e525cbb1d4bbf6f4904ceee75548241"}, + {file = "fastapi-0.104.1.tar.gz", hash = "sha256:e5e4540a7c5e1dcfbbcf5b903c234feddcdcd881f191977a1c5dfd917487e7ae"}, ] [package.dependencies] @@ -524,23 +524,22 @@ files = [ [[package]] name = "matrix-client" -version = "0.4.0" +version = "0.3.2" description = "Client-Server SDK for Matrix" optional = false python-versions = "*" files = [ - {file = "matrix_client-0.4.0-py2.py3-none-any.whl", hash = "sha256:20cb42fb644879858c3fdd348d1c349c33676f11d1597f820abfd0fc0e009cb1"}, - {file = "matrix_client-0.4.0.tar.gz", hash = "sha256:0678af40f2cb2f0928a908a410c029747d40cb961ac5a3f1bd05aa35563c3156"}, + {file = "matrix_client-0.3.2-py2.py3-none-any.whl", hash = "sha256:2855a2614a177db66f9bc3ba38cbd2876041456f663c334f72a160ab6bb11c49"}, + {file = "matrix_client-0.3.2.tar.gz", hash = "sha256:dce3ccb8665df0d519f08e07a16e6d3f9fab3a947df4b7a7c4bb26573d68f2d5"}, ] [package.dependencies] -requests = ">=2.22,<3.0" -urllib3 = ">=1.21,<2.0" +requests = "*" [package.extras] -doc = ["Sphinx (>=1.7.6,<2.dev0)", "sphinx-rtd-theme (>=0.1.9,<0.2.dev0)", "sphinxcontrib-napoleon (>=0.5.3,<0.6.dev0)"] -e2e = ["canonicaljson (>=1.1,<2.0)", "python-olm (>=3.1,<4.0)"] -test = ["pytest (>=4.6,<6.0.0)", "responses (>=0.10.6,<0.11.dev0)"] +doc = ["Sphinx (==1.4.6)", "sphinx-rtd-theme (==0.1.9)", "sphinxcontrib-napoleon (==0.5.3)"] +format = ["flake8"] +test = ["pytest", "responses"] [[package]] name = "mypy" @@ -905,13 +904,13 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pyright" -version = "1.1.333" +version = "1.1.334" description = "Command line wrapper for pyright" optional = false python-versions = ">=3.7" files = [ - {file = "pyright-1.1.333-py3-none-any.whl", hash = "sha256:f0a7b7b0cac11c396b17ef3cf6c8527aca1269edaf5cf8203eed7d6dd1ef52aa"}, - {file = "pyright-1.1.333.tar.gz", hash = "sha256:1c49b0029048120c4378f3baf6c1dcbbfb221678bb69654fe773c514430ac53c"}, + {file = "pyright-1.1.334-py3-none-any.whl", hash = "sha256:dcb13e8358e021189672c4d6ebcad192ab061e4c7225036973ec493183c6da68"}, + {file = "pyright-1.1.334.tar.gz", hash = "sha256:3adaf10f1f4209575dc022f9c897f7ef024639b7ea5b3cbe49302147e6949cd4"}, ] [package.dependencies] @@ -1059,28 +1058,28 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "ruff" -version = "0.1.3" -description = "An extremely fast Python linter, written in Rust." +version = "0.1.4" +description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.1.3-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:b46d43d51f7061652eeadb426a9e3caa1e0002470229ab2fc19de8a7b0766901"}, - {file = "ruff-0.1.3-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:b8afeb9abd26b4029c72adc9921b8363374f4e7edb78385ffaa80278313a15f9"}, - {file = "ruff-0.1.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca3cf365bf32e9ba7e6db3f48a4d3e2c446cd19ebee04f05338bc3910114528b"}, - {file = "ruff-0.1.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4874c165f96c14a00590dcc727a04dca0cfd110334c24b039458c06cf78a672e"}, - {file = "ruff-0.1.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eec2dd31eed114e48ea42dbffc443e9b7221976554a504767ceaee3dd38edeb8"}, - {file = "ruff-0.1.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:dc3ec4edb3b73f21b4aa51337e16674c752f1d76a4a543af56d7d04e97769613"}, - {file = "ruff-0.1.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e3de9ed2e39160800281848ff4670e1698037ca039bda7b9274f849258d26ce"}, - {file = "ruff-0.1.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c595193881922cc0556a90f3af99b1c5681f0c552e7a2a189956141d8666fe8"}, - {file = "ruff-0.1.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f75e670d529aa2288cd00fc0e9b9287603d95e1536d7a7e0cafe00f75e0dd9d"}, - {file = "ruff-0.1.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:76dd49f6cd945d82d9d4a9a6622c54a994689d8d7b22fa1322983389b4892e20"}, - {file = "ruff-0.1.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:918b454bc4f8874a616f0d725590277c42949431ceb303950e87fef7a7d94cb3"}, - {file = "ruff-0.1.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d8859605e729cd5e53aa38275568dbbdb4fe882d2ea2714c5453b678dca83784"}, - {file = "ruff-0.1.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0b6c55f5ef8d9dd05b230bb6ab80bc4381ecb60ae56db0330f660ea240cb0d4a"}, - {file = "ruff-0.1.3-py3-none-win32.whl", hash = "sha256:3e7afcbdcfbe3399c34e0f6370c30f6e529193c731b885316c5a09c9e4317eef"}, - {file = "ruff-0.1.3-py3-none-win_amd64.whl", hash = "sha256:7a18df6638cec4a5bd75350639b2bb2a2366e01222825562c7346674bdceb7ea"}, - {file = "ruff-0.1.3-py3-none-win_arm64.whl", hash = "sha256:12fd53696c83a194a2db7f9a46337ce06445fb9aa7d25ea6f293cf75b21aca9f"}, - {file = "ruff-0.1.3.tar.gz", hash = "sha256:3ba6145369a151401d5db79f0a47d50e470384d0d89d0d6f7fab0b589ad07c34"}, + {file = "ruff-0.1.4-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:864958706b669cce31d629902175138ad8a069d99ca53514611521f532d91495"}, + {file = "ruff-0.1.4-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:9fdd61883bb34317c788af87f4cd75dfee3a73f5ded714b77ba928e418d6e39e"}, + {file = "ruff-0.1.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4eaca8c9cc39aa7f0f0d7b8fe24ecb51232d1bb620fc4441a61161be4a17539"}, + {file = "ruff-0.1.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a9a1301dc43cbf633fb603242bccd0aaa34834750a14a4c1817e2e5c8d60de17"}, + {file = "ruff-0.1.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78e8db8ab6f100f02e28b3d713270c857d370b8d61871d5c7d1702ae411df683"}, + {file = "ruff-0.1.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:80fea754eaae06335784b8ea053d6eb8e9aac75359ebddd6fee0858e87c8d510"}, + {file = "ruff-0.1.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6bc02a480d4bfffd163a723698da15d1a9aec2fced4c06f2a753f87f4ce6969c"}, + {file = "ruff-0.1.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9862811b403063765b03e716dac0fda8fdbe78b675cd947ed5873506448acea4"}, + {file = "ruff-0.1.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58826efb8b3efbb59bb306f4b19640b7e366967a31c049d49311d9eb3a4c60cb"}, + {file = "ruff-0.1.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:fdfd453fc91d9d86d6aaa33b1bafa69d114cf7421057868f0b79104079d3e66e"}, + {file = "ruff-0.1.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e8791482d508bd0b36c76481ad3117987301b86072158bdb69d796503e1c84a8"}, + {file = "ruff-0.1.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01206e361021426e3c1b7fba06ddcb20dbc5037d64f6841e5f2b21084dc51800"}, + {file = "ruff-0.1.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:645591a613a42cb7e5c2b667cbefd3877b21e0252b59272ba7212c3d35a5819f"}, + {file = "ruff-0.1.4-py3-none-win32.whl", hash = "sha256:99908ca2b3b85bffe7e1414275d004917d1e0dfc99d497ccd2ecd19ad115fd0d"}, + {file = "ruff-0.1.4-py3-none-win_amd64.whl", hash = "sha256:1dfd6bf8f6ad0a4ac99333f437e0ec168989adc5d837ecd38ddb2cc4a2e3db8a"}, + {file = "ruff-0.1.4-py3-none-win_arm64.whl", hash = "sha256:d98ae9ebf56444e18a3e3652b3383204748f73e247dea6caaf8b52d37e6b32da"}, + {file = "ruff-0.1.4.tar.gz", hash = "sha256:21520ecca4cc555162068d87c747b8f95e1e95f8ecfcbbe59e8dd00710586315"}, ] [[package]] @@ -1154,13 +1153,13 @@ files = [ [[package]] name = "tomlkit" -version = "0.12.1" +version = "0.12.2" description = "Style preserving TOML library" optional = false python-versions = ">=3.7" files = [ - {file = "tomlkit-0.12.1-py3-none-any.whl", hash = "sha256:712cbd236609acc6a3e2e97253dfc52d4c2082982a88f61b640ecf0817eab899"}, - {file = "tomlkit-0.12.1.tar.gz", hash = "sha256:38e1ff8edb991273ec9f6181244a6a391ac30e9f5098e7535640ea6be97a7c86"}, + {file = "tomlkit-0.12.2-py3-none-any.whl", hash = "sha256:eeea7ac7563faeab0a1ed8fe12c2e5a51c61f933f2502f7e9db0241a65163ad0"}, + {file = "tomlkit-0.12.2.tar.gz", hash = "sha256:df32fab589a81f0d7dc525a4267b6d7a64ee99619cbd1eeb0fae32c1dd426977"}, ] [[package]] @@ -1197,19 +1196,20 @@ files = [ [[package]] name = "urllib3" -version = "1.26.18" +version = "2.0.7" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = ">=3.7" files = [ - {file = "urllib3-1.26.18-py2.py3-none-any.whl", hash = "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07"}, - {file = "urllib3-1.26.18.tar.gz", hash = "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0"}, + {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, + {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, ] [package.extras] -brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] [[package]] name = "uvicorn" @@ -1231,13 +1231,13 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", [[package]] name = "wcwidth" -version = "0.2.8" +version = "0.2.9" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = "*" files = [ - {file = "wcwidth-0.2.8-py2.py3-none-any.whl", hash = "sha256:77f719e01648ed600dfa5402c347481c0992263b81a027344f3e1ba25493a704"}, - {file = "wcwidth-0.2.8.tar.gz", hash = "sha256:8705c569999ffbb4f6a87c6d1b80f324bd6db952f5eb0b95bc07517f4c1813d4"}, + {file = "wcwidth-0.2.9-py2.py3-none-any.whl", hash = "sha256:9a929bd8380f6cd9571a968a9c8f4353ca58d7cd812a4822bba831f8d685b223"}, + {file = "wcwidth-0.2.9.tar.gz", hash = "sha256:a675d1a4a2d24ef67096a04b85b02deeecd8e226f57b5e3a72dbb9ed99d27da8"}, ] [[package]] @@ -1275,5 +1275,5 @@ typing-extensions = ">=3.7" [metadata] lock-version = "2.0" -python-versions = ">=3.11,<3.12" -content-hash = "ed3cbc138222a794d331565d9115dbe63cc8a55b024f9404692813fae1679972" +python-versions = ">=3.11,<3.13" +content-hash = "22132c70cd732fe0b6fef7f5b311793647a8457bb0765505ef023f593141aac8" From 3eb9a135e54680d56b50ce32fde599f6f6bf0f82 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Tue, 7 Nov 2023 14:45:35 +0100 Subject: [PATCH 138/143] docs: add dependabot to todo list --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 57eb2069..8ddab021 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,7 @@ Tentative list of things to do in the future: - [ ] Query MyMdC for the stream name given a proposal number - [ ] Query MyMdC for the list of topics and pass that on instead of trying to get them with the Zulip API - [ ] Improve logging (structlog/loguru? sentry?) +- [ ] Set up dependabot ## Deployment From 535327460791a17ba5fc20188627cf06f45b9718 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Tue, 7 Nov 2023 15:01:31 +0100 Subject: [PATCH 139/143] docs: add debugging section --- README.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/README.md b/README.md index 8ddab021..fd0ed3c5 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,45 @@ poetry shell poe serve ``` +### Debugging + +When debugging it can be very useful, especially given the async nature of the API, to do so interactively. If using VSCode this can be done by creating a launch configuration for FastAPI. This will start the server and allow you to set breakpoints and step through the code. + +Here is an example configuration, placed in `.vscode/launch.json`: + +```json +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Python: FastAPI", + "type": "python", + "request": "launch", + "module": "uvicorn", + "args": [ + "zulip_write_only_proxy.main:app", + "--port", + "8080", + ], + "jinja": true, + "justMyCode": false + }, + { + "name": "Python: FastAPI - Test Debug", + "type": "python", + "request": "launch", + "module": "pytest", + "jinja": true, + "justMyCode": false + } + ] +} +``` + +This will add two configurations to the debug menu, one for running the server and one for running tests. The `justMyCode` option is set to `false` to allow for debugging/stepping into third party libraries. + +See the [VSCode Debugging documentation](https://code.visualstudio.com/docs/editor/debugging) for more information. + ### Client Configuration The configuration is very basic and is stored in a JSON file. The client token is used as a key and the value is a dictionary containing the proposal number and stream name. From 9cfd9a21b5e54b79dc9d379616b3dc5f046c3a55 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Mon, 13 Nov 2023 13:19:34 +0100 Subject: [PATCH 140/143] ci: add dynamic versioning plugin during setup --- .github/setup/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/setup/action.yml b/.github/setup/action.yml index 4eba538d..a7464159 100644 --- a/.github/setup/action.yml +++ b/.github/setup/action.yml @@ -21,6 +21,7 @@ runs: shell: bash run: | python -m pip install --upgrade pip poetry + poetry self add "poetry-dynamic-versioning[plugin]" poetry config virtualenvs.create true --local poetry config virtualenvs.in-project true --local From 42aed39fead23674744b3a09d0116d9a8ca85588 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Mon, 13 Nov 2023 13:37:27 +0100 Subject: [PATCH 141/143] build(deploy): expose port 8080 in dockerfile --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index 20d09501..4c9977ba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,4 +15,6 @@ RUN --mount=type=cache,target=/root/.cache\ CMD ["uvicorn", "zulip_write_only_proxy.main:app", "--host", "0.0.0.0", "--port", "8080"] +EXPOSE 8080 + HEALTHCHECK CMD curl --fail http://localhost:8080/health || exit 1 From bb837a0f4ff21d5332ccb5534041e31891f80c17 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Tue, 14 Nov 2023 15:34:34 +0100 Subject: [PATCH 142/143] fix(endpoint): import version info from module __init__ instead of from _version _version might not exist depending on install method/context, __init__ imports handle this --- src/zulip_write_only_proxy/main.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/zulip_write_only_proxy/main.py b/src/zulip_write_only_proxy/main.py index 9142933f..61ba92fe 100644 --- a/src/zulip_write_only_proxy/main.py +++ b/src/zulip_write_only_proxy/main.py @@ -8,7 +8,7 @@ import fastapi from fastapi.security import APIKeyHeader -from . import _logging, _version, models, services +from . import __version__, __version_tuple__, _logging, models, services @asynccontextmanager @@ -127,10 +127,10 @@ def get_me( def healthcheck(): return { "status": "OK", - "dirty": "dirty" in _version.__version__, - "dev": "+" in _version.__version__, - "version": _version.__version__, - "version_tuple": _version.__version_tuple__, + "dirty": "dirty" in __version__, + "dev": "+" in __version__, + "version": __version__, + "version_tuple": __version_tuple__, } From ec09a7c4f0de746b5aad87faf5a8ba71024edf82 Mon Sep 17 00:00:00 2001 From: Robert Rosca <32569096+RobertRosca@users.noreply.github.com> Date: Tue, 14 Nov 2023 16:32:56 +0100 Subject: [PATCH 143/143] docs: add build-image workflow test and client bot config to todo list --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index fd0ed3c5..1a2764c4 100644 --- a/README.md +++ b/README.md @@ -214,6 +214,8 @@ Tentative list of things to do in the future: - [ ] Query MyMdC for the list of topics and pass that on instead of trying to get them with the Zulip API - [ ] Improve logging (structlog/loguru? sentry?) - [ ] Set up dependabot +- [ ] Add test step to build-image workflow +- [ ] Allow for configuring zulip bots to use per client ## Deployment