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.
- arkitekt_next/__init__.py +43 -0
- arkitekt_next/apps/__init__.py +3 -0
- arkitekt_next/apps/easy.py +99 -0
- arkitekt_next/apps/next.py +40 -0
- arkitekt_next/apps/qt.py +97 -0
- arkitekt_next/apps/service/__init__.py +3 -0
- arkitekt_next/apps/service/fakts.py +88 -0
- arkitekt_next/apps/service/fakts_next.py +79 -0
- arkitekt_next/apps/service/fakts_qt.py +82 -0
- arkitekt_next/apps/service/fluss_next.py +31 -0
- arkitekt_next/apps/service/grant_registry.py +27 -0
- arkitekt_next/apps/service/herre.py +24 -0
- arkitekt_next/apps/service/herre_qt.py +57 -0
- arkitekt_next/apps/service/kabinet.py +31 -0
- arkitekt_next/apps/service/mikro_next.py +81 -0
- arkitekt_next/apps/service/rekuest_next.py +53 -0
- arkitekt_next/apps/service/unlok_next.py +32 -0
- arkitekt_next/apps/types.py +53 -0
- arkitekt_next/builders.py +264 -0
- arkitekt_next/cli/__init__.py +0 -0
- arkitekt_next/cli/commands/call/__init__.py +0 -0
- arkitekt_next/cli/commands/call/local.py +132 -0
- arkitekt_next/cli/commands/call/main.py +22 -0
- arkitekt_next/cli/commands/call/remote.py +90 -0
- arkitekt_next/cli/commands/gen/__init__.py +0 -0
- arkitekt_next/cli/commands/gen/compile.py +45 -0
- arkitekt_next/cli/commands/gen/init.py +122 -0
- arkitekt_next/cli/commands/gen/main.py +29 -0
- arkitekt_next/cli/commands/gen/watch.py +32 -0
- arkitekt_next/cli/commands/init/__init__.py +0 -0
- arkitekt_next/cli/commands/init/main.py +194 -0
- arkitekt_next/cli/commands/inspect/__init__.py +0 -0
- arkitekt_next/cli/commands/inspect/definitions.py +53 -0
- arkitekt_next/cli/commands/inspect/main.py +22 -0
- arkitekt_next/cli/commands/inspect/variables.py +92 -0
- arkitekt_next/cli/commands/manifest/__init__.py +0 -0
- arkitekt_next/cli/commands/manifest/inspect.py +42 -0
- arkitekt_next/cli/commands/manifest/main.py +25 -0
- arkitekt_next/cli/commands/manifest/scopes.py +155 -0
- arkitekt_next/cli/commands/manifest/version.py +147 -0
- arkitekt_next/cli/commands/manifest/wizard.py +94 -0
- arkitekt_next/cli/commands/port/__init__.py +0 -0
- arkitekt_next/cli/commands/port/build.py +231 -0
- arkitekt_next/cli/commands/port/init.py +82 -0
- arkitekt_next/cli/commands/port/main.py +31 -0
- arkitekt_next/cli/commands/port/publish.py +102 -0
- arkitekt_next/cli/commands/port/stage.py +59 -0
- arkitekt_next/cli/commands/port/utils.py +47 -0
- arkitekt_next/cli/commands/port/validate.py +78 -0
- arkitekt_next/cli/commands/port/wizard.py +329 -0
- arkitekt_next/cli/commands/run/__init__.py +0 -0
- arkitekt_next/cli/commands/run/dev.py +349 -0
- arkitekt_next/cli/commands/run/main.py +22 -0
- arkitekt_next/cli/commands/run/prod.py +57 -0
- arkitekt_next/cli/commands/run/utils.py +10 -0
- arkitekt_next/cli/commands/server/__init__.py +0 -0
- arkitekt_next/cli/commands/server/down.py +56 -0
- arkitekt_next/cli/commands/server/init.py +74 -0
- arkitekt_next/cli/commands/server/inspect.py +59 -0
- arkitekt_next/cli/commands/server/main.py +33 -0
- arkitekt_next/cli/commands/server/open.py +66 -0
- arkitekt_next/cli/commands/server/remove.py +60 -0
- arkitekt_next/cli/commands/server/stop.py +56 -0
- arkitekt_next/cli/commands/server/up.py +70 -0
- arkitekt_next/cli/commands/server/utils.py +33 -0
- arkitekt_next/cli/configs/base.yaml +867 -0
- arkitekt_next/cli/constants.py +63 -0
- arkitekt_next/cli/dockerfiles/vanilla.dockerfile +8 -0
- arkitekt_next/cli/errors.py +4 -0
- arkitekt_next/cli/inspect.py +1 -0
- arkitekt_next/cli/io.py +255 -0
- arkitekt_next/cli/main.py +83 -0
- arkitekt_next/cli/options.py +166 -0
- arkitekt_next/cli/schemas/fluss.schema.graphql +2446 -0
- arkitekt_next/cli/schemas/gucker.schema.graphql +8908 -0
- arkitekt_next/cli/schemas/kabinet.schema.graphql +515 -0
- arkitekt_next/cli/schemas/kluster.schema.graphql +109 -0
- arkitekt_next/cli/schemas/konviktion.schema.graphql +70 -0
- arkitekt_next/cli/schemas/kuay.schema.graphql +356 -0
- arkitekt_next/cli/schemas/mikro.schema.graphql +8908 -0
- arkitekt_next/cli/schemas/mikro_next.schema.graphql +1639 -0
- arkitekt_next/cli/schemas/napari.schema.graphql +8908 -0
- arkitekt_next/cli/schemas/omero_ark.schema.graphql +100 -0
- arkitekt_next/cli/schemas/port.schema.graphql +356 -0
- arkitekt_next/cli/schemas/rekuest.schema.graphql +4630 -0
- arkitekt_next/cli/schemas/rekuest_next.schema.graphql +1159 -0
- arkitekt_next/cli/schemas/unlok.schema.graphql +1013 -0
- arkitekt_next/cli/templates/filter.py +26 -0
- arkitekt_next/cli/templates/simple.py +67 -0
- arkitekt_next/cli/texts.py +20 -0
- arkitekt_next/cli/types.py +365 -0
- arkitekt_next/cli/ui.py +111 -0
- arkitekt_next/cli/utils.py +15 -0
- arkitekt_next/cli/validators.py +17 -0
- arkitekt_next/cli/vars.py +39 -0
- arkitekt_next/cli/versions/v1.yaml +1 -0
- arkitekt_next/constants.py +6 -0
- arkitekt_next/model.py +110 -0
- arkitekt_next/qt/__init__.py +9 -0
- arkitekt_next/qt/assets/dark/gear.png +0 -0
- arkitekt_next/qt/assets/dark/green pulse.gif +0 -0
- arkitekt_next/qt/assets/dark/orange pulse.gif +0 -0
- arkitekt_next/qt/assets/dark/pink pulse.gif +0 -0
- arkitekt_next/qt/assets/dark/red pulse.gif +0 -0
- arkitekt_next/qt/assets/light/gear.png +0 -0
- arkitekt_next/qt/assets/light/green pulse.gif +0 -0
- arkitekt_next/qt/assets/light/orange pulse.gif +0 -0
- arkitekt_next/qt/assets/light/pink pulse.gif +0 -0
- arkitekt_next/qt/assets/light/red pulse.gif +0 -0
- arkitekt_next/qt/magic_bar.py +545 -0
- arkitekt_next/qt/utils.py +30 -0
- arkitekt_next/service_registry.py +51 -0
- arkitekt_next/tqdm.py +43 -0
- arkitekt_next/utils.py +38 -0
- arkitekt_next-0.7.8.dist-info/LICENSE +21 -0
- arkitekt_next-0.7.8.dist-info/METADATA +155 -0
- arkitekt_next-0.7.8.dist-info/RECORD +119 -0
- arkitekt_next-0.7.8.dist-info/WHEEL +4 -0
- 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
|