aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/supafunc
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/supafunc')
-rw-r--r--.venv/lib/python3.12/site-packages/supafunc/__init__.py34
-rw-r--r--.venv/lib/python3.12/site-packages/supafunc/_async/__init__.py1
-rw-r--r--.venv/lib/python3.12/site-packages/supafunc/_async/functions_client.py127
-rw-r--r--.venv/lib/python3.12/site-packages/supafunc/_sync/__init__.py1
-rw-r--r--.venv/lib/python3.12/site-packages/supafunc/_sync/functions_client.py127
-rw-r--r--.venv/lib/python3.12/site-packages/supafunc/errors.py44
-rw-r--r--.venv/lib/python3.12/site-packages/supafunc/utils.py69
-rw-r--r--.venv/lib/python3.12/site-packages/supafunc/version.py1
8 files changed, 404 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/supafunc/__init__.py b/.venv/lib/python3.12/site-packages/supafunc/__init__.py
new file mode 100644
index 00000000..7b819695
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/supafunc/__init__.py
@@ -0,0 +1,34 @@
+from __future__ import annotations
+
+from typing import Literal, Optional, Union, overload
+
+from ._async.functions_client import AsyncFunctionsClient
+from ._sync.functions_client import SyncFunctionsClient
+from .utils import FunctionRegion
+
+__all__ = ["create_client"]
+
+
+@overload
+def create_client(
+ url: str, headers: dict[str, str], *, is_async: Literal[True], verify: bool
+) -> AsyncFunctionsClient: ...
+
+
+@overload
+def create_client(
+ url: str, headers: dict[str, str], *, is_async: Literal[False], verify: bool
+) -> SyncFunctionsClient: ...
+
+
+def create_client(
+ url: str,
+ headers: dict[str, str],
+ *,
+ is_async: bool,
+ verify: bool = True,
+) -> Union[AsyncFunctionsClient, SyncFunctionsClient]:
+ if is_async:
+ return AsyncFunctionsClient(url, headers, verify)
+ else:
+ return SyncFunctionsClient(url, headers, verify)
diff --git a/.venv/lib/python3.12/site-packages/supafunc/_async/__init__.py b/.venv/lib/python3.12/site-packages/supafunc/_async/__init__.py
new file mode 100644
index 00000000..9d48db4f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/supafunc/_async/__init__.py
@@ -0,0 +1 @@
+from __future__ import annotations
diff --git a/.venv/lib/python3.12/site-packages/supafunc/_async/functions_client.py b/.venv/lib/python3.12/site-packages/supafunc/_async/functions_client.py
new file mode 100644
index 00000000..42d8d207
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/supafunc/_async/functions_client.py
@@ -0,0 +1,127 @@
+from typing import Any, Dict, Literal, Optional, Union
+from warnings import warn
+
+from httpx import HTTPError, Response
+
+from ..errors import FunctionsHttpError, FunctionsRelayError
+from ..utils import (
+ AsyncClient,
+ FunctionRegion,
+ is_http_url,
+ is_valid_jwt,
+ is_valid_str_arg,
+)
+from ..version import __version__
+
+
+class AsyncFunctionsClient:
+ def __init__(
+ self,
+ url: str,
+ headers: Dict,
+ timeout: int,
+ verify: bool = True,
+ proxy: Optional[str] = None,
+ ):
+ if not is_http_url(url):
+ raise ValueError("url must be a valid HTTP URL string")
+ self.url = url
+ self.headers = {
+ "User-Agent": f"supabase-py/functions-py v{__version__}",
+ **headers,
+ }
+ self._client = AsyncClient(
+ base_url=self.url,
+ headers=self.headers,
+ verify=bool(verify),
+ timeout=int(abs(timeout)),
+ proxy=proxy,
+ follow_redirects=True,
+ http2=True,
+ )
+
+ async def _request(
+ self,
+ method: Literal["GET", "OPTIONS", "HEAD", "POST", "PUT", "PATCH", "DELETE"],
+ url: str,
+ headers: Optional[Dict[str, str]] = None,
+ json: Optional[Dict[Any, Any]] = None,
+ ) -> Response:
+
+ user_data = {"data": json} if isinstance(json, str) else {"json": json}
+ response = await self._client.request(method, url, **user_data, headers=headers)
+
+ try:
+ response.raise_for_status()
+ except HTTPError as exc:
+ raise FunctionsHttpError(
+ response.json().get("error")
+ or f"An error occurred while requesting your edge function at {exc.request.url!r}."
+ ) from exc
+
+ return response
+
+ def set_auth(self, token: str) -> None:
+ """Updates the authorization header
+
+ Parameters
+ ----------
+ token : str
+ the new jwt token sent in the authorization header
+ """
+
+ if not is_valid_jwt(token):
+ raise ValueError("token must be a valid JWT authorization token string.")
+
+ self.headers["Authorization"] = f"Bearer {token}"
+
+ async def invoke(
+ self, function_name: str, invoke_options: Optional[Dict] = None
+ ) -> Union[Dict, bytes]:
+ """Invokes a function
+
+ Parameters
+ ----------
+ function_name : the name of the function to invoke
+ invoke_options : object with the following properties
+ `headers`: object representing the headers to send with the request
+ `body`: the body of the request
+ `responseType`: how the response should be parsed. The default is `json`
+ """
+ if not is_valid_str_arg(function_name):
+ raise ValueError("function_name must a valid string value.")
+ headers = self.headers
+ body = None
+ response_type = "text/plain"
+ if invoke_options is not None:
+ headers.update(invoke_options.get("headers", {}))
+ response_type = invoke_options.get("responseType", "text/plain")
+
+ region = invoke_options.get("region")
+ if region:
+ if not isinstance(region, FunctionRegion):
+ warn(f"Use FunctionRegion({region})")
+ region = FunctionRegion(region)
+
+ if region.value != "any":
+ headers["x-region"] = region.value
+
+ body = invoke_options.get("body")
+ if isinstance(body, str):
+ headers["Content-Type"] = "text/plain"
+ elif isinstance(body, dict):
+ headers["Content-Type"] = "application/json"
+
+ response = await self._request(
+ "POST", f"{self.url}/{function_name}", headers=headers, json=body
+ )
+ is_relay_error = response.headers.get("x-relay-header")
+
+ if is_relay_error and is_relay_error == "true":
+ raise FunctionsRelayError(response.json().get("error"))
+
+ if response_type == "json":
+ data = response.json()
+ else:
+ data = response.content
+ return data
diff --git a/.venv/lib/python3.12/site-packages/supafunc/_sync/__init__.py b/.venv/lib/python3.12/site-packages/supafunc/_sync/__init__.py
new file mode 100644
index 00000000..9d48db4f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/supafunc/_sync/__init__.py
@@ -0,0 +1 @@
+from __future__ import annotations
diff --git a/.venv/lib/python3.12/site-packages/supafunc/_sync/functions_client.py b/.venv/lib/python3.12/site-packages/supafunc/_sync/functions_client.py
new file mode 100644
index 00000000..99780eb7
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/supafunc/_sync/functions_client.py
@@ -0,0 +1,127 @@
+from typing import Any, Dict, Literal, Optional, Union
+from warnings import warn
+
+from httpx import HTTPError, Response
+
+from ..errors import FunctionsHttpError, FunctionsRelayError
+from ..utils import (
+ FunctionRegion,
+ SyncClient,
+ is_http_url,
+ is_valid_jwt,
+ is_valid_str_arg,
+)
+from ..version import __version__
+
+
+class SyncFunctionsClient:
+ def __init__(
+ self,
+ url: str,
+ headers: Dict,
+ timeout: int,
+ verify: bool = True,
+ proxy: Optional[str] = None,
+ ):
+ if not is_http_url(url):
+ raise ValueError("url must be a valid HTTP URL string")
+ self.url = url
+ self.headers = {
+ "User-Agent": f"supabase-py/functions-py v{__version__}",
+ **headers,
+ }
+ self._client = SyncClient(
+ base_url=self.url,
+ headers=self.headers,
+ verify=bool(verify),
+ timeout=int(abs(timeout)),
+ proxy=proxy,
+ follow_redirects=True,
+ http2=True,
+ )
+
+ def _request(
+ self,
+ method: Literal["GET", "OPTIONS", "HEAD", "POST", "PUT", "PATCH", "DELETE"],
+ url: str,
+ headers: Optional[Dict[str, str]] = None,
+ json: Optional[Dict[Any, Any]] = None,
+ ) -> Response:
+
+ user_data = {"data": json} if isinstance(json, str) else {"json": json}
+ response = self._client.request(method, url, **user_data, headers=headers)
+
+ try:
+ response.raise_for_status()
+ except HTTPError as exc:
+ raise FunctionsHttpError(
+ response.json().get("error")
+ or f"An error occurred while requesting your edge function at {exc.request.url!r}."
+ ) from exc
+
+ return response
+
+ def set_auth(self, token: str) -> None:
+ """Updates the authorization header
+
+ Parameters
+ ----------
+ token : str
+ the new jwt token sent in the authorization header
+ """
+
+ if not is_valid_jwt(token):
+ raise ValueError("token must be a valid JWT authorization token string.")
+
+ self.headers["Authorization"] = f"Bearer {token}"
+
+ def invoke(
+ self, function_name: str, invoke_options: Optional[Dict] = None
+ ) -> Union[Dict, bytes]:
+ """Invokes a function
+
+ Parameters
+ ----------
+ function_name : the name of the function to invoke
+ invoke_options : object with the following properties
+ `headers`: object representing the headers to send with the request
+ `body`: the body of the request
+ `responseType`: how the response should be parsed. The default is `json`
+ """
+ if not is_valid_str_arg(function_name):
+ raise ValueError("function_name must a valid string value.")
+ headers = self.headers
+ body = None
+ response_type = "text/plain"
+ if invoke_options is not None:
+ headers.update(invoke_options.get("headers", {}))
+ response_type = invoke_options.get("responseType", "text/plain")
+
+ region = invoke_options.get("region")
+ if region:
+ if not isinstance(region, FunctionRegion):
+ warn(f"Use FunctionRegion({region})")
+ region = FunctionRegion(region)
+
+ if region.value != "any":
+ headers["x-region"] = region.value
+
+ body = invoke_options.get("body")
+ if isinstance(body, str):
+ headers["Content-Type"] = "text/plain"
+ elif isinstance(body, dict):
+ headers["Content-Type"] = "application/json"
+
+ response = self._request(
+ "POST", f"{self.url}/{function_name}", headers=headers, json=body
+ )
+ is_relay_error = response.headers.get("x-relay-header")
+
+ if is_relay_error and is_relay_error == "true":
+ raise FunctionsRelayError(response.json().get("error"))
+
+ if response_type == "json":
+ data = response.json()
+ else:
+ data = response.content
+ return data
diff --git a/.venv/lib/python3.12/site-packages/supafunc/errors.py b/.venv/lib/python3.12/site-packages/supafunc/errors.py
new file mode 100644
index 00000000..ced5f313
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/supafunc/errors.py
@@ -0,0 +1,44 @@
+from __future__ import annotations
+
+from typing import TypedDict
+
+
+class FunctionsApiErrorDict(TypedDict):
+ name: str
+ message: str
+ status: int
+
+
+class FunctionsError(Exception):
+ def __init__(self, message: str, name: str, status: int) -> None:
+ super().__init__(message)
+ self.message = message
+ self.name = name
+ self.status = status
+
+ def to_dict(self) -> FunctionsApiErrorDict:
+ return {
+ "name": self.name,
+ "message": self.message,
+ "status": self.status,
+ }
+
+
+class FunctionsHttpError(FunctionsError):
+ def __init__(self, message: str) -> None:
+ super().__init__(
+ message,
+ "FunctionsHttpError",
+ 400,
+ )
+
+
+class FunctionsRelayError(FunctionsError):
+ """Base exception for relay errors."""
+
+ def __init__(self, message: str) -> None:
+ super().__init__(
+ message,
+ "FunctionsRelayError",
+ 400,
+ )
diff --git a/.venv/lib/python3.12/site-packages/supafunc/utils.py b/.venv/lib/python3.12/site-packages/supafunc/utils.py
new file mode 100644
index 00000000..e7bf6453
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/supafunc/utils.py
@@ -0,0 +1,69 @@
+import re
+import sys
+from urllib.parse import urlparse
+
+from httpx import AsyncClient as AsyncClient # noqa: F401
+from httpx import Client as BaseClient
+
+if sys.version_info >= (3, 11):
+ from enum import StrEnum
+else:
+ from strenum import StrEnum
+
+
+DEFAULT_FUNCTION_CLIENT_TIMEOUT = 5
+BASE64URL_REGEX = r"^([a-z0-9_-]{4})*($|[a-z0-9_-]{3}$|[a-z0-9_-]{2}$)$"
+
+
+class FunctionRegion(StrEnum):
+ Any = "any"
+ ApNortheast1 = "ap-northeast-1"
+ ApNortheast2 = "ap-northeast-2"
+ ApSouth1 = "ap-south-1"
+ ApSoutheast1 = "ap-southeast-1"
+ ApSoutheast2 = "ap-southeast-2"
+ CaCentral1 = "ca-central-1"
+ EuCentral1 = "eu-central-1"
+ EuWest1 = "eu-west-1"
+ EuWest2 = "eu-west-2"
+ EuWest3 = "eu-west-3"
+ SaEast1 = "sa-east-1"
+ UsEast1 = "us-east-1"
+ UsWest1 = "us-west-1"
+ UsWest2 = "us-west-2"
+
+
+class SyncClient(BaseClient):
+ def aclose(self) -> None:
+ self.close()
+
+
+def is_valid_str_arg(target: str) -> bool:
+ return isinstance(target, str) and len(target.strip()) > 0
+
+
+def is_http_url(url: str) -> bool:
+ return urlparse(url).scheme in {"https", "http"}
+
+
+def is_valid_jwt(value: str) -> bool:
+ """Checks if value looks like a JWT, does not do any extra parsing."""
+ if not isinstance(value, str):
+ return False
+
+ # Remove trailing whitespaces if any.
+ value = value.strip()
+
+ # Remove "Bearer " prefix if any.
+ if value.startswith("Bearer "):
+ value = value[7:]
+
+ # Valid JWT must have 2 dots (Header.Paylod.Signature)
+ if value.count(".") != 2:
+ return False
+
+ for part in value.split("."):
+ if not re.search(BASE64URL_REGEX, part, re.IGNORECASE):
+ return False
+
+ return True
diff --git a/.venv/lib/python3.12/site-packages/supafunc/version.py b/.venv/lib/python3.12/site-packages/supafunc/version.py
new file mode 100644
index 00000000..be3f964d
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/supafunc/version.py
@@ -0,0 +1 @@
+__version__ = "0.9.3" # {x-release-please-version}