buildfunctions 0.1.0__py3-none-any.whl → 0.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1,139 @@
1
- from .api import Buildfunctions
1
+ """Buildfunctions SDK - Python SDK for the serverless platform for AI agents.
2
+
3
+ Example:
4
+ import asyncio
5
+ from buildfunctions import Buildfunctions, CPUSandbox, GPUSandbox, GPUFunction
6
+
7
+ async def main():
8
+ # Initialize the client (authenticates with the API)
9
+ client = await Buildfunctions({
10
+ "apiToken": os.environ["BUILDFUNCTIONS_API_TOKEN"]
11
+ })
12
+
13
+ # Access authenticated user info (supports both dot and bracket notation)
14
+ print(client.user.username)
15
+ print(client.authenticatedAt)
16
+
17
+ # Create a CPU sandbox
18
+ sandbox = await CPUSandbox.create({
19
+ "name": "my-sandbox",
20
+ "language": "python",
21
+ "memory": "512MB",
22
+ })
23
+
24
+ # Run code
25
+ result = await sandbox.run()
26
+ print(result.response)
27
+
28
+ # Clean up
29
+ await sandbox.delete()
30
+
31
+ asyncio.run(main())
32
+ """
33
+
34
+ # Client exports - match TypeScript SDK naming exactly
35
+ from buildfunctions.client import (
36
+ Buildfunctions,
37
+ buildfunctions,
38
+ createClient,
39
+ create_client,
40
+ init,
41
+ )
42
+
43
+ # Function builders - match TypeScript SDK naming exactly
44
+ from buildfunctions.cpu_function import CPUFunction, create_cpu_function
45
+ from buildfunctions.gpu_function import GPUFunction, create_gpu_function
46
+
47
+ # Sandbox factories - match TypeScript SDK naming exactly
48
+ from buildfunctions.cpu_sandbox import CPUSandbox, create_cpu_sandbox
49
+ from buildfunctions.gpu_sandbox import GPUSandbox, create_gpu_sandbox
50
+
51
+ # Errors
52
+ from buildfunctions.errors import (
53
+ AuthenticationError,
54
+ BuildfunctionsError,
55
+ CapacityError,
56
+ NotFoundError,
57
+ ValidationError,
58
+ )
59
+
60
+ # Types
61
+ from buildfunctions.types import (
62
+ AuthenticatedUser,
63
+ AuthResponse,
64
+ BuildfunctionsConfig,
65
+ CPUFunctionOptions,
66
+ CPUSandboxConfig,
67
+ CPUSandboxInstance,
68
+ CreateFunctionOptions,
69
+ DeployedFunction,
70
+ ErrorCode,
71
+ FileMetadata,
72
+ FindUniqueOptions,
73
+ Framework,
74
+ FunctionConfig,
75
+ GPUFunctionOptions,
76
+ GPUSandboxConfig,
77
+ GPUSandboxInstance,
78
+ GPUType,
79
+ Language,
80
+ ListOptions,
81
+ Memory,
82
+ RunResult,
83
+ Runtime,
84
+ SandboxInstance,
85
+ UploadOptions,
86
+ )
87
+
88
+ __all__ = [
89
+ # Client (PascalCase - matches TypeScript)
90
+ "Buildfunctions",
91
+ "createClient",
92
+ "init",
93
+ # Client (snake_case aliases)
94
+ "buildfunctions",
95
+ "create_client",
96
+ # Function builders (PascalCase - matches TypeScript)
97
+ "CPUFunction",
98
+ "GPUFunction",
99
+ # Function builders (snake_case aliases)
100
+ "create_cpu_function",
101
+ "create_gpu_function",
102
+ # Sandbox factories (PascalCase - matches TypeScript)
103
+ "CPUSandbox",
104
+ "GPUSandbox",
105
+ # Sandbox factories (snake_case aliases)
106
+ "create_cpu_sandbox",
107
+ "create_gpu_sandbox",
108
+ # Errors
109
+ "BuildfunctionsError",
110
+ "AuthenticationError",
111
+ "NotFoundError",
112
+ "ValidationError",
113
+ "CapacityError",
114
+ # Types
115
+ "BuildfunctionsConfig",
116
+ "AuthenticatedUser",
117
+ "AuthResponse",
118
+ "Language",
119
+ "Runtime",
120
+ "GPUType",
121
+ "Framework",
122
+ "Memory",
123
+ "FunctionConfig",
124
+ "CPUFunctionOptions",
125
+ "GPUFunctionOptions",
126
+ "CreateFunctionOptions",
127
+ "DeployedFunction",
128
+ "CPUSandboxConfig",
129
+ "GPUSandboxConfig",
130
+ "RunResult",
131
+ "UploadOptions",
132
+ "SandboxInstance",
133
+ "CPUSandboxInstance",
134
+ "GPUSandboxInstance",
135
+ "FindUniqueOptions",
136
+ "ListOptions",
137
+ "ErrorCode",
138
+ "FileMetadata",
139
+ ]
@@ -0,0 +1,282 @@
1
+ """Buildfunctions SDK Client."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from datetime import datetime, timezone
7
+ from typing import Any
8
+
9
+ from buildfunctions.cpu_function import set_api_token
10
+ from buildfunctions.cpu_sandbox import set_cpu_sandbox_api_token
11
+ from buildfunctions.dotdict import DotDict
12
+ from buildfunctions.errors import NotFoundError
13
+ from buildfunctions.framework import detect_framework
14
+ from buildfunctions.gpu_function import GPUFunction, set_gpu_api_token
15
+ from buildfunctions.gpu_sandbox import set_gpu_sandbox_api_token
16
+ from buildfunctions.http_client import create_http_client
17
+ from buildfunctions.memory import parse_memory
18
+ from buildfunctions.resolve_code import get_caller_file, resolve_code
19
+ from buildfunctions.types import (
20
+ AuthResponse,
21
+ BuildfunctionsConfig,
22
+ CreateFunctionOptions,
23
+ DeployedFunction,
24
+ FindUniqueOptions,
25
+ ListOptions,
26
+ )
27
+
28
+ DEFAULT_BASE_URL = "https://www.buildfunctions.com"
29
+ DEFAULT_GPU_BUILD_URL = "https://prod-gpu-build.buildfunctions.link"
30
+
31
+
32
+ def _format_requirements(requirements: str | list[str] | None) -> str:
33
+ if not requirements:
34
+ return ""
35
+ if isinstance(requirements, list):
36
+ return "\n".join(requirements)
37
+ return requirements
38
+
39
+
40
+ def _get_default_runtime(language: str) -> str:
41
+ if language == "javascript":
42
+ raise ValueError('JavaScript requires explicit runtime: "nodejs" or "deno"')
43
+ return language
44
+
45
+
46
+ def _get_file_extension(language: str) -> str:
47
+ match language:
48
+ case "javascript":
49
+ return ".js"
50
+ case "typescript":
51
+ return ".ts"
52
+ case "python":
53
+ return ".py"
54
+ case "go":
55
+ return ".go"
56
+ case "shell":
57
+ return ".sh"
58
+ case _:
59
+ return ".js"
60
+
61
+
62
+ def _create_functions_manager(http: dict[str, Any]) -> DotDict:
63
+ """Create a functions manager with list/findUnique/get/create/delete methods."""
64
+
65
+ def _wrap_function(fn: dict[str, Any]) -> DotDict:
66
+ async def delete_fn() -> None:
67
+ await http["delete"]("/api/sdk/functions/build", {"siteId": fn["id"]})
68
+
69
+ return DotDict({**fn, "delete": delete_fn})
70
+
71
+ async def list_fn(options: ListOptions | None = None) -> list[DotDict]:
72
+ page = (options or {}).get("page", 1)
73
+ response = await http["get"]("/api/sdk/functions", {"page": page})
74
+ return [_wrap_function(fn) for fn in response["stringifiedQueryResults"]]
75
+
76
+ async def find_unique(options: FindUniqueOptions) -> DotDict | None:
77
+ where = options.get("where", options) if isinstance(options, dict) else options
78
+
79
+ if where.get("id"):
80
+ try:
81
+ fn = await http["get"]("/api/sdk/functions/build", {"siteId": where["id"]})
82
+ return _wrap_function(fn)
83
+ except NotFoundError:
84
+ return None
85
+
86
+ if where.get("name"):
87
+ functions = await list_fn()
88
+ for fn in functions:
89
+ if fn.get("name") == where["name"]:
90
+ return fn
91
+ return None
92
+
93
+ return None
94
+
95
+ async def get(site_id: str) -> DotDict:
96
+ fn = await http["get"]("/api/sdk/functions/build", {"siteId": site_id})
97
+ return _wrap_function(fn)
98
+
99
+ async def create(options: CreateFunctionOptions) -> DotDict:
100
+ # Get the caller's file location to resolve relative paths correctly
101
+ caller_file = get_caller_file()
102
+ caller_dir = caller_file.parent if caller_file else None
103
+
104
+ # Resolve code (inline string or file path) relative to the caller's location
105
+ resolved_code = await resolve_code(options["code"], caller_dir)
106
+
107
+ file_ext = _get_file_extension(options["language"])
108
+ name = options["name"].lower()
109
+ is_gpu = options.get("processor_type") == "GPU" or bool(options.get("gpu"))
110
+ runtime = options.get("runtime") or _get_default_runtime(options["language"])
111
+
112
+ if is_gpu:
113
+ requirements = _format_requirements(options.get("requirements"))
114
+ env_variables_list = options.get("env_variables", [])
115
+ env_dict = {v["key"]: v["value"] for v in env_variables_list} if env_variables_list else {}
116
+
117
+ deployed = await GPUFunction.create({
118
+ "name": options["name"],
119
+ "code": resolved_code,
120
+ "language": options["language"],
121
+ "runtime": runtime,
122
+ "gpu": options.get("gpu", "T4"),
123
+ "vcpus": options.get("vcpus"),
124
+ "config": {
125
+ "memory": parse_memory(options["memory"]) if options.get("memory") else 1024,
126
+ "timeout": options.get("timeout", 60),
127
+ },
128
+ "dependencies": requirements,
129
+ "env_variables": env_dict if env_dict else {},
130
+ "cron_schedule": options.get("cron_schedule", ""),
131
+ "framework": options.get("framework") or detect_framework(requirements),
132
+ "model_name": options.get("model_name", ""),
133
+ "model_path": options.get("model_path", ""),
134
+ })
135
+
136
+ if not deployed:
137
+ raise RuntimeError("GPU Function deployment failed")
138
+ return DotDict(deployed) if not isinstance(deployed, DotDict) else deployed
139
+
140
+ # CPU build
141
+ body = {
142
+ "name": name,
143
+ "fileExt": file_ext,
144
+ "sourceWith": resolved_code,
145
+ "sourceWithout": resolved_code,
146
+ "language": options["language"],
147
+ "runtime": runtime,
148
+ "memoryAllocated": parse_memory(options["memory"]) if options.get("memory") else 128,
149
+ "timeout": options.get("timeout", 10),
150
+ "envVariables": json.dumps(options.get("env_variables", [])),
151
+ "requirements": _format_requirements(options.get("requirements")),
152
+ "cronExpression": options.get("cron_schedule", ""),
153
+ "processorType": "CPU",
154
+ "selectedFramework": options.get("framework") or detect_framework(
155
+ _format_requirements(options.get("requirements"))
156
+ ),
157
+ "subdomain": name,
158
+ "totalVariables": len(options.get("env_variables", [])),
159
+ "functionCount": 0,
160
+ }
161
+
162
+ response = await http["post"]("/api/sdk/functions/build", body)
163
+ now = datetime.now(timezone.utc).isoformat()
164
+
165
+ return _wrap_function({
166
+ "id": response["siteId"],
167
+ "name": name,
168
+ "subdomain": name,
169
+ "endpoint": response["endpoint"],
170
+ "lambdaUrl": response.get("sslCertificateEndpoint", ""),
171
+ "language": options["language"],
172
+ "runtime": runtime,
173
+ "lambdaMemoryAllocated": parse_memory(options["memory"]) if options.get("memory") else 128,
174
+ "timeoutSeconds": options.get("timeout", 10),
175
+ "isGPUF": False,
176
+ "framework": options.get("framework", ""),
177
+ "createdAt": now,
178
+ "updatedAt": now,
179
+ })
180
+
181
+ async def delete_fn(site_id: str) -> None:
182
+ await http["delete"]("/api/sdk/functions/build", {"siteId": site_id})
183
+
184
+ return DotDict({
185
+ "list": list_fn,
186
+ "findUnique": find_unique,
187
+ "find_unique": find_unique,
188
+ "get": get,
189
+ "create": create,
190
+ "delete": delete_fn,
191
+ })
192
+
193
+
194
+ async def Buildfunctions(config: BuildfunctionsConfig | None = None) -> DotDict:
195
+ """Create a Buildfunctions SDK client.
196
+
197
+ Authenticates with the API and returns a client with:
198
+ - functions: Functions manager (list, findUnique, get, create, delete)
199
+ - user: Authenticated user info
200
+ - sessionExpiresAt: Session expiration timestamp
201
+ - authenticatedAt: Authentication timestamp
202
+ - getHttpClient: Returns the underlying HTTP client
203
+
204
+ Supports both dot notation and bracket notation:
205
+ client.user.username OR client["user"]["username"]
206
+ """
207
+ if config is None:
208
+ import os
209
+
210
+ from dotenv import load_dotenv
211
+
212
+ load_dotenv()
213
+ api_token = os.environ.get("BUILDFUNCTIONS_API_TOKEN", "")
214
+ config = BuildfunctionsConfig(api_token=api_token)
215
+
216
+ api_token = config.get("api_token") or config.get("apiToken", "")
217
+ if not api_token:
218
+ raise ValueError("API token is required")
219
+
220
+ base_url = config.get("base_url") or config.get("baseUrl", DEFAULT_BASE_URL)
221
+ gpu_build_url = config.get("gpu_build_url") or config.get("gpuBuildUrl", DEFAULT_GPU_BUILD_URL)
222
+
223
+ # Don't wrap http in DotDict - it has methods like 'get' that conflict with dict builtins
224
+ http = create_http_client(base_url=base_url, api_token=api_token)
225
+
226
+ auth_response: AuthResponse = await http["post"]("/api/sdk/auth")
227
+
228
+ if not auth_response.get("authenticated"):
229
+ raise RuntimeError("Authentication failed")
230
+
231
+ http["set_token"](auth_response["sessionToken"])
232
+
233
+ user_id = auth_response["user"].get("id", "")
234
+ username = auth_response["user"].get("username") or None
235
+ compute_tier = auth_response["user"].get("compute_tier") or auth_response["user"].get("computeTier") or None
236
+
237
+ set_cpu_sandbox_api_token(auth_response["sessionToken"], base_url)
238
+ set_gpu_sandbox_api_token(auth_response["sessionToken"], gpu_build_url, user_id, username, compute_tier, base_url)
239
+ set_gpu_api_token(auth_response["sessionToken"], gpu_build_url, base_url, user_id, username, compute_tier)
240
+ set_api_token(auth_response["sessionToken"], base_url)
241
+
242
+ functions = _create_functions_manager(http)
243
+
244
+ return DotDict({
245
+ "functions": functions,
246
+ "user": DotDict(auth_response["user"]),
247
+ # Support both naming conventions
248
+ "sessionExpiresAt": auth_response.get("expiresAt"),
249
+ "session_expires_at": auth_response.get("expiresAt"),
250
+ "authenticatedAt": auth_response.get("authenticatedAt"),
251
+ "authenticated_at": auth_response.get("authenticatedAt"),
252
+ "getHttpClient": lambda: http,
253
+ "get_http_client": lambda: http,
254
+ })
255
+
256
+
257
+ async def createClient(config: BuildfunctionsConfig | None = None) -> dict[str, Any] | None:
258
+ """Create a Buildfunctions client, returning None on failure."""
259
+ try:
260
+ return await Buildfunctions(config)
261
+ except Exception:
262
+ return None
263
+
264
+
265
+ # Aliases for snake_case compatibility
266
+ buildfunctions = Buildfunctions
267
+ create_client = createClient
268
+
269
+
270
+ def init(
271
+ api_token: str,
272
+ base_url: str | None = None,
273
+ gpu_build_url: str | None = None,
274
+ user_id: str | None = None,
275
+ username: str | None = None,
276
+ compute_tier: str | None = None,
277
+ ) -> None:
278
+ """Global initialization - sets tokens across all modules."""
279
+ set_api_token(api_token, base_url)
280
+ set_gpu_api_token(api_token, gpu_build_url, base_url, user_id, username, compute_tier)
281
+ set_cpu_sandbox_api_token(api_token, base_url)
282
+ set_gpu_sandbox_api_token(api_token, gpu_build_url, user_id, username, compute_tier, base_url)
@@ -0,0 +1,167 @@
1
+ """CPU Function - Deploy serverless functions to Buildfunctions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from typing import Any
7
+
8
+ import httpx
9
+
10
+ from buildfunctions.dotdict import DotDict
11
+ from buildfunctions.errors import ValidationError
12
+ from buildfunctions.memory import parse_memory
13
+ from buildfunctions.resolve_code import resolve_code
14
+ from buildfunctions.types import CPUFunctionOptions, DeployedFunction
15
+
16
+ DEFAULT_BASE_URL = "https://www.buildfunctions.com"
17
+
18
+ # Module-level state
19
+ _global_api_token: str | None = None
20
+ _global_base_url: str | None = None
21
+
22
+
23
+ def set_api_token(api_token: str, base_url: str | None = None) -> None:
24
+ """Set the API token for function deployment."""
25
+ global _global_api_token, _global_base_url
26
+ _global_api_token = api_token
27
+ _global_base_url = base_url
28
+
29
+
30
+ def _get_file_extension(language: str) -> str:
31
+ extensions: dict[str, str] = {
32
+ "javascript": ".js",
33
+ "typescript": ".ts",
34
+ "python": ".py",
35
+ "go": ".go",
36
+ "shell": ".sh",
37
+ }
38
+ return extensions.get(language, ".js")
39
+
40
+
41
+ def _get_default_runtime(language: str) -> str:
42
+ if language == "javascript":
43
+ raise ValidationError('JavaScript requires explicit runtime: "nodejs" or "deno"')
44
+ return language
45
+
46
+
47
+ def _format_requirements(requirements: str | list[str] | None) -> str:
48
+ if not requirements:
49
+ return ""
50
+ if isinstance(requirements, list):
51
+ return "\n".join(requirements)
52
+ return requirements
53
+
54
+
55
+ def _validate_options(options: CPUFunctionOptions) -> None:
56
+ name = options.get("name")
57
+ if not name or not isinstance(name, str):
58
+ raise ValidationError("Function name is required")
59
+
60
+ import re
61
+
62
+ if not re.match(r"^[a-z0-9-]+$", name.lower()):
63
+ raise ValidationError("Function name can only contain lowercase letters, numbers, and hyphens")
64
+
65
+ code = options.get("code")
66
+ if not code or not isinstance(code, str):
67
+ raise ValidationError("Function code is required")
68
+
69
+ if not options.get("language"):
70
+ raise ValidationError("Language is required")
71
+
72
+
73
+ def _build_request_body(options: CPUFunctionOptions) -> dict[str, Any]:
74
+ name = options["name"]
75
+ language = options["language"]
76
+ code = options["code"]
77
+ config = options.get("config", {})
78
+ env_variables = options.get("env_variables", {})
79
+ dependencies = options.get("dependencies")
80
+ cron_schedule = options.get("cron_schedule")
81
+
82
+ runtime = options.get("runtime") or _get_default_runtime(language)
83
+ file_ext = _get_file_extension(language)
84
+
85
+ env_vars_list = [{"key": k, "value": v} for k, v in env_variables.items()] if env_variables else []
86
+
87
+ return {
88
+ "name": name.lower(),
89
+ "language": language,
90
+ "runtime": runtime,
91
+ "sourceWith": code,
92
+ "fileExt": file_ext,
93
+ "processorType": "CPU only",
94
+ "memoryAllocated": parse_memory(config.get("memory", 1024)) if config.get("memory") else 1024,
95
+ "timeout": config.get("timeout", 10) if config else 10,
96
+ "envVariables": json.dumps(env_vars_list),
97
+ "requirements": _format_requirements(dependencies),
98
+ "cronExpression": cron_schedule or "",
99
+ "totalVariables": len(env_variables) if env_variables else 0,
100
+ }
101
+
102
+
103
+ async def _create_cpu_function(options: CPUFunctionOptions) -> DeployedFunction | None:
104
+ """Internal function to create and deploy a CPU function."""
105
+ if not _global_api_token:
106
+ raise ValidationError("API key not set. Initialize Buildfunctions client first.")
107
+
108
+ api_token = _global_api_token
109
+ base_url = _global_base_url or DEFAULT_BASE_URL
110
+
111
+ resolved_code = await resolve_code(options["code"])
112
+ resolved_options = {**options, "code": resolved_code}
113
+ _validate_options(resolved_options)
114
+ body = _build_request_body(resolved_options)
115
+
116
+ async with httpx.AsyncClient(timeout=httpx.Timeout(600.0)) as client:
117
+ response = await client.post(
118
+ f"{base_url}/api/sdk/functions/build",
119
+ headers={
120
+ "Content-Type": "application/json",
121
+ "Authorization": f"Bearer {api_token}",
122
+ },
123
+ json=body,
124
+ )
125
+
126
+ if not response.is_success:
127
+ return None
128
+
129
+ data = response.json()
130
+ name = options["name"].lower()
131
+ runtime = options.get("runtime") or _get_default_runtime(options["language"])
132
+
133
+ async def delete_fn() -> None:
134
+ async with httpx.AsyncClient(timeout=httpx.Timeout(30.0)) as c:
135
+ await c.request(
136
+ "DELETE",
137
+ f"{base_url}/api/sdk/functions/build",
138
+ headers={
139
+ "Content-Type": "application/json",
140
+ "Authorization": f"Bearer {api_token}",
141
+ },
142
+ json={"siteId": data.get("siteId")},
143
+ )
144
+
145
+ return DotDict({
146
+ "id": data.get("siteId", ""),
147
+ "name": name,
148
+ "subdomain": name,
149
+ "endpoint": data.get("endpoint", ""),
150
+ "lambdaUrl": data.get("sslCertificateEndpoint", ""),
151
+ "language": options["language"],
152
+ "runtime": runtime,
153
+ "isGPUF": False,
154
+ "delete": delete_fn,
155
+ })
156
+
157
+
158
+ class CPUFunction:
159
+ """CPU Function factory - matches TypeScript SDK pattern."""
160
+
161
+ @staticmethod
162
+ async def create(options: CPUFunctionOptions) -> DeployedFunction | None:
163
+ """Create and deploy a new CPU function."""
164
+ return await _create_cpu_function(options)
165
+
166
+
167
+ create_cpu_function = _create_cpu_function