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 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.
@@ -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"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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,7 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/seekapi/__init__.py
4
+ src/seekapi.egg-info/PKG-INFO
5
+ src/seekapi.egg-info/SOURCES.txt
6
+ src/seekapi.egg-info/dependency_links.txt
7
+ src/seekapi.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ seekapi