arkitekt-next 0.11.0__py3-none-any.whl → 0.12.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.

Potentially problematic release.


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

arkitekt_next/__init__.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from .builders import easy, interactive
2
2
  from .apps.protocols import App
3
3
  from fakts_next.helpers import afakt, fakt
4
- from .init_registry import init, InitHookRegisty, get_current_init_hook_registry
4
+ from .init_registry import init, InitHookRegistry, get_default_init_hook_registry
5
5
  from .service_registry import (
6
6
  require,
7
7
  ServiceBuilderRegistry,
@@ -28,6 +28,7 @@ try:
28
28
  from rekuest_next.actors.context import progress, aprogress
29
29
  from rekuest_next.actors.context import log, alog
30
30
  from rekuest_next.structures.model import model
31
+ from rekuest_next.actors.context import apublish, publish
31
32
  from rekuest_next.remote import (
32
33
  call,
33
34
  acall,
@@ -39,6 +40,8 @@ try:
39
40
  )
40
41
  except ImportError as e:
41
42
  raise e
43
+ publish = missing_install("rekuest_next", e)
44
+ apublish = missing_install("rekuest_next", e)
42
45
  structure = missing_install("rekuest_next", e)
43
46
  register = missing_install("rekuest_next", e)
44
47
  background = missing_install("rekuest_next", e)
@@ -73,6 +76,8 @@ __all__ = [
73
76
  "afakt",
74
77
  "fakt",
75
78
  "progress",
79
+ "InitHookRegistry",
80
+ "get_default_init_hook_registry",
76
81
  "aprogress",
77
82
  "ServiceBuilderRegistry",
78
83
  "get_default_service_registry",
@@ -1,7 +1,6 @@
1
1
  from typing import Optional
2
2
 
3
3
  from fakts_next.fakts import Fakts
4
- from fakts_next.grants.hard import HardFakts
5
4
  from fakts_next.grants.remote import RemoteGrant
6
5
  from fakts_next.grants.remote.discovery.well_known import WellKnownDiscovery
7
6
  from fakts_next.grants.remote.demanders.static import StaticDemander
@@ -45,7 +44,7 @@ def build_arkitekt_next_fakts_next(
45
44
  )
46
45
 
47
46
 
48
- def build_arkitekt_next_redeem_fakts_next(manifest: Manifest, redeem_token: str, url):
47
+ def build_arkitekt_next_redeem_fakts_next(manifest: Manifest, redeem_token: str, url: str) -> Fakts:
49
48
  identifier = manifest.identifier
50
49
  version = manifest.version
51
50
 
@@ -65,7 +64,7 @@ def build_arkitekt_next_redeem_fakts_next(manifest: Manifest, redeem_token: str,
65
64
  def build_arkitekt_next_token_fakts_next(
66
65
  manifest: Manifest,
67
66
  token: str,
68
- url,
67
+ url: str,
69
68
  ):
70
69
  identifier = manifest.identifier
71
70
  version = manifest.version
@@ -82,11 +81,3 @@ def build_arkitekt_next_token_fakts_next(
82
81
  ),
83
82
  )
84
83
 
85
-
86
- def build_local_fakts(manifest, fakts):
87
- identifier = manifest.identifier
88
- version = manifest.version
89
-
90
- return Fakts(
91
- grant=HardFakts(fakts=fakts),
92
- )
arkitekt_next/builders.py CHANGED
@@ -6,14 +6,13 @@ from arkitekt_next.apps.service.fakts_next import (
6
6
  build_arkitekt_next_fakts_next,
7
7
  build_arkitekt_next_redeem_fakts_next,
8
8
  build_arkitekt_next_token_fakts_next,
9
- build_local_fakts,
10
9
  )
11
10
  from arkitekt_next.apps.service.herre import build_arkitekt_next_herre_next
12
11
  from .utils import create_arkitekt_next_folder
13
12
  from .base_models import Manifest
14
13
  from .apps.protocols import App
15
14
  from .service_registry import ServiceBuilderRegistry, get_default_service_registry
16
- from .init_registry import InitHookRegisty, get_current_init_hook_registry
15
+ from .init_registry import InitHookRegistry, get_default_init_hook_registry
17
16
  from arkitekt_next.constants import DEFAULT_ARKITEKT_URL
18
17
 
19
18
 
@@ -29,9 +28,8 @@ def easy(
29
28
  no_cache: bool = False,
30
29
  redeem_token: Optional[str] = None,
31
30
  service_registry: Optional[ServiceBuilderRegistry] = None,
32
- init_hook_registry: Optional[InitHookRegisty] = None,
33
- fakts: Optional[str] = None,
34
- **kwargs,
31
+ init_hook_registry: Optional[InitHookRegistry] = None,
32
+ instance_id: str = "main",
35
33
  ) -> App:
36
34
  """Creates a next app
37
35
 
@@ -100,12 +98,8 @@ def easy(
100
98
  A built app, that can be used to interact with the ArkitektNext server
101
99
  """
102
100
  service_registry = service_registry or get_default_service_registry()
103
- init_hook_registry = init_hook_registry or get_current_init_hook_registry()
101
+ init_hook_registry = init_hook_registry or get_default_init_hook_registry()
104
102
 
105
- if init_hook_registry is None:
106
- raise ValueError(
107
- "No init hook registry found. Please provide a init hook registry or use the default one."
108
- )
109
103
 
110
104
  if identifier is None:
111
105
  identifier = __file__.split("/")[-1].replace(".py", "")
@@ -121,13 +115,8 @@ def easy(
121
115
  requirements=service_registry.get_requirements(),
122
116
  )
123
117
 
124
- if fakts:
125
- fakts_next = build_local_fakts(
126
- manifest=manifest,
127
- fakts=fakts,
128
- )
129
118
 
130
- elif token:
119
+ if token:
131
120
  fakts_next = build_arkitekt_next_token_fakts_next(
132
121
  manifest=manifest,
133
122
  token=token,
@@ -150,7 +139,8 @@ def easy(
150
139
 
151
140
  herre_next = build_arkitekt_next_herre_next(fakts_next=fakts_next)
152
141
 
153
- params = kwargs
142
+ params = {
143
+ "instance_id": instance_id,}
154
144
 
155
145
  create_arkitekt_next_folder(with_cache=True)
156
146
 
@@ -189,8 +179,7 @@ def interactive(
189
179
  app_kind: str = "development",
190
180
  registry: Optional[ServiceBuilderRegistry] = None,
191
181
  sync_mode: bool = True,
192
- **kwargs,
193
- ):
182
+ ) -> App:
194
183
  """Creates an interactive jupyter app"""
195
184
 
196
185
  app = easy(
@@ -204,9 +193,7 @@ def interactive(
204
193
  token=token,
205
194
  no_cache=no_cache,
206
195
  redeem_token=redeem_token,
207
- app_kind=app_kind,
208
- registry=registry,
209
- **kwargs,
196
+
210
197
  )
211
198
 
212
199
  if sync_mode:
@@ -215,7 +202,7 @@ def interactive(
215
202
  # to avoid having to use await for every call. This is the default
216
203
  # behaviour for the app, but can be overwritten by setting
217
204
  # app.koil.sync_in_async = False
218
- app.koil.sync_in_async = True
205
+ app._koil.sync_in_async = True
219
206
  app.enter()
220
207
 
221
208
  return app
@@ -1,4 +1,5 @@
1
1
 
2
+ from click import Context
2
3
  import rich_click as click
3
4
  from arkitekt_next.cli.options import *
4
5
  import asyncio
@@ -8,7 +9,6 @@ from arkitekt_next.cli.utils import import_builder
8
9
  from rekuest_next.rekuest import RekuestNext
9
10
  from rich.console import Console
10
11
  from typing import Dict, Any
11
- import asyncio
12
12
 
13
13
 
14
14
  async def call_app(
@@ -22,15 +22,11 @@ async def call_app(
22
22
 
23
23
  async with app:
24
24
 
25
- rekuest: RekuestNext = app.services["rekuest"]
26
-
25
+ rekuest: RekuestNext = app.rekuest
27
26
 
28
- raise NotImplementedError("Not implemented yet")
27
+ raise NotImplementedError("Not implemented yet. Do you want to implement it? :)")
29
28
 
30
29
 
31
- t = asyncio.create_task(rekuest.arun())
32
-
33
- await t
34
30
 
35
31
 
36
32
 
@@ -66,12 +62,12 @@ async def call_app(
66
62
  )
67
63
  @click.option("--fakts", "-f", "fakts", type=(str, str), multiple=True)
68
64
  def local(
69
- ctx,
70
- entrypoint=None,
71
- builder=None,
72
- args=None,
73
- template: str = None,
74
- fakts: str = None,
65
+ ctx: Context,
66
+ entrypoint: str | None =None,
67
+ builder: str | None =None,
68
+ args: Optional[Dict[str, Any]] = None,
69
+ template: str | None = None,
70
+ fakts: Optional[Dict[str, str]] = None,
75
71
  **builder_kwargs,
76
72
  ):
77
73
  """Runs the app in production mode
@@ -10,7 +10,7 @@ import os
10
10
  type=click.Path(exists=True),
11
11
  default=None,
12
12
  )
13
- def compile(projects, config):
13
+ def compile(projects, config: click.Path):
14
14
  """Genererate the code of a project"
15
15
 
16
16
  Uses a previously generated graphql-config.yaml file to generate the code for a or multiple projects.
@@ -5,11 +5,13 @@ import os
5
5
  from rich.panel import Panel
6
6
  import subprocess
7
7
  import uuid
8
+
9
+ from kabinet.api.schema import RequirementInput
8
10
  from .io import generate_build
9
11
  from click import Context
10
12
  from .types import Flavour, InspectionInput
11
13
  import yaml
12
- from typing import Dict, Optional
14
+ from typing import Dict, List, Optional
13
15
  import json
14
16
  from arkitekt_next.base_models import Requirement
15
17
  from arkitekt_next.constants import DEFAULT_ARKITEKT_URL
@@ -93,7 +95,7 @@ def inspect_docker_container(build_id: str) -> InspectionInput:
93
95
  raise InspectionError(f"An error occurred: {e.stdout + e.stderr}") from e
94
96
 
95
97
 
96
- def inspect_templates(build_id: str, url: str) -> list[ImplementationInput]:
98
+ def inspect_implementations(build_id: str, url: str) -> list[ImplementationInput]:
97
99
  try:
98
100
  # Run 'docker inspect' with the container ID or name
99
101
  process = subprocess.Popen(
@@ -116,7 +118,7 @@ def inspect_templates(build_id: str, url: str) -> list[ImplementationInput]:
116
118
  stderr=subprocess.PIPE,
117
119
  )
118
120
 
119
- lines = []
121
+ lines: list[str] = []
120
122
  # Poll process for new output until finished
121
123
  while True:
122
124
  if process.poll() is not None:
@@ -129,9 +131,25 @@ def inspect_templates(build_id: str, url: str) -> list[ImplementationInput]:
129
131
 
130
132
  output = process.communicate()[0]
131
133
  exitCode = process.returncode
132
-
133
134
  result = "\n".join(lines)
134
135
 
136
+
137
+
138
+ if exitCode != 0:
139
+
140
+ if "ModuleNotFoundError" in result:
141
+ raise click.ClickException(
142
+ " It looks like you are missing a module in your container. Please make sure that you have all the dependencies installed in your container. The error is listed above."
143
+ )
144
+
145
+
146
+
147
+
148
+ raise click.ClickException(
149
+ "When running the command `arkitekt_next inspect implementations` we got an error. The error is listed above."
150
+ )
151
+
152
+
135
153
  # Parse the JSON output
136
154
  correct_part = result.split("--START_TEMPLATES--")[1].split(
137
155
  "--END_TEMPLATES--"
@@ -158,7 +176,7 @@ def inspect_templates(build_id: str, url: str) -> list[ImplementationInput]:
158
176
  raise InspectionError(f"An error occurred: {e.stdout + e.stderr}") from e
159
177
 
160
178
 
161
- def inspect_requirements(build_id: str) -> Dict[str, Requirement]:
179
+ def inspect_requirements(build_id: str) -> List[RequirementInput]:
162
180
  try:
163
181
  # Run 'docker inspect' with the container ID or name
164
182
  result = subprocess.run(
@@ -205,10 +223,10 @@ def inspect_requirements(build_id: str) -> Dict[str, Requirement]:
205
223
 
206
224
  def inspect_build(build_id: str, url: str) -> InspectionInput:
207
225
  size, size_root_fs = inspect_docker_container(build_id)
208
- templates = inspect_templates(build_id, url)
226
+ implementations = inspect_implementations(build_id, url)
209
227
  requirements = inspect_requirements(build_id)
210
228
 
211
- return InspectionInput(size=size, templates=templates, requirements=requirements)
229
+ return InspectionInput(size=size, implementations=tuple(implementations), requirements=tuple(requirements))
212
230
 
213
231
 
214
232
  def get_flavours(ctx: Context, select: Optional[str] = None) -> Dict[str, Flavour]:
@@ -223,7 +241,7 @@ def get_flavours(ctx: Context, select: Optional[str] = None) -> Dict[str, Flavou
223
241
  "We could not find the flavours folder. Please run `arkitekt_next port init` first to create a buildable flavour"
224
242
  )
225
243
 
226
- flavours = {}
244
+ flavours: Dict[str, Flavour] = {}
227
245
 
228
246
  for dir_name in os.listdir(flavours_folder):
229
247
  dir = os.path.join(flavours_folder, dir_name)
@@ -283,7 +301,7 @@ def build(
283
301
  ctx: Context,
284
302
  flavour: str,
285
303
  no_inspect: bool,
286
- tag: str = None,
304
+ tag: str | None = None,
287
305
  url: str = DEFAULT_ARKITEKT_URL,
288
306
  ) -> None:
289
307
  """Builds the arkitekt_next app to docker"""
@@ -303,7 +321,7 @@ def build(
303
321
 
304
322
  build_run = str(uuid.uuid4())
305
323
 
306
- for key, flavour in flavours.items():
324
+ for key, inspected_flavour in flavours.items():
307
325
  md = Panel(
308
326
  "Building Flavour [bold]{}[/bold]".format(key),
309
327
  subtitle="This may take a while...",
@@ -311,7 +329,7 @@ def build(
311
329
  )
312
330
  console.print(md)
313
331
 
314
- build_tag = build_flavour(key, flavour)
332
+ build_tag = build_flavour(key, inspected_flavour)
315
333
 
316
334
  if tag:
317
335
  subprocess.run(["docker", "tag", build_tag, tag], check=True)
@@ -320,7 +338,7 @@ def build(
320
338
  if not no_inspect:
321
339
  inspection = inspect_build(build_tag, url)
322
340
 
323
- generate_build(build_run, build_tag, key, flavour, manifest, inspection)
341
+ generate_build(build_run, build_tag, key, inspected_flavour, manifest, inspection)
324
342
 
325
343
  md = Panel(
326
344
  "Built Flavour [bold]{}[/bold]".format(key),
@@ -119,7 +119,7 @@ def generate_build(
119
119
  with open(config_file, "w") as file:
120
120
  yaml.safe_dump(
121
121
  json.loads(
122
- config.json(exclude_none=True, exclude_unset=True, by_alias=True)
122
+ config.model_dump_json(exclude_none=True, exclude_unset=True, by_alias=True)
123
123
  ),
124
124
  file,
125
125
  sort_keys=True,
@@ -1,5 +1,8 @@
1
+ from functools import partial
1
2
  from importlib import import_module, reload
2
3
  import asyncio
4
+
5
+ from click import Context
3
6
  from arkitekt_next import App
4
7
  from watchfiles import awatch, Change
5
8
  from rich.panel import Panel
@@ -165,10 +168,40 @@ def is_entrypoint_change(
165
168
  return False
166
169
 
167
170
 
171
+
172
+
173
+ def callback(console: Console, future: asyncio.Task[None]):
174
+ if future.cancelled():
175
+ return
176
+ else:
177
+ has_exception = future.exception()
178
+
179
+ if not has_exception:
180
+ panel = Panel(
181
+ "App finished running", style="bold yellow", border_style="yellow"
182
+ )
183
+ console.print(panel)
184
+ else:
185
+ try:
186
+ raise has_exception
187
+ except Exception:
188
+ console.print_exception()
189
+ panel = Panel(
190
+ "Error running App", style="bold red", border_style="red"
191
+ )
192
+ console.print(panel)
193
+
194
+
195
+
196
+
197
+
198
+
199
+
200
+
168
201
  async def run_dev(
169
202
  console: Console,
170
203
  manifest: Manifest,
171
- version=None,
204
+ version: str | None =None,
172
205
  builder: str = "arkitekt_next.builders.easy",
173
206
  deep: bool = False,
174
207
  **builder_kwargs,
@@ -209,26 +242,10 @@ async def run_dev(
209
242
  console.print(panel)
210
243
  module = None
211
244
 
212
- def callback(future):
213
- if future.cancelled():
214
- return
215
- else:
216
- has_exception = future.exception()
217
-
218
- if not has_exception:
219
- panel = Panel(
220
- "App finished running", style="bold yellow", border_style="yellow"
221
- )
222
- console.print(panel)
223
- else:
224
- try:
225
- raise has_exception
226
- except Exception:
227
- console.print_exception()
228
- panel = Panel(
229
- "Error running App", style="bold red", border_style="red"
230
- )
231
- console.print(panel)
245
+
246
+
247
+ current_run: asyncio.Future[None] | None = None
248
+ # This is the main task that is running the app
232
249
 
233
250
  try:
234
251
  app: App = builder_func(
@@ -241,14 +258,16 @@ async def run_dev(
241
258
  panel = Panel(group, style="bold green", border_style="green")
242
259
  console.print(panel)
243
260
 
244
- x = asyncio.create_task(run_app(app))
245
- x.add_done_callback(callback)
261
+ current_run = asyncio.create_task(run_app(app))
262
+ current_run.add_done_callback(partial(callback, console))
246
263
  except Exception:
247
264
  console.print_exception()
248
265
  panel = Panel(
249
266
  "Error building initial App", style="bold red", border_style="red"
250
267
  )
251
268
  console.print(panel)
269
+
270
+
252
271
 
253
272
  async for changes in awatch(
254
273
  ".",
@@ -257,9 +276,12 @@ async def run_dev(
257
276
  step=500,
258
277
  ):
259
278
  if deep:
279
+ #
260
280
  to_be_reloaded = check_deeps(changes)
261
281
  if not to_be_reloaded:
262
282
  continue
283
+ else:
284
+ to_be_reloaded: Set[str] = set()
263
285
 
264
286
  group = construct_changes_group(changes)
265
287
  panel = Panel(group, style="bold blue", border_style="blue")
@@ -267,17 +289,17 @@ async def run_dev(
267
289
 
268
290
  console.print(panel)
269
291
  # Cancelling the app
270
- if not x or x.done():
292
+ if not current_run or current_run.done():
271
293
  pass
272
294
 
273
295
  else:
274
- x.cancel()
296
+ current_run.cancel()
275
297
  panel = Panel(
276
298
  "Cancelling latest version", style="bold yellow", border_style="yellow"
277
299
  )
278
300
  console.print(panel)
279
301
  try:
280
- await x
302
+ await current_run
281
303
 
282
304
  except asyncio.CancelledError:
283
305
  pass
@@ -293,6 +315,8 @@ async def run_dev(
293
315
  if deep:
294
316
  reload_modules(to_be_reloaded)
295
317
  else:
318
+
319
+
296
320
  reload(module)
297
321
  except Exception:
298
322
  console.print_exception()
@@ -315,8 +339,8 @@ async def run_dev(
315
339
  panel = Panel(group, style="bold green", border_style="green")
316
340
  console.print(panel)
317
341
 
318
- x = asyncio.create_task(run_app(app))
319
- x.add_done_callback(callback)
342
+ current_run = asyncio.create_task(run_app(app))
343
+ current_run.add_done_callback(partial(callback, console))
320
344
  except Exception:
321
345
  console.print_exception()
322
346
  panel = Panel(
@@ -340,7 +364,7 @@ async def run_dev(
340
364
  is_flag=True,
341
365
  )
342
366
  @click.pass_context
343
- def dev(ctx, **kwargs):
367
+ def dev(ctx: Context, **kwargs):
344
368
  """Runs the app in dev mode (with hot reloading)
345
369
 
346
370
  Running the app in dev mode will automatically reload the app when changes are detected.
@@ -4,6 +4,20 @@ from arkitekt_next.apps.protocols import App
4
4
 
5
5
 
6
6
  def import_builder(builder: str) -> Callable[..., App]:
7
+ """ Import a builder function from a module.
8
+
9
+ Parameters
10
+ ----------
11
+ builder : str
12
+ The builder function to import, in the format "module.function".
13
+
14
+ Returns
15
+ -------
16
+ Callable[..., App]
17
+ The imported builder function.
18
+
19
+ """
20
+
7
21
  module_path, function_name = builder.rsplit(".", 1)
8
22
  module = import_module(module_path)
9
23
  function = getattr(module, function_name)
arkitekt_next/cli/io.py CHANGED
@@ -68,7 +68,7 @@ def write_manifest(manifest: Manifest):
68
68
 
69
69
  with open(config_file, "w") as file:
70
70
  yaml.safe_dump(
71
- json.loads(manifest.json(exclude_none=True, exclude_unset=True)),
71
+ json.loads(manifest.model_dump_json(exclude_none=True, exclude_unset=True)),
72
72
  file,
73
73
  sort_keys=True,
74
74
  )
@@ -1,6 +1,6 @@
1
1
  import contextvars
2
2
  from functools import wraps
3
- from typing import Callable, Dict, Optional, TypeVar, overload, cast
3
+ from typing import Callable, Dict, Optional, TypeVar, overload
4
4
  from arkitekt_next.apps.protocols import App
5
5
 
6
6
  Params = Dict[str, str]
@@ -9,26 +9,29 @@ Params = Dict[str, str]
9
9
  current_init_hook_registry = contextvars.ContextVar(
10
10
  "current_init_hook_registry", default=None
11
11
  )
12
- GLOBAL_INIT_HOOK_REGISTRY = None
13
-
14
12
 
15
- def get_default_init_hook_registry():
16
- global GLOBAL_INIT_HOOK_REGISTRY
17
- if GLOBAL_INIT_HOOK_REGISTRY is None:
18
- GLOBAL_INIT_HOOK_REGISTRY = InitHookRegisty()
19
- return GLOBAL_INIT_HOOK_REGISTRY
20
-
21
-
22
- def get_current_init_hook_registry(allow_global=True):
23
- return current_init_hook_registry.get(get_default_init_hook_registry())
24
13
 
25
14
 
26
15
  InitHook = Callable[[App], None]
27
16
 
28
17
 
29
- class InitHookRegisty:
18
+ class InitHookRegistry:
19
+ """ A registry for init hooks. This is used to register init hooks that
20
+ are called when the app is initialized. The init hooks are called in
21
+ the order they are registered
22
+
23
+ The purpose of init hooks is to allow specific initialization
24
+ code to be run when the app is initialized. This is useful if you
25
+ plan to add some custom configuration or setup code that needs to be
26
+ run before the app is getting conneted to the server.
27
+
28
+ """
29
+
30
+
31
+
30
32
  def __init__(self):
31
33
  self.init_hooks: Dict[str, InitHook] = {}
34
+ self.cli_only_hooks: Dict[str, InitHook] = {}
32
35
 
33
36
  def register(
34
37
  self,
@@ -36,67 +39,79 @@ class InitHookRegisty:
36
39
  name: Optional[str] = None,
37
40
  only_cli: bool = False,
38
41
  ):
42
+ """ Register a function as an init hook. This function will be called
43
+
44
+
45
+ when the app is initialized. The init hooks are called in the order
46
+
47
+
48
+ """
49
+
39
50
  if name is None:
40
51
  name = function.__name__
52
+
53
+ if only_cli:
54
+ if name not in self.cli_only_hooks:
55
+ self.cli_only_hooks[name] = function
56
+ else:
57
+ raise ValueError(f"CLI Hook {name} already registered")
41
58
 
42
59
  if name not in self.init_hooks:
43
60
  self.init_hooks[name] = function
44
61
  else:
45
- raise ValueError(f"Service {name} already registered")
62
+ raise ValueError(f"Init Hook {name} already registered")
46
63
 
47
- def run_all(self, app: App):
64
+ def run_all(self, app: App, is_cli: bool = False):
48
65
  for hook in self.init_hooks.values():
49
66
  hook(app)
67
+
68
+ if is_cli:
69
+ for hook in self.cli_only_hooks.values():
70
+ hook(app)
50
71
 
51
72
 
52
73
  T = TypeVar("T", bound=InitHook)
53
74
 
54
75
 
55
76
  @overload
56
- def init(
57
- *func: T,
58
- ) -> T: ...
77
+ def init(func: T) -> T: ...
59
78
 
60
79
 
61
80
  @overload
62
- def init(
63
- *,
64
- only_cli: bool = False,
65
- init_hook_registry: InitHookRegisty | None = None,
66
- ) -> Callable[[T], T]: ...
67
-
81
+ def init(*, only_cli: bool = False, init_hook_registry: InitHookRegistry | None = None) -> Callable[[T], T]: ...
68
82
 
69
83
  def init(
70
- *func: T,
84
+ func: T | None = None,
85
+ *,
71
86
  only_cli: bool = False,
72
- init_hook_registry: InitHookRegisty | None = None,
87
+ init_hook_registry: InitHookRegistry | None = None,
73
88
  ) -> T | Callable[[T], T]:
74
89
  """Register a function as an init hook. This function will be called when the app is initialized."""
75
90
  init_hook_registry = init_hook_registry or get_default_init_hook_registry()
76
91
 
77
- if len(func) > 1:
78
- raise ValueError("You can only register one function or actor at a time.")
79
- if len(func) == 1:
80
- function_or_actor = func[0]
81
-
82
- init_hook_registry.register(function_or_actor)
92
+ if func is not None:
93
+ init_hook_registry.register(func, only_cli=only_cli)
94
+ setattr(func, "__is_init_hook__", True)
95
+ return func
83
96
 
84
- setattr(function_or_actor, "__is_init_hook__", True)
97
+ def decorator(inner: T) -> T:
98
+ @wraps(inner)
99
+ def wrapped(app: App):
100
+ return inner(app)
85
101
 
86
- return function_or_actor
102
+ init_hook_registry.register(wrapped, only_cli=only_cli)
103
+ setattr(inner, "__is_init_hook__", True)
104
+ return inner
87
105
 
88
- else:
106
+ return decorator
89
107
 
90
- def real_decorator(function_or_actor):
91
- # Simple bypass for now
92
- @wraps(function_or_actor)
93
- def wrapped_function(*args, **kwargs):
94
- return function_or_actor(*args, **kwargs)
95
108
 
96
- init_hook_registry.register(wrapped_function, only_cli=only_cli)
97
109
 
98
- setattr(function_or_actor, "__is_init_hook__", True)
110
+ GLOBAL_INIT_HOOK_REGISTRY = None
99
111
 
100
- return wrapped_function
101
112
 
102
- return cast(Callable[[T], T], real_decorator)
113
+ def get_default_init_hook_registry():
114
+ global GLOBAL_INIT_HOOK_REGISTRY
115
+ if GLOBAL_INIT_HOOK_REGISTRY is None:
116
+ GLOBAL_INIT_HOOK_REGISTRY = InitHookRegistry() # type: ignore
117
+ return GLOBAL_INIT_HOOK_REGISTRY
@@ -0,0 +1,83 @@
1
+ from typing import Any, Protocol
2
+ """
3
+ This module contains the types for the apps
4
+ depending on the builder used.
5
+ This module imports all the apps and their types
6
+ and sets them as attributes on the App class, if they are available.
7
+ If they are not available, they are set to Any, so that we can add
8
+ an import exception to the app.
9
+
10
+
11
+ """
12
+
13
+ import logging
14
+ from typing import Any, Dict, TYPE_CHECKING
15
+ from koil import unkoil
16
+ from koil.composition import KoiledModel
17
+ from arkitekt_next.base_models import Manifest
18
+ from fakts_next import Fakts
19
+
20
+
21
+ if TYPE_CHECKING:
22
+ from rekuest_next.rekuest import RekuestNext
23
+
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+
29
+
30
+
31
+
32
+
33
+
34
+
35
+ class App(Protocol):
36
+ """An app that is built with the easy builder"""
37
+ fakts: Fakts
38
+ herre: Herre
39
+ manifest: Manifest
40
+ services: Dict[str, KoiledModel]
41
+
42
+ @property
43
+ def rekuest(self) -> "RekuestNext":
44
+ """Get the rekuest service"""
45
+ if "rekuest" not in self.services:
46
+ raise ValueError("Rekuest service is not available")
47
+ return self.services["rekuest"]
48
+
49
+ def run(self):
50
+ return unkoil(self.rekuest.arun)
51
+
52
+ async def arun(self):
53
+ return await self.rekuest.arun()
54
+
55
+ def run_detached(self):
56
+ """Run the app detached"""
57
+ return self.rekuest.run_detached()
58
+
59
+ def register(self, *args, **kwargs):
60
+ """Register a service"""
61
+
62
+ self.rekuest.register(*args, **kwargs)
63
+
64
+ async def __aenter__(self):
65
+ await super().__aenter__()
66
+ for service in self.services.values():
67
+ await service.__aenter__()
68
+
69
+ return self
70
+
71
+ async def __aexit__(self, exc_type, exc_value, traceback):
72
+ for service in self.services.values():
73
+ await service.__aexit__(exc_type, exc_value, traceback)
74
+
75
+
76
+ class Builder(Protocol):
77
+ """A protocol for a builder class.
78
+
79
+ This protocol defines the methods that a builder class must implement.
80
+ """
81
+
82
+ def __call__(self, *args: Any, **kwds: Any) -> Any:
83
+ return super().__call__(*args, **kwds)
@@ -3,7 +3,7 @@ from herre_next import Herre
3
3
  from fakts_next import Fakts
4
4
  from koil.composition.base import KoiledModel
5
5
  from .base_models import Manifest, Requirement
6
- from typing import Any, Callable, Dict, Optional, Protocol, Set, TypeVar, overload
6
+ from typing import Any, Callable, Dict, Optional, Protocol, Set, TypeVar
7
7
  from typing import runtime_checkable
8
8
  from pydantic import BaseModel
9
9
 
@@ -13,14 +13,6 @@ Params = Dict[str, str]
13
13
  current_service_registry = contextvars.ContextVar(
14
14
  "current_service_registry", default=None
15
15
  )
16
- GLOBAL_SERVICE_REGISTRY = None
17
-
18
-
19
- def get_default_service_registry() -> "ServiceBuilderRegistry":
20
- global GLOBAL_SERVICE_REGISTRY
21
- if GLOBAL_SERVICE_REGISTRY is None:
22
- GLOBAL_SERVICE_REGISTRY = ServiceBuilderRegistry()
23
- return GLOBAL_SERVICE_REGISTRY
24
16
 
25
17
 
26
18
  class Registration(BaseModel):
@@ -107,7 +99,7 @@ basic_requirements = [
107
99
 
108
100
 
109
101
  class ServiceBuilderRegistry:
110
- def __init__(self, import_services=True):
102
+ def __init__(self):
111
103
  self.service_builders: Dict[str, ArkitektService] = {}
112
104
  self.additional_requirements: Dict[str, Requirement] = {}
113
105
 
@@ -170,8 +162,6 @@ class ServiceBuilderRegistry:
170
162
  return sorted_requirements
171
163
 
172
164
 
173
- class SetupInfo:
174
- services: Dict[str, object]
175
165
 
176
166
 
177
167
  T = TypeVar("T")
@@ -182,11 +172,48 @@ def require(
182
172
  service: str,
183
173
  description: str | None = None,
184
174
  service_registry: Optional[ServiceBuilderRegistry] = None,
185
- ):
186
- """Register a requirement with the service registry"""
175
+ ) -> Requirement:
176
+ """Register a requirement with the service registry
177
+
178
+ Parameters
179
+ ----------
180
+ key : str
181
+ The key for the requirement. This should be unique across all
182
+ requirements.
183
+
184
+ service : str
185
+ The service that you require. This should be a uinque fakts
186
+ service name. I.e `live.arkitekt.lok` or `live.arkitekt.lok:0.0.1`
187
+
188
+ description : str | None
189
+ The description for the requirement. This should be a short
190
+ description of the requirement that gets displayed to the user.
191
+
192
+ service_registry : ServiceBuilderRegistry | None
193
+ The service registry to register the requirement with. If
194
+ None, the default service registry will be used.
195
+
196
+ Returns
197
+ -------
198
+ Requirement
199
+ The requirement that was registered. This can be used to
200
+ get the requirement later on.
201
+
202
+ """
187
203
  service_hook_registry = service_registry or get_default_service_registry()
188
204
 
189
205
  requirement = Requirement(key=key, service=service, description=description)
190
206
  service_hook_registry.register_requirement(requirement)
191
207
 
192
208
  return requirement
209
+
210
+
211
+
212
+ GLOBAL_SERVICE_REGISTRY = None
213
+
214
+
215
+ def get_default_service_registry() -> "ServiceBuilderRegistry":
216
+ global GLOBAL_SERVICE_REGISTRY
217
+ if GLOBAL_SERVICE_REGISTRY is None:
218
+ GLOBAL_SERVICE_REGISTRY = ServiceBuilderRegistry() # type: ignore
219
+ return GLOBAL_SERVICE_REGISTRY
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arkitekt-next
3
- Version: 0.11.0
3
+ Version: 0.12.0
4
4
  Summary: client for the arkitekt_next platform
5
5
  Author-email: jhnnsrs <jhnnsrs@gmail.com>
6
6
  License-Expression: MIT
@@ -9,10 +9,11 @@ Requires-Python: <4,>=3.11
9
9
  Requires-Dist: click>=8.2.0
10
10
  Requires-Dist: dokker>=2.1.2
11
11
  Requires-Dist: fakts-next>=1.2.2
12
- Requires-Dist: herre-next>=1.2.0
12
+ Requires-Dist: herre-next>=1.3
13
+ Requires-Dist: kabinet>=0.5
13
14
  Requires-Dist: koil>=2.0.4
14
15
  Requires-Dist: rath>=3.4
15
- Requires-Dist: rekuest-next>=0.7
16
+ Requires-Dist: rekuest-next>=0.8
16
17
  Requires-Dist: rich-click>=1.8.8
17
18
  Requires-Dist: semver>=3.0.4
18
19
  Requires-Dist: watchfiles>=1.0.5
@@ -1,16 +1,17 @@
1
1
  arkitekt_next/__blok__.py,sha256=68Zx7ydA5CBnvm1ADdZZV31Oqp3ZV65-0jG7YZ_xKBc,1983
2
- arkitekt_next/__init__.py,sha256=JxzP4bml1fjgRB_B6NTUpbYrO2rhab51LvKITBXYSrk,2884
2
+ arkitekt_next/__init__.py,sha256=HOKaWJpoMBgHx9Xo8WaO6vY-RhF_bQ6aUQh3p5r20B4,3108
3
3
  arkitekt_next/base_models.py,sha256=GlxtMcV4YbeY8wkbK943HsIFzB8caEsDOfzJ2EDLJ9s,4247
4
- arkitekt_next/builders.py,sha256=srdYxOqIVhPO0UX3qRemrWZlr0Pb559T_60SxrVu_vo,7994
4
+ arkitekt_next/builders.py,sha256=7FsGNpLa9mGhSW9UTAXt1ZJLdlIvbsPy7qQxYD0LEmU,7627
5
5
  arkitekt_next/constants.py,sha256=N_tP85BDHPiOG663wwaEyMWYiUGtL2T_h4wBVPgsSUU,87
6
- arkitekt_next/init_registry.py,sha256=Ge--JiYnD3718Gwmw2-p2vHImX-Nwirs-DBmlcnsjak,2605
7
- arkitekt_next/service_registry.py,sha256=Oh_TKl1V2KFpyg96PpdXTTVfGNMVdCokYLe-amBuaBk,6527
6
+ arkitekt_next/init_registry.py,sha256=RHAHBI1dJNpFrNZRiDxMHBj_p4GLq2Oj0GnY9Vs31os,3230
7
+ arkitekt_next/protocols.py,sha256=rLm9xeO3JT6b8Vrm8jOMZt_dxmbjvN_uubNrM3BwLwc,2032
8
+ arkitekt_next/service_registry.py,sha256=gA7c2JKWqZPme5VatVJWFlX0e8HPcxb2BkzZrBMGPUk,7307
8
9
  arkitekt_next/tqdm.py,sha256=KLGeviRa8iEx89OENMXOuoz8hRDKxDktwG3L8oPlR7I,3415
9
10
  arkitekt_next/utils.py,sha256=7kWfUU4jacD8sKgV2-GsZV547EXYpzDNx_7LnIrb0oU,2454
10
11
  arkitekt_next/apps/__init__.py,sha256=zJckn9LUtKJdL7k5TrJPFoS9uItqHhjvtLDtC1PZvKA,46
11
12
  arkitekt_next/apps/protocols.py,sha256=-NSGdR32Jx_oMyjocVPIoPa3fS4sbxKqG3ZApAg-JYg,1760
12
13
  arkitekt_next/apps/service/__init__.py,sha256=p4iRwiFBKRq2lfbjDBzUR_GMhPWjkjWTa01ohuKz_L4,157
13
- arkitekt_next/apps/service/fakts_next.py,sha256=ORRoObgynGSW96_HDuUSgkj7ZcX8ytDqU_iQLfXn8xg,2830
14
+ arkitekt_next/apps/service/fakts_next.py,sha256=4kM0NwlQKWT_8db-EfyfpMQbjTImOIlgLraG0P6Wa_M,2632
14
15
  arkitekt_next/apps/service/fakts_qt.py,sha256=k8DZU_aTBYDSuHQyj54BAUreh0R8zRApVhCBSsErlXc,1943
15
16
  arkitekt_next/apps/service/grant_registry.py,sha256=h0jRKBd9EAXiVV6aHVtzNAlm1yiWPigg0qka68y6yKM,876
16
17
  arkitekt_next/apps/service/herre.py,sha256=y3Urlja0A8ZbrnVNnU6UuVMUtlOzIeatj51d0hLZriM,844
@@ -65,7 +66,7 @@ arkitekt_next/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
65
66
  arkitekt_next/cli/constants.py,sha256=ONXKA8LRxXQkOQ56ZElVMZSTiaIH1LE_ikmUUKem98g,1410
66
67
  arkitekt_next/cli/errors.py,sha256=zLTjaCbun6qM2nTldjyZd-DvykqKn5A3Gn80uYdx7Vc,93
67
68
  arkitekt_next/cli/inspect.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
68
- arkitekt_next/cli/io.py,sha256=g5vj5W5k96t7WYcmWHL9CwPfUJbBl-JDX8huGqxWMYs,1889
69
+ arkitekt_next/cli/io.py,sha256=FyL7UQgDvss_I2M6NYtcZKvOwYmtOsGVmlaHZkk6BCQ,1900
69
70
  arkitekt_next/cli/main.py,sha256=Ua7sq_OfC491F6r6zNs_oEiR4jVOsZwxLpX0ZXR0Jk0,2227
70
71
  arkitekt_next/cli/options.py,sha256=LwhEdjZutH_go568buS37L9Gn_roinrCC6BUu2sC3B0,3814
71
72
  arkitekt_next/cli/texts.py,sha256=sYK94BLCt_FWFtdgPDZuKgpINICGFcHQLqO74fxQkug,688
@@ -75,11 +76,11 @@ arkitekt_next/cli/utils.py,sha256=koY46mGLmmM1EJ9KbeF5-vNiKAY-2Dk7ldPOcrIgkxs,44
75
76
  arkitekt_next/cli/validators.py,sha256=XkLrOrDzBJwcG1keTawa_NJPt3QIBhb5KjepeH4N1KA,719
76
77
  arkitekt_next/cli/vars.py,sha256=ev7cKDSPoik8hU9A_ohNpjRZX4FT1GYJaBoGLnxCKjU,982
77
78
  arkitekt_next/cli/commands/call/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
78
- arkitekt_next/cli/commands/call/local.py,sha256=AhtYE3TeRLlk7TE0QtdgRBJdzISGjKVGzCVGrllS63k,2375
79
+ arkitekt_next/cli/commands/call/local.py,sha256=FOuqU9vaep9bholIJQWWuPTQKZbAZVRjHLaIxy88wSg,2416
79
80
  arkitekt_next/cli/commands/call/main.py,sha256=asVl-geP4jwVm1cBByUH4aSrOeimo1NaZSV6QkPRkPE,485
80
81
  arkitekt_next/cli/commands/call/remote.py,sha256=h-gKzNyAsvjbovcUVyeKRgFObvFksq5Hmb_1o6mQ3Eg,2138
81
82
  arkitekt_next/cli/commands/gen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
82
- arkitekt_next/cli/commands/gen/compile.py,sha256=lygqut1fIiopa4MbBXkv1X-45g6UBxDxpGP7R6h-b7U,1445
83
+ arkitekt_next/cli/commands/gen/compile.py,sha256=RlajAgrbJ5FfoNnoef3B0a4HfJx12-xPT2SxwjqpvPA,1457
83
84
  arkitekt_next/cli/commands/gen/init.py,sha256=7DqABT1Ob7rRM5Qc0veV4wo7YE66k_KWrYL8dXjd55M,5316
84
85
  arkitekt_next/cli/commands/gen/main.py,sha256=_BdkcsXoWY5_3gmboq2e0pGYM6lAnwqQgBAyxmvdf6U,947
85
86
  arkitekt_next/cli/commands/gen/watch.py,sha256=nf4QckdTJOWxvKoeNRwvC4rXQ4xhIx8LCDDJzpPhcY8,1189
@@ -91,9 +92,9 @@ arkitekt_next/cli/commands/inspect/main.py,sha256=khT5u8bF4EokgA-JtOLVVN3dD0jq32
91
92
  arkitekt_next/cli/commands/inspect/requirements.py,sha256=CiVApGLpG2haDkFj0klIrKIjhIV5Z_TL8HrZeS4Swug,1761
92
93
  arkitekt_next/cli/commands/inspect/variables.py,sha256=sBVTKCI98BWAUiqhF5xRu_jQDgwk8sGfcyEozpAnDwg,2654
93
94
  arkitekt_next/cli/commands/kabinet/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
94
- arkitekt_next/cli/commands/kabinet/build.py,sha256=1rCIfosLbEPZhgXhHxqUTq_k_15MV1Nl4UgYWN1v6dM,9924
95
+ arkitekt_next/cli/commands/kabinet/build.py,sha256=uxAxaPL2bBXhNXFho5NsexYslLWqCiK11vFDbEh7yZE,10689
95
96
  arkitekt_next/cli/commands/kabinet/init.py,sha256=otVakPcD6bC-7VtmqiLWNZKi3BE9evtMVv0TOGRnaD4,3583
96
- arkitekt_next/cli/commands/kabinet/io.py,sha256=0u7tCOgojuLIwtjW8pJQZcKvzsRq37mIWVuc4ZOQZlk,5556
97
+ arkitekt_next/cli/commands/kabinet/io.py,sha256=1jFMDAYbZ4Ius0sZe_UDngxLJHaf-x9O86l2geMUrpo,5567
97
98
  arkitekt_next/cli/commands/kabinet/main.py,sha256=PFc8JGeSxbU8ZzxPlFDQp7--ogQyxL9sGQ4pA8XQmEo,1314
98
99
  arkitekt_next/cli/commands/kabinet/publish.py,sha256=B22UKmJMkiC1GZl--Nxwm-AWpAYaZ0o0Wo4t1iTJQ2M,3538
99
100
  arkitekt_next/cli/commands/kabinet/stage.py,sha256=kOkZ_GCkIzCEUmtc80u35hFwU4uOtfXODfhrPuHMaMk,2013
@@ -106,10 +107,10 @@ arkitekt_next/cli/commands/manifest/main.py,sha256=b_kz8wPYF1eHv_9WmB9Gop8TlDUgf
106
107
  arkitekt_next/cli/commands/manifest/scopes.py,sha256=sw0HRy8GliEcmx3Sh6cPRpBkf1vxAGJwLgIO63ZB_t4,4879
107
108
  arkitekt_next/cli/commands/manifest/version.py,sha256=3JdcXqiFeCvIaEpsMfeSKWU_uQehDkEawEpdSxWaKSA,4822
108
109
  arkitekt_next/cli/commands/run/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
109
- arkitekt_next/cli/commands/run/dev.py,sha256=VRaPAyC1X0xLWKrzxb5KU-0F3xVvsIR_d5QCTovzriM,10060
110
+ arkitekt_next/cli/commands/run/dev.py,sha256=eF7LMHxqqwelW82BscKDqJJuUhnuupyMBf53LsC2yms,10461
110
111
  arkitekt_next/cli/commands/run/main.py,sha256=0bNO3DqwbZ4ddMsDWbCGmlPD6Cs3Jlg4yh2-zilsEbY,552
111
112
  arkitekt_next/cli/commands/run/prod.py,sha256=EqDMa_eYNaffHZOBHGQEGNJVKdq8NHCgfoqK8yH63B4,1637
112
- arkitekt_next/cli/commands/run/utils.py,sha256=UwlybCNWs7GMtErXw9pZWaVudrLk2fRMKQ7m_PoMj5A,329
113
+ arkitekt_next/cli/commands/run/utils.py,sha256=NnvB9_EphzbQU13MkoGq3sxbt49-ykOOjquTnSjk6k0,617
113
114
  arkitekt_next/cli/dockerfiles/vanilla.dockerfile,sha256=0ujdG22rZ6v2n5TMHNA4HR5f1PP9KJvAQORz-DGHBpo,184
114
115
  arkitekt_next/cli/schemas/fluss.schema.graphql,sha256=MqrSpOxtWO9kWFCoUn9ySBPhIH3XozFiqrQl2zjEbiQ,35803
115
116
  arkitekt_next/cli/schemas/gucker.schema.graphql,sha256=r_KL-MoFRCGUobbtcBLCnpmFcali2A12TguynqQgmN4,152947
@@ -143,8 +144,8 @@ arkitekt_next/qt/assets/light/green pulse.gif,sha256=cUd2F3Qlvjb7SnsU-LjGgeLTa8K
143
144
  arkitekt_next/qt/assets/light/orange pulse.gif,sha256=0gDvrRed0mzZZPHB4tP6vptx7myUCAa_hEVGdjRhNy8,94769
144
145
  arkitekt_next/qt/assets/light/pink pulse.gif,sha256=rxd6ZTHSIG9JZuuHhi3jiSB_JYFBZpy7OWUeZETlhQ4,107513
145
146
  arkitekt_next/qt/assets/light/red pulse.gif,sha256=U7WLbZvSl5e-Ob5RmawtlC0Rh9VVHxkjDbGjj7NYVUo,108749
146
- arkitekt_next-0.11.0.dist-info/METADATA,sha256=-sny-SGzpCCja8stX0JfGZU7obgJOtbFimoXuQu_B_Y,5754
147
- arkitekt_next-0.11.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
148
- arkitekt_next-0.11.0.dist-info/entry_points.txt,sha256=IY-mUEDBU6QncqnYw-Ufw5bvNNXjqSlLf1uexZZCJhc,61
149
- arkitekt_next-0.11.0.dist-info/licenses/LICENSE,sha256=YZ2oRjC248t-GpoEyw7J13vwKYNG6zhYMaEAix6EzF0,1089
150
- arkitekt_next-0.11.0.dist-info/RECORD,,
147
+ arkitekt_next-0.12.0.dist-info/METADATA,sha256=boj9ueLrCGDKqnpRCf72m6uOFQgTuLxIbm7ph654OR0,5780
148
+ arkitekt_next-0.12.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
149
+ arkitekt_next-0.12.0.dist-info/entry_points.txt,sha256=IY-mUEDBU6QncqnYw-Ufw5bvNNXjqSlLf1uexZZCJhc,61
150
+ arkitekt_next-0.12.0.dist-info/licenses/LICENSE,sha256=YZ2oRjC248t-GpoEyw7J13vwKYNG6zhYMaEAix6EzF0,1089
151
+ arkitekt_next-0.12.0.dist-info/RECORD,,