diff --git a/auth_service/poetry.lock b/auth_service/poetry.lock index eb4adbb..005820e 100644 --- a/auth_service/poetry.lock +++ b/auth_service/poetry.lock @@ -1,4 +1,17 @@ -# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. + +[[package]] +name = "async-timeout" +version = "5.0.1" +description = "Timeout context manager for asyncio programs" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_full_version < \"3.11.3\"" +files = [ + {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, + {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, +] [[package]] name = "attrs" @@ -6,18 +19,19 @@ version = "25.3.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, ] [package.extras] -benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] -tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] +tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] [[package]] name = "blinker" @@ -25,6 +39,7 @@ version = "1.9.0" description = "Fast, simple object-to-object and broadcast signaling" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc"}, {file = "blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf"}, @@ -36,6 +51,7 @@ version = "0.13.0" description = "A collection of cache libraries in the same API interface." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "cachelib-0.13.0-py3-none-any.whl", hash = "sha256:8c8019e53b6302967d4e8329a504acf75e7bc46130291d30188a6e4e58162516"}, {file = "cachelib-0.13.0.tar.gz", hash = "sha256:209d8996e3c57595bee274ff97116d1d73c4980b2fd9a34c7846cd07fd2e1a48"}, @@ -47,6 +63,7 @@ version = "2025.1.31" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, @@ -58,6 +75,7 @@ version = "3.4.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, @@ -159,6 +177,7 @@ version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -173,6 +192,8 @@ 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" +groups = ["main"] +markers = "platform_system == \"Windows\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -184,6 +205,7 @@ version = "2.7.0" description = "DNS toolkit" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86"}, {file = "dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1"}, @@ -204,6 +226,7 @@ version = "0.9.9" description = "Deprecated package" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "dotenv-0.9.9-py2.py3-none-any.whl", hash = "sha256:29cf74a087b31dafdb5a446b6d7e11cbce8ed2741540e2339c69fbef92c94ce9"}, ] @@ -217,6 +240,7 @@ version = "0.9.7.1" description = "Extract swagger specs from your flask project" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "flasgger-0.9.7.1.tar.gz", hash = "sha256:ca098e10bfbb12f047acc6299cc70a33851943a746e550d86e65e60d4df245fb"}, ] @@ -235,6 +259,7 @@ version = "3.1.0" description = "A simple framework for building complex web applications." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "flask-3.1.0-py3-none-any.whl", hash = "sha256:d667207822eb83f1c4b50949b1623c8fc8d51f2341d65f72e1a1815397551136"}, {file = "flask-3.1.0.tar.gz", hash = "sha256:5f873c5184c897c8d9d1b05df1e3d01b14910ce69607a117bd3277098a5836ac"}, @@ -257,6 +282,7 @@ version = "2.3.1" description = "Adds caching support to Flask applications." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "Flask_Caching-2.3.1-py3-none-any.whl", hash = "sha256:d3efcf600e5925ea5a2fcb810f13b341ae984f5b52c00e9d9070392f3ca10761"}, {file = "flask_caching-2.3.1.tar.gz", hash = "sha256:65d7fd1b4eebf810f844de7de6258254b3248296ee429bdcb3f741bcbf7b98c9"}, @@ -272,6 +298,7 @@ version = "5.0.1" description = "A Flask extension simplifying CORS support" optional = false python-versions = "<4.0,>=3.9" +groups = ["main"] files = [ {file = "flask_cors-5.0.1-py3-none-any.whl", hash = "sha256:fa5cb364ead54bbf401a26dbf03030c6b18fb2fcaf70408096a572b409586b0c"}, {file = "flask_cors-5.0.1.tar.gz", hash = "sha256:6ccb38d16d6b72bbc156c1c3f192bc435bfcc3c2bc864b2df1eb9b2d97b2403c"}, @@ -287,6 +314,7 @@ version = "23.0.0" description = "WSGI HTTP Server for UNIX" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d"}, {file = "gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"}, @@ -308,6 +336,7 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -322,6 +351,7 @@ version = "2.2.0" description = "Safely pass data to untrusted environments and back." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, @@ -333,6 +363,7 @@ version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -350,6 +381,7 @@ version = "4.23.0" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566"}, {file = "jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4"}, @@ -371,6 +403,7 @@ version = "2025.4.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af"}, {file = "jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608"}, @@ -385,6 +418,7 @@ version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, @@ -455,6 +489,7 @@ version = "3.1.3" description = "A sane and fast Markdown parser with useful plugins and renderers" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "mistune-3.1.3-py3-none-any.whl", hash = "sha256:1a32314113cff28aa6432e99e522677c8587fd83e3d51c29b82a52409c842bd9"}, {file = "mistune-3.1.3.tar.gz", hash = "sha256:a7035c21782b2becb6be62f8f25d3df81ccb4d6fa477a6525b15af06539f02a0"}, @@ -466,6 +501,7 @@ version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, @@ -477,6 +513,7 @@ version = "4.12.0" description = "Python driver for MongoDB " optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "pymongo-4.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e23d9b5e8d2dfc3ac0540966e93008e471345ec9a2797b77be551e64b70fc8ee"}, {file = "pymongo-4.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ecf325f31bf8be70ec5cab50b45a8e09acf3952d693215acac965cecaeb6b58d"}, @@ -543,9 +580,9 @@ dnspython = ">=1.16.0,<3.0.0" [package.extras] aws = ["pymongo-auth-aws (>=1.1.0,<2.0.0)"] docs = ["furo (==2024.8.6)", "readthedocs-sphinx-search (>=0.3,<1.0)", "sphinx (>=5.3,<9)", "sphinx-autobuild (>=2020.9.1)", "sphinx-rtd-theme (>=2,<4)", "sphinxcontrib-shellcheck (>=1,<2)"] -encryption = ["certifi", "pymongo-auth-aws (>=1.1.0,<2.0.0)", "pymongocrypt (>=1.13.0,<2.0.0)"] -gssapi = ["pykerberos", "winkerberos (>=0.5.0)"] -ocsp = ["certifi", "cryptography (>=2.5)", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] +encryption = ["certifi ; os_name == \"nt\" or sys_platform == \"darwin\"", "pymongo-auth-aws (>=1.1.0,<2.0.0)", "pymongocrypt (>=1.13.0,<2.0.0)"] +gssapi = ["pykerberos ; os_name != \"nt\"", "winkerberos (>=0.5.0) ; os_name == \"nt\""] +ocsp = ["certifi ; os_name == \"nt\" or sys_platform == \"darwin\"", "cryptography (>=2.5)", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] snappy = ["python-snappy"] test = ["pytest (>=8.2)", "pytest-asyncio (>=0.24.0)"] zstd = ["zstandard"] @@ -556,6 +593,7 @@ version = "1.1.0" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d"}, {file = "python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5"}, @@ -570,6 +608,7 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -632,11 +671,15 @@ version = "5.2.1" description = "Python client for Redis database and key-value store" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "redis-5.2.1-py3-none-any.whl", hash = "sha256:ee7e1056b9aea0f04c6c2ed59452947f34c4940ee025f5dd83e6a6418b6989e4"}, {file = "redis-5.2.1.tar.gz", hash = "sha256:16f2e22dff21d5125e8481515e386711a34cbec50f0e44413dd7d9c060a54e0f"}, ] +[package.dependencies] +async-timeout = {version = ">=4.0.3", markers = "python_full_version < \"3.11.3\""} + [package.extras] hiredis = ["hiredis (>=3.0.0)"] ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==23.2.1)", "requests (>=2.31.0)"] @@ -647,6 +690,7 @@ version = "0.36.2" description = "JSON Referencing + Python" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0"}, {file = "referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa"}, @@ -663,6 +707,7 @@ version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, @@ -684,6 +729,7 @@ version = "0.24.0" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "rpds_py-0.24.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:006f4342fe729a368c6df36578d7a348c7c716be1da0a1a0f86e3021f8e98724"}, {file = "rpds_py-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2d53747da70a4e4b17f559569d5f9506420966083a31c5fbd84e764461c4444b"}, @@ -807,6 +853,7 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -818,6 +865,8 @@ version = "4.13.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version < \"3.13\"" files = [ {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"}, {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"}, @@ -829,13 +878,14 @@ version = "2.4.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813"}, {file = "urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -846,6 +896,7 @@ version = "3.1.3" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"}, {file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"}, @@ -858,6 +909,6 @@ MarkupSafe = ">=2.1.1" watchdog = ["watchdog (>=2.3)"] [metadata] -lock-version = "2.0" -python-versions = "^3.12" -content-hash = "e99131afad59a785d9b583441644319fb0fc82f89a610f4a853247430f1b22d3" +lock-version = "2.1" +python-versions = "^3.11" +content-hash = "b1a25b743caed55f57e7d1b45be39bbf906b93ec9177b63c9a363cd14bc08f67" diff --git a/auth_service/pyproject.toml b/auth_service/pyproject.toml index 0c06ba6..640c82c 100644 --- a/auth_service/pyproject.toml +++ b/auth_service/pyproject.toml @@ -3,10 +3,10 @@ name = "auth-service" version = "0.1.0" description = "serviço de autenticação para o cluster do LabTech" authors = ["Danrley Pereira "] -readme = "README.md" +package-mode = false [tool.poetry.dependencies] -python = "^3.12" +python = "^3.11" flask = "^3.1.0" flasgger = "^0.9.7.1" pymongo = "^4.11.3" diff --git a/docker-compose.yml b/docker-compose.yml index d80fb8a..b256e79 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,6 +23,27 @@ services: options: max-size: "10m" max-file: "3" + + rabbitmq: + image: rabbitmq:3-management + container_name: rabbitmq-local + restart: unless-stopped + ports: + - "5672:5672" # Porta para o protocolo AMQP + - "15672:15672" # Porta para a interface de gerenciamento + environment: + - RABBITMQ_DEFAULT_USER=user + - RABBITMQ_DEFAULT_PASS=password + volumes: + - rabbitmq-data:/var/lib/rabbitmq + healthcheck: + test: ["CMD", "rabbitmq-diagnostics", "ping"] + interval: 30s + timeout: 10s + retries: 5 + volumes: redis-data: driver: local + rabbitmq-data: + driver: local diff --git a/internal_apis/.env.example b/internal_apis/.env.example index 290c03a..deb19d4 100644 --- a/internal_apis/.env.example +++ b/internal_apis/.env.example @@ -1,4 +1,7 @@ MONGO_URI= MONGO_DATABASE= API_KEY_LIST= -SERVER_NAME= \ No newline at end of file +SERVER_NAME= +GLOBAL_BUCKET_SIZE= +GLOBAL_QUEUE_NAME= +GLOBAL_LEAK_RATE= \ No newline at end of file diff --git a/internal_apis/Dockerfile.tests b/internal_apis/Dockerfile.tests new file mode 100644 index 0000000..cda9866 --- /dev/null +++ b/internal_apis/Dockerfile.tests @@ -0,0 +1,10 @@ +FROM python:3.13 + +WORKDIR /app/internal_apis + +ENV PYTHONPATH=/app/internal_apis + +COPY requirements.txt ./ +RUN pip install -r requirements.txt + +COPY . . diff --git a/internal_apis/backpressure/README.md b/internal_apis/backpressure/README.md new file mode 100644 index 0000000..f496604 --- /dev/null +++ b/internal_apis/backpressure/README.md @@ -0,0 +1,137 @@ +# Backpressure API (Leaky Bucket) + +## O que é o sistema de Leaky Bucket? + +O algoritmo de Leaky Bucket é uma estratégia de controle de fluxo e limitação de requisições (rate limiting). Ele funciona como um balde com um furo: as requisições entram no balde (bucket) e "vazam" em uma taxa constante. Se o balde enche (ultrapassa a capacidade), as requisições excedentes são descartadas ou bloqueadas. Isso garante que o sistema não seja sobrecarregado por picos de tráfego. + +Neste projeto, existem dois tipos de buckets: + +## 1. Leaky Bucket Global (RabbitMQ) +- **Finalidade:** Limita o número total de requisições aceitas por todos os usuários em um determinado endpoint ou aplicação. +- **Implementação:** Arquivo `leaky_bucket_rabbitmq.py`. +- **Backend:** RabbitMQ (fila com tamanho máximo). +- **Como usar:** + - O uso automático via `@request.before_request` foi removido por questões de segurança. Agora, o controle global deve ser adicionado **manualmente** em cada endpoint desejado, usando o decorator: + ```python + from backpressure.leaky_bucket_rabbitmq import LeakyBucketRabbitMQ + @LeakyBucketRabbitMQ.add_to_global_leaky_bucket() + def meu_endpoint(): + ... + ``` + - Os parâmetros do bucket global (capacidade, nome da fila, taxa de vazamento) **são definidos via variáveis de ambiente** no arquivo `.env`: + - `GLOBAL_BUCKET_SIZE`: capacidade máxima do bucket global (ex: 125) + - `GLOBAL_QUEUE_NAME`: nome da fila no RabbitMQ (ex: global) + - `GLOBAL_LEAK_RATE`: taxa de vazamento do bucket global (ex: 0.3) + - **Não é necessário passar parâmetros no decorator**. O sistema buscará as configurações automaticamente do `.env`. + +--- + +## 2. Leaky Bucket Individual (Redis) +- **Finalidade:** Limita o número de requisições por usuário (identificado por IP ou outro identificador). +- **Implementação:** Arquivo `individual_leaky_bucket.py`. +- **Backend:** Redis (armazenamento de tokens por IP). +- **Como usar:** + ```python + from backpressure.individual_leaky_bucket import LeakyBucket + @LeakyBucket.individual_leaky_bucket(bucketcapacity=5, leakrate=1, keytimeout=60) + def meu_endpoint(): + ... + ``` + - `bucketcapacity`: número máximo de requisições permitidas por usuário. + - `leakrate`: intervalo (em segundos) para "vazamento" de cada token. + - `keytimeout`: tempo de expiração do registro no Redis (em segundos). + +--- + +## Como adicionar os buckets aos endpoints +- Para controle individual, adicione o decorator `@LeakyBucket.individual_leaky_bucket(...)` ao endpoint desejado. +- Para controle global, adicione o decorator `@LeakyBucketRabbitMQ.add_to_global_leaky_bucket()` ao endpoint. +- **Ordem recomendada:** + ```python + @LeakyBucketRabbitMQ.add_to_global_leaky_bucket() + @LeakyBucket.individual_leaky_bucket(...) + def meu_endpoint(): + ... + ``` + +## ATENÇÃO CRÍTICA SOBRE ORDEM DOS DECORATORS +> **IMPORTANTE:** Caso você utilize **os dois sistemas de leaky bucket juntos** (global e individual) em um mesmo endpoint, **O DECORATOR DO INDIVIDUAL LEAKY BUCKET DEVE SER SEMPRE O PRIMEIRO** (ou seja, deve estar mais "próximo" da função do endpoint) e o decorator do global leaky bucket deve vir depois. +> +> **Exemplo correto:** +> ```python +> @LeakyBucketRabbitMQ.add_to_global_leaky_bucket() +> @LeakyBucket.individual_leaky_bucket(...) +> def meu_endpoint(): +> ... +> ``` +> +> **Se a ordem for invertida, podem ocorrer graves vulnerabilidades de rate limiting, permitindo que usuários burlem o controle global!** +> +> **NUNCA inverta essa ordem!** + +--- + +## Configuração do sistema global via .env +- As informações da fila global do leaky bucket são definidas no arquivo `.env` na raiz do projeto: + ```env + GLOBAL_BUCKET_SIZE=125 + GLOBAL_QUEUE_NAME=global + GLOBAL_LEAK_RATE=0.3 + ``` +- Para alterar a capacidade, nome da fila ou taxa de vazamento, basta editar o `.env` e reiniciar o serviço. +- **Atenção:** O RabbitMQ não permite alterar argumentos de uma fila já existente. Se mudar o `GLOBAL_BUCKET_SIZE` ou outros argumentos, altere também o `GLOBAL_QUEUE_NAME` para evitar conflitos, ou exclua a fila antiga manualmente. + +--- + +# Testes automatizados + +## Estrutura dos testes +Os testes automatizados do sistema de backpressure estão localizados em: +- `backpressure/test_global_leaky_bucket.py` (testes do bucket global) +- `backpressure/test_individual_leaky_bucket.py` (testes do bucket individual) + +Os testes cobrem cenários de limite, vazamento, bloqueio e funcionamento dos buckets. + +## Como rodar os testes + +### Pré-requisitos +- Python 3.11+ instalado localmente. +- Instale as dependências do projeto com Poetry: + ```sh + poetry install + ``` +- O serviço do RabbitMQ deve estar rodando em um container Docker (ou localmente) e acessível conforme as configurações do `.env`. +- O Redis **não é necessário**: os testes usam `fakeredis` (mock em memória). + +### Rodando testes pelo PyCharm (recomendado) +- Você pode rodar qualquer teste individualmente pelo próprio PyCharm, clicando no ícone de execução (▶️) que aparece ao lado da função de teste ou do nome do arquivo de teste. +- Certifique-se de que o interpretador Python do PyCharm está configurado para usar o ambiente virtual criado pelo Poetry (ou o Python correto). +- O RabbitMQ deve estar rodando normalmente em Docker. + +### Rodando os testes com o poetry via terminal +1. Certifique-se de que o RabbitMQ está rodando (exemplo usando Docker Compose): + ```sh + docker compose up -d rabbitmq + ``` +2. Execute os testes (com Poetry): + ```sh + poetry run pytest backpressure/ + ``` +3. Para rodar um teste específico: + ```sh + poetry run pytest backpressure/test_global_leaky_bucket.py::test_nome_do_teste + ``` + Substitua pelo nome do arquivo e da função de teste desejada. + + +### Dicas +- Não é necessário rodar nenhum container de testes, apenas o RabbitMQ. +- Se mudar as configurações do `.env`, reinicie o RabbitMQ e os testes. +- Consulte os logs do sistema para mensagens de erro detalhadas. + + +## Observações finais +- O sistema de backpressure é fundamental para garantir a resiliência da API. +- Sempre respeite a ordem dos decorators para evitar vulnerabilidades. +- Mantenha o `.env` atualizado conforme a configuração desejada do bucket global. +- Para dúvidas ou problemas, consulte este README ou peça suporte ao time. diff --git a/internal_apis/backpressure/individual_leaky_bucket.py b/internal_apis/backpressure/individual_leaky_bucket.py new file mode 100644 index 0000000..cc838d5 --- /dev/null +++ b/internal_apis/backpressure/individual_leaky_bucket.py @@ -0,0 +1,73 @@ +from datetime import datetime +from auth_service.controller import AuthenticationController +import redis +import time +from functools import wraps +from flask import Flask, request, jsonify +from auth_service.auth_routes import token_required +import traceback + +class LeakyBucket: + + @staticmethod + def get_redis(): + return redis.Redis.from_url("redis://localhost:6379/0") + + @staticmethod + + def individual_leaky_bucket(bucketcapacity, leakrate, keytimeout): + def decorator(f): + @wraps(f) + def wrapped(*args, **kwargs): + try: + r = LeakyBucket.get_redis() + r.ping() + # print("[DEBUG] Conectado ao Redis com sucesso.") + except Exception as e: + # print("[ERRO] Falha ao conectar ao Redis:", e) + traceback.print_exc() + return jsonify({"status": "erro", "mensagem": "Erro ao conectar ao Redis"}), 500 + + client_ip = request.headers.get('X-Forwarded-For', request.remote_addr) + if not client_ip: + return jsonify({"message": "Não foi possível identificar o endereço de IP do cliente."}), 400 + key = f"leaky_bucket:{client_ip}" + # print(f"[DEBUG] IP da requisicao: {client_ip}") + now = time.time() + + + try: + data = r.hmget(name=key, keys=["last_access", "tokens"]) + # print(f"[DEBUG] Dados brutos do Redis: {data}") + last_access = float(data[0]) if data[0] else now + tokens = int(data[1]) if data[1] else 0 + readable_last_access = datetime.fromtimestamp(last_access).strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] + # print(f"[INFO] Último acesso: {readable_last_access}, Tokens agora: {tokens}") + except Exception as e: + # print("[ERRO] Falha ao obter ou interpretar dados do Redis:", e) + traceback.print_exc() + return jsonify({"status": "erro", "mensagem": "Erro ao acessar dados do Redis"}), 500 + + time_elapsed = now - last_access + leaked_tokens = int(time_elapsed / leakrate) + tokens = max(0, tokens - leaked_tokens) + if leaked_tokens < 0: + last_access = now + + if tokens < bucketcapacity: + tokens += 1 + try: + r.hset(name=key, mapping={"tokens": tokens, "last_access": now}) + r.expire(key, keytimeout) + return f(*args, **kwargs) + except Exception as e: + traceback.print_exc() + return jsonify({"status": "erro", "mensagem": "Erro ao atualizar Redis"}), 500 + else: + # Sinaliza para o before_request do global não processar + request._leaky_bucket_individual_blocked = True + return jsonify({"status": "error", "message": "Request limit exceeded!"}), 429 + + wrapped._has_individual_leaky_bucket = True + return wrapped + return decorator diff --git a/internal_apis/backpressure/leaky_bucket_rabbitmq.py b/internal_apis/backpressure/leaky_bucket_rabbitmq.py new file mode 100644 index 0000000..ae835f1 --- /dev/null +++ b/internal_apis/backpressure/leaky_bucket_rabbitmq.py @@ -0,0 +1,81 @@ +import os +import time +import traceback +from functools import wraps +from flask import request, jsonify +from datetime import datetime + +import time +from functools import wraps +from flask import request, jsonify +from backpressure import rabbitmq_utils + +class LeakyBucketRabbitMQ: + # Variáveis estáticas para conexão e canal persistentes + _connection = None + _channel = None + + @staticmethod + def _get_persistent_connection(): + # Cria ou retorna conexão/canal persistente + if LeakyBucketRabbitMQ._connection is None or LeakyBucketRabbitMQ._channel is None or LeakyBucketRabbitMQ._connection.is_closed or LeakyBucketRabbitMQ._channel.is_closed: + LeakyBucketRabbitMQ._connection, LeakyBucketRabbitMQ._channel = rabbitmq_utils.create_rabbitmq_connection() + return LeakyBucketRabbitMQ._connection, LeakyBucketRabbitMQ._channel + + @staticmethod + def _declare_queue_safe(channel, queue_name, bucketcapacity): + try: + queue = channel.queue_declare(queue=queue_name, durable=True, arguments={'x-max-length': bucketcapacity}) + return queue + except Exception as e: + # Trata erro de precondição (fila já existe com argumentos diferentes) + if 'PRECONDITION_FAILED' in str(e): + # Fecha canal/conexão e reabre para garantir canal válido + try: + if hasattr(channel, 'connection') and channel.connection and not channel.connection.is_closed: + channel.close() + channel.connection.close() + except Exception: + pass + # Reabre conexão/canal + from backpressure import rabbitmq_utils + connection, channel = rabbitmq_utils.create_rabbitmq_connection() + queue = channel.queue_declare(queue=queue_name, durable=True, passive=True) + # Atualiza canal persistente + LeakyBucketRabbitMQ._connection = connection + LeakyBucketRabbitMQ._channel = channel + return queue + else: + raise + + @staticmethod + def add_to_global_leaky_bucket(bucketcapacity=int(os.getenv('GLOBAL_BUCKET_SIZE')), queue_name=os.getenv('GLOBAL_QUEUE_NAME')): + def decorator(f): + @wraps(f) + def wrapped(*args, **kwargs): + + try: + current_queue = queue_name or rabbitmq_utils.get_leakybucket_queue_name() + connection, channel = LeakyBucketRabbitMQ._get_persistent_connection() + queue = LeakyBucketRabbitMQ._declare_queue_safe(channel, current_queue, bucketcapacity) + current_tokens = queue.method.message_count + + if current_tokens < bucketcapacity: + req_id = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3] + message = f'{current_queue}: {req_id}' + channel.basic_publish(exchange='', routing_key=current_queue, body=message.encode()) + return f(*args, **kwargs) + else: + return jsonify({"status": "error", "message": "Request limit exceeded!"}), 429 + except Exception as e: + LeakyBucketRabbitMQ._connection = None + LeakyBucketRabbitMQ._channel = None + return jsonify({ + "status": "error", + "message": f"Erro interno: {str(e)}", + "trace": traceback.format_exc() + }), 500 + + return wrapped + return decorator + diff --git a/internal_apis/backpressure/leaky_bucket_worker.py b/internal_apis/backpressure/leaky_bucket_worker.py new file mode 100644 index 0000000..5aae746 --- /dev/null +++ b/internal_apis/backpressure/leaky_bucket_worker.py @@ -0,0 +1,33 @@ +# internal_apis/backpressure/leaky_bucket_worker.py +import time +from backpressure.rabbitmq_utils import create_rabbitmq_connection +from backpressure.rabbitmq_utils import get_leakybucket_queue_name +from flask import request +import os + +def start_leaky_bucket_worker(leakrate, bucketcapacity, queue_name=None): + if queue_name is None: + queue_name = get_leakybucket_queue_name() + + print(f"[*] Worker iniciado. Ouvindo a fila: {queue_name}") + + connection, channel = create_rabbitmq_connection() + channel.queue_delete(queue=queue_name) + channel.queue_declare(queue=queue_name, durable=True, arguments={'x-max-length': bucketcapacity}) + + log_file_path = os.path.join(os.path.dirname(__file__), 'test_logs') + + while True: + method_frame, header_frame, body = channel.basic_get(queue=queue_name) + if method_frame: + print(f"Mensagem consumida: {body.decode()}") # Log para depuração + with open(log_file_path, "a") as f: + f.write(body.decode() + "\n") + channel.basic_ack(method_frame.delivery_tag) + else: + + time.sleep(leakrate) + +if __name__ == "__main__": + start_leaky_bucket_worker(0.3, 125, queue_name='global') + diff --git a/internal_apis/backpressure/rabbitmq_utils.py b/internal_apis/backpressure/rabbitmq_utils.py new file mode 100644 index 0000000..5c9d13a --- /dev/null +++ b/internal_apis/backpressure/rabbitmq_utils.py @@ -0,0 +1,26 @@ +import os +import pika +from flask import g +from flask import request + + +def create_rabbitmq_connection(): + host = os.environ.get("RABBITMQ_HOST", "localhost") + user = os.environ.get("RABBITMQ_USER", "user") + password = os.environ.get("RABBITMQ_PASS", "password") + credentials = pika.PlainCredentials(user, password) + connection = pika.BlockingConnection( + pika.ConnectionParameters(host=host, port=5672, virtual_host='/', credentials=credentials) + ) + channel = connection.channel() + return connection, channel + +def get_general_queue_name(queue_type, token): + + return f"Queue Service:{queue_type} --- User: {token}" + +def get_leakybucket_queue_name(): + """Retorna um nome de fila padrão para o rate limit global.""" + # Esta função agora retorna um nome de fila fixo. + # A lógica de IP foi removida para focar em um limite global. + return get_general_queue_name("leaky_bucket", "global") diff --git a/internal_apis/backpressure/test_global_leaky_bucket.py b/internal_apis/backpressure/test_global_leaky_bucket.py new file mode 100644 index 0000000..c64abca --- /dev/null +++ b/internal_apis/backpressure/test_global_leaky_bucket.py @@ -0,0 +1,79 @@ +import time +import pytest +import pika +import threading +from flask import Flask, jsonify +from backpressure.leaky_bucket_rabbitmq import LeakyBucketRabbitMQ +from leaky_bucket_worker import start_leaky_bucket_worker + +@pytest.fixture() +def app(): + app = Flask(__name__) + + apply_leakybucket = LeakyBucketRabbitMQ.add_to_global_leaky_bucket( + bucketcapacity=5, queue_name="test_queue") + + @app.route("/api/protected") + @apply_leakybucket + def protected(): + return jsonify({"status": "ok", "message": "Acesso Permitido"}) + + return app + +@pytest.fixture(autouse=True) +def start_queue(monkeypatch): + #def mock_get_queue_name(name): + # return 'test_queue' + + #monkeypatch.setattr('backpressure.rabbitmq_utils.get_leakybucket_queue_name', mock_get_queue_name) + + worker_thread = threading.Thread( + target=start_leaky_bucket_worker, args=(1, 5, "test_queue"), daemon=True + ) + worker_thread.start() + +@pytest.fixture(autouse=True) +def clean_queue(): + credentials = pika.PlainCredentials('user', 'password') + connection = pika.BlockingConnection( + pika.ConnectionParameters('localhost', 5672, '/', credentials) + ) + channel = connection.channel() + channel.queue_delete(queue='test_queue') + channel.queue_declare(queue='test_queue', durable=True, arguments={'x-max-length': 5}) + connection.close() + + +@pytest.fixture() +def client(app): + return app.test_client() + + + +def test_requests_above_limit(client): + for i in range(5): + response = client.get("/api/protected") + assert response.status_code == 200 + assert response.get_json()["status"] == "ok" + print(f"Requisição {i + 1}: status {response.status_code}, resposta: {response.get_data(as_text=True)}") + + # Sexta requisição deve ser bloqueada + response = client.get("/api/protected") + print("Requisição 6: status", response.status_code, "resposta:", response.get_data(as_text=True)) + assert response.status_code == 429 + assert "limit" in response.get_json()["message"].lower() + + + +def test_requests_leaks(client): + for i in range(5): + response = client.get("/api/protected") + assert response.status_code == 200 + print(f"Requisição {i + 1}: status {response.status_code}, resposta: {response.get_data(as_text=True)}") + # Aguarde o vazamento de tokens + time.sleep(1.1) + + # Requisição deve ser aceita novamente + response = client.get("/api/protected") + print(f"Requisição acima do limite do bucket porém apos o vazamento: status {response.status_code}, resposta: {response.get_data(as_text=True)}") + assert response.status_code == 200 diff --git a/internal_apis/backpressure/test_individual_leaky_bucket.py b/internal_apis/backpressure/test_individual_leaky_bucket.py new file mode 100644 index 0000000..1afccdf --- /dev/null +++ b/internal_apis/backpressure/test_individual_leaky_bucket.py @@ -0,0 +1,130 @@ +import time +from functools import wraps +import pika +import pytest +import fakeredis +from flask import Flask, jsonify +from backpressure.individual_leaky_bucket import LeakyBucket + + +@pytest.fixture +def fake_redis(monkeypatch): + r = fakeredis.FakeStrictRedis(decode_responses=True) + r.flushdb() # Limpa o redis falso antes de cada teste + + monkeypatch.setattr(LeakyBucket, "get_redis", staticmethod(lambda: r)) + return r + +#substituindo o get_redis original dentro do individual_leaky_bucket.py (que pega o redis original) pela nossa funcao que pega o redis falso + +@pytest.fixture() +def app(fake_redis): + app = Flask(__name__) + + apply_leakybucket = LeakyBucket.individual_leaky_bucket( + bucketcapacity=5, + leakrate=0.1, + keytimeout=10 + ) + + #Configurando o leaky bucket com capacidade de 5 requisições, vazamento de 0.1 requisições por segundo e timeout de 60 segundos + + @app.route("/api/protected") + @apply_leakybucket + def protected(): + return jsonify({"status": "ok", "message": "Acesso Permitido"}) + + return app + + # Configurando o Flask para rodar com o leaky bucket aplicado na rota /api/protected + +@pytest.fixture() +def client(app): + return app.test_client() + +# utiliza o test_client() do flask para simular as requisições sem ter que envolver o servidor + +def test_requests_above_limit(client): + + for i in range(5): + request = client.get("/api/protected") + print(f"Requisição {i + 1}: status {request.status_code}, resposta: {request.get_data(as_text=True)}") + assert request.status_code == 200 + assert request.get_json()["status"] == "ok" + + #realiza as 5 requisições maximas e verifica se cada uma foi aceita e se em cada uma o json foi retornado corretamente + + test_request = client.get("/api/protected") + assert test_request.status_code == 429 + assert "limit" in test_request.get_json()["message"].lower() + print(f"Requisição 6: status {test_request.status_code}, resposta: {test_request.get_data(as_text=True)}") + + #realiza a sexta requisição e verifica se ela foi recusada, verificando também se o json foi retornado corretamente + + +def test_requests_below_limit(client): + + for _ in range(5): + request = client.get("/api/protected") + print(request.data) + print(request.status_code) + print(request.get_data(as_text=True)) + assert request.status_code == 200 + assert request.get_json()["status"] == "ok" + + #realiza as 5 requisições maximas e verifica se cada uma foi aceita e se em cada uma o json foi retornado corretamente + +def test_n_requests(client): + + for _ in range(99): + request = client.get("/api/protected") + print(request.status_code) + print(request.get_data(as_text=True)) + + #Testa n requisições (teste livre sem assert) + +def test_requests_leaks(client): + + for _ in range(5): + request = client.get("/api/protected") + print(request.status_code) + print(request.get_data(as_text=True)) + assert request.status_code == 200 + assert request.get_json()["status"] == "ok" + + test_request = client.get("/api/protected") + assert test_request.status_code == 429 + assert "limit" in test_request.get_json()["message"].lower() + print(test_request.status_code) + print(test_request.get_data(as_text=True)) + + time.sleep(1.1) + + test_request2 = client.get("/api/protected") + assert test_request2.status_code == 200 + assert test_request2.get_json()["status"] == "ok" + print(test_request2.status_code) + print(test_request2.get_data(as_text=True)) + + #Este teste verifica se após um vazamento de 0.1 requisições por segundo, a requisição é aceita novamente após o tempo de vazamento + + +def test_requests_timeout(client): + + for _ in range(5): + request = client.get("/api/protected") + print(request.status_code) + print(request.get_data(as_text=True)) + assert request.status_code == 200 + assert request.get_json()["status"] == "ok" + + time.sleep(11) # Espera o timeout de 10 segundos + + for _ in range(5): + request = client.get("/api/protected") + print(request.status_code) + print(request.get_data(as_text=True)) + assert request.status_code == 200 + assert request.get_json()["status"] == "ok" + + #testa o timeout de 10 segundos, realizando 5 requisições antes e 5 depois do timeout, verificando se todas foram aceitas \ No newline at end of file diff --git a/internal_apis/backpressure/test_logs b/internal_apis/backpressure/test_logs new file mode 100644 index 0000000..a095b61 --- /dev/null +++ b/internal_apis/backpressure/test_logs @@ -0,0 +1,397 @@ +test_queue: 2025-06-23 23:46:33.119 +test_queue: 2025-06-23 23:46:33.126 +leaky_bucket_queue: 2025-06-23 21:55:31.582 +leaky_bucket_queue: 2025-06-23 21:55:34.054 +leaky_bucket_queue: 2025-06-23 21:55:34.921 +leaky_bucket_queue: 2025-06-23 21:55:35.302 +leaky_bucket_queue: 2025-06-23 21:55:35.725 +leaky_bucket_queue: 2025-06-23 21:55:41.092 +leaky_bucket_queue: 2025-06-23 21:55:41.962 +leaky_bucket_queue: 2025-06-23 21:55:42.285 +leaky_bucket_queue: 2025-06-23 21:55:42.625 +leaky_bucket_queue: 2025-06-23 21:55:42.944 +leaky_bucket_queue: 2025-06-23 21:55:43.256 +leaky_bucket_queue: 2025-06-23 21:55:43.601 +leaky_bucket_queue: 2025-06-23 21:55:43.921 +leaky_bucket_queue: 2025-06-23 21:55:44.569 +leaky_bucket_queue: 2025-06-23 21:57:00.241 +leaky_bucket_queue: 2025-06-23 21:57:00.727 +leaky_bucket_queue: 2025-06-23 21:57:01.221 +leaky_bucket_queue: 2025-06-23 21:57:07.585 +leaky_bucket_queue: 2025-06-23 21:57:07.913 +leaky_bucket_queue: 2025-06-23 21:57:08.266 +leaky_bucket_queue: 2025-06-23 21:57:08.577 +leaky_bucket_queue: 2025-06-23 21:57:08.890 +leaky_bucket_queue: 2025-06-23 21:57:09.193 +leaky_bucket_queue: 2025-06-23 21:57:20.500 +leaky_bucket_queue: 2025-06-23 21:57:21.372 +leaky_bucket_queue: 2025-06-23 21:59:16.708 +leaky_bucket_queue: 2025-06-23 21:59:16.979 +leaky_bucket_queue: 2025-06-23 21:59:21.916 +leaky_bucket_queue: 2025-06-23 21:59:22.238 +leaky_bucket_queue: 2025-06-23 21:59:23.143 +global: 2025-06-24 00:47:10.530 +global: 2025-06-24 00:47:11.610 +global: 2025-06-24 00:47:16.229 +global: 2025-06-24 00:47:16.530 +global: 2025-06-24 00:47:16.853 +global: 2025-06-24 00:47:17.206 +global: 2025-06-24 00:47:17.950 +global: 2025-06-24 00:47:18.290 +global: 2025-06-24 00:47:18.630 +global: 2025-06-24 00:47:19.265 +global: 2025-06-24 00:47:19.981 +global: 2025-06-24 00:47:21.266 +global: 2025-06-24 00:47:21.926 +global: 2025-06-24 00:47:22.954 +global: 2025-06-24 00:47:24.838 +global: 2025-06-24 00:47:25.554 +global: 2025-06-24 00:47:26.298 +global: 2025-06-24 00:47:28.222 +global: 2025-06-24 01:06:44.827 +global: 2025-06-24 01:07:45.475 +global: 2025-06-24 01:10:52.827 +global: 2025-06-24 01:11:15.920 +global: 2025-06-24 01:12:05.824 +global: 2025-06-24 01:12:08.883 +global: 2025-06-24 01:12:09.520 +global: 2025-06-24 01:12:09.959 +global: 2025-06-24 01:12:10.219 +global: 2025-06-24 01:12:10.543 +global: 2025-06-24 01:12:10.903 +global: 2025-06-24 01:12:11.323 +global: 2025-06-24 01:12:13.463 +global: 2025-06-24 01:13:43.795 +global: 2025-06-24 01:13:48.284 +global: 2025-06-24 01:13:50.270 +global: 2025-06-24 01:13:51.110 +global: 2025-06-24 01:13:51.754 +global: 2025-06-24 01:13:52.374 +global: 2025-06-24 01:13:58.982 +global: 2025-06-24 01:13:59.420 +global: 2025-06-24 01:13:59.682 +global: 2025-06-24 01:14:00.234 +global: 2025-06-24 01:58:36.722 +global: 2025-06-24 01:58:37.562 +global: 2025-06-24 01:58:41.502 +global: 2025-06-24 01:58:43.429 +global: 2025-06-24 01:58:44.205 +global: 2025-06-24 01:58:45.297 +global: 2025-06-24 01:58:46.217 +global: 2025-06-24 01:58:54.633 +global: 2025-06-24 01:58:55.669 +global: 2025-06-24 01:58:56.408 +global: 2025-06-24 01:58:57.668 +global: 2025-06-24 01:58:58.409 +global: 2025-06-24 14:53:39.768 +global: 2025-06-24 14:54:02.008 +global: 2025-06-24 15:06:48.020 +global: 2025-06-24 15:06:49.210 +global: 2025-06-24 15:41:07.416 +global: 2025-06-24 15:50:42.560 +global: 2025-06-24 15:51:10.122 +global: 2025-06-24 15:56:20.191 +global: 2025-06-24 15:58:30.769 +global: 2025-06-24 15:59:00.018 +global: 2025-06-24 17:17:11.367 +global: 2025-06-24 17:18:32.569 +global: 2025-06-24 17:21:29.273 +global: 2025-06-24 17:24:08.664 +global: 2025-06-24 17:26:51.940 +global: 2025-06-24 17:27:22.173 +global: 2025-06-24 17:28:54.699 +global: 2025-06-24 17:28:57.463 +global: 2025-06-24 17:28:58.209 +global: 2025-06-24 17:28:59.020 +global: 2025-06-24 17:29:07.848 +global: 2025-06-24 17:30:56.526 +global: 2025-06-24 17:30:57.896 +global: 2025-06-24 17:42:14.411 +global: 2025-06-24 17:42:15.480 +global: 2025-06-24 17:48:27.275 +global: 2025-06-24 17:48:29.001 +global: 2025-06-24 17:49:10.344 +global: 2025-06-24 17:49:11.557 +global: 2025-06-24 17:49:11.865 +global: 2025-06-24 17:49:48.917 +global: 2025-06-24 17:51:08.407 +global: 2025-06-24 17:51:10.284 +global: 2025-06-24 17:51:11.104 +global: 2025-06-24 17:51:44.258 +global: 2025-06-24 17:53:12.026 +global: 2025-06-24 17:53:13.482 +global: 2025-06-24 17:53:14.406 +global: 2025-06-24 18:22:55.896 +global: 2025-06-24 18:23:06.196 +global: 2025-06-24 18:23:11.503 +global: 2025-06-24 18:23:11.650 +global: 2025-06-24 18:23:11.754 +global: 2025-06-24 18:23:11.865 +global: 2025-06-24 18:23:12.012 +global: 2025-06-24 18:23:12.155 +global: 2025-06-24 18:23:38.278 +global: 2025-06-24 18:23:54.872 +global: 2025-06-24 18:25:58.086 +global: 2025-06-24 18:26:27.405 +global: 2025-06-24 18:26:28.630 +global: 2025-06-24 18:26:47.178 +global: 2025-06-24 18:26:52.160 +global: 2025-06-24 18:26:56.512 +global: 2025-06-24 18:32:53.221 +global: 2025-06-24 18:32:55.371 +global: 2025-06-24 22:20:06.431 +global: 2025-06-24 22:21:35.747 +global: 2025-06-24 22:59:26.169 +global: 2025-06-24 22:59:27.160 +global: 2025-06-24 23:00:03.242 +global: 2025-06-24 23:00:03.459 +global: 2025-06-24 23:00:03.562 +global: 2025-06-24 23:01:00.046 +global: 2025-06-24 23:01:00.385 +global: 2025-06-24 23:01:01.079 +global: 2025-06-24 23:01:11.396 +global: 2025-06-24 23:01:33.516 +global: 2025-06-24 23:01:37.392 +global: 2025-06-24 23:19:52.709 +global: 2025-06-24 23:19:53.237 +global: 2025-06-24 23:34:48.348 +global: 2025-06-24 23:34:50.935 +global: 2025-06-24 23:35:48.723 +global: 2025-06-24 23:35:57.592 +global: 2025-06-24 23:35:58.250 +global: 2025-06-24 23:39:28.281 +global: 2025-06-24 23:39:30.155 +global: 2025-06-24 23:42:29.459 +global: 2025-06-24 23:42:57.344 +global: 2025-06-24 23:43:38.167 +global: 2025-06-24 23:44:10.629 +global: 2025-06-24 23:44:25.924 +global: 2025-06-24 23:46:34.897 +global: 2025-06-24 23:46:50.662 +global: 2025-06-24 23:50:45.229 +global: 2025-06-24 23:50:56.070 +global: 2025-06-24 23:51:09.942 +global: 2025-06-24 23:52:02.703 +global: 2025-06-24 23:53:30.211 +global: 2025-06-24 23:53:46.606 +global: 2025-06-24 23:57:56.929 +global: 2025-06-24 23:57:57.593 +global: 2025-06-25 01:21:07.150 +global: 2025-06-25 01:21:10.473 +global: 2025-06-25 01:22:06.201 +global: 2025-06-25 01:45:57.994 +global: 2025-06-25 01:46:17.036 +global: 2025-06-25 01:46:30.500 +global: 2025-06-25 01:46:46.406 +global: 2025-06-25 01:48:14.563 +global: 2025-06-25 01:48:15.786 +global: 2025-06-25 01:48:16.222 +global: 2025-06-25 01:48:16.643 +global: 2025-06-25 01:48:17.139 +global: 2025-06-25 01:48:17.865 +global: 2025-06-25 01:48:18.689 +global: 2025-06-25 01:48:19.427 +global: 2025-06-25 01:48:19.623 +global: 2025-06-25 01:48:19.816 +global: 2025-06-25 01:48:20.069 +global: 2025-06-25 01:48:20.317 +global: 2025-06-25 02:27:48.511 +global: 2025-06-25 02:30:05.423 +global: 2025-06-25 02:31:41.642 +global: 2025-06-25 02:31:41.654 +global: 2025-06-25 02:31:41.772 +global: 2025-06-25 02:31:41.917 +global: 2025-06-25 02:31:42.059 +global: 2025-06-25 02:31:42.953 +global: 2025-06-25 02:31:43.109 +global: 2025-06-25 02:31:43.268 +global: 2025-06-25 02:35:09.905 +global: 2025-06-25 02:35:10.294 +global: 2025-06-25 02:35:10.764 +global: 2025-06-25 02:35:11.246 +global: 2025-06-25 02:35:11.744 +global: 2025-06-25 02:35:12.480 +global: 2025-06-25 02:35:13.312 +global: 2025-06-25 02:35:14.002 +global: 2025-06-25 02:35:16.043 +global: 2025-06-25 02:35:17.110 +global: 2025-06-25 02:35:17.930 +global: 2025-06-25 02:35:18.700 +global: 2025-06-25 02:35:19.431 +global: 2025-06-25 02:35:20.129 +global: 2025-06-25 02:35:20.800 +global: 2025-06-25 02:35:21.477 +global: 2025-06-25 02:35:22.676 +global: 2025-06-25 02:35:22.959 +global: 2025-06-25 02:35:23.311 +global: 2025-06-25 02:35:23.415 +global: 2025-06-25 02:35:23.626 +global: 2025-06-25 02:35:24.478 +global: 2025-06-25 02:35:25.592 +global: 2025-06-25 02:35:25.740 +global: 2025-06-25 02:35:25.889 +global: 2025-06-25 02:35:26.006 +global: 2025-06-25 02:35:26.141 +global: 2025-06-25 14:23:28.444 +global: 2025-06-25 14:23:35.867 +global: 2025-06-25 14:23:36.629 +global: 2025-06-25 14:23:37.304 +global: 2025-06-25 14:23:38.012 +global: 2025-06-25 14:23:38.684 +global: 2025-06-25 14:23:39.506 +global: 2025-06-25 14:23:40.236 +global: 2025-06-25 14:23:40.518 +global: 2025-06-25 14:23:40.634 +global: 2025-06-25 14:23:40.982 +global: 2025-06-25 14:23:41.232 +global: 2025-06-25 14:23:41.374 +global: 2025-06-25 14:23:41.509 +global: 2025-06-25 14:40:03.465 +global: 2025-06-25 14:40:04.101 +global: 2025-06-25 14:40:08.308 +global: 2025-06-25 14:40:19.286 +global: 2025-06-25 14:40:19.744 +global: 2025-06-25 14:40:19.873 +global: 2025-06-25 14:40:20.021 +global: 2025-06-25 14:40:20.151 +global: 2025-06-25 14:40:20.284 +global: 2025-06-25 14:40:20.454 +global: 2025-06-25 14:40:20.572 +global: 2025-06-25 14:40:20.731 +global: 2025-06-25 14:40:20.837 +global: 2025-06-25 14:40:20.964 +global: 2025-06-25 14:40:21.101 +global: 2025-06-25 14:40:21.226 +global: 2025-06-25 14:40:21.370 +global: 2025-06-25 14:40:21.486 +global: 2025-06-25 14:40:21.792 +global: 2025-06-25 14:40:21.883 +global: 2025-06-25 14:40:22.022 +global: 2025-06-25 14:40:22.275 +global: 2025-06-25 14:40:28.965 +global: 2025-06-25 14:40:30.073 +global: 2025-06-25 14:40:30.198 +global: 2025-06-25 14:40:30.334 +global: 2025-06-25 14:40:30.449 +global: 2025-06-25 14:40:30.589 +global: 2025-06-25 14:40:31.500 +global: 2025-06-25 14:40:31.600 +global: 2025-06-25 14:40:31.741 +global: 2025-06-25 14:40:31.855 +global: 2025-06-25 14:40:31.998 +global: 2025-06-25 14:40:32.221 +global: 2025-06-25 14:40:32.264 +global: 2025-06-25 14:40:32.402 +global: 2025-06-25 14:40:32.553 +global: 2025-06-25 14:40:32.687 +global: 2025-06-25 14:40:32.819 +global: 2025-06-25 14:40:32.957 +global: 2025-06-25 14:40:33.088 +global: 2025-06-25 14:40:33.227 +global: 2025-06-25 14:40:36.461 +global: 2025-06-25 14:40:41.502 +global: 2025-06-25 14:40:42.284 +global: 2025-06-25 14:40:43.019 +global: 2025-06-25 14:40:43.816 +global: 2025-06-25 14:40:44.574 +global: 2025-06-25 14:40:45.243 +global: 2025-06-25 14:40:45.888 +global: 2025-06-25 14:40:46.530 +global: 2025-06-25 14:40:47.057 +global: 2025-06-25 14:40:47.520 +global: 2025-06-25 14:40:47.993 +global: 2025-06-25 14:40:48.786 +global: 2025-06-25 14:40:49.473 +global: 2025-06-25 14:41:08.089 +global: 2025-06-25 14:41:15.334 +global: 2025-06-25 14:41:16.254 +global: 2025-06-25 14:41:17.133 +global: 2025-06-25 14:41:17.859 +global: 2025-06-25 14:41:19.036 +global: 2025-06-25 14:41:19.926 +global: 2025-06-25 14:41:20.758 +global: 2025-06-25 14:41:21.457 +global: 2025-06-25 14:41:22.117 +global: 2025-06-25 14:41:22.819 +global: 2025-06-25 14:41:23.440 +global: 2025-06-25 14:41:24.202 +global: 2025-06-25 14:41:24.738 +global: 2025-06-25 14:41:25.280 +global: 2025-06-25 14:41:25.743 +global: 2025-06-25 14:41:27.017 +test_queue: 2025-06-25 14:42:54.719 +test_queue: 2025-06-25 14:42:54.720 +test_queue: 2025-06-25 14:42:54.722 +test_queue: 2025-06-25 14:42:54.723 +test_queue: 2025-06-25 14:42:54.724 +test_queue: 2025-06-25 14:42:58.146 +test_queue: 2025-06-25 14:42:58.148 +test_queue: 2025-06-25 14:42:58.149 +test_queue: 2025-06-25 14:42:58.151 +test_queue: 2025-06-25 14:43:00.839 +test_queue: 2025-06-25 14:43:00.840 +test_queue: 2025-06-25 14:43:00.842 +test_queue: 2025-06-25 14:43:00.843 +global: 2025-06-25 15:13:28.314 +global: 2025-06-25 15:13:35.547 +global: 2025-06-25 15:13:36.261 +global: 2025-06-25 15:13:36.944 +global: 2025-06-25 15:13:38.114 +global: 2025-06-25 15:14:40.710 +global: 2025-06-25 15:14:41.474 +global: 2025-06-25 15:14:42.060 +global: 2025-06-25 15:15:37.561 +global: 2025-06-25 15:17:01.066 +global: 2025-06-25 15:17:01.695 +global: 2025-06-25 15:17:03.195 +global: 2025-06-25 15:17:11.779 +global: 2025-06-25 15:17:12.850 +global: 2025-06-25 15:17:39.010 +global: 2025-06-25 15:17:41.400 +global: 2025-06-25 15:17:42.151 +global: 2025-06-25 15:17:42.946 +global: 2025-06-25 15:17:43.587 +global: 2025-06-25 15:17:44.287 +global: 2025-06-25 15:17:44.979 +global: 2025-06-25 15:17:45.796 +global: 2025-06-25 15:19:04.160 +global: 2025-06-25 15:19:11.543 +global: 2025-06-25 15:19:12.645 +global: 2025-06-25 15:19:13.374 +global: 2025-06-25 15:19:14.386 +global: 2025-06-25 15:19:15.188 +global: 2025-06-25 15:19:15.926 +global: 2025-06-25 15:19:16.621 +global: 2025-06-25 15:19:17.276 +global: 2025-06-25 15:19:35.712 +global: 2025-06-25 15:19:36.599 +global: 2025-06-25 15:19:37.411 +global: 2025-06-25 15:19:38.202 +global: 2025-06-25 15:19:39.254 +global: 2025-06-25 15:19:40.038 +global: 2025-06-25 15:19:40.954 +global: 2025-06-25 15:20:03.503 +global: 2025-06-25 15:20:11.100 +global: 2025-06-25 15:20:11.799 +global: 2025-06-25 15:24:34.863 +global: 2025-06-25 15:24:36.046 +global: 2025-06-25 15:24:37.093 +global: 2025-06-25 21:53:07.337 +global: 2025-06-25 21:53:09.483 +global: 2025-06-25 21:53:20.220 +global: 2025-06-25 21:53:21.004 +global: 2025-06-25 21:59:49.062 +global: 2025-06-25 21:59:51.581 +test_queue: 2025-06-25 22:01:55.065 +test_queue: 2025-06-25 22:01:55.067 +test_queue: 2025-06-25 22:01:55.068 +test_queue: 2025-06-25 22:01:55.069 +test_queue: 2025-06-25 22:01:55.070 +global: 2025-06-25 22:08:46.989 +global: 2025-06-25 22:08:53.058 +global: 2025-06-25 22:09:02.801 +global: 2025-06-25 22:09:29.489 +global: 2025-06-25 22:09:30.568 +global: 2025-06-25 22:10:10.731 +global: 2025-06-25 22:10:11.817 +global: 2025-06-25 22:21:23.596 diff --git a/internal_apis/main.py b/internal_apis/main.py index 89c8888..dabba28 100644 --- a/internal_apis/main.py +++ b/internal_apis/main.py @@ -5,5 +5,6 @@ internal_apis = create_app(get_config()) if __name__ == "__main__": - internal_apis.run(host="0.0.0.0", port=5081, debug=True) + internal_apis.run(host="0.0.0.0", port=5081, debug=False) + diff --git a/internal_apis/poetry.lock b/internal_apis/poetry.lock index b8abbe4..f452f83 100644 --- a/internal_apis/poetry.lock +++ b/internal_apis/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. [[package]] name = "aniso8601" @@ -6,29 +6,44 @@ version = "7.0.0" description = "A library for parsing ISO 8601 strings." optional = false python-versions = "*" +groups = ["main"] files = [ {file = "aniso8601-7.0.0-py2.py3-none-any.whl", hash = "sha256:d10a4bf949f619f719b227ef5386e31f49a2b6d453004b21f02661ccc8670c7b"}, {file = "aniso8601-7.0.0.tar.gz", hash = "sha256:513d2b6637b7853806ae79ffaca6f3e8754bdd547048f5ccc1420aec4b714f1e"}, ] +[[package]] +name = "async-timeout" +version = "5.0.1" +description = "Timeout context manager for asyncio programs" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_full_version < \"3.11.3\"" +files = [ + {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, + {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, +] + [[package]] name = "attrs" version = "25.3.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, ] [package.extras] -benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] -tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] +tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] [[package]] name = "blinker" @@ -36,6 +51,7 @@ version = "1.9.0" description = "Fast, simple object-to-object and broadcast signaling" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc"}, {file = "blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf"}, @@ -47,6 +63,7 @@ version = "0.13.0" description = "A collection of cache libraries in the same API interface." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "cachelib-0.13.0-py3-none-any.whl", hash = "sha256:8c8019e53b6302967d4e8329a504acf75e7bc46130291d30188a6e4e58162516"}, {file = "cachelib-0.13.0.tar.gz", hash = "sha256:209d8996e3c57595bee274ff97116d1d73c4980b2fd9a34c7846cd07fd2e1a48"}, @@ -58,6 +75,7 @@ version = "2025.1.31" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, @@ -69,6 +87,7 @@ version = "3.4.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, @@ -170,6 +189,7 @@ version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -184,6 +204,8 @@ 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" +groups = ["main"] +markers = "platform_system == \"Windows\" or sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -195,6 +217,7 @@ version = "2.7.0" description = "DNS toolkit" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86"}, {file = "dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1"}, @@ -215,6 +238,7 @@ version = "0.9.7.1" description = "Extract swagger specs from your flask project" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "flasgger-0.9.7.1.tar.gz", hash = "sha256:ca098e10bfbb12f047acc6299cc70a33851943a746e550d86e65e60d4df245fb"}, ] @@ -233,6 +257,7 @@ version = "3.1.0" description = "A simple framework for building complex web applications." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "flask-3.1.0-py3-none-any.whl", hash = "sha256:d667207822eb83f1c4b50949b1623c8fc8d51f2341d65f72e1a1815397551136"}, {file = "flask-3.1.0.tar.gz", hash = "sha256:5f873c5184c897c8d9d1b05df1e3d01b14910ce69607a117bd3277098a5836ac"}, @@ -255,6 +280,7 @@ version = "2.3.1" description = "Adds caching support to Flask applications." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "Flask_Caching-2.3.1-py3-none-any.whl", hash = "sha256:d3efcf600e5925ea5a2fcb810f13b341ae984f5b52c00e9d9070392f3ca10761"}, {file = "flask_caching-2.3.1.tar.gz", hash = "sha256:65d7fd1b4eebf810f844de7de6258254b3248296ee429bdcb3f741bcbf7b98c9"}, @@ -270,6 +296,7 @@ version = "2.0.1" description = "Adds GraphQL support to your Flask application" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "Flask-GraphQL-2.0.1.tar.gz", hash = "sha256:825578c044df436cd74503a38bbd31c919a90acda5e9b6e0e45736964bc5235d"}, ] @@ -285,6 +312,7 @@ version = "2.1.9" description = "GraphQL Framework for Python" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "graphene-2.1.9-py2.py3-none-any.whl", hash = "sha256:3d446eb1237c551052bc31155cf1a3a607053e4f58c9172b83a1b597beaa0868"}, {file = "graphene-2.1.9.tar.gz", hash = "sha256:b9f2850e064eebfee9a3ef4a1f8aa0742848d97652173ab44c82cc8a62b9ed93"}, @@ -307,6 +335,7 @@ version = "0.2.0" description = "Graphene Mongoengine integration" optional = false python-versions = ">=2.7" +groups = ["main"] files = [ {file = "graphene-mongo-0.2.0.tar.gz", hash = "sha256:3f7f7ff7652ebec2b2157afc32ce43388191de727f26e907fcc6a327620b1959"}, ] @@ -323,6 +352,7 @@ version = "2.3.2" description = "GraphQL implementation for Python" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "graphql-core-2.3.2.tar.gz", hash = "sha256:aac46a9ac524c9855910c14c48fc5d60474def7f99fd10245e76608eba7af746"}, {file = "graphql_core-2.3.2-py2.py3-none-any.whl", hash = "sha256:44c9bac4514e5e30c5a595fac8e3c76c1975cae14db215e8174c7fe995825bad"}, @@ -343,6 +373,7 @@ version = "2.0.1" description = "Relay implementation for Python" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "graphql-relay-2.0.1.tar.gz", hash = "sha256:870b6b5304123a38a0b215a79eace021acce5a466bf40cd39fa18cb8528afabb"}, {file = "graphql_relay-2.0.1-py3-none-any.whl", hash = "sha256:ac514cb86db9a43014d7e73511d521137ac12cf0101b2eaa5f0a3da2e10d913d"}, @@ -359,6 +390,7 @@ version = "1.2.0" description = "GraphQL Server tools for powering your server" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "graphql-server-core-1.2.0.tar.gz", hash = "sha256:04ee90da0322949f7b49ff6905688e3a21a9efbd5a7d7835997e431a0afdbd11"}, ] @@ -376,6 +408,7 @@ version = "23.0.0" description = "WSGI HTTP Server for UNIX" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d"}, {file = "gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"}, @@ -397,6 +430,7 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -411,6 +445,7 @@ version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, @@ -422,6 +457,7 @@ version = "2.1.0" description = "Simple module to parse ISO 8601 dates" optional = false python-versions = ">=3.7,<4.0" +groups = ["main"] files = [ {file = "iso8601-2.1.0-py3-none-any.whl", hash = "sha256:aac4145c4dcb66ad8b648a02830f5e2ff6c24af20f4f482689be402db2429242"}, {file = "iso8601-2.1.0.tar.gz", hash = "sha256:6b1d3829ee8921c4301998c909f7829fa9ed3cbdac0d3b16af2d743aed1ba8df"}, @@ -433,6 +469,7 @@ version = "2.2.0" description = "Safely pass data to untrusted environments and back." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, @@ -444,6 +481,7 @@ version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -461,6 +499,7 @@ version = "4.23.0" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566"}, {file = "jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4"}, @@ -482,6 +521,7 @@ version = "2025.4.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af"}, {file = "jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608"}, @@ -496,6 +536,7 @@ version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, @@ -566,6 +607,7 @@ version = "3.1.3" description = "A sane and fast Markdown parser with useful plugins and renderers" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "mistune-3.1.3-py3-none-any.whl", hash = "sha256:1a32314113cff28aa6432e99e522677c8587fd83e3d51c29b82a52409c842bd9"}, {file = "mistune-3.1.3.tar.gz", hash = "sha256:a7035c21782b2becb6be62f8f25d3df81ccb4d6fa477a6525b15af06539f02a0"}, @@ -577,6 +619,7 @@ version = "0.29.1" description = "MongoEngine is a Python Object-Document Mapper for working with MongoDB." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "mongoengine-0.29.1-py3-none-any.whl", hash = "sha256:9302ec407dd60f47f62cc07684d9f6cac87f1e93283c54203851788104d33df4"}, {file = "mongoengine-0.29.1.tar.gz", hash = "sha256:3b43abaf2d5f0b7d39efc2b7d9e78f4d4a5dc7ce92b9889ba81a5a9b8dee3cf3"}, @@ -594,6 +637,7 @@ version = "4.3.0" description = "Fake pymongo stub for testing simple MongoDB-dependent code" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "mongomock-4.3.0-py2.py3-none-any.whl", hash = "sha256:5ef86bd12fc8806c6e7af32f21266c61b6c4ba96096f85129852d1c4fec1327e"}, {file = "mongomock-4.3.0.tar.gz", hash = "sha256:32667b79066fabc12d4f17f16a8fd7361b5f4435208b3ba32c226e52212a8c30"}, @@ -614,6 +658,7 @@ version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, @@ -625,6 +670,7 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -640,6 +686,7 @@ version = "2.3" description = "Promises/A+ implementation for Python" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "promise-2.3.tar.gz", hash = "sha256:dfd18337c523ba4b6a58801c164c1904a9d4d1b1747c7d5dbf45b693a49d93d0"}, ] @@ -656,6 +703,7 @@ version = "4.12.0" description = "Python driver for MongoDB " optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "pymongo-4.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e23d9b5e8d2dfc3ac0540966e93008e471345ec9a2797b77be551e64b70fc8ee"}, {file = "pymongo-4.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ecf325f31bf8be70ec5cab50b45a8e09acf3952d693215acac965cecaeb6b58d"}, @@ -722,9 +770,9 @@ dnspython = ">=1.16.0,<3.0.0" [package.extras] aws = ["pymongo-auth-aws (>=1.1.0,<2.0.0)"] docs = ["furo (==2024.8.6)", "readthedocs-sphinx-search (>=0.3,<1.0)", "sphinx (>=5.3,<9)", "sphinx-autobuild (>=2020.9.1)", "sphinx-rtd-theme (>=2,<4)", "sphinxcontrib-shellcheck (>=1,<2)"] -encryption = ["certifi", "pymongo-auth-aws (>=1.1.0,<2.0.0)", "pymongocrypt (>=1.13.0,<2.0.0)"] -gssapi = ["pykerberos", "winkerberos (>=0.5.0)"] -ocsp = ["certifi", "cryptography (>=2.5)", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] +encryption = ["certifi ; os_name == \"nt\" or sys_platform == \"darwin\"", "pymongo-auth-aws (>=1.1.0,<2.0.0)", "pymongocrypt (>=1.13.0,<2.0.0)"] +gssapi = ["pykerberos ; os_name != \"nt\"", "winkerberos (>=0.5.0) ; os_name == \"nt\""] +ocsp = ["certifi ; os_name == \"nt\" or sys_platform == \"darwin\"", "cryptography (>=2.5)", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] snappy = ["python-snappy"] test = ["pytest (>=8.2)", "pytest-asyncio (>=0.24.0)"] zstd = ["zstandard"] @@ -735,6 +783,7 @@ version = "8.3.5" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"}, {file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"}, @@ -755,6 +804,7 @@ version = "1.1.0" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d"}, {file = "python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5"}, @@ -769,6 +819,7 @@ version = "2025.2" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00"}, {file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"}, @@ -780,6 +831,7 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -842,11 +894,15 @@ version = "5.2.1" description = "Python client for Redis database and key-value store" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "redis-5.2.1-py3-none-any.whl", hash = "sha256:ee7e1056b9aea0f04c6c2ed59452947f34c4940ee025f5dd83e6a6418b6989e4"}, {file = "redis-5.2.1.tar.gz", hash = "sha256:16f2e22dff21d5125e8481515e386711a34cbec50f0e44413dd7d9c060a54e0f"}, ] +[package.dependencies] +async-timeout = {version = ">=4.0.3", markers = "python_full_version < \"3.11.3\""} + [package.extras] hiredis = ["hiredis (>=3.0.0)"] ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==23.2.1)", "requests (>=2.31.0)"] @@ -857,6 +913,7 @@ version = "0.36.2" description = "JSON Referencing + Python" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0"}, {file = "referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa"}, @@ -873,6 +930,7 @@ version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, @@ -894,6 +952,7 @@ version = "0.24.0" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "rpds_py-0.24.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:006f4342fe729a368c6df36578d7a348c7c716be1da0a1a0f86e3021f8e98724"}, {file = "rpds_py-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2d53747da70a4e4b17f559569d5f9506420966083a31c5fbd84e764461c4444b"}, @@ -1017,6 +1076,7 @@ version = "1.6.3" description = "Reactive Extensions (Rx) for Python" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "Rx-1.6.3.tar.gz", hash = "sha256:ca71b65d0fc0603a3b5cfaa9e33f5ba81e4aae10a58491133595088d7734b2da"}, ] @@ -1027,6 +1087,7 @@ version = "1.0.0" description = "Various objects to denote special meanings in python" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "sentinels-1.0.0.tar.gz", hash = "sha256:7be0704d7fe1925e397e92d18669ace2f619c92b5d4eb21a89f31e026f9ff4b1"}, ] @@ -1037,19 +1098,20 @@ version = "75.9.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "setuptools-75.9.1-py3-none-any.whl", hash = "sha256:0a6f876d62f4d978ca1a11ab4daf728d1357731f978543ff18ecdbf9fd071f73"}, {file = "setuptools-75.9.1.tar.gz", hash = "sha256:b6eca2c3070cdc82f71b4cb4bb2946bc0760a210d11362278cf1ff394e6ea32c"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"] -core = ["importlib_metadata (>=6)", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] +core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] [[package]] name = "singledispatch" @@ -1057,13 +1119,14 @@ version = "4.1.1" description = "Backport functools.singledispatch to older Pythons." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "singledispatch-4.1.1-py3-none-any.whl", hash = "sha256:43cb2faed6140af6c43f95c1621f750591d2ec2017005ca330691683e495e863"}, {file = "singledispatch-4.1.1.tar.gz", hash = "sha256:f200caabe9ddf6e3072332f51ebd4e6780bec24fc265291fae9298af07705ce8"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] @@ -1076,6 +1139,7 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -1087,6 +1151,8 @@ version = "4.13.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version < \"3.13\"" files = [ {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"}, {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"}, @@ -1098,13 +1164,14 @@ version = "2.4.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813"}, {file = "urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -1115,6 +1182,7 @@ version = "3.1.3" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"}, {file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"}, @@ -1127,6 +1195,6 @@ MarkupSafe = ">=2.1.1" watchdog = ["watchdog (>=2.3)"] [metadata] -lock-version = "2.0" -python-versions = "^3.12" -content-hash = "dbaecbb617895bf5d1be69ae316f1e13f01490904dc237241c357326458e48c5" +lock-version = "2.1" +python-versions = "^3.11" +content-hash = "36802b66463c310e3678f3bc62bb6dd14bbbc5326b49be3fae04cb2c5a622645" diff --git a/internal_apis/pyproject.toml b/internal_apis/pyproject.toml index fe29eeb..9475117 100644 --- a/internal_apis/pyproject.toml +++ b/internal_apis/pyproject.toml @@ -16,7 +16,7 @@ packages = [ ] [tool.poetry.dependencies] -python = "^3.12" +python = "^3.11" flask = "^3.0.3" graphene = "2.1.9" flask-graphql = "2.0.1" diff --git a/internal_apis/src/SLL/__init__.py b/internal_apis/src/SLL/__init__.py index c9b7575..a15b096 100644 --- a/internal_apis/src/SLL/__init__.py +++ b/internal_apis/src/SLL/__init__.py @@ -1,20 +1,32 @@ +import os + from flasgger import Swagger from flask import Flask, jsonify from mongoengine import connect from utils import log_error_request +import threading +from backpressure.leaky_bucket_worker import start_leaky_bucket_worker +from utils import log_info_request from utils.cache import init_cache from .graphql import setup_graphql_routes from .restapi import setup_rest_routes + def create_app(config_class): app = Flask(__name__) + app.config.from_object(config_class) + init_cache(app) # Registra as rotas REST e GraphQL setup_rest_routes(app) setup_graphql_routes(app) + worker_thread = threading.Thread( + target=start_leaky_bucket_worker, args=(float(os.getenv('GLOBAL_LEAK_RATE')), int(os.getenv('GLOBAL_BUCKET_SIZE')), os.getenv('GLOBAL_QUEUE_NAME')), daemon=True + ) + worker_thread.start() # Configuração do Swagger swagger_config = { @@ -69,4 +81,6 @@ def trigger_error(): raise RuntimeError("This is a test error for 500 handler") + return app + diff --git a/internal_apis/src/SLL/campus_routes.py b/internal_apis/src/SLL/campus_routes.py index def7b68..04a5e9f 100644 --- a/internal_apis/src/SLL/campus_routes.py +++ b/internal_apis/src/SLL/campus_routes.py @@ -1,13 +1,18 @@ from flask import Blueprint, jsonify, request from flasgger import swag_from - +from internal_apis.src.utils.auth import check_api_key from DAL import Campus +from auth_routes import token_required from utils import log_info_request, log_resource_not_found, get_swagger_specification +from backpressure.individual_leaky_bucket import LeakyBucket +from backpressure.leaky_bucket_rabbitmq import LeakyBucketRabbitMQ campus_bp = Blueprint('campus_bp', __name__, url_prefix="/campus") spec = get_swagger_specification(path="campus", method="GET") @campus_bp.route("/", methods=["GET"]) +@LeakyBucket.individual_leaky_bucket(bucketcapacity=2, leakrate=5, keytimeout=25) +@LeakyBucketRabbitMQ.add_to_global_leaky_bucket() @log_info_request @swag_from(spec) def get_campus(): diff --git a/internal_apis/src/utils/auth.py b/internal_apis/src/utils/auth.py index 7ef8c94..d77b548 100644 --- a/internal_apis/src/utils/auth.py +++ b/internal_apis/src/utils/auth.py @@ -10,7 +10,7 @@ def validate_api_key(api_key): if api_key in API_KEY_LIST: - log_authentication_request(api_key) + #log_authentication_request(api_key) return True return False diff --git a/internal_apis/src/utils/log_functions.py b/internal_apis/src/utils/log_functions.py index c2f55e5..ab6527c 100644 --- a/internal_apis/src/utils/log_functions.py +++ b/internal_apis/src/utils/log_functions.py @@ -4,6 +4,17 @@ from .py_log import AppLogger, Logmessage, LogType +def log_api_key(resource_type, parameter, value): + AppLogger.log( + Logmessage.RESOURCE_NOT_FOUND, + LogType.ERROR, + resource_type=resource_type, + parameter=parameter, + value=value, + request_path=request.path, + ip_address=request.remote_addr, + ) + def log_resource_not_found(resource_type, parameter, value): AppLogger.log( Logmessage.RESOURCE_NOT_FOUND, @@ -18,12 +29,19 @@ def log_resource_not_found(resource_type, parameter, value): def log_info_request(func): @wraps(func) def wrapper(*args, **kwargs): + if request.headers.get("X-API-Key"): + reported_api_key = request.headers.get("X-API-Key") + else: + reported_api_key = "No API Key Informed" + AppLogger.log( Logmessage.REQUEST_INFO, LogType.INFO, ip_address=request.remote_addr, request_method=request.method, request_path=request.path, + api_key=reported_api_key + ) return func(*args, **kwargs) return wrapper @@ -72,4 +90,4 @@ def log_invalid_credentials(api_key): request_path=request.path, ip_address=request.remote_addr, api_key=api_key - ) \ No newline at end of file + ) diff --git a/internal_apis/src/utils/py_log.py b/internal_apis/src/utils/py_log.py index 565f68a..8abde54 100644 --- a/internal_apis/src/utils/py_log.py +++ b/internal_apis/src/utils/py_log.py @@ -3,6 +3,7 @@ from enum import Enum # Deactivate werkzeug logs +logging.getLogger('pika').setLevel(logging.WARNING) logging.getLogger('werkzeug').setLevel(logging.ERROR) logging.basicConfig(level=logging.INFO, filename="py_log.log", filemode="a", @@ -10,7 +11,7 @@ class Logmessage(Enum): - REQUEST_INFO = "Method: {request_method} - Path: {request_path} - IP: {ip_address}" + REQUEST_INFO = "Method: {request_method} - Path: {request_path} - IP: {ip_address} - API Key Informed: {api_key}" ERROR = "Message: {error_message} - Path: {request_path} - IP: {ip_address}" MISSING_CREDENTIALS = "Missing credentials: {request_method} - Path: {request_path} - IP: {ip_address}" INVALID_API_KEY = "Invalid API key: {request_method} - Path: {request_path} - IP: {ip_address} - API Key: {api_key}" @@ -46,3 +47,4 @@ def log(message: Logmessage, log_type: LogType, **kwargs): }.get(log_type.value, logging.info) log_function(formatted_message) +