poetry-plugin-ivcap 0.4.1__py3-none-any.whl → 0.5.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.
- poetry_plugin_ivcap/constants.py +3 -0
- poetry_plugin_ivcap/docker.py +3 -1
- poetry_plugin_ivcap/ivcap.py +122 -60
- poetry_plugin_ivcap/plugin.py +2 -1
- {poetry_plugin_ivcap-0.4.1.dist-info → poetry_plugin_ivcap-0.5.0.dist-info}/METADATA +1 -1
- poetry_plugin_ivcap-0.5.0.dist-info/RECORD +11 -0
- poetry_plugin_ivcap-0.4.1.dist-info/RECORD +0 -11
- {poetry_plugin_ivcap-0.4.1.dist-info → poetry_plugin_ivcap-0.5.0.dist-info}/AUTHORS.md +0 -0
- {poetry_plugin_ivcap-0.4.1.dist-info → poetry_plugin_ivcap-0.5.0.dist-info}/LICENSE +0 -0
- {poetry_plugin_ivcap-0.4.1.dist-info → poetry_plugin_ivcap-0.5.0.dist-info}/WHEEL +0 -0
- {poetry_plugin_ivcap-0.4.1.dist-info → poetry_plugin_ivcap-0.5.0.dist-info}/entry_points.txt +0 -0
poetry_plugin_ivcap/constants.py
CHANGED
|
@@ -19,6 +19,7 @@ DOCKER_BUILD_TEMPLATE_OPT = "docker-build-template"
|
|
|
19
19
|
|
|
20
20
|
DEF_POLICY = "urn:ivcap:policy:ivcap.base.metadata"
|
|
21
21
|
DEF_PORT = 8000
|
|
22
|
+
DEF_IVCAP_BASE_URL = "https://develop.ivcap.net"
|
|
22
23
|
|
|
23
24
|
DOCKER_BUILD_TEMPLATE = """
|
|
24
25
|
docker buildx build
|
|
@@ -33,6 +34,7 @@ docker buildx build
|
|
|
33
34
|
DOCKER_LAMBDA_RUN_TEMPLATE = """
|
|
34
35
|
docker run -it
|
|
35
36
|
-p #PORT#:#PORT#
|
|
37
|
+
-e IVCAP_BASE_URL=#IVCAP_BASE_URL#
|
|
36
38
|
--platform=linux/#ARCH#
|
|
37
39
|
--rm \
|
|
38
40
|
#NAME#_#ARCH#:#TAG#
|
|
@@ -40,6 +42,7 @@ DOCKER_LAMBDA_RUN_TEMPLATE = """
|
|
|
40
42
|
|
|
41
43
|
DOCKER_BATCH_RUN_TEMPLATE = """
|
|
42
44
|
docker run -it
|
|
45
|
+
-e IVCAP_BASE_URL=#IVCAP_BASE_URL#
|
|
43
46
|
--platform=linux/#ARCH#
|
|
44
47
|
-v #PROJECT_DIR#:/data
|
|
45
48
|
--rm \
|
poetry_plugin_ivcap/docker.py
CHANGED
|
@@ -12,7 +12,7 @@ from typing import Dict, List, Optional
|
|
|
12
12
|
from pydantic import BaseModel, Field
|
|
13
13
|
import subprocess
|
|
14
14
|
|
|
15
|
-
from .constants import DEF_PORT, DOCKER_BATCH_RUN_TEMPLATE, DOCKER_BUILD_TEMPLATE, DOCKER_BUILD_TEMPLATE_OPT, DOCKER_LAMBDA_RUN_TEMPLATE, DOCKER_RUN_OPT, DOCKER_RUN_TEMPLATE_OPT, PLUGIN_NAME, SERVICE_TYPE_OPT
|
|
15
|
+
from .constants import DEF_IVCAP_BASE_URL, DEF_PORT, DOCKER_BATCH_RUN_TEMPLATE, DOCKER_BUILD_TEMPLATE, DOCKER_BUILD_TEMPLATE_OPT, DOCKER_LAMBDA_RUN_TEMPLATE, DOCKER_RUN_OPT, DOCKER_RUN_TEMPLATE_OPT, PLUGIN_NAME, SERVICE_TYPE_OPT
|
|
16
16
|
from .util import command_exists, get_name, get_version
|
|
17
17
|
|
|
18
18
|
class DockerConfig(BaseModel):
|
|
@@ -66,8 +66,10 @@ class DockerConfig(BaseModel):
|
|
|
66
66
|
port_in_args = False
|
|
67
67
|
port = str(pdata.get("port", DEF_PORT))
|
|
68
68
|
|
|
69
|
+
base_url = os.environ.get("IVCAP_BASE_URL", DEF_IVCAP_BASE_URL)
|
|
69
70
|
t = template.strip()\
|
|
70
71
|
.replace("#DOCKER_NAME#", self.docker_name)\
|
|
72
|
+
.replace("#IVCAP_BASE_URL#", base_url)\
|
|
71
73
|
.replace("#NAME#", self.name)\
|
|
72
74
|
.replace("#TAG#", self.tag)\
|
|
73
75
|
.replace("#PORT#", port)\
|
poetry_plugin_ivcap/ivcap.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
# Use of this source code is governed by a BSD-style license that can be
|
|
4
4
|
# found in the LICENSE file. See the AUTHORS file for names of contributors.
|
|
5
5
|
#
|
|
6
|
+
import argparse
|
|
6
7
|
import os
|
|
7
8
|
import re
|
|
8
9
|
import subprocess
|
|
@@ -12,8 +13,11 @@ import uuid
|
|
|
12
13
|
import humanize
|
|
13
14
|
import subprocess
|
|
14
15
|
import requests
|
|
16
|
+
import time
|
|
17
|
+
import json
|
|
15
18
|
|
|
16
|
-
|
|
19
|
+
|
|
20
|
+
from .constants import DEF_POLICY, PLUGIN_NAME, POLICY_OPT, SERVICE_FILE_OPT, SERVICE_ID_OPT, DEF_IVCAP_BASE_URL
|
|
17
21
|
|
|
18
22
|
from .docker import docker_cfg, docker_build, docker_push
|
|
19
23
|
from .util import command_exists, get_name, string_to_number
|
|
@@ -51,7 +55,9 @@ def service_register(data, line):
|
|
|
51
55
|
|
|
52
56
|
cmd = ["poetry", "run", "python", service, "--print-service-description"]
|
|
53
57
|
line(f"<debug>Running: {' '.join(cmd)} </debug>")
|
|
54
|
-
|
|
58
|
+
env = os.environ.copy()
|
|
59
|
+
env.setdefault("IVCAP_BASE_URL", DEF_IVCAP_BASE_URL)
|
|
60
|
+
svc = subprocess.check_output(cmd, env=env).decode()
|
|
55
61
|
|
|
56
62
|
svc = svc.replace("#DOCKER_IMG#", pkg.strip())\
|
|
57
63
|
.replace("#SERVICE_ID#", service_id)
|
|
@@ -87,7 +93,9 @@ def tool_register(data, line):
|
|
|
87
93
|
|
|
88
94
|
cmd = ["poetry", "run", "python", service, "--print-tool-description"]
|
|
89
95
|
line(f"<debug>Running: {' '.join(cmd)} </debug>")
|
|
90
|
-
|
|
96
|
+
env = os.environ.copy()
|
|
97
|
+
env.setdefault("IVCAP_BASE_URL", DEF_IVCAP_BASE_URL)
|
|
98
|
+
svc = subprocess.check_output(cmd, env=env).decode()
|
|
91
99
|
|
|
92
100
|
service_id = get_service_id(data, False, line)
|
|
93
101
|
svc = svc.replace("#SERVICE_ID#", service_id)
|
|
@@ -139,20 +147,16 @@ def exec_job(data, args, is_silent, line):
|
|
|
139
147
|
requests.Response: The response object from the API call.
|
|
140
148
|
"""
|
|
141
149
|
# Parse 'args' for run options
|
|
150
|
+
p = exec_parser()
|
|
142
151
|
if not isinstance(args, list) or len(args) < 1:
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
except ValueError:
|
|
152
|
-
raise Exception("Timeout value must be an integer")
|
|
153
|
-
else:
|
|
154
|
-
raise Exception("args must be [file_name] or [file_name, '--timeout', value]")
|
|
155
|
-
|
|
152
|
+
p.error("missing request file name")
|
|
153
|
+
# raise Exception("args must be a list with at least one element")
|
|
154
|
+
file_name = args.pop(0)
|
|
155
|
+
if file_name == "-h" or file_name == "--help":
|
|
156
|
+
p.print_help()
|
|
157
|
+
return
|
|
158
|
+
pa = p.parse_args(args)
|
|
159
|
+
timeout = 0 if pa.stream else pa.timeout
|
|
156
160
|
# Get access token using ivcap CLI
|
|
157
161
|
try:
|
|
158
162
|
token = subprocess.check_output(
|
|
@@ -198,29 +202,6 @@ def exec_job(data, args, is_silent, line):
|
|
|
198
202
|
except Exception as e:
|
|
199
203
|
raise RuntimeError(f"Job submission failed: {e}")
|
|
200
204
|
|
|
201
|
-
# Handle response according to requirements
|
|
202
|
-
import time
|
|
203
|
-
import json
|
|
204
|
-
|
|
205
|
-
def handle_response(resp):
|
|
206
|
-
content_type = resp.headers.get("content-type", "")
|
|
207
|
-
if resp.status_code >= 300:
|
|
208
|
-
line(f"<warning>WARNING: Received status code {resp.status_code}</warning>")
|
|
209
|
-
line(f"<info>Headers: {str(resp.headers)}</info>")
|
|
210
|
-
line(f"<info>Body: {str(resp.text)}</info>")
|
|
211
|
-
elif resp.status_code == 200:
|
|
212
|
-
if "application/json" in content_type:
|
|
213
|
-
try:
|
|
214
|
-
parsed = resp.json()
|
|
215
|
-
print(json.dumps(parsed, indent=2, sort_keys=True))
|
|
216
|
-
except Exception as e:
|
|
217
|
-
line(f"<warning>Failed to parse JSON response: {e}</warning>")
|
|
218
|
-
line(f"<warning>Headers: {str(resp.headers)}</warning>")
|
|
219
|
-
else:
|
|
220
|
-
line(f"<info>Headers: {str(resp.headers)}</info>")
|
|
221
|
-
else:
|
|
222
|
-
line(f"<warning>Received status code {resp.status_code}</warning>")
|
|
223
|
-
line(f"<warning>Headers: {str(resp.headers)}</warning>")
|
|
224
205
|
|
|
225
206
|
if response.status_code == 202:
|
|
226
207
|
try:
|
|
@@ -229,32 +210,113 @@ def exec_job(data, args, is_silent, line):
|
|
|
229
210
|
# location = f"{payload.get('location')}/output"
|
|
230
211
|
location = f"{payload.get('location')}"
|
|
231
212
|
job_id = payload.get("job-id")
|
|
232
|
-
retry_later = payload.get("retry-later",
|
|
213
|
+
retry_later = payload.get("retry-later", 5)
|
|
233
214
|
if not is_silent:
|
|
234
215
|
line(f"<debug>Job '{job_id}' accepted, but no result yet. Polling in {retry_later} seconds.</debug>")
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
poll_resp = requests.get(location, headers=poll_headers)
|
|
241
|
-
if poll_resp.status_code == 202:
|
|
242
|
-
try:
|
|
243
|
-
poll_payload = poll_resp.json()
|
|
244
|
-
location = poll_payload.get("location", location)
|
|
245
|
-
retry_later = poll_payload.get("retry-later", retry_later)
|
|
246
|
-
if not is_silent:
|
|
247
|
-
line(f"<debug>Still processing. Next poll in {retry_later} seconds.</debug>")
|
|
248
|
-
except Exception as e:
|
|
249
|
-
line(f"<error>Failed to parse polling response: {e}</error>")
|
|
250
|
-
break
|
|
251
|
-
else:
|
|
252
|
-
handle_response(poll_resp)
|
|
253
|
-
break
|
|
216
|
+
if pa.stream:
|
|
217
|
+
stream_result(location, token, pa)
|
|
218
|
+
else:
|
|
219
|
+
poll_for_result(location, retry_later, token, is_silent, line)
|
|
220
|
+
|
|
254
221
|
except Exception as e:
|
|
255
222
|
line(f"<error>Failed to handle 202 response: {e}</error>")
|
|
256
223
|
else:
|
|
257
|
-
handle_response(response)
|
|
224
|
+
handle_response(response, line)
|
|
225
|
+
|
|
226
|
+
def handle_response(resp, line):
|
|
227
|
+
content_type = resp.headers.get("content-type", "")
|
|
228
|
+
if resp.status_code >= 300:
|
|
229
|
+
line(f"<warning>WARNING: Received status code {resp.status_code}</warning>")
|
|
230
|
+
line(f"<info>Headers: {str(resp.headers)}</info>")
|
|
231
|
+
line(f"<info>Body: {str(resp.text)}</info>")
|
|
232
|
+
elif resp.status_code == 200:
|
|
233
|
+
if "application/json" in content_type:
|
|
234
|
+
try:
|
|
235
|
+
parsed = resp.json()
|
|
236
|
+
status = parsed.get("status")
|
|
237
|
+
if status and (not status in ["succeeded", "failed", "error"]):
|
|
238
|
+
return status
|
|
239
|
+
print(json.dumps(parsed, indent=2, sort_keys=True))
|
|
240
|
+
return None
|
|
241
|
+
except Exception as e:
|
|
242
|
+
line(f"<warning>Failed to parse JSON response: {e}</warning>")
|
|
243
|
+
line(f"<warning>Headers: {str(resp.headers)}</warning>")
|
|
244
|
+
else:
|
|
245
|
+
line(f"<info>Headers: {str(resp.headers)}</info>")
|
|
246
|
+
else:
|
|
247
|
+
line(f"<warning>Received status code {resp.status_code}</warning>")
|
|
248
|
+
line(f"<warning>Headers: {str(resp.headers)}</warning>")
|
|
249
|
+
return "unknown"
|
|
250
|
+
|
|
251
|
+
def poll_for_result(location, retry_later, token, is_silent, line):
|
|
252
|
+
while True:
|
|
253
|
+
time.sleep(retry_later)
|
|
254
|
+
poll_headers = {
|
|
255
|
+
"Authorization": f"Bearer {token}"
|
|
256
|
+
}
|
|
257
|
+
poll_resp = requests.get(location, headers=poll_headers)
|
|
258
|
+
if poll_resp.status_code == 202:
|
|
259
|
+
try:
|
|
260
|
+
poll_payload = poll_resp.json()
|
|
261
|
+
location = poll_payload.get("location", location)
|
|
262
|
+
retry_later = poll_payload.get("retry-later", retry_later)
|
|
263
|
+
if not is_silent:
|
|
264
|
+
line(f"<debug>Still processing. Next poll in {retry_later} seconds.</debug>")
|
|
265
|
+
except Exception as e:
|
|
266
|
+
line(f"<error>Failed to parse polling response: {e}</error>")
|
|
267
|
+
break
|
|
268
|
+
else:
|
|
269
|
+
status = handle_response(poll_resp, line)
|
|
270
|
+
if status:
|
|
271
|
+
if not is_silent:
|
|
272
|
+
line(f"<debug>Status: '{status}'. Next poll in {retry_later} seconds.</debug>")
|
|
273
|
+
else:
|
|
274
|
+
break
|
|
275
|
+
|
|
276
|
+
def stream_result(location, token, pa):
|
|
277
|
+
"""
|
|
278
|
+
Stream the result content from the given location using the provided token.
|
|
279
|
+
"""
|
|
280
|
+
headers = {
|
|
281
|
+
"Authorization": f"Bearer {token}",
|
|
282
|
+
"Accept": "text/event-stream"
|
|
283
|
+
}
|
|
284
|
+
with requests.get(location + "/events", stream=True, headers=headers, timeout=(5, 65)) as r:
|
|
285
|
+
r.raise_for_status()
|
|
286
|
+
for row in r.iter_lines(decode_unicode=True, chunk_size=1):
|
|
287
|
+
if row is None:
|
|
288
|
+
continue
|
|
289
|
+
if row.startswith(":"):
|
|
290
|
+
# comment/heartbeat
|
|
291
|
+
continue
|
|
292
|
+
print_sse_row(row, pa) # raw SSE lines (e.g., "data: {...}", "event: message")
|
|
293
|
+
|
|
294
|
+
def print_sse_row(row, pa):
|
|
295
|
+
if pa.raw_events:
|
|
296
|
+
print(row)
|
|
297
|
+
elif row.startswith("data: "):
|
|
298
|
+
# JSON data
|
|
299
|
+
print("----")
|
|
300
|
+
try:
|
|
301
|
+
data = json.loads(row[6:])
|
|
302
|
+
print(json.dumps(data, indent=2, sort_keys=True))
|
|
303
|
+
except json.JSONDecodeError as e:
|
|
304
|
+
print(f"Failed to decode JSON: {e}")
|
|
305
|
+
|
|
306
|
+
def exec_parser():
|
|
307
|
+
p = argparse.ArgumentParser(prog="poetry ivcap job-exec request_file --")
|
|
308
|
+
p.add_argument("--timeout", type=nonneg_int, default=5, help="[5] seconds; 0 allowed")
|
|
309
|
+
p.add_argument("--with-result-content", dest="with_result_content", action="store_true",
|
|
310
|
+
help="include result content in the response")
|
|
311
|
+
p.add_argument("--stream", action="store_true", help="stream the result content")
|
|
312
|
+
p.add_argument("--raw-events", action="store_true", help="print raw SSE events")
|
|
313
|
+
return p
|
|
314
|
+
|
|
315
|
+
def nonneg_int(s: str) -> int:
|
|
316
|
+
v = int(s)
|
|
317
|
+
if v < 0:
|
|
318
|
+
raise argparse.ArgumentTypeError("timeout must be >= 0")
|
|
319
|
+
return v
|
|
258
320
|
|
|
259
321
|
def get_account_id(data, line, is_silent=False):
|
|
260
322
|
check_ivcap_cmd(line)
|
poetry_plugin_ivcap/plugin.py
CHANGED
|
@@ -10,7 +10,7 @@ from cleo.helpers import argument, option
|
|
|
10
10
|
import subprocess
|
|
11
11
|
from importlib.metadata import version
|
|
12
12
|
|
|
13
|
-
from poetry_plugin_ivcap.constants import DOCKER_BUILD_TEMPLATE_OPT, DOCKER_RUN_TEMPLATE_OPT, PLUGIN_CMD, PLUGIN_NAME
|
|
13
|
+
from poetry_plugin_ivcap.constants import DEF_IVCAP_BASE_URL, DOCKER_BUILD_TEMPLATE_OPT, DOCKER_RUN_TEMPLATE_OPT, PLUGIN_CMD, PLUGIN_NAME
|
|
14
14
|
from poetry_plugin_ivcap.constants import PORT_OPT, SERVICE_FILE_OPT, SERVICE_ID_OPT, SERVICE_TYPE_OPT, POLICY_OPT
|
|
15
15
|
from poetry_plugin_ivcap.util import get_version
|
|
16
16
|
|
|
@@ -127,6 +127,7 @@ Configurable options in pyproject.toml:
|
|
|
127
127
|
|
|
128
128
|
env = os.environ.copy()
|
|
129
129
|
env["VERSION"] = get_version(data, None, line)
|
|
130
|
+
env.setdefault("IVCAP_BASE_URL", DEF_IVCAP_BASE_URL)
|
|
130
131
|
|
|
131
132
|
cmd = ["poetry", "run", "python", service]
|
|
132
133
|
cmd.extend(args)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
poetry_plugin_ivcap/constants.py,sha256=zsUnw-cS3uTqRlkAsWPYBeMpn5Wpz7r4ByjYaFZQFUo,1318
|
|
2
|
+
poetry_plugin_ivcap/docker.py,sha256=Pny5uF7juKy8Jld7D-5euAtgzguDZ8E2SRn-romk-Sg,7409
|
|
3
|
+
poetry_plugin_ivcap/ivcap.py,sha256=tXu3vj7FOTrE4Jc9RFoLxOnNdBT5NAkyTB-_lGMgaZI,12804
|
|
4
|
+
poetry_plugin_ivcap/plugin.py,sha256=NWYdqjvlw3cqg4DVcEcZLGAVuL1aa9o0GmQGNZ6wfbI,5506
|
|
5
|
+
poetry_plugin_ivcap/util.py,sha256=bcjjbKoV_pAgeuC7Ws9XbJa3phFpNVyrRAlFJ1VubRg,3429
|
|
6
|
+
poetry_plugin_ivcap-0.5.0.dist-info/AUTHORS.md,sha256=s9xR4_HAHQgbNlj505LViebt5AtACQmhPf92aJvNYgg,88
|
|
7
|
+
poetry_plugin_ivcap-0.5.0.dist-info/LICENSE,sha256=dsQrDPPwW7iJs9pxahgJKDW8RNPf5FyXG70MFUlxcuk,1587
|
|
8
|
+
poetry_plugin_ivcap-0.5.0.dist-info/METADATA,sha256=HMskOhMdt6JyvVzTycjXqm32sOOxJUbTMeEnKPLICnc,3032
|
|
9
|
+
poetry_plugin_ivcap-0.5.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
10
|
+
poetry_plugin_ivcap-0.5.0.dist-info/entry_points.txt,sha256=3xagEFBkGgrVe8WyjmhlHLr4JDEWPN_W4DwxnIBWbNY,74
|
|
11
|
+
poetry_plugin_ivcap-0.5.0.dist-info/RECORD,,
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
poetry_plugin_ivcap/constants.py,sha256=lW5K-iNdlYMXTdxRWlSQOfbqbU8PHfZ066JMcsM_jr0,1183
|
|
2
|
-
poetry_plugin_ivcap/docker.py,sha256=L4HrUP94TzIfM5t1vwu6y7grmT8mrYM4sAFq9aHhvBk,7265
|
|
3
|
-
poetry_plugin_ivcap/ivcap.py,sha256=YjVAKRU6OkGqLHpjE2xsIPq0VWrMcQK8XuHcVe5gCzU,10656
|
|
4
|
-
poetry_plugin_ivcap/plugin.py,sha256=-9IXRVQpyvV8KS3myUiKjqfCqtupmh4oRrz0Xw-KrNw,5425
|
|
5
|
-
poetry_plugin_ivcap/util.py,sha256=bcjjbKoV_pAgeuC7Ws9XbJa3phFpNVyrRAlFJ1VubRg,3429
|
|
6
|
-
poetry_plugin_ivcap-0.4.1.dist-info/AUTHORS.md,sha256=s9xR4_HAHQgbNlj505LViebt5AtACQmhPf92aJvNYgg,88
|
|
7
|
-
poetry_plugin_ivcap-0.4.1.dist-info/LICENSE,sha256=dsQrDPPwW7iJs9pxahgJKDW8RNPf5FyXG70MFUlxcuk,1587
|
|
8
|
-
poetry_plugin_ivcap-0.4.1.dist-info/METADATA,sha256=3QncVKfIAbcEBtmdDiafElrzuYPz2Lwe5_LGLOekAAQ,3032
|
|
9
|
-
poetry_plugin_ivcap-0.4.1.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
10
|
-
poetry_plugin_ivcap-0.4.1.dist-info/entry_points.txt,sha256=3xagEFBkGgrVe8WyjmhlHLr4JDEWPN_W4DwxnIBWbNY,74
|
|
11
|
-
poetry_plugin_ivcap-0.4.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{poetry_plugin_ivcap-0.4.1.dist-info → poetry_plugin_ivcap-0.5.0.dist-info}/entry_points.txt
RENAMED
|
File without changes
|