arkitekt-next 0.7.8__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 (119) hide show
  1. arkitekt_next/__init__.py +43 -0
  2. arkitekt_next/apps/__init__.py +3 -0
  3. arkitekt_next/apps/easy.py +99 -0
  4. arkitekt_next/apps/next.py +40 -0
  5. arkitekt_next/apps/qt.py +97 -0
  6. arkitekt_next/apps/service/__init__.py +3 -0
  7. arkitekt_next/apps/service/fakts.py +88 -0
  8. arkitekt_next/apps/service/fakts_next.py +79 -0
  9. arkitekt_next/apps/service/fakts_qt.py +82 -0
  10. arkitekt_next/apps/service/fluss_next.py +31 -0
  11. arkitekt_next/apps/service/grant_registry.py +27 -0
  12. arkitekt_next/apps/service/herre.py +24 -0
  13. arkitekt_next/apps/service/herre_qt.py +57 -0
  14. arkitekt_next/apps/service/kabinet.py +31 -0
  15. arkitekt_next/apps/service/mikro_next.py +81 -0
  16. arkitekt_next/apps/service/rekuest_next.py +53 -0
  17. arkitekt_next/apps/service/unlok_next.py +32 -0
  18. arkitekt_next/apps/types.py +53 -0
  19. arkitekt_next/builders.py +264 -0
  20. arkitekt_next/cli/__init__.py +0 -0
  21. arkitekt_next/cli/commands/call/__init__.py +0 -0
  22. arkitekt_next/cli/commands/call/local.py +132 -0
  23. arkitekt_next/cli/commands/call/main.py +22 -0
  24. arkitekt_next/cli/commands/call/remote.py +90 -0
  25. arkitekt_next/cli/commands/gen/__init__.py +0 -0
  26. arkitekt_next/cli/commands/gen/compile.py +45 -0
  27. arkitekt_next/cli/commands/gen/init.py +122 -0
  28. arkitekt_next/cli/commands/gen/main.py +29 -0
  29. arkitekt_next/cli/commands/gen/watch.py +32 -0
  30. arkitekt_next/cli/commands/init/__init__.py +0 -0
  31. arkitekt_next/cli/commands/init/main.py +194 -0
  32. arkitekt_next/cli/commands/inspect/__init__.py +0 -0
  33. arkitekt_next/cli/commands/inspect/definitions.py +53 -0
  34. arkitekt_next/cli/commands/inspect/main.py +22 -0
  35. arkitekt_next/cli/commands/inspect/variables.py +92 -0
  36. arkitekt_next/cli/commands/manifest/__init__.py +0 -0
  37. arkitekt_next/cli/commands/manifest/inspect.py +42 -0
  38. arkitekt_next/cli/commands/manifest/main.py +25 -0
  39. arkitekt_next/cli/commands/manifest/scopes.py +155 -0
  40. arkitekt_next/cli/commands/manifest/version.py +147 -0
  41. arkitekt_next/cli/commands/manifest/wizard.py +94 -0
  42. arkitekt_next/cli/commands/port/__init__.py +0 -0
  43. arkitekt_next/cli/commands/port/build.py +231 -0
  44. arkitekt_next/cli/commands/port/init.py +82 -0
  45. arkitekt_next/cli/commands/port/main.py +31 -0
  46. arkitekt_next/cli/commands/port/publish.py +102 -0
  47. arkitekt_next/cli/commands/port/stage.py +59 -0
  48. arkitekt_next/cli/commands/port/utils.py +47 -0
  49. arkitekt_next/cli/commands/port/validate.py +78 -0
  50. arkitekt_next/cli/commands/port/wizard.py +329 -0
  51. arkitekt_next/cli/commands/run/__init__.py +0 -0
  52. arkitekt_next/cli/commands/run/dev.py +349 -0
  53. arkitekt_next/cli/commands/run/main.py +22 -0
  54. arkitekt_next/cli/commands/run/prod.py +57 -0
  55. arkitekt_next/cli/commands/run/utils.py +10 -0
  56. arkitekt_next/cli/commands/server/__init__.py +0 -0
  57. arkitekt_next/cli/commands/server/down.py +56 -0
  58. arkitekt_next/cli/commands/server/init.py +74 -0
  59. arkitekt_next/cli/commands/server/inspect.py +59 -0
  60. arkitekt_next/cli/commands/server/main.py +33 -0
  61. arkitekt_next/cli/commands/server/open.py +66 -0
  62. arkitekt_next/cli/commands/server/remove.py +60 -0
  63. arkitekt_next/cli/commands/server/stop.py +56 -0
  64. arkitekt_next/cli/commands/server/up.py +70 -0
  65. arkitekt_next/cli/commands/server/utils.py +33 -0
  66. arkitekt_next/cli/configs/base.yaml +867 -0
  67. arkitekt_next/cli/constants.py +63 -0
  68. arkitekt_next/cli/dockerfiles/vanilla.dockerfile +8 -0
  69. arkitekt_next/cli/errors.py +4 -0
  70. arkitekt_next/cli/inspect.py +1 -0
  71. arkitekt_next/cli/io.py +255 -0
  72. arkitekt_next/cli/main.py +83 -0
  73. arkitekt_next/cli/options.py +166 -0
  74. arkitekt_next/cli/schemas/fluss.schema.graphql +2446 -0
  75. arkitekt_next/cli/schemas/gucker.schema.graphql +8908 -0
  76. arkitekt_next/cli/schemas/kabinet.schema.graphql +515 -0
  77. arkitekt_next/cli/schemas/kluster.schema.graphql +109 -0
  78. arkitekt_next/cli/schemas/konviktion.schema.graphql +70 -0
  79. arkitekt_next/cli/schemas/kuay.schema.graphql +356 -0
  80. arkitekt_next/cli/schemas/mikro.schema.graphql +8908 -0
  81. arkitekt_next/cli/schemas/mikro_next.schema.graphql +1639 -0
  82. arkitekt_next/cli/schemas/napari.schema.graphql +8908 -0
  83. arkitekt_next/cli/schemas/omero_ark.schema.graphql +100 -0
  84. arkitekt_next/cli/schemas/port.schema.graphql +356 -0
  85. arkitekt_next/cli/schemas/rekuest.schema.graphql +4630 -0
  86. arkitekt_next/cli/schemas/rekuest_next.schema.graphql +1159 -0
  87. arkitekt_next/cli/schemas/unlok.schema.graphql +1013 -0
  88. arkitekt_next/cli/templates/filter.py +26 -0
  89. arkitekt_next/cli/templates/simple.py +67 -0
  90. arkitekt_next/cli/texts.py +20 -0
  91. arkitekt_next/cli/types.py +365 -0
  92. arkitekt_next/cli/ui.py +111 -0
  93. arkitekt_next/cli/utils.py +15 -0
  94. arkitekt_next/cli/validators.py +17 -0
  95. arkitekt_next/cli/vars.py +39 -0
  96. arkitekt_next/cli/versions/v1.yaml +1 -0
  97. arkitekt_next/constants.py +6 -0
  98. arkitekt_next/model.py +110 -0
  99. arkitekt_next/qt/__init__.py +9 -0
  100. arkitekt_next/qt/assets/dark/gear.png +0 -0
  101. arkitekt_next/qt/assets/dark/green pulse.gif +0 -0
  102. arkitekt_next/qt/assets/dark/orange pulse.gif +0 -0
  103. arkitekt_next/qt/assets/dark/pink pulse.gif +0 -0
  104. arkitekt_next/qt/assets/dark/red pulse.gif +0 -0
  105. arkitekt_next/qt/assets/light/gear.png +0 -0
  106. arkitekt_next/qt/assets/light/green pulse.gif +0 -0
  107. arkitekt_next/qt/assets/light/orange pulse.gif +0 -0
  108. arkitekt_next/qt/assets/light/pink pulse.gif +0 -0
  109. arkitekt_next/qt/assets/light/red pulse.gif +0 -0
  110. arkitekt_next/qt/magic_bar.py +545 -0
  111. arkitekt_next/qt/utils.py +30 -0
  112. arkitekt_next/service_registry.py +51 -0
  113. arkitekt_next/tqdm.py +43 -0
  114. arkitekt_next/utils.py +38 -0
  115. arkitekt_next-0.7.8.dist-info/LICENSE +21 -0
  116. arkitekt_next-0.7.8.dist-info/METADATA +155 -0
  117. arkitekt_next-0.7.8.dist-info/RECORD +119 -0
  118. arkitekt_next-0.7.8.dist-info/WHEEL +4 -0
  119. arkitekt_next-0.7.8.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,90 @@
1
+ import rich_click as click
2
+ from arkitekt_next.cli.options import *
3
+ import asyncio
4
+ from arkitekt_next.cli.ui import construct_run_panel
5
+ from importlib import import_module
6
+ from rekuest.postmans.utils import arkiuse
7
+ import rich_click as click
8
+ from arkitekt_next.cli.options import *
9
+ import asyncio
10
+ from arkitekt_next.cli.ui import construct_run_panel
11
+ from importlib import import_module
12
+ from arkitekt_next.cli.utils import import_builder
13
+ from arkitekt_next.constants import DEFAULT_ARKITEKT_URL
14
+
15
+
16
+ async def call_app(
17
+ app: App,
18
+ hash,
19
+ arg,
20
+ ):
21
+ async with app:
22
+ async with arkiuse(
23
+ hash=hash,
24
+ postman=app.rekuest.postman,
25
+ ) as a:
26
+ print(arg)
27
+ print(await a.aassign(kwargs=arg))
28
+
29
+
30
+ @click.command("prod")
31
+ @click.option(
32
+ "--url",
33
+ help="The fakts url for connection",
34
+ default=DEFAULT_ARKITEKT_URL,
35
+ envvar="FAKTS_URL",
36
+ )
37
+ @with_builder
38
+ @with_token
39
+ @with_instance_id
40
+ @with_headless
41
+ @with_log_level
42
+ @with_skip_cache
43
+ @click.pass_context
44
+ @click.option(
45
+ "--arg",
46
+ "-a",
47
+ "args",
48
+ help="Key Value pairs for the setup",
49
+ type=(str, str),
50
+ multiple=True,
51
+ )
52
+ @click.option(
53
+ "--hash",
54
+ "-h",
55
+ help="The hash of the node to run",
56
+ type=str,
57
+ )
58
+ def remote(ctx, entrypoint=None, builder=None, args=None, hash=str, **builder_kwargs):
59
+ """ALlows you to run a get the output of a node in a remote app.
60
+
61
+ This is useful for debugging and testing. In this mode the app itself will not
62
+ be run, so local nodes cannot be called. Only nodes that are availabble on your
63
+ arkitekt_next server can be called.
64
+
65
+ """
66
+
67
+ manifest = get_manifest(ctx)
68
+ console = get_console(ctx)
69
+ entrypoint = entrypoint or manifest.entrypoint
70
+
71
+ kwargs = dict(args or [])
72
+
73
+ builder = import_builder(builder)
74
+
75
+ with console.status("Loading entrypoint module..."):
76
+ try:
77
+ import_module(entrypoint)
78
+ except ModuleNotFoundError as e:
79
+ console.print(f"Could not find entrypoint module {entrypoint}")
80
+ raise e
81
+
82
+ app = builder(
83
+ **manifest.to_builder_dict(),
84
+ **builder_kwargs,
85
+ )
86
+
87
+ panel = construct_run_panel(app)
88
+ console.print(panel)
89
+
90
+ asyncio.run(call_app(app, hash, kwargs))
File without changes
@@ -0,0 +1,45 @@
1
+ import rich_click as click
2
+ import os
3
+
4
+
5
+ @click.command()
6
+ @click.argument("projects", default=None, required=False, nargs=-1)
7
+ @click.option(
8
+ "--config",
9
+ help="The config to use",
10
+ type=click.Path(exists=True),
11
+ default=None,
12
+ )
13
+ def compile(projects, config):
14
+ """Genererate the code of a project"
15
+
16
+ Uses a previously generated graphql-config.yaml file to generate the code for a or multiple projects.
17
+ If no project is specified, all projects will be generated.
18
+
19
+
20
+ """
21
+ app_directory = os.getcwd()
22
+
23
+ from turms.run import scan_folder_for_single_config, load_projects_from_configpath
24
+ from turms.cli.main import generate_projects
25
+
26
+ config = config or scan_folder_for_single_config(app_directory)
27
+ if not config:
28
+ raise click.ClickException(
29
+ f"No config file found. Please run `arkitekt_next gen init` in {app_directory} to create a default config file or specify a config file with the --config flag"
30
+ )
31
+
32
+ parsing_projects = load_projects_from_configpath(config)
33
+ if projects:
34
+ parsing_projects = {
35
+ key: value for key, value in parsing_projects.items() if key in projects
36
+ }
37
+
38
+ if not parsing_projects:
39
+ raise click.ClickException(
40
+ f"No projects found with the name '{projects}'. Available Projects: {', '.join(parsing_projects.keys())}"
41
+ )
42
+
43
+ generate_projects(parsing_projects, title="ArkitektNext Compile")
44
+
45
+ pass
@@ -0,0 +1,122 @@
1
+ import os
2
+ import shutil
3
+ import rich_click as click
4
+ from arkitekt_next.cli.options import (
5
+ with_documents,
6
+ with_graphql_config,
7
+ with_api_path,
8
+ with_boring,
9
+ with_choose_services,
10
+ with_schemas,
11
+ with_seperate_document_dirs,
12
+ )
13
+ import yaml
14
+ from arkitekt_next.cli.utils import build_relative_dir
15
+ from arkitekt_next.cli.vars import get_console, get_manifest
16
+
17
+
18
+ @click.command()
19
+ @with_seperate_document_dirs
20
+ @with_boring
21
+ @with_choose_services
22
+ @with_graphql_config
23
+ @with_api_path
24
+ @with_schemas
25
+ @with_graphql_config
26
+ @with_documents
27
+ @click.pass_context
28
+ def init(ctx, boring, services, config, documents, schemas, path, seperate_doc_dirs):
29
+ """Initialize code generation for the arkitekt_next app
30
+
31
+ Code generation for API's is done with the help of GraphQL Code Generation
32
+ that is powered by turms. This command initializes the code generation for
33
+ the app. It creates the necessary folders and files for the code generation
34
+ to work. It also creates a graphql config file that is used by turms to
35
+ generate the code.
36
+
37
+ """
38
+ app_directory = os.getcwd()
39
+
40
+ app_api_path = os.path.join(app_directory, path)
41
+ app_documents = os.path.join(app_directory, "graphql", "documents")
42
+
43
+ app_schemas = os.path.join(app_directory, "graphql", "schemas")
44
+
45
+ if documents:
46
+ os.makedirs(app_documents, exist_ok=True)
47
+ if schemas:
48
+ os.makedirs(app_schemas, exist_ok=True)
49
+ if path:
50
+ os.makedirs(app_api_path, exist_ok=True)
51
+
52
+ # Initializing the config
53
+ projects = {}
54
+
55
+ base_config = yaml.load(
56
+ open(build_relative_dir("configs", "base.yaml"), "r"), Loader=yaml.FullLoader
57
+ )
58
+
59
+ for service in services:
60
+ schema_path = build_relative_dir("schemas", f"{service}.schema.graphql")
61
+
62
+ if documents:
63
+ os.makedirs(os.path.join(app_documents, service), exist_ok=True)
64
+ if seperate_doc_dirs:
65
+ os.makedirs(
66
+ os.path.join(app_documents, service, "queries"), exist_ok=True
67
+ )
68
+ os.makedirs(
69
+ os.path.join(app_documents, service, "mutations"), exist_ok=True
70
+ )
71
+ os.makedirs(
72
+ os.path.join(app_documents, service, "subscriptions"), exist_ok=True
73
+ )
74
+
75
+ if schemas:
76
+ if os.path.exists(schema_path):
77
+ try:
78
+ shutil.copyfile(
79
+ schema_path,
80
+ os.path.join(app_schemas, service + ".graphql"),
81
+ )
82
+ except FileExistsError:
83
+ if click.confirm(
84
+ f"Schema for {service} already exist. Do you want to overwrite them?"
85
+ ):
86
+ shutil.copyfile(
87
+ schema_path,
88
+ os.path.join(app_schemas, service + ".graphql"),
89
+ )
90
+ else:
91
+ get_console(ctx).print(f"[red]No schema found for {service} [/]")
92
+
93
+ try:
94
+ project = base_config["projects"][service]
95
+ except KeyError:
96
+ get_console(ctx).print(f"[red]No config found for {service} [/]")
97
+ continue
98
+
99
+ if schemas:
100
+ project["schema"] = os.path.join(app_schemas, service + ".graphql")
101
+ if documents:
102
+ project["documents"] = (
103
+ os.path.join(app_documents, service) + "/**/*.graphql"
104
+ )
105
+
106
+ project["extensions"]["turms"]["out_dir"] = path
107
+ project["extensions"]["turms"]["generated_name"] = f"{service}.py"
108
+
109
+ projects[service] = project
110
+
111
+ if os.path.exists(config):
112
+ if not click.confirm(
113
+ f"GraphQL Config file already exists. Do you want to overwrite?"
114
+ ):
115
+ click.echo("Aborting...")
116
+ ctx.abort()
117
+
118
+ graph_config_path = os.path.join(app_directory, config)
119
+ yaml.safe_dump(
120
+ {"projects": projects}, open(graph_config_path, "w"), sort_keys=False
121
+ )
122
+ get_console(ctx).print(f"Config file written to {graph_config_path}")
@@ -0,0 +1,29 @@
1
+ import rich_click as click
2
+ from .watch import watch
3
+ from .compile import compile
4
+ from .init import init
5
+
6
+
7
+ @click.group()
8
+ def gen():
9
+ """Codegeneration tools for ArkitektNext Apps (requires turms)
10
+
11
+ Code generation for API's is done with the help of GraphQL Code Generation
12
+ that is powered by [link=https://github.com/jhnnsrs/turms]turms[/link]. Simply
13
+ design your API in the documents folder and run `arkitekt_next gen compile` to
14
+ create fully typed code for your API. You can also run `arkitekt_next gen watch`
15
+ to automatically generate code when your documents change. This is useful
16
+ for development.
17
+
18
+ """
19
+ try:
20
+ pass
21
+ except ImportError as e:
22
+ raise click.ClickException(
23
+ "Turms is not installed. Please install turms first before using the arkitekt_next codegen."
24
+ ) from e
25
+
26
+
27
+ gen.add_command(watch, "watch")
28
+ gen.add_command(compile, "compile")
29
+ gen.add_command(init, "init")
@@ -0,0 +1,32 @@
1
+ import rich_click as click
2
+ import os
3
+
4
+
5
+ @click.command()
6
+ @click.argument("project", default=None, required=False)
7
+ @click.option(
8
+ "--config", help="The config to use", type=click.Path(exists=True), default=None
9
+ )
10
+ def watch(project, config):
11
+ """Watch your projects documents and automatically generate code when they change
12
+
13
+ This command will watch all the projects in your config file and automatically
14
+ generate code when the documents change. This is useful for development.
15
+
16
+ """
17
+ app_directory = os.getcwd()
18
+
19
+ from turms.run import scan_folder_for_single_config, load_projects_from_configpath
20
+ from turms.cli.main import watch_projects
21
+
22
+ config = config or scan_folder_for_single_config(app_directory)
23
+ if not config:
24
+ raise click.ClickException(
25
+ f"No config file found. Please run `arkitekt_next gen init` in {app_directory} to create a default config file or specify a config file with the --config flag"
26
+ )
27
+
28
+ projects = load_projects_from_configpath(config)
29
+ if project:
30
+ projects = {key: value for key, value in projects.items() if key == project}
31
+
32
+ watch_projects(projects, title="ArkitektNext Code Watch")
File without changes
@@ -0,0 +1,194 @@
1
+ import rich_click as click
2
+ import os
3
+ from arkitekt_next.cli.constants import *
4
+ from getpass import getuser
5
+ from arkitekt_next.cli.types import Manifest, Requirement
6
+ from arkitekt_next.cli.vars import get_manifest, get_console
7
+ import semver
8
+ from arkitekt_next.cli.io import write_manifest
9
+ from rich.panel import Panel
10
+ from typing import Optional, List
11
+
12
+
13
+ def check_overwrite_app(ctx, param, value):
14
+ """Callback to check and prompt for file overwrite."""
15
+
16
+ app_file = ctx.params["entrypoint"] + ".py"
17
+ if os.path.exists(app_file) and not value:
18
+ should_overwrite = click.confirm(
19
+ "Entrypoint File already exists. Do you want to overwrite?"
20
+ )
21
+ return should_overwrite
22
+
23
+ return value
24
+
25
+
26
+ def check_overwrite(ctx, param, value):
27
+ """Callback to check and prompt for file overwrite."""
28
+
29
+ try:
30
+ manifest = get_manifest(ctx)
31
+ if not value:
32
+ should_overwrite = click.confirm(
33
+ f"Another ArkitektNext app {manifest.to_console_string()} exists already at {os.getcwd()}?. Do you want to overwrite?",
34
+ abort=True,
35
+ )
36
+ if not should_overwrite:
37
+ ctx.abort()
38
+ except click.ClickException:
39
+ pass
40
+
41
+ return value
42
+
43
+
44
+ def ensure_semver(ctx, param, value):
45
+ """Callback to check and prompt for file overwrite."""
46
+
47
+ if not value:
48
+ value = click.prompt(
49
+ "The version of your app",
50
+ default="0.0.1",
51
+ )
52
+
53
+ while not semver.Version.is_valid(value):
54
+ get_console().print(
55
+ "ArkitektNext versions need to follow [link=https://semver.org]semver[/link]. Please choose a correct format (examples: 0.0.0, 0.1.0, 0.0.0-alpha.1)"
56
+ )
57
+ value = click.prompt(
58
+ "The version of your app",
59
+ default="0.0.1",
60
+ )
61
+
62
+ return value
63
+
64
+
65
+ @click.command()
66
+ @click.option(
67
+ "--overwrite-manifest",
68
+ "-om",
69
+ help="Should we overwrite the existing manifest if it already exists?",
70
+ is_flag=True,
71
+ default=False,
72
+ callback=check_overwrite,
73
+ )
74
+ @click.option(
75
+ "--template",
76
+ "-t",
77
+ help="The template to use. You can choose from a variety of preconfigured templates. They are just starting points and can be changed later.",
78
+ type=click.Choice(compile_templates()),
79
+ default="simple",
80
+ )
81
+ @click.option(
82
+ "--identifier",
83
+ "-i",
84
+ help="The identifier of your app. This will be used to identify your app in the ArkitektNext ecosystem. It should be unique and should follow the [link=https://en.wikipedia.org/wiki/Reverse_domain_name_notation]reverse domain name notation[/link] (example: com.example.myapp)",
85
+ prompt="Your app identifier",
86
+ default=os.path.basename(os.getcwd()),
87
+ )
88
+ @click.option(
89
+ "--version",
90
+ "-v",
91
+ help="The version of your app. Needs to follow [link=https://semver.org/]semantic versioning[/link].",
92
+ default="0.0.1",
93
+ callback=ensure_semver,
94
+ )
95
+ @click.option(
96
+ "--author",
97
+ help="The author of your app. This will be shown to users of your app",
98
+ prompt="Your name",
99
+ default=getuser(),
100
+ )
101
+ @click.option(
102
+ "--template",
103
+ help="Which template to use top create entrypoint",
104
+ prompt="Your app file template",
105
+ type=click.Choice(compile_templates()),
106
+ default="simple",
107
+ )
108
+ @click.option(
109
+ "--logo",
110
+ help="Which logo to use for this app, needs to be a valid url",
111
+ required=False,
112
+ )
113
+ @click.option(
114
+ "--entrypoint",
115
+ "-e",
116
+ help="The entrypoint of your app. This will be the name of the python file. Omit the .py ending",
117
+ prompt="Your app file",
118
+ default="app",
119
+ )
120
+ @click.option(
121
+ "--overwrite-app",
122
+ "-oa",
123
+ help="Do you want to overwrite the app file if it exists?",
124
+ is_flag=True,
125
+ default=False,
126
+ callback=check_overwrite_app,
127
+ )
128
+ @click.option(
129
+ "--requirements",
130
+ "-r",
131
+ help="Dedicated hardware requirements for this app. They will be used to inform Plugin Schedulers like port, on which compute resources to put your plugin app, once deployed.You can choose multiple for your app. For a list of requirements, run `arkitekt_next manifest requirements available`",
132
+ type=click.Choice(compile_requirements()),
133
+ multiple=True,
134
+ default=[],
135
+ )
136
+ @click.option(
137
+ "--scopes",
138
+ "-s",
139
+ help="The scopes of the app. You can choose multiple for your app. For a list of scopes, run `arkitekt_next manifest scopes available`",
140
+ type=click.Choice(compile_scopes()),
141
+ multiple=True,
142
+ default=["read"],
143
+ )
144
+ @click.pass_context
145
+ def init(
146
+ ctx,
147
+ identifier: str,
148
+ version: str,
149
+ author: str,
150
+ logo: Optional[str],
151
+ scopes: List[str],
152
+ template: str,
153
+ requirements: List[Requirement],
154
+ entrypoint: str,
155
+ overwrite_manifest: bool,
156
+ overwrite_app: bool,
157
+ ):
158
+ """Initializes an ArkitektNext app
159
+
160
+ This command will create a new ArkitektNext app in the current directory. It will
161
+ create a `.arkitekt_next` folder that will contain a manifest and a `app.py` file,
162
+ which will serve as the entrypoint for your app. By default, the app will be
163
+ initialized with a simple hello world app, but you can choose from a variety
164
+ of templates.
165
+
166
+ """
167
+
168
+ console = get_console(ctx)
169
+
170
+ manifest = Manifest(
171
+ logo=logo,
172
+ author=author,
173
+ identifier=identifier,
174
+ version=version,
175
+ scopes=scopes,
176
+ requirements=requirements,
177
+ entrypoint=entrypoint,
178
+ )
179
+
180
+ with open(build_relative_dir("templates", f"{template}.py")) as f:
181
+ template_app = f.read()
182
+
183
+ if not os.path.exists(f"{entrypoint}.py") or overwrite_app:
184
+ with open(f"{entrypoint}.py", "w") as f:
185
+ f.write(template_app)
186
+
187
+ write_manifest(manifest)
188
+ md = Panel(
189
+ f"{manifest.to_console_string()} was successfully initialized\n\n"
190
+ + "[not bold white]We are excited to see what you come up with!",
191
+ border_style="green",
192
+ style="green",
193
+ )
194
+ console.print(md)
File without changes
@@ -0,0 +1,53 @@
1
+ import rich_click as click
2
+ from importlib import import_module
3
+ from arkitekt_next.cli.vars import get_console, get_manifest
4
+ from arkitekt_next.cli.options import with_builder
5
+ import json
6
+
7
+
8
+ async def run_app(app):
9
+ async with app:
10
+ await app.rekuest.run()
11
+
12
+
13
+ @click.command("prod")
14
+ @click.pass_context
15
+ @click.option(
16
+ "--pretty",
17
+ "-p",
18
+ help="Should we just output json?",
19
+ is_flag=True,
20
+ default=False,
21
+ )
22
+ @with_builder
23
+ def definitions(
24
+ ctx,
25
+ pretty: bool,
26
+ builder=None,
27
+ ):
28
+ """Runs the app in production mode
29
+
30
+ \n
31
+ You can specify the builder to use with the --builder flag. By default, the easy builder is used, which is designed to be easy to use and to get started with.
32
+
33
+ """
34
+ from rekuest.definition.registry import get_default_definition_registry
35
+
36
+ manifest = get_manifest(ctx)
37
+ console = get_console(ctx)
38
+
39
+ entrypoint = manifest.entrypoint
40
+
41
+ with console.status("Loading entrypoint module..."):
42
+ try:
43
+ import_module(entrypoint)
44
+ except ModuleNotFoundError as e:
45
+ console.print(f"Could not find entrypoint module {entrypoint}")
46
+ raise e
47
+
48
+ definitions = get_default_definition_registry().dump()
49
+
50
+ if pretty:
51
+ console.print(json.dumps(definitions, indent=2))
52
+ else:
53
+ print(json.dumps(definitions))
@@ -0,0 +1,22 @@
1
+ import rich_click as click
2
+
3
+
4
+ import rich_click as click
5
+ from .variables import variables
6
+ from .definitions import definitions
7
+
8
+
9
+ @click.group()
10
+ @click.pass_context
11
+ def inspect(ctx):
12
+ """Inspects your arkitekt_next app
13
+
14
+ Inspects various parts of your arkitekt_next app. This is useful for debugging
15
+ and development. It also represents methods that are called by the arkitekt_next
16
+ server when you run your app in production mode.
17
+
18
+ """
19
+
20
+
21
+ inspect.add_command(variables, "variables")
22
+ inspect.add_command(definitions, "definitions")
@@ -0,0 +1,92 @@
1
+ from importlib import import_module
2
+ import inspect as pyinspect
3
+ import rich_click as click
4
+
5
+ from arkitekt_next.cli.ui import construct_leaking_group
6
+ from arkitekt_next.cli.vars import get_console, get_manifest
7
+ from rich.panel import Panel
8
+
9
+ import rich_click as click
10
+
11
+
12
+ def inspect_dangerous_variables(module_path):
13
+ """Inspect the module and return a dictionary of all the variables that are
14
+ not upper case and that are not classes, modules, functions or builtins.
15
+
16
+ This is used to check if a module is safe to import. Or if it runs code
17
+ that might be dangegours:
18
+
19
+ TODO: This is not the ebst way to do this. We should probably use the ast
20
+ module to parse the module and check for dangerous code. This is a quick
21
+ and dirty solution.
22
+
23
+ """
24
+
25
+ module = import_module(module_path)
26
+
27
+ dangerous_variables = {}
28
+
29
+ for key, value in pyinspect.getmembers(module):
30
+ if key.startswith("_"):
31
+ continue
32
+ if pyinspect.isclass(value):
33
+ continue
34
+ if pyinspect.ismodule(value):
35
+ continue
36
+ if pyinspect.isfunction(value):
37
+ continue
38
+ if pyinspect.isbuiltin(value):
39
+ continue
40
+
41
+ if type(value) in [str, float, int, bool, list, dict, tuple]:
42
+ if key != key.upper():
43
+ dangerous_variables[key] = value
44
+ continue
45
+
46
+ return dangerous_variables
47
+
48
+
49
+ def scan_module(module_path):
50
+ """Scan a module for dangerous variables."""
51
+ return inspect_dangerous_variables(module_path)
52
+
53
+
54
+ @click.command()
55
+ @click.pass_context
56
+ def variables(ctx, entrypoint):
57
+ """Scans your arkitekt_next app for unsafe variables
58
+
59
+ When designing an ArkitektNext app, you should not have variables in your
60
+ global scope, as on potential reloads (like for example in development)
61
+ these variables will be redefined and cause memory leaks and other issues.
62
+
63
+ You can use this command to scan your app for dangerous variables. If you
64
+ have any, you should consider moving them into a function or class.
65
+
66
+ """
67
+
68
+ console = get_console(ctx)
69
+
70
+ if not entrypoint:
71
+ manifest = get_manifest(ctx)
72
+ entrypoint = manifest.entrypoint
73
+
74
+ variables = scan_module(entrypoint)
75
+
76
+ if not variables:
77
+ console.print(
78
+ Panel(
79
+ "No dangerous variables found. You are good to go! 🎉",
80
+ style="green",
81
+ border_style="green",
82
+ title="ArkitektNext Scan",
83
+ )
84
+ )
85
+ return
86
+
87
+ group = construct_leaking_group(variables)
88
+
89
+ panel = Panel(
90
+ group, title="ArkitektNext Scan", expand=True, border_style="red", style="red"
91
+ )
92
+ console.print(panel)
File without changes