seekapi 1.0.0__tar.gz
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.
- seekapi-1.0.0/PKG-INFO +109 -0
- seekapi-1.0.0/README.md +91 -0
- seekapi-1.0.0/pyproject.toml +28 -0
- seekapi-1.0.0/setup.cfg +4 -0
- seekapi-1.0.0/src/seekapi/__init__.py +218 -0
- seekapi-1.0.0/src/seekapi.egg-info/PKG-INFO +109 -0
- seekapi-1.0.0/src/seekapi.egg-info/SOURCES.txt +7 -0
- seekapi-1.0.0/src/seekapi.egg-info/dependency_links.txt +1 -0
- seekapi-1.0.0/src/seekapi.egg-info/top_level.txt +1 -0
seekapi-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: seekapi
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: SDK for SeekAPI workers: input/output handling, push data and files, success/failure.
|
|
5
|
+
Author: SeekAPI
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/seekapi/workers
|
|
8
|
+
Keywords: seekapi,worker,lambda,sdk
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# SeekAPI Python SDK
|
|
20
|
+
|
|
21
|
+
SDK for building SeekAPI workers in Python. Handles **input** (fetch from presigned URL) and **output** (push JSON and files), with a minimal Lambda return contract.
|
|
22
|
+
|
|
23
|
+
- **Zero dependencies** — uses only the standard library.
|
|
24
|
+
- **Python 3.10+** — works in AWS Lambda runtimes.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install seekapi
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Quick start (recommended)
|
|
33
|
+
|
|
34
|
+
Use `run()` so the SDK handles input, output, and errors. You only implement a function from input → output:
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from seekapi import run
|
|
38
|
+
|
|
39
|
+
def handler(event, context):
|
|
40
|
+
return run(event, context, lambda input: {
|
|
41
|
+
"message": f"hello {input.get('name', 'world')}"
|
|
42
|
+
})
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Low-level API
|
|
46
|
+
|
|
47
|
+
When you need more control (e.g. multiple `push_data` calls or `push_file`):
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from seekapi import (
|
|
51
|
+
get_input,
|
|
52
|
+
create_context,
|
|
53
|
+
push_data,
|
|
54
|
+
push_file,
|
|
55
|
+
register_request_id,
|
|
56
|
+
success,
|
|
57
|
+
failure,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def handler(event, context):
|
|
61
|
+
request_id = getattr(context, "aws_request_id", None) if context else None
|
|
62
|
+
job_uuid = (event.get("job_uuid") or "").strip()
|
|
63
|
+
if request_id and job_uuid:
|
|
64
|
+
register_request_id(job_uuid, request_id)
|
|
65
|
+
|
|
66
|
+
ctx = create_context(event)
|
|
67
|
+
try:
|
|
68
|
+
input_data = get_input(event) # fetches and parses JSON from input_presigned_url
|
|
69
|
+
# ... your logic ...
|
|
70
|
+
push_data(ctx, {"result": "ok"})
|
|
71
|
+
return success(request_id=request_id)
|
|
72
|
+
except Exception as e:
|
|
73
|
+
return failure("WORKER_ERROR", str(e), request_id=request_id)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### API reference
|
|
77
|
+
|
|
78
|
+
| Function | Description |
|
|
79
|
+
|----------|-------------|
|
|
80
|
+
| `get_input(event, timeout=10)` | Fetch and parse job input JSON from `event["input_presigned_url"]`. Raises `MissingInputError` if URL missing, `ValueError` on HTTP/JSON errors. |
|
|
81
|
+
| `create_context(event)` | Build context dict for push_data/push_file (job_uuid, execution_token, api_base, secret from env). |
|
|
82
|
+
| `push_data(context, data, type="json")` | Push JSON to the job (overwrites response_json). |
|
|
83
|
+
| `push_file(context, name, local_path, content_type=None)` | Push a file to the job temp_files. |
|
|
84
|
+
| `register_request_id(job_uuid, request_id)` | Register Lambda request_id for live logs (no-op if env not set). |
|
|
85
|
+
| `success(request_id=None)` | Return `{ "ok": true, "request_id"? }`. |
|
|
86
|
+
| `failure(code, message, request_id=None)` | Return `{ "ok": false, "error": { "code", "message" }, "request_id"? }`. |
|
|
87
|
+
| `run(event, context, user_fn)` | Get input → call `user_fn(input)` → push result → return success; on exception return failure. |
|
|
88
|
+
|
|
89
|
+
### Environment (set by the platform)
|
|
90
|
+
|
|
91
|
+
- `WORKER_API_BASE_URL` — backend base URL for push and register_request_id.
|
|
92
|
+
- `WORKER_INTERNAL_SECRET` — secret for internal API auth.
|
|
93
|
+
|
|
94
|
+
### Errors
|
|
95
|
+
|
|
96
|
+
- **MissingInputError** — `event` has no `input_presigned_url`.
|
|
97
|
+
- **ValueError** — Input fetch failed (HTTP error, timeout, invalid JSON). Message is descriptive.
|
|
98
|
+
|
|
99
|
+
## Publishing to PyPI
|
|
100
|
+
|
|
101
|
+
From the package directory:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
pip install build twine
|
|
105
|
+
python -m build
|
|
106
|
+
twine upload dist/*
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Use a version bump and tag before each release.
|
seekapi-1.0.0/README.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# SeekAPI Python SDK
|
|
2
|
+
|
|
3
|
+
SDK for building SeekAPI workers in Python. Handles **input** (fetch from presigned URL) and **output** (push JSON and files), with a minimal Lambda return contract.
|
|
4
|
+
|
|
5
|
+
- **Zero dependencies** — uses only the standard library.
|
|
6
|
+
- **Python 3.10+** — works in AWS Lambda runtimes.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install seekapi
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Quick start (recommended)
|
|
15
|
+
|
|
16
|
+
Use `run()` so the SDK handles input, output, and errors. You only implement a function from input → output:
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
from seekapi import run
|
|
20
|
+
|
|
21
|
+
def handler(event, context):
|
|
22
|
+
return run(event, context, lambda input: {
|
|
23
|
+
"message": f"hello {input.get('name', 'world')}"
|
|
24
|
+
})
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Low-level API
|
|
28
|
+
|
|
29
|
+
When you need more control (e.g. multiple `push_data` calls or `push_file`):
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from seekapi import (
|
|
33
|
+
get_input,
|
|
34
|
+
create_context,
|
|
35
|
+
push_data,
|
|
36
|
+
push_file,
|
|
37
|
+
register_request_id,
|
|
38
|
+
success,
|
|
39
|
+
failure,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
def handler(event, context):
|
|
43
|
+
request_id = getattr(context, "aws_request_id", None) if context else None
|
|
44
|
+
job_uuid = (event.get("job_uuid") or "").strip()
|
|
45
|
+
if request_id and job_uuid:
|
|
46
|
+
register_request_id(job_uuid, request_id)
|
|
47
|
+
|
|
48
|
+
ctx = create_context(event)
|
|
49
|
+
try:
|
|
50
|
+
input_data = get_input(event) # fetches and parses JSON from input_presigned_url
|
|
51
|
+
# ... your logic ...
|
|
52
|
+
push_data(ctx, {"result": "ok"})
|
|
53
|
+
return success(request_id=request_id)
|
|
54
|
+
except Exception as e:
|
|
55
|
+
return failure("WORKER_ERROR", str(e), request_id=request_id)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### API reference
|
|
59
|
+
|
|
60
|
+
| Function | Description |
|
|
61
|
+
|----------|-------------|
|
|
62
|
+
| `get_input(event, timeout=10)` | Fetch and parse job input JSON from `event["input_presigned_url"]`. Raises `MissingInputError` if URL missing, `ValueError` on HTTP/JSON errors. |
|
|
63
|
+
| `create_context(event)` | Build context dict for push_data/push_file (job_uuid, execution_token, api_base, secret from env). |
|
|
64
|
+
| `push_data(context, data, type="json")` | Push JSON to the job (overwrites response_json). |
|
|
65
|
+
| `push_file(context, name, local_path, content_type=None)` | Push a file to the job temp_files. |
|
|
66
|
+
| `register_request_id(job_uuid, request_id)` | Register Lambda request_id for live logs (no-op if env not set). |
|
|
67
|
+
| `success(request_id=None)` | Return `{ "ok": true, "request_id"? }`. |
|
|
68
|
+
| `failure(code, message, request_id=None)` | Return `{ "ok": false, "error": { "code", "message" }, "request_id"? }`. |
|
|
69
|
+
| `run(event, context, user_fn)` | Get input → call `user_fn(input)` → push result → return success; on exception return failure. |
|
|
70
|
+
|
|
71
|
+
### Environment (set by the platform)
|
|
72
|
+
|
|
73
|
+
- `WORKER_API_BASE_URL` — backend base URL for push and register_request_id.
|
|
74
|
+
- `WORKER_INTERNAL_SECRET` — secret for internal API auth.
|
|
75
|
+
|
|
76
|
+
### Errors
|
|
77
|
+
|
|
78
|
+
- **MissingInputError** — `event` has no `input_presigned_url`.
|
|
79
|
+
- **ValueError** — Input fetch failed (HTTP error, timeout, invalid JSON). Message is descriptive.
|
|
80
|
+
|
|
81
|
+
## Publishing to PyPI
|
|
82
|
+
|
|
83
|
+
From the package directory:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
pip install build twine
|
|
87
|
+
python -m build
|
|
88
|
+
twine upload dist/*
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Use a version bump and tag before each release.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "seekapi"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "SDK for SeekAPI workers: input/output handling, push data and files, success/failure."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "SeekAPI" }]
|
|
13
|
+
keywords = ["seekapi", "worker", "lambda", "sdk"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Repository = "https://github.com/seekapi/workers"
|
|
26
|
+
|
|
27
|
+
[tool.setuptools.packages.find]
|
|
28
|
+
where = ["src"]
|
seekapi-1.0.0/setup.cfg
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""SeekAPI worker SDK: get_input, push_data/push_file, run(), success/failure.
|
|
2
|
+
Zero dependencies; use in AWS Lambda workers with WORKER_API_BASE_URL and WORKER_INTERNAL_SECRET set."""
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import mimetypes
|
|
6
|
+
import os
|
|
7
|
+
from urllib.error import HTTPError, URLError
|
|
8
|
+
from urllib.request import Request, urlopen
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MissingInputError(ValueError):
|
|
12
|
+
"""Raised when event has no input_presigned_url."""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_input(event: dict, timeout: int = 10) -> dict:
|
|
16
|
+
"""Fetch and parse job input from the presigned URL in the event.
|
|
17
|
+
Returns the JSON body as a dict. Raises MissingInputError if input_presigned_url
|
|
18
|
+
is missing, or ValueError on HTTP/JSON errors (with a clear message)."""
|
|
19
|
+
url = (event.get("input_presigned_url") or "").strip()
|
|
20
|
+
if not url:
|
|
21
|
+
raise MissingInputError("Missing input_presigned_url in event")
|
|
22
|
+
try:
|
|
23
|
+
with urlopen(url, timeout=timeout) as resp:
|
|
24
|
+
if resp.status >= 400:
|
|
25
|
+
raise ValueError(f"Input URL returned HTTP {resp.status}")
|
|
26
|
+
body = resp.read().decode("utf-8")
|
|
27
|
+
return json.loads(body)
|
|
28
|
+
except (HTTPError, URLError) as e:
|
|
29
|
+
raise ValueError(f"Failed to fetch input: {e}") from e
|
|
30
|
+
except json.JSONDecodeError as e:
|
|
31
|
+
raise ValueError(f"Invalid JSON in input: {e}") from e
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def create_context(event: dict) -> dict:
|
|
35
|
+
"""Build context from Lambda event for push_data/push_file.
|
|
36
|
+
Returns dict with job_uuid, execution_token, api_base, secret (from env)."""
|
|
37
|
+
job_uuid = (event.get("job_uuid") or "").strip()
|
|
38
|
+
execution_token = (event.get("execution_token") or "").strip()
|
|
39
|
+
api_base = (os.environ.get("WORKER_API_BASE_URL") or "").strip().rstrip("/")
|
|
40
|
+
secret = (os.environ.get("WORKER_INTERNAL_SECRET") or "").strip()
|
|
41
|
+
return {
|
|
42
|
+
"job_uuid": job_uuid,
|
|
43
|
+
"execution_token": execution_token,
|
|
44
|
+
"api_base": api_base,
|
|
45
|
+
"secret": secret,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def register_request_id(job_uuid: str, request_id: str) -> None:
|
|
50
|
+
"""Call the API to register this Lambda's request_id so live execution logs can be streamed.
|
|
51
|
+
No-op if WORKER_API_BASE_URL or WORKER_INTERNAL_SECRET is not set."""
|
|
52
|
+
base = (os.environ.get("WORKER_API_BASE_URL") or "").strip().rstrip("/")
|
|
53
|
+
secret = (os.environ.get("WORKER_INTERNAL_SECRET") or "").strip()
|
|
54
|
+
if not base or not secret:
|
|
55
|
+
return
|
|
56
|
+
url = f"{base}/v1/internal/jobs/{job_uuid}/worker-request-id"
|
|
57
|
+
try:
|
|
58
|
+
data = json.dumps({"request_id": request_id}).encode("utf-8")
|
|
59
|
+
req = Request(
|
|
60
|
+
url,
|
|
61
|
+
data=data,
|
|
62
|
+
method="POST",
|
|
63
|
+
headers={
|
|
64
|
+
"Content-Type": "application/json",
|
|
65
|
+
"X-Internal-Secret": secret,
|
|
66
|
+
},
|
|
67
|
+
)
|
|
68
|
+
urlopen(req, timeout=5)
|
|
69
|
+
except (HTTPError, URLError, OSError):
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def push_data(
|
|
74
|
+
context: dict,
|
|
75
|
+
data: dict,
|
|
76
|
+
type: str = "json",
|
|
77
|
+
) -> None:
|
|
78
|
+
"""Push JSON data to the job (overwrites response_json). No-op if api_base/secret not set."""
|
|
79
|
+
api_base = context.get("api_base") or ""
|
|
80
|
+
secret = context.get("secret") or ""
|
|
81
|
+
job_uuid = context.get("job_uuid") or ""
|
|
82
|
+
execution_token = context.get("execution_token") or ""
|
|
83
|
+
if not api_base or not secret or not job_uuid or not execution_token:
|
|
84
|
+
return
|
|
85
|
+
url = f"{api_base}/v1/internal/jobs/{job_uuid}/push"
|
|
86
|
+
try:
|
|
87
|
+
body = json.dumps({
|
|
88
|
+
"execution_token": execution_token,
|
|
89
|
+
"data": data,
|
|
90
|
+
"type": type,
|
|
91
|
+
}).encode("utf-8")
|
|
92
|
+
req = Request(
|
|
93
|
+
url,
|
|
94
|
+
data=body,
|
|
95
|
+
method="POST",
|
|
96
|
+
headers={
|
|
97
|
+
"Content-Type": "application/json",
|
|
98
|
+
"X-Internal-Secret": secret,
|
|
99
|
+
},
|
|
100
|
+
)
|
|
101
|
+
urlopen(req, timeout=30)
|
|
102
|
+
except (HTTPError, URLError, OSError):
|
|
103
|
+
raise
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def push_file(
|
|
107
|
+
context: dict,
|
|
108
|
+
name: str,
|
|
109
|
+
local_path: str,
|
|
110
|
+
content_type: str | None = None,
|
|
111
|
+
) -> None:
|
|
112
|
+
"""Push a file from local_path to the job temp_files. No-op if api_base/secret not set."""
|
|
113
|
+
api_base = context.get("api_base") or ""
|
|
114
|
+
secret = context.get("secret") or ""
|
|
115
|
+
job_uuid = context.get("job_uuid") or ""
|
|
116
|
+
execution_token = context.get("execution_token") or ""
|
|
117
|
+
if not api_base or not secret or not job_uuid or not execution_token:
|
|
118
|
+
return
|
|
119
|
+
url = f"{api_base}/v1/internal/jobs/{job_uuid}/push/file"
|
|
120
|
+
ct = content_type
|
|
121
|
+
if not ct:
|
|
122
|
+
ct, _ = mimetypes.guess_type(local_path)
|
|
123
|
+
ct = ct or "application/octet-stream"
|
|
124
|
+
try:
|
|
125
|
+
with open(local_path, "rb") as f:
|
|
126
|
+
body_bytes = f.read()
|
|
127
|
+
except OSError:
|
|
128
|
+
raise
|
|
129
|
+
|
|
130
|
+
# multipart/form-data: execution_token, name, file
|
|
131
|
+
boundary = "----WorkerSDKBoundary" + os.urandom(8).hex()
|
|
132
|
+
b = boundary.encode("ascii")
|
|
133
|
+
crlf = b"\r\n"
|
|
134
|
+
body_parts = [
|
|
135
|
+
b"--" + b + crlf,
|
|
136
|
+
b'Content-Disposition: form-data; name="execution_token"' + crlf + crlf,
|
|
137
|
+
execution_token.encode("utf-8"),
|
|
138
|
+
crlf,
|
|
139
|
+
b"--" + b + crlf,
|
|
140
|
+
b'Content-Disposition: form-data; name="name"' + crlf + crlf,
|
|
141
|
+
name.encode("utf-8"),
|
|
142
|
+
crlf,
|
|
143
|
+
b"--" + b + crlf,
|
|
144
|
+
b'Content-Disposition: form-data; name="file"; filename="' + name.encode("utf-8") + b'"' + crlf,
|
|
145
|
+
b"Content-Type: " + ct.encode("ascii") + crlf + crlf,
|
|
146
|
+
body_bytes,
|
|
147
|
+
crlf,
|
|
148
|
+
b"--" + b + b"--" + crlf,
|
|
149
|
+
]
|
|
150
|
+
body = b"".join(body_parts)
|
|
151
|
+
req = Request(
|
|
152
|
+
url,
|
|
153
|
+
data=body,
|
|
154
|
+
method="POST",
|
|
155
|
+
headers={
|
|
156
|
+
"Content-Type": f"multipart/form-data; boundary={boundary}",
|
|
157
|
+
"X-Internal-Secret": secret,
|
|
158
|
+
},
|
|
159
|
+
)
|
|
160
|
+
try:
|
|
161
|
+
urlopen(req, timeout=60)
|
|
162
|
+
except (HTTPError, URLError, OSError):
|
|
163
|
+
raise
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def success(request_id: str | None = None) -> dict:
|
|
167
|
+
"""Return minimal success payload for Lambda. Result is taken from last push_data."""
|
|
168
|
+
out: dict = {"ok": True}
|
|
169
|
+
if request_id:
|
|
170
|
+
out["request_id"] = request_id
|
|
171
|
+
return out
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def failure(code: str, message: str, request_id: str | None = None) -> dict:
|
|
175
|
+
"""Return minimal failure payload for Lambda."""
|
|
176
|
+
out: dict = {
|
|
177
|
+
"ok": False,
|
|
178
|
+
"error": {"code": code, "message": message},
|
|
179
|
+
}
|
|
180
|
+
if request_id:
|
|
181
|
+
out["request_id"] = request_id
|
|
182
|
+
return out
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def run(
|
|
186
|
+
event: dict,
|
|
187
|
+
context: object,
|
|
188
|
+
user_fn: "object", # Callable[[dict], dict]
|
|
189
|
+
) -> dict:
|
|
190
|
+
"""Run a worker with input/output handled by the SDK.
|
|
191
|
+
Fetches input via get_input(event), calls user_fn(input_data), pushes the
|
|
192
|
+
result via push_data, and returns success(request_id). On any exception,
|
|
193
|
+
returns failure('WORKER_ERROR', str(e), request_id)."""
|
|
194
|
+
request_id = getattr(context, "aws_request_id", None) if context else None
|
|
195
|
+
job_uuid = (event.get("job_uuid") or "").strip()
|
|
196
|
+
if request_id and job_uuid:
|
|
197
|
+
register_request_id(job_uuid, request_id)
|
|
198
|
+
ctx = create_context(event)
|
|
199
|
+
try:
|
|
200
|
+
input_data = get_input(event)
|
|
201
|
+
result = user_fn(input_data)
|
|
202
|
+
push_data(ctx, result)
|
|
203
|
+
return success(request_id=request_id)
|
|
204
|
+
except Exception as e:
|
|
205
|
+
return failure("WORKER_ERROR", str(e), request_id=request_id)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
__all__ = [
|
|
209
|
+
"MissingInputError",
|
|
210
|
+
"get_input",
|
|
211
|
+
"create_context",
|
|
212
|
+
"register_request_id",
|
|
213
|
+
"push_data",
|
|
214
|
+
"push_file",
|
|
215
|
+
"success",
|
|
216
|
+
"failure",
|
|
217
|
+
"run",
|
|
218
|
+
]
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: seekapi
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: SDK for SeekAPI workers: input/output handling, push data and files, success/failure.
|
|
5
|
+
Author: SeekAPI
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/seekapi/workers
|
|
8
|
+
Keywords: seekapi,worker,lambda,sdk
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# SeekAPI Python SDK
|
|
20
|
+
|
|
21
|
+
SDK for building SeekAPI workers in Python. Handles **input** (fetch from presigned URL) and **output** (push JSON and files), with a minimal Lambda return contract.
|
|
22
|
+
|
|
23
|
+
- **Zero dependencies** — uses only the standard library.
|
|
24
|
+
- **Python 3.10+** — works in AWS Lambda runtimes.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install seekapi
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Quick start (recommended)
|
|
33
|
+
|
|
34
|
+
Use `run()` so the SDK handles input, output, and errors. You only implement a function from input → output:
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from seekapi import run
|
|
38
|
+
|
|
39
|
+
def handler(event, context):
|
|
40
|
+
return run(event, context, lambda input: {
|
|
41
|
+
"message": f"hello {input.get('name', 'world')}"
|
|
42
|
+
})
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Low-level API
|
|
46
|
+
|
|
47
|
+
When you need more control (e.g. multiple `push_data` calls or `push_file`):
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from seekapi import (
|
|
51
|
+
get_input,
|
|
52
|
+
create_context,
|
|
53
|
+
push_data,
|
|
54
|
+
push_file,
|
|
55
|
+
register_request_id,
|
|
56
|
+
success,
|
|
57
|
+
failure,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def handler(event, context):
|
|
61
|
+
request_id = getattr(context, "aws_request_id", None) if context else None
|
|
62
|
+
job_uuid = (event.get("job_uuid") or "").strip()
|
|
63
|
+
if request_id and job_uuid:
|
|
64
|
+
register_request_id(job_uuid, request_id)
|
|
65
|
+
|
|
66
|
+
ctx = create_context(event)
|
|
67
|
+
try:
|
|
68
|
+
input_data = get_input(event) # fetches and parses JSON from input_presigned_url
|
|
69
|
+
# ... your logic ...
|
|
70
|
+
push_data(ctx, {"result": "ok"})
|
|
71
|
+
return success(request_id=request_id)
|
|
72
|
+
except Exception as e:
|
|
73
|
+
return failure("WORKER_ERROR", str(e), request_id=request_id)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### API reference
|
|
77
|
+
|
|
78
|
+
| Function | Description |
|
|
79
|
+
|----------|-------------|
|
|
80
|
+
| `get_input(event, timeout=10)` | Fetch and parse job input JSON from `event["input_presigned_url"]`. Raises `MissingInputError` if URL missing, `ValueError` on HTTP/JSON errors. |
|
|
81
|
+
| `create_context(event)` | Build context dict for push_data/push_file (job_uuid, execution_token, api_base, secret from env). |
|
|
82
|
+
| `push_data(context, data, type="json")` | Push JSON to the job (overwrites response_json). |
|
|
83
|
+
| `push_file(context, name, local_path, content_type=None)` | Push a file to the job temp_files. |
|
|
84
|
+
| `register_request_id(job_uuid, request_id)` | Register Lambda request_id for live logs (no-op if env not set). |
|
|
85
|
+
| `success(request_id=None)` | Return `{ "ok": true, "request_id"? }`. |
|
|
86
|
+
| `failure(code, message, request_id=None)` | Return `{ "ok": false, "error": { "code", "message" }, "request_id"? }`. |
|
|
87
|
+
| `run(event, context, user_fn)` | Get input → call `user_fn(input)` → push result → return success; on exception return failure. |
|
|
88
|
+
|
|
89
|
+
### Environment (set by the platform)
|
|
90
|
+
|
|
91
|
+
- `WORKER_API_BASE_URL` — backend base URL for push and register_request_id.
|
|
92
|
+
- `WORKER_INTERNAL_SECRET` — secret for internal API auth.
|
|
93
|
+
|
|
94
|
+
### Errors
|
|
95
|
+
|
|
96
|
+
- **MissingInputError** — `event` has no `input_presigned_url`.
|
|
97
|
+
- **ValueError** — Input fetch failed (HTTP error, timeout, invalid JSON). Message is descriptive.
|
|
98
|
+
|
|
99
|
+
## Publishing to PyPI
|
|
100
|
+
|
|
101
|
+
From the package directory:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
pip install build twine
|
|
105
|
+
python -m build
|
|
106
|
+
twine upload dist/*
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Use a version bump and tag before each release.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
seekapi
|