Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions polyapi/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,16 @@ def {function_name}(

Function ID: {function_id}
\"""
resp = execute("{function_type}", "{function_id}", {data})
return {api_response_type}(resp.json()) # type: ignore
if get_direct_execute_config():
resp = direct_execute("{function_type}", "{function_id}", {data})
return {api_response_type}({{
"status": resp.status_code,
"headers": dict(resp.headers),
"data": resp.json()
}}) # type: ignore
else:
resp = execute("{function_type}", "{function_id}", {data})
return {api_response_type}(resp.json()) # type: ignore


"""
Expand Down
31 changes: 30 additions & 1 deletion polyapi/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
# cached values
API_KEY = None
API_URL = None
API_FUNCTION_DIRECT_EXECUTE = None
MTLS_CERT_PATH = None
MTLS_KEY_PATH = None
MTLS_CA_PATH = None


def get_config_file_path() -> str:
Expand Down Expand Up @@ -45,6 +49,13 @@ def get_api_key_and_url() -> Tuple[str | None, str | None]:
API_KEY = key
API_URL = url

# Read and cache MTLS and direct execute settings
global API_FUNCTION_DIRECT_EXECUTE, MTLS_CERT_PATH, MTLS_KEY_PATH, MTLS_CA_PATH
API_FUNCTION_DIRECT_EXECUTE = config.get("polyapi", "api_function_direct_execute", fallback="false").lower() == "true"
MTLS_CERT_PATH = config.get("polyapi", "mtls_cert_path", fallback=None)
MTLS_KEY_PATH = config.get("polyapi", "mtls_key_path", fallback=None)
MTLS_CA_PATH = config.get("polyapi", "mtls_ca_path", fallback=None)

return key, url


Expand Down Expand Up @@ -104,4 +115,22 @@ def clear_config():

path = get_config_file_path()
if os.path.exists(path):
os.remove(path)
os.remove(path)


def get_mtls_config() -> Tuple[bool, str | None, str | None, str | None]:
"""Return MTLS configuration settings"""
global MTLS_CERT_PATH, MTLS_KEY_PATH, MTLS_CA_PATH
if MTLS_CERT_PATH is None or MTLS_KEY_PATH is None or MTLS_CA_PATH is None:
# Force a config read if values aren't cached
get_api_key_and_url()
return bool(MTLS_CERT_PATH and MTLS_KEY_PATH and MTLS_CA_PATH), MTLS_CERT_PATH, MTLS_KEY_PATH, MTLS_CA_PATH


def get_direct_execute_config() -> bool:
"""Return whether direct execute is enabled"""
global API_FUNCTION_DIRECT_EXECUTE
if API_FUNCTION_DIRECT_EXECUTE is None:
# Force a config read if value isn't cached
get_api_key_and_url()
return bool(API_FUNCTION_DIRECT_EXECUTE)
56 changes: 51 additions & 5 deletions polyapi/execute.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,68 @@
from typing import Dict
from typing import Dict, Optional
import requests
from requests import Response
from polyapi.config import get_api_key_and_url
from polyapi.config import get_api_key_and_url, get_mtls_config
from polyapi.exceptions import PolyApiException

def direct_execute(function_type, function_id, data) -> Response:
""" execute a specific function id/type
"""
api_key, api_url = get_api_key_and_url()
headers = {"Authorization": f"Bearer {api_key}"}
url = f"{api_url}/functions/{function_type}/{function_id}/direct-execute"

endpoint_info = requests.post(url, json=data, headers=headers)
if endpoint_info.status_code < 200 or endpoint_info.status_code >= 300:
raise PolyApiException(f"{endpoint_info.status_code}: {endpoint_info.content.decode('utf-8', errors='ignore')}")

endpoint_info_data = endpoint_info.json()
request_params = endpoint_info_data.copy()
request_params.pop("url", None)

if "maxRedirects" in request_params:
request_params["allow_redirects"] = request_params.pop("maxRedirects") > 0

has_mtls, cert_path, key_path, ca_path = get_mtls_config()

if has_mtls:
resp = requests.request(
url=endpoint_info_data["url"],
cert=(cert_path, key_path),
verify=ca_path,
**request_params
)
else:
resp = requests.request(
url=endpoint_info_data["url"],
verify=False,
**request_params
)

if resp.status_code < 200 or resp.status_code >= 300:
error_content = resp.content.decode("utf-8", errors="ignore")
raise PolyApiException(f"{resp.status_code}: {error_content}")

return resp

def execute(function_type, function_id, data) -> Response:
""" execute a specific function id/type
"""
api_key, api_url = get_api_key_and_url()
headers = {"Authorization": f"Bearer {api_key}"}

url = f"{api_url}/functions/{function_type}/{function_id}/execute"
resp = requests.post(url, json=data, headers=headers)
# print(resp.status_code)
# print(resp.headers["content-type"])

# Make the request
resp = requests.post(
url,
json=data,
headers=headers,
)

if resp.status_code < 200 or resp.status_code >= 300:
error_content = resp.content.decode("utf-8", errors="ignore")
raise PolyApiException(f"{resp.status_code}: {error_content}")

return resp


Expand Down
6 changes: 5 additions & 1 deletion polyapi/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from .server import render_server_function
from .utils import add_import_to_init, get_auth_headers, init_the_init, print_green, to_func_namespace
from .variables import generate_variables
from .config import get_api_key_and_url
from .config import get_api_key_and_url, get_direct_execute_config

SUPPORTED_FUNCTION_TYPES = {
"apiFunction",
Expand Down Expand Up @@ -46,6 +46,10 @@ def get_specs(contexts=Optional[List[str]], no_types: bool = False) -> List:
if contexts:
params["contexts"] = contexts

# Add apiFunctionDirectExecute parameter if direct execute is enabled
if get_direct_execute_config():
params["apiFunctionDirectExecute"] = "true"

resp = requests.get(url, headers=headers, params=params)
if resp.status_code == 200:
return resp.json()
Expand Down
2 changes: 1 addition & 1 deletion polyapi/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

# this string should be in every __init__ file.
# it contains all the imports needed for the function or variable code to run
CODE_IMPORTS = "from typing import List, Dict, Any, Optional, Callable\nfrom typing_extensions import TypedDict, NotRequired\nimport logging\nimport requests\nimport socketio # type: ignore\nfrom polyapi.config import get_api_key_and_url\nfrom polyapi.execute import execute, execute_post, variable_get, variable_update\n\n"
CODE_IMPORTS = "from typing import List, Dict, Any, Optional, Callable\nfrom typing_extensions import TypedDict, NotRequired\nimport logging\nimport requests\nimport socketio # type: ignore\nfrom polyapi.config import get_api_key_and_url, get_direct_execute_config\nfrom polyapi.execute import execute, execute_post, variable_get, variable_update, direct_execute\n\n"


def init_the_init(full_path: str, code_imports="") -> None:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ requires = ["setuptools>=61.2", "wheel"]

[project]
name = "polyapi-python"
version = "0.3.7.dev0"
version = "0.3.7.dev1"
description = "The Python Client for PolyAPI, the IPaaS by Developers for Developers"
authors = [{ name = "Dan Fellin", email = "dan@polyapi.io" }]
dependencies = [
Expand Down