├── tests ├── __init__.py ├── _sync │ ├── __init__.py │ └── test_function_client.py ├── _async │ ├── __init__.py │ └── test_function_client.py ├── test_client.py ├── test_errors.py └── test_utils.py ├── .github ├── CODEOWNERS ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ ├── stale.yml │ ├── conventional-commits.yml │ ├── conventional-commits-lint.js │ └── ci.yml ├── .release-please-manifest.json ├── supabase_functions ├── _async │ ├── __init__.py │ └── functions_client.py ├── _sync │ ├── __init__.py │ └── functions_client.py ├── version.py ├── __init__.py ├── errors.py └── utils.py ├── .coveragerc ├── release-please-config.json ├── README.md ├── .pre-commit-config.yaml ├── pyproject.toml ├── Makefile ├── LICENSE ├── conftest.py ├── .gitignore ├── CHANGELOG.md └── poetry.lock /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/_sync/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/_async/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @J0 @silentworks 2 | -------------------------------------------------------------------------------- /.release-please-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | ".": "0.10.1" 3 | } 4 | -------------------------------------------------------------------------------- /supabase_functions/_async/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | -------------------------------------------------------------------------------- /supabase_functions/_sync/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | -------------------------------------------------------------------------------- /supabase_functions/version.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.10.1" # {x-release-please-version} 2 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = 3 | build_sync.py 4 | conftest.py 5 | tests/* 6 | -------------------------------------------------------------------------------- /release-please-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "last-release-sha": "919eed01befa14a36a61133061e6832b09c5efd7", 3 | "packages": { 4 | ".": { 5 | "changelog-path": "CHANGELOG.md", 6 | "release-type": "python" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | target-branch: "main" 8 | - package-ecosystem: "pip" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | target-branch: "main" 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!WARNING] 2 | > This repository has been moved to the [supabase-py monorepo](https://github.com/supabase/supabase-py/tree/main/src/functions). 3 | 4 | # Functions-py 5 | 6 | 7 | ## Installation 8 | 9 | `pip3 install supabase_functions` 10 | 11 | ## Usage 12 | 13 | Deploy your function as per documentation. 14 | 15 | 16 | ```python3 17 | import asyncio 18 | from supabase_functions import AsyncFunctionsClient 19 | async def run_func(): 20 | fc = AsyncFunctionsClient("https://.functions.supabase.co", {}) 21 | res = await fc.invoke("payment-sheet", {"responseType": "json"}) 22 | 23 | if __name__ == "__main__": 24 | asyncio.run(run_func()) 25 | ``` 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | exclude: '^.*\.(md|MD)$' 2 | repos: 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v5.0.0 5 | hooks: 6 | - id: trailing-whitespace 7 | - id: check-added-large-files 8 | - id: end-of-file-fixer 9 | - id: mixed-line-ending 10 | args: ["--fix=lf"] 11 | 12 | - repo: https://github.com/astral-sh/ruff-pre-commit 13 | # Ruff version. 14 | rev: v0.12.1 15 | hooks: 16 | # Run the linter. 17 | - id: ruff 18 | types_or: [ python, pyi ] 19 | args: [ --fix ] 20 | # Run the formatter. 21 | - id: ruff-format 22 | types_or: [ python, pyi ] 23 | 24 | - repo: https://github.com/commitizen-tools/commitizen 25 | rev: v3.22.0 26 | hooks: 27 | - id: commitizen 28 | stages: [commit-msg] 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Stale Issues & PRs 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | 7 | permissions: 8 | contents: write 9 | issues: write 10 | pull-requests: write 11 | 12 | jobs: 13 | mark_stale: 14 | name: Mark issues and PRs as Stale 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/stale@v9 18 | with: 19 | days-before-pr-stale: 365 20 | days-before-pr-close: -1 21 | days-before-issue-stale: 365 22 | days-before-issue-close: -1 23 | stale-issue-message: > 24 | This issue is stale because it has been open for 365 days with no activity. 25 | stale-pr-message: > 26 | This pull request is stale because it has been open for 365 days with no activity. 27 | close-issue-message: > 28 | This issue has been marked as stale and closed due to inactivity. 29 | close-pr-message: > 30 | This pull request has been marked as stale and closed due to inactivity. 31 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "supabase_functions" 3 | version = "0.10.1" # {x-release-please-version} 4 | description = "Library for Supabase Functions" 5 | authors = [ 6 | "Joel Lee ", 7 | "Andrew Smith " 8 | ] 9 | license = "MIT" 10 | readme = "README.md" 11 | repository = "https://github.com/supabase/functions-py" 12 | 13 | [tool.poetry.dependencies] 14 | python = "^3.9" 15 | httpx = {version = ">=0.26,<0.29", extras = ["http2"]} 16 | strenum = "^0.4.15" 17 | 18 | [tool.poetry.group.dev.dependencies] 19 | pre-commit = ">=3.4,<5.0" 20 | pyjwt = "^2.8.0" 21 | pytest = ">=7.4.2,<9.0.0" 22 | pytest-cov = ">=4,<7" 23 | unasync-cli = "^0.0.9" 24 | pytest-asyncio = ">=0.21.1,<1.2.0" 25 | ruff = "^0.12.1" 26 | 27 | [tool.pytest.ini_options] 28 | asyncio_mode = "auto" 29 | addopts = "tests" 30 | filterwarnings = [ 31 | "ignore::DeprecationWarning", # ignore deprecation warnings globally 32 | ] 33 | 34 | [build-system] 35 | requires = ["poetry-core>=1.0.0"] 36 | build-backend = "poetry.core.masonry.api" 37 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | install: 2 | poetry install 3 | 4 | install_poetry: 5 | curl -sSL https://install.python-poetry.org | python - 6 | poetry install 7 | 8 | tests: install tests_only tests_pre_commit 9 | 10 | tests_pre_commit: 11 | poetry run pre-commit run --all-files 12 | 13 | run_tests: tests 14 | 15 | tests_only: 16 | poetry run pytest --cov=./ --cov-report=xml --cov-report=html -vv 17 | 18 | build_sync: 19 | poetry run unasync supabase_functions tests 20 | sed -i '0,/SyncMock, /{s/SyncMock, //}' tests/_sync/test_function_client.py 21 | sed -i 's/SyncMock/Mock/g' tests/_sync/test_function_client.py 22 | sed -i 's/SyncClient/Client/g' supabase_functions/_sync/functions_client.py tests/_sync/test_function_client.py 23 | 24 | 25 | rename_project: rename_package_dir rename_package 26 | 27 | rename_package_dir: 28 | mv supabase_functions supafunc 29 | 30 | rename_package: 31 | sed -i 's/supabase_functions/supafunc/g' pyproject.toml tests/test_client.py tests/test_errors.py tests/test_utils.py tests/_async/test_function_client.py tests/_sync/test_function_client.py README.md 32 | 33 | build_package: 34 | poetry build 35 | -------------------------------------------------------------------------------- /supabase_functions/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Literal, Union, overload 4 | 5 | from ._async.functions_client import AsyncFunctionsClient 6 | from ._sync.functions_client import SyncFunctionsClient 7 | from .utils import FunctionRegion 8 | 9 | __all__ = [ 10 | "create_client", 11 | "FunctionRegion", 12 | "AsyncFunctionsClient", 13 | "SyncFunctionsClient", 14 | ] 15 | 16 | 17 | @overload 18 | def create_client( 19 | url: str, headers: dict[str, str], *, is_async: Literal[True], verify: bool 20 | ) -> AsyncFunctionsClient: ... 21 | 22 | 23 | @overload 24 | def create_client( 25 | url: str, headers: dict[str, str], *, is_async: Literal[False], verify: bool 26 | ) -> SyncFunctionsClient: ... 27 | 28 | 29 | def create_client( 30 | url: str, 31 | headers: dict[str, str], 32 | *, 33 | is_async: bool, 34 | verify: bool = True, 35 | ) -> Union[AsyncFunctionsClient, SyncFunctionsClient]: 36 | if is_async: 37 | return AsyncFunctionsClient(url, headers, verify) 38 | else: 39 | return SyncFunctionsClient(url, headers, verify) 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Supabase 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/conventional-commits.yml: -------------------------------------------------------------------------------- 1 | name: Check pull requests 2 | 3 | on: 4 | push: 5 | branches-ignore: # Run the checks on all branches but the protected ones 6 | - main 7 | - release/* 8 | 9 | pull_request_target: 10 | branches: 11 | - main 12 | - release/* 13 | types: 14 | - opened 15 | - edited 16 | - reopened 17 | - ready_for_review 18 | 19 | permissions: 20 | contents: read 21 | 22 | jobs: 23 | check-conventional-commits: 24 | runs-on: ubuntu-latest 25 | 26 | steps: 27 | - uses: actions/checkout@v4 28 | with: 29 | sparse-checkout: | 30 | .github 31 | 32 | - if: ${{ github.event_name == 'pull_request_target' }} 33 | run: | 34 | set -ex 35 | 36 | node .github/workflows/conventional-commits-lint.js pr < None: 14 | super().__init__(message) 15 | self.message = message 16 | self.name = name 17 | self.status = status 18 | 19 | def to_dict(self) -> FunctionsApiErrorDict: 20 | return { 21 | "name": self.name, 22 | "message": self.message, 23 | "status": self.status, 24 | } 25 | 26 | 27 | class FunctionsHttpError(FunctionsError): 28 | def __init__(self, message: str, code: int | None = None) -> None: 29 | super().__init__( 30 | message, 31 | "FunctionsHttpError", 32 | 400 if code is None else code, 33 | ) 34 | 35 | 36 | class FunctionsRelayError(FunctionsError): 37 | """Base exception for relay errors.""" 38 | 39 | def __init__(self, message: str, code: int | None = None) -> None: 40 | super().__init__( 41 | message, 42 | "FunctionsRelayError", 43 | 400 if code is None else code, 44 | ) 45 | -------------------------------------------------------------------------------- /tests/test_client.py: -------------------------------------------------------------------------------- 1 | from typing import Dict 2 | 3 | import pytest 4 | 5 | from supabase_functions import AsyncFunctionsClient, SyncFunctionsClient, create_client 6 | 7 | 8 | @pytest.fixture 9 | def valid_url() -> str: 10 | return "https://example.com" 11 | 12 | 13 | @pytest.fixture 14 | def valid_headers() -> Dict[str, str]: 15 | return {"Authorization": "Bearer test_token", "Content-Type": "application/json"} 16 | 17 | 18 | def test_create_async_client(valid_url, valid_headers): 19 | # Test creating async client with explicit verify=True 20 | client = create_client( 21 | url=valid_url, headers=valid_headers, is_async=True, verify=True 22 | ) 23 | 24 | assert isinstance(client, AsyncFunctionsClient) 25 | assert client.url == valid_url 26 | assert all(client.headers[key] == value for key, value in valid_headers.items()) 27 | 28 | 29 | def test_create_sync_client(valid_url, valid_headers): 30 | # Test creating sync client with explicit verify=True 31 | client = create_client( 32 | url=valid_url, headers=valid_headers, is_async=False, verify=True 33 | ) 34 | 35 | assert isinstance(client, SyncFunctionsClient) 36 | assert client.url == valid_url 37 | assert all(client.headers[key] == value for key, value in valid_headers.items()) 38 | 39 | 40 | def test_type_hints(): 41 | from typing import Union, get_type_hints 42 | 43 | hints = get_type_hints(create_client) 44 | 45 | assert hints["url"] is str 46 | assert hints["headers"] == dict[str, str] 47 | assert hints["is_async"] is bool 48 | assert hints["verify"] is bool 49 | assert hints["return"] == Union[AsyncFunctionsClient, SyncFunctionsClient] 50 | -------------------------------------------------------------------------------- /supabase_functions/utils.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from urllib.parse import urlparse 3 | from warnings import warn 4 | 5 | from httpx import AsyncClient as AsyncClient # noqa: F401 6 | from httpx import Client as BaseClient 7 | 8 | if sys.version_info >= (3, 11): 9 | from enum import StrEnum 10 | else: 11 | from strenum import StrEnum 12 | 13 | 14 | DEFAULT_FUNCTION_CLIENT_TIMEOUT = 5 15 | BASE64URL_REGEX = r"^([a-z0-9_-]{4})*($|[a-z0-9_-]{3}$|[a-z0-9_-]{2}$)$" 16 | 17 | 18 | class FunctionRegion(StrEnum): 19 | Any = "any" 20 | ApNortheast1 = "ap-northeast-1" 21 | ApNortheast2 = "ap-northeast-2" 22 | ApSouth1 = "ap-south-1" 23 | ApSoutheast1 = "ap-southeast-1" 24 | ApSoutheast2 = "ap-southeast-2" 25 | CaCentral1 = "ca-central-1" 26 | EuCentral1 = "eu-central-1" 27 | EuWest1 = "eu-west-1" 28 | EuWest2 = "eu-west-2" 29 | EuWest3 = "eu-west-3" 30 | SaEast1 = "sa-east-1" 31 | UsEast1 = "us-east-1" 32 | UsWest1 = "us-west-1" 33 | UsWest2 = "us-west-2" 34 | 35 | 36 | class SyncClient(BaseClient): 37 | def __init__(self, *args, **kwargs): 38 | warn( 39 | "The 'SyncClient' class is deprecated. Please use `Client` from the httpx package instead.", 40 | DeprecationWarning, 41 | stacklevel=2, 42 | ) 43 | 44 | super().__init__(*args, **kwargs) 45 | 46 | def aclose(self) -> None: 47 | warn( 48 | "The 'aclose' method is deprecated. Please use `close` method from `Client` in the httpx package instead.", 49 | DeprecationWarning, 50 | stacklevel=2, 51 | ) 52 | self.close() 53 | 54 | 55 | def is_valid_str_arg(target: str) -> bool: 56 | return isinstance(target, str) and len(target.strip()) > 0 57 | 58 | 59 | def is_http_url(url: str) -> bool: 60 | return urlparse(url).scheme in {"https", "http"} 61 | -------------------------------------------------------------------------------- /conftest.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Tuple 2 | 3 | import pytest 4 | 5 | # store history of failures per test class name and per index 6 | # in parametrize (if parametrize used) 7 | _test_failed_incremental: Dict[str, Dict[Tuple[int, ...], str]] = {} 8 | 9 | 10 | def pytest_runtest_makereport(item, call): 11 | if "incremental" in item.keywords: 12 | # incremental marker is used 13 | if call.excinfo is not None: 14 | # the test has failed 15 | # retrieve the class name of the test 16 | cls_name = str(item.cls) 17 | # retrieve the index of the test (if parametrize is used 18 | # in combination with incremental) 19 | parametrize_index = ( 20 | tuple(item.callspec.indices.values()) 21 | if hasattr(item, "callspec") 22 | else () 23 | ) 24 | # retrieve the name of the test function 25 | test_name = item.originalname or item.name 26 | # store in _test_failed_incremental the original name of the failed test 27 | _test_failed_incremental.setdefault(cls_name, {}).setdefault( 28 | parametrize_index, test_name 29 | ) 30 | 31 | 32 | def pytest_runtest_setup(item): 33 | if "incremental" in item.keywords: 34 | # retrieve the class name of the test 35 | cls_name = str(item.cls) 36 | # check if a previous test has failed for this class 37 | if cls_name in _test_failed_incremental: 38 | # retrieve the index of the test (if parametrize is used 39 | # in combination with incremental) 40 | parametrize_index = ( 41 | tuple(item.callspec.indices.values()) 42 | if hasattr(item, "callspec") 43 | else () 44 | ) 45 | # retrieve the name of the first test function to 46 | # fail for this class name and index 47 | test_name = _test_failed_incremental[cls_name].get(parametrize_index, None) 48 | # if name found, test has failed for the combination of 49 | # class name & test name 50 | if test_name is not None: 51 | pytest.xfail(f"previous test failed ({test_name})") 52 | -------------------------------------------------------------------------------- /.github/workflows/conventional-commits-lint.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const fs = require("fs"); 4 | 5 | const TITLE_PATTERN = 6 | /^(?[^:!(]+)(?\([^)]+\))?(?[!])?:.+$/; 7 | const RELEASE_AS_DIRECTIVE = /^\s*Release-As:/im; 8 | const BREAKING_CHANGE_DIRECTIVE = /^\s*BREAKING[ \t]+CHANGE:/im; 9 | 10 | const ALLOWED_CONVENTIONAL_COMMIT_PREFIXES = [ 11 | "revert", 12 | "feat", 13 | "fix", 14 | "ci", 15 | "docs", 16 | "chore", 17 | "style", 18 | "test", 19 | "refactor", 20 | ]; 21 | 22 | const object = process.argv[2]; 23 | const payload = JSON.parse(fs.readFileSync(process.stdin.fd, "utf-8")); 24 | 25 | let validate = []; 26 | 27 | if (object === "pr") { 28 | validate.push({ 29 | title: payload.pull_request.title, 30 | content: payload.pull_request.body, 31 | }); 32 | } else if (object === "push") { 33 | validate.push( 34 | ...payload.commits 35 | .map((commit) => ({ 36 | title: commit.message.split("\n")[0], 37 | content: commit.message, 38 | })) 39 | .filter(({ title }) => !title.startsWith("Merge branch ") && !title.startsWith("Revert ")), 40 | ); 41 | } else { 42 | console.error( 43 | `Unknown object for first argument "${object}", use 'pr' or 'push'.`, 44 | ); 45 | process.exit(0); 46 | } 47 | 48 | let failed = false; 49 | 50 | validate.forEach((payload) => { 51 | if (payload.title) { 52 | const match = payload.title.match(TITLE_PATTERN); 53 | if (!match) { 54 | return 55 | } 56 | 57 | const { groups } = match 58 | 59 | if (groups) { 60 | if ( 61 | !ALLOWED_CONVENTIONAL_COMMIT_PREFIXES.find( 62 | (prefix) => prefix === groups.prefix, 63 | ) 64 | ) { 65 | console.error( 66 | `PR (or a commit in it) is using a disallowed conventional commit prefix ("${groups.prefix}"). Only ${ALLOWED_CONVENTIONAL_COMMIT_PREFIXES.join(", ")} are allowed. Make sure the prefix is lowercase!`, 67 | ); 68 | failed = true; 69 | } 70 | } else { 71 | console.error( 72 | "PR or commit title must match conventional commit structure.", 73 | ); 74 | failed = true; 75 | } 76 | } 77 | 78 | if (payload.content) { 79 | if (payload.content.match(RELEASE_AS_DIRECTIVE)) { 80 | console.error( 81 | "PR descriptions or commit messages must not contain Release-As conventional commit directives.", 82 | ); 83 | failed = true; 84 | } 85 | } 86 | }); 87 | 88 | if (failed) { 89 | process.exit(1); 90 | } 91 | 92 | process.exit(0); 93 | -------------------------------------------------------------------------------- /tests/test_errors.py: -------------------------------------------------------------------------------- 1 | from typing import Type 2 | 3 | import pytest 4 | 5 | from supabase_functions.errors import ( 6 | FunctionsApiErrorDict, 7 | FunctionsError, 8 | FunctionsHttpError, 9 | FunctionsRelayError, 10 | ) 11 | 12 | 13 | @pytest.mark.parametrize( 14 | "error_class,expected_name,expected_status", 15 | [ 16 | (FunctionsError, "test_error", 500), 17 | (FunctionsHttpError, "FunctionsHttpError", 400), 18 | (FunctionsRelayError, "FunctionsRelayError", 400), 19 | ], 20 | ) 21 | def test_error_initialization( 22 | error_class: Type[FunctionsError], expected_name: str, expected_status: int 23 | ): 24 | test_message = "Test error message" 25 | 26 | if error_class is FunctionsError: 27 | error = error_class(test_message, expected_name, expected_status) 28 | else: 29 | error = error_class(test_message) 30 | 31 | assert str(error) == test_message 32 | assert error.message == test_message 33 | assert error.name == expected_name 34 | assert error.status == expected_status 35 | assert isinstance(error, Exception) 36 | 37 | 38 | @pytest.mark.parametrize( 39 | "error_class,expected_name,expected_status", 40 | [ 41 | (FunctionsError, "test_error", 500), 42 | (FunctionsHttpError, "FunctionsHttpError", 400), 43 | (FunctionsRelayError, "FunctionsRelayError", 400), 44 | ], 45 | ) 46 | def test_error_to_dict( 47 | error_class: Type[FunctionsError], expected_name: str, expected_status: int 48 | ): 49 | test_message = "Test error message" 50 | 51 | if error_class is FunctionsError: 52 | error = error_class(test_message, expected_name, expected_status) 53 | else: 54 | error = error_class(test_message) 55 | 56 | error_dict = error.to_dict() 57 | 58 | assert isinstance(error_dict, dict) 59 | assert error_dict["message"] == test_message 60 | assert error_dict["name"] == expected_name 61 | assert error_dict["status"] == expected_status 62 | 63 | # Verify the dict matches the TypedDict structure 64 | typed_dict: FunctionsApiErrorDict = error_dict 65 | assert isinstance(typed_dict["name"], str) 66 | assert isinstance(typed_dict["message"], str) 67 | assert isinstance(typed_dict["status"], int) 68 | 69 | 70 | def test_functions_error_inheritance(): 71 | # Test that all error classes inherit from FunctionsError 72 | assert issubclass(FunctionsHttpError, FunctionsError) 73 | assert issubclass(FunctionsRelayError, FunctionsError) 74 | 75 | 76 | def test_error_as_exception(): 77 | # Test that errors can be raised and caught 78 | test_message = "Test exception" 79 | 80 | # Test base error 81 | with pytest.raises(FunctionsError) as exc_info: 82 | raise FunctionsError(test_message, "test_error", 500) 83 | assert str(exc_info.value) == test_message 84 | 85 | # Test HTTP error 86 | with pytest.raises(FunctionsHttpError) as exc_info: 87 | raise FunctionsHttpError(test_message) 88 | assert str(exc_info.value) == test_message 89 | 90 | # Test Relay error 91 | with pytest.raises(FunctionsRelayError) as exc_info: 92 | raise FunctionsRelayError(test_message) 93 | assert str(exc_info.value) == test_message 94 | 95 | 96 | def test_error_message_types(): 97 | # Test that errors handle different message types appropriately 98 | test_cases = [ 99 | "Simple string", 100 | "String with unicode: 你好", 101 | "String with special chars: !@#$%^&*()", 102 | "", # Empty string 103 | "A" * 1000, # Long string 104 | ] 105 | 106 | for message in test_cases: 107 | error = FunctionsError(message, "test", 500) 108 | assert error.message == message 109 | assert error.to_dict()["message"] == message 110 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from typing import Any 3 | 4 | import pytest 5 | 6 | from supabase_functions.utils import ( 7 | BASE64URL_REGEX, 8 | FunctionRegion, 9 | SyncClient, 10 | is_http_url, 11 | is_valid_str_arg, 12 | ) 13 | 14 | 15 | def test_function_region_values(): 16 | assert FunctionRegion.Any.value == "any" 17 | assert FunctionRegion.ApNortheast1.value == "ap-northeast-1" 18 | assert FunctionRegion.ApNortheast2.value == "ap-northeast-2" 19 | assert FunctionRegion.ApSouth1.value == "ap-south-1" 20 | assert FunctionRegion.ApSoutheast1.value == "ap-southeast-1" 21 | assert FunctionRegion.ApSoutheast2.value == "ap-southeast-2" 22 | assert FunctionRegion.CaCentral1.value == "ca-central-1" 23 | assert FunctionRegion.EuCentral1.value == "eu-central-1" 24 | assert FunctionRegion.EuWest1.value == "eu-west-1" 25 | assert FunctionRegion.EuWest2.value == "eu-west-2" 26 | assert FunctionRegion.EuWest3.value == "eu-west-3" 27 | assert FunctionRegion.SaEast1.value == "sa-east-1" 28 | assert FunctionRegion.UsEast1.value == "us-east-1" 29 | assert FunctionRegion.UsWest1.value == "us-west-1" 30 | assert FunctionRegion.UsWest2.value == "us-west-2" 31 | 32 | 33 | def test_sync_client(): 34 | client = SyncClient() 35 | # Verify that aclose method exists and calls close 36 | assert hasattr(client, "aclose") 37 | assert callable(client.aclose) 38 | client.aclose() # Should not raise any exception 39 | 40 | 41 | @pytest.mark.parametrize( 42 | "test_input,expected", 43 | [ 44 | ("valid_string", True), 45 | (" spaced_string ", True), 46 | ("", False), 47 | (" ", False), 48 | (None, False), 49 | (123, False), 50 | ([], False), 51 | ({}, False), 52 | ], 53 | ) 54 | def test_is_valid_str_arg(test_input: Any, expected: bool): 55 | assert is_valid_str_arg(test_input) == expected 56 | 57 | 58 | @pytest.mark.parametrize( 59 | "test_input,expected", 60 | [ 61 | ("https://example.com", True), 62 | ("http://localhost", True), 63 | ("http://127.0.0.1:8000", True), 64 | ("https://api.supabase.com", True), 65 | ("ftp://example.com", False), 66 | ("ws://example.com", False), 67 | ("not-a-url", False), 68 | ("", False), 69 | ], 70 | ) 71 | def test_is_http_url(test_input: str, expected: bool): 72 | assert is_http_url(test_input) == expected 73 | 74 | 75 | def test_base64url_regex(): 76 | import re 77 | 78 | pattern = re.compile(BASE64URL_REGEX, re.IGNORECASE) 79 | 80 | # Valid base64url strings 81 | assert pattern.match("abcd") 82 | assert pattern.match("1234") 83 | assert pattern.match("abc") 84 | assert pattern.match("12") 85 | assert pattern.match("ab") 86 | assert pattern.match("ABCD") 87 | assert pattern.match("ABC") 88 | assert pattern.match("AB") 89 | assert pattern.match("a-b_") 90 | 91 | # Invalid base64url strings 92 | assert not pattern.match("a") # too short 93 | assert not pattern.match("abcde") # invalid length 94 | assert not pattern.match("abc!") # invalid character 95 | assert not pattern.match("abc@") # invalid character 96 | 97 | 98 | @pytest.mark.skipif( 99 | sys.version_info < (3, 11), 100 | reason="StrEnum import test only relevant for Python 3.11+", 101 | ) 102 | def test_strenum_import_python_311_plus(): 103 | from enum import StrEnum as BuiltinStrEnum 104 | 105 | assert isinstance(FunctionRegion.Any, BuiltinStrEnum) 106 | 107 | 108 | @pytest.mark.skipif( 109 | sys.version_info >= (3, 11), 110 | reason="strenum import test only relevant for Python < 3.11", 111 | ) 112 | def test_strenum_import_python_310_and_below(): 113 | from strenum import StrEnum as ExternalStrEnum 114 | 115 | assert isinstance(FunctionRegion.Any, ExternalStrEnum) 116 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI/CD 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | test: 15 | name: Test / OS ${{ matrix.os }} / Python ${{ matrix.python-version }} 16 | strategy: 17 | matrix: 18 | os: [ubuntu-latest] 19 | python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] 20 | runs-on: ${{ matrix.os }} 21 | steps: 22 | - name: Clone Repository 23 | uses: actions/checkout@v4 24 | 25 | - name: Set up Python ${{ matrix.python-version }} 26 | uses: actions/setup-python@v5 27 | with: 28 | python-version: ${{ matrix.python-version }} 29 | 30 | - name: Set up Poetry 31 | run: pipx install poetry==1.8.5 --python python${{ matrix.python-version }} 32 | 33 | - name: Run Tests 34 | run: make run_tests 35 | 36 | - name: Upload coverage to Coveralls 37 | uses: coverallsapp/github-action@v2 38 | with: 39 | github-token: ${{ secrets.GITHUB_TOKEN }} 40 | flag-name: run-${{ join(matrix.*, '-') }} 41 | parallel: true 42 | 43 | finish_tests: 44 | needs: test 45 | name: Upload tests coveralls results 46 | if: ${{ always() }} 47 | runs-on: ubuntu-latest 48 | steps: 49 | - name: Coveralls Finished 50 | uses: coverallsapp/github-action@v2 51 | with: 52 | github-token: ${{ secrets.GITHUB_TOKEN }} 53 | parallel-finished: true 54 | carryforward: "run-ubuntu-latest-3.9,run-ubuntu-latest-3.10,run-ubuntu-latest-3.11,run-ubuntu-latest-3.12,run-ubuntu-latest-3.13" 55 | 56 | release-please: 57 | needs: test 58 | if: ${{ github.ref == 'refs/heads/main' && github.event_name == 'push' && github.repository_owner == 'supabase' }} 59 | runs-on: ubuntu-latest 60 | name: "Bump version and create changelog" 61 | permissions: 62 | id-token: write # IMPORTANT: this permission is mandatory for trusted publishing 63 | contents: write # needed for github actions bot to write to repo 64 | pull-requests: write 65 | steps: 66 | - uses: googleapis/release-please-action@v4 67 | id: release 68 | with: 69 | target-branch: ${{ github.ref_name }} 70 | publish: 71 | needs: release-please 72 | if: ${{ startsWith(github.event.head_commit.message, 'chore(main)') && github.ref == 'refs/heads/main' && github.event_name == 'push' && github.repository_owner == 'supabase' }} 73 | runs-on: ubuntu-latest 74 | name: "supabase_functions: Publish to PyPi" 75 | environment: 76 | name: pypi 77 | url: https://pypi.org/p/supabase_functions 78 | permissions: 79 | id-token: write # IMPORTANT: this permission is mandatory for trusted publishing 80 | contents: write # needed for github actions bot to write to repo 81 | steps: 82 | - name: Set up Python 3.11 83 | uses: actions/setup-python@v5 84 | with: 85 | python-version: 3.11 86 | 87 | - name: Clone Repository 88 | uses: actions/checkout@v4 89 | with: 90 | ref: ${{ github.ref }} 91 | fetch-depth: 0 92 | 93 | - name: Set up Poetry 94 | run: pipx install poetry==1.8.5 --python python3.11 95 | 96 | - name: Install dependencies 97 | run: poetry install 98 | 99 | - name: Build package dist directory 100 | run: poetry build 101 | 102 | - name: Publish package distributions to PyPI 103 | uses: pypa/gh-action-pypi-publish@release/v1 104 | publish_legacy: 105 | needs: publish 106 | if: ${{ startsWith(github.event.head_commit.message, 'chore(main)') && github.ref == 'refs/heads/main' && github.event_name == 'push' && github.repository_owner == 'supabase' }} 107 | runs-on: ubuntu-latest 108 | name: "supafunc: Publish to PyPi" 109 | environment: 110 | name: pypi 111 | url: https://pypi.org/p/supafunc 112 | permissions: 113 | id-token: write # IMPORTANT: this permission is mandatory for trusted publishing 114 | contents: write # needed for github actions bot to write to repo 115 | steps: 116 | - name: Set up Python 3.11 117 | uses: actions/setup-python@v5 118 | with: 119 | python-version: 3.11 120 | 121 | - name: Clone Repository 122 | uses: actions/checkout@v4 123 | with: 124 | ref: ${{ github.ref }} 125 | fetch-depth: 0 126 | 127 | - name: Rename Project 128 | id: rename_project 129 | run: make rename_project 130 | 131 | - name: Set up Poetry 132 | run: pipx install poetry==1.8.5 --python python3.11 133 | 134 | - name: Install the project dependencies 135 | run: poetry install 136 | 137 | - name: Build package dist directory 138 | run: poetry build 139 | 140 | - name: Publish package distributions to PyPI 141 | uses: pypa/gh-action-pypi-publish@release/v1 142 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .mypy_cache/ 2 | __pycache__/ 3 | tags 4 | 5 | # Swap 6 | [._]*.s[a-v][a-z] 7 | !*.svg # comment out if you don't need vector files 8 | [._]*.sw[a-p] 9 | [._]s[a-rt-v][a-z] 10 | [._]ss[a-gi-z] 11 | [._]sw[a-p] 12 | 13 | # Session 14 | Session.vim 15 | Sessionx.vim 16 | 17 | # Temporary 18 | .netrwhist 19 | *~ 20 | # Auto-generated tag files 21 | tags 22 | # Persistent undo 23 | [._]*.un~ 24 | 25 | # Byte-compiled / optimized / DLL files 26 | __pycache__/ 27 | *.py[cod] 28 | *$py.class 29 | 30 | # C extensions 31 | *.so 32 | 33 | # Distribution / packaging 34 | .Python 35 | build/ 36 | develop-eggs/ 37 | dist/ 38 | downloads/ 39 | eggs/ 40 | .eggs/ 41 | lib64/ 42 | parts/ 43 | sdist/ 44 | var/ 45 | wheels/ 46 | share/python-wheels/ 47 | *.egg-info/ 48 | .installed.cfg 49 | *.egg 50 | MANIFEST 51 | 52 | # PyInstaller 53 | # Usually these files are written by a python script from a template 54 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 55 | *.manifest 56 | *.spec 57 | 58 | # Installer logs 59 | pip-log.txt 60 | pip-delete-this-directory.txt 61 | 62 | # Unit test / coverage reports 63 | htmlcov/ 64 | .tox/ 65 | .nox/ 66 | .coverage 67 | .coverage.* 68 | .cache 69 | nosetests.xml 70 | coverage.xml 71 | *.cover 72 | *.py,cover 73 | .hypothesis/ 74 | .pytest_cache/ 75 | cover/ 76 | 77 | # Translations 78 | *.mo 79 | *.pot 80 | 81 | # Django stuff: 82 | *.log 83 | local_settings.py 84 | db.sqlite3 85 | db.sqlite3-journal 86 | 87 | # Flask stuff: 88 | instance/ 89 | .webassets-cache 90 | 91 | # Scrapy stuff: 92 | .scrapy 93 | 94 | # Sphinx documentation 95 | docs/_build/ 96 | 97 | # PyBuilder 98 | .pybuilder/ 99 | target/ 100 | 101 | # Jupyter Notebook 102 | .ipynb_checkpoints 103 | 104 | # IPython 105 | profile_default/ 106 | ipython_config.py 107 | 108 | # pyenv 109 | # For a library or package, you might want to ignore these files since the code is 110 | # intended to run in multiple environments; otherwise, check them in: 111 | # .python-version 112 | 113 | # pipenv 114 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 115 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 116 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 117 | # install all needed dependencies. 118 | #Pipfile.lock 119 | 120 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 121 | __pypackages__/ 122 | 123 | # Celery stuff 124 | celerybeat-schedule 125 | celerybeat.pid 126 | 127 | # SageMath parsed files 128 | *.sage.py 129 | 130 | # Environments 131 | .env 132 | .venv 133 | env/ 134 | venv/ 135 | ENV/ 136 | env.bak/ 137 | venv.bak/ 138 | 139 | # Spyder project settings 140 | .spyderproject 141 | .spyproject 142 | 143 | # Rope project settings 144 | .ropeproject 145 | 146 | # mkdocs documentation 147 | /site 148 | 149 | # mypy 150 | .mypy_cache/ 151 | .dmypy.json 152 | dmypy.json 153 | 154 | # Pyre type checker 155 | .pyre/ 156 | 157 | # pytype static type analyzer 158 | .pytype/ 159 | 160 | # Cython debug symbols 161 | cython_debug/ 162 | 163 | # Editors 164 | .vscode/ 165 | .idea/ 166 | 167 | # Vagrant 168 | .vagrant/ 169 | 170 | # Mac/OSX 171 | .DS_Store 172 | 173 | # Windows 174 | Thumbs.db 175 | 176 | # Source for the following rules: https://raw.githubusercontent.com/github/gitignore/master/Python.gitignore 177 | # Byte-compiled / optimized / DLL files 178 | __pycache__/ 179 | *.py[cod] 180 | *$py.class 181 | 182 | # C extensions 183 | *.so 184 | 185 | # Distribution / packaging 186 | .Python 187 | build/ 188 | develop-eggs/ 189 | dist/ 190 | downloads/ 191 | eggs/ 192 | .eggs/ 193 | lib64/ 194 | parts/ 195 | sdist/ 196 | var/ 197 | wheels/ 198 | *.egg-info/ 199 | .installed.cfg 200 | *.egg 201 | MANIFEST 202 | 203 | # PyInstaller 204 | # Usually these files are written by a python script from a template 205 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 206 | *.manifest 207 | *.spec 208 | 209 | # Installer logs 210 | pip-log.txt 211 | pip-delete-this-directory.txt 212 | 213 | # Unit test / coverage reports 214 | htmlcov/ 215 | .tox/ 216 | .nox/ 217 | .coverage 218 | .coverage.* 219 | .cache 220 | nosetests.xml 221 | coverage.xml 222 | *.cover 223 | .hypothesis/ 224 | .pytest_cache/ 225 | 226 | # Translations 227 | *.mo 228 | *.pot 229 | 230 | # Django stuff: 231 | *.log 232 | local_settings.py 233 | db.sqlite3 234 | 235 | # Flask stuff: 236 | instance/ 237 | .webassets-cache 238 | 239 | # Scrapy stuff: 240 | .scrapy 241 | 242 | # Sphinx documentation 243 | docs/_build/ 244 | 245 | # PyBuilder 246 | target/ 247 | 248 | # Jupyter Notebook 249 | .ipynb_checkpoints 250 | 251 | # IPython 252 | profile_default/ 253 | ipython_config.py 254 | 255 | # pyenv 256 | .python-version 257 | 258 | # celery beat schedule file 259 | celerybeat-schedule 260 | 261 | # SageMath parsed files 262 | *.sage.py 263 | 264 | # Environments 265 | .env 266 | .venv 267 | env/ 268 | venv/ 269 | ENV/ 270 | env.bak/ 271 | venv.bak/ 272 | 273 | # Spyder project settings 274 | .spyderproject 275 | .spyproject 276 | 277 | # Rope project settings 278 | .ropeproject 279 | 280 | # mkdocs documentation 281 | /site 282 | 283 | # mypy 284 | .mypy_cache/ 285 | .dmypy.json 286 | dmypy.json 287 | -------------------------------------------------------------------------------- /supabase_functions/_sync/functions_client.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict, Literal, Optional, Union 2 | from warnings import warn 3 | 4 | from httpx import Client, HTTPError, Response 5 | 6 | from ..errors import FunctionsHttpError, FunctionsRelayError 7 | from ..utils import ( 8 | FunctionRegion, 9 | is_http_url, 10 | is_valid_str_arg, 11 | ) 12 | from ..version import __version__ 13 | 14 | 15 | class SyncFunctionsClient: 16 | def __init__( 17 | self, 18 | url: str, 19 | headers: Dict, 20 | timeout: Optional[int] = None, 21 | verify: Optional[bool] = None, 22 | proxy: Optional[str] = None, 23 | http_client: Optional[Client] = None, 24 | ): 25 | if not is_http_url(url): 26 | raise ValueError("url must be a valid HTTP URL string") 27 | self.url = url 28 | self.headers = { 29 | "User-Agent": f"supabase-py/functions-py v{__version__}", 30 | **headers, 31 | } 32 | 33 | if timeout is not None: 34 | warn( 35 | "The 'timeout' parameter is deprecated. Please configure it in the http client instead.", 36 | DeprecationWarning, 37 | stacklevel=2, 38 | ) 39 | if verify is not None: 40 | warn( 41 | "The 'verify' parameter is deprecated. Please configure it in the http client instead.", 42 | DeprecationWarning, 43 | stacklevel=2, 44 | ) 45 | if proxy is not None: 46 | warn( 47 | "The 'proxy' parameter is deprecated. Please configure it in the http client instead.", 48 | DeprecationWarning, 49 | stacklevel=2, 50 | ) 51 | 52 | self.verify = bool(verify) if verify is not None else True 53 | self.timeout = int(abs(timeout)) if timeout is not None else 60 54 | 55 | if http_client is not None: 56 | http_client.base_url = self.url 57 | http_client.headers.update({**self.headers}) 58 | self._client = http_client 59 | else: 60 | self._client = Client( 61 | base_url=self.url, 62 | headers=self.headers, 63 | verify=self.verify, 64 | timeout=self.timeout, 65 | proxy=proxy, 66 | follow_redirects=True, 67 | http2=True, 68 | ) 69 | 70 | def _request( 71 | self, 72 | method: Literal["GET", "OPTIONS", "HEAD", "POST", "PUT", "PATCH", "DELETE"], 73 | url: str, 74 | headers: Optional[Dict[str, str]] = None, 75 | json: Optional[Dict[Any, Any]] = None, 76 | ) -> Response: 77 | user_data = {"data": json} if isinstance(json, str) else {"json": json} 78 | response = self._client.request(method, url, **user_data, headers=headers) 79 | 80 | try: 81 | response.raise_for_status() 82 | except HTTPError as exc: 83 | status_code = None 84 | if hasattr(response, "status_code"): 85 | status_code = response.status_code 86 | 87 | raise FunctionsHttpError( 88 | response.json().get("error") 89 | or f"An error occurred while requesting your edge function at {exc.request.url!r}.", 90 | status_code, 91 | ) from exc 92 | 93 | return response 94 | 95 | def set_auth(self, token: str) -> None: 96 | """Updates the authorization header 97 | 98 | Parameters 99 | ---------- 100 | token : str 101 | the new jwt token sent in the authorization header 102 | """ 103 | 104 | self.headers["Authorization"] = f"Bearer {token}" 105 | 106 | def invoke( 107 | self, function_name: str, invoke_options: Optional[Dict] = None 108 | ) -> Union[Dict, bytes]: 109 | """Invokes a function 110 | 111 | Parameters 112 | ---------- 113 | function_name : the name of the function to invoke 114 | invoke_options : object with the following properties 115 | `headers`: object representing the headers to send with the request 116 | `body`: the body of the request 117 | `responseType`: how the response should be parsed. The default is `json` 118 | """ 119 | if not is_valid_str_arg(function_name): 120 | raise ValueError("function_name must a valid string value.") 121 | headers = self.headers 122 | body = None 123 | response_type = "text/plain" 124 | if invoke_options is not None: 125 | headers.update(invoke_options.get("headers", {})) 126 | response_type = invoke_options.get("responseType", "text/plain") 127 | 128 | region = invoke_options.get("region") 129 | if region: 130 | if not isinstance(region, FunctionRegion): 131 | warn(f"Use FunctionRegion({region})") 132 | region = FunctionRegion(region) 133 | 134 | if region.value != "any": 135 | headers["x-region"] = region.value 136 | 137 | body = invoke_options.get("body") 138 | if isinstance(body, str): 139 | headers["Content-Type"] = "text/plain" 140 | elif isinstance(body, dict): 141 | headers["Content-Type"] = "application/json" 142 | 143 | response = self._request( 144 | "POST", f"{self.url}/{function_name}", headers=headers, json=body 145 | ) 146 | is_relay_error = response.headers.get("x-relay-header") 147 | 148 | if is_relay_error and is_relay_error == "true": 149 | raise FunctionsRelayError(response.json().get("error")) 150 | 151 | if response_type == "json": 152 | data = response.json() 153 | else: 154 | data = response.content 155 | return data 156 | -------------------------------------------------------------------------------- /supabase_functions/_async/functions_client.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict, Literal, Optional, Union 2 | from warnings import warn 3 | 4 | from httpx import AsyncClient, HTTPError, Response 5 | 6 | from ..errors import FunctionsHttpError, FunctionsRelayError 7 | from ..utils import ( 8 | FunctionRegion, 9 | is_http_url, 10 | is_valid_str_arg, 11 | ) 12 | from ..version import __version__ 13 | 14 | 15 | class AsyncFunctionsClient: 16 | def __init__( 17 | self, 18 | url: str, 19 | headers: Dict, 20 | timeout: Optional[int] = None, 21 | verify: Optional[bool] = None, 22 | proxy: Optional[str] = None, 23 | http_client: Optional[AsyncClient] = None, 24 | ): 25 | if not is_http_url(url): 26 | raise ValueError("url must be a valid HTTP URL string") 27 | self.url = url 28 | self.headers = { 29 | "User-Agent": f"supabase-py/functions-py v{__version__}", 30 | **headers, 31 | } 32 | 33 | if timeout is not None: 34 | warn( 35 | "The 'timeout' parameter is deprecated. Please configure it in the http client instead.", 36 | DeprecationWarning, 37 | stacklevel=2, 38 | ) 39 | if verify is not None: 40 | warn( 41 | "The 'verify' parameter is deprecated. Please configure it in the http client instead.", 42 | DeprecationWarning, 43 | stacklevel=2, 44 | ) 45 | if proxy is not None: 46 | warn( 47 | "The 'proxy' parameter is deprecated. Please configure it in the http client instead.", 48 | DeprecationWarning, 49 | stacklevel=2, 50 | ) 51 | 52 | self.verify = bool(verify) if verify is not None else True 53 | self.timeout = int(abs(timeout)) if timeout is not None else 60 54 | 55 | if http_client is not None: 56 | http_client.base_url = self.url 57 | http_client.headers.update({**self.headers}) 58 | self._client = http_client 59 | else: 60 | self._client = AsyncClient( 61 | base_url=self.url, 62 | headers=self.headers, 63 | verify=self.verify, 64 | timeout=self.timeout, 65 | proxy=proxy, 66 | follow_redirects=True, 67 | http2=True, 68 | ) 69 | 70 | async def _request( 71 | self, 72 | method: Literal["GET", "OPTIONS", "HEAD", "POST", "PUT", "PATCH", "DELETE"], 73 | url: str, 74 | headers: Optional[Dict[str, str]] = None, 75 | json: Optional[Dict[Any, Any]] = None, 76 | ) -> Response: 77 | user_data = {"data": json} if isinstance(json, str) else {"json": json} 78 | response = await self._client.request(method, url, **user_data, headers=headers) 79 | 80 | try: 81 | response.raise_for_status() 82 | except HTTPError as exc: 83 | status_code = None 84 | if hasattr(response, "status_code"): 85 | status_code = response.status_code 86 | 87 | raise FunctionsHttpError( 88 | response.json().get("error") 89 | or f"An error occurred while requesting your edge function at {exc.request.url!r}.", 90 | status_code, 91 | ) from exc 92 | 93 | return response 94 | 95 | def set_auth(self, token: str) -> None: 96 | """Updates the authorization header 97 | 98 | Parameters 99 | ---------- 100 | token : str 101 | the new jwt token sent in the authorization header 102 | """ 103 | 104 | self.headers["Authorization"] = f"Bearer {token}" 105 | 106 | async def invoke( 107 | self, function_name: str, invoke_options: Optional[Dict] = None 108 | ) -> Union[Dict, bytes]: 109 | """Invokes a function 110 | 111 | Parameters 112 | ---------- 113 | function_name : the name of the function to invoke 114 | invoke_options : object with the following properties 115 | `headers`: object representing the headers to send with the request 116 | `body`: the body of the request 117 | `responseType`: how the response should be parsed. The default is `text/plain` 118 | """ 119 | if not is_valid_str_arg(function_name): 120 | raise ValueError("function_name must a valid string value.") 121 | headers = self.headers 122 | body = None 123 | response_type = "text/plain" 124 | if invoke_options is not None: 125 | headers.update(invoke_options.get("headers", {})) 126 | response_type = invoke_options.get("responseType", "text/plain") 127 | 128 | region = invoke_options.get("region") 129 | if region: 130 | if not isinstance(region, FunctionRegion): 131 | warn(f"Use FunctionRegion({region})") 132 | region = FunctionRegion(region) 133 | 134 | if region.value != "any": 135 | headers["x-region"] = region.value 136 | 137 | body = invoke_options.get("body") 138 | if isinstance(body, str): 139 | headers["Content-Type"] = "text/plain" 140 | elif isinstance(body, dict): 141 | headers["Content-Type"] = "application/json" 142 | 143 | response = await self._request( 144 | "POST", f"{self.url}/{function_name}", headers=headers, json=body 145 | ) 146 | is_relay_error = response.headers.get("x-relay-header") 147 | 148 | if is_relay_error and is_relay_error == "true": 149 | raise FunctionsRelayError(response.json().get("error")) 150 | 151 | if response_type == "json": 152 | data = response.json() 153 | else: 154 | data = response.content 155 | return data 156 | -------------------------------------------------------------------------------- /tests/_sync/test_function_client.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import Mock, patch 2 | 3 | import pytest 4 | from httpx import Client, HTTPError, Response, Timeout 5 | 6 | # Import the class to test 7 | from supabase_functions import SyncFunctionsClient 8 | from supabase_functions.errors import FunctionsHttpError, FunctionsRelayError 9 | from supabase_functions.utils import FunctionRegion 10 | from supabase_functions.version import __version__ 11 | 12 | 13 | @pytest.fixture 14 | def valid_url(): 15 | return "https://example.com" 16 | 17 | 18 | @pytest.fixture 19 | def default_headers(): 20 | return {"Authorization": "Bearer valid.jwt.token"} 21 | 22 | 23 | @pytest.fixture 24 | def client(valid_url, default_headers): 25 | return SyncFunctionsClient( 26 | url=valid_url, headers=default_headers, timeout=10, verify=True 27 | ) 28 | 29 | 30 | def test_init_with_valid_params(valid_url, default_headers): 31 | client = SyncFunctionsClient( 32 | url=valid_url, headers=default_headers, timeout=10, verify=True 33 | ) 34 | assert client.url == valid_url 35 | assert "User-Agent" in client.headers 36 | assert client.headers["User-Agent"] == f"supabase-py/functions-py v{__version__}" 37 | assert client._client.timeout == Timeout(10) 38 | 39 | 40 | @pytest.mark.parametrize("invalid_url", ["not-a-url", "ftp://invalid.com", "", None]) 41 | def test_init_with_invalid_url(invalid_url, default_headers): 42 | with pytest.raises(ValueError, match="url must be a valid HTTP URL string"): 43 | SyncFunctionsClient(url=invalid_url, headers=default_headers, timeout=10) 44 | 45 | 46 | def test_set_auth_valid_token(client: SyncFunctionsClient): 47 | valid_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U" 48 | client.set_auth(valid_token) 49 | assert client.headers["Authorization"] == f"Bearer {valid_token}" 50 | 51 | 52 | def test_invoke_success_json(client: SyncFunctionsClient): 53 | mock_response = Mock(spec=Response) 54 | mock_response.json.return_value = {"message": "success"} 55 | mock_response.raise_for_status = Mock() 56 | mock_response.headers = {} 57 | 58 | with patch.object(client._client, "request", new_callable=Mock) as mock_request: 59 | mock_request.return_value = mock_response 60 | 61 | result = client.invoke( 62 | "test-function", {"responseType": "json", "body": {"test": "data"}} 63 | ) 64 | 65 | assert result == {"message": "success"} 66 | mock_request.assert_called_once() 67 | _, kwargs = mock_request.call_args 68 | assert kwargs["json"] == {"test": "data"} 69 | 70 | 71 | def test_invoke_success_binary(client: SyncFunctionsClient): 72 | mock_response = Mock(spec=Response) 73 | mock_response.content = b"binary content" 74 | mock_response.raise_for_status = Mock() 75 | mock_response.headers = {} 76 | 77 | with patch.object(client._client, "request", new_callable=Mock) as mock_request: 78 | mock_request.return_value = mock_response 79 | 80 | result = client.invoke("test-function") 81 | 82 | assert result == b"binary content" 83 | mock_request.assert_called_once() 84 | 85 | 86 | def test_invoke_with_region(client: SyncFunctionsClient): 87 | mock_response = Mock(spec=Response) 88 | mock_response.json.return_value = {"message": "success"} 89 | mock_response.raise_for_status = Mock() 90 | mock_response.headers = {} 91 | 92 | with patch.object(client._client, "request", new_callable=Mock) as mock_request: 93 | mock_request.return_value = mock_response 94 | 95 | client.invoke("test-function", {"region": FunctionRegion("us-east-1")}) 96 | 97 | _, kwargs = mock_request.call_args 98 | assert kwargs["headers"]["x-region"] == "us-east-1" 99 | 100 | 101 | def test_invoke_with_region_string(client: SyncFunctionsClient): 102 | mock_response = Mock(spec=Response) 103 | mock_response.json.return_value = {"message": "success"} 104 | mock_response.raise_for_status = Mock() 105 | mock_response.headers = {} 106 | 107 | with patch.object(client._client, "request", new_callable=Mock) as mock_request: 108 | mock_request.return_value = mock_response 109 | 110 | with pytest.warns(UserWarning, match=r"Use FunctionRegion\(us-east-1\)"): 111 | client.invoke("test-function", {"region": "us-east-1"}) 112 | 113 | _, kwargs = mock_request.call_args 114 | assert kwargs["headers"]["x-region"] == "us-east-1" 115 | 116 | 117 | def test_invoke_with_http_error(client: SyncFunctionsClient): 118 | mock_response = Mock(spec=Response) 119 | mock_response.json.return_value = {"error": "Custom error message"} 120 | mock_response.raise_for_status.side_effect = HTTPError("HTTP Error") 121 | mock_response.headers = {} 122 | 123 | with patch.object(client._client, "request", new_callable=Mock) as mock_request: 124 | mock_request.return_value = mock_response 125 | 126 | with pytest.raises(FunctionsHttpError, match="Custom error message"): 127 | client.invoke("test-function") 128 | 129 | 130 | def test_invoke_with_relay_error(client: SyncFunctionsClient): 131 | mock_response = Mock(spec=Response) 132 | mock_response.json.return_value = {"error": "Relay error message"} 133 | mock_response.raise_for_status = Mock() 134 | mock_response.headers = {"x-relay-header": "true"} 135 | 136 | with patch.object(client._client, "request", new_callable=Mock) as mock_request: 137 | mock_request.return_value = mock_response 138 | 139 | with pytest.raises(FunctionsRelayError, match="Relay error message"): 140 | client.invoke("test-function") 141 | 142 | 143 | def test_invoke_invalid_function_name(client: SyncFunctionsClient): 144 | with pytest.raises(ValueError, match="function_name must a valid string value."): 145 | client.invoke("") 146 | 147 | 148 | def test_invoke_with_string_body(client: SyncFunctionsClient): 149 | mock_response = Mock(spec=Response) 150 | mock_response.json.return_value = {"message": "success"} 151 | mock_response.raise_for_status = Mock() 152 | mock_response.headers = {} 153 | 154 | with patch.object(client._client, "request", new_callable=Mock) as mock_request: 155 | mock_request.return_value = mock_response 156 | 157 | client.invoke("test-function", {"body": "string data"}) 158 | 159 | _, kwargs = mock_request.call_args 160 | assert kwargs["headers"]["Content-Type"] == "text/plain" 161 | 162 | 163 | def test_invoke_with_json_body(client: SyncFunctionsClient): 164 | mock_response = Mock(spec=Response) 165 | mock_response.json.return_value = {"message": "success"} 166 | mock_response.raise_for_status = Mock() 167 | mock_response.headers = {} 168 | 169 | with patch.object(client._client, "request", new_callable=Mock) as mock_request: 170 | mock_request.return_value = mock_response 171 | 172 | client.invoke("test-function", {"body": {"key": "value"}}) 173 | 174 | _, kwargs = mock_request.call_args 175 | assert kwargs["headers"]["Content-Type"] == "application/json" 176 | 177 | 178 | def test_init_with_httpx_client(): 179 | # Create a custom httpx client with specific options 180 | headers = {"x-user-agent": "my-app/0.0.1"} 181 | custom_client = Client( 182 | timeout=Timeout(30), follow_redirects=True, max_redirects=5, headers=headers 183 | ) 184 | 185 | # Initialize the functions client with the custom httpx client 186 | client = SyncFunctionsClient( 187 | url="https://example.com", 188 | headers={"Authorization": "Bearer token"}, 189 | timeout=10, 190 | http_client=custom_client, 191 | ) 192 | 193 | # Verify the custom client options are preserved 194 | assert client._client.timeout == Timeout(30) 195 | assert client._client.follow_redirects is True 196 | assert client._client.max_redirects == 5 197 | assert client._client.headers.get("x-user-agent") == "my-app/0.0.1" 198 | 199 | # Verify the client is properly configured with our custom client 200 | assert client._client is custom_client 201 | -------------------------------------------------------------------------------- /tests/_async/test_function_client.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import AsyncMock, Mock, patch 2 | 3 | import pytest 4 | from httpx import AsyncClient, HTTPError, Response, Timeout 5 | 6 | # Import the class to test 7 | from supabase_functions import AsyncFunctionsClient 8 | from supabase_functions.errors import FunctionsHttpError, FunctionsRelayError 9 | from supabase_functions.utils import FunctionRegion 10 | from supabase_functions.version import __version__ 11 | 12 | 13 | @pytest.fixture 14 | def valid_url(): 15 | return "https://example.com" 16 | 17 | 18 | @pytest.fixture 19 | def default_headers(): 20 | return {"Authorization": "Bearer valid.jwt.token"} 21 | 22 | 23 | @pytest.fixture 24 | def client(valid_url, default_headers): 25 | return AsyncFunctionsClient( 26 | url=valid_url, headers=default_headers, timeout=10, verify=True 27 | ) 28 | 29 | 30 | async def test_init_with_valid_params(valid_url, default_headers): 31 | client = AsyncFunctionsClient( 32 | url=valid_url, headers=default_headers, timeout=10, verify=True 33 | ) 34 | assert client.url == valid_url 35 | assert "User-Agent" in client.headers 36 | assert client.headers["User-Agent"] == f"supabase-py/functions-py v{__version__}" 37 | assert client._client.timeout == Timeout(10) 38 | 39 | 40 | @pytest.mark.parametrize("invalid_url", ["not-a-url", "ftp://invalid.com", "", None]) 41 | def test_init_with_invalid_url(invalid_url, default_headers): 42 | with pytest.raises(ValueError, match="url must be a valid HTTP URL string"): 43 | AsyncFunctionsClient(url=invalid_url, headers=default_headers, timeout=10) 44 | 45 | 46 | async def test_set_auth_valid_token(client: AsyncFunctionsClient): 47 | valid_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U" 48 | client.set_auth(valid_token) 49 | assert client.headers["Authorization"] == f"Bearer {valid_token}" 50 | 51 | 52 | async def test_invoke_success_json(client: AsyncFunctionsClient): 53 | mock_response = Mock(spec=Response) 54 | mock_response.json.return_value = {"message": "success"} 55 | mock_response.raise_for_status = Mock() 56 | mock_response.headers = {} 57 | 58 | with patch.object( 59 | client._client, "request", new_callable=AsyncMock 60 | ) as mock_request: 61 | mock_request.return_value = mock_response 62 | 63 | result = await client.invoke( 64 | "test-function", {"responseType": "json", "body": {"test": "data"}} 65 | ) 66 | 67 | assert result == {"message": "success"} 68 | mock_request.assert_called_once() 69 | _, kwargs = mock_request.call_args 70 | assert kwargs["json"] == {"test": "data"} 71 | 72 | 73 | async def test_invoke_success_binary(client: AsyncFunctionsClient): 74 | mock_response = Mock(spec=Response) 75 | mock_response.content = b"binary content" 76 | mock_response.raise_for_status = Mock() 77 | mock_response.headers = {} 78 | 79 | with patch.object( 80 | client._client, "request", new_callable=AsyncMock 81 | ) as mock_request: 82 | mock_request.return_value = mock_response 83 | 84 | result = await client.invoke("test-function") 85 | 86 | assert result == b"binary content" 87 | mock_request.assert_called_once() 88 | 89 | 90 | async def test_invoke_with_region(client: AsyncFunctionsClient): 91 | mock_response = Mock(spec=Response) 92 | mock_response.json.return_value = {"message": "success"} 93 | mock_response.raise_for_status = Mock() 94 | mock_response.headers = {} 95 | 96 | with patch.object( 97 | client._client, "request", new_callable=AsyncMock 98 | ) as mock_request: 99 | mock_request.return_value = mock_response 100 | 101 | await client.invoke("test-function", {"region": FunctionRegion("us-east-1")}) 102 | 103 | _, kwargs = mock_request.call_args 104 | assert kwargs["headers"]["x-region"] == "us-east-1" 105 | 106 | 107 | async def test_invoke_with_region_string(client: AsyncFunctionsClient): 108 | mock_response = Mock(spec=Response) 109 | mock_response.json.return_value = {"message": "success"} 110 | mock_response.raise_for_status = Mock() 111 | mock_response.headers = {} 112 | 113 | with patch.object( 114 | client._client, "request", new_callable=AsyncMock 115 | ) as mock_request: 116 | mock_request.return_value = mock_response 117 | 118 | with pytest.warns(UserWarning, match=r"Use FunctionRegion\(us-east-1\)"): 119 | await client.invoke("test-function", {"region": "us-east-1"}) 120 | 121 | _, kwargs = mock_request.call_args 122 | assert kwargs["headers"]["x-region"] == "us-east-1" 123 | 124 | 125 | async def test_invoke_with_http_error(client: AsyncFunctionsClient): 126 | mock_response = Mock(spec=Response) 127 | mock_response.json.return_value = {"error": "Custom error message"} 128 | mock_response.raise_for_status.side_effect = HTTPError("HTTP Error") 129 | mock_response.headers = {} 130 | 131 | with patch.object( 132 | client._client, "request", new_callable=AsyncMock 133 | ) as mock_request: 134 | mock_request.return_value = mock_response 135 | 136 | with pytest.raises(FunctionsHttpError, match="Custom error message"): 137 | await client.invoke("test-function") 138 | 139 | 140 | async def test_invoke_with_relay_error(client: AsyncFunctionsClient): 141 | mock_response = Mock(spec=Response) 142 | mock_response.json.return_value = {"error": "Relay error message"} 143 | mock_response.raise_for_status = Mock() 144 | mock_response.headers = {"x-relay-header": "true"} 145 | 146 | with patch.object( 147 | client._client, "request", new_callable=AsyncMock 148 | ) as mock_request: 149 | mock_request.return_value = mock_response 150 | 151 | with pytest.raises(FunctionsRelayError, match="Relay error message"): 152 | await client.invoke("test-function") 153 | 154 | 155 | async def test_invoke_invalid_function_name(client: AsyncFunctionsClient): 156 | with pytest.raises(ValueError, match="function_name must a valid string value."): 157 | await client.invoke("") 158 | 159 | 160 | async def test_invoke_with_string_body(client: AsyncFunctionsClient): 161 | mock_response = Mock(spec=Response) 162 | mock_response.json.return_value = {"message": "success"} 163 | mock_response.raise_for_status = Mock() 164 | mock_response.headers = {} 165 | 166 | with patch.object( 167 | client._client, "request", new_callable=AsyncMock 168 | ) as mock_request: 169 | mock_request.return_value = mock_response 170 | 171 | await client.invoke("test-function", {"body": "string data"}) 172 | 173 | _, kwargs = mock_request.call_args 174 | assert kwargs["headers"]["Content-Type"] == "text/plain" 175 | 176 | 177 | async def test_invoke_with_json_body(client: AsyncFunctionsClient): 178 | mock_response = Mock(spec=Response) 179 | mock_response.json.return_value = {"message": "success"} 180 | mock_response.raise_for_status = Mock() 181 | mock_response.headers = {} 182 | 183 | with patch.object( 184 | client._client, "request", new_callable=AsyncMock 185 | ) as mock_request: 186 | mock_request.return_value = mock_response 187 | 188 | await client.invoke("test-function", {"body": {"key": "value"}}) 189 | 190 | _, kwargs = mock_request.call_args 191 | assert kwargs["headers"]["Content-Type"] == "application/json" 192 | 193 | 194 | async def test_init_with_httpx_client(): 195 | # Create a custom httpx client with specific options 196 | headers = {"x-user-agent": "my-app/0.0.1"} 197 | custom_client = AsyncClient( 198 | timeout=Timeout(30), follow_redirects=True, max_redirects=5, headers=headers 199 | ) 200 | 201 | # Initialize the functions client with the custom httpx client 202 | client = AsyncFunctionsClient( 203 | url="https://example.com", 204 | headers={"Authorization": "Bearer token"}, 205 | timeout=10, 206 | http_client=custom_client, 207 | ) 208 | 209 | # Verify the custom client options are preserved 210 | assert client._client.timeout == Timeout(30) 211 | assert client._client.follow_redirects is True 212 | assert client._client.max_redirects == 5 213 | assert client._client.headers.get("x-user-agent") == "my-app/0.0.1" 214 | 215 | # Verify the client is properly configured with our custom client 216 | assert client._client is custom_client 217 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## [0.10.1](https://github.com/supabase/functions-py/compare/v0.10.0...v0.10.1) (2025-06-20) 4 | 5 | 6 | ### Bug Fixes 7 | 8 | * 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)) 9 | 10 | ## [0.10.0](https://github.com/supabase/functions-py/compare/v0.9.4...v0.10.0) (2025-06-19) 11 | 12 | 13 | ### Features 14 | 15 | * allow injection of httpx client ([#205](https://github.com/supabase/functions-py/issues/205)) ([1d3ce59](https://github.com/supabase/functions-py/commit/1d3ce59d7da6b6224cb4e357125c1cbb47f8647b)) 16 | 17 | ## [0.9.4](https://github.com/supabase/functions-py/compare/v0.9.3...v0.9.4) (2025-03-26) 18 | 19 | 20 | ### Bug Fixes 21 | 22 | * 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)) 23 | 24 | ## [0.9.3](https://github.com/supabase/functions-py/compare/v0.9.2...v0.9.3) (2025-01-29) 25 | 26 | 27 | ### Bug Fixes 28 | 29 | * 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)) 30 | 31 | ## [0.9.2](https://github.com/supabase/functions-py/compare/v0.9.1...v0.9.2) (2025-01-17) 32 | 33 | 34 | ### Bug Fixes 35 | 36 | * 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)) 37 | 38 | ## [0.9.1](https://github.com/supabase/functions-py/compare/v0.9.0...v0.9.1) (2025-01-17) 39 | 40 | 41 | ### Bug Fixes 42 | 43 | * add full test coverage ([#180](https://github.com/supabase/functions-py/issues/180)) ([2bc3d3c](https://github.com/supabase/functions-py/commit/2bc3d3ccb4d62c98c5d13d25c2073fb86f84feb6)) 44 | 45 | ## [0.9.0](https://github.com/supabase/functions-py/compare/v0.8.0...v0.9.0) (2024-11-28) 46 | 47 | 48 | ### Features 49 | 50 | * rewrite region enumerated literals as Enums ([#164](https://github.com/supabase/functions-py/issues/164)) ([3ca78fa](https://github.com/supabase/functions-py/commit/3ca78fa14ceb6b0cb2b70ee6dd7a1229fe974cf4)) 51 | 52 | ## [0.8.0](https://github.com/supabase/functions-py/compare/v0.7.0...v0.8.0) (2024-11-22) 53 | 54 | 55 | ### Features 56 | 57 | * Check if token is a JWT ([#159](https://github.com/supabase/functions-py/issues/159)) ([44f7b39](https://github.com/supabase/functions-py/commit/44f7b39ee5f7a4d7a8019bc02599b551fb71272d)) 58 | 59 | ## [0.7.0](https://github.com/supabase/functions-py/compare/v0.6.2...v0.7.0) (2024-10-31) 60 | 61 | 62 | ### Features 63 | 64 | * 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)) 65 | 66 | ## [0.6.2](https://github.com/supabase/functions-py/compare/v0.6.1...v0.6.2) (2024-10-15) 67 | 68 | 69 | ### Bug Fixes 70 | 71 | * 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)) 72 | * Types to use Option[T] ([#152](https://github.com/supabase/functions-py/issues/152)) ([637bf4e](https://github.com/supabase/functions-py/commit/637bf4e33f1c6a845654dba923e6215ed8cd4a7f)) 73 | 74 | ## [0.6.1](https://github.com/supabase/functions-py/compare/v0.6.0...v0.6.1) (2024-10-02) 75 | 76 | 77 | ### Bug Fixes 78 | 79 | * httpx minimum version update ([#150](https://github.com/supabase/functions-py/issues/150)) ([e784961](https://github.com/supabase/functions-py/commit/e7849619532c65f8d02908bc09b4abc60315b213)) 80 | 81 | ## [0.6.0](https://github.com/supabase/functions-py/compare/v0.5.1...v0.6.0) (2024-09-25) 82 | 83 | 84 | ### Features 85 | 86 | * Proxy support ([#148](https://github.com/supabase/functions-py/issues/148)) ([7710a3f](https://github.com/supabase/functions-py/commit/7710a3f1068d44ecc21aac48c9f1f9a349fe8968)) 87 | 88 | ## v0.5.1 (2024-07-25) 89 | 90 | ### Chore 91 | 92 | * 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)) 93 | 94 | * 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)) 95 | 96 | * chore(deps-dev): bump pytest from 8.2.2 to 8.3.1 (#123) ([`e4c53f2`](https://github.com/supabase-community/functions-py/commit/e4c53f2421e17d3891b4ab3d57aa991da0039a86)) 97 | 98 | ### Fix 99 | 100 | * fix: add x-region support (#126) ([`59135d8`](https://github.com/supabase-community/functions-py/commit/59135d8de63030837a2730dd2631da746386b8fe)) 101 | 102 | ## v0.5.0 (2024-07-21) 103 | 104 | ### Chore 105 | 106 | * chore(release): bump version to v0.5.0 ([`8496b3e`](https://github.com/supabase-community/functions-py/commit/8496b3e0d8eb0cbd7d4d9663b864496abcda5e0e)) 107 | 108 | * 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)) 109 | 110 | ### Feature 111 | 112 | * feat: add edge functions timeout (#120) ([`d0abc3c`](https://github.com/supabase-community/functions-py/commit/d0abc3c6a03ce0b2347379b0d7ffdc9f4d37b287)) 113 | 114 | ## v0.4.7 (2024-07-14) 115 | 116 | ### Chore 117 | 118 | * chore(release): bump version to v0.4.7 ([`9d200a9`](https://github.com/supabase-community/functions-py/commit/9d200a97bf89c296e975d4d63aa15b12be6b646a)) 119 | 120 | * 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)) 121 | 122 | * 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)) 123 | 124 | * 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)) 125 | 126 | * 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)) 127 | 128 | ### Fix 129 | 130 | * fix: version bump (#121) ([`8f9b380`](https://github.com/supabase-community/functions-py/commit/8f9b3802759eec255f3f096ed818d64cd1ff3596)) 131 | 132 | ### Unknown 133 | 134 | * Enable HTTP2 (#115) ([`dbe0c73`](https://github.com/supabase-community/functions-py/commit/dbe0c73f025608adb0de1cb7b269de1eae23241d)) 135 | 136 | ## v0.4.6 (2024-06-05) 137 | 138 | ### Chore 139 | 140 | * chore(release): bump version to v0.4.6 ([`f038cad`](https://github.com/supabase-community/functions-py/commit/f038cad35cbeaafbef9a26e4c853237a994ac9c6)) 141 | 142 | * 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)) 143 | 144 | * chore(deps-dev): bump pytest from 8.2.1 to 8.2.2 (#109) ([`9d83fc0`](https://github.com/supabase-community/functions-py/commit/9d83fc0ace5c5a9310a5c3878a2db3586fd97899)) 145 | 146 | * 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)) 147 | 148 | * chore(deps-dev): bump pytest from 8.2.0 to 8.2.1 (#103) ([`b9523b8`](https://github.com/supabase-community/functions-py/commit/b9523b82b60b9e205077db31d7838af0dba3ea80)) 149 | 150 | * 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)) 151 | 152 | * 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)) 153 | 154 | * 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)) 155 | 156 | * 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)) 157 | 158 | * 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)) 159 | 160 | * chore(deps-dev): bump pytest from 8.1.1 to 8.2.0 (#89) ([`b0f272b`](https://github.com/supabase-community/functions-py/commit/b0f272b7412daee3b23395c9f1363e5290e06e30)) 161 | 162 | * chore(deps-dev): bump black from 24.3.0 to 24.4.2 (#88) ([`b7944de`](https://github.com/supabase-community/functions-py/commit/b7944decb2584a519941ed53e8786754ac6765d8)) 163 | 164 | * 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)) 165 | 166 | * 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)) 167 | 168 | * 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)) 169 | 170 | * 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)) 171 | 172 | * chore(deps-dev): bump respx from 0.21.0 to 0.21.1 (#77) ([`0cf2183`](https://github.com/supabase-community/functions-py/commit/0cf2183efb19ce6898bcdd7d4163534578944996)) 173 | 174 | * 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)) 175 | 176 | * 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)) 177 | 178 | * chore(deps): bump actions/cache from 3 to 4 (#74) ([`9f61fda`](https://github.com/supabase-community/functions-py/commit/9f61fdadc006ac7a390a1890cbf270e8eecf1c67)) 179 | 180 | * chore(deps): bump abatilo/actions-poetry from 2 to 3 (#73) ([`e521357`](https://github.com/supabase-community/functions-py/commit/e5213574c32f87c1e47c647c86593e1d59c408c8)) 181 | 182 | * chore(deps): bump python-semantic-release/python-semantic-release from 9.3.0 to 9.3.1 (#72) 183 | 184 | Signed-off-by: dependabot[bot] <support@github.com> 185 | Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`a0a9ad8`](https://github.com/supabase-community/functions-py/commit/a0a9ad8959625772b5e63ebecee96eb783e15d12)) 186 | 187 | ### Ci 188 | 189 | * 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)) 190 | 191 | * 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)) 192 | 193 | ### Fix 194 | 195 | * fix: add "verify" flag to the creation of client ([`f43038c`](https://github.com/supabase-community/functions-py/commit/f43038cf59d772a47420f8ae6cb3ca81b2808193)) 196 | 197 | ### Unknown 198 | 199 | * Follow redirects (#107) ([`ccb3f7f`](https://github.com/supabase-community/functions-py/commit/ccb3f7fa81e0e1257e6551a483900083b173135c)) 200 | 201 | * Update .pre-commit-config.yaml (#93) ([`3201e5d`](https://github.com/supabase-community/functions-py/commit/3201e5d9e68c1e580ea5e61028b08dde4ba3123e)) 202 | 203 | * Add stale bot (#92) ([`1fb54a8`](https://github.com/supabase-community/functions-py/commit/1fb54a80b211c93a8b7b8118842ab411dc33e22b)) 204 | 205 | ## v0.4.5 (2024-03-23) 206 | 207 | ### Chore 208 | 209 | * chore(release): bump version to v0.4.5 ([`d2b7efb`](https://github.com/supabase-community/functions-py/commit/d2b7efb158baf22a37d0652614c6aaee43fa389a)) 210 | 211 | ### Fix 212 | 213 | * fix: configure poetry in github action (#71) ([`886f0fb`](https://github.com/supabase-community/functions-py/commit/886f0fb1590567527159df8944a9ab7d418e5f00)) 214 | 215 | ## v0.4.4 (2024-03-23) 216 | 217 | ### Chore 218 | 219 | * chore(release): bump version to v0.4.4 ([`ebb987f`](https://github.com/supabase-community/functions-py/commit/ebb987fc0df58d41a0307065d28ae14b6697cb3b)) 220 | 221 | ### Fix 222 | 223 | * fix: update to perform build via poetry (#70) ([`d518ce5`](https://github.com/supabase-community/functions-py/commit/d518ce5b81fef2ec30907c9261fde29522e37222)) 224 | 225 | ## v0.4.3 (2024-03-23) 226 | 227 | ### Chore 228 | 229 | * chore(release): bump version to v0.4.3 ([`5c707e9`](https://github.com/supabase-community/functions-py/commit/5c707e998df07afdd67c629f034e954564a1c65b)) 230 | 231 | ### Fix 232 | 233 | * fix: add supafunc package distribution (#69) ([`d8a6f9a`](https://github.com/supabase-community/functions-py/commit/d8a6f9a89909c5d4bc3d6c11fb369407931c9cc5)) 234 | 235 | ## v0.4.2 (2024-03-23) 236 | 237 | ### Chore 238 | 239 | * chore(release): bump version to v0.4.2 ([`be38b0b`](https://github.com/supabase-community/functions-py/commit/be38b0b38ce878df629b1170b69ccbaf5e54ba03)) 240 | 241 | ### Fix 242 | 243 | * fix: ci workflow (#68) ([`a747a72`](https://github.com/supabase-community/functions-py/commit/a747a729b35d31197d2b772d2f5ddc9e5ec9daed)) 244 | 245 | ## v0.4.1 (2024-03-23) 246 | 247 | ### Chore 248 | 249 | * chore(release): bump version to v0.4.1 ([`f9a9a2f`](https://github.com/supabase-community/functions-py/commit/f9a9a2feede1cec36d9a9c9dc35d64594d4f1f84)) 250 | 251 | * chore(deps-dev): bump python-semantic-release from 9.2.0 to 9.3.0 (#66) 252 | 253 | Signed-off-by: dependabot[bot] <support@github.com> 254 | Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`7d1ce46`](https://github.com/supabase-community/functions-py/commit/7d1ce467159fa08d8cde29056d52553b186319ce)) 255 | 256 | * chore(deps): bump codecov/codecov-action from 3 to 4 (#50) 257 | 258 | Signed-off-by: dependabot[bot] <support@github.com> 259 | Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`ffe43b2`](https://github.com/supabase-community/functions-py/commit/ffe43b2ded9396a893d1c12d9a0077b70aed52df)) 260 | 261 | * chore(deps): bump actions/checkout from 2 to 4 (#52) 262 | 263 | Signed-off-by: dependabot[bot] <support@github.com> 264 | Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`a809e98`](https://github.com/supabase-community/functions-py/commit/a809e98368b1ff8369c787439a5ba2d01a0ebfcf)) 265 | 266 | * chore(deps): bump abatilo/actions-poetry from 2.2.0 to 3.0.0 (#54) 267 | 268 | Signed-off-by: dependabot[bot] <support@github.com> 269 | Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`4c1365c`](https://github.com/supabase-community/functions-py/commit/4c1365cbb94502cad0bc14032bbf48c74de1971f)) 270 | 271 | * chore(deps): bump python-semantic-release/python-semantic-release from 8.0.0 to 9.3.0 (#65) 272 | 273 | Signed-off-by: dependabot[bot] <support@github.com> 274 | Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`cddbd1f`](https://github.com/supabase-community/functions-py/commit/cddbd1fe983e600ec4f9cf177a9b5e1f582a7ff0)) 275 | 276 | * chore(deps-dev): bump pytest from 8.0.2 to 8.1.1 (#57) 277 | 278 | Signed-off-by: dependabot[bot] <support@github.com> 279 | Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`43a2741`](https://github.com/supabase-community/functions-py/commit/43a27418526d2cdd8739ad4f4c69e21a6743d43e)) 280 | 281 | * chore(deps-dev): bump respx from 0.20.2 to 0.21.0 (#61) 282 | 283 | Signed-off-by: dependabot[bot] <support@github.com> 284 | Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`a1481fd`](https://github.com/supabase-community/functions-py/commit/a1481fd126b10719bd92061012a9efba0354530f)) 285 | 286 | * chore(deps-dev): bump pytest-asyncio from 0.23.5 to 0.23.6 (#62) 287 | 288 | Signed-off-by: dependabot[bot] <support@github.com> 289 | Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`a821fe8`](https://github.com/supabase-community/functions-py/commit/a821fe80e5f2f2a68395e86ecb782856f1d18642)) 290 | 291 | * chore(deps-dev): bump python-semantic-release from 9.1.1 to 9.2.0 (#60) 292 | 293 | Signed-off-by: dependabot[bot] <support@github.com> 294 | Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`719ed64`](https://github.com/supabase-community/functions-py/commit/719ed64f587c40bd2c98255d34c36788e1ccae1b)) 295 | 296 | * chore(deps): bump actions/setup-python from 2 to 5 (#53) 297 | 298 | Signed-off-by: dependabot[bot] <support@github.com> 299 | Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`867bab3`](https://github.com/supabase-community/functions-py/commit/867bab3a99197da2a7437b903b52afc7a4e5d0a1)) 300 | 301 | * chore(deps-dev): bump black from 24.2.0 to 24.3.0 (#58) 302 | 303 | Signed-off-by: dependabot[bot] <support@github.com> 304 | Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`0a84c99`](https://github.com/supabase-community/functions-py/commit/0a84c9951bc5ba78d218599bcd1d15a32909fdf9)) 305 | 306 | * chore(deps-dev): bump pytest from 7.4.4 to 8.0.2 (#48) 307 | 308 | Signed-off-by: dependabot[bot] <support@github.com> 309 | Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`ae754cf`](https://github.com/supabase-community/functions-py/commit/ae754cfb4bc7e8832a7aac95f0d29077b2b7ad80)) 310 | 311 | * chore(deps-dev): bump python-semantic-release from 8.7.0 to 9.1.1 (#47) 312 | 313 | Signed-off-by: dependabot[bot] <support@github.com> 314 | Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`f108ed7`](https://github.com/supabase-community/functions-py/commit/f108ed7691a2bd0801b946e96c7c16cec234972b)) 315 | 316 | * chore(deps-dev): bump black from 23.12.1 to 24.2.0 (#43) 317 | 318 | Signed-off-by: dependabot[bot] <support@github.com> 319 | Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`ed09227`](https://github.com/supabase-community/functions-py/commit/ed092279ad8911f2664a92c0c1e332b3f42e4555)) 320 | 321 | * chore(deps-dev): bump pytest-asyncio from 0.23.3 to 0.23.5 (#42) 322 | 323 | Signed-off-by: dependabot[bot] <support@github.com> 324 | Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`e87489b`](https://github.com/supabase-community/functions-py/commit/e87489b98c7fab52417e9b1f81a2fd4a83caf627)) 325 | 326 | * chore: rename package to supabase_functions (#37) ([`5b5b5c9`](https://github.com/supabase-community/functions-py/commit/5b5b5c9071d74b7ec3e3da0dd83bbd6bd9303152)) 327 | 328 | ### Fix 329 | 330 | * fix: Update library name in pyproject file (#67) ([`5b5fe0d`](https://github.com/supabase-community/functions-py/commit/5b5fe0dcdfd81b1e91da47d49d6aecaad0505a3d)) 331 | 332 | * fix: bump httpx from 0.25.2 to 0.27.0 (#46) 333 | 334 | Signed-off-by: dependabot[bot] <support@github.com> 335 | Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ([`03fcb25`](https://github.com/supabase-community/functions-py/commit/03fcb25d5fdca5ee07edac2ba769d797f54e1dc7)) 336 | 337 | ## v0.4.0 (2024-02-29) 338 | 339 | ### Chore 340 | 341 | * chore(release): bump version to v0.4.0 ([`555df53`](https://github.com/supabase-community/functions-py/commit/555df5384d2c8c8de7565f609f231923c118cd8f)) 342 | 343 | ### Feature 344 | 345 | * feat: add actions to dependabot.yml (#49) ([`0fa1c6b`](https://github.com/supabase-community/functions-py/commit/0fa1c6b91363714a0f889d73e52a47ca2e5be349)) 346 | 347 | ## v0.3.3 (2024-01-03) 348 | 349 | ### Chore 350 | 351 | * chore(release): bump version to v0.3.3 ([`cd02bb2`](https://github.com/supabase-community/functions-py/commit/cd02bb26e501adb6c66007f637af0ba87b37a6f0)) 352 | 353 | ### Fix 354 | 355 | * fix: update job to publish legacy package if current is released (#36) ([`2565c37`](https://github.com/supabase-community/functions-py/commit/2565c372124c08bc2a0bd8fd4b3005cf427062e3)) 356 | 357 | ## v0.3.2 (2024-01-03) 358 | 359 | ### Chore 360 | 361 | * chore(release): bump version to v0.3.2 ([`403418c`](https://github.com/supabase-community/functions-py/commit/403418cc3a801be12e73f84e53daddd517cd5de0)) 362 | 363 | * chore(deps-dev): bump isort from 5.12.0 to 5.13.0 (#24) ([`e7443ee`](https://github.com/supabase-community/functions-py/commit/e7443eeaad029a19a4276bae8ebfb899d042be3a)) 364 | 365 | * chore(deps-dev): bump isort from 5.12.0 to 5.13.0 366 | 367 | Bumps [isort](https://github.com/pycqa/isort) from 5.12.0 to 5.13.0. 368 | - [Release notes](https://github.com/pycqa/isort/releases) 369 | - [Changelog](https://github.com/PyCQA/isort/blob/main/CHANGELOG.md) 370 | - [Commits](https://github.com/pycqa/isort/compare/5.12.0...5.13.0) 371 | 372 | --- 373 | updated-dependencies: 374 | - dependency-name: isort 375 | dependency-type: direct:development 376 | update-type: version-update:semver-minor 377 | ... 378 | 379 | Signed-off-by: dependabot[bot] <support@github.com> ([`89a31c9`](https://github.com/supabase-community/functions-py/commit/89a31c9afb987063f18dd853bc826e4d7e815be3)) 380 | 381 | * 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)) 382 | 383 | * chore(deps-dev): bump pytest-asyncio from 0.21.1 to 0.23.2 384 | 385 | Bumps [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) from 0.21.1 to 0.23.2. 386 | - [Release notes](https://github.com/pytest-dev/pytest-asyncio/releases) 387 | - [Commits](https://github.com/pytest-dev/pytest-asyncio/compare/v0.21.1...v0.23.2) 388 | 389 | --- 390 | updated-dependencies: 391 | - dependency-name: pytest-asyncio 392 | dependency-type: direct:development 393 | update-type: version-update:semver-minor 394 | ... 395 | 396 | Signed-off-by: dependabot[bot] <support@github.com> ([`f6fb590`](https://github.com/supabase-community/functions-py/commit/f6fb5906e99e5194413ef8b7f819ba82ae47355e)) 397 | 398 | * chore: allow manual workflow trigger for releases (#19) ([`07d1ffa`](https://github.com/supabase-community/functions-py/commit/07d1ffa6faa89219f7c73cbd4699436763d9d8bc)) 399 | 400 | * chore: allow manual workflow trigger for releases ([`2a01399`](https://github.com/supabase-community/functions-py/commit/2a013997215f417fb0efe2badfc9b8a2d3686c48)) 401 | 402 | ### Ci 403 | 404 | * ci: update workflow with new pypi project name (#34) ([`7564e2b`](https://github.com/supabase-community/functions-py/commit/7564e2bc1d157a279175a3c8ad6fb2708e1700f4)) 405 | 406 | ### Fix 407 | 408 | * fix: update httpx and other dev dependencies (#35) ([`1f8897f`](https://github.com/supabase-community/functions-py/commit/1f8897f88acc4449cd697bd0e122bd4ee3bf0417)) 409 | 410 | ## v0.3.1 (2023-10-30) 411 | 412 | ### Chore 413 | 414 | * chore(release): bump version to v0.3.1 ([`b787f01`](https://github.com/supabase-community/functions-py/commit/b787f0187c1a5312ea368919afd24863ff2f40f0)) 415 | 416 | ### Fix 417 | 418 | * fix: exceptions now has message in dictionary (#16) ([`7273927`](https://github.com/supabase-community/functions-py/commit/7273927aa9d0e6eb9d9c9985a7ba5b42f9b6296d)) 419 | 420 | * fix: exceptions now has message in dictionary 421 | 422 | Added tests to check for the messages. ([`07a813a`](https://github.com/supabase-community/functions-py/commit/07a813a02ffcf8999802cece27ee5278c140760d)) 423 | 424 | ## v0.3.0 (2023-10-29) 425 | 426 | ### Chore 427 | 428 | * chore(release): bump version to v0.3.0 ([`4e18712`](https://github.com/supabase-community/functions-py/commit/4e1871215e72efed058d5adf619ae2be0bb27b56)) 429 | 430 | ### Feature 431 | 432 | * feat: downgrade httpx dep to 0.24.0 (#15) ([`1f37216`](https://github.com/supabase-community/functions-py/commit/1f37216326c26b65a3c9ccd1c29bea0a184c7624)) 433 | 434 | ### Fix 435 | 436 | * fix: update lockfile ([`d4856ec`](https://github.com/supabase-community/functions-py/commit/d4856ec8c6bbbde7efe7ca67c5137ba75e8e7bdb)) 437 | 438 | ### Unknown 439 | 440 | * Update pyproject.toml ([`dd43949`](https://github.com/supabase-community/functions-py/commit/dd4394994ae995dd6f953093da73cbd9c1344483)) 441 | 442 | * Restoring order to the CI/CD pipeline ([`4f28dc6`](https://github.com/supabase-community/functions-py/commit/4f28dc628c9a9aac27a153121c90960bddb5c8bf)) 443 | 444 | ## v0.2.4 (2023-10-25) 445 | 446 | ### Chore 447 | 448 | * chore(release): bump version to v0.2.4 ([`f618547`](https://github.com/supabase-community/functions-py/commit/f61854760d2d90d1352962e427d099da6dac50c1)) 449 | 450 | * chore: update readme with correct function call ([`88fc1a7`](https://github.com/supabase-community/functions-py/commit/88fc1a797ef7d848bd2e870ddadcf8d51d405989)) 451 | 452 | * chore(release): bump version to v0.2.4 ([`e958722`](https://github.com/supabase-community/functions-py/commit/e95872200a7470da0e92bd95431eea1e20c66df3)) 453 | 454 | * chore: bump autoflake version ([`fc3a7bb`](https://github.com/supabase-community/functions-py/commit/fc3a7bb5788feca7acbdf4662feee7cce87f2cda)) 455 | 456 | ### Fix 457 | 458 | * fix: correct return type from invoke ([`9a15026`](https://github.com/supabase-community/functions-py/commit/9a15026bbbc63cd4b6d960f8a48db40a06770381)) 459 | 460 | * fix: add single instance of client instantiation ([`4b8a134`](https://github.com/supabase-community/functions-py/commit/4b8a134ac675bdcc0387cb1d1d55068e1b6be253)) 461 | 462 | ### Unknown 463 | 464 | * Temporary CI change to allow publishing of package ([`2d66f21`](https://github.com/supabase-community/functions-py/commit/2d66f21c01efcfd3ba34ab458c11977009580118)) 465 | 466 | * Merge pull request #11 from supabase-community/silentworks/update-readme 467 | 468 | chore: update readme with correct function call ([`1dfe72e`](https://github.com/supabase-community/functions-py/commit/1dfe72eb28c35b0452e8f65d6e9612f9d4e80eb3)) 469 | 470 | * Merge pull request #9 from supabase-community/silentworks/sync_functions 471 | 472 | Add sync functions ([`e2db242`](https://github.com/supabase-community/functions-py/commit/e2db242994c66ce3beec399416512342a2266f85)) 473 | 474 | * update file formatting with black ([`b1c64f5`](https://github.com/supabase-community/functions-py/commit/b1c64f51a487e9782c45324d0903a5e18c7bd31e)) 475 | 476 | * Apply suggestions from code review 477 | 478 | Co-authored-by: Anand <40204976+anand2312@users.noreply.github.com> ([`e2a59aa`](https://github.com/supabase-community/functions-py/commit/e2a59aaff604a8c0ff1e3d648a1d2ae3aff44ea7)) 479 | 480 | * Add github workflow ([`14c8db9`](https://github.com/supabase-community/functions-py/commit/14c8db932527056343e5e7af012db87af6242006)) 481 | 482 | * Update errors and function signature of invoke ([`575da96`](https://github.com/supabase-community/functions-py/commit/575da968238a494de0996226df0bf54a48bf4e2b)) 483 | 484 | * Apply suggestions from code review 485 | 486 | Co-authored-by: Anand <40204976+anand2312@users.noreply.github.com> ([`b60615d`](https://github.com/supabase-community/functions-py/commit/b60615d2c70c5a2e32e87f850d5fe1d68e492f59)) 487 | 488 | * Update supafunc/errors.py 489 | 490 | Co-authored-by: Anand <40204976+anand2312@users.noreply.github.com> ([`2971673`](https://github.com/supabase-community/functions-py/commit/2971673451c4775c3f5e400983972bebefff4dfe)) 491 | 492 | * Add sync functions 493 | Add pytests 494 | Throw errors instead of returning them ([`692022f`](https://github.com/supabase-community/functions-py/commit/692022fa4816de5ec3e4cd929352535af719bb87)) 495 | 496 | ## v0.2.3 (2023-08-04) 497 | 498 | ### Chore 499 | 500 | * chore: bump version ([`d5f32ba`](https://github.com/supabase-community/functions-py/commit/d5f32ba75368cc1ba337e1cda0e6d89d426160b1)) 501 | 502 | * chore: bump httpx to 0.24 ([`6152992`](https://github.com/supabase-community/functions-py/commit/615299278b1d810c1113546938b81eabf075987f)) 503 | 504 | * chore(deps): bump httpx from 0.23.0 to 0.24.1 505 | 506 | Bumps [httpx](https://github.com/encode/httpx) from 0.23.0 to 0.24.1. 507 | - [Release notes](https://github.com/encode/httpx/releases) 508 | - [Changelog](https://github.com/encode/httpx/blob/master/CHANGELOG.md) 509 | - [Commits](https://github.com/encode/httpx/compare/0.23.0...0.24.1) 510 | 511 | --- 512 | updated-dependencies: 513 | - dependency-name: httpx 514 | dependency-type: direct:production 515 | update-type: version-update:semver-minor 516 | ... 517 | 518 | Signed-off-by: dependabot[bot] <support@github.com> ([`f5c5ca4`](https://github.com/supabase-community/functions-py/commit/f5c5ca44de7d130be5ba2da0671ae8a845ad4d0d)) 519 | 520 | ### Fix 521 | 522 | * fix: small typo in authorization 523 | 524 | for consistent naming ([`3f0bba8`](https://github.com/supabase-community/functions-py/commit/3f0bba80100f86886f4e8132862fc1e96868e479)) 525 | 526 | ### Unknown 527 | 528 | * Merge pull request #4 from anand2312/annad/bump-httpx 529 | 530 | chore: bump httpx to 0.24 ([`f9d1c3c`](https://github.com/supabase-community/functions-py/commit/f9d1c3c6f3611f322d336bbd86f080c0d65f6d28)) 531 | 532 | * Merge pull request #3 from supabase-community/dependabot/pip/main/httpx-0.24.1 533 | 534 | chore(deps): bump httpx from 0.23.0 to 0.24.1 ([`67ada27`](https://github.com/supabase-community/functions-py/commit/67ada272a6ea645b9b51041e6dedd829d3410113)) 535 | 536 | * Create dependabot.yml ([`c7394d7`](https://github.com/supabase-community/functions-py/commit/c7394d7691b6ed35997f6222fe8f37748e132242)) 537 | 538 | * Merge pull request #2 from 0xflotus/patch-1 539 | 540 | fix: small typo in authorization ([`7c60eda`](https://github.com/supabase-community/functions-py/commit/7c60eda605337784a63a99a1405a6cb2c5f407f1)) 541 | 542 | ## v0.2.2 (2022-10-10) 543 | 544 | ### Chore 545 | 546 | * chore: update version ([`a6584a7`](https://github.com/supabase-community/functions-py/commit/a6584a783ed9fea347f89c87f420ba4d56e0383a)) 547 | 548 | ### Feature 549 | 550 | * feat: version 0.1.4 ([`7ffeec5`](https://github.com/supabase-community/functions-py/commit/7ffeec5465ce86f7ee077dbf18c21f332f31b1a5)) 551 | 552 | ### Fix 553 | 554 | * fix: update dependencies ([`91bc97b`](https://github.com/supabase-community/functions-py/commit/91bc97b66ef609618bb953a6557a2eb904b35d00)) 555 | 556 | * fix: add default for optional headers ([`02c838c`](https://github.com/supabase-community/functions-py/commit/02c838c73b692ea16912192278dd8550570553a1)) 557 | 558 | * fix: pass down body ([`d3f9f61`](https://github.com/supabase-community/functions-py/commit/d3f9f6187b7bd7206f89eb3331fa6ea6f13dd58e)) 559 | 560 | ## v0.1.4 (2022-03-31) 561 | 562 | ### Chore 563 | 564 | * chore: update version ([`883661f`](https://github.com/supabase-community/functions-py/commit/883661f3c50d8d5fabcbf9639daaf7e6b8fa2499)) 565 | 566 | * chore: update version ([`61f78b4`](https://github.com/supabase-community/functions-py/commit/61f78b4986917a234e631a233d72f65b28b414a4)) 567 | 568 | * chore: cleanup and add LICENSE ([`c9d035e`](https://github.com/supabase-community/functions-py/commit/c9d035eb4005ef9b595206395513abaca8325953)) 569 | 570 | * chore: rename and update README ([`0e26f92`](https://github.com/supabase-community/functions-py/commit/0e26f92f27079d6ab63da6860f6a26d229be2374)) 571 | 572 | ### Unknown 573 | 574 | * initial commit ([`5c93da6`](https://github.com/supabase-community/functions-py/commit/5c93da6948288c3312c0065e22ab968d25b9801b)) 575 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "anyio" 5 | version = "4.6.2.post1" 6 | description = "High level compatibility layer for multiple asynchronous event loop implementations" 7 | optional = false 8 | python-versions = ">=3.9" 9 | groups = ["main"] 10 | files = [ 11 | {file = "anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d"}, 12 | {file = "anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c"}, 13 | ] 14 | 15 | [package.dependencies] 16 | exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} 17 | idna = ">=2.8" 18 | sniffio = ">=1.1" 19 | typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} 20 | 21 | [package.extras] 22 | doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] 23 | test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21.0b1) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\""] 24 | trio = ["trio (>=0.26.1)"] 25 | 26 | [[package]] 27 | name = "backports-asyncio-runner" 28 | version = "1.2.0" 29 | description = "Backport of asyncio.Runner, a context manager that controls event loop life cycle." 30 | optional = false 31 | python-versions = "<3.11,>=3.8" 32 | groups = ["dev"] 33 | markers = "python_version < \"3.11\"" 34 | files = [ 35 | {file = "backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5"}, 36 | {file = "backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162"}, 37 | ] 38 | 39 | [[package]] 40 | name = "certifi" 41 | version = "2024.8.30" 42 | description = "Python package for providing Mozilla's CA Bundle." 43 | optional = false 44 | python-versions = ">=3.6" 45 | groups = ["main"] 46 | files = [ 47 | {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, 48 | {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, 49 | ] 50 | 51 | [[package]] 52 | name = "cfgv" 53 | version = "3.4.0" 54 | description = "Validate configuration and produce human readable error messages." 55 | optional = false 56 | python-versions = ">=3.8" 57 | groups = ["dev"] 58 | files = [ 59 | {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, 60 | {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, 61 | ] 62 | 63 | [[package]] 64 | name = "click" 65 | version = "8.1.7" 66 | description = "Composable command line interface toolkit" 67 | optional = false 68 | python-versions = ">=3.7" 69 | groups = ["dev"] 70 | files = [ 71 | {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, 72 | {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, 73 | ] 74 | 75 | [package.dependencies] 76 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 77 | 78 | [[package]] 79 | name = "colorama" 80 | version = "0.4.6" 81 | description = "Cross-platform colored terminal text." 82 | optional = false 83 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 84 | groups = ["dev"] 85 | markers = "sys_platform == \"win32\" or platform_system == \"Windows\"" 86 | files = [ 87 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 88 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 89 | ] 90 | 91 | [[package]] 92 | name = "coverage" 93 | version = "7.9.1" 94 | description = "Code coverage measurement for Python" 95 | optional = false 96 | python-versions = ">=3.9" 97 | groups = ["dev"] 98 | files = [ 99 | {file = "coverage-7.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cc94d7c5e8423920787c33d811c0be67b7be83c705f001f7180c7b186dcf10ca"}, 100 | {file = "coverage-7.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:16aa0830d0c08a2c40c264cef801db8bc4fc0e1892782e45bcacbd5889270509"}, 101 | {file = "coverage-7.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf95981b126f23db63e9dbe4cf65bd71f9a6305696fa5e2262693bc4e2183f5b"}, 102 | {file = "coverage-7.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f05031cf21699785cd47cb7485f67df619e7bcdae38e0fde40d23d3d0210d3c3"}, 103 | {file = "coverage-7.9.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb4fbcab8764dc072cb651a4bcda4d11fb5658a1d8d68842a862a6610bd8cfa3"}, 104 | {file = "coverage-7.9.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0f16649a7330ec307942ed27d06ee7e7a38417144620bb3d6e9a18ded8a2d3e5"}, 105 | {file = "coverage-7.9.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cea0a27a89e6432705fffc178064503508e3c0184b4f061700e771a09de58187"}, 106 | {file = "coverage-7.9.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e980b53a959fa53b6f05343afbd1e6f44a23ed6c23c4b4c56c6662bbb40c82ce"}, 107 | {file = "coverage-7.9.1-cp310-cp310-win32.whl", hash = "sha256:70760b4c5560be6ca70d11f8988ee6542b003f982b32f83d5ac0b72476607b70"}, 108 | {file = "coverage-7.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:a66e8f628b71f78c0e0342003d53b53101ba4e00ea8dabb799d9dba0abbbcebe"}, 109 | {file = "coverage-7.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:95c765060e65c692da2d2f51a9499c5e9f5cf5453aeaf1420e3fc847cc060582"}, 110 | {file = "coverage-7.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ba383dc6afd5ec5b7a0d0c23d38895db0e15bcba7fb0fa8901f245267ac30d86"}, 111 | {file = "coverage-7.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37ae0383f13cbdcf1e5e7014489b0d71cc0106458878ccde52e8a12ced4298ed"}, 112 | {file = "coverage-7.9.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:69aa417a030bf11ec46149636314c24c8d60fadb12fc0ee8f10fda0d918c879d"}, 113 | {file = "coverage-7.9.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a4be2a28656afe279b34d4f91c3e26eccf2f85500d4a4ff0b1f8b54bf807338"}, 114 | {file = "coverage-7.9.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:382e7ddd5289f140259b610e5f5c58f713d025cb2f66d0eb17e68d0a94278875"}, 115 | {file = "coverage-7.9.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e5532482344186c543c37bfad0ee6069e8ae4fc38d073b8bc836fc8f03c9e250"}, 116 | {file = "coverage-7.9.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a39d18b3f50cc121d0ce3838d32d58bd1d15dab89c910358ebefc3665712256c"}, 117 | {file = "coverage-7.9.1-cp311-cp311-win32.whl", hash = "sha256:dd24bd8d77c98557880def750782df77ab2b6885a18483dc8588792247174b32"}, 118 | {file = "coverage-7.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:6b55ad10a35a21b8015eabddc9ba31eb590f54adc9cd39bcf09ff5349fd52125"}, 119 | {file = "coverage-7.9.1-cp311-cp311-win_arm64.whl", hash = "sha256:6ad935f0016be24c0e97fc8c40c465f9c4b85cbbe6eac48934c0dc4d2568321e"}, 120 | {file = "coverage-7.9.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8de12b4b87c20de895f10567639c0797b621b22897b0af3ce4b4e204a743626"}, 121 | {file = "coverage-7.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5add197315a054e92cee1b5f686a2bcba60c4c3e66ee3de77ace6c867bdee7cb"}, 122 | {file = "coverage-7.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:600a1d4106fe66f41e5d0136dfbc68fe7200a5cbe85610ddf094f8f22e1b0300"}, 123 | {file = "coverage-7.9.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a876e4c3e5a2a1715a6608906aa5a2e0475b9c0f68343c2ada98110512ab1d8"}, 124 | {file = "coverage-7.9.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81f34346dd63010453922c8e628a52ea2d2ccd73cb2487f7700ac531b247c8a5"}, 125 | {file = "coverage-7.9.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:888f8eee13f2377ce86d44f338968eedec3291876b0b8a7289247ba52cb984cd"}, 126 | {file = "coverage-7.9.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9969ef1e69b8c8e1e70d591f91bbc37fc9a3621e447525d1602801a24ceda898"}, 127 | {file = "coverage-7.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:60c458224331ee3f1a5b472773e4a085cc27a86a0b48205409d364272d67140d"}, 128 | {file = "coverage-7.9.1-cp312-cp312-win32.whl", hash = "sha256:5f646a99a8c2b3ff4c6a6e081f78fad0dde275cd59f8f49dc4eab2e394332e74"}, 129 | {file = "coverage-7.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:30f445f85c353090b83e552dcbbdad3ec84c7967e108c3ae54556ca69955563e"}, 130 | {file = "coverage-7.9.1-cp312-cp312-win_arm64.whl", hash = "sha256:af41da5dca398d3474129c58cb2b106a5d93bbb196be0d307ac82311ca234342"}, 131 | {file = "coverage-7.9.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:31324f18d5969feef7344a932c32428a2d1a3e50b15a6404e97cba1cc9b2c631"}, 132 | {file = "coverage-7.9.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0c804506d624e8a20fb3108764c52e0eef664e29d21692afa375e0dd98dc384f"}, 133 | {file = "coverage-7.9.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef64c27bc40189f36fcc50c3fb8f16ccda73b6a0b80d9bd6e6ce4cffcd810bbd"}, 134 | {file = "coverage-7.9.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4fe2348cc6ec372e25adec0219ee2334a68d2f5222e0cba9c0d613394e12d86"}, 135 | {file = "coverage-7.9.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34ed2186fe52fcc24d4561041979a0dec69adae7bce2ae8d1c49eace13e55c43"}, 136 | {file = "coverage-7.9.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:25308bd3d00d5eedd5ae7d4357161f4df743e3c0240fa773ee1b0f75e6c7c0f1"}, 137 | {file = "coverage-7.9.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:73e9439310f65d55a5a1e0564b48e34f5369bee943d72c88378f2d576f5a5751"}, 138 | {file = "coverage-7.9.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:37ab6be0859141b53aa89412a82454b482c81cf750de4f29223d52268a86de67"}, 139 | {file = "coverage-7.9.1-cp313-cp313-win32.whl", hash = "sha256:64bdd969456e2d02a8b08aa047a92d269c7ac1f47e0c977675d550c9a0863643"}, 140 | {file = "coverage-7.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:be9e3f68ca9edb897c2184ad0eee815c635565dbe7a0e7e814dc1f7cbab92c0a"}, 141 | {file = "coverage-7.9.1-cp313-cp313-win_arm64.whl", hash = "sha256:1c503289ffef1d5105d91bbb4d62cbe4b14bec4d13ca225f9c73cde9bb46207d"}, 142 | {file = "coverage-7.9.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0b3496922cb5f4215bf5caaef4cf12364a26b0be82e9ed6d050f3352cf2d7ef0"}, 143 | {file = "coverage-7.9.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9565c3ab1c93310569ec0d86b017f128f027cab0b622b7af288696d7ed43a16d"}, 144 | {file = "coverage-7.9.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2241ad5dbf79ae1d9c08fe52b36d03ca122fb9ac6bca0f34439e99f8327ac89f"}, 145 | {file = "coverage-7.9.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bb5838701ca68b10ebc0937dbd0eb81974bac54447c55cd58dea5bca8451029"}, 146 | {file = "coverage-7.9.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b30a25f814591a8c0c5372c11ac8967f669b97444c47fd794926e175c4047ece"}, 147 | {file = "coverage-7.9.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2d04b16a6062516df97969f1ae7efd0de9c31eb6ebdceaa0d213b21c0ca1a683"}, 148 | {file = "coverage-7.9.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7931b9e249edefb07cd6ae10c702788546341d5fe44db5b6108a25da4dca513f"}, 149 | {file = "coverage-7.9.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52e92b01041151bf607ee858e5a56c62d4b70f4dac85b8c8cb7fb8a351ab2c10"}, 150 | {file = "coverage-7.9.1-cp313-cp313t-win32.whl", hash = "sha256:684e2110ed84fd1ca5f40e89aa44adf1729dc85444004111aa01866507adf363"}, 151 | {file = "coverage-7.9.1-cp313-cp313t-win_amd64.whl", hash = "sha256:437c576979e4db840539674e68c84b3cda82bc824dd138d56bead1435f1cb5d7"}, 152 | {file = "coverage-7.9.1-cp313-cp313t-win_arm64.whl", hash = "sha256:18a0912944d70aaf5f399e350445738a1a20b50fbea788f640751c2ed9208b6c"}, 153 | {file = "coverage-7.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6f424507f57878e424d9a95dc4ead3fbdd72fd201e404e861e465f28ea469951"}, 154 | {file = "coverage-7.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:535fde4001b2783ac80865d90e7cc7798b6b126f4cd8a8c54acfe76804e54e58"}, 155 | {file = "coverage-7.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02532fd3290bb8fa6bec876520842428e2a6ed6c27014eca81b031c2d30e3f71"}, 156 | {file = "coverage-7.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56f5eb308b17bca3bbff810f55ee26d51926d9f89ba92707ee41d3c061257e55"}, 157 | {file = "coverage-7.9.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfa447506c1a52271f1b0de3f42ea0fa14676052549095e378d5bff1c505ff7b"}, 158 | {file = "coverage-7.9.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9ca8e220006966b4a7b68e8984a6aee645a0384b0769e829ba60281fe61ec4f7"}, 159 | {file = "coverage-7.9.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:49f1d0788ba5b7ba65933f3a18864117c6506619f5ca80326b478f72acf3f385"}, 160 | {file = "coverage-7.9.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:68cd53aec6f45b8e4724c0950ce86eacb775c6be01ce6e3669fe4f3a21e768ed"}, 161 | {file = "coverage-7.9.1-cp39-cp39-win32.whl", hash = "sha256:95335095b6c7b1cc14c3f3f17d5452ce677e8490d101698562b2ffcacc304c8d"}, 162 | {file = "coverage-7.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:e1b5191d1648acc439b24721caab2fd0c86679d8549ed2c84d5a7ec1bedcc244"}, 163 | {file = "coverage-7.9.1-pp39.pp310.pp311-none-any.whl", hash = "sha256:db0f04118d1db74db6c9e1cb1898532c7dcc220f1d2718f058601f7c3f499514"}, 164 | {file = "coverage-7.9.1-py3-none-any.whl", hash = "sha256:66b974b145aa189516b6bf2d8423e888b742517d37872f6ee4c5be0073bd9a3c"}, 165 | {file = "coverage-7.9.1.tar.gz", hash = "sha256:6cf43c78c4282708a28e466316935ec7489a9c487518a77fa68f716c67909cec"}, 166 | ] 167 | 168 | [package.dependencies] 169 | tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} 170 | 171 | [package.extras] 172 | toml = ["tomli ; python_full_version <= \"3.11.0a6\""] 173 | 174 | [[package]] 175 | name = "distlib" 176 | version = "0.3.9" 177 | description = "Distribution utilities" 178 | optional = false 179 | python-versions = "*" 180 | groups = ["dev"] 181 | files = [ 182 | {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, 183 | {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, 184 | ] 185 | 186 | [[package]] 187 | name = "exceptiongroup" 188 | version = "1.2.2" 189 | description = "Backport of PEP 654 (exception groups)" 190 | optional = false 191 | python-versions = ">=3.7" 192 | groups = ["main", "dev"] 193 | markers = "python_version < \"3.11\"" 194 | files = [ 195 | {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, 196 | {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, 197 | ] 198 | 199 | [package.extras] 200 | test = ["pytest (>=6)"] 201 | 202 | [[package]] 203 | name = "filelock" 204 | version = "3.16.1" 205 | description = "A platform independent file lock." 206 | optional = false 207 | python-versions = ">=3.8" 208 | groups = ["dev"] 209 | files = [ 210 | {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, 211 | {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, 212 | ] 213 | 214 | [package.extras] 215 | docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"] 216 | testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] 217 | typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] 218 | 219 | [[package]] 220 | name = "h11" 221 | version = "0.14.0" 222 | description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" 223 | optional = false 224 | python-versions = ">=3.7" 225 | groups = ["main"] 226 | files = [ 227 | {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, 228 | {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, 229 | ] 230 | 231 | [[package]] 232 | name = "h2" 233 | version = "4.1.0" 234 | description = "HTTP/2 State-Machine based protocol implementation" 235 | optional = false 236 | python-versions = ">=3.6.1" 237 | groups = ["main"] 238 | files = [ 239 | {file = "h2-4.1.0-py3-none-any.whl", hash = "sha256:03a46bcf682256c95b5fd9e9a99c1323584c3eec6440d379b9903d709476bc6d"}, 240 | {file = "h2-4.1.0.tar.gz", hash = "sha256:a83aca08fbe7aacb79fec788c9c0bac936343560ed9ec18b82a13a12c28d2abb"}, 241 | ] 242 | 243 | [package.dependencies] 244 | hpack = ">=4.0,<5" 245 | hyperframe = ">=6.0,<7" 246 | 247 | [[package]] 248 | name = "hpack" 249 | version = "4.0.0" 250 | description = "Pure-Python HPACK header compression" 251 | optional = false 252 | python-versions = ">=3.6.1" 253 | groups = ["main"] 254 | files = [ 255 | {file = "hpack-4.0.0-py3-none-any.whl", hash = "sha256:84a076fad3dc9a9f8063ccb8041ef100867b1878b25ef0ee63847a5d53818a6c"}, 256 | {file = "hpack-4.0.0.tar.gz", hash = "sha256:fc41de0c63e687ebffde81187a948221294896f6bdc0ae2312708df339430095"}, 257 | ] 258 | 259 | [[package]] 260 | name = "httpcore" 261 | version = "1.0.6" 262 | description = "A minimal low-level HTTP client." 263 | optional = false 264 | python-versions = ">=3.8" 265 | groups = ["main"] 266 | files = [ 267 | {file = "httpcore-1.0.6-py3-none-any.whl", hash = "sha256:27b59625743b85577a8c0e10e55b50b5368a4f2cfe8cc7bcfa9cf00829c2682f"}, 268 | {file = "httpcore-1.0.6.tar.gz", hash = "sha256:73f6dbd6eb8c21bbf7ef8efad555481853f5f6acdeaff1edb0694289269ee17f"}, 269 | ] 270 | 271 | [package.dependencies] 272 | certifi = "*" 273 | h11 = ">=0.13,<0.15" 274 | 275 | [package.extras] 276 | asyncio = ["anyio (>=4.0,<5.0)"] 277 | http2 = ["h2 (>=3,<5)"] 278 | socks = ["socksio (==1.*)"] 279 | trio = ["trio (>=0.22.0,<1.0)"] 280 | 281 | [[package]] 282 | name = "httpx" 283 | version = "0.28.1" 284 | description = "The next generation HTTP client." 285 | optional = false 286 | python-versions = ">=3.8" 287 | groups = ["main"] 288 | files = [ 289 | {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, 290 | {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, 291 | ] 292 | 293 | [package.dependencies] 294 | anyio = "*" 295 | certifi = "*" 296 | h2 = {version = ">=3,<5", optional = true, markers = "extra == \"http2\""} 297 | httpcore = "==1.*" 298 | idna = "*" 299 | 300 | [package.extras] 301 | brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] 302 | cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] 303 | http2 = ["h2 (>=3,<5)"] 304 | socks = ["socksio (==1.*)"] 305 | zstd = ["zstandard (>=0.18.0)"] 306 | 307 | [[package]] 308 | name = "hyperframe" 309 | version = "6.0.1" 310 | description = "HTTP/2 framing layer for Python" 311 | optional = false 312 | python-versions = ">=3.6.1" 313 | groups = ["main"] 314 | files = [ 315 | {file = "hyperframe-6.0.1-py3-none-any.whl", hash = "sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15"}, 316 | {file = "hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914"}, 317 | ] 318 | 319 | [[package]] 320 | name = "identify" 321 | version = "2.6.1" 322 | description = "File identification library for Python" 323 | optional = false 324 | python-versions = ">=3.8" 325 | groups = ["dev"] 326 | files = [ 327 | {file = "identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0"}, 328 | {file = "identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98"}, 329 | ] 330 | 331 | [package.extras] 332 | license = ["ukkonen"] 333 | 334 | [[package]] 335 | name = "idna" 336 | version = "3.10" 337 | description = "Internationalized Domain Names in Applications (IDNA)" 338 | optional = false 339 | python-versions = ">=3.6" 340 | groups = ["main"] 341 | files = [ 342 | {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, 343 | {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, 344 | ] 345 | 346 | [package.extras] 347 | all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] 348 | 349 | [[package]] 350 | name = "iniconfig" 351 | version = "2.0.0" 352 | description = "brain-dead simple config-ini parsing" 353 | optional = false 354 | python-versions = ">=3.7" 355 | groups = ["dev"] 356 | files = [ 357 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 358 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 359 | ] 360 | 361 | [[package]] 362 | name = "nodeenv" 363 | version = "1.9.1" 364 | description = "Node.js virtual environment builder" 365 | optional = false 366 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 367 | groups = ["dev"] 368 | files = [ 369 | {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, 370 | {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, 371 | ] 372 | 373 | [[package]] 374 | name = "packaging" 375 | version = "24.1" 376 | description = "Core utilities for Python packages" 377 | optional = false 378 | python-versions = ">=3.8" 379 | groups = ["dev"] 380 | files = [ 381 | {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, 382 | {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, 383 | ] 384 | 385 | [[package]] 386 | name = "platformdirs" 387 | version = "4.3.6" 388 | description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." 389 | optional = false 390 | python-versions = ">=3.8" 391 | groups = ["dev"] 392 | files = [ 393 | {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, 394 | {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, 395 | ] 396 | 397 | [package.extras] 398 | docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] 399 | test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] 400 | type = ["mypy (>=1.11.2)"] 401 | 402 | [[package]] 403 | name = "pluggy" 404 | version = "1.5.0" 405 | description = "plugin and hook calling mechanisms for python" 406 | optional = false 407 | python-versions = ">=3.8" 408 | groups = ["dev"] 409 | files = [ 410 | {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, 411 | {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, 412 | ] 413 | 414 | [package.extras] 415 | dev = ["pre-commit", "tox"] 416 | testing = ["pytest", "pytest-benchmark"] 417 | 418 | [[package]] 419 | name = "pre-commit" 420 | version = "4.2.0" 421 | description = "A framework for managing and maintaining multi-language pre-commit hooks." 422 | optional = false 423 | python-versions = ">=3.9" 424 | groups = ["dev"] 425 | files = [ 426 | {file = "pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd"}, 427 | {file = "pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146"}, 428 | ] 429 | 430 | [package.dependencies] 431 | cfgv = ">=2.0.0" 432 | identify = ">=1.0.0" 433 | nodeenv = ">=0.11.1" 434 | pyyaml = ">=5.1" 435 | virtualenv = ">=20.10.0" 436 | 437 | [[package]] 438 | name = "pygments" 439 | version = "2.19.1" 440 | description = "Pygments is a syntax highlighting package written in Python." 441 | optional = false 442 | python-versions = ">=3.8" 443 | groups = ["dev"] 444 | files = [ 445 | {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, 446 | {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, 447 | ] 448 | 449 | [package.extras] 450 | windows-terminal = ["colorama (>=0.4.6)"] 451 | 452 | [[package]] 453 | name = "pyjwt" 454 | version = "2.10.1" 455 | description = "JSON Web Token implementation in Python" 456 | optional = false 457 | python-versions = ">=3.9" 458 | groups = ["dev"] 459 | files = [ 460 | {file = "PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb"}, 461 | {file = "pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953"}, 462 | ] 463 | 464 | [package.extras] 465 | crypto = ["cryptography (>=3.4.0)"] 466 | dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx", "sphinx-rtd-theme", "zope.interface"] 467 | docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] 468 | tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] 469 | 470 | [[package]] 471 | name = "pytest" 472 | version = "8.4.1" 473 | description = "pytest: simple powerful testing with Python" 474 | optional = false 475 | python-versions = ">=3.9" 476 | groups = ["dev"] 477 | files = [ 478 | {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"}, 479 | {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"}, 480 | ] 481 | 482 | [package.dependencies] 483 | colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} 484 | exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} 485 | iniconfig = ">=1" 486 | packaging = ">=20" 487 | pluggy = ">=1.5,<2" 488 | pygments = ">=2.7.2" 489 | tomli = {version = ">=1", markers = "python_version < \"3.11\""} 490 | 491 | [package.extras] 492 | dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] 493 | 494 | [[package]] 495 | name = "pytest-asyncio" 496 | version = "1.1.0" 497 | description = "Pytest support for asyncio" 498 | optional = false 499 | python-versions = ">=3.9" 500 | groups = ["dev"] 501 | files = [ 502 | {file = "pytest_asyncio-1.1.0-py3-none-any.whl", hash = "sha256:5fe2d69607b0bd75c656d1211f969cadba035030156745ee09e7d71740e58ecf"}, 503 | {file = "pytest_asyncio-1.1.0.tar.gz", hash = "sha256:796aa822981e01b68c12e4827b8697108f7205020f24b5793b3c41555dab68ea"}, 504 | ] 505 | 506 | [package.dependencies] 507 | backports-asyncio-runner = {version = ">=1.1,<2", markers = "python_version < \"3.11\""} 508 | pytest = ">=8.2,<9" 509 | typing-extensions = {version = ">=4.12", markers = "python_version < \"3.10\""} 510 | 511 | [package.extras] 512 | docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"] 513 | testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] 514 | 515 | [[package]] 516 | name = "pytest-cov" 517 | version = "6.2.1" 518 | description = "Pytest plugin for measuring coverage." 519 | optional = false 520 | python-versions = ">=3.9" 521 | groups = ["dev"] 522 | files = [ 523 | {file = "pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5"}, 524 | {file = "pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2"}, 525 | ] 526 | 527 | [package.dependencies] 528 | coverage = {version = ">=7.5", extras = ["toml"]} 529 | pluggy = ">=1.2" 530 | pytest = ">=6.2.5" 531 | 532 | [package.extras] 533 | testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] 534 | 535 | [[package]] 536 | name = "pyyaml" 537 | version = "6.0.2" 538 | description = "YAML parser and emitter for Python" 539 | optional = false 540 | python-versions = ">=3.8" 541 | groups = ["dev"] 542 | files = [ 543 | {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, 544 | {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, 545 | {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, 546 | {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, 547 | {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, 548 | {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, 549 | {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, 550 | {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, 551 | {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, 552 | {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, 553 | {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, 554 | {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, 555 | {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, 556 | {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, 557 | {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, 558 | {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, 559 | {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, 560 | {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, 561 | {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, 562 | {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, 563 | {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, 564 | {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, 565 | {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, 566 | {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, 567 | {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, 568 | {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, 569 | {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, 570 | {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, 571 | {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, 572 | {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, 573 | {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, 574 | {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, 575 | {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, 576 | {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, 577 | {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, 578 | {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, 579 | {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, 580 | {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, 581 | {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, 582 | {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, 583 | {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, 584 | {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, 585 | {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, 586 | {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, 587 | {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, 588 | {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, 589 | {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, 590 | {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, 591 | {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, 592 | {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, 593 | {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, 594 | {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, 595 | {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, 596 | ] 597 | 598 | [[package]] 599 | name = "ruff" 600 | version = "0.12.5" 601 | description = "An extremely fast Python linter and code formatter, written in Rust." 602 | optional = false 603 | python-versions = ">=3.7" 604 | groups = ["dev"] 605 | files = [ 606 | {file = "ruff-0.12.5-py3-none-linux_armv6l.whl", hash = "sha256:1de2c887e9dec6cb31fcb9948299de5b2db38144e66403b9660c9548a67abd92"}, 607 | {file = "ruff-0.12.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d1ab65e7d8152f519e7dea4de892317c9da7a108da1c56b6a3c1d5e7cf4c5e9a"}, 608 | {file = "ruff-0.12.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:962775ed5b27c7aa3fdc0d8f4d4433deae7659ef99ea20f783d666e77338b8cf"}, 609 | {file = "ruff-0.12.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73b4cae449597e7195a49eb1cdca89fd9fbb16140c7579899e87f4c85bf82f73"}, 610 | {file = "ruff-0.12.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b13489c3dc50de5e2d40110c0cce371e00186b880842e245186ca862bf9a1ac"}, 611 | {file = "ruff-0.12.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1504fea81461cf4841778b3ef0a078757602a3b3ea4b008feb1308cb3f23e08"}, 612 | {file = "ruff-0.12.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c7da4129016ae26c32dfcbd5b671fe652b5ab7fc40095d80dcff78175e7eddd4"}, 613 | {file = "ruff-0.12.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ca972c80f7ebcfd8af75a0f18b17c42d9f1ef203d163669150453f50ca98ab7b"}, 614 | {file = "ruff-0.12.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8dbbf9f25dfb501f4237ae7501d6364b76a01341c6f1b2cd6764fe449124bb2a"}, 615 | {file = "ruff-0.12.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c47dea6ae39421851685141ba9734767f960113d51e83fd7bb9958d5be8763a"}, 616 | {file = "ruff-0.12.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c5076aa0e61e30f848846f0265c873c249d4b558105b221be1828f9f79903dc5"}, 617 | {file = "ruff-0.12.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a5a4c7830dadd3d8c39b1cc85386e2c1e62344f20766be6f173c22fb5f72f293"}, 618 | {file = "ruff-0.12.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:46699f73c2b5b137b9dc0fc1a190b43e35b008b398c6066ea1350cce6326adcb"}, 619 | {file = "ruff-0.12.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5a655a0a0d396f0f072faafc18ebd59adde8ca85fb848dc1b0d9f024b9c4d3bb"}, 620 | {file = "ruff-0.12.5-py3-none-win32.whl", hash = "sha256:dfeb2627c459b0b78ca2bbdc38dd11cc9a0a88bf91db982058b26ce41714ffa9"}, 621 | {file = "ruff-0.12.5-py3-none-win_amd64.whl", hash = "sha256:ae0d90cf5f49466c954991b9d8b953bd093c32c27608e409ae3564c63c5306a5"}, 622 | {file = "ruff-0.12.5-py3-none-win_arm64.whl", hash = "sha256:48cdbfc633de2c5c37d9f090ba3b352d1576b0015bfc3bc98eaf230275b7e805"}, 623 | {file = "ruff-0.12.5.tar.gz", hash = "sha256:b209db6102b66f13625940b7f8c7d0f18e20039bb7f6101fbdac935c9612057e"}, 624 | ] 625 | 626 | [[package]] 627 | name = "setuptools" 628 | version = "58.5.3" 629 | description = "Easily download, build, install, upgrade, and uninstall Python packages" 630 | optional = false 631 | python-versions = ">=3.6" 632 | groups = ["dev"] 633 | files = [ 634 | {file = "setuptools-58.5.3-py3-none-any.whl", hash = "sha256:a481fbc56b33f5d8f6b33dce41482e64c68b668be44ff42922903b03872590bf"}, 635 | {file = "setuptools-58.5.3.tar.gz", hash = "sha256:dae6b934a965c8a59d6d230d3867ec408bb95e73bd538ff77e71fedf1eaca729"}, 636 | ] 637 | 638 | [package.extras] 639 | docs = ["furo", "jaraco.packaging (>=8.2)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx", "sphinx-inline-tabs", "sphinxcontrib-towncrier"] 640 | testing = ["flake8-2020", "jaraco.envs", "jaraco.path (>=3.2.0)", "mock", "paver", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy ; platform_python_implementation != \"PyPy\"", "pytest-virtualenv (>=1.2.7)", "pytest-xdist", "sphinx", "virtualenv (>=13.0.0)", "wheel"] 641 | 642 | [[package]] 643 | name = "sniffio" 644 | version = "1.3.1" 645 | description = "Sniff out which async library your code is running under" 646 | optional = false 647 | python-versions = ">=3.7" 648 | groups = ["main"] 649 | files = [ 650 | {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, 651 | {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, 652 | ] 653 | 654 | [[package]] 655 | name = "strenum" 656 | version = "0.4.15" 657 | description = "An Enum that inherits from str." 658 | optional = false 659 | python-versions = "*" 660 | groups = ["main"] 661 | files = [ 662 | {file = "StrEnum-0.4.15-py3-none-any.whl", hash = "sha256:a30cda4af7cc6b5bf52c8055bc4bf4b2b6b14a93b574626da33df53cf7740659"}, 663 | {file = "StrEnum-0.4.15.tar.gz", hash = "sha256:878fb5ab705442070e4dd1929bb5e2249511c0bcf2b0eeacf3bcd80875c82eff"}, 664 | ] 665 | 666 | [package.extras] 667 | docs = ["myst-parser[linkify]", "sphinx", "sphinx-rtd-theme"] 668 | release = ["twine"] 669 | test = ["pylint", "pytest", "pytest-black", "pytest-cov", "pytest-pylint"] 670 | 671 | [[package]] 672 | name = "tomli" 673 | version = "2.0.2" 674 | description = "A lil' TOML parser" 675 | optional = false 676 | python-versions = ">=3.8" 677 | groups = ["dev"] 678 | markers = "python_full_version <= \"3.11.0a6\"" 679 | files = [ 680 | {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, 681 | {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, 682 | ] 683 | 684 | [[package]] 685 | name = "typer" 686 | version = "0.4.2" 687 | description = "Typer, build great CLIs. Easy to code. Based on Python type hints." 688 | optional = false 689 | python-versions = ">=3.6" 690 | groups = ["dev"] 691 | files = [ 692 | {file = "typer-0.4.2-py3-none-any.whl", hash = "sha256:023bae00d1baf358a6cc7cea45851639360bb716de687b42b0a4641cd99173f1"}, 693 | {file = "typer-0.4.2.tar.gz", hash = "sha256:b8261c6c0152dd73478b5ba96ba677e5d6948c715c310f7c91079f311f62ec03"}, 694 | ] 695 | 696 | [package.dependencies] 697 | click = ">=7.1.1,<9.0.0" 698 | 699 | [package.extras] 700 | all = ["colorama (>=0.4.3,<0.5.0)", "shellingham (>=1.3.0,<2.0.0)"] 701 | dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "pre-commit (>=2.17.0,<3.0.0)"] 702 | doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)"] 703 | test = ["black (>=22.3.0,<23.0.0)", "coverage (>=5.2,<6.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "pytest (>=4.4.0,<5.4.0)", "pytest-cov (>=2.10.0,<3.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "pytest-xdist (>=1.32.0,<2.0.0)", "shellingham (>=1.3.0,<2.0.0)"] 704 | 705 | [[package]] 706 | name = "typing-extensions" 707 | version = "4.12.2" 708 | description = "Backported and Experimental Type Hints for Python 3.8+" 709 | optional = false 710 | python-versions = ">=3.8" 711 | groups = ["main", "dev"] 712 | files = [ 713 | {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, 714 | {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, 715 | ] 716 | markers = {main = "python_version < \"3.11\"", dev = "python_version < \"3.10\""} 717 | 718 | [[package]] 719 | name = "unasync" 720 | version = "0.5.0" 721 | description = "The async transformation code." 722 | optional = false 723 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 724 | groups = ["dev"] 725 | files = [ 726 | {file = "unasync-0.5.0-py3-none-any.whl", hash = "sha256:8d4536dae85e87b8751dfcc776f7656fd0baf54bb022a7889440dc1b9dc3becb"}, 727 | {file = "unasync-0.5.0.tar.gz", hash = "sha256:b675d87cf56da68bd065d3b7a67ac71df85591978d84c53083c20d79a7e5096d"}, 728 | ] 729 | 730 | [[package]] 731 | name = "unasync-cli" 732 | version = "0.0.9" 733 | description = "Command line interface for unasync" 734 | optional = false 735 | python-versions = ">=3.6.14,<4.0.0" 736 | groups = ["dev"] 737 | files = [ 738 | {file = "unasync-cli-0.0.9.tar.gz", hash = "sha256:ca9d8c57ebb68911f8f8f68f243c7f6d0bb246ee3fd14743bc51c8317e276554"}, 739 | {file = "unasync_cli-0.0.9-py3-none-any.whl", hash = "sha256:f96c42fb2862efa555ce6d6415a5983ceb162aa0e45be701656d20a955c7c540"}, 740 | ] 741 | 742 | [package.dependencies] 743 | setuptools = ">=58.2.0,<59.0.0" 744 | typer = ">=0.4.0,<0.5.0" 745 | unasync = ">=0.5.0,<0.6.0" 746 | 747 | [[package]] 748 | name = "virtualenv" 749 | version = "20.26.6" 750 | description = "Virtual Python Environment builder" 751 | optional = false 752 | python-versions = ">=3.7" 753 | groups = ["dev"] 754 | files = [ 755 | {file = "virtualenv-20.26.6-py3-none-any.whl", hash = "sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2"}, 756 | {file = "virtualenv-20.26.6.tar.gz", hash = "sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48"}, 757 | ] 758 | 759 | [package.dependencies] 760 | distlib = ">=0.3.7,<1" 761 | filelock = ">=3.12.2,<4" 762 | platformdirs = ">=3.9.1,<5" 763 | 764 | [package.extras] 765 | docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] 766 | test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] 767 | 768 | [metadata] 769 | lock-version = "2.1" 770 | python-versions = "^3.9" 771 | content-hash = "b57200d09ea54a88f14de62379b6e3e0b79620096816581225837066eb37fb24" 772 | --------------------------------------------------------------------------------