arkitekt-next 0.8.60__py3-none-any.whl → 0.8.62__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.

Potentially problematic release.


This version of arkitekt-next might be problematic. Click here for more details.

Files changed (40) hide show
  1. arkitekt_next/__blok__.py +3 -0
  2. arkitekt_next/__init__.py +5 -1
  3. arkitekt_next/base_models.py +2 -4
  4. arkitekt_next/bloks/arkitekt.py +15 -13
  5. arkitekt_next/bloks/base.py +6 -4
  6. arkitekt_next/bloks/gateway.py +10 -6
  7. arkitekt_next/bloks/kraph.py +4 -1
  8. arkitekt_next/bloks/local_sign.py +261 -0
  9. arkitekt_next/bloks/lok.py +5 -3
  10. arkitekt_next/bloks/ollama.py +1 -4
  11. arkitekt_next/bloks/orkestrator.py +4 -6
  12. arkitekt_next/bloks/self_signed.py +1 -8
  13. arkitekt_next/bloks/services/certer.py +2 -2
  14. arkitekt_next/cli/commands/gen/init.py +2 -2
  15. arkitekt_next/cli/commands/inspect/templates.py +0 -2
  16. arkitekt_next/cli/commands/kabinet/build.py +28 -9
  17. arkitekt_next/cli/commands/kabinet/init.py +1 -1
  18. arkitekt_next/cli/commands/kabinet/io.py +206 -0
  19. arkitekt_next/cli/commands/kabinet/main.py +27 -15
  20. arkitekt_next/cli/commands/kabinet/publish.py +2 -2
  21. arkitekt_next/cli/commands/kabinet/stage.py +1 -1
  22. arkitekt_next/cli/commands/kabinet/types.py +139 -0
  23. arkitekt_next/cli/commands/kabinet/utils.py +1 -1
  24. arkitekt_next/cli/commands/kabinet/validate.py +1 -1
  25. arkitekt_next/cli/commands/manifest/inspect.py +2 -7
  26. arkitekt_next/cli/commands/manifest/main.py +0 -2
  27. arkitekt_next/cli/io.py +0 -192
  28. arkitekt_next/cli/types.py +2 -147
  29. arkitekt_next/cli/ui.py +1 -1
  30. arkitekt_next/init_registry.py +2 -11
  31. arkitekt_next/qt/builders.py +2 -3
  32. arkitekt_next/service_registry.py +5 -87
  33. arkitekt_next/utils.py +3 -2
  34. {arkitekt_next-0.8.60.dist-info → arkitekt_next-0.8.62.dist-info}/METADATA +19 -9
  35. {arkitekt_next-0.8.60.dist-info → arkitekt_next-0.8.62.dist-info}/RECORD +38 -37
  36. arkitekt_next/cli/commands/kabinet/wizard.py +0 -329
  37. arkitekt_next/cli/commands/manifest/wizard.py +0 -94
  38. {arkitekt_next-0.8.60.dist-info → arkitekt_next-0.8.62.dist-info}/LICENSE +0 -0
  39. {arkitekt_next-0.8.60.dist-info → arkitekt_next-0.8.62.dist-info}/WHEEL +0 -0
  40. {arkitekt_next-0.8.60.dist-info → arkitekt_next-0.8.62.dist-info}/entry_points.txt +0 -0
@@ -5,9 +5,9 @@ import os
5
5
  from rich.panel import Panel
6
6
  import subprocess
7
7
  import uuid
8
- from arkitekt_next.cli.io import generate_build
8
+ from .io import generate_build
9
9
  from click import Context
10
- from arkitekt_next.cli.types import Flavour, InspectionInput
10
+ from .types import Flavour, InspectionInput
11
11
  import yaml
12
12
  from typing import Dict, Optional
13
13
  import json
@@ -93,24 +93,38 @@ def inspect_docker_container(build_id: str) -> InspectionInput:
93
93
  raise InspectionError(f"An error occurred: {e.stdout + e.stderr}") from e
94
94
 
95
95
 
96
- def inspect_templates(build_id: str, url:str) -> list[TemplateInput]:
96
+ def inspect_templates(build_id: str, url: str) -> list[TemplateInput]:
97
97
  try:
98
98
  # Run 'docker inspect' with the container ID or name
99
99
  process = subprocess.Popen(
100
- " ".join(["docker", "run", "-it", "--network", "host", build_id, "arkitekt-next", "inspect", "templates", "-mr", "--url", url]),
100
+ " ".join(
101
+ [
102
+ "docker",
103
+ "run",
104
+ "-it",
105
+ "--network",
106
+ "host",
107
+ build_id,
108
+ "arkitekt-next",
109
+ "inspect",
110
+ "templates",
111
+ "-mr",
112
+ "--url",
113
+ url,
114
+ ]
115
+ ),
101
116
  shell=True,
102
117
  stdout=subprocess.PIPE,
103
118
  stderr=subprocess.PIPE,
104
119
  )
105
120
 
106
-
107
121
  lines = []
108
- # Poll process for new output until finished
122
+ # Poll process for new output until finished
109
123
  while True:
110
124
  if process.poll() is not None:
111
125
  break
112
126
  nextline = process.stdout.readline()
113
-
127
+
114
128
  lines.append(nextline.decode("utf-8"))
115
129
  sys.stdout.buffer.write(nextline)
116
130
  sys.stdout.flush()
@@ -267,7 +281,13 @@ def get_flavours(ctx: Context, select: Optional[str] = None) -> Dict[str, Flavou
267
281
  default=DEFAULT_ARKITEKT_URL,
268
282
  )
269
283
  @click.pass_context
270
- def build(ctx: Context, flavour: str, no_inspect: bool, tag: str = None, url: str = DEFAULT_ARKITEKT_URL) -> None:
284
+ def build(
285
+ ctx: Context,
286
+ flavour: str,
287
+ no_inspect: bool,
288
+ tag: str = None,
289
+ url: str = DEFAULT_ARKITEKT_URL,
290
+ ) -> None:
271
291
  """Builds the arkitekt_next app to docker"""
272
292
 
273
293
  manifest = get_manifest(ctx)
@@ -298,7 +318,6 @@ def build(ctx: Context, flavour: str, no_inspect: bool, tag: str = None, url: st
298
318
  if tag:
299
319
  subprocess.run(["docker", "tag", build_tag, tag], check=True)
300
320
 
301
-
302
321
  inspection = None
303
322
  if not no_inspect:
304
323
  inspection = inspect_build(build_tag, url)
@@ -1,6 +1,6 @@
1
1
  from importlib.metadata import version
2
2
  from arkitekt_next.cli.constants import compile_dockerfiles
3
- from arkitekt_next.cli.types import Flavour
3
+ from .types import Flavour
4
4
  from arkitekt_next.cli.utils import build_relative_dir
5
5
  import rich_click as click
6
6
  from click import Context
@@ -0,0 +1,206 @@
1
+ import datetime
2
+ import uuid
3
+ from arkitekt_next.utils import create_arkitekt_next_folder
4
+ import os
5
+ from typing import Optional, List, Dict
6
+ from arkitekt_next.cli.types import (
7
+ Manifest,
8
+ )
9
+
10
+ from .types import (
11
+ Build,
12
+ BuildsConfigFile,
13
+ Flavour,
14
+ DeploymentsConfigFile,
15
+ )
16
+ from kabinet.api.schema import (
17
+ InspectionInput,
18
+ AppImageInput,
19
+ DockerImageInput,
20
+ ManifestInput,
21
+ )
22
+
23
+ import yaml
24
+ import json
25
+ import rich_click as click
26
+
27
+
28
+ def get_builds(selected_run: Optional[str] = None) -> Dict[str, Build]:
29
+ """Will load the builds.yaml file and return a dictionary of builds
30
+
31
+ Will load the builds.yaml file and return a dictionary of builds
32
+ where the key is the build_id and the value is the build object.
33
+
34
+
35
+ Returns
36
+ -------
37
+ Dict[str, Build]
38
+ The loaded builds
39
+ """
40
+ path = create_arkitekt_next_folder()
41
+ config_file = os.path.join(path, "builds.yaml")
42
+
43
+ builds = {}
44
+
45
+ if os.path.exists(config_file):
46
+ with open(config_file, "r") as file:
47
+ config = BuildsConfigFile(**yaml.safe_load(file))
48
+
49
+ # We will only return the builds from the selected run
50
+ selected_run = selected_run or config.latest_build_run
51
+
52
+ builds = {
53
+ build.build_id: build
54
+ for build in config.builds
55
+ if build.build_run == selected_run
56
+ }
57
+ return builds
58
+ else:
59
+ raise click.ClickException(
60
+ "Could not find any builds. Please run `arkitekt_next port build` first"
61
+ )
62
+
63
+
64
+ def manifest_to_input(manifest: Manifest) -> ManifestInput:
65
+
66
+ return ManifestInput(**manifest.model_dump(by_alias=True))
67
+
68
+
69
+ def generate_build(
70
+ build_run: str,
71
+ build_id: str,
72
+ flavour_name: str,
73
+ flavour: Flavour,
74
+ manifest: Manifest,
75
+ inspection: Optional[InspectionInput],
76
+ ) -> Build:
77
+ """Generates a build from a builder, build_id and manifest
78
+
79
+ Will generate a build from a builder, build_id and manifest,
80
+ and write it to the builds.yaml file in the arkitekt_next folder.
81
+
82
+
83
+ Parameters
84
+ ----------
85
+ builder : str
86
+ The builder that was used to build the build
87
+ build_id : str
88
+ The build_id of the build
89
+ manifest : Manifest
90
+ The manifest of the build
91
+
92
+ Returns
93
+ -------
94
+ Build
95
+ The generated build
96
+ """
97
+ path = create_arkitekt_next_folder()
98
+
99
+ config_file = os.path.join(path, "builds.yaml")
100
+
101
+ build = Build(
102
+ manifest=manifest_to_input(manifest),
103
+ flavour=flavour_name,
104
+ selectors=flavour.selectors,
105
+ build_id=build_id,
106
+ build_run=build_run,
107
+ description=flavour.description,
108
+ inspection=inspection,
109
+ )
110
+
111
+ if os.path.exists(config_file):
112
+ with open(config_file, "r") as file:
113
+ config = BuildsConfigFile(**yaml.safe_load(file))
114
+ config.builds.append(build)
115
+ config.latest_build_run = build_run
116
+ else:
117
+ config = BuildsConfigFile(builds=[build], latest_build_run=build_run)
118
+
119
+ with open(config_file, "w") as file:
120
+ yaml.safe_dump(
121
+ json.loads(
122
+ config.json(exclude_none=True, exclude_unset=True, by_alias=True)
123
+ ),
124
+ file,
125
+ sort_keys=True,
126
+ )
127
+
128
+ return build
129
+
130
+
131
+ def get_deployments() -> DeploymentsConfigFile:
132
+ """Loads the deployments.yaml file and returns the deployments
133
+
134
+ Will load the deployments.yaml file and return the deployments
135
+ as a DeploymentsConfigFile object. If no deployments.yaml file
136
+ exists, it will return an empty DeploymentsConfigFile object.
137
+
138
+ Returns
139
+ -------
140
+ DeploymentsConfigFile
141
+ The deployments as a DeploymentsConfigFile object
142
+ """
143
+ path = create_arkitekt_next_folder()
144
+ config_file = os.path.join(path, "deployments.yaml")
145
+ if os.path.exists(config_file):
146
+ with open(config_file, "r") as file:
147
+ return DeploymentsConfigFile(**yaml.safe_load(file))
148
+ else:
149
+ return DeploymentsConfigFile()
150
+
151
+
152
+ def generate_deployment(
153
+ deployment_run: str,
154
+ build: Build,
155
+ image: str,
156
+ ) -> AppImageInput:
157
+ """Generates a deployment from a build and an image
158
+
159
+ Parameters
160
+ ----------
161
+
162
+ build : Build
163
+ The build that should be deployed
164
+ image: str
165
+ The image that is the actuall deployment of the build
166
+ with_definitions: bool:
167
+ Should we generated and inspect definitions to bundle with
168
+ the deployment?
169
+
170
+ Returns:
171
+ ------
172
+ Deployment: The created deployment
173
+
174
+ """
175
+
176
+ path = create_arkitekt_next_folder()
177
+
178
+ config_file = os.path.join(path, "deployments.yaml")
179
+
180
+ app_image = AppImageInput(
181
+ appImageId=uuid.uuid4().hex,
182
+ manifest=build.manifest,
183
+ flavourName=build.flavour,
184
+ selectors=build.selectors,
185
+ inspection=build.inspection,
186
+ image=DockerImageInput(imageString=image, buildAt=datetime.datetime.now()),
187
+ )
188
+
189
+ if os.path.exists(config_file):
190
+ with open(config_file, "r") as file:
191
+ config = DeploymentsConfigFile(**yaml.safe_load(file))
192
+ config.app_images.append(app_image)
193
+ config.latest_app_image = app_image.app_image_id
194
+ else:
195
+ config = DeploymentsConfigFile(
196
+ app_images=[app_image], latest_app_image=app_image.app_image_id
197
+ )
198
+
199
+ with open(config_file, "w") as file:
200
+ yaml.safe_dump(
201
+ json.loads(config.model_dump_json(exclude_none=True, by_alias=True)),
202
+ file,
203
+ sort_keys=True,
204
+ )
205
+
206
+ return app_image
@@ -1,14 +1,34 @@
1
1
  import rich_click as click
2
- from .build import build
3
- from .publish import publish
4
- from .stage import stage
5
- from .init import init
6
- from .wizard import wizard
7
- from .validate import validate
2
+
8
3
  from click import Context
9
4
 
10
5
 
11
- @click.group()
6
+ class LazyGroup(click.Group):
7
+
8
+ def list_commands(self, ctx):
9
+ return ["build", "init", "validate", "publish", "stage", "wizard"]
10
+
11
+ def get_command(self, ctx, cmd_name):
12
+ from .build import build
13
+ from .publish import publish
14
+ from .stage import stage
15
+ from .init import init
16
+ from .validate import validate
17
+
18
+ if cmd_name == "build":
19
+ return build
20
+ elif cmd_name == "init":
21
+ return init
22
+ elif cmd_name == "validate":
23
+ return validate
24
+ elif cmd_name == "publish":
25
+ return publish
26
+ elif cmd_name == "stage":
27
+ return stage
28
+ return None
29
+
30
+
31
+ @click.group(cls=LazyGroup)
12
32
  @click.pass_context
13
33
  def kabinet(ctx: Context) -> None:
14
34
  """Deploy the arkitekt_next app with Port
@@ -21,11 +41,3 @@ def kabinet(ctx: Context) -> None:
21
41
  """
22
42
 
23
43
  pass
24
-
25
-
26
- kabinet.add_command(build, "build")
27
- kabinet.add_command(init, "init")
28
- kabinet.add_command(validate, "validate")
29
- kabinet.add_command(publish, "publish")
30
- kabinet.add_command(stage, "stage")
31
- kabinet.add_command(wizard, "wizard")
@@ -2,9 +2,9 @@ import rich_click as click
2
2
  import subprocess
3
3
  from .utils import search_username_in_docker_info
4
4
  from arkitekt_next.cli.vars import get_console
5
- from arkitekt_next.cli.types import Build
5
+ from .types import Build
6
6
  from rich.panel import Panel
7
- from arkitekt_next.cli.io import get_builds, get_deployments, generate_deployment
7
+ from .io import get_builds, get_deployments, generate_deployment
8
8
  from click import Context
9
9
  import uuid
10
10
 
@@ -2,7 +2,7 @@ import rich_click as click
2
2
  from arkitekt_next.cli.vars import get_manifest
3
3
  import subprocess
4
4
  from click import Context
5
- from arkitekt_next.cli.io import get_builds
5
+ from .io import get_builds
6
6
  from arkitekt_next.constants import DEFAULT_ARKITEKT_URL
7
7
 
8
8
 
@@ -0,0 +1,139 @@
1
+ from importlib.metadata import version
2
+ from pydantic import BaseModel, Field, field_validator
3
+ import datetime
4
+ from typing import List, Optional, Union, Literal, Dict
5
+ from enum import Enum
6
+ import semver
7
+ import uuid
8
+ from arkitekt_next.base_models import Requirement
9
+ from string import Formatter
10
+ import os
11
+
12
+ from kabinet.api.schema import (
13
+ AppImageInput,
14
+ InspectionInput,
15
+ SelectorInput,
16
+ ManifestInput,
17
+ )
18
+
19
+ ALLOWED_BUILDER_KEYS = [
20
+ "tag",
21
+ "dockerfile",
22
+ "package_version",
23
+ ]
24
+
25
+
26
+ class SelectorType(str, Enum):
27
+ RAM = "ram"
28
+ CPU = "cpu"
29
+ GPU = "gpu"
30
+ LABEL = "label"
31
+
32
+
33
+ class Flavour(BaseModel):
34
+ selectors: List[SelectorInput]
35
+ description: str = Field(default="")
36
+ dockerfile: str = Field(default="Dockerfile")
37
+ build_command: List[str] = Field(
38
+ default_factory=lambda: [
39
+ "docker",
40
+ "build",
41
+ "-t",
42
+ "{tag}",
43
+ "-f",
44
+ "{dockerfile}",
45
+ ".",
46
+ ]
47
+ )
48
+
49
+ @field_validator("build_command", mode="before")
50
+ def check_valid_template_name(cls, value):
51
+ """Checks that the build_command templates are valid"""
52
+
53
+ for v in value:
54
+ for literal_text, field_name, format_spec, conversion in Formatter().parse(
55
+ v
56
+ ):
57
+ if field_name is not None:
58
+ assert (
59
+ field_name in ALLOWED_BUILDER_KEYS
60
+ ), f"Invalid template key {field_name}. Allowed keys are {ALLOWED_BUILDER_KEYS}"
61
+
62
+ return value
63
+
64
+ def generate_build_command(self, tag: str, relative_dir: str):
65
+ """Generates the build command for this flavour"""
66
+
67
+ dockerfile = os.path.join(relative_dir, self.dockerfile)
68
+
69
+ return [v.format(tag=tag, dockerfile=dockerfile) for v in self.build_command]
70
+
71
+ def check_relative_paths(self, flavour_folder: str):
72
+ """Checks that the paths are relative to the flavour folder"""
73
+
74
+ dockerfile_path = os.path.join(flavour_folder, self.dockerfile)
75
+
76
+ if not os.path.exists(dockerfile_path):
77
+ raise Exception(
78
+ f"Could not find Dockerfile {self.dockerfile} in flavour {flavour_folder}"
79
+ )
80
+
81
+
82
+ class DeploymentsConfigFile(BaseModel):
83
+ """The ConfigFile is a pydantic model that represents the deployments.yaml file
84
+
85
+
86
+ Parameters
87
+ ----------
88
+ BaseModel : _type_
89
+ _description_
90
+ """
91
+
92
+ app_images: List[AppImageInput] = []
93
+ latest_app_image: Optional[str] = None
94
+
95
+
96
+ class Build(BaseModel):
97
+ build_run: str
98
+ build_id: str
99
+ inspection: Optional[InspectionInput] = None
100
+ description: str = Field(default="")
101
+ selectors: List[SelectorInput] = Field(default_factory=list)
102
+ flavour: str = Field(default="vanilla")
103
+ manifest: ManifestInput
104
+ build_at: datetime.datetime = Field(default_factory=datetime.datetime.now)
105
+ base_docker_command: List[str] = Field(
106
+ default_factory=lambda: ["docker", "run", "-it", "--net", "host"]
107
+ )
108
+ base_arkitekt_next_command: List[str] = Field(
109
+ default_factory=lambda: ["arkitekt-next", "run", "prod", "--headless"]
110
+ )
111
+
112
+ def build_docker_command(self) -> List[str]:
113
+ """Builds the docker command for this build"""
114
+
115
+ base_command = self.base_docker_command
116
+
117
+ for selector in self.selectors:
118
+ base_command = base_command + selector.build_docker_params()
119
+
120
+ base_command = base_command + [self.build_id]
121
+
122
+ return base_command
123
+
124
+ def build_arkitekt_next_command(self, fakts_next_url: str):
125
+ """Builds the arkitekt_next command for this build"""
126
+
127
+ base_command = self.base_arkitekt_next_command
128
+
129
+ for selector in self.selectors:
130
+ base_command = base_command + selector.build_arkitekt_next_params()
131
+
132
+ base_command = base_command + ["--url", fakts_next_url]
133
+
134
+ return base_command
135
+
136
+
137
+ class BuildsConfigFile(BaseModel):
138
+ builds: List[Build] = Field(default_factory=list)
139
+ latest_build_run: Optional[str] = None
@@ -2,7 +2,7 @@ import logging
2
2
  from typing import Optional
3
3
  import os
4
4
  import yaml
5
- from arkitekt_next.cli.types import Flavour
5
+ from .types import Flavour
6
6
  from arkitekt_next.cli.vars import get_console
7
7
  import rich_click as click
8
8
 
@@ -1,4 +1,4 @@
1
- from arkitekt_next.cli.types import Flavour
1
+ from .types import Flavour
2
2
  import rich_click as click
3
3
  from click import Context
4
4
  from rich import get_console
@@ -17,20 +17,15 @@ def inspect(ctx) -> None:
17
17
  """
18
18
  manifest = get_manifest(ctx)
19
19
 
20
- table = Table.grid()
21
- table.add_column()
20
+ table = Table.grid(padding=(0, 3))
22
21
  table.add_column()
22
+ table.add_column(justify="right")
23
23
  table.add_row("Identifier", manifest.identifier)
24
24
  table.add_row("Version", manifest.version)
25
25
  table.add_row("Author", manifest.author)
26
26
  table.add_row("Logo", manifest.logo or "-")
27
27
  table.add_row("Entrypoint", manifest.entrypoint)
28
28
  table.add_row("Scopes", ", ".join(manifest.scopes) if manifest.scopes else "-")
29
- table.add_row(
30
- "Requirements",
31
- ", ".join(manifest.requirements) if manifest.requirements else "-",
32
- )
33
- table.add_row("Created at", str(manifest.created_at.strftime("%Y/%m/%d %H:%M")))
34
29
 
35
30
  panel = Panel(
36
31
  Group("[bold green]Manifest[/]", table),
@@ -2,7 +2,6 @@ import rich_click as click
2
2
  from .inspect import inspect
3
3
  from .scopes import scopes_group
4
4
  from .version import version
5
- from .wizard import wizard
6
5
 
7
6
 
8
7
  @click.group()
@@ -22,4 +21,3 @@ def manifest(ctx) -> None:
22
21
  manifest.add_command(inspect, "inspect")
23
22
  manifest.add_command(scopes_group, "scopes")
24
23
  manifest.add_command(version, "version")
25
- manifest.add_command(wizard, "wizard")