workpeg-sdk 0.1.0__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.
@@ -0,0 +1,5 @@
1
+ # src/workpeg_sdk/__init__.py
2
+
3
+ __all__ = ["__version__"]
4
+
5
+ __version__ = "0.1.0"
@@ -0,0 +1,86 @@
1
+ # src/workpeg_sdk/create_new.py
2
+ import argparse
3
+ import os
4
+ import shutil
5
+ from pathlib import Path
6
+ import importlib.resources as pkg_resources
7
+
8
+
9
+ class TemplateError(Exception):
10
+ pass
11
+
12
+
13
+ def _copy_tree(src_root: Path, dst_root: Path, *, force: bool) -> None:
14
+ """
15
+ Recursively copy src_root directory contents into dst_root.
16
+ """
17
+ if not src_root.exists():
18
+ raise TemplateError(f"Template root not found: {src_root}")
19
+
20
+ dst_root.mkdir(parents=True, exist_ok=True)
21
+
22
+ for item in src_root.rglob("*"):
23
+ rel = item.relative_to(src_root)
24
+ dst = dst_root / rel
25
+
26
+ if item.is_dir():
27
+ dst.mkdir(parents=True, exist_ok=True)
28
+ continue
29
+
30
+ if dst.exists() and not force:
31
+ raise TemplateError(f"Target file exists: {dst} (use --force)")
32
+
33
+ dst.parent.mkdir(parents=True, exist_ok=True)
34
+ shutil.copy2(item, dst)
35
+
36
+
37
+ def create_new_project(target_dir: str, *, force: bool = False) -> Path:
38
+ """
39
+ Copy the function template from workpeg_sdk/templates/functions/*
40
+ into target_dir.
41
+ """
42
+ target = Path(target_dir).expanduser().resolve()
43
+
44
+ # Get a Traversable pointing at .../workpeg_sdk/templates/functions
45
+ traversable = (
46
+ pkg_resources.files("workpeg_sdk") / "templates" / "functions"
47
+ )
48
+
49
+ # as_file() gives us a real filesystem path even if the package
50
+ # is loaded from a wheel/zip. This completely avoids the old
51
+ # "not available as a filesystem path" issue.
52
+ with pkg_resources.as_file(traversable) as src_path:
53
+ src_root = Path(src_path)
54
+ _copy_tree(src_root, target, force=force)
55
+
56
+ return target
57
+
58
+
59
+ def build_parser() -> argparse.ArgumentParser:
60
+ p = argparse.ArgumentParser(
61
+ prog="workpeg-new-function",
62
+ description="Create a new Workpeg function"
63
+ " project from the built-in template.",
64
+ )
65
+ p.add_argument(
66
+ "path",
67
+ help="Target directory to create/populate (e.g. ./my-function).",
68
+ )
69
+ p.add_argument(
70
+ "--force",
71
+ action="store_true",
72
+ help="Overwrite existing files in the target directory.",
73
+ )
74
+ return p
75
+
76
+
77
+ def main() -> None:
78
+ parser = build_parser()
79
+ args = parser.parse_args()
80
+
81
+ try:
82
+ out = create_new_project(args.path, force=args.force)
83
+ print(str(out))
84
+ except TemplateError as e:
85
+ print(f"ERROR: {e}", file=os.sys.stderr)
86
+ raise SystemExit(2)
workpeg_sdk/runtime.py ADDED
@@ -0,0 +1,164 @@
1
+ # src/workpeg_sdk/runtime.py
2
+
3
+ import importlib
4
+ import json
5
+ import os
6
+ import sys
7
+ import traceback
8
+ from typing import Any, Callable, Dict, Tuple
9
+
10
+
11
+ DEFAULT_ENTRYPOINT = "app.main:main"
12
+
13
+
14
+ class FunctionRuntimeError(Exception):
15
+ """Custom exception for runtime-level errors."""
16
+ pass
17
+
18
+
19
+ def parse_entrypoint(entry: str) -> Tuple[str, str]:
20
+ """
21
+ Parse 'module.path:func_name' into (module, func).
22
+ """
23
+ try:
24
+ module_name, func_name = entry.split(":", 1)
25
+ module_name = module_name.strip()
26
+ func_name = func_name.strip()
27
+ if not module_name or not func_name:
28
+ raise ValueError
29
+ return module_name, func_name
30
+ except ValueError:
31
+ raise FunctionRuntimeError(
32
+ f"Invalid FUNCTION_ENTRYPOINT format: '{entry}'. "
33
+ "Expected 'module.path:func_name'."
34
+ )
35
+
36
+
37
+ def _ensure_cwd_on_syspath() -> None:
38
+ """
39
+ Ensure current working directory is at the front of sys.path.
40
+
41
+ This is critical when running via a console script installed by pip,
42
+ because sys.path[0] is the script's directory (e.g. venv/bin),
43
+ NOT the current working directory.
44
+ """
45
+ cwd = os.getcwd()
46
+ if cwd not in sys.path:
47
+ sys.path.insert(0, cwd)
48
+
49
+
50
+ def load_function() -> Callable[[Dict[str, Any], Dict[str, Any]], Any]:
51
+ """
52
+ Load the user function based on env FUNCTION_ENTRYPOINT or default.
53
+ """
54
+ _ensure_cwd_on_syspath()
55
+
56
+ entry = os.getenv("FUNCTION_ENTRYPOINT", DEFAULT_ENTRYPOINT)
57
+ module_name, func_name = parse_entrypoint(entry)
58
+
59
+ try:
60
+ module = importlib.import_module(module_name)
61
+ except Exception as exc:
62
+ raise FunctionRuntimeError(
63
+ f"Failed to import module '{module_name}' from '{entry}': {exc}"
64
+ ) from exc
65
+
66
+ try:
67
+ fn = getattr(module, func_name)
68
+ except AttributeError as exc:
69
+ raise FunctionRuntimeError(
70
+ f"Module '{module_name}'"
71
+ f" does not define '{func_name}' from '{entry}'."
72
+ ) from exc
73
+
74
+ if not callable(fn):
75
+ raise FunctionRuntimeError(
76
+ f"'{entry}' is not callable. Expected a function."
77
+ )
78
+
79
+ return fn
80
+
81
+
82
+ def read_request() -> Dict[str, Any]:
83
+ """
84
+ Read a single JSON object from stdin.
85
+ Expected format: {"context": {...}, "payload": {...}}
86
+ """
87
+ try:
88
+ raw = sys.stdin.read()
89
+ if not raw.strip():
90
+ raise FunctionRuntimeError("No input received on stdin.")
91
+ data = json.loads(raw)
92
+ if not isinstance(data, dict):
93
+ raise FunctionRuntimeError("Input JSON must be a JSON object.")
94
+ return data
95
+ except json.JSONDecodeError as exc:
96
+ raise FunctionRuntimeError(f"Invalid JSON input: {exc}") from exc
97
+
98
+
99
+ def run_once() -> int:
100
+ """
101
+ Run a single invocation:
102
+ - load function
103
+ - read request
104
+ - call function
105
+ - emit JSON result
106
+
107
+ Returns exit code (0 on success, 1 on error).
108
+ """
109
+ try:
110
+ fn = load_function()
111
+ request = read_request()
112
+
113
+ context = request.get("context", {})
114
+ payload = request.get("payload", {})
115
+
116
+ if not isinstance(context, dict):
117
+ raise FunctionRuntimeError(
118
+ "Field 'context' must be a JSON object.")
119
+ if not isinstance(payload, dict):
120
+ raise FunctionRuntimeError(
121
+ "Field 'payload' must be a JSON object.")
122
+
123
+ result = fn(context, payload)
124
+
125
+ output = {
126
+ "status": "success",
127
+ "result": result,
128
+ }
129
+ print(json.dumps(output))
130
+ return 0
131
+
132
+ except FunctionRuntimeError as e:
133
+ # Runtime / wiring error
134
+ err_output = {
135
+ "status": "error",
136
+ "error_type": "runtime_error",
137
+ "error": str(e),
138
+ }
139
+ # Send JSON to stdout (for the host)
140
+ # and more detail to stderr (for logs)
141
+ print(json.dumps(err_output))
142
+ print(f"[workpeg-runtime] runtime_error: {e}", file=sys.stderr)
143
+ return 1
144
+
145
+ except Exception as e:
146
+ # User code error
147
+ trace = traceback.format_exc()
148
+ err_output = {
149
+ "status": "error",
150
+ "error_type": "user_error",
151
+ "error": str(e),
152
+ "trace": trace,
153
+ }
154
+ print(json.dumps(err_output))
155
+ print(f"[workpeg-runtime] user_error: {e}\n{trace}", file=sys.stderr)
156
+ return 1
157
+
158
+
159
+ def main() -> None:
160
+ """
161
+ Console script entrypoint for 'workpeg-runtime'.
162
+ """
163
+ exit_code = run_once()
164
+ sys.exit(exit_code)
File without changes
@@ -0,0 +1,17 @@
1
+ FROM python:3.12-slim
2
+
3
+ ENV PYTHONDONTWRITEBYTECODE=1
4
+ ENV PYTHONUNBUFFERED=1
5
+
6
+ RUN useradd -m -u 10001 appuser
7
+
8
+ WORKDIR /app
9
+
10
+ COPY requirements.txt /app/requirements.txt
11
+ RUN pip install --no-cache-dir -r /app/requirements.txt
12
+
13
+ COPY app /app/app
14
+
15
+ USER appuser
16
+
17
+ ENTRYPOINT ["workpeg-runtime"]
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2011-2025 The Bootstrap Authors
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
13
+ all 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
21
+ THE SOFTWARE.
@@ -0,0 +1,81 @@
1
+ # Workpeg Function Project
2
+
3
+ This directory was created using **Workpeg SDK**.
4
+
5
+ It contains everything you need to build and run a single **Workpeg Function**, which can later be attached to a Peg inside Workpeg.
6
+
7
+ ---
8
+
9
+ ## Project Structure
10
+
11
+ ```text
12
+ app/
13
+ __init__.py
14
+ main.py # Your function: def main(context, payload) -> dict
15
+ requirements.txt # Python dependencies (must include workpeg-sdk)
16
+ Dockerfile # Example container runtime using workpeg-runtime
17
+ ```
18
+
19
+ ---
20
+
21
+ ## Function Contract
22
+
23
+ Your function **must** define:
24
+
25
+ ```python
26
+ def main(context: dict, payload: dict) -> dict:
27
+ ...
28
+ ```
29
+
30
+ - `context` – execution metadata injected by Workpeg (user, peg, execution id, etc.)
31
+ - `payload` – input data sent by the caller
32
+ - return value – must be JSON serializable
33
+
34
+ By default, the runtime looks for `app.main:main`.
35
+ You can change this using the `FUNCTION_ENTRYPOINT` environment variable:
36
+
37
+ ```bash
38
+ export FUNCTION_ENTRYPOINT="module.path:function_name"
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Running Locally
44
+
45
+ Make sure `workpeg-sdk` is installed (or use the generated `requirements.txt`):
46
+
47
+ ```bash
48
+ pip install -r requirements.txt
49
+ ```
50
+
51
+ Then run the function:
52
+
53
+ ```bash
54
+ echo '{"context": {}, "payload": {"hello": "world"}}' | workpeg-runtime
55
+ ```
56
+
57
+ Expected output:
58
+
59
+ ```json
60
+ { "status": "success", "result": { "hello": "world" } }
61
+ ```
62
+
63
+ ---
64
+
65
+ ## Docker (Optional)
66
+
67
+ You can build and run the container using the included `Dockerfile`:
68
+
69
+ ```bash
70
+ docker build -t my-workpeg-function .
71
+ echo '{"context": {}, "payload": {"hello": "world"}}' | docker run --rm -i my-workpeg-function
72
+ ```
73
+
74
+ ---
75
+
76
+ ## More
77
+
78
+ This template is part of the official Workpeg SDK.
79
+
80
+ Repo: [https://gitlab.com/workpeg/workpeg-sdk](https://gitlab.com/workpeg/workpeg-sdk)
81
+ Full documentation and Peg integration guides are coming soon.
File without changes
@@ -0,0 +1,2 @@
1
+ def main(context, payload):
2
+ return payload
@@ -0,0 +1,125 @@
1
+ Metadata-Version: 2.4
2
+ Name: workpeg-sdk
3
+ Version: 0.1.0
4
+ Summary: Workpeg function runtime and SDK
5
+ Author-email: Workpeg <support@workpeg.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://gitlab.com/workpeg/workpeg-sdk
8
+ Project-URL: Repository, https://gitlab.com/workpeg/workpeg-sdk
9
+ Keywords: workpeg,serverless,functions,runtime
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Dynamic: license-file
18
+
19
+ # Workpeg SDK
20
+
21
+ SDK for building **Workpeg Pegs and Functions**.
22
+
23
+ Currently focused on:
24
+
25
+ * `workpeg-runtime` – function execution runtime
26
+ * `workpeg-new-function` – project scaffolding
27
+
28
+ Frontend features, Peg integrations, push & deployment tooling are coming soon.
29
+
30
+ ---
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ pip install workpeg-sdk
36
+ ```
37
+
38
+ ---
39
+
40
+ ## Getting Started
41
+
42
+ ### 1. Create a New Function
43
+
44
+ ```bash
45
+ workpeg-new-function my-peg
46
+ cd my-peg
47
+ ```
48
+
49
+ This generates:
50
+
51
+ ```
52
+ my-peg/
53
+ app/
54
+ __init__.py
55
+ main.py
56
+ requirements.txt
57
+ Dockerfile
58
+ ```
59
+
60
+ ---
61
+
62
+ ### 2. Implement Your Function
63
+
64
+ Edit `app/main.py`:
65
+
66
+ ```python
67
+ def main(context, payload):
68
+ return payload
69
+ ```
70
+
71
+ Every Workpeg Function must define:
72
+
73
+ ```python
74
+ def main(context: dict, payload: dict) -> dict
75
+ ```
76
+
77
+ * `context` → execution metadata (provided by Workpeg)
78
+ * `payload` → input data
79
+ * return value → must be JSON serializable
80
+
81
+ ---
82
+
83
+ ### 3. Run Locally
84
+
85
+ ```bash
86
+ echo '{"context": {}, "payload": {"hello": "world"}}' | workpeg-runtime
87
+ ```
88
+
89
+ Example output:
90
+
91
+ ```json
92
+ {"status":"success","result":{"hello":"world"}}
93
+ ```
94
+
95
+ ---
96
+
97
+ ## Runtime
98
+
99
+ `workpeg-runtime`:
100
+
101
+ 1. Reads JSON from STDIN
102
+ 2. Loads `app.main:main`
103
+ 3. Executes the function
104
+ 4. Writes structured JSON to STDOUT
105
+
106
+ Override entrypoint:
107
+
108
+ ```bash
109
+ FUNCTION_ENTRYPOINT="module.path:function_name" workpeg-runtime
110
+ ```
111
+
112
+ ---
113
+
114
+ ## Roadmap
115
+
116
+ * Peg-level integrations
117
+ * Frontend features (Streamlit/Reflex style)
118
+ * Push & deployment tooling
119
+ * Hosted execution
120
+
121
+ ---
122
+
123
+ 📘 Documentation coming soon.
124
+
125
+ MIT License
@@ -0,0 +1,15 @@
1
+ workpeg_sdk/__init__.py,sha256=y5VvJPaDTdeEX8SbdZRLHxuZyx1Sr1Xbh-fMuKrXJHI,80
2
+ workpeg_sdk/create_new.py,sha256=JIRc7cNVuxr3cm1AG8gZMakFf4a-Q9wQ6WVHkd8nR0k,2423
3
+ workpeg_sdk/runtime.py,sha256=4ksjupPKYv3jQg6KTDrwS3x1LKqNJKb18xIE33V4RMs,4520
4
+ workpeg_sdk/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ workpeg_sdk/templates/functions/Dockerfile,sha256=JJqkAZIpfMp_j_dBi5QCnqWcYResMo1o-2zj4iZhuJw,288
6
+ workpeg_sdk/templates/functions/LICENSE,sha256=aBE_D2wOlXFk9dvBsu-Wt1xgJkzHXk_7wYR9O7PNb58,1092
7
+ workpeg_sdk/templates/functions/README.md,sha256=B5HmxUflOKsr3-cS1EwarieBZSD34Zd-Nge0THQfK0M,1783
8
+ workpeg_sdk/templates/functions/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ workpeg_sdk/templates/functions/app/main.py,sha256=pgI-9eriwIbGqRjG2gjM2ldKeX5FMltJpeN5Bz5-wlQ,47
10
+ workpeg_sdk-0.1.0.dist-info/licenses/LICENSE,sha256=aBE_D2wOlXFk9dvBsu-Wt1xgJkzHXk_7wYR9O7PNb58,1092
11
+ workpeg_sdk-0.1.0.dist-info/METADATA,sha256=Cs2dU8BcSLoRk82cffjQnj0DJ5vK938yDTr5GQ64joY,2144
12
+ workpeg_sdk-0.1.0.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
13
+ workpeg_sdk-0.1.0.dist-info/entry_points.txt,sha256=Zwt02joUSB4wpQ-zxRzplT7AjmDZ5aU5_PfxoChmoNs,112
14
+ workpeg_sdk-0.1.0.dist-info/top_level.txt,sha256=0BobHoWoeCtlHrk9c0oIrS8FDOet9WJRVlSz6e8glGM,12
15
+ workpeg_sdk-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ workpeg-new-function = workpeg_sdk.create_new:main
3
+ workpeg-runtime = workpeg_sdk.runtime:main
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2011-2025 The Bootstrap Authors
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
13
+ all 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
21
+ THE SOFTWARE.
@@ -0,0 +1 @@
1
+ workpeg_sdk