Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
2a4b538
# Feature (2970): Update python client to support setup command (#22)
sudiptatj Oct 31, 2024
8723978
improve polyapi-python setup (#24)
eupharis Nov 4, 2024
7ece4d0
# Feature (3007): Update python -m polyapi function add --logs option…
sudiptatj Nov 4, 2024
e292e4e
Project Glide + Refactor main command line args parsing (#26)
aarongoin Nov 14, 2024
cd89d8a
fix for poly cache directory path construction
aarongoin Nov 18, 2024
a8c2f52
one more adjustment to the deployables cache directory so there can't…
aarongoin Nov 18, 2024
4a0277a
this better?
aarongoin Nov 18, 2024
571e85a
verbose logging on upload code to see what's failing in CI/CD
aarongoin Nov 18, 2024
dbbbe61
bumpity
aarongoin Nov 18, 2024
c03268e
whoops
aarongoin Nov 18, 2024
64c2e37
so close
aarongoin Nov 18, 2024
2f71748
better?
aarongoin Nov 18, 2024
57a2d40
okay this should be the fix
aarongoin Nov 18, 2024
0e78bd3
is it this?
aarongoin Nov 18, 2024
30b2a28
maybe
aarongoin Nov 18, 2024
156d3df
oh for the love of pete
aarongoin Nov 18, 2024
20d8982
whatever. might be a pypi issue
aarongoin Nov 19, 2024
37b421b
removing verbose logging
aarongoin Nov 19, 2024
4cc8a16
fixing bugs in sync command to use correct api urls
aarongoin Nov 19, 2024
aa0be64
update logging
aarongoin Nov 19, 2024
db8aec6
lint
aarongoin Nov 19, 2024
32a65d1
improved auth
aarongoin Nov 19, 2024
14cf1ce
last fix for function sync
aarongoin Nov 19, 2024
9d4824a
fix bug when comment arguments don't align with the function
aarongoin Nov 19, 2024
ffe19f0
try forcing the poly directory to exist
aarongoin Nov 20, 2024
0d32c4a
test logging
aarongoin Nov 20, 2024
8bbb2c7
remove debug logging
aarongoin Nov 20, 2024
2808c97
fixing project glide deployable types and bumping the version
aarongoin Nov 22, 2024
123f0b7
fixing missing arguments in python client function upload
aarongoin Nov 22, 2024
7dc7afe
fixing return type for trained functions
aarongoin Nov 22, 2024
e117f1e
fix bug preventing use of poly sync command locally
aarongoin Nov 22, 2024
d4a656f
next version of client!
eupharis Nov 26, 2024
7441019
EN #3183 allow null logs flag for python client (#28)
eneumann Dec 5, 2024
57e7369
let the typing_extensions versions increase to support latest openai …
eupharis Dec 6, 2024
03fd34c
update dependency in one more place
eupharis Dec 6, 2024
28a1c39
Some bug fixes for python client (#29)
aarongoin Dec 14, 2024
027ceb5
Merge branch 'main' into develop
eupharis Dec 23, 2024
4ff9ba9
0.3.2
eupharis Dec 23, 2024
e895ae9
add poly schemas support (#31)
eupharis Mar 24, 2025
d189377
update to v4
eupharis Mar 24, 2025
a140802
v4 everywhere
eupharis Mar 24, 2025
96f63c3
bump version
eupharis Mar 24, 2025
15eab87
add new version
eupharis Mar 24, 2025
3ad2607
remove warning, just go with any type for now
eupharis Mar 26, 2025
1479d39
better generate printed messages, fix generate bug after function add
eupharis Mar 26, 2025
7db1cbe
Update python version (#32)
dchiniquy Mar 27, 2025
a3f09cd
Update python image (#33)
dchiniquy Mar 27, 2025
19fafde
Rollback version
nahuel-polyapi Mar 27, 2025
fccbf1e
onward (#34)
eupharis Apr 8, 2025
1e947d6
next (#35)
eupharis Apr 8, 2025
11108e0
improve intellisense detection of schemas
eupharis Apr 11, 2025
ef7e5e9
release 0.3.3.dev8, fix misleading generate after setup
eupharis Apr 14, 2025
71e2332
0.3.3.dev9 - add support for optional arguments (#36)
eupharis Apr 14, 2025
2b4a2d0
next
eupharis Apr 15, 2025
2169e15
release 0.3.3.dev10
eupharis Apr 15, 2025
c2db3e9
EN #3943 update to support SFX serverSideAsync True by setting correc…
eneumann May 2, 2025
2fc69ea
deploying version 0.3.3 for R22
nahuel-polyapi May 6, 2025
1e683f2
Merge branch 'main' into develop
eupharis May 6, 2025
a4a4872
upgrade version
FedeMarchiniHotovo May 6, 2025
e0e74b7
Merge branch 'main' into develop
FedeMarchiniHotovo May 6, 2025
a6ba2f3
4084 - revert strippping none values from function arguments during e…
akinboboye May 7, 2025
9c1a2a1
P2) Update clients and specs endpoint so when generating with no-type…
RichardDzurus May 7, 2025
d20794b
4010 generate contexts (#43)
akinboboye May 8, 2025
d02bae3
make contexts truly optional
eupharis May 12, 2025
bf238b2
P3) (Optoro) Allow variable to be secret in the UI, but gettable in f…
RichardDzurus May 13, 2025
78d4b15
add generate contexts (#45)
akinboboye May 15, 2025
ebf6e6b
adds mtls and direct execute options (#44)
RichardDzurus May 22, 2025
938dfdd
polyCustom - prevent rewrites of executionId (#46)
RichardDzurus May 26, 2025
db5381b
4292 (#47)
akinboboye May 27, 2025
93cff00
create mock schemas to fit everything when using no types flag (#48)
RichardDzurus May 28, 2025
050853e
EN #4348 flatten new-lines in arg descriptions (#50)
eneumann May 28, 2025
ee50a54
EN #4360 fix return types for TS funcs (#49)
eneumann May 28, 2025
bd33593
adding ability for python client server and client functions to add c…
aarongoin May 28, 2025
bc8e096
fix type error!
aarongoin May 28, 2025
569ec60
try simple upgrade
eupharis May 30, 2025
f60ee7e
Merge branch 'main' into R23
FedeMarchiniHotovo Jun 3, 2025
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
6 changes: 2 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
.env
.env*
.venv/
.venv/*
*env
*venv
.DS_Store

# Pip
Expand Down
92 changes: 85 additions & 7 deletions polyapi/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import os
import sys
import copy
import truststore
from typing import Dict, Any
from typing import Any, Dict, Optional, overload, Literal
from typing_extensions import TypedDict
truststore.inject_into_ssl()
from .cli import CLI_COMMANDS

Expand All @@ -15,9 +17,85 @@
sys.exit(1)


polyCustom: Dict[str, Any] = {
"executionId": None,
"executionApiKey": None,
"responseStatusCode": 200,
"responseContentType": None,
}
class PolyCustomDict(TypedDict, total=False):
"""Type definition for polyCustom dictionary."""
executionId: Optional[str] # Read-only
executionApiKey: Optional[str]
responseStatusCode: int
responseContentType: Optional[str]
responseHeaders: Dict[str, str]


class _PolyCustom:
def __init__(self):
self._internal_store = {
"executionId": None,
"executionApiKey": None,
"responseStatusCode": 200,
"responseContentType": None,
"responseHeaders": {},
}
self._execution_id_locked = False

def set_once(self, key: str, value: Any) -> None:
if key == "executionId" and self._execution_id_locked:
# Silently ignore attempts to overwrite locked executionId
return
self._internal_store[key] = value
if key == "executionId":
# Lock executionId after setting it
self.lock_execution_id()

def get(self, key: str, default: Any = None) -> Any:
return self._internal_store.get(key, default)

def lock_execution_id(self) -> None:
self._execution_id_locked = True

def unlock_execution_id(self) -> None:
self._execution_id_locked = False

@overload
def __getitem__(self, key: Literal["executionId"]) -> Optional[str]: ...

@overload
def __getitem__(self, key: Literal["executionApiKey"]) -> Optional[str]: ...

@overload
def __getitem__(self, key: Literal["responseStatusCode"]) -> int: ...

@overload
def __getitem__(self, key: Literal["responseContentType"]) -> Optional[str]: ...

@overload
def __getitem__(self, key: Literal["responseHeaders"]) -> Dict[str, str]: ...

def __getitem__(self, key: str) -> Any:
return self.get(key)

@overload
def __setitem__(self, key: Literal["executionApiKey"], value: Optional[str]) -> None: ...

@overload
def __setitem__(self, key: Literal["responseStatusCode"], value: int) -> None: ...

@overload
def __setitem__(self, key: Literal["responseContentType"], value: Optional[str]) -> None: ...

@overload
def __setitem__(self, key: Literal["responseHeaders"], value: Dict[str, str]) -> None: ...

def __setitem__(self, key: str, value: Any) -> None:
self.set_once(key, value)

def __repr__(self) -> str:
return f"PolyCustom({self._internal_store})"

def copy(self) -> '_PolyCustom':
new = _PolyCustom()
new._internal_store = copy.deepcopy(self._internal_store)
new._execution_id_locked = self._execution_id_locked
return new


polyCustom: PolyCustomDict = _PolyCustom()
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
11 changes: 9 additions & 2 deletions polyapi/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,13 @@ def setup(args):
###########################################################################
# Generate command
generate_parser = subparsers.add_parser("generate", help="Generates Poly library")
generate_parser.add_argument("--no-types", action="store_true", help="Generate SDK without type definitions")
generate_parser.add_argument("--contexts", type=str, required=False, help="Contexts to generate")

def generate_command(args):
initialize_config()
generate()
contexts = args.contexts.split(",") if args.contexts else None
generate(contexts=contexts, no_types=args.no_types)

generate_parser.set_defaults(command=generate_command)

Expand All @@ -68,6 +71,7 @@ def generate_command(args):
fn_add_parser.add_argument("--logs", choices=["enabled", "disabled"], default=None, help="Enable or disable logs for the function.")
fn_add_parser.add_argument("--execution-api-key", required=False, default="", help="API key for execution (for server functions only).")
fn_add_parser.add_argument("--disable-ai", "--skip-generate", action="store_true", help="Pass --disable-ai skip AI generation of missing descriptions")
fn_add_parser.add_argument("--generate-contexts", type=str, help="Server function only – only include certain contexts to speed up function execution")

def add_function(args):
initialize_config()
Expand All @@ -79,6 +83,8 @@ def add_function(args):
err = "You must specify `--server` or `--client`."
elif logs_enabled and not args.server:
err = "Option `logs` is only for server functions (--server)."
elif args.generate_contexts and not args.server:
err = "Option `generate-contexts` is only for server functions (--server)."

if err:
print_red("ERROR")
Expand All @@ -94,7 +100,8 @@ def add_function(args):
server=args.server,
logs_enabled=logs_enabled,
generate=not args.disable_ai,
execution_api_key=args.execution_api_key
execution_api_key=args.execution_api_key,
generate_contexts=args.generate_contexts
)

fn_add_parser.set_defaults(command=add_function)
Expand Down
35 changes: 31 additions & 4 deletions polyapi/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@
import configparser
from typing import Tuple

from polyapi.utils import is_valid_polyapi_url, is_valid_uuid, print_green, print_yellow
from polyapi.utils import is_valid_polyapi_url, print_green, print_yellow

# 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 @@ -78,8 +89,6 @@ def initialize_config(force=False):
errors = []
if not is_valid_polyapi_url(url):
errors.append(f"{url} is not a valid Poly API Base URL")
if not is_valid_uuid(key):
errors.append(f"{key} is not a valid Poly App Key or User Key")
if errors:
print_yellow("\n".join(errors))
sys.exit(1)
Expand All @@ -106,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)
60 changes: 51 additions & 9 deletions polyapi/execute.py
Original file line number Diff line number Diff line change
@@ -1,26 +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
"""
data_without_None = data
if isinstance(data, Dict):
data_without_None = {k: v for k, v in data.items() if v is not None}

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_without_None, 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/function_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def function_add_or_update(
client: bool,
server: bool,
logs_enabled: Optional[bool],
generate_contexts: Optional[str],
generate: bool = True,
execution_api_key: str = ""
):
Expand Down Expand Up @@ -59,6 +60,9 @@ def function_add_or_update(
"logsEnabled": logs_enabled,
}

if generate_contexts:
data["generateContexts"] = generate_contexts.split(",")

if server and parsed["dependencies"]:
print_yellow(
"\nPlease note that deploying your functions will take a few minutes because it makes use of libraries other than polyapi."
Expand All @@ -82,7 +86,7 @@ def function_add_or_update(

headers = get_auth_headers(api_key)
resp = requests.post(url, headers=headers, json=data)
if resp.status_code == 201:
if resp.status_code in [200, 201]:
print_green("DEPLOYED")
function_id = resp.json()["id"]
print(f"Function ID: {function_id}")
Expand Down
Loading