poetry-plugin-ivcap 0.1.0__py3-none-any.whl → 0.2.2__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.
@@ -3,15 +3,16 @@
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
+ from datetime import datetime, timezone
6
7
  import os
7
8
  import re
8
9
  import sys
9
10
  import tempfile
10
- from typing import Dict, Optional
11
+ from typing import Dict, List, Optional
11
12
  from pydantic import BaseModel, Field
12
13
  import subprocess
13
14
 
14
- from .util import command_exists, get_name
15
+ from .util import command_exists, get_name, get_version
15
16
 
16
17
  DOCKER_BUILD_TEMPLATE = """
17
18
  docker buildx build
@@ -23,12 +24,20 @@ docker buildx build
23
24
  --load #PROJECT_DIR#
24
25
  """
25
26
 
26
- DOCKER_RUN_TEMPLATE = """
27
+ DOCKER_LAMBDA_RUN_TEMPLATE = """
27
28
  docker run -it
28
29
  -p #PORT#:#PORT#
29
30
  --platform=linux/#ARCH#
30
31
  --rm \
31
- #NAME#_#ARCH#:#TAG# --port #PORT#
32
+ #NAME#_#ARCH#:#TAG#
33
+ """
34
+
35
+ DOCKER_BATCH_RUN_TEMPLATE = """
36
+ docker run -it
37
+ --platform=linux/#ARCH#
38
+ -v #PROJECT_DIR#:/data
39
+ --rm \
40
+ #NAME#_#ARCH#:#TAG#
32
41
  """
33
42
 
34
43
  class DockerConfig(BaseModel):
@@ -37,7 +46,7 @@ class DockerConfig(BaseModel):
37
46
  arch: Optional[str] = Field(None)
38
47
  version: Optional[str] = Field(None)
39
48
  dockerfile: Optional[str] = Field("Dockerfile")
40
- project_dir: Optional[str] = Field(".")
49
+ project_dir: Optional[str] = Field(os.getcwd())
41
50
 
42
51
  @property
43
52
  def docker_name(self) -> str:
@@ -57,26 +66,61 @@ class DockerConfig(BaseModel):
57
66
  .split()
58
67
  return t
59
68
 
60
- def from_run_template(self, data) -> str:
69
+ def from_run_template(self, data, args, line) -> List[any]:
61
70
  pdata = data.get("tool", {}).get("poetry-plugin-ivcap", {})
62
- template = pdata.get("docker-run-template", DOCKER_RUN_TEMPLATE).strip()
63
- opts = pdata.get("docker-run-opts", {"port": 8080})
71
+ template = pdata.get("docker-run-template")
72
+ if template is None:
73
+ smode = pdata.get("service-type")
74
+ if smode is None:
75
+ line("<error>ERROR: 'service-type' is not defined in [poetry-plugin-ivcap]</error>")
76
+ sys.exit(1)
77
+ if smode == "lambda":
78
+ template = DOCKER_LAMBDA_RUN_TEMPLATE
79
+ elif smode == "batch":
80
+ template = DOCKER_BATCH_RUN_TEMPLATE
81
+ else:
82
+ line(f"<error>ERROR: Unknown service type '{smode}' in [poetry-plugin-ivcap]</error>")
83
+ sys.exit(1)
84
+
85
+ opts = pdata.get("docker-run-opts", [])
86
+ port = get_port_value(args)
87
+ port_in_args = True
88
+ if not port:
89
+ port = get_port_value(opts)
90
+ if not port:
91
+ port_in_args = False
92
+ port = str(pdata.get("port", 8000))
93
+
94
+ # elif opts.get('port') is not None:
95
+ # opts['port'] = opts.get('port')
96
+ # elif '--port' in template:
97
+ # # If the template has a port but no port is provided, use a default
98
+ # opts['port'] = '8000'
99
+
64
100
  t = template.strip()\
65
101
  .replace("#NAME#", self.name)\
66
102
  .replace("#TAG#", self.tag)\
103
+ .replace("#PORT#", port)\
67
104
  .replace("#ARCH#", self.arch)\
68
105
  .replace("#VERSION#", self.version)\
69
106
  .replace("#PROJECT_DIR#", self.project_dir)
70
107
 
71
- for key, value in opts.items():
72
- t = t.replace(f"#{key.upper()}#", str(value))
108
+ # for key, value in opts.items():
109
+ # if key != "port":
110
+ # t = t.replace(f"#{key.upper()}#", str(value))
73
111
 
74
- return t.split()
112
+
113
+ cmd = t.split()
114
+ cmd.extend(opts)
115
+ cmd.extend(args)
116
+ if smode == "lambda" and not port_in_args:
117
+ cmd.extend(["--port", port])
118
+ return cmd
75
119
 
76
120
 
77
121
  def docker_build(data: dict, line, arch = None) -> None:
78
122
  check_docker_cmd(line)
79
- config = _docker_cfg(data, line, arch)
123
+ config = docker_cfg(data, line, arch)
80
124
  build_cmd = config.from_build_template(data)
81
125
  line(f"<info>INFO: {' '.join(build_cmd)}</info>")
82
126
  process = subprocess.Popen(build_cmd, stdout=sys.stdout, stderr=sys.stderr)
@@ -87,10 +131,10 @@ def docker_build(data: dict, line, arch = None) -> None:
87
131
  line("<info>INFO: Docker build completed successfully</info>")
88
132
  return config.docker_name
89
133
 
90
- def docker_run(data: dict, line) -> None:
134
+ def docker_run(data: dict, args, line) -> None:
91
135
  check_docker_cmd(line)
92
- config = _docker_cfg(data, line)
93
- build_run = config.from_run_template(data)
136
+ config = docker_cfg(data, line)
137
+ build_run = config.from_run_template(data, args, line)
94
138
  line(f"<info>INFO: {' '.join(build_run)}</info>")
95
139
  process = subprocess.Popen(build_run, stdout=sys.stdout, stderr=sys.stderr)
96
140
  exit_code = process.wait()
@@ -99,24 +143,15 @@ def docker_run(data: dict, line) -> None:
99
143
  else:
100
144
  line("<info>INFO: Docker run completed successfully</info>")
101
145
 
102
- def _docker_cfg(data: dict, line, arch = None) -> DockerConfig:
103
- project_data = data.get("project", {})
146
+ def docker_cfg(data: dict, line, arch = None) -> DockerConfig:
104
147
  name = get_name(data)
105
- version = project_data.get("version", None)
106
148
 
107
149
  pdata = data.get("tool", {}).get("poetry-plugin-ivcap", {})
108
- config = DockerConfig(name=name, version=version, **pdata.get("docker", {}))
150
+ config = DockerConfig(name=name, **pdata.get("docker", {}))
109
151
  if arch:
110
152
  # override architecture if provided
111
153
  config.arch = arch
112
154
 
113
- if not config.version:
114
- try:
115
- config.version = subprocess.check_output(['git', 'describe', '--tags', '--abbrev=0']).decode().strip()
116
- except Exception as e:
117
- line(f"<error>Error retrieving latest tag: {e}</error>")
118
- config.version = "???"
119
-
120
155
  if not config.tag:
121
156
  try:
122
157
  config.tag = subprocess.check_output(['git', 'rev-parse', "--short", 'HEAD']).decode().strip()
@@ -124,6 +159,9 @@ def _docker_cfg(data: dict, line, arch = None) -> DockerConfig:
124
159
  line(f"<warning>WARN: retrieving commit hash: {e}</warning>")
125
160
  config.tag = "latest"
126
161
 
162
+ if not config.version:
163
+ config.version = get_version(data, config.tag, line)
164
+
127
165
  if not config.arch:
128
166
  try:
129
167
  config.arch = subprocess.check_output(['uname', '-m']).decode().strip()
@@ -183,3 +221,12 @@ def check_docker_cmd(line):
183
221
  if not command_exists("docker"):
184
222
  line("<error>'docker' command not found. Please install it first.</error>")
185
223
  sys.exit(1)
224
+
225
+ def get_port_value(arr):
226
+ try:
227
+ index = arr.index('--port')
228
+ return arr[index + 1]
229
+ except ValueError:
230
+ return None
231
+ except IndexError:
232
+ return None
@@ -11,14 +11,12 @@ import tempfile
11
11
  import uuid
12
12
  import humanize
13
13
 
14
- from .docker import _docker_cfg, docker_build, docker_push
14
+ from .docker import docker_cfg, docker_build, docker_push
15
15
  from .util import command_exists, execute_subprocess_and_capture_output, get_name, string_to_number
16
16
 
17
17
  def docker_publish(data, line):
18
18
  check_ivcap_cmd(line)
19
- #dname = docker_build(data, line, arch="amd64")
20
- dname = "gene_onology_term_mapper_amd64:9a9a7cc"
21
- print(dname)
19
+ dname = docker_build(data, line, arch="amd64")
22
20
 
23
21
  size_cmd = ["docker", "inspect", "--format='{{.Size}}'", dname]
24
22
  line(f"<debug>Running: {' '.join(size_cmd)} </debug>")
@@ -38,7 +36,7 @@ def service_register(data, line):
38
36
  line("<error>Missing 'service-file' in [tool.poetry-plugin-ivcap]</error>")
39
37
  return
40
38
 
41
- dcfg = _docker_cfg(data, line, "amd64")
39
+ dcfg = docker_cfg(data, line, "amd64")
42
40
  pkg_cmd = ["ivcap", "package", "list", dcfg.docker_name]
43
41
  line(f"<debug>Running: {' '.join(pkg_cmd)} </debug>")
44
42
  pkg = subprocess.check_output(pkg_cmd).decode()
@@ -3,11 +3,14 @@
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 os
6
7
  from poetry.plugins.application_plugin import ApplicationPlugin
7
8
  from cleo.commands.command import Command
8
9
  from cleo.helpers import argument
9
10
  import subprocess
10
11
 
12
+ from poetry_plugin_ivcap.util import get_version
13
+
11
14
  from .ivcap import create_service_id, service_register, tool_register
12
15
  from .docker import docker_build, docker_run
13
16
  from .ivcap import docker_publish
@@ -16,30 +19,54 @@ class IvcapCommand(Command):
16
19
  name = "ivcap"
17
20
  description = "IVCAP plugin `poetry ivcap <subcommand>`"
18
21
  help = """\
22
+
19
23
  IVCAP plugin
20
24
 
25
+ Supporting the development of services and tools for the IVCAP platform
26
+
21
27
  Available subcommands:
22
- run Run the configured service
23
- deploy Deploy using configured settings
28
+ run Run the service locally
29
+ docker-build Build the docker image for this service
30
+ docker-run Run the service's docker image locally
31
+ docker-publish Publish the service's docker image to IVCAP
32
+ service-register Register the service with IVCAP
33
+ create-service-id Create a unique service ID for the service
34
+ tool-register Register the service as an AI Tool with IVCAP
24
35
 
25
36
  Example:
26
- poetry ivcap run
37
+ poetry ivcap run -- --port 8080
38
+
39
+ Configurable options in pyproject.toml:
40
+
41
+ [tool.poetry-plugin-ivcap]
42
+ service-file = "service.py" # The Python file that implements the service
43
+ service-file = "service.py"
44
+ service-id = "urn:ivcap:service:ac158a1f-dfb4-5dac-bf2e-9bf15e0f2cc7" # A unique identifier for the service
45
+
46
+ docker-build-template = "docker buildx build -t #DOCKER_NAME# ."
47
+ docker-run-template = "docker run -rm -p #PORT#:#PORT#"
27
48
  """
28
49
  arguments = [
29
- argument("subcommand", optional=True, description="Subcommand: run, deploy, etc.")
50
+ argument("subcommand", optional=True, description="Subcommand: run, deploy, etc."),
51
+ argument("args", optional=True, multiple=True, description="Additional arguments for subcommand"),
30
52
  ]
31
53
 
54
+ allow_extra_args = True
55
+ ignore_validation_errors = True
56
+
32
57
  def handle(self):
33
58
  poetry = self.application.poetry
34
59
  data = poetry.pyproject.data
35
60
 
36
61
  sub = self.argument("subcommand")
62
+ args = self.argument("args")
63
+
37
64
  if sub == "run":
38
- self.run_service(data)
65
+ self.run_service(data, args, self.line)
39
66
  elif sub == "docker-build":
40
67
  docker_build(data, self.line)
41
68
  elif sub == "docker-run":
42
- docker_run(data, self.line)
69
+ docker_run(data, args, self.line)
43
70
  elif sub == "docker-publish":
44
71
  docker_publish(data, self.line)
45
72
  elif sub == "service-register":
@@ -50,20 +77,30 @@ Example:
50
77
  elif sub == "tool-register":
51
78
  tool_register(data, self.line)
52
79
  else:
53
- self.line(f"<error>Unknown subcommand: {sub}</error>")
80
+ if not (sub == None or sub == "help"):
81
+ self.line(f"<error>Unknown subcommand: {sub}</error>")
82
+ print(self.help)
54
83
 
55
- def run_service(self, data):
84
+ def run_service(self, data, args, line):
56
85
  config = data.get("tool", {}).get("poetry-plugin-ivcap", {})
57
86
 
58
87
  service = config.get("service-file")
59
- port = config.get("port")
60
-
61
- if not service or not port:
62
- self.line("<error>Missing 'service-file' or 'port' in [tool.poetry-plugin-ivcap]</error>")
88
+ if not service:
89
+ self.line("<error>Missing 'service-file' in [tool.poetry-plugin-ivcap]</error>")
63
90
  return
64
91
 
65
- self.line(f"<info>Running: python {service} --port {port}</info>")
66
- subprocess.run(["poetry", "run", "python", service, "--port", str(port)])
92
+ if not '--port' in args:
93
+ port = config.get("port")
94
+ if port:
95
+ args.extend(["--port", str(port)])
96
+
97
+ env = os.environ.copy()
98
+ env["VERSION"] = get_version(data, None, line)
99
+
100
+ cmd = ["poetry", "run", "python", service]
101
+ cmd.extend(args)
102
+ self.line(f"<info>Running: {' '.join(cmd)}</info>")
103
+ subprocess.run(cmd, env=env)
67
104
 
68
105
  class IvcapPlugin(ApplicationPlugin):
69
106
  def activate(self, application):
@@ -5,6 +5,7 @@
5
5
  #
6
6
  import subprocess
7
7
  import platform
8
+ from datetime import datetime, timezone
8
9
 
9
10
  def get_name(data) -> str:
10
11
  project_data = data.get("project", {})
@@ -85,3 +86,23 @@ def execute_subprocess_and_capture_output(command):
85
86
  full_output += line
86
87
 
87
88
  return full_output
89
+
90
+ def get_version(data, tag, line):
91
+ if not tag:
92
+ try:
93
+ tag = subprocess.check_output(['git', 'rev-parse', "--short", 'HEAD']).decode().strip()
94
+ except Exception as e:
95
+ line(f"<warning>WARN: retrieving commit hash: {e}</warning>")
96
+ tag = "latest"
97
+
98
+ project_data = data.get("project", {})
99
+ v = project_data.get("version", None)
100
+ if not v:
101
+ try:
102
+ v = subprocess.check_output(['git', 'describe', '--tags', '--abbrev=0']).decode().strip()
103
+ except Exception as e:
104
+ line(f"<error>Error retrieving latest tag: {e}</error>")
105
+ v = "???"
106
+ now = datetime.now(timezone.utc).astimezone()
107
+ version = f"{v}|{tag}|{now.replace(microsecond=0).isoformat()}"
108
+ return version
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: poetry-plugin-ivcap
3
- Version: 0.1.0
3
+ Version: 0.2.2
4
4
  Summary: A custom Poetry command for IVCAP deployments
5
5
  License: MIT
6
6
  Author: Max Ott
@@ -27,9 +27,11 @@ Add to your `pyproject.toml`:
27
27
 
28
28
  ```toml
29
29
  [tool.poetry-plugin-ivcap]
30
- default_target = "docker"
31
- docker_tag = "myapp:dev"
32
- ```
30
+ service-file = "service.py"
31
+ service-id = "urn:ivcap:service:ac158a1f-dfb4-5dac-bf2e-9bf15e0f2cc7"
32
+ port = 8077
33
+ # docker
34
+ # docker-run-opts = { port = 8079 }```
33
35
 
34
36
  ## Installation
35
37
 
@@ -40,8 +42,44 @@ poetry self add poetry-plugin-ivcap
40
42
  ## Usage
41
43
 
42
44
  ```bash
43
- poetry ivcap
44
- poetry ivcap docker
45
+ poetry ivcap run
46
+ poetry ivcap docker-build
47
+ poetry ivcap docker-run
48
+ poetry ivcap docker-publish
49
+ poetry ivcap service-register
50
+ poetry ivcap create-service-id
51
+ poetry ivcap tool-register
52
+ ```
53
+
54
+ To get help on the currently installed version
55
+ ```
56
+ % poetry ivcap
57
+
58
+ IVCAP plugin
59
+
60
+ Supporting the development of services and tools for the IVCAP platform
61
+
62
+ Available subcommands:
63
+ run Run the service locally
64
+ docker-build Build the docker image for this service
65
+ docker-run Run the service's docker image locally
66
+ docker-publish Publish the service's docker image to IVCAP
67
+ service-register Register the service with IVCAP
68
+ create-service-id Create a unique service ID for the service
69
+ tool-register Register the service as an AI Tool with IVCAP
70
+
71
+ Example:
72
+ poetry ivcap run
73
+
74
+ Configurable optiosn in pyproject.toml:
75
+
76
+ [tool.poetry-plugin-ivcap]
77
+ service-file = "service.py" # The Python file that implements the service
78
+ service-file = "service.py"
79
+ service-id = "urn:ivcap:service:ac158a1f-dfb4-5dac-bf2e-9bf15e0f2cc7" # A unique identifier for the service
80
+
81
+ docker-build-template = "docker buildx build -t #DOCKER_NAME# ."
82
+ docker-run-template = "docker run -rm -p #PORT#:#PORT#"
45
83
  ```
46
84
 
47
85
  ## Development
@@ -0,0 +1,10 @@
1
+ poetry_plugin_ivcap/docker.py,sha256=9frC-F5S3p3vP-4mrLSlqvtx9ayNZ-xZXsMkWAtg4y4,7952
2
+ poetry_plugin_ivcap/ivcap.py,sha256=5s9y6U-nhdmTh-Xeek8l_oCJvnufYa485GAlr4GE_Xs,5624
3
+ poetry_plugin_ivcap/plugin.py,sha256=v_lW-G4FE1l2SmKt_Xfx-mrwxabWhWjsKsbvGS0h7HY,3869
4
+ poetry_plugin_ivcap/util.py,sha256=bcjjbKoV_pAgeuC7Ws9XbJa3phFpNVyrRAlFJ1VubRg,3429
5
+ poetry_plugin_ivcap-0.2.2.dist-info/AUTHORS.md,sha256=s9xR4_HAHQgbNlj505LViebt5AtACQmhPf92aJvNYgg,88
6
+ poetry_plugin_ivcap-0.2.2.dist-info/LICENSE,sha256=dsQrDPPwW7iJs9pxahgJKDW8RNPf5FyXG70MFUlxcuk,1587
7
+ poetry_plugin_ivcap-0.2.2.dist-info/METADATA,sha256=iV3A31qJW2WrwOpX0MGT87gLJuVUY9vxTxNJRRmjEKw,3034
8
+ poetry_plugin_ivcap-0.2.2.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
9
+ poetry_plugin_ivcap-0.2.2.dist-info/entry_points.txt,sha256=3xagEFBkGgrVe8WyjmhlHLr4JDEWPN_W4DwxnIBWbNY,74
10
+ poetry_plugin_ivcap-0.2.2.dist-info/RECORD,,
@@ -1,10 +0,0 @@
1
- poetry_plugin_ivcap/docker.py,sha256=ePxs6RdDm0znmGpnybd1O-N9Ef1WjUX5eqRV0xf7pKk,6609
2
- poetry_plugin_ivcap/ivcap.py,sha256=wXy1IGiOiNA8r9tJIE0_hhamQn6J6pXzc0Zc33euO3k,5697
3
- poetry_plugin_ivcap/plugin.py,sha256=cOXF7ttBI883LC2fj7AUeDUF6w-tYI9l9Fi3FB23B-g,2432
4
- poetry_plugin_ivcap/util.py,sha256=cAfofL50KZAcaghkDuizGMoW8qx3hkH3A0h9S9DraZc,2620
5
- poetry_plugin_ivcap-0.1.0.dist-info/AUTHORS.md,sha256=s9xR4_HAHQgbNlj505LViebt5AtACQmhPf92aJvNYgg,88
6
- poetry_plugin_ivcap-0.1.0.dist-info/LICENSE,sha256=dsQrDPPwW7iJs9pxahgJKDW8RNPf5FyXG70MFUlxcuk,1587
7
- poetry_plugin_ivcap-0.1.0.dist-info/METADATA,sha256=qAsv8_FAyzJn2726zSRRniUmOWlgcywv21rFilKsqcY,1719
8
- poetry_plugin_ivcap-0.1.0.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
9
- poetry_plugin_ivcap-0.1.0.dist-info/entry_points.txt,sha256=3xagEFBkGgrVe8WyjmhlHLr4JDEWPN_W4DwxnIBWbNY,74
10
- poetry_plugin_ivcap-0.1.0.dist-info/RECORD,,