diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 5573a2ce..c1aa7c83 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,4 +1,5 @@ { "src/supabase": "2.18.1", - "src/realtime": "2.7.0" + "src/realtime": "2.7.0", + "src/functions": "0.10.1" } diff --git a/Makefile b/Makefile index bd5f527f..b5e8c2a2 100644 --- a/Makefile +++ b/Makefile @@ -1,15 +1,17 @@ .PHONY: ci, default, pre-commit default: - @echo "Available targets are: ci, pre-commit" + @echo "Available targets are: ci, pre-commit, publish" ci: pre-commit make -C src/realtime tests + make -C src/functions tests make -C src/supabase tests publish: uv build --project realtime uv build --project supabase + uv build --project functions uv publish pre-commit: diff --git a/pyproject.toml b/pyproject.toml index a21d84fe..766cbf1f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,13 @@ [tool.uv.workspace] members = [ "src/realtime", + "src/functions", "src/supabase" ] [tool.uv.sources] realtime = { workspace = true } +supabase_functions = { workspace = true } supabase = { workspace = true } [tool.pytest.ini_options] diff --git a/release-please-config.json b/release-please-config.json index d4e910dc..820447f3 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -5,9 +5,13 @@ "changelog-path": "src/realtime/CHANGELOG.md", "release-type": "python" }, + "src/functions": { + "changelog-path": "src/functions/CHANGELOG.md", + "release-type": "python" + } "src/supabase": { "changelog-path": "src/supabase/CHANGELOG.md", "release-type": "python" - } + }, } } diff --git a/src/functions/CHANGELOG.md b/src/functions/CHANGELOG.md new file mode 100644 index 00000000..a73f074c --- /dev/null +++ b/src/functions/CHANGELOG.md @@ -0,0 +1,574 @@ +# CHANGELOG + +## [0.10.1](https://github.com/supabase/functions-py/compare/v0.10.0...v0.10.1) (2025-06-20) + + +### Bug Fixes + +* remove jwt key validation to allow new api keys ([#212](https://github.com/supabase/functions-py/issues/212)) ([5b852e4](https://github.com/supabase/functions-py/commit/5b852e4b92f232c9b6c91e802d2f1cdbb0ab72e2)) + +## [0.10.0](https://github.com/supabase/functions-py/compare/v0.9.4...v0.10.0) (2025-06-19) + + +### Features + +* allow injection of httpx client ([#205](https://github.com/supabase/functions-py/issues/205)) ([1d3ce59](https://github.com/supabase/functions-py/commit/1d3ce59d7da6b6224cb4e357125c1cbb47f8647b)) + +## [0.9.4](https://github.com/supabase/functions-py/compare/v0.9.3...v0.9.4) (2025-03-26) + + +### Bug Fixes + +* custom error status code from edge function returned correctly ([#196](https://github.com/supabase/functions-py/issues/196)) ([7077a38](https://github.com/supabase/functions-py/commit/7077a38fb6c303f3c2f6d78f1265a43b04e7ec59)) + +## [0.9.3](https://github.com/supabase/functions-py/compare/v0.9.2...v0.9.3) (2025-01-29) + + +### Bug Fixes + +* body types other than JSON are improperly handled when invoking a function ([#186](https://github.com/supabase/functions-py/issues/186)) ([d1ba63a](https://github.com/supabase/functions-py/commit/d1ba63a87336e475c6a765c61d1a259a2770930f)) + +## [0.9.2](https://github.com/supabase/functions-py/compare/v0.9.1...v0.9.2) (2025-01-17) + + +### Bug Fixes + +* ci pipeline issue with renaming the project ([#182](https://github.com/supabase/functions-py/issues/182)) ([658a84d](https://github.com/supabase/functions-py/commit/658a84d95542defd328427e5566910106e2540b9)) + +## [0.9.1](https://github.com/supabase/functions-py/compare/v0.9.0...v0.9.1) (2025-01-17) + + +### Bug Fixes + +* add full test coverage ([#180](https://github.com/supabase/functions-py/issues/180)) ([2bc3d3c](https://github.com/supabase/functions-py/commit/2bc3d3ccb4d62c98c5d13d25c2073fb86f84feb6)) + +## [0.9.0](https://github.com/supabase/functions-py/compare/v0.8.0...v0.9.0) (2024-11-28) + + +### Features + +* rewrite region enumerated literals as Enums ([#164](https://github.com/supabase/functions-py/issues/164)) ([3ca78fa](https://github.com/supabase/functions-py/commit/3ca78fa14ceb6b0cb2b70ee6dd7a1229fe974cf4)) + +## [0.8.0](https://github.com/supabase/functions-py/compare/v0.7.0...v0.8.0) (2024-11-22) + + +### Features + +* Check if token is a JWT ([#159](https://github.com/supabase/functions-py/issues/159)) ([44f7b39](https://github.com/supabase/functions-py/commit/44f7b39ee5f7a4d7a8019bc02599b551fb71272d)) + +## [0.7.0](https://github.com/supabase/functions-py/compare/v0.6.2...v0.7.0) (2024-10-31) + + +### Features + +* Check if url is an HTTP URL ([#156](https://github.com/supabase/functions-py/issues/156)) ([6123554](https://github.com/supabase/functions-py/commit/6123554c3916d091407c00eab1e4cfb0c57dce56)) + +## [0.6.2](https://github.com/supabase/functions-py/compare/v0.6.1...v0.6.2) (2024-10-15) + + +### Bug Fixes + +* bump minimal version of Python to 3.9 ([#154](https://github.com/supabase/functions-py/issues/154)) ([f2dab24](https://github.com/supabase/functions-py/commit/f2dab248b81df6c981434cee5d4160e95cb92df9)) +* Types to use Option[T] ([#152](https://github.com/supabase/functions-py/issues/152)) ([637bf4e](https://github.com/supabase/functions-py/commit/637bf4e33f1c6a845654dba923e6215ed8cd4a7f)) + +## [0.6.1](https://github.com/supabase/functions-py/compare/v0.6.0...v0.6.1) (2024-10-02) + + +### Bug Fixes + +* httpx minimum version update ([#150](https://github.com/supabase/functions-py/issues/150)) ([e784961](https://github.com/supabase/functions-py/commit/e7849619532c65f8d02908bc09b4abc60315b213)) + +## [0.6.0](https://github.com/supabase/functions-py/compare/v0.5.1...v0.6.0) (2024-09-25) + + +### Features + +* Proxy support ([#148](https://github.com/supabase/functions-py/issues/148)) ([7710a3f](https://github.com/supabase/functions-py/commit/7710a3f1068d44ecc21aac48c9f1f9a349fe8968)) + +## v0.5.1 (2024-07-25) + +### Chore + +* chore(deps): bump python-semantic-release/python-semantic-release from 9.8.5 to 9.8.6 (#125) ([`8db273a`](https://github.com/supabase-community/functions-py/commit/8db273add3dd634667171e575143bc5a2e27faa7)) + +* chore(deps-dev): bump python-semantic-release from 9.8.5 to 9.8.6 (#124) ([`3b122ca`](https://github.com/supabase-community/functions-py/commit/3b122cab749abe862f5b4174cb70bd1e7f3d7ce8)) + +* chore(deps-dev): bump pytest from 8.2.2 to 8.3.1 (#123) ([`e4c53f2`](https://github.com/supabase-community/functions-py/commit/e4c53f2421e17d3891b4ab3d57aa991da0039a86)) + +### Fix + +* fix: add x-region support (#126) ([`59135d8`](https://github.com/supabase-community/functions-py/commit/59135d8de63030837a2730dd2631da746386b8fe)) + +## v0.5.0 (2024-07-21) + +### Chore + +* chore(release): bump version to v0.5.0 ([`8496b3e`](https://github.com/supabase-community/functions-py/commit/8496b3e0d8eb0cbd7d4d9663b864496abcda5e0e)) + +* chore(deps-dev): bump pytest-asyncio from 0.23.7 to 0.23.8 (#122) ([`e1d064b`](https://github.com/supabase-community/functions-py/commit/e1d064b75fc828cdd3972ab64e65b9868043246c)) + +### Feature + +* feat: add edge functions timeout (#120) ([`d0abc3c`](https://github.com/supabase-community/functions-py/commit/d0abc3c6a03ce0b2347379b0d7ffdc9f4d37b287)) + +## v0.4.7 (2024-07-14) + +### Chore + +* chore(release): bump version to v0.4.7 ([`9d200a9`](https://github.com/supabase-community/functions-py/commit/9d200a97bf89c296e975d4d63aa15b12be6b646a)) + +* chore(deps-dev): bump python-semantic-release from 9.8.3 to 9.8.5 (#119) ([`d3e1104`](https://github.com/supabase-community/functions-py/commit/d3e1104f221ce7274789856b5a3704f8aa25e60f)) + +* chore(deps): bump python-semantic-release/python-semantic-release from 9.8.3 to 9.8.5 (#118) ([`0403bfb`](https://github.com/supabase-community/functions-py/commit/0403bfb84fb9e624974c16880f61a77cc244ff17)) + +* chore(deps-dev): bump python-semantic-release from 9.8.1 to 9.8.3 (#114) ([`3850c82`](https://github.com/supabase-community/functions-py/commit/3850c82add00a26b5e1a74fda7ce750bae85699a)) + +* chore(deps): bump python-semantic-release/python-semantic-release from 9.8.1 to 9.8.3 (#113) ([`c1c0d67`](https://github.com/supabase-community/functions-py/commit/c1c0d67881273f68689959b9858767aa4372c4ef)) + +### Fix + +* fix: version bump (#121) ([`8f9b380`](https://github.com/supabase-community/functions-py/commit/8f9b3802759eec255f3f096ed818d64cd1ff3596)) + +### Unknown + +* Enable HTTP2 (#115) ([`dbe0c73`](https://github.com/supabase-community/functions-py/commit/dbe0c73f025608adb0de1cb7b269de1eae23241d)) + +## v0.4.6 (2024-06-05) + +### Chore + +* chore(release): bump version to v0.4.6 ([`f038cad`](https://github.com/supabase-community/functions-py/commit/f038cad35cbeaafbef9a26e4c853237a994ac9c6)) + +* chore(deps-dev): bump python-semantic-release from 9.8.0 to 9.8.1 (#110) ([`6167b4a`](https://github.com/supabase-community/functions-py/commit/6167b4a894b45062009ad67feefff16a1ba3ff61)) + +* chore(deps-dev): bump pytest from 8.2.1 to 8.2.2 (#109) ([`9d83fc0`](https://github.com/supabase-community/functions-py/commit/9d83fc0ace5c5a9310a5c3878a2db3586fd97899)) + +* chore(deps-dev): bump python-semantic-release from 9.7.3 to 9.8.0 (#104) ([`95f4c8e`](https://github.com/supabase-community/functions-py/commit/95f4c8ea317b62548dc196fdc81308b843e09f23)) + +* chore(deps-dev): bump pytest from 8.2.0 to 8.2.1 (#103) ([`b9523b8`](https://github.com/supabase-community/functions-py/commit/b9523b82b60b9e205077db31d7838af0dba3ea80)) + +* chore(deps-dev): bump pytest-asyncio from 0.23.6 to 0.23.7 (#102) ([`cee6c49`](https://github.com/supabase-community/functions-py/commit/cee6c49bdd155bbeab32a69977d5049148bcea5b)) + +* chore(deps): bump python-semantic-release/python-semantic-release from 9.7.2 to 9.7.3 (#101) ([`556f667`](https://github.com/supabase-community/functions-py/commit/556f667e189b787d58895b0e74c71ad60e0b3d2f)) + +* chore(deps-dev): bump python-semantic-release from 9.7.2 to 9.7.3 (#100) ([`0a7f65f`](https://github.com/supabase-community/functions-py/commit/0a7f65fbb4ad9f0fb6fc2d1b07538775d72fbc7a)) + +* chore(deps): bump python-semantic-release/python-semantic-release from 9.5.0 to 9.7.2 (#99) ([`730f067`](https://github.com/supabase-community/functions-py/commit/730f067030d047fb8675b366bc428930f06aa4f1)) + +* chore(deps-dev): bump python-semantic-release from 9.5.0 to 9.7.2 (#98) ([`6b392ac`](https://github.com/supabase-community/functions-py/commit/6b392aca65d47f215a3f51ff840d15e951da2cae)) + +* chore(deps-dev): bump pytest from 8.1.1 to 8.2.0 (#89) ([`b0f272b`](https://github.com/supabase-community/functions-py/commit/b0f272b7412daee3b23395c9f1363e5290e06e30)) + +* chore(deps-dev): bump black from 24.3.0 to 24.4.2 (#88) ([`b7944de`](https://github.com/supabase-community/functions-py/commit/b7944decb2584a519941ed53e8786754ac6765d8)) + +* chore(ci): bump python-semantic-release/python-semantic-release from 9.4.1 to 9.5.0 (#86) ([`fbf01fe`](https://github.com/supabase-community/functions-py/commit/fbf01fe6ff2060152168b96083630d182a4f75b6)) + +* chore(deps-dev): bump python-semantic-release from 9.4.1 to 9.5.0 (#85) ([`53653e0`](https://github.com/supabase-community/functions-py/commit/53653e027c31e2aecb686c06f188bd3914bb417d)) + +* chore(deps-dev): bump python-semantic-release from 9.3.1 to 9.4.1 (#81) ([`42b8b95`](https://github.com/supabase-community/functions-py/commit/42b8b9577060dd445a6f7de5fb34e135084e450a)) + +* chore(deps): bump python-semantic-release/python-semantic-release from 9.3.1 to 9.4.1 (#80) ([`9d7c165`](https://github.com/supabase-community/functions-py/commit/9d7c165c9b0f3b495b35d6e2d7f2942f8f056f16)) + +* chore(deps-dev): bump respx from 0.21.0 to 0.21.1 (#77) ([`0cf2183`](https://github.com/supabase-community/functions-py/commit/0cf2183efb19ce6898bcdd7d4163534578944996)) + +* chore(deps-dev): bump python-semantic-release from 9.3.0 to 9.3.1 (#76) ([`98ff552`](https://github.com/supabase-community/functions-py/commit/98ff5526ba1fd5b2abbb5fac3c10dba94864b4a6)) + +* chore(deps-dev): bump pytest-cov from 4.1.0 to 5.0.0 (#75) ([`519fa30`](https://github.com/supabase-community/functions-py/commit/519fa3060f78ee1e3ba13dbf89fc49a5155a0710)) + +* chore(deps): bump actions/cache from 3 to 4 (#74) ([`9f61fda`](https://github.com/supabase-community/functions-py/commit/9f61fdadc006ac7a390a1890cbf270e8eecf1c67)) + +* chore(deps): bump abatilo/actions-poetry from 2 to 3 (#73) ([`e521357`](https://github.com/supabase-community/functions-py/commit/e5213574c32f87c1e47c647c86593e1d59c408c8)) + +* chore(deps): bump python-semantic-release/python-semantic-release from 9.3.0 to 9.3.1 (#72) + +Signed-off-by: dependabot[bot] <support@github.com> +Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`a0a9ad8`](https://github.com/supabase-community/functions-py/commit/a0a9ad8959625772b5e63ebecee96eb783e15d12)) + +### Ci + +* ci(deps): bump python-semantic-release/python-semantic-release from 9.8.0 to 9.8.1 (#108) ([`1e162de`](https://github.com/supabase-community/functions-py/commit/1e162deaedd96e286d74d7cd86cbe66ad0add834)) + +* ci(deps): bump python-semantic-release/python-semantic-release from 9.7.3 to 9.8.0 (#105) ([`66c3c1a`](https://github.com/supabase-community/functions-py/commit/66c3c1ad682fa0054ad3520cee9499d496923b03)) + +### Fix + +* fix: add "verify" flag to the creation of client ([`f43038c`](https://github.com/supabase-community/functions-py/commit/f43038cf59d772a47420f8ae6cb3ca81b2808193)) + +### Unknown + +* Follow redirects (#107) ([`ccb3f7f`](https://github.com/supabase-community/functions-py/commit/ccb3f7fa81e0e1257e6551a483900083b173135c)) + +* Update .pre-commit-config.yaml (#93) ([`3201e5d`](https://github.com/supabase-community/functions-py/commit/3201e5d9e68c1e580ea5e61028b08dde4ba3123e)) + +* Add stale bot (#92) ([`1fb54a8`](https://github.com/supabase-community/functions-py/commit/1fb54a80b211c93a8b7b8118842ab411dc33e22b)) + +## v0.4.5 (2024-03-23) + +### Chore + +* chore(release): bump version to v0.4.5 ([`d2b7efb`](https://github.com/supabase-community/functions-py/commit/d2b7efb158baf22a37d0652614c6aaee43fa389a)) + +### Fix + +* fix: configure poetry in github action (#71) ([`886f0fb`](https://github.com/supabase-community/functions-py/commit/886f0fb1590567527159df8944a9ab7d418e5f00)) + +## v0.4.4 (2024-03-23) + +### Chore + +* chore(release): bump version to v0.4.4 ([`ebb987f`](https://github.com/supabase-community/functions-py/commit/ebb987fc0df58d41a0307065d28ae14b6697cb3b)) + +### Fix + +* fix: update to perform build via poetry (#70) ([`d518ce5`](https://github.com/supabase-community/functions-py/commit/d518ce5b81fef2ec30907c9261fde29522e37222)) + +## v0.4.3 (2024-03-23) + +### Chore + +* chore(release): bump version to v0.4.3 ([`5c707e9`](https://github.com/supabase-community/functions-py/commit/5c707e998df07afdd67c629f034e954564a1c65b)) + +### Fix + +* fix: add supafunc package distribution (#69) ([`d8a6f9a`](https://github.com/supabase-community/functions-py/commit/d8a6f9a89909c5d4bc3d6c11fb369407931c9cc5)) + +## v0.4.2 (2024-03-23) + +### Chore + +* chore(release): bump version to v0.4.2 ([`be38b0b`](https://github.com/supabase-community/functions-py/commit/be38b0b38ce878df629b1170b69ccbaf5e54ba03)) + +### Fix + +* fix: ci workflow (#68) ([`a747a72`](https://github.com/supabase-community/functions-py/commit/a747a729b35d31197d2b772d2f5ddc9e5ec9daed)) + +## v0.4.1 (2024-03-23) + +### Chore + +* chore(release): bump version to v0.4.1 ([`f9a9a2f`](https://github.com/supabase-community/functions-py/commit/f9a9a2feede1cec36d9a9c9dc35d64594d4f1f84)) + +* chore(deps-dev): bump python-semantic-release from 9.2.0 to 9.3.0 (#66) + +Signed-off-by: dependabot[bot] <support@github.com> +Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`7d1ce46`](https://github.com/supabase-community/functions-py/commit/7d1ce467159fa08d8cde29056d52553b186319ce)) + +* chore(deps): bump codecov/codecov-action from 3 to 4 (#50) + +Signed-off-by: dependabot[bot] <support@github.com> +Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`ffe43b2`](https://github.com/supabase-community/functions-py/commit/ffe43b2ded9396a893d1c12d9a0077b70aed52df)) + +* chore(deps): bump actions/checkout from 2 to 4 (#52) + +Signed-off-by: dependabot[bot] <support@github.com> +Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`a809e98`](https://github.com/supabase-community/functions-py/commit/a809e98368b1ff8369c787439a5ba2d01a0ebfcf)) + +* chore(deps): bump abatilo/actions-poetry from 2.2.0 to 3.0.0 (#54) + +Signed-off-by: dependabot[bot] <support@github.com> +Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`4c1365c`](https://github.com/supabase-community/functions-py/commit/4c1365cbb94502cad0bc14032bbf48c74de1971f)) + +* chore(deps): bump python-semantic-release/python-semantic-release from 8.0.0 to 9.3.0 (#65) + +Signed-off-by: dependabot[bot] <support@github.com> +Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`cddbd1f`](https://github.com/supabase-community/functions-py/commit/cddbd1fe983e600ec4f9cf177a9b5e1f582a7ff0)) + +* chore(deps-dev): bump pytest from 8.0.2 to 8.1.1 (#57) + +Signed-off-by: dependabot[bot] <support@github.com> +Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`43a2741`](https://github.com/supabase-community/functions-py/commit/43a27418526d2cdd8739ad4f4c69e21a6743d43e)) + +* chore(deps-dev): bump respx from 0.20.2 to 0.21.0 (#61) + +Signed-off-by: dependabot[bot] <support@github.com> +Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`a1481fd`](https://github.com/supabase-community/functions-py/commit/a1481fd126b10719bd92061012a9efba0354530f)) + +* chore(deps-dev): bump pytest-asyncio from 0.23.5 to 0.23.6 (#62) + +Signed-off-by: dependabot[bot] <support@github.com> +Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`a821fe8`](https://github.com/supabase-community/functions-py/commit/a821fe80e5f2f2a68395e86ecb782856f1d18642)) + +* chore(deps-dev): bump python-semantic-release from 9.1.1 to 9.2.0 (#60) + +Signed-off-by: dependabot[bot] <support@github.com> +Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`719ed64`](https://github.com/supabase-community/functions-py/commit/719ed64f587c40bd2c98255d34c36788e1ccae1b)) + +* chore(deps): bump actions/setup-python from 2 to 5 (#53) + +Signed-off-by: dependabot[bot] <support@github.com> +Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`867bab3`](https://github.com/supabase-community/functions-py/commit/867bab3a99197da2a7437b903b52afc7a4e5d0a1)) + +* chore(deps-dev): bump black from 24.2.0 to 24.3.0 (#58) + +Signed-off-by: dependabot[bot] <support@github.com> +Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`0a84c99`](https://github.com/supabase-community/functions-py/commit/0a84c9951bc5ba78d218599bcd1d15a32909fdf9)) + +* chore(deps-dev): bump pytest from 7.4.4 to 8.0.2 (#48) + +Signed-off-by: dependabot[bot] <support@github.com> +Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`ae754cf`](https://github.com/supabase-community/functions-py/commit/ae754cfb4bc7e8832a7aac95f0d29077b2b7ad80)) + +* chore(deps-dev): bump python-semantic-release from 8.7.0 to 9.1.1 (#47) + +Signed-off-by: dependabot[bot] <support@github.com> +Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`f108ed7`](https://github.com/supabase-community/functions-py/commit/f108ed7691a2bd0801b946e96c7c16cec234972b)) + +* chore(deps-dev): bump black from 23.12.1 to 24.2.0 (#43) + +Signed-off-by: dependabot[bot] <support@github.com> +Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`ed09227`](https://github.com/supabase-community/functions-py/commit/ed092279ad8911f2664a92c0c1e332b3f42e4555)) + +* chore(deps-dev): bump pytest-asyncio from 0.23.3 to 0.23.5 (#42) + +Signed-off-by: dependabot[bot] <support@github.com> +Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`e87489b`](https://github.com/supabase-community/functions-py/commit/e87489b98c7fab52417e9b1f81a2fd4a83caf627)) + +* chore: rename package to supabase_functions (#37) ([`5b5b5c9`](https://github.com/supabase-community/functions-py/commit/5b5b5c9071d74b7ec3e3da0dd83bbd6bd9303152)) + +### Fix + +* fix: Update library name in pyproject file (#67) ([`5b5fe0d`](https://github.com/supabase-community/functions-py/commit/5b5fe0dcdfd81b1e91da47d49d6aecaad0505a3d)) + +* fix: bump httpx from 0.25.2 to 0.27.0 (#46) + +Signed-off-by: dependabot[bot] <support@github.com> +Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`03fcb25`](https://github.com/supabase-community/functions-py/commit/03fcb25d5fdca5ee07edac2ba769d797f54e1dc7)) + +## v0.4.0 (2024-02-29) + +### Chore + +* chore(release): bump version to v0.4.0 ([`555df53`](https://github.com/supabase-community/functions-py/commit/555df5384d2c8c8de7565f609f231923c118cd8f)) + +### Feature + +* feat: add actions to dependabot.yml (#49) ([`0fa1c6b`](https://github.com/supabase-community/functions-py/commit/0fa1c6b91363714a0f889d73e52a47ca2e5be349)) + +## v0.3.3 (2024-01-03) + +### Chore + +* chore(release): bump version to v0.3.3 ([`cd02bb2`](https://github.com/supabase-community/functions-py/commit/cd02bb26e501adb6c66007f637af0ba87b37a6f0)) + +### Fix + +* fix: update job to publish legacy package if current is released (#36) ([`2565c37`](https://github.com/supabase-community/functions-py/commit/2565c372124c08bc2a0bd8fd4b3005cf427062e3)) + +## v0.3.2 (2024-01-03) + +### Chore + +* chore(release): bump version to v0.3.2 ([`403418c`](https://github.com/supabase-community/functions-py/commit/403418cc3a801be12e73f84e53daddd517cd5de0)) + +* chore(deps-dev): bump isort from 5.12.0 to 5.13.0 (#24) ([`e7443ee`](https://github.com/supabase-community/functions-py/commit/e7443eeaad029a19a4276bae8ebfb899d042be3a)) + +* chore(deps-dev): bump isort from 5.12.0 to 5.13.0 + +Bumps [isort](https://github.com/pycqa/isort) from 5.12.0 to 5.13.0. +- [Release notes](https://github.com/pycqa/isort/releases) +- [Changelog](https://github.com/PyCQA/isort/blob/main/CHANGELOG.md) +- [Commits](https://github.com/pycqa/isort/compare/5.12.0...5.13.0) + +--- +updated-dependencies: +- dependency-name: isort + dependency-type: direct:development + update-type: version-update:semver-minor +... + +Signed-off-by: dependabot[bot] <support@github.com> ([`89a31c9`](https://github.com/supabase-community/functions-py/commit/89a31c9afb987063f18dd853bc826e4d7e815be3)) + +* chore(deps-dev): bump pytest-asyncio from 0.21.1 to 0.23.2 (#22) ([`ab767f5`](https://github.com/supabase-community/functions-py/commit/ab767f5cf591679f38404cf609a42d853620c96f)) + +* chore(deps-dev): bump pytest-asyncio from 0.21.1 to 0.23.2 + +Bumps [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) from 0.21.1 to 0.23.2. +- [Release notes](https://github.com/pytest-dev/pytest-asyncio/releases) +- [Commits](https://github.com/pytest-dev/pytest-asyncio/compare/v0.21.1...v0.23.2) + +--- +updated-dependencies: +- dependency-name: pytest-asyncio + dependency-type: direct:development + update-type: version-update:semver-minor +... + +Signed-off-by: dependabot[bot] <support@github.com> ([`f6fb590`](https://github.com/supabase-community/functions-py/commit/f6fb5906e99e5194413ef8b7f819ba82ae47355e)) + +* chore: allow manual workflow trigger for releases (#19) ([`07d1ffa`](https://github.com/supabase-community/functions-py/commit/07d1ffa6faa89219f7c73cbd4699436763d9d8bc)) + +* chore: allow manual workflow trigger for releases ([`2a01399`](https://github.com/supabase-community/functions-py/commit/2a013997215f417fb0efe2badfc9b8a2d3686c48)) + +### Ci + +* ci: update workflow with new pypi project name (#34) ([`7564e2b`](https://github.com/supabase-community/functions-py/commit/7564e2bc1d157a279175a3c8ad6fb2708e1700f4)) + +### Fix + +* fix: update httpx and other dev dependencies (#35) ([`1f8897f`](https://github.com/supabase-community/functions-py/commit/1f8897f88acc4449cd697bd0e122bd4ee3bf0417)) + +## v0.3.1 (2023-10-30) + +### Chore + +* chore(release): bump version to v0.3.1 ([`b787f01`](https://github.com/supabase-community/functions-py/commit/b787f0187c1a5312ea368919afd24863ff2f40f0)) + +### Fix + +* fix: exceptions now has message in dictionary (#16) ([`7273927`](https://github.com/supabase-community/functions-py/commit/7273927aa9d0e6eb9d9c9985a7ba5b42f9b6296d)) + +* fix: exceptions now has message in dictionary + +Added tests to check for the messages. ([`07a813a`](https://github.com/supabase-community/functions-py/commit/07a813a02ffcf8999802cece27ee5278c140760d)) + +## v0.3.0 (2023-10-29) + +### Chore + +* chore(release): bump version to v0.3.0 ([`4e18712`](https://github.com/supabase-community/functions-py/commit/4e1871215e72efed058d5adf619ae2be0bb27b56)) + +### Feature + +* feat: downgrade httpx dep to 0.24.0 (#15) ([`1f37216`](https://github.com/supabase-community/functions-py/commit/1f37216326c26b65a3c9ccd1c29bea0a184c7624)) + +### Fix + +* fix: update lockfile ([`d4856ec`](https://github.com/supabase-community/functions-py/commit/d4856ec8c6bbbde7efe7ca67c5137ba75e8e7bdb)) + +### Unknown + +* Update pyproject.toml ([`dd43949`](https://github.com/supabase-community/functions-py/commit/dd4394994ae995dd6f953093da73cbd9c1344483)) + +* Restoring order to the CI/CD pipeline ([`4f28dc6`](https://github.com/supabase-community/functions-py/commit/4f28dc628c9a9aac27a153121c90960bddb5c8bf)) + +## v0.2.4 (2023-10-25) + +### Chore + +* chore(release): bump version to v0.2.4 ([`f618547`](https://github.com/supabase-community/functions-py/commit/f61854760d2d90d1352962e427d099da6dac50c1)) + +* chore: update readme with correct function call ([`88fc1a7`](https://github.com/supabase-community/functions-py/commit/88fc1a797ef7d848bd2e870ddadcf8d51d405989)) + +* chore(release): bump version to v0.2.4 ([`e958722`](https://github.com/supabase-community/functions-py/commit/e95872200a7470da0e92bd95431eea1e20c66df3)) + +* chore: bump autoflake version ([`fc3a7bb`](https://github.com/supabase-community/functions-py/commit/fc3a7bb5788feca7acbdf4662feee7cce87f2cda)) + +### Fix + +* fix: correct return type from invoke ([`9a15026`](https://github.com/supabase-community/functions-py/commit/9a15026bbbc63cd4b6d960f8a48db40a06770381)) + +* fix: add single instance of client instantiation ([`4b8a134`](https://github.com/supabase-community/functions-py/commit/4b8a134ac675bdcc0387cb1d1d55068e1b6be253)) + +### Unknown + +* Temporary CI change to allow publishing of package ([`2d66f21`](https://github.com/supabase-community/functions-py/commit/2d66f21c01efcfd3ba34ab458c11977009580118)) + +* Merge pull request #11 from supabase-community/silentworks/update-readme + +chore: update readme with correct function call ([`1dfe72e`](https://github.com/supabase-community/functions-py/commit/1dfe72eb28c35b0452e8f65d6e9612f9d4e80eb3)) + +* Merge pull request #9 from supabase-community/silentworks/sync_functions + +Add sync functions ([`e2db242`](https://github.com/supabase-community/functions-py/commit/e2db242994c66ce3beec399416512342a2266f85)) + +* update file formatting with black ([`b1c64f5`](https://github.com/supabase-community/functions-py/commit/b1c64f51a487e9782c45324d0903a5e18c7bd31e)) + +* Apply suggestions from code review + +Co-authored-by: Anand <40204976+anand2312@users.noreply.github.com> ([`e2a59aa`](https://github.com/supabase-community/functions-py/commit/e2a59aaff604a8c0ff1e3d648a1d2ae3aff44ea7)) + +* Add github workflow ([`14c8db9`](https://github.com/supabase-community/functions-py/commit/14c8db932527056343e5e7af012db87af6242006)) + +* Update errors and function signature of invoke ([`575da96`](https://github.com/supabase-community/functions-py/commit/575da968238a494de0996226df0bf54a48bf4e2b)) + +* Apply suggestions from code review + +Co-authored-by: Anand <40204976+anand2312@users.noreply.github.com> ([`b60615d`](https://github.com/supabase-community/functions-py/commit/b60615d2c70c5a2e32e87f850d5fe1d68e492f59)) + +* Update supafunc/errors.py + +Co-authored-by: Anand <40204976+anand2312@users.noreply.github.com> ([`2971673`](https://github.com/supabase-community/functions-py/commit/2971673451c4775c3f5e400983972bebefff4dfe)) + +* Add sync functions +Add pytests +Throw errors instead of returning them ([`692022f`](https://github.com/supabase-community/functions-py/commit/692022fa4816de5ec3e4cd929352535af719bb87)) + +## v0.2.3 (2023-08-04) + +### Chore + +* chore: bump version ([`d5f32ba`](https://github.com/supabase-community/functions-py/commit/d5f32ba75368cc1ba337e1cda0e6d89d426160b1)) + +* chore: bump httpx to 0.24 ([`6152992`](https://github.com/supabase-community/functions-py/commit/615299278b1d810c1113546938b81eabf075987f)) + +* chore(deps): bump httpx from 0.23.0 to 0.24.1 + +Bumps [httpx](https://github.com/encode/httpx) from 0.23.0 to 0.24.1. +- [Release notes](https://github.com/encode/httpx/releases) +- [Changelog](https://github.com/encode/httpx/blob/master/CHANGELOG.md) +- [Commits](https://github.com/encode/httpx/compare/0.23.0...0.24.1) + +--- +updated-dependencies: +- dependency-name: httpx + dependency-type: direct:production + update-type: version-update:semver-minor +... + +Signed-off-by: dependabot[bot] <support@github.com> ([`f5c5ca4`](https://github.com/supabase-community/functions-py/commit/f5c5ca44de7d130be5ba2da0671ae8a845ad4d0d)) + +### Fix + +* fix: small typo in authorization + +for consistent naming ([`3f0bba8`](https://github.com/supabase-community/functions-py/commit/3f0bba80100f86886f4e8132862fc1e96868e479)) + +### Unknown + +* Merge pull request #4 from anand2312/annad/bump-httpx + +chore: bump httpx to 0.24 ([`f9d1c3c`](https://github.com/supabase-community/functions-py/commit/f9d1c3c6f3611f322d336bbd86f080c0d65f6d28)) + +* Merge pull request #3 from supabase-community/dependabot/pip/main/httpx-0.24.1 + +chore(deps): bump httpx from 0.23.0 to 0.24.1 ([`67ada27`](https://github.com/supabase-community/functions-py/commit/67ada272a6ea645b9b51041e6dedd829d3410113)) + +* Create dependabot.yml ([`c7394d7`](https://github.com/supabase-community/functions-py/commit/c7394d7691b6ed35997f6222fe8f37748e132242)) + +* Merge pull request #2 from 0xflotus/patch-1 + +fix: small typo in authorization ([`7c60eda`](https://github.com/supabase-community/functions-py/commit/7c60eda605337784a63a99a1405a6cb2c5f407f1)) + +## v0.2.2 (2022-10-10) + +### Chore + +* chore: update version ([`a6584a7`](https://github.com/supabase-community/functions-py/commit/a6584a783ed9fea347f89c87f420ba4d56e0383a)) + +### Feature + +* feat: version 0.1.4 ([`7ffeec5`](https://github.com/supabase-community/functions-py/commit/7ffeec5465ce86f7ee077dbf18c21f332f31b1a5)) + +### Fix + +* fix: update dependencies ([`91bc97b`](https://github.com/supabase-community/functions-py/commit/91bc97b66ef609618bb953a6557a2eb904b35d00)) + +* fix: add default for optional headers ([`02c838c`](https://github.com/supabase-community/functions-py/commit/02c838c73b692ea16912192278dd8550570553a1)) + +* fix: pass down body ([`d3f9f61`](https://github.com/supabase-community/functions-py/commit/d3f9f6187b7bd7206f89eb3331fa6ea6f13dd58e)) + +## v0.1.4 (2022-03-31) + +### Chore + +* chore: update version ([`883661f`](https://github.com/supabase-community/functions-py/commit/883661f3c50d8d5fabcbf9639daaf7e6b8fa2499)) + +* chore: update version ([`61f78b4`](https://github.com/supabase-community/functions-py/commit/61f78b4986917a234e631a233d72f65b28b414a4)) + +* chore: cleanup and add LICENSE ([`c9d035e`](https://github.com/supabase-community/functions-py/commit/c9d035eb4005ef9b595206395513abaca8325953)) + +* chore: rename and update README ([`0e26f92`](https://github.com/supabase-community/functions-py/commit/0e26f92f27079d6ab63da6860f6a26d229be2374)) + +### Unknown + +* initial commit ([`5c93da6`](https://github.com/supabase-community/functions-py/commit/5c93da6948288c3312c0065e22ab968d25b9801b)) diff --git a/src/functions/Makefile b/src/functions/Makefile new file mode 100644 index 00000000..53bb6d25 --- /dev/null +++ b/src/functions/Makefile @@ -0,0 +1,12 @@ +tests: pytest + +pytest: + uv run --package supabase_functions pytest --cov=./ --cov-report=xml --cov-report=html -vv + +unasync: + uv run --package supabase_functions run-unasync.py + +build-sync: unasync + sed -i '0,/SyncMock, /{s/SyncMock, //}' tests/_sync/test_function_client.py + sed -i 's/SyncMock/Mock/g' tests/_sync/test_function_client.py + sed -i 's/SyncClient/Client/g' src/supabase_functions/_sync/functions_client.py tests/_sync/test_function_client.py diff --git a/src/functions/README.md b/src/functions/README.md new file mode 100644 index 00000000..38e15a41 --- /dev/null +++ b/src/functions/README.md @@ -0,0 +1,22 @@ +# Functions-py + + +## Installation + +`pip3 install supabase_functions` + +## Usage + +Deploy your function as per documentation. + + +```python3 +import asyncio +from supabase_functions import AsyncFunctionsClient +async def run_func(): + fc = AsyncFunctionsClient("https://.functions.supabase.co", {}) + res = await fc.invoke("payment-sheet", {"responseType": "json"}) + +if __name__ == "__main__": + asyncio.run(run_func()) +``` diff --git a/src/functions/conftest.py b/src/functions/conftest.py new file mode 100644 index 00000000..79b61d44 --- /dev/null +++ b/src/functions/conftest.py @@ -0,0 +1,51 @@ +from typing import Dict, Tuple + +import pytest + +# store history of failures per test class name and per index +# in parametrize (if parametrize used) +_test_failed_incremental: Dict[str, Dict[Tuple[int, ...], str]] = {} + + +def pytest_runtest_makereport(item, call): + if "incremental" in item.keywords: + # incremental marker is used + if call.excinfo is not None: + # the test has failed + # retrieve the class name of the test + cls_name = str(item.cls) + # retrieve the index of the test (if parametrize is used + # in combination with incremental) + parametrize_index = ( + tuple(item.callspec.indices.values()) + if hasattr(item, "callspec") + else () + ) + # retrieve the name of the test function + test_name = item.originalname or item.name + # store in _test_failed_incremental the original name of the failed test + _test_failed_incremental.setdefault(cls_name, {}).setdefault( + parametrize_index, test_name + ) + + +def pytest_runtest_setup(item): + if "incremental" in item.keywords: + # retrieve the class name of the test + cls_name = str(item.cls) + # check if a previous test has failed for this class + if cls_name in _test_failed_incremental: + # retrieve the index of the test (if parametrize is used + # in combination with incremental) + parametrize_index = ( + tuple(item.callspec.indices.values()) + if hasattr(item, "callspec") + else () + ) + # retrieve the name of the first test function to + # fail for this class name and index + test_name = _test_failed_incremental[cls_name].get(parametrize_index, None) + # if name found, test has failed for the combination of + # class name & test name + if test_name is not None: + pytest.xfail(f"previous test failed ({test_name})") diff --git a/src/functions/pyproject.toml b/src/functions/pyproject.toml new file mode 100644 index 00000000..e2f399d5 --- /dev/null +++ b/src/functions/pyproject.toml @@ -0,0 +1,44 @@ +[project] +name = "supabase_functions" +version = "0.10.1" # {x-release-please-version} +description = "Library for Supabase Functions" +authors = [ + { name = "Joel Lee", email = "joel@joellee.org" }, + { name = "Andrew Smith", email = "a.smith@silentworks.co.uk" }, +] +license = "MIT" +readme = "README.md" +repository = "https://github.com/supabase/supabase-py" +requires-python = ">=3.9" +dependencies = [ + "httpx[http2] >=0.26,<0.29", + "strenum >=0.4.15", +] + +[dependency-groups] +tests = [ + "pyjwt >=2.8.0", + "pytest >=7.4.2,<9.0.0", + "pytest-cov >=4,<7", + "pytest-asyncio >=0.21.1,<1.2.0", +] +lints = [ + "unasync>=0.6.0", + "ruff >=0.12.1", + "pre-commit >=3.4,<5.0" +] +dev = [{ include-group = "lints" }, {include-group = "tests" }] + +[tool.uv] +default-groups = [ "dev" ] + +[tool.pytest.ini_options] +asyncio_mode = "auto" +addopts = "tests" +filterwarnings = [ + "ignore::DeprecationWarning", # ignore deprecation warnings globally +] + +[build-system] +requires = ["uv_build>=0.8.3,<0.9.0"] +build-backend = "uv_build" diff --git a/src/functions/run-unasync.py b/src/functions/run-unasync.py new file mode 100644 index 00000000..0d7c3fa1 --- /dev/null +++ b/src/functions/run-unasync.py @@ -0,0 +1,12 @@ +import unasync +from pathlib import Path + +paths = Path("src/functions").glob("**/*.py") +tests = Path("tests").glob("**/*.py") + +rules = (unasync._DEFAULT_RULE,) + +files = [str(p) for p in list(paths) + list(tests)] + +if __name__ == "__main__": + unasync.unasync_files(files, rules=rules) diff --git a/src/functions/src/supabase_functions/__init__.py b/src/functions/src/supabase_functions/__init__.py new file mode 100644 index 00000000..7faa9521 --- /dev/null +++ b/src/functions/src/supabase_functions/__init__.py @@ -0,0 +1,39 @@ +from __future__ import annotations + +from typing import Literal, Union, overload + +from ._async.functions_client import AsyncFunctionsClient +from ._sync.functions_client import SyncFunctionsClient +from .utils import FunctionRegion + +__all__ = [ + "create_client", + "FunctionRegion", + "AsyncFunctionsClient", + "SyncFunctionsClient", +] + + +@overload +def create_client( + url: str, headers: dict[str, str], *, is_async: Literal[True], verify: bool +) -> AsyncFunctionsClient: ... + + +@overload +def create_client( + url: str, headers: dict[str, str], *, is_async: Literal[False], verify: bool +) -> SyncFunctionsClient: ... + + +def create_client( + url: str, + headers: dict[str, str], + *, + is_async: bool, + verify: bool = True, +) -> Union[AsyncFunctionsClient, SyncFunctionsClient]: + if is_async: + return AsyncFunctionsClient(url, headers, verify) + else: + return SyncFunctionsClient(url, headers, verify) diff --git a/src/functions/src/supabase_functions/_async/__init__.py b/src/functions/src/supabase_functions/_async/__init__.py new file mode 100644 index 00000000..9d48db4f --- /dev/null +++ b/src/functions/src/supabase_functions/_async/__init__.py @@ -0,0 +1 @@ +from __future__ import annotations diff --git a/src/functions/src/supabase_functions/_async/functions_client.py b/src/functions/src/supabase_functions/_async/functions_client.py new file mode 100644 index 00000000..0ca6d23a --- /dev/null +++ b/src/functions/src/supabase_functions/_async/functions_client.py @@ -0,0 +1,155 @@ +from typing import Any, Dict, Literal, Optional, Union +from warnings import warn + +from httpx import AsyncClient, HTTPError, Response + +from ..errors import FunctionsHttpError, FunctionsRelayError +from ..utils import ( + FunctionRegion, + is_http_url, + is_valid_str_arg, +) +from ..version import __version__ + + +class AsyncFunctionsClient: + def __init__( + self, + url: str, + headers: Dict, + timeout: Optional[int] = None, + verify: Optional[bool] = None, + proxy: Optional[str] = None, + http_client: Optional[AsyncClient] = None, + ): + if not is_http_url(url): + raise ValueError("url must be a valid HTTP URL string") + self.url = url + self.headers = { + "User-Agent": f"supabase-py/functions-py v{__version__}", + **headers, + } + + if timeout is not None: + warn( + "The 'timeout' parameter is deprecated. Please configure it in the http client instead.", + DeprecationWarning, + stacklevel=2, + ) + if verify is not None: + warn( + "The 'verify' parameter is deprecated. Please configure it in the http client instead.", + DeprecationWarning, + stacklevel=2, + ) + if proxy is not None: + warn( + "The 'proxy' parameter is deprecated. Please configure it in the http client instead.", + DeprecationWarning, + stacklevel=2, + ) + + self.verify = bool(verify) if verify is not None else True + self.timeout = int(abs(timeout)) if timeout is not None else 60 + + if http_client is not None: + http_client.base_url = self.url + http_client.headers.update({**self.headers}) + self._client = http_client + else: + self._client = AsyncClient( + base_url=self.url, + headers=self.headers, + verify=self.verify, + timeout=self.timeout, + proxy=proxy, + follow_redirects=True, + http2=True, + ) + + async def _request( + self, + method: Literal["GET", "OPTIONS", "HEAD", "POST", "PUT", "PATCH", "DELETE"], + url: str, + headers: Optional[Dict[str, str]] = None, + json: Optional[Dict[Any, Any]] = None, + ) -> Response: + user_data = {"data": json} if isinstance(json, str) else {"json": json} + response = await self._client.request(method, url, **user_data, headers=headers) + + try: + response.raise_for_status() + except HTTPError as exc: + status_code = None + if hasattr(response, "status_code"): + status_code = response.status_code + + raise FunctionsHttpError( + response.json().get("error") + or f"An error occurred while requesting your edge function at {exc.request.url!r}.", + status_code, + ) from exc + + return response + + def set_auth(self, token: str) -> None: + """Updates the authorization header + + Parameters + ---------- + token : str + the new jwt token sent in the authorization header + """ + + self.headers["Authorization"] = f"Bearer {token}" + + async def invoke( + self, function_name: str, invoke_options: Optional[Dict] = None + ) -> Union[Dict, bytes]: + """Invokes a function + + Parameters + ---------- + function_name : the name of the function to invoke + invoke_options : object with the following properties + `headers`: object representing the headers to send with the request + `body`: the body of the request + `responseType`: how the response should be parsed. The default is `json` + """ + if not is_valid_str_arg(function_name): + raise ValueError("function_name must a valid string value.") + headers = self.headers + body = None + response_type = "text/plain" + if invoke_options is not None: + headers.update(invoke_options.get("headers", {})) + response_type = invoke_options.get("responseType", "text/plain") + + region = invoke_options.get("region") + if region: + if not isinstance(region, FunctionRegion): + warn(f"Use FunctionRegion({region})") + region = FunctionRegion(region) + + if region.value != "any": + headers["x-region"] = region.value + + body = invoke_options.get("body") + if isinstance(body, str): + headers["Content-Type"] = "text/plain" + elif isinstance(body, dict): + headers["Content-Type"] = "application/json" + + response = await self._request( + "POST", f"{self.url}/{function_name}", headers=headers, json=body + ) + is_relay_error = response.headers.get("x-relay-header") + + if is_relay_error and is_relay_error == "true": + raise FunctionsRelayError(response.json().get("error")) + + if response_type == "json": + data = response.json() + else: + data = response.content + return data diff --git a/src/functions/src/supabase_functions/_sync/__init__.py b/src/functions/src/supabase_functions/_sync/__init__.py new file mode 100644 index 00000000..9d48db4f --- /dev/null +++ b/src/functions/src/supabase_functions/_sync/__init__.py @@ -0,0 +1 @@ +from __future__ import annotations diff --git a/src/functions/src/supabase_functions/_sync/functions_client.py b/src/functions/src/supabase_functions/_sync/functions_client.py new file mode 100644 index 00000000..2c50dd9a --- /dev/null +++ b/src/functions/src/supabase_functions/_sync/functions_client.py @@ -0,0 +1,155 @@ +from typing import Any, Dict, Literal, Optional, Union +from warnings import warn + +from httpx import Client, HTTPError, Response + +from ..errors import FunctionsHttpError, FunctionsRelayError +from ..utils import ( + FunctionRegion, + is_http_url, + is_valid_str_arg, +) +from ..version import __version__ + + +class SyncFunctionsClient: + def __init__( + self, + url: str, + headers: Dict, + timeout: Optional[int] = None, + verify: Optional[bool] = None, + proxy: Optional[str] = None, + http_client: Optional[Client] = None, + ): + if not is_http_url(url): + raise ValueError("url must be a valid HTTP URL string") + self.url = url + self.headers = { + "User-Agent": f"supabase-py/functions-py v{__version__}", + **headers, + } + + if timeout is not None: + warn( + "The 'timeout' parameter is deprecated. Please configure it in the http client instead.", + DeprecationWarning, + stacklevel=2, + ) + if verify is not None: + warn( + "The 'verify' parameter is deprecated. Please configure it in the http client instead.", + DeprecationWarning, + stacklevel=2, + ) + if proxy is not None: + warn( + "The 'proxy' parameter is deprecated. Please configure it in the http client instead.", + DeprecationWarning, + stacklevel=2, + ) + + self.verify = bool(verify) if verify is not None else True + self.timeout = int(abs(timeout)) if timeout is not None else 60 + + if http_client is not None: + http_client.base_url = self.url + http_client.headers.update({**self.headers}) + self._client = http_client + else: + self._client = Client( + base_url=self.url, + headers=self.headers, + verify=self.verify, + timeout=self.timeout, + proxy=proxy, + follow_redirects=True, + http2=True, + ) + + def _request( + self, + method: Literal["GET", "OPTIONS", "HEAD", "POST", "PUT", "PATCH", "DELETE"], + url: str, + headers: Optional[Dict[str, str]] = None, + json: Optional[Dict[Any, Any]] = None, + ) -> Response: + user_data = {"data": json} if isinstance(json, str) else {"json": json} + response = self._client.request(method, url, **user_data, headers=headers) + + try: + response.raise_for_status() + except HTTPError as exc: + status_code = None + if hasattr(response, "status_code"): + status_code = response.status_code + + raise FunctionsHttpError( + response.json().get("error") + or f"An error occurred while requesting your edge function at {exc.request.url!r}.", + status_code, + ) from exc + + return response + + def set_auth(self, token: str) -> None: + """Updates the authorization header + + Parameters + ---------- + token : str + the new jwt token sent in the authorization header + """ + + self.headers["Authorization"] = f"Bearer {token}" + + def invoke( + self, function_name: str, invoke_options: Optional[Dict] = None + ) -> Union[Dict, bytes]: + """Invokes a function + + Parameters + ---------- + function_name : the name of the function to invoke + invoke_options : object with the following properties + `headers`: object representing the headers to send with the request + `body`: the body of the request + `responseType`: how the response should be parsed. The default is `json` + """ + if not is_valid_str_arg(function_name): + raise ValueError("function_name must a valid string value.") + headers = self.headers + body = None + response_type = "text/plain" + if invoke_options is not None: + headers.update(invoke_options.get("headers", {})) + response_type = invoke_options.get("responseType", "text/plain") + + region = invoke_options.get("region") + if region: + if not isinstance(region, FunctionRegion): + warn(f"Use FunctionRegion({region})") + region = FunctionRegion(region) + + if region.value != "any": + headers["x-region"] = region.value + + body = invoke_options.get("body") + if isinstance(body, str): + headers["Content-Type"] = "text/plain" + elif isinstance(body, dict): + headers["Content-Type"] = "application/json" + + response = self._request( + "POST", f"{self.url}/{function_name}", headers=headers, json=body + ) + is_relay_error = response.headers.get("x-relay-header") + + if is_relay_error and is_relay_error == "true": + raise FunctionsRelayError(response.json().get("error")) + + if response_type == "json": + data = response.json() + else: + data = response.content + return data diff --git a/src/functions/src/supabase_functions/errors.py b/src/functions/src/supabase_functions/errors.py new file mode 100644 index 00000000..0529e344 --- /dev/null +++ b/src/functions/src/supabase_functions/errors.py @@ -0,0 +1,44 @@ +from __future__ import annotations + +from typing import TypedDict + + +class FunctionsApiErrorDict(TypedDict): + name: str + message: str + status: int + + +class FunctionsError(Exception): + def __init__(self, message: str, name: str, status: int) -> None: + super().__init__(message) + self.message = message + self.name = name + self.status = status + + def to_dict(self) -> FunctionsApiErrorDict: + return { + "name": self.name, + "message": self.message, + "status": self.status, + } + + +class FunctionsHttpError(FunctionsError): + def __init__(self, message: str, code: int | None = None) -> None: + super().__init__( + message, + "FunctionsHttpError", + 400 if code is None else code, + ) + + +class FunctionsRelayError(FunctionsError): + """Base exception for relay errors.""" + + def __init__(self, message: str, code: int | None = None) -> None: + super().__init__( + message, + "FunctionsRelayError", + 400 if code is None else code, + ) diff --git a/src/functions/src/supabase_functions/utils.py b/src/functions/src/supabase_functions/utils.py new file mode 100644 index 00000000..6740c25e --- /dev/null +++ b/src/functions/src/supabase_functions/utils.py @@ -0,0 +1,60 @@ +import sys +from urllib.parse import urlparse +from warnings import warn + +from httpx import AsyncClient as AsyncClient # noqa: F401 +from httpx import Client as BaseClient + +if sys.version_info >= (3, 11): + from enum import StrEnum +else: + from strenum import StrEnum + + +DEFAULT_FUNCTION_CLIENT_TIMEOUT = 5 +BASE64URL_REGEX = r"^([a-z0-9_-]{4})*($|[a-z0-9_-]{3}$|[a-z0-9_-]{2}$)$" + + +class FunctionRegion(StrEnum): + Any = "any" + ApNortheast1 = "ap-northeast-1" + ApNortheast2 = "ap-northeast-2" + ApSouth1 = "ap-south-1" + ApSoutheast1 = "ap-southeast-1" + ApSoutheast2 = "ap-southeast-2" + CaCentral1 = "ca-central-1" + EuCentral1 = "eu-central-1" + EuWest1 = "eu-west-1" + EuWest2 = "eu-west-2" + EuWest3 = "eu-west-3" + SaEast1 = "sa-east-1" + UsEast1 = "us-east-1" + UsWest1 = "us-west-1" + UsWest2 = "us-west-2" + + +class SyncClient(BaseClient): + def __init__(self, *args, **kwargs): + warn( + "The 'SyncClient' class is deprecated. Please use `Client` from the httpx package instead.", + DeprecationWarning, + stacklevel=2, + ) + + super().__init__(*args, **kwargs) + + def aclose(self) -> None: + warn( + "The 'aclose' method is deprecated. Please use `close` method from `Client` in the httpx package instead.", + DeprecationWarning, + stacklevel=2, + ) + self.close() + + +def is_valid_str_arg(target: str) -> bool: + return isinstance(target, str) and len(target.strip()) > 0 + + +def is_http_url(url: str) -> bool: + return urlparse(url).scheme in {"https", "http"} diff --git a/src/functions/src/supabase_functions/version.py b/src/functions/src/supabase_functions/version.py new file mode 100644 index 00000000..645cf3a8 --- /dev/null +++ b/src/functions/src/supabase_functions/version.py @@ -0,0 +1 @@ +__version__ = "0.10.1" # {x-release-please-version} diff --git a/src/functions/tests/__init__.py b/src/functions/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/functions/tests/_async/__init__.py b/src/functions/tests/_async/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/functions/tests/_async/test_function_client.py b/src/functions/tests/_async/test_function_client.py new file mode 100644 index 00000000..f9b7e61d --- /dev/null +++ b/src/functions/tests/_async/test_function_client.py @@ -0,0 +1,216 @@ +from unittest.mock import AsyncMock, Mock, patch + +import pytest +from httpx import AsyncClient, HTTPError, Response, Timeout + +# Import the class to test +from supabase_functions import AsyncFunctionsClient +from supabase_functions.errors import FunctionsHttpError, FunctionsRelayError +from supabase_functions.utils import FunctionRegion +from supabase_functions.version import __version__ + + +@pytest.fixture +def valid_url(): + return "https://example.com" + + +@pytest.fixture +def default_headers(): + return {"Authorization": "Bearer valid.jwt.token"} + + +@pytest.fixture +def client(valid_url, default_headers): + return AsyncFunctionsClient( + url=valid_url, headers=default_headers, timeout=10, verify=True + ) + + +async def test_init_with_valid_params(valid_url, default_headers): + client = AsyncFunctionsClient( + url=valid_url, headers=default_headers, timeout=10, verify=True + ) + assert client.url == valid_url + assert "User-Agent" in client.headers + assert client.headers["User-Agent"] == f"supabase-py/functions-py v{__version__}" + assert client._client.timeout == Timeout(10) + + +@pytest.mark.parametrize("invalid_url", ["not-a-url", "ftp://invalid.com", "", None]) +def test_init_with_invalid_url(invalid_url, default_headers): + with pytest.raises(ValueError, match="url must be a valid HTTP URL string"): + AsyncFunctionsClient(url=invalid_url, headers=default_headers, timeout=10) + + +async def test_set_auth_valid_token(client: AsyncFunctionsClient): + valid_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U" + client.set_auth(valid_token) + assert client.headers["Authorization"] == f"Bearer {valid_token}" + + +async def test_invoke_success_json(client: AsyncFunctionsClient): + mock_response = Mock(spec=Response) + mock_response.json.return_value = {"message": "success"} + mock_response.raise_for_status = Mock() + mock_response.headers = {} + + with patch.object( + client._client, "request", new_callable=AsyncMock + ) as mock_request: + mock_request.return_value = mock_response + + result = await client.invoke( + "test-function", {"responseType": "json", "body": {"test": "data"}} + ) + + assert result == {"message": "success"} + mock_request.assert_called_once() + _, kwargs = mock_request.call_args + assert kwargs["json"] == {"test": "data"} + + +async def test_invoke_success_binary(client: AsyncFunctionsClient): + mock_response = Mock(spec=Response) + mock_response.content = b"binary content" + mock_response.raise_for_status = Mock() + mock_response.headers = {} + + with patch.object( + client._client, "request", new_callable=AsyncMock + ) as mock_request: + mock_request.return_value = mock_response + + result = await client.invoke("test-function") + + assert result == b"binary content" + mock_request.assert_called_once() + + +async def test_invoke_with_region(client: AsyncFunctionsClient): + mock_response = Mock(spec=Response) + mock_response.json.return_value = {"message": "success"} + mock_response.raise_for_status = Mock() + mock_response.headers = {} + + with patch.object( + client._client, "request", new_callable=AsyncMock + ) as mock_request: + mock_request.return_value = mock_response + + await client.invoke("test-function", {"region": FunctionRegion("us-east-1")}) + + _, kwargs = mock_request.call_args + assert kwargs["headers"]["x-region"] == "us-east-1" + + +async def test_invoke_with_region_string(client: AsyncFunctionsClient): + mock_response = Mock(spec=Response) + mock_response.json.return_value = {"message": "success"} + mock_response.raise_for_status = Mock() + mock_response.headers = {} + + with patch.object( + client._client, "request", new_callable=AsyncMock + ) as mock_request: + mock_request.return_value = mock_response + + with pytest.warns(UserWarning, match=r"Use FunctionRegion\(us-east-1\)"): + await client.invoke("test-function", {"region": "us-east-1"}) + + _, kwargs = mock_request.call_args + assert kwargs["headers"]["x-region"] == "us-east-1" + + +async def test_invoke_with_http_error(client: AsyncFunctionsClient): + mock_response = Mock(spec=Response) + mock_response.json.return_value = {"error": "Custom error message"} + mock_response.raise_for_status.side_effect = HTTPError("HTTP Error") + mock_response.headers = {} + + with patch.object( + client._client, "request", new_callable=AsyncMock + ) as mock_request: + mock_request.return_value = mock_response + + with pytest.raises(FunctionsHttpError, match="Custom error message"): + await client.invoke("test-function") + + +async def test_invoke_with_relay_error(client: AsyncFunctionsClient): + mock_response = Mock(spec=Response) + mock_response.json.return_value = {"error": "Relay error message"} + mock_response.raise_for_status = Mock() + mock_response.headers = {"x-relay-header": "true"} + + with patch.object( + client._client, "request", new_callable=AsyncMock + ) as mock_request: + mock_request.return_value = mock_response + + with pytest.raises(FunctionsRelayError, match="Relay error message"): + await client.invoke("test-function") + + +async def test_invoke_invalid_function_name(client: AsyncFunctionsClient): + with pytest.raises(ValueError, match="function_name must a valid string value."): + await client.invoke("") + + +async def test_invoke_with_string_body(client: AsyncFunctionsClient): + mock_response = Mock(spec=Response) + mock_response.json.return_value = {"message": "success"} + mock_response.raise_for_status = Mock() + mock_response.headers = {} + + with patch.object( + client._client, "request", new_callable=AsyncMock + ) as mock_request: + mock_request.return_value = mock_response + + await client.invoke("test-function", {"body": "string data"}) + + _, kwargs = mock_request.call_args + assert kwargs["headers"]["Content-Type"] == "text/plain" + + +async def test_invoke_with_json_body(client: AsyncFunctionsClient): + mock_response = Mock(spec=Response) + mock_response.json.return_value = {"message": "success"} + mock_response.raise_for_status = Mock() + mock_response.headers = {} + + with patch.object( + client._client, "request", new_callable=AsyncMock + ) as mock_request: + mock_request.return_value = mock_response + + await client.invoke("test-function", {"body": {"key": "value"}}) + + _, kwargs = mock_request.call_args + assert kwargs["headers"]["Content-Type"] == "application/json" + + +async def test_init_with_httpx_client(): + # Create a custom httpx client with specific options + headers = {"x-user-agent": "my-app/0.0.1"} + custom_client = AsyncClient( + timeout=Timeout(30), follow_redirects=True, max_redirects=5, headers=headers + ) + + # Initialize the functions client with the custom httpx client + client = AsyncFunctionsClient( + url="https://example.com", + headers={"Authorization": "Bearer token"}, + timeout=10, + http_client=custom_client, + ) + + # Verify the custom client options are preserved + assert client._client.timeout == Timeout(30) + assert client._client.follow_redirects is True + assert client._client.max_redirects == 5 + assert client._client.headers.get("x-user-agent") == "my-app/0.0.1" + + # Verify the client is properly configured with our custom client + assert client._client is custom_client diff --git a/src/functions/tests/_sync/__init__.py b/src/functions/tests/_sync/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/functions/tests/_sync/test_function_client.py b/src/functions/tests/_sync/test_function_client.py new file mode 100644 index 00000000..7f2b8194 --- /dev/null +++ b/src/functions/tests/_sync/test_function_client.py @@ -0,0 +1,200 @@ +from unittest.mock import Mock, patch + +import pytest +from httpx import Client, HTTPError, Response, Timeout + +# Import the class to test +from supabase_functions import SyncFunctionsClient +from supabase_functions.errors import FunctionsHttpError, FunctionsRelayError +from supabase_functions.utils import FunctionRegion +from supabase_functions.version import __version__ + + +@pytest.fixture +def valid_url(): + return "https://example.com" + + +@pytest.fixture +def default_headers(): + return {"Authorization": "Bearer valid.jwt.token"} + + +@pytest.fixture +def client(valid_url, default_headers): + return SyncFunctionsClient( + url=valid_url, headers=default_headers, timeout=10, verify=True + ) + + +def test_init_with_valid_params(valid_url, default_headers): + client = SyncFunctionsClient( + url=valid_url, headers=default_headers, timeout=10, verify=True + ) + assert client.url == valid_url + assert "User-Agent" in client.headers + assert client.headers["User-Agent"] == f"supabase-py/functions-py v{__version__}" + assert client._client.timeout == Timeout(10) + + +@pytest.mark.parametrize("invalid_url", ["not-a-url", "ftp://invalid.com", "", None]) +def test_init_with_invalid_url(invalid_url, default_headers): + with pytest.raises(ValueError, match="url must be a valid HTTP URL string"): + SyncFunctionsClient(url=invalid_url, headers=default_headers, timeout=10) + + +def test_set_auth_valid_token(client: SyncFunctionsClient): + valid_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U" + client.set_auth(valid_token) + assert client.headers["Authorization"] == f"Bearer {valid_token}" + + +def test_invoke_success_json(client: SyncFunctionsClient): + mock_response = Mock(spec=Response) + mock_response.json.return_value = {"message": "success"} + mock_response.raise_for_status = Mock() + mock_response.headers = {} + + with patch.object(client._client, "request", new_callable=Mock) as mock_request: + mock_request.return_value = mock_response + + result = client.invoke( + "test-function", {"responseType": "json", "body": {"test": "data"}} + ) + + assert result == {"message": "success"} + mock_request.assert_called_once() + _, kwargs = mock_request.call_args + assert kwargs["json"] == {"test": "data"} + + +def test_invoke_success_binary(client: SyncFunctionsClient): + mock_response = Mock(spec=Response) + mock_response.content = b"binary content" + mock_response.raise_for_status = Mock() + mock_response.headers = {} + + with patch.object(client._client, "request", new_callable=Mock) as mock_request: + mock_request.return_value = mock_response + + result = client.invoke("test-function") + + assert result == b"binary content" + mock_request.assert_called_once() + + +def test_invoke_with_region(client: SyncFunctionsClient): + mock_response = Mock(spec=Response) + mock_response.json.return_value = {"message": "success"} + mock_response.raise_for_status = Mock() + mock_response.headers = {} + + with patch.object(client._client, "request", new_callable=Mock) as mock_request: + mock_request.return_value = mock_response + + client.invoke("test-function", {"region": FunctionRegion("us-east-1")}) + + _, kwargs = mock_request.call_args + assert kwargs["headers"]["x-region"] == "us-east-1" + + +def test_invoke_with_region_string(client: SyncFunctionsClient): + mock_response = Mock(spec=Response) + mock_response.json.return_value = {"message": "success"} + mock_response.raise_for_status = Mock() + mock_response.headers = {} + + with patch.object(client._client, "request", new_callable=Mock) as mock_request: + mock_request.return_value = mock_response + + with pytest.warns(UserWarning, match=r"Use FunctionRegion\(us-east-1\)"): + client.invoke("test-function", {"region": "us-east-1"}) + + _, kwargs = mock_request.call_args + assert kwargs["headers"]["x-region"] == "us-east-1" + + +def test_invoke_with_http_error(client: SyncFunctionsClient): + mock_response = Mock(spec=Response) + mock_response.json.return_value = {"error": "Custom error message"} + mock_response.raise_for_status.side_effect = HTTPError("HTTP Error") + mock_response.headers = {} + + with patch.object(client._client, "request", new_callable=Mock) as mock_request: + mock_request.return_value = mock_response + + with pytest.raises(FunctionsHttpError, match="Custom error message"): + client.invoke("test-function") + + +def test_invoke_with_relay_error(client: SyncFunctionsClient): + mock_response = Mock(spec=Response) + mock_response.json.return_value = {"error": "Relay error message"} + mock_response.raise_for_status = Mock() + mock_response.headers = {"x-relay-header": "true"} + + with patch.object(client._client, "request", new_callable=Mock) as mock_request: + mock_request.return_value = mock_response + + with pytest.raises(FunctionsRelayError, match="Relay error message"): + client.invoke("test-function") + + +def test_invoke_invalid_function_name(client: SyncFunctionsClient): + with pytest.raises(ValueError, match="function_name must a valid string value."): + client.invoke("") + + +def test_invoke_with_string_body(client: SyncFunctionsClient): + mock_response = Mock(spec=Response) + mock_response.json.return_value = {"message": "success"} + mock_response.raise_for_status = Mock() + mock_response.headers = {} + + with patch.object(client._client, "request", new_callable=Mock) as mock_request: + mock_request.return_value = mock_response + + client.invoke("test-function", {"body": "string data"}) + + _, kwargs = mock_request.call_args + assert kwargs["headers"]["Content-Type"] == "text/plain" + + +def test_invoke_with_json_body(client: SyncFunctionsClient): + mock_response = Mock(spec=Response) + mock_response.json.return_value = {"message": "success"} + mock_response.raise_for_status = Mock() + mock_response.headers = {} + + with patch.object(client._client, "request", new_callable=Mock) as mock_request: + mock_request.return_value = mock_response + + client.invoke("test-function", {"body": {"key": "value"}}) + + _, kwargs = mock_request.call_args + assert kwargs["headers"]["Content-Type"] == "application/json" + + +def test_init_with_httpx_client(): + # Create a custom httpx client with specific options + headers = {"x-user-agent": "my-app/0.0.1"} + custom_client = Client( + timeout=Timeout(30), follow_redirects=True, max_redirects=5, headers=headers + ) + + # Initialize the functions client with the custom httpx client + client = SyncFunctionsClient( + url="https://example.com", + headers={"Authorization": "Bearer token"}, + timeout=10, + http_client=custom_client, + ) + + # Verify the custom client options are preserved + assert client._client.timeout == Timeout(30) + assert client._client.follow_redirects is True + assert client._client.max_redirects == 5 + assert client._client.headers.get("x-user-agent") == "my-app/0.0.1" + + # Verify the client is properly configured with our custom client + assert client._client is custom_client diff --git a/src/functions/tests/test_client.py b/src/functions/tests/test_client.py new file mode 100644 index 00000000..60929098 --- /dev/null +++ b/src/functions/tests/test_client.py @@ -0,0 +1,49 @@ +from typing import Dict + +import pytest + +from supabase_functions import AsyncFunctionsClient, SyncFunctionsClient, create_client + + +@pytest.fixture +def valid_url() -> str: + return "https://example.com" + + +@pytest.fixture +def valid_headers() -> Dict[str, str]: + return {"Authorization": "Bearer test_token", "Content-Type": "application/json"} + + +def test_create_async_client(valid_url, valid_headers): + # Test creating async client with explicit verify=True + client = create_client( + url=valid_url, headers=valid_headers, is_async=True, verify=True + ) + + assert isinstance(client, AsyncFunctionsClient) + assert client.url == valid_url + assert all(client.headers[key] == value for key, value in valid_headers.items()) + + +def test_create_sync_client(valid_url, valid_headers): + # Test creating sync client with explicit verify=True + client = create_client( + url=valid_url, headers=valid_headers, is_async=False, verify=True + ) + + assert isinstance(client, SyncFunctionsClient) + assert client.url == valid_url + assert all(client.headers[key] == value for key, value in valid_headers.items()) + + +def test_type_hints(): + from typing import Union, get_type_hints + + hints = get_type_hints(create_client) + + assert hints["url"] is str + assert hints["headers"] == dict[str, str] + assert hints["is_async"] is bool + assert hints["verify"] is bool + assert hints["return"] == Union[AsyncFunctionsClient, SyncFunctionsClient] diff --git a/src/functions/tests/test_errors.py b/src/functions/tests/test_errors.py new file mode 100644 index 00000000..bf296c5c --- /dev/null +++ b/src/functions/tests/test_errors.py @@ -0,0 +1,109 @@ +from typing import Type + +import pytest + +from supabase_functions.errors import ( + FunctionsApiErrorDict, + FunctionsError, + FunctionsHttpError, + FunctionsRelayError, +) + + +@pytest.mark.parametrize( + "error_class,expected_name,expected_status", + [ + (FunctionsError, "test_error", 500), + (FunctionsHttpError, "FunctionsHttpError", 400), + (FunctionsRelayError, "FunctionsRelayError", 400), + ], +) +def test_error_initialization( + error_class: Type[FunctionsError], expected_name: str, expected_status: int +): + test_message = "Test error message" + + if error_class is FunctionsError: + error = error_class(test_message, expected_name, expected_status) + else: + error = error_class(test_message) + + assert str(error) == test_message + assert error.message == test_message + assert error.name == expected_name + assert error.status == expected_status + assert isinstance(error, Exception) + + +@pytest.mark.parametrize( + "error_class,expected_name,expected_status", + [ + (FunctionsError, "test_error", 500), + (FunctionsHttpError, "FunctionsHttpError", 400), + (FunctionsRelayError, "FunctionsRelayError", 400), + ], +) +def test_error_to_dict( + error_class: Type[FunctionsError], expected_name: str, expected_status: int +): + test_message = "Test error message" + + if error_class is FunctionsError: + error = error_class(test_message, expected_name, expected_status) + else: + error = error_class(test_message) + + error_dict = error.to_dict() + + assert isinstance(error_dict, dict) + assert error_dict["message"] == test_message + assert error_dict["name"] == expected_name + assert error_dict["status"] == expected_status + + # Verify the dict matches the TypedDict structure + typed_dict: FunctionsApiErrorDict = error_dict + assert isinstance(typed_dict["name"], str) + assert isinstance(typed_dict["message"], str) + assert isinstance(typed_dict["status"], int) + + +def test_functions_error_inheritance(): + # Test that all error classes inherit from FunctionsError + assert issubclass(FunctionsHttpError, FunctionsError) + assert issubclass(FunctionsRelayError, FunctionsError) + + +def test_error_as_exception(): + # Test that errors can be raised and caught + test_message = "Test exception" + + # Test base error + with pytest.raises(FunctionsError) as exc_info: + raise FunctionsError(test_message, "test_error", 500) + assert str(exc_info.value) == test_message + + # Test HTTP error + with pytest.raises(FunctionsHttpError) as exc_info: + raise FunctionsHttpError(test_message) + assert str(exc_info.value) == test_message + + # Test Relay error + with pytest.raises(FunctionsRelayError) as exc_info: + raise FunctionsRelayError(test_message) + assert str(exc_info.value) == test_message + + +def test_error_message_types(): + # Test that errors handle different message types appropriately + test_cases = [ + "Simple string", + "String with unicode: 你好", + "String with special chars: !@#$%^&*()", + "", # Empty string + "A" * 1000, # Long string + ] + + for message in test_cases: + error = FunctionsError(message, "test", 500) + assert error.message == message + assert error.to_dict()["message"] == message diff --git a/src/functions/tests/test_utils.py b/src/functions/tests/test_utils.py new file mode 100644 index 00000000..581b5e3a --- /dev/null +++ b/src/functions/tests/test_utils.py @@ -0,0 +1,115 @@ +import sys +from typing import Any + +import pytest + +from supabase_functions.utils import ( + BASE64URL_REGEX, + FunctionRegion, + SyncClient, + is_http_url, + is_valid_str_arg, +) + + +def test_function_region_values(): + assert FunctionRegion.Any.value == "any" + assert FunctionRegion.ApNortheast1.value == "ap-northeast-1" + assert FunctionRegion.ApNortheast2.value == "ap-northeast-2" + assert FunctionRegion.ApSouth1.value == "ap-south-1" + assert FunctionRegion.ApSoutheast1.value == "ap-southeast-1" + assert FunctionRegion.ApSoutheast2.value == "ap-southeast-2" + assert FunctionRegion.CaCentral1.value == "ca-central-1" + assert FunctionRegion.EuCentral1.value == "eu-central-1" + assert FunctionRegion.EuWest1.value == "eu-west-1" + assert FunctionRegion.EuWest2.value == "eu-west-2" + assert FunctionRegion.EuWest3.value == "eu-west-3" + assert FunctionRegion.SaEast1.value == "sa-east-1" + assert FunctionRegion.UsEast1.value == "us-east-1" + assert FunctionRegion.UsWest1.value == "us-west-1" + assert FunctionRegion.UsWest2.value == "us-west-2" + + +def test_sync_client(): + client = SyncClient() + # Verify that aclose method exists and calls close + assert hasattr(client, "aclose") + assert callable(client.aclose) + client.aclose() # Should not raise any exception + + +@pytest.mark.parametrize( + "test_input,expected", + [ + ("valid_string", True), + (" spaced_string ", True), + ("", False), + (" ", False), + (None, False), + (123, False), + ([], False), + ({}, False), + ], +) +def test_is_valid_str_arg(test_input: Any, expected: bool): + assert is_valid_str_arg(test_input) == expected + + +@pytest.mark.parametrize( + "test_input,expected", + [ + ("https://example.com", True), + ("http://localhost", True), + ("http://127.0.0.1:8000", True), + ("https://api.supabase.com", True), + ("ftp://example.com", False), + ("ws://example.com", False), + ("not-a-url", False), + ("", False), + ], +) +def test_is_http_url(test_input: str, expected: bool): + assert is_http_url(test_input) == expected + + +def test_base64url_regex(): + import re + + pattern = re.compile(BASE64URL_REGEX, re.IGNORECASE) + + # Valid base64url strings + assert pattern.match("abcd") + assert pattern.match("1234") + assert pattern.match("abc") + assert pattern.match("12") + assert pattern.match("ab") + assert pattern.match("ABCD") + assert pattern.match("ABC") + assert pattern.match("AB") + assert pattern.match("a-b_") + + # Invalid base64url strings + assert not pattern.match("a") # too short + assert not pattern.match("abcde") # invalid length + assert not pattern.match("abc!") # invalid character + assert not pattern.match("abc@") # invalid character + + +@pytest.mark.skipif( + sys.version_info < (3, 11), + reason="StrEnum import test only relevant for Python 3.11+", +) +def test_strenum_import_python_311_plus(): + from enum import StrEnum as BuiltinStrEnum + + assert isinstance(FunctionRegion.Any, BuiltinStrEnum) + + +@pytest.mark.skipif( + sys.version_info >= (3, 11), + reason="strenum import test only relevant for Python < 3.11", +) +def test_strenum_import_python_310_and_below(): + from strenum import StrEnum as ExternalStrEnum + + assert isinstance(FunctionRegion.Any, ExternalStrEnum) diff --git a/src/functions/uv.lock b/src/functions/uv.lock new file mode 100644 index 00000000..441c54dc --- /dev/null +++ b/src/functions/uv.lock @@ -0,0 +1,698 @@ +version = 1 +revision = 2 +requires-python = ">=3.9" +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version < '3.10'", +] + +[[package]] +name = "anyio" +version = "4.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/b4/636b3b65173d3ce9a38ef5f0522789614e590dab6a8d505340a4efe4c567/anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6", size = 213252, upload-time = "2025-08-04T08:54:26.451Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" }, +] + +[[package]] +name = "backports-asyncio-runner" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162", size = 69893, upload-time = "2025-07-02T02:27:15.685Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" }, +] + +[[package]] +name = "certifi" +version = "2025.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, +] + +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" }, +] + +[[package]] +name = "click" +version = "8.1.8" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, +] + +[[package]] +name = "click" +version = "8.2.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "coverage" +version = "7.10.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/4e/08b493f1f1d8a5182df0044acc970799b58a8d289608e0d891a03e9d269a/coverage-7.10.4.tar.gz", hash = "sha256:25f5130af6c8e7297fd14634955ba9e1697f47143f289e2a23284177c0061d27", size = 823798, upload-time = "2025-08-17T00:26:43.314Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/f4/350759710db50362685f922259c140592dba15eb4e2325656a98413864d9/coverage-7.10.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d92d6edb0ccafd20c6fbf9891ca720b39c2a6a4b4a6f9cf323ca2c986f33e475", size = 216403, upload-time = "2025-08-17T00:24:19.083Z" }, + { url = "https://files.pythonhosted.org/packages/29/7e/e467c2bb4d5ecfd166bfd22c405cce4c50de2763ba1d78e2729c59539a42/coverage-7.10.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7202da14dc0236884fcc45665ffb2d79d4991a53fbdf152ab22f69f70923cc22", size = 216802, upload-time = "2025-08-17T00:24:21.824Z" }, + { url = "https://files.pythonhosted.org/packages/62/ab/2accdd1ccfe63b890e5eb39118f63c155202df287798364868a2884a50af/coverage-7.10.4-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ada418633ae24ec8d0fcad5efe6fc7aa3c62497c6ed86589e57844ad04365674", size = 243558, upload-time = "2025-08-17T00:24:23.569Z" }, + { url = "https://files.pythonhosted.org/packages/43/04/c14c33d0cfc0f4db6b3504d01a47f4c798563d932a836fd5f2dbc0521d3d/coverage-7.10.4-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b828e33eca6c3322adda3b5884456f98c435182a44917ded05005adfa1415500", size = 245370, upload-time = "2025-08-17T00:24:24.858Z" }, + { url = "https://files.pythonhosted.org/packages/99/71/147053061f1f51c1d3b3d040c3cb26876964a3a0dca0765d2441411ca568/coverage-7.10.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:802793ba397afcfdbe9f91f89d65ae88b958d95edc8caf948e1f47d8b6b2b606", size = 247228, upload-time = "2025-08-17T00:24:26.167Z" }, + { url = "https://files.pythonhosted.org/packages/cc/92/7ef882205d4d4eb502e6154ee7122c1a1b1ce3f29d0166921e0fb550a5d3/coverage-7.10.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d0b23512338c54101d3bf7a1ab107d9d75abda1d5f69bc0887fd079253e4c27e", size = 245270, upload-time = "2025-08-17T00:24:27.424Z" }, + { url = "https://files.pythonhosted.org/packages/ab/3d/297a20603abcc6c7d89d801286eb477b0b861f3c5a4222730f1c9837be3e/coverage-7.10.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:f36b7dcf72d06a8c5e2dd3aca02be2b1b5db5f86404627dff834396efce958f2", size = 243287, upload-time = "2025-08-17T00:24:28.697Z" }, + { url = "https://files.pythonhosted.org/packages/65/f9/b04111438f41f1ddd5dc88706d5f8064ae5bb962203c49fe417fa23a362d/coverage-7.10.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fce316c367a1dc2c411821365592eeb335ff1781956d87a0410eae248188ba51", size = 244164, upload-time = "2025-08-17T00:24:30.393Z" }, + { url = "https://files.pythonhosted.org/packages/1e/e5/c7d9eb7a9ea66cf92d069077719fb2b07782dcd7050b01a9b88766b52154/coverage-7.10.4-cp310-cp310-win32.whl", hash = "sha256:8c5dab29fc8070b3766b5fc85f8d89b19634584429a2da6d42da5edfadaf32ae", size = 218917, upload-time = "2025-08-17T00:24:31.67Z" }, + { url = "https://files.pythonhosted.org/packages/66/30/4d9d3b81f5a836b31a7428b8a25e6d490d4dca5ff2952492af130153c35c/coverage-7.10.4-cp310-cp310-win_amd64.whl", hash = "sha256:4b0d114616f0fccb529a1817457d5fb52a10e106f86c5fb3b0bd0d45d0d69b93", size = 219822, upload-time = "2025-08-17T00:24:32.89Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ba/2c9817e62018e7d480d14f684c160b3038df9ff69c5af7d80e97d143e4d1/coverage-7.10.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:05d5f98ec893d4a2abc8bc5f046f2f4367404e7e5d5d18b83de8fde1093ebc4f", size = 216514, upload-time = "2025-08-17T00:24:34.188Z" }, + { url = "https://files.pythonhosted.org/packages/e3/5a/093412a959a6b6261446221ba9fb23bb63f661a5de70b5d130763c87f916/coverage-7.10.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9267efd28f8994b750d171e58e481e3bbd69e44baed540e4c789f8e368b24b88", size = 216914, upload-time = "2025-08-17T00:24:35.881Z" }, + { url = "https://files.pythonhosted.org/packages/2c/1f/2fdf4a71cfe93b07eae845ebf763267539a7d8b7e16b062f959d56d7e433/coverage-7.10.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4456a039fdc1a89ea60823d0330f1ac6f97b0dbe9e2b6fb4873e889584b085fb", size = 247308, upload-time = "2025-08-17T00:24:37.61Z" }, + { url = "https://files.pythonhosted.org/packages/ba/16/33f6cded458e84f008b9f6bc379609a6a1eda7bffe349153b9960803fc11/coverage-7.10.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c2bfbd2a9f7e68a21c5bd191be94bfdb2691ac40d325bac9ef3ae45ff5c753d9", size = 249241, upload-time = "2025-08-17T00:24:38.919Z" }, + { url = "https://files.pythonhosted.org/packages/84/98/9c18e47c889be58339ff2157c63b91a219272503ee32b49d926eea2337f2/coverage-7.10.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab7765f10ae1df7e7fe37de9e64b5a269b812ee22e2da3f84f97b1c7732a0d8", size = 251346, upload-time = "2025-08-17T00:24:40.507Z" }, + { url = "https://files.pythonhosted.org/packages/6d/07/00a6c0d53e9a22d36d8e95ddd049b860eef8f4b9fd299f7ce34d8e323356/coverage-7.10.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a09b13695166236e171ec1627ff8434b9a9bae47528d0ba9d944c912d33b3d2", size = 249037, upload-time = "2025-08-17T00:24:41.904Z" }, + { url = "https://files.pythonhosted.org/packages/3e/0e/1e1b944d6a6483d07bab5ef6ce063fcf3d0cc555a16a8c05ebaab11f5607/coverage-7.10.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5c9e75dfdc0167d5675e9804f04a56b2cf47fb83a524654297000b578b8adcb7", size = 247090, upload-time = "2025-08-17T00:24:43.193Z" }, + { url = "https://files.pythonhosted.org/packages/62/43/2ce5ab8a728b8e25ced077111581290ffaef9efaf860a28e25435ab925cf/coverage-7.10.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c751261bfe6481caba15ec005a194cb60aad06f29235a74c24f18546d8377df0", size = 247732, upload-time = "2025-08-17T00:24:44.906Z" }, + { url = "https://files.pythonhosted.org/packages/a4/f3/706c4a24f42c1c5f3a2ca56637ab1270f84d9e75355160dc34d5e39bb5b7/coverage-7.10.4-cp311-cp311-win32.whl", hash = "sha256:051c7c9e765f003c2ff6e8c81ccea28a70fb5b0142671e4e3ede7cebd45c80af", size = 218961, upload-time = "2025-08-17T00:24:46.241Z" }, + { url = "https://files.pythonhosted.org/packages/e8/aa/6b9ea06e0290bf1cf2a2765bba89d561c5c563b4e9db8298bf83699c8b67/coverage-7.10.4-cp311-cp311-win_amd64.whl", hash = "sha256:1a647b152f10be08fb771ae4a1421dbff66141e3d8ab27d543b5eb9ea5af8e52", size = 219851, upload-time = "2025-08-17T00:24:48.795Z" }, + { url = "https://files.pythonhosted.org/packages/8b/be/f0dc9ad50ee183369e643cd7ed8f2ef5c491bc20b4c3387cbed97dd6e0d1/coverage-7.10.4-cp311-cp311-win_arm64.whl", hash = "sha256:b09b9e4e1de0d406ca9f19a371c2beefe3193b542f64a6dd40cfcf435b7d6aa0", size = 218530, upload-time = "2025-08-17T00:24:50.164Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4a/781c9e4dd57cabda2a28e2ce5b00b6be416015265851060945a5ed4bd85e/coverage-7.10.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a1f0264abcabd4853d4cb9b3d164adbf1565da7dab1da1669e93f3ea60162d79", size = 216706, upload-time = "2025-08-17T00:24:51.528Z" }, + { url = "https://files.pythonhosted.org/packages/6a/8c/51255202ca03d2e7b664770289f80db6f47b05138e06cce112b3957d5dfd/coverage-7.10.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:536cbe6b118a4df231b11af3e0f974a72a095182ff8ec5f4868c931e8043ef3e", size = 216939, upload-time = "2025-08-17T00:24:53.171Z" }, + { url = "https://files.pythonhosted.org/packages/06/7f/df11131483698660f94d3c847dc76461369782d7a7644fcd72ac90da8fd0/coverage-7.10.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9a4c0d84134797b7bf3f080599d0cd501471f6c98b715405166860d79cfaa97e", size = 248429, upload-time = "2025-08-17T00:24:54.934Z" }, + { url = "https://files.pythonhosted.org/packages/eb/fa/13ac5eda7300e160bf98f082e75f5c5b4189bf3a883dd1ee42dbedfdc617/coverage-7.10.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7c155fc0f9cee8c9803ea0ad153ab6a3b956baa5d4cd993405dc0b45b2a0b9e0", size = 251178, upload-time = "2025-08-17T00:24:56.353Z" }, + { url = "https://files.pythonhosted.org/packages/9a/bc/f63b56a58ad0bec68a840e7be6b7ed9d6f6288d790760647bb88f5fea41e/coverage-7.10.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a5f2ab6e451d4b07855d8bcf063adf11e199bff421a4ba57f5bb95b7444ca62", size = 252313, upload-time = "2025-08-17T00:24:57.692Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b6/79338f1ea27b01266f845afb4485976211264ab92407d1c307babe3592a7/coverage-7.10.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:685b67d99b945b0c221be0780c336b303a7753b3e0ec0d618c795aada25d5e7a", size = 250230, upload-time = "2025-08-17T00:24:59.293Z" }, + { url = "https://files.pythonhosted.org/packages/bc/93/3b24f1da3e0286a4dc5832427e1d448d5296f8287464b1ff4a222abeeeb5/coverage-7.10.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0c079027e50c2ae44da51c2e294596cbc9dbb58f7ca45b30651c7e411060fc23", size = 248351, upload-time = "2025-08-17T00:25:00.676Z" }, + { url = "https://files.pythonhosted.org/packages/de/5f/d59412f869e49dcc5b89398ef3146c8bfaec870b179cc344d27932e0554b/coverage-7.10.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3749aa72b93ce516f77cf5034d8e3c0dfd45c6e8a163a602ede2dc5f9a0bb927", size = 249788, upload-time = "2025-08-17T00:25:02.354Z" }, + { url = "https://files.pythonhosted.org/packages/cc/52/04a3b733f40a0cc7c4a5b9b010844111dbf906df3e868b13e1ce7b39ac31/coverage-7.10.4-cp312-cp312-win32.whl", hash = "sha256:fecb97b3a52fa9bcd5a7375e72fae209088faf671d39fae67261f37772d5559a", size = 219131, upload-time = "2025-08-17T00:25:03.79Z" }, + { url = "https://files.pythonhosted.org/packages/83/dd/12909fc0b83888197b3ec43a4ac7753589591c08d00d9deda4158df2734e/coverage-7.10.4-cp312-cp312-win_amd64.whl", hash = "sha256:26de58f355626628a21fe6a70e1e1fad95702dafebfb0685280962ae1449f17b", size = 219939, upload-time = "2025-08-17T00:25:05.494Z" }, + { url = "https://files.pythonhosted.org/packages/83/c7/058bb3220fdd6821bada9685eadac2940429ab3c97025ce53549ff423cc1/coverage-7.10.4-cp312-cp312-win_arm64.whl", hash = "sha256:67e8885408f8325198862bc487038a4980c9277d753cb8812510927f2176437a", size = 218572, upload-time = "2025-08-17T00:25:06.897Z" }, + { url = "https://files.pythonhosted.org/packages/46/b0/4a3662de81f2ed792a4e425d59c4ae50d8dd1d844de252838c200beed65a/coverage-7.10.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2b8e1d2015d5dfdbf964ecef12944c0c8c55b885bb5c0467ae8ef55e0e151233", size = 216735, upload-time = "2025-08-17T00:25:08.617Z" }, + { url = "https://files.pythonhosted.org/packages/c5/e8/e2dcffea01921bfffc6170fb4406cffb763a3b43a047bbd7923566708193/coverage-7.10.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:25735c299439018d66eb2dccf54f625aceb78645687a05f9f848f6e6c751e169", size = 216982, upload-time = "2025-08-17T00:25:10.384Z" }, + { url = "https://files.pythonhosted.org/packages/9d/59/cc89bb6ac869704d2781c2f5f7957d07097c77da0e8fdd4fd50dbf2ac9c0/coverage-7.10.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:715c06cb5eceac4d9b7cdf783ce04aa495f6aff657543fea75c30215b28ddb74", size = 247981, upload-time = "2025-08-17T00:25:11.854Z" }, + { url = "https://files.pythonhosted.org/packages/aa/23/3da089aa177ceaf0d3f96754ebc1318597822e6387560914cc480086e730/coverage-7.10.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e017ac69fac9aacd7df6dc464c05833e834dc5b00c914d7af9a5249fcccf07ef", size = 250584, upload-time = "2025-08-17T00:25:13.483Z" }, + { url = "https://files.pythonhosted.org/packages/ad/82/e8693c368535b4e5fad05252a366a1794d481c79ae0333ed943472fd778d/coverage-7.10.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bad180cc40b3fccb0f0e8c702d781492654ac2580d468e3ffc8065e38c6c2408", size = 251856, upload-time = "2025-08-17T00:25:15.27Z" }, + { url = "https://files.pythonhosted.org/packages/56/19/8b9cb13292e602fa4135b10a26ac4ce169a7fc7c285ff08bedd42ff6acca/coverage-7.10.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:becbdcd14f685fada010a5f792bf0895675ecf7481304fe159f0cd3f289550bd", size = 250015, upload-time = "2025-08-17T00:25:16.759Z" }, + { url = "https://files.pythonhosted.org/packages/10/e7/e5903990ce089527cf1c4f88b702985bd65c61ac245923f1ff1257dbcc02/coverage-7.10.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0b485ca21e16a76f68060911f97ebbe3e0d891da1dbbce6af7ca1ab3f98b9097", size = 247908, upload-time = "2025-08-17T00:25:18.232Z" }, + { url = "https://files.pythonhosted.org/packages/dd/c9/7d464f116df1df7fe340669af1ddbe1a371fc60f3082ff3dc837c4f1f2ab/coverage-7.10.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6c1d098ccfe8e1e0a1ed9a0249138899948afd2978cbf48eb1cc3fcd38469690", size = 249525, upload-time = "2025-08-17T00:25:20.141Z" }, + { url = "https://files.pythonhosted.org/packages/ce/42/722e0cdbf6c19e7235c2020837d4e00f3b07820fd012201a983238cc3a30/coverage-7.10.4-cp313-cp313-win32.whl", hash = "sha256:8630f8af2ca84b5c367c3df907b1706621abe06d6929f5045fd628968d421e6e", size = 219173, upload-time = "2025-08-17T00:25:21.56Z" }, + { url = "https://files.pythonhosted.org/packages/97/7e/aa70366f8275955cd51fa1ed52a521c7fcebcc0fc279f53c8c1ee6006dfe/coverage-7.10.4-cp313-cp313-win_amd64.whl", hash = "sha256:f68835d31c421736be367d32f179e14ca932978293fe1b4c7a6a49b555dff5b2", size = 219969, upload-time = "2025-08-17T00:25:23.501Z" }, + { url = "https://files.pythonhosted.org/packages/ac/96/c39d92d5aad8fec28d4606556bfc92b6fee0ab51e4a548d9b49fb15a777c/coverage-7.10.4-cp313-cp313-win_arm64.whl", hash = "sha256:6eaa61ff6724ca7ebc5326d1fae062d85e19b38dd922d50903702e6078370ae7", size = 218601, upload-time = "2025-08-17T00:25:25.295Z" }, + { url = "https://files.pythonhosted.org/packages/79/13/34d549a6177bd80fa5db758cb6fd3057b7ad9296d8707d4ab7f480b0135f/coverage-7.10.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:702978108876bfb3d997604930b05fe769462cc3000150b0e607b7b444f2fd84", size = 217445, upload-time = "2025-08-17T00:25:27.129Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c0/433da866359bf39bf595f46d134ff2d6b4293aeea7f3328b6898733b0633/coverage-7.10.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e8f978e8c5521d9c8f2086ac60d931d583fab0a16f382f6eb89453fe998e2484", size = 217676, upload-time = "2025-08-17T00:25:28.641Z" }, + { url = "https://files.pythonhosted.org/packages/7e/d7/2b99aa8737f7801fd95222c79a4ebc8c5dd4460d4bed7ef26b17a60c8d74/coverage-7.10.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:df0ac2ccfd19351411c45e43ab60932b74472e4648b0a9edf6a3b58846e246a9", size = 259002, upload-time = "2025-08-17T00:25:30.065Z" }, + { url = "https://files.pythonhosted.org/packages/08/cf/86432b69d57debaef5abf19aae661ba8f4fcd2882fa762e14added4bd334/coverage-7.10.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:73a0d1aaaa3796179f336448e1576a3de6fc95ff4f07c2d7251d4caf5d18cf8d", size = 261178, upload-time = "2025-08-17T00:25:31.517Z" }, + { url = "https://files.pythonhosted.org/packages/23/78/85176593f4aa6e869cbed7a8098da3448a50e3fac5cb2ecba57729a5220d/coverage-7.10.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:873da6d0ed6b3ffc0bc01f2c7e3ad7e2023751c0d8d86c26fe7322c314b031dc", size = 263402, upload-time = "2025-08-17T00:25:33.339Z" }, + { url = "https://files.pythonhosted.org/packages/88/1d/57a27b6789b79abcac0cc5805b31320d7a97fa20f728a6a7c562db9a3733/coverage-7.10.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c6446c75b0e7dda5daa876a1c87b480b2b52affb972fedd6c22edf1aaf2e00ec", size = 260957, upload-time = "2025-08-17T00:25:34.795Z" }, + { url = "https://files.pythonhosted.org/packages/fa/e5/3e5ddfd42835c6def6cd5b2bdb3348da2e34c08d9c1211e91a49e9fd709d/coverage-7.10.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:6e73933e296634e520390c44758d553d3b573b321608118363e52113790633b9", size = 258718, upload-time = "2025-08-17T00:25:36.259Z" }, + { url = "https://files.pythonhosted.org/packages/1a/0b/d364f0f7ef111615dc4e05a6ed02cac7b6f2ac169884aa57faeae9eb5fa0/coverage-7.10.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52073d4b08d2cb571234c8a71eb32af3c6923149cf644a51d5957ac128cf6aa4", size = 259848, upload-time = "2025-08-17T00:25:37.754Z" }, + { url = "https://files.pythonhosted.org/packages/10/c6/bbea60a3b309621162e53faf7fac740daaf083048ea22077418e1ecaba3f/coverage-7.10.4-cp313-cp313t-win32.whl", hash = "sha256:e24afb178f21f9ceb1aefbc73eb524769aa9b504a42b26857243f881af56880c", size = 219833, upload-time = "2025-08-17T00:25:39.252Z" }, + { url = "https://files.pythonhosted.org/packages/44/a5/f9f080d49cfb117ddffe672f21eab41bd23a46179a907820743afac7c021/coverage-7.10.4-cp313-cp313t-win_amd64.whl", hash = "sha256:be04507ff1ad206f4be3d156a674e3fb84bbb751ea1b23b142979ac9eebaa15f", size = 220897, upload-time = "2025-08-17T00:25:40.772Z" }, + { url = "https://files.pythonhosted.org/packages/46/89/49a3fc784fa73d707f603e586d84a18c2e7796707044e9d73d13260930b7/coverage-7.10.4-cp313-cp313t-win_arm64.whl", hash = "sha256:f3e3ff3f69d02b5dad67a6eac68cc9c71ae343b6328aae96e914f9f2f23a22e2", size = 219160, upload-time = "2025-08-17T00:25:42.229Z" }, + { url = "https://files.pythonhosted.org/packages/b5/22/525f84b4cbcff66024d29f6909d7ecde97223f998116d3677cfba0d115b5/coverage-7.10.4-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a59fe0af7dd7211ba595cf7e2867458381f7e5d7b4cffe46274e0b2f5b9f4eb4", size = 216717, upload-time = "2025-08-17T00:25:43.875Z" }, + { url = "https://files.pythonhosted.org/packages/a6/58/213577f77efe44333a416d4bcb251471e7f64b19b5886bb515561b5ce389/coverage-7.10.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3a6c35c5b70f569ee38dc3350cd14fdd0347a8b389a18bb37538cc43e6f730e6", size = 216994, upload-time = "2025-08-17T00:25:45.405Z" }, + { url = "https://files.pythonhosted.org/packages/17/85/34ac02d0985a09472f41b609a1d7babc32df87c726c7612dc93d30679b5a/coverage-7.10.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:acb7baf49f513554c4af6ef8e2bd6e8ac74e6ea0c7386df8b3eb586d82ccccc4", size = 248038, upload-time = "2025-08-17T00:25:46.981Z" }, + { url = "https://files.pythonhosted.org/packages/47/4f/2140305ec93642fdaf988f139813629cbb6d8efa661b30a04b6f7c67c31e/coverage-7.10.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a89afecec1ed12ac13ed203238b560cbfad3522bae37d91c102e690b8b1dc46c", size = 250575, upload-time = "2025-08-17T00:25:48.613Z" }, + { url = "https://files.pythonhosted.org/packages/f2/b5/41b5784180b82a083c76aeba8f2c72ea1cb789e5382157b7dc852832aea2/coverage-7.10.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:480442727f464407d8ade6e677b7f21f3b96a9838ab541b9a28ce9e44123c14e", size = 251927, upload-time = "2025-08-17T00:25:50.881Z" }, + { url = "https://files.pythonhosted.org/packages/78/ca/c1dd063e50b71f5aea2ebb27a1c404e7b5ecf5714c8b5301f20e4e8831ac/coverage-7.10.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a89bf193707f4a17f1ed461504031074d87f035153239f16ce86dfb8f8c7ac76", size = 249930, upload-time = "2025-08-17T00:25:52.422Z" }, + { url = "https://files.pythonhosted.org/packages/8d/66/d8907408612ffee100d731798e6090aedb3ba766ecf929df296c1a7ee4fb/coverage-7.10.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:3ddd912c2fc440f0fb3229e764feec85669d5d80a988ff1b336a27d73f63c818", size = 247862, upload-time = "2025-08-17T00:25:54.316Z" }, + { url = "https://files.pythonhosted.org/packages/29/db/53cd8ec8b1c9c52d8e22a25434785bfc2d1e70c0cfb4d278a1326c87f741/coverage-7.10.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8a538944ee3a42265e61c7298aeba9ea43f31c01271cf028f437a7b4075592cf", size = 249360, upload-time = "2025-08-17T00:25:55.833Z" }, + { url = "https://files.pythonhosted.org/packages/4f/75/5ec0a28ae4a0804124ea5a5becd2b0fa3adf30967ac656711fb5cdf67c60/coverage-7.10.4-cp314-cp314-win32.whl", hash = "sha256:fd2e6002be1c62476eb862b8514b1ba7e7684c50165f2a8d389e77da6c9a2ebd", size = 219449, upload-time = "2025-08-17T00:25:57.984Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ab/66e2ee085ec60672bf5250f11101ad8143b81f24989e8c0e575d16bb1e53/coverage-7.10.4-cp314-cp314-win_amd64.whl", hash = "sha256:ec113277f2b5cf188d95fb66a65c7431f2b9192ee7e6ec9b72b30bbfb53c244a", size = 220246, upload-time = "2025-08-17T00:25:59.868Z" }, + { url = "https://files.pythonhosted.org/packages/37/3b/00b448d385f149143190846217797d730b973c3c0ec2045a7e0f5db3a7d0/coverage-7.10.4-cp314-cp314-win_arm64.whl", hash = "sha256:9744954bfd387796c6a091b50d55ca7cac3d08767795b5eec69ad0f7dbf12d38", size = 218825, upload-time = "2025-08-17T00:26:01.44Z" }, + { url = "https://files.pythonhosted.org/packages/ee/2e/55e20d3d1ce00b513efb6fd35f13899e1c6d4f76c6cbcc9851c7227cd469/coverage-7.10.4-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:5af4829904dda6aabb54a23879f0f4412094ba9ef153aaa464e3c1b1c9bc98e6", size = 217462, upload-time = "2025-08-17T00:26:03.014Z" }, + { url = "https://files.pythonhosted.org/packages/47/b3/aab1260df5876f5921e2c57519e73a6f6eeacc0ae451e109d44ee747563e/coverage-7.10.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7bba5ed85e034831fac761ae506c0644d24fd5594727e174b5a73aff343a7508", size = 217675, upload-time = "2025-08-17T00:26:04.606Z" }, + { url = "https://files.pythonhosted.org/packages/67/23/1cfe2aa50c7026180989f0bfc242168ac7c8399ccc66eb816b171e0ab05e/coverage-7.10.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d57d555b0719834b55ad35045de6cc80fc2b28e05adb6b03c98479f9553b387f", size = 259176, upload-time = "2025-08-17T00:26:06.159Z" }, + { url = "https://files.pythonhosted.org/packages/9d/72/5882b6aeed3f9de7fc4049874fd7d24213bf1d06882f5c754c8a682606ec/coverage-7.10.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ba62c51a72048bb1ea72db265e6bd8beaabf9809cd2125bbb5306c6ce105f214", size = 261341, upload-time = "2025-08-17T00:26:08.137Z" }, + { url = "https://files.pythonhosted.org/packages/1b/70/a0c76e3087596ae155f8e71a49c2c534c58b92aeacaf4d9d0cbbf2dde53b/coverage-7.10.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0acf0c62a6095f07e9db4ec365cc58c0ef5babb757e54745a1aa2ea2a2564af1", size = 263600, upload-time = "2025-08-17T00:26:11.045Z" }, + { url = "https://files.pythonhosted.org/packages/cb/5f/27e4cd4505b9a3c05257fb7fc509acbc778c830c450cb4ace00bf2b7bda7/coverage-7.10.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e1033bf0f763f5cf49ffe6594314b11027dcc1073ac590b415ea93463466deec", size = 261036, upload-time = "2025-08-17T00:26:12.693Z" }, + { url = "https://files.pythonhosted.org/packages/02/d6/cf2ae3a7f90ab226ea765a104c4e76c5126f73c93a92eaea41e1dc6a1892/coverage-7.10.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:92c29eff894832b6a40da1789b1f252305af921750b03ee4535919db9179453d", size = 258794, upload-time = "2025-08-17T00:26:14.261Z" }, + { url = "https://files.pythonhosted.org/packages/9e/b1/39f222eab0d78aa2001cdb7852aa1140bba632db23a5cfd832218b496d6c/coverage-7.10.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:822c4c830989c2093527e92acd97be4638a44eb042b1bdc0e7a278d84a070bd3", size = 259946, upload-time = "2025-08-17T00:26:15.899Z" }, + { url = "https://files.pythonhosted.org/packages/74/b2/49d82acefe2fe7c777436a3097f928c7242a842538b190f66aac01f29321/coverage-7.10.4-cp314-cp314t-win32.whl", hash = "sha256:e694d855dac2e7cf194ba33653e4ba7aad7267a802a7b3fc4347d0517d5d65cd", size = 220226, upload-time = "2025-08-17T00:26:17.566Z" }, + { url = "https://files.pythonhosted.org/packages/06/b0/afb942b6b2fc30bdbc7b05b087beae11c2b0daaa08e160586cf012b6ad70/coverage-7.10.4-cp314-cp314t-win_amd64.whl", hash = "sha256:efcc54b38ef7d5bfa98050f220b415bc5bb3d432bd6350a861cf6da0ede2cdcd", size = 221346, upload-time = "2025-08-17T00:26:19.311Z" }, + { url = "https://files.pythonhosted.org/packages/d8/66/e0531c9d1525cb6eac5b5733c76f27f3053ee92665f83f8899516fea6e76/coverage-7.10.4-cp314-cp314t-win_arm64.whl", hash = "sha256:6f3a3496c0fa26bfac4ebc458747b778cff201c8ae94fa05e1391bab0dbc473c", size = 219368, upload-time = "2025-08-17T00:26:21.011Z" }, + { url = "https://files.pythonhosted.org/packages/d1/61/4e38d86d31a268778d69bb3fd1fc88e0c7a78ffdee48f2b5d9e028a3dce5/coverage-7.10.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:48fd4d52600c2a9d5622e52dfae674a7845c5e1dceaf68b88c99feb511fbcfd6", size = 216393, upload-time = "2025-08-17T00:26:22.648Z" }, + { url = "https://files.pythonhosted.org/packages/17/16/5c2fdb1d213f57e0ff107738397aff68582fa90a6575ca165b49eae5a809/coverage-7.10.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:56217b470d09d69e6b7dcae38200f95e389a77db801cb129101697a4553b18b6", size = 216779, upload-time = "2025-08-17T00:26:24.422Z" }, + { url = "https://files.pythonhosted.org/packages/26/99/3aca6b4028e3667ccfbaef9cfd9dca8d85eb14deee7868373cc48cbee553/coverage-7.10.4-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:44ac3f21a6e28c5ff7f7a47bca5f87885f6a1e623e637899125ba47acd87334d", size = 243214, upload-time = "2025-08-17T00:26:26.468Z" }, + { url = "https://files.pythonhosted.org/packages/0d/33/27a7d2557f85001b2edb6a2f14037851f87ca7d69a4ca79460e1859f4c7f/coverage-7.10.4-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3387739d72c84d17b4d2f7348749cac2e6700e7152026912b60998ee9a40066b", size = 245037, upload-time = "2025-08-17T00:26:28.071Z" }, + { url = "https://files.pythonhosted.org/packages/6d/68/92c0e18d36d34c774cb5053c9413188c27f8b3f9587e315193a30c1695ce/coverage-7.10.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f111ff20d9a6348e0125be892608e33408dd268f73b020940dfa8511ad05503", size = 246809, upload-time = "2025-08-17T00:26:29.828Z" }, + { url = "https://files.pythonhosted.org/packages/03/22/f0618594010903401e782459c100755af3f275ea86d49b0d4f3afa3658d9/coverage-7.10.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:01a852f0a9859734b018a3f483cc962d0b381d48d350b1a0c47d618c73a0c398", size = 244695, upload-time = "2025-08-17T00:26:31.495Z" }, + { url = "https://files.pythonhosted.org/packages/e5/45/e704923a037a4a38a3c13ae6405c31236db2d274307ab28fd1a23b961cad/coverage-7.10.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:225111dd06759ba4e37cee4c0b4f3df2b15c879e9e3c37bf986389300b9917c3", size = 242766, upload-time = "2025-08-17T00:26:33.425Z" }, + { url = "https://files.pythonhosted.org/packages/e4/b7/4dc6f2b41aa907ae330ed841deb49c9487f6ec5072a577fc3a3b284fe855/coverage-7.10.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2178d4183bd1ba608f0bb12e71e55838ba1b7dbb730264f8b08de9f8ef0c27d0", size = 243723, upload-time = "2025-08-17T00:26:35.688Z" }, + { url = "https://files.pythonhosted.org/packages/b7/62/0e58abc2ce2d9a5b906dd1c08802864f756365843c413aebf0184148ddfb/coverage-7.10.4-cp39-cp39-win32.whl", hash = "sha256:93d175fe81913aee7a6ea430abbdf2a79f1d9fd451610e12e334e4fe3264f563", size = 218927, upload-time = "2025-08-17T00:26:37.725Z" }, + { url = "https://files.pythonhosted.org/packages/a4/3e/4668a5b5601450d9c8aa71cc4f7e6c6c259350e577c758b894443598322a/coverage-7.10.4-cp39-cp39-win_amd64.whl", hash = "sha256:2221a823404bb941c7721cf0ef55ac6ee5c25d905beb60c0bba5e5e85415d353", size = 219838, upload-time = "2025-08-17T00:26:39.786Z" }, + { url = "https://files.pythonhosted.org/packages/bb/78/983efd23200921d9edb6bd40512e1aa04af553d7d5a171e50f9b2b45d109/coverage-7.10.4-py3-none-any.whl", hash = "sha256:065d75447228d05121e5c938ca8f0e91eed60a1eb2d1258d42d5084fecfc3302", size = 208365, upload-time = "2025-08-17T00:26:41.479Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + +[[package]] +name = "distlib" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, +] + +[[package]] +name = "filelock" +version = "3.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/40/bb/0ab3e58d22305b6f5440629d20683af28959bf793d98d11950e305c1c326/filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58", size = 17687, upload-time = "2025-08-14T16:56:03.016Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/14/42b2651a2f46b022ccd948bca9f2d5af0fd8929c4eec235b8d6d844fbe67/filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d", size = 15988, upload-time = "2025-08-14T16:56:01.633Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "h2" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "hpack" }, + { name = "hyperframe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1b/38/d7f80fd13e6582fb8e0df8c9a653dcc02b03ca34f4d72f34869298c5baf8/h2-4.2.0.tar.gz", hash = "sha256:c8a52129695e88b1a0578d8d2cc6842bbd79128ac685463b887ee278126ad01f", size = 2150682, upload-time = "2025-02-02T07:43:51.815Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/9e/984486f2d0a0bd2b024bf4bc1c62688fcafa9e61991f041fb0e2def4a982/h2-4.2.0-py3-none-any.whl", hash = "sha256:479a53ad425bb29af087f3458a61d30780bc818e4ebcf01f0b536ba916462ed0", size = 60957, upload-time = "2025-02-01T11:02:26.481Z" }, +] + +[[package]] +name = "hpack" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276, upload-time = "2025-01-22T21:44:58.347Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357, upload-time = "2025-01-22T21:44:56.92Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[package.optional-dependencies] +http2 = [ + { name = "h2" }, +] + +[[package]] +name = "hyperframe" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566, upload-time = "2025-01-22T21:41:49.302Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007, upload-time = "2025-01-22T21:41:47.295Z" }, +] + +[[package]] +name = "identify" +version = "2.6.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ca/ffbabe3635bb839aa36b3a893c91a9b0d368cb4d8073e03a12896970af82/identify-2.6.13.tar.gz", hash = "sha256:da8d6c828e773620e13bfa86ea601c5a5310ba4bcd65edf378198b56a1f9fb32", size = 99243, upload-time = "2025-08-09T19:35:00.6Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/ce/461b60a3ee109518c055953729bf9ed089a04db895d47e95444071dcdef2/identify-2.6.13-py2.py3-none-any.whl", hash = "sha256:60381139b3ae39447482ecc406944190f690d4a2997f2584062089848361b33b", size = 99153, upload-time = "2025-08-09T19:34:59.1Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pre-commit" +version = "4.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ff/29/7cf5bbc236333876e4b41f56e06857a87937ce4bf91e117a6991a2dbb02a/pre_commit-4.3.0.tar.gz", hash = "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16", size = 193792, upload-time = "2025-08-09T18:56:14.651Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8", size = 220965, upload-time = "2025-08-09T18:56:13.192Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pyjwt" +version = "2.10.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, +] + +[[package]] +name = "pytest-asyncio" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" }, + { name = "pytest" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4e/51/f8794af39eeb870e87a8c8068642fc07bce0c854d6865d7dd0f2a9d338c2/pytest_asyncio-1.1.0.tar.gz", hash = "sha256:796aa822981e01b68c12e4827b8697108f7205020f24b5793b3c41555dab68ea", size = 46652, upload-time = "2025-07-16T04:29:26.393Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/9d/bf86eddabf8c6c9cb1ea9a869d6873b46f105a5d292d3a6f7071f5b07935/pytest_asyncio-1.1.0-py3-none-any.whl", hash = "sha256:5fe2d69607b0bd75c656d1211f969cadba035030156745ee09e7d71740e58ecf", size = 15157, upload-time = "2025-07-16T04:29:24.929Z" }, +] + +[[package]] +name = "pytest-cov" +version = "6.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432, upload-time = "2025-06-12T10:47:47.684Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644, upload-time = "2025-06-12T10:47:45.932Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199, upload-time = "2024-08-06T20:31:40.178Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758, upload-time = "2024-08-06T20:31:42.173Z" }, + { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463, upload-time = "2024-08-06T20:31:44.263Z" }, + { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280, upload-time = "2024-08-06T20:31:50.199Z" }, + { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239, upload-time = "2024-08-06T20:31:52.292Z" }, + { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802, upload-time = "2024-08-06T20:31:53.836Z" }, + { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527, upload-time = "2024-08-06T20:31:55.565Z" }, + { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052, upload-time = "2024-08-06T20:31:56.914Z" }, + { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774, upload-time = "2024-08-06T20:31:58.304Z" }, + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, + { url = "https://files.pythonhosted.org/packages/65/d8/b7a1db13636d7fb7d4ff431593c510c8b8fca920ade06ca8ef20015493c5/PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", size = 184777, upload-time = "2024-08-06T20:33:25.896Z" }, + { url = "https://files.pythonhosted.org/packages/0a/02/6ec546cd45143fdf9840b2c6be8d875116a64076218b61d68e12548e5839/PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", size = 172318, upload-time = "2024-08-06T20:33:27.212Z" }, + { url = "https://files.pythonhosted.org/packages/0e/9a/8cc68be846c972bda34f6c2a93abb644fb2476f4dcc924d52175786932c9/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", size = 720891, upload-time = "2024-08-06T20:33:28.974Z" }, + { url = "https://files.pythonhosted.org/packages/e9/6c/6e1b7f40181bc4805e2e07f4abc10a88ce4648e7e95ff1abe4ae4014a9b2/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", size = 722614, upload-time = "2024-08-06T20:33:34.157Z" }, + { url = "https://files.pythonhosted.org/packages/3d/32/e7bd8535d22ea2874cef6a81021ba019474ace0d13a4819c2a4bce79bd6a/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", size = 737360, upload-time = "2024-08-06T20:33:35.84Z" }, + { url = "https://files.pythonhosted.org/packages/d7/12/7322c1e30b9be969670b672573d45479edef72c9a0deac3bb2868f5d7469/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", size = 699006, upload-time = "2024-08-06T20:33:37.501Z" }, + { url = "https://files.pythonhosted.org/packages/82/72/04fcad41ca56491995076630c3ec1e834be241664c0c09a64c9a2589b507/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", size = 723577, upload-time = "2024-08-06T20:33:39.389Z" }, + { url = "https://files.pythonhosted.org/packages/ed/5e/46168b1f2757f1fcd442bc3029cd8767d88a98c9c05770d8b420948743bb/PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", size = 144593, upload-time = "2024-08-06T20:33:46.63Z" }, + { url = "https://files.pythonhosted.org/packages/19/87/5124b1c1f2412bb95c59ec481eaf936cd32f0fe2a7b16b97b81c4c017a6a/PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", size = 162312, upload-time = "2024-08-06T20:33:49.073Z" }, +] + +[[package]] +name = "ruff" +version = "0.12.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/45/2e403fa7007816b5fbb324cb4f8ed3c7402a927a0a0cb2b6279879a8bfdc/ruff-0.12.9.tar.gz", hash = "sha256:fbd94b2e3c623f659962934e52c2bea6fc6da11f667a427a368adaf3af2c866a", size = 5254702, upload-time = "2025-08-14T16:08:55.2Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/20/53bf098537adb7b6a97d98fcdebf6e916fcd11b2e21d15f8c171507909cc/ruff-0.12.9-py3-none-linux_armv6l.whl", hash = "sha256:fcebc6c79fcae3f220d05585229463621f5dbf24d79fdc4936d9302e177cfa3e", size = 11759705, upload-time = "2025-08-14T16:08:12.968Z" }, + { url = "https://files.pythonhosted.org/packages/20/4d/c764ee423002aac1ec66b9d541285dd29d2c0640a8086c87de59ebbe80d5/ruff-0.12.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:aed9d15f8c5755c0e74467731a007fcad41f19bcce41cd75f768bbd687f8535f", size = 12527042, upload-time = "2025-08-14T16:08:16.54Z" }, + { url = "https://files.pythonhosted.org/packages/8b/45/cfcdf6d3eb5fc78a5b419e7e616d6ccba0013dc5b180522920af2897e1be/ruff-0.12.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5b15ea354c6ff0d7423814ba6d44be2807644d0c05e9ed60caca87e963e93f70", size = 11724457, upload-time = "2025-08-14T16:08:18.686Z" }, + { url = "https://files.pythonhosted.org/packages/72/e6/44615c754b55662200c48bebb02196dbb14111b6e266ab071b7e7297b4ec/ruff-0.12.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d596c2d0393c2502eaabfef723bd74ca35348a8dac4267d18a94910087807c53", size = 11949446, upload-time = "2025-08-14T16:08:21.059Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d1/9b7d46625d617c7df520d40d5ac6cdcdf20cbccb88fad4b5ecd476a6bb8d/ruff-0.12.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1b15599931a1a7a03c388b9c5df1bfa62be7ede6eb7ef753b272381f39c3d0ff", size = 11566350, upload-time = "2025-08-14T16:08:23.433Z" }, + { url = "https://files.pythonhosted.org/packages/59/20/b73132f66f2856bc29d2d263c6ca457f8476b0bbbe064dac3ac3337a270f/ruff-0.12.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d02faa2977fb6f3f32ddb7828e212b7dd499c59eb896ae6c03ea5c303575756", size = 13270430, upload-time = "2025-08-14T16:08:25.837Z" }, + { url = "https://files.pythonhosted.org/packages/a2/21/eaf3806f0a3d4c6be0a69d435646fba775b65f3f2097d54898b0fd4bb12e/ruff-0.12.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:17d5b6b0b3a25259b69ebcba87908496e6830e03acfb929ef9fd4c58675fa2ea", size = 14264717, upload-time = "2025-08-14T16:08:27.907Z" }, + { url = "https://files.pythonhosted.org/packages/d2/82/1d0c53bd37dcb582b2c521d352fbf4876b1e28bc0d8894344198f6c9950d/ruff-0.12.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:72db7521860e246adbb43f6ef464dd2a532ef2ef1f5dd0d470455b8d9f1773e0", size = 13684331, upload-time = "2025-08-14T16:08:30.352Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2f/1c5cf6d8f656306d42a686f1e207f71d7cebdcbe7b2aa18e4e8a0cb74da3/ruff-0.12.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a03242c1522b4e0885af63320ad754d53983c9599157ee33e77d748363c561ce", size = 12739151, upload-time = "2025-08-14T16:08:32.55Z" }, + { url = "https://files.pythonhosted.org/packages/47/09/25033198bff89b24d734e6479e39b1968e4c992e82262d61cdccaf11afb9/ruff-0.12.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fc83e4e9751e6c13b5046d7162f205d0a7bac5840183c5beebf824b08a27340", size = 12954992, upload-time = "2025-08-14T16:08:34.816Z" }, + { url = "https://files.pythonhosted.org/packages/52/8e/d0dbf2f9dca66c2d7131feefc386523404014968cd6d22f057763935ab32/ruff-0.12.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:881465ed56ba4dd26a691954650de6ad389a2d1fdb130fe51ff18a25639fe4bb", size = 12899569, upload-time = "2025-08-14T16:08:36.852Z" }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b614d7c08515b1428ed4d3f1d4e3d687deffb2479703b90237682586fa66/ruff-0.12.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:43f07a3ccfc62cdb4d3a3348bf0588358a66da756aa113e071b8ca8c3b9826af", size = 11751983, upload-time = "2025-08-14T16:08:39.314Z" }, + { url = "https://files.pythonhosted.org/packages/58/d6/383e9f818a2441b1a0ed898d7875f11273f10882f997388b2b51cb2ae8b5/ruff-0.12.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:07adb221c54b6bba24387911e5734357f042e5669fa5718920ee728aba3cbadc", size = 11538635, upload-time = "2025-08-14T16:08:41.297Z" }, + { url = "https://files.pythonhosted.org/packages/20/9c/56f869d314edaa9fc1f491706d1d8a47747b9d714130368fbd69ce9024e9/ruff-0.12.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f5cd34fabfdea3933ab85d72359f118035882a01bff15bd1d2b15261d85d5f66", size = 12534346, upload-time = "2025-08-14T16:08:43.39Z" }, + { url = "https://files.pythonhosted.org/packages/bd/4b/d8b95c6795a6c93b439bc913ee7a94fda42bb30a79285d47b80074003ee7/ruff-0.12.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f6be1d2ca0686c54564da8e7ee9e25f93bdd6868263805f8c0b8fc6a449db6d7", size = 13017021, upload-time = "2025-08-14T16:08:45.889Z" }, + { url = "https://files.pythonhosted.org/packages/c7/c1/5f9a839a697ce1acd7af44836f7c2181cdae5accd17a5cb85fcbd694075e/ruff-0.12.9-py3-none-win32.whl", hash = "sha256:cc7a37bd2509974379d0115cc5608a1a4a6c4bff1b452ea69db83c8855d53f93", size = 11734785, upload-time = "2025-08-14T16:08:48.062Z" }, + { url = "https://files.pythonhosted.org/packages/fa/66/cdddc2d1d9a9f677520b7cfc490d234336f523d4b429c1298de359a3be08/ruff-0.12.9-py3-none-win_amd64.whl", hash = "sha256:6fb15b1977309741d7d098c8a3cb7a30bc112760a00fb6efb7abc85f00ba5908", size = 12840654, upload-time = "2025-08-14T16:08:50.158Z" }, + { url = "https://files.pythonhosted.org/packages/ac/fd/669816bc6b5b93b9586f3c1d87cd6bc05028470b3ecfebb5938252c47a35/ruff-0.12.9-py3-none-win_arm64.whl", hash = "sha256:63c8c819739d86b96d500cce885956a1a48ab056bbcbc61b747ad494b2485089", size = 11949623, upload-time = "2025-08-14T16:08:52.233Z" }, +] + +[[package]] +name = "setuptools" +version = "58.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/00/05f51ceab8d3b9be4295000d8be4c830c53e5477755888994e9825606cd9/setuptools-58.5.3.tar.gz", hash = "sha256:dae6b934a965c8a59d6d230d3867ec408bb95e73bd538ff77e71fedf1eaca729", size = 2269854, upload-time = "2021-11-04T23:33:38.499Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/e9/84e2865fddfaba4506bc5d293d2a535bf27e31b12ca16d31564f8ce28cdb/setuptools-58.5.3-py3-none-any.whl", hash = "sha256:a481fbc56b33f5d8f6b33dce41482e64c68b668be44ff42922903b03872590bf", size = 946828, upload-time = "2021-11-04T23:33:37.023Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "strenum" +version = "0.4.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/ad/430fb60d90e1d112a62ff57bdd1f286ec73a2a0331272febfddd21f330e1/StrEnum-0.4.15.tar.gz", hash = "sha256:878fb5ab705442070e4dd1929bb5e2249511c0bcf2b0eeacf3bcd80875c82eff", size = 23384, upload-time = "2023-06-29T22:02:58.399Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/69/297302c5f5f59c862faa31e6cb9a4cd74721cd1e052b38e464c5b402df8b/StrEnum-0.4.15-py3-none-any.whl", hash = "sha256:a30cda4af7cc6b5bf52c8055bc4bf4b2b6b14a93b574626da33df53cf7740659", size = 8851, upload-time = "2023-06-29T22:02:56.947Z" }, +] + +[[package]] +name = "supabase-functions" +version = "0.10.1" +source = { editable = "." } +dependencies = [ + { name = "httpx", extra = ["http2"] }, + { name = "strenum" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pre-commit" }, + { name = "pyjwt" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-cov" }, + { name = "ruff" }, + { name = "unasync-cli" }, +] +lints = [ + { name = "pre-commit" }, + { name = "ruff" }, + { name = "unasync-cli" }, +] +tests = [ + { name = "pyjwt" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-cov" }, +] + +[package.metadata] +requires-dist = [ + { name = "httpx", extras = ["http2"], specifier = ">=0.26,<0.29" }, + { name = "strenum", specifier = ">=0.4.15" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "pre-commit", specifier = ">=3.4,<5.0" }, + { name = "pyjwt", specifier = ">=2.8.0" }, + { name = "pytest", specifier = ">=7.4.2,<9.0.0" }, + { name = "pytest-asyncio", specifier = ">=0.21.1,<1.2.0" }, + { name = "pytest-cov", specifier = ">=4,<7" }, + { name = "ruff", specifier = ">=0.12.1" }, + { name = "unasync-cli" }, +] +lints = [ + { name = "pre-commit", specifier = ">=3.4,<5.0" }, + { name = "ruff", specifier = ">=0.12.1" }, + { name = "unasync-cli" }, +] +tests = [ + { name = "pyjwt", specifier = ">=2.8.0" }, + { name = "pytest", specifier = ">=7.4.2,<9.0.0" }, + { name = "pytest-asyncio", specifier = ">=0.21.1,<1.2.0" }, + { name = "pytest-cov", specifier = ">=4,<7" }, +] + +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, +] + +[[package]] +name = "typer" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "click", version = "8.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/9e/8ae5ef480622282fc98507c0226e95de0accdf5e98ab7f57a5c8ba054339/typer-0.4.2.tar.gz", hash = "sha256:b8261c6c0152dd73478b5ba96ba677e5d6948c715c310f7c91079f311f62ec03", size = 223132, upload-time = "2022-07-02T18:05:28.861Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/ea/b565bc44a1af5278ef2ff9d571cdb4f4bc31fd450b0630441c93401c243c/typer-0.4.2-py3-none-any.whl", hash = "sha256:023bae00d1baf358a6cc7cea45851639360bb716de687b42b0a4641cd99173f1", size = 27955, upload-time = "2022-07-02T18:05:26.022Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.14.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, +] + +[[package]] +name = "unasync" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/ce/eaa16d97812685a14a3b04f8554c6a8ef432b9aee816c4e98dd5f3e75baa/unasync-0.5.0.tar.gz", hash = "sha256:b675d87cf56da68bd065d3b7a67ac71df85591978d84c53083c20d79a7e5096d", size = 17219, upload-time = "2020-05-03T04:34:18.407Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/56/ab76753489cab5a8e7dcbb1fb758f014de0b50b59d519c4f4c04edc79eda/unasync-0.5.0-py3-none-any.whl", hash = "sha256:8d4536dae85e87b8751dfcc776f7656fd0baf54bb022a7889440dc1b9dc3becb", size = 10225, upload-time = "2020-05-03T04:34:16.653Z" }, +] + +[[package]] +name = "unasync-cli" +version = "0.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "setuptools" }, + { name = "typer" }, + { name = "unasync" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/e0/93258a6bd980f456e6b6f9c438ebcac84e79df3e2c67c9970e551532db45/unasync-cli-0.0.9.tar.gz", hash = "sha256:ca9d8c57ebb68911f8f8f68f243c7f6d0bb246ee3fd14743bc51c8317e276554", size = 4413, upload-time = "2021-10-23T06:40:54.565Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/32/c466a04f93123302dc0a648654d6e16a2e5eca3961d9144a46a27f9f6b22/unasync_cli-0.0.9-py3-none-any.whl", hash = "sha256:f96c42fb2862efa555ce6d6415a5983ceb162aa0e45be701656d20a955c7c540", size = 4751, upload-time = "2021-10-23T06:40:53.012Z" }, +] + +[[package]] +name = "virtualenv" +version = "20.34.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/14/37fcdba2808a6c615681cd216fecae00413c9dab44fb2e57805ecf3eaee3/virtualenv-20.34.0.tar.gz", hash = "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a", size = 6003808, upload-time = "2025-08-13T14:24:07.464Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/06/04c8e804f813cf972e3262f3f8584c232de64f0cde9f703b46cf53a45090/virtualenv-20.34.0-py3-none-any.whl", hash = "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026", size = 5983279, upload-time = "2025-08-13T14:24:05.111Z" }, +] diff --git a/src/realtime/pyproject.toml b/src/realtime/pyproject.toml index 8b861ea6..44ba5f4e 100644 --- a/src/realtime/pyproject.toml +++ b/src/realtime/pyproject.toml @@ -8,7 +8,7 @@ authors = [ ] license = "MIT" readme = "README.md" -repository = "https://github.com/supabase/realtime-py" +repository = "https://github.com/supabase/supabase-py" requires-python = ">=3.9" dependencies = [ "websockets >=11,<16", diff --git a/src/supabase/pyproject.toml b/src/supabase/pyproject.toml index f60093c4..0de2b819 100644 --- a/src/supabase/pyproject.toml +++ b/src/supabase/pyproject.toml @@ -20,10 +20,10 @@ classifiers = [ requires-python = ">=3.9" dependencies = [ "realtime", + "supabase_functions", "postgrest == 1.1.1", "supabase_auth == 2.12.3", "storage3 == 0.12.0", - "supabase_functions == 0.10.1", "httpx >=0.26,<0.29", ] @@ -41,6 +41,7 @@ dev = [ pre-commit = [ "pre-commit >= 4.1.0", "commitizen >=4.8.3", + "unasync >=0.6.0", ] tests = [ "pytest >= 8.4.1", @@ -49,7 +50,6 @@ tests = [ "python-dotenv >= 1.1.1", ] lints = [ - "unasync-cli", "ruff >=0.12.1", ] diff --git a/uv.lock b/uv.lock index 7f5b0ba2..fb0da69a 100644 --- a/uv.lock +++ b/uv.lock @@ -10,6 +10,7 @@ resolution-markers = [ members = [ "realtime", "supabase", + "supabase-functions", ] [[package]] @@ -1681,6 +1682,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4c/9b/0b8aa09817b63e78d94b4977f18b1fcaead3165a5ee49251c5d5c245bb2d/ruff-0.12.7-py3-none-win_arm64.whl", hash = "sha256:dfce05101dbd11833a0776716d5d1578641b7fddb537fe7fa956ab85d1769b69", size = 11982083, upload-time = "2025-07-29T22:32:33.881Z" }, ] +[[package]] +name = "setuptools" +version = "80.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, +] + [[package]] name = "six" version = "1.17.0" @@ -1744,15 +1754,15 @@ dev = [ { name = "pytest-cov" }, { name = "python-dotenv" }, { name = "ruff" }, - { name = "unasync-cli" }, + { name = "unasync" }, ] lints = [ { name = "ruff" }, - { name = "unasync-cli" }, ] pre-commit = [ { name = "commitizen" }, { name = "pre-commit" }, + { name = "unasync" }, ] tests = [ { name = "pytest" }, @@ -1768,7 +1778,7 @@ requires-dist = [ { name = "realtime", editable = "src/realtime" }, { name = "storage3", specifier = "==0.12.0" }, { name = "supabase-auth", specifier = "==2.12.3" }, - { name = "supabase-functions", specifier = "==0.10.1" }, + { name = "supabase-functions", editable = "src/functions" }, ] [package.metadata.requires-dev] @@ -1780,15 +1790,13 @@ dev = [ { name = "pytest-cov", specifier = ">=6.2.1" }, { name = "python-dotenv", specifier = ">=1.1.1" }, { name = "ruff", specifier = ">=0.12.1" }, - { name = "unasync-cli" }, -] -lints = [ - { name = "ruff", specifier = ">=0.12.1" }, - { name = "unasync-cli" }, + { name = "unasync", specifier = ">=0.6.0" }, ] +lints = [{ name = "ruff", specifier = ">=0.12.1" }] pre-commit = [ { name = "commitizen", specifier = ">=4.8.3" }, { name = "pre-commit", specifier = ">=4.1.0" }, + { name = "unasync", specifier = ">=0.6.0" }, ] tests = [ { name = "pytest", specifier = ">=8.4.1" }, @@ -1814,14 +1822,60 @@ wheels = [ [[package]] name = "supabase-functions" version = "0.10.1" -source = { registry = "https://pypi.org/simple" } +source = { editable = "src/functions" } dependencies = [ { name = "httpx", extra = ["http2"] }, { name = "strenum" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6c/e4/6df7cd4366396553449e9907c745862ebf010305835b2bac99933dd7db9d/supabase_functions-0.10.1.tar.gz", hash = "sha256:4779d33a1cc3d4aea567f586b16d8efdb7cddcd6b40ce367c5fb24288af3a4f1", size = 5025, upload-time = "2025-06-23T18:26:12.239Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/06/060118a1e602c9bda8e4bf950bd1c8b5e1542349f2940ec57541266fabe1/supabase_functions-0.10.1-py3-none-any.whl", hash = "sha256:1db85e20210b465075aacee4e171332424f7305f9903c5918096be1423d6fcc5", size = 8275, upload-time = "2025-06-23T18:26:10.387Z" }, + +[package.dev-dependencies] +dev = [ + { name = "pre-commit" }, + { name = "pyjwt" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-cov" }, + { name = "ruff" }, + { name = "unasync" }, +] +lints = [ + { name = "pre-commit" }, + { name = "ruff" }, + { name = "unasync" }, +] +tests = [ + { name = "pyjwt" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-cov" }, +] + +[package.metadata] +requires-dist = [ + { name = "httpx", extras = ["http2"], specifier = ">=0.26,<0.29" }, + { name = "strenum", specifier = ">=0.4.15" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "pre-commit", specifier = ">=3.4,<5.0" }, + { name = "pyjwt", specifier = ">=2.8.0" }, + { name = "pytest", specifier = ">=7.4.2,<9.0.0" }, + { name = "pytest-asyncio", specifier = ">=0.21.1,<1.2.0" }, + { name = "pytest-cov", specifier = ">=4,<7" }, + { name = "ruff", specifier = ">=0.12.1" }, + { name = "unasync", specifier = ">=0.6.0" }, +] +lints = [ + { name = "pre-commit", specifier = ">=3.4,<5.0" }, + { name = "ruff", specifier = ">=0.12.1" }, + { name = "unasync", specifier = ">=0.6.0" }, +] +tests = [ + { name = "pyjwt", specifier = ">=2.8.0" }, + { name = "pytest", specifier = ">=7.4.2,<9.0.0" }, + { name = "pytest-asyncio", specifier = ">=0.21.1,<1.2.0" }, + { name = "pytest-cov", specifier = ">=4,<7" }, ] [[package]] @@ -1833,6 +1887,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4f/bd/de8d508070629b6d84a30d01d57e4a65c69aa7f5abe7560b8fad3b50ea59/termcolor-3.1.0-py3-none-any.whl", hash = "sha256:591dd26b5c2ce03b9e43f391264626557873ce1d379019786f99b0c2bee140aa", size = 7684, upload-time = "2025-04-30T11:37:52.382Z" }, ] +[[package]] +name = "tokenize-rt" +version = "6.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/69/ed/8f07e893132d5051d86a553e749d5c89b2a4776eb3a579b72ed61f8559ca/tokenize_rt-6.2.0.tar.gz", hash = "sha256:8439c042b330c553fdbe1758e4a05c0ed460dbbbb24a606f11f0dee75da4cad6", size = 5476, upload-time = "2025-05-23T23:48:00.035Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl", hash = "sha256:a152bf4f249c847a66497a4a95f63376ed68ac6abf092a2f7cfb29d044ecff44", size = 6004, upload-time = "2025-05-23T23:47:58.812Z" }, +] + [[package]] name = "tomli" version = "2.2.1" @@ -1881,18 +1944,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901, upload-time = "2025-06-05T07:13:43.546Z" }, ] -[[package]] -name = "typer" -version = "0.4.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fb/9e/8ae5ef480622282fc98507c0226e95de0accdf5e98ab7f57a5c8ba054339/typer-0.4.2.tar.gz", hash = "sha256:b8261c6c0152dd73478b5ba96ba677e5d6948c715c310f7c91079f311f62ec03", size = 223132, upload-time = "2022-07-02T18:05:28.861Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/ea/b565bc44a1af5278ef2ff9d571cdb4f4bc31fd450b0630441c93401c243c/typer-0.4.2-py3-none-any.whl", hash = "sha256:023bae00d1baf358a6cc7cea45851639360bb716de687b42b0a4641cd99173f1", size = 27955, upload-time = "2022-07-02T18:05:26.022Z" }, -] - [[package]] name = "typing-extensions" version = "4.14.1" @@ -1985,15 +2036,16 @@ wheels = [ ] [[package]] -name = "unasync-cli" -version = "0.0.1" +name = "unasync" +version = "0.6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typer" }, + { name = "setuptools" }, + { name = "tokenize-rt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a5/98/a896214f2c842e5a61519fe81ecf50f487dbc271b97e19c6c1dd2fe04b40/unasync-cli-0.0.1.tar.gz", hash = "sha256:7270085c8c008ea329ab7f4feeb5730c85e5ecd4996330207b468215c3137ad1", size = 2970, upload-time = "2021-10-21T00:42:14.02Z" } +sdist = { url = "https://files.pythonhosted.org/packages/28/4e/735dbc0885ca197bcd80a2479ca24035627e2e768c784261fc7f1b8d7600/unasync-0.6.0.tar.gz", hash = "sha256:a9d01ace3e1068b20550ab15b7f9723b15b8bcde728bc1770bcb578374c7ee58", size = 18755, upload-time = "2024-05-03T11:14:58.312Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/59/2d9f2d9a86514fab0dbf0c59e11317ea1116db76f34c84348616eb9132aa/unasync_cli-0.0.1-py3-none-any.whl", hash = "sha256:e3c65b1266824e7a12e16ea7752fc36fcaf8019a37c432bdf169b3d33a24280e", size = 3538, upload-time = "2021-10-21T00:42:12.45Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b5/d2842541718ffa12060854735587543120a31ebc339435e0bd0faf368541/unasync-0.6.0-py3-none-any.whl", hash = "sha256:9cf7aaaea9737e417d8949bf9be55dc25fdb4ef1f4edc21b58f76ff0d2b9d73f", size = 9959, upload-time = "2024-05-03T11:14:56.17Z" }, ] [[package]]