scale-nucleus 0.1.24__py3-none-any.whl → 0.6.4__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.
- cli/client.py +14 -0
- cli/datasets.py +77 -0
- cli/helpers/__init__.py +0 -0
- cli/helpers/nucleus_url.py +10 -0
- cli/helpers/web_helper.py +40 -0
- cli/install_completion.py +33 -0
- cli/jobs.py +42 -0
- cli/models.py +35 -0
- cli/nu.py +42 -0
- cli/reference.py +8 -0
- cli/slices.py +62 -0
- cli/tests.py +121 -0
- nucleus/__init__.py +446 -710
- nucleus/annotation.py +405 -85
- nucleus/autocurate.py +9 -0
- nucleus/connection.py +87 -0
- nucleus/constants.py +5 -1
- nucleus/data_transfer_object/__init__.py +0 -0
- nucleus/data_transfer_object/dataset_details.py +9 -0
- nucleus/data_transfer_object/dataset_info.py +26 -0
- nucleus/data_transfer_object/dataset_size.py +5 -0
- nucleus/data_transfer_object/scenes_list.py +18 -0
- nucleus/dataset.py +1137 -212
- nucleus/dataset_item.py +130 -26
- nucleus/dataset_item_uploader.py +297 -0
- nucleus/deprecation_warning.py +32 -0
- nucleus/errors.py +9 -0
- nucleus/job.py +71 -3
- nucleus/logger.py +9 -0
- nucleus/metadata_manager.py +45 -0
- nucleus/metrics/__init__.py +10 -0
- nucleus/metrics/base.py +117 -0
- nucleus/metrics/categorization_metrics.py +197 -0
- nucleus/metrics/errors.py +7 -0
- nucleus/metrics/filters.py +40 -0
- nucleus/metrics/geometry.py +198 -0
- nucleus/metrics/metric_utils.py +28 -0
- nucleus/metrics/polygon_metrics.py +480 -0
- nucleus/metrics/polygon_utils.py +299 -0
- nucleus/model.py +121 -15
- nucleus/model_run.py +34 -57
- nucleus/payload_constructor.py +29 -19
- nucleus/prediction.py +259 -17
- nucleus/pydantic_base.py +26 -0
- nucleus/retry_strategy.py +4 -0
- nucleus/scene.py +204 -19
- nucleus/slice.py +230 -67
- nucleus/upload_response.py +20 -9
- nucleus/url_utils.py +4 -0
- nucleus/utils.py +134 -37
- nucleus/validate/__init__.py +24 -0
- nucleus/validate/client.py +168 -0
- nucleus/validate/constants.py +20 -0
- nucleus/validate/data_transfer_objects/__init__.py +0 -0
- nucleus/validate/data_transfer_objects/eval_function.py +81 -0
- nucleus/validate/data_transfer_objects/scenario_test.py +19 -0
- nucleus/validate/data_transfer_objects/scenario_test_evaluations.py +11 -0
- nucleus/validate/data_transfer_objects/scenario_test_metric.py +12 -0
- nucleus/validate/errors.py +6 -0
- nucleus/validate/eval_functions/__init__.py +0 -0
- nucleus/validate/eval_functions/available_eval_functions.py +212 -0
- nucleus/validate/eval_functions/base_eval_function.py +60 -0
- nucleus/validate/scenario_test.py +143 -0
- nucleus/validate/scenario_test_evaluation.py +114 -0
- nucleus/validate/scenario_test_metric.py +14 -0
- nucleus/validate/utils.py +8 -0
- {scale_nucleus-0.1.24.dist-info → scale_nucleus-0.6.4.dist-info}/LICENSE +0 -0
- scale_nucleus-0.6.4.dist-info/METADATA +213 -0
- scale_nucleus-0.6.4.dist-info/RECORD +71 -0
- {scale_nucleus-0.1.24.dist-info → scale_nucleus-0.6.4.dist-info}/WHEEL +1 -1
- scale_nucleus-0.6.4.dist-info/entry_points.txt +3 -0
- scale_nucleus-0.1.24.dist-info/METADATA +0 -85
- scale_nucleus-0.1.24.dist-info/RECORD +0 -21
cli/client.py
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
import functools
|
2
|
+
import os
|
3
|
+
|
4
|
+
import nucleus
|
5
|
+
|
6
|
+
|
7
|
+
@functools.lru_cache()
|
8
|
+
def init_client():
|
9
|
+
api_key = os.environ.get("NUCLEUS_API_KEY", None)
|
10
|
+
if api_key:
|
11
|
+
client = nucleus.NucleusClient(api_key)
|
12
|
+
else:
|
13
|
+
raise RuntimeError("No NUCLEUS_API_KEY set")
|
14
|
+
return client
|
cli/datasets.py
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
import click
|
2
|
+
from rich.console import Console
|
3
|
+
from rich.table import Column, Table
|
4
|
+
|
5
|
+
from cli.client import init_client
|
6
|
+
from cli.helpers.nucleus_url import nucleus_url
|
7
|
+
from cli.helpers.web_helper import launch_web_or_invoke
|
8
|
+
|
9
|
+
|
10
|
+
@click.group("datasets", invoke_without_command=True)
|
11
|
+
@click.option("--web", is_flag=True, help="Launch browser")
|
12
|
+
@click.pass_context
|
13
|
+
def datasets(ctx, web):
|
14
|
+
"""Datasets are the base collections of items in Nucleus
|
15
|
+
|
16
|
+
https://dashboard.scale.com/nucleus/datasets
|
17
|
+
"""
|
18
|
+
launch_web_or_invoke(
|
19
|
+
sub_url="datasets", ctx=ctx, launch_browser=web, command=list_datasets
|
20
|
+
)
|
21
|
+
|
22
|
+
|
23
|
+
@datasets.command("list")
|
24
|
+
@click.option(
|
25
|
+
"-m", "--machine-readable", is_flag=True, help="Removes pretty printing"
|
26
|
+
)
|
27
|
+
def list_datasets(machine_readable):
|
28
|
+
"""List all available Datasets"""
|
29
|
+
console = Console()
|
30
|
+
with console.status("Finding your Datasets!", spinner="dots4"):
|
31
|
+
client = init_client()
|
32
|
+
all_datasets = client.datasets
|
33
|
+
if machine_readable:
|
34
|
+
table_params = {"box": None, "pad_edge": False}
|
35
|
+
else:
|
36
|
+
table_params = {
|
37
|
+
"title": ":fire: Datasets",
|
38
|
+
"title_justify": "left",
|
39
|
+
}
|
40
|
+
|
41
|
+
table = Table(
|
42
|
+
"id", "Name", Column("url", overflow="fold"), **table_params
|
43
|
+
)
|
44
|
+
for ds in all_datasets:
|
45
|
+
table.add_row(ds.id, ds.name, nucleus_url(ds.id))
|
46
|
+
console.print(table)
|
47
|
+
|
48
|
+
|
49
|
+
@datasets.command("delete")
|
50
|
+
@click.option("--id", prompt=True)
|
51
|
+
@click.option(
|
52
|
+
"--no-confirm-deletion",
|
53
|
+
is_flag=True,
|
54
|
+
help="WARNING: No confirmation for deletion",
|
55
|
+
)
|
56
|
+
@click.pass_context
|
57
|
+
def delete_dataset(ctx, id, no_confirm_deletion):
|
58
|
+
"""Delete a Dataset"""
|
59
|
+
console = Console()
|
60
|
+
client = init_client()
|
61
|
+
id = id.strip()
|
62
|
+
dataset = client.get_dataset(id)
|
63
|
+
delete_string = ""
|
64
|
+
if not no_confirm_deletion:
|
65
|
+
delete_string = click.prompt(
|
66
|
+
click.style(
|
67
|
+
f"Type 'DELETE' to delete dataset: {dataset}", fg="red"
|
68
|
+
)
|
69
|
+
)
|
70
|
+
if no_confirm_deletion or delete_string == "DELETE":
|
71
|
+
client.delete_dataset(dataset.id)
|
72
|
+
console.print(f":fire: :anguished: Deleted {id}")
|
73
|
+
else:
|
74
|
+
console.print(
|
75
|
+
f":rotating_light: Refusing to delete {id}. Received '{delete_string}' instead of 'DELETE'"
|
76
|
+
)
|
77
|
+
ctx.abort()
|
cli/helpers/__init__.py
ADDED
File without changes
|
@@ -0,0 +1,10 @@
|
|
1
|
+
import os
|
2
|
+
from urllib.parse import urljoin
|
3
|
+
|
4
|
+
|
5
|
+
def nucleus_url(sub_path: str):
|
6
|
+
nucleus_base = os.environ.get(
|
7
|
+
"NUCLEUS_DASHBOARD", "https://dashboard.scale.com/nucleus/"
|
8
|
+
)
|
9
|
+
extra_params = os.environ.get("NUCLEUS_DASH_PARAMS", "")
|
10
|
+
return urljoin(nucleus_base, sub_path.lstrip("/") + extra_params)
|
@@ -0,0 +1,40 @@
|
|
1
|
+
import click
|
2
|
+
|
3
|
+
from cli.helpers.nucleus_url import nucleus_url
|
4
|
+
|
5
|
+
|
6
|
+
def launch_web_or_show_help(
|
7
|
+
sub_url: str, ctx: click.Context, launch_browser: bool
|
8
|
+
):
|
9
|
+
""" Launches the sub_url (composed with nuclues_url(sub_url)) in the browser if requested"""
|
10
|
+
if not ctx.invoked_subcommand:
|
11
|
+
if launch_browser:
|
12
|
+
url = nucleus_url(sub_url)
|
13
|
+
click.launch(url)
|
14
|
+
else:
|
15
|
+
click.echo(ctx.get_help())
|
16
|
+
else:
|
17
|
+
if launch_browser:
|
18
|
+
click.echo(click.style("--web does not work with sub-commands"))
|
19
|
+
ctx.abort()
|
20
|
+
|
21
|
+
|
22
|
+
def launch_web_or_invoke(
|
23
|
+
sub_url: str,
|
24
|
+
ctx: click.Context,
|
25
|
+
launch_browser: bool,
|
26
|
+
command: click.BaseCommand,
|
27
|
+
):
|
28
|
+
"""Launches the sub_url (composed with nuclues_url(sub_url)) in the browser if requested, otherwise invokes
|
29
|
+
the passed command
|
30
|
+
"""
|
31
|
+
if not ctx.invoked_subcommand:
|
32
|
+
if launch_browser:
|
33
|
+
url = nucleus_url(sub_url)
|
34
|
+
click.launch(url)
|
35
|
+
else:
|
36
|
+
ctx.invoke(command)
|
37
|
+
else:
|
38
|
+
if launch_browser:
|
39
|
+
click.echo(click.style("--web does not work with sub-commands"))
|
40
|
+
ctx.abort()
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import os
|
2
|
+
import shutil
|
3
|
+
|
4
|
+
import click
|
5
|
+
from shellingham import detect_shell
|
6
|
+
|
7
|
+
|
8
|
+
@click.command("install-completion")
|
9
|
+
def install_completion():
|
10
|
+
"""Install shell completion script to your rc file"""
|
11
|
+
shell, _ = detect_shell()
|
12
|
+
if shell == "zsh":
|
13
|
+
rc_path = "~/.zshrc"
|
14
|
+
append_to_file = 'eval "$(_NU_COMPLETE=zsh_source nu)"'
|
15
|
+
elif shell == "bash":
|
16
|
+
rc_path = "~/.bashrc"
|
17
|
+
append_to_file = 'eval "$(_NU_COMPLETE=bash_source nu)"'
|
18
|
+
elif shell == "fish":
|
19
|
+
rc_path = "~/.config/fish/completions/foo-bar.fish"
|
20
|
+
append_to_file = "eval (env _NU_COMPLETE=fish_source nu)"
|
21
|
+
else:
|
22
|
+
raise RuntimeError(f"Unsupported shell {shell} for completions")
|
23
|
+
|
24
|
+
rc_path_expanded = os.path.expanduser(rc_path)
|
25
|
+
rc_bak = f"{rc_path_expanded}.bak"
|
26
|
+
shutil.copy(rc_path_expanded, rc_bak)
|
27
|
+
click.echo(f"Backed up {rc_path} to {rc_bak}")
|
28
|
+
with open(rc_path_expanded, mode="a") as rc_file:
|
29
|
+
rc_file.write("\n")
|
30
|
+
rc_file.write("# Shell completion for nu\n")
|
31
|
+
rc_file.write(append_to_file)
|
32
|
+
click.echo(f"Completion script added to {rc_path}")
|
33
|
+
click.echo(f"Don't forget to `source {rc_path}")
|
cli/jobs.py
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
import click
|
2
|
+
from rich.live import Live
|
3
|
+
from rich.spinner import Spinner
|
4
|
+
from rich.table import Column, Table
|
5
|
+
|
6
|
+
from cli.client import init_client
|
7
|
+
from cli.helpers.web_helper import launch_web_or_invoke
|
8
|
+
|
9
|
+
|
10
|
+
@click.group("jobs", invoke_without_command=True)
|
11
|
+
@click.option("--web", is_flag=True, help="Launch browser")
|
12
|
+
@click.pass_context
|
13
|
+
def jobs(ctx, web):
|
14
|
+
"""Jobs are a wrapper around various long-running tasks withing Nucleus
|
15
|
+
|
16
|
+
https://dashboard.scale.com/nucleus/jobs
|
17
|
+
"""
|
18
|
+
launch_web_or_invoke("jobs", ctx, web, list_jobs)
|
19
|
+
|
20
|
+
|
21
|
+
@jobs.command("list")
|
22
|
+
def list_jobs():
|
23
|
+
"""List all of your Jobs"""
|
24
|
+
client = init_client()
|
25
|
+
table = Table(
|
26
|
+
Column("id", overflow="fold", min_width=24),
|
27
|
+
"status",
|
28
|
+
"type",
|
29
|
+
"created at",
|
30
|
+
title=":satellite: Jobs",
|
31
|
+
title_justify="left",
|
32
|
+
)
|
33
|
+
with Live(Spinner("dots4", text="Finding your Jobs!")) as live:
|
34
|
+
all_jobs = client.jobs
|
35
|
+
for job in all_jobs:
|
36
|
+
table.add_row(
|
37
|
+
job.job_id,
|
38
|
+
job.job_last_known_status,
|
39
|
+
job.job_type,
|
40
|
+
job.job_creation_time,
|
41
|
+
)
|
42
|
+
live.update(table)
|
cli/models.py
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
import click
|
2
|
+
from rich.console import Console
|
3
|
+
from rich.table import Column, Table
|
4
|
+
|
5
|
+
from cli.client import init_client
|
6
|
+
from cli.helpers.nucleus_url import nucleus_url
|
7
|
+
from cli.helpers.web_helper import launch_web_or_invoke
|
8
|
+
|
9
|
+
|
10
|
+
@click.group("models", invoke_without_command=True)
|
11
|
+
@click.option("--web", is_flag=True, help="Launch browser")
|
12
|
+
@click.pass_context
|
13
|
+
def models(ctx, web):
|
14
|
+
"""Models help you store and access your ML model data
|
15
|
+
|
16
|
+
https://dashboard.scale.com/nucleus/models
|
17
|
+
"""
|
18
|
+
launch_web_or_invoke("models", ctx, web, list_models)
|
19
|
+
|
20
|
+
|
21
|
+
@models.command("list")
|
22
|
+
def list_models():
|
23
|
+
"""List your Models"""
|
24
|
+
console = Console()
|
25
|
+
with console.status("Finding your Models!", spinner="dots4"):
|
26
|
+
client = init_client()
|
27
|
+
table = Table(
|
28
|
+
Column("id", overflow="fold", min_width=24),
|
29
|
+
"name",
|
30
|
+
Column("url", overflow="fold"),
|
31
|
+
)
|
32
|
+
models = client.models
|
33
|
+
for m in models:
|
34
|
+
table.add_row(m.id, m.name, nucleus_url(m.id))
|
35
|
+
console.print(table)
|
cli/nu.py
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
import click
|
2
|
+
|
3
|
+
from cli.datasets import datasets
|
4
|
+
from cli.helpers.web_helper import launch_web_or_show_help
|
5
|
+
from cli.install_completion import install_completion
|
6
|
+
from cli.jobs import jobs
|
7
|
+
from cli.models import models
|
8
|
+
from cli.reference import reference
|
9
|
+
from cli.slices import slices
|
10
|
+
from cli.tests import tests
|
11
|
+
|
12
|
+
|
13
|
+
@click.group("cli", invoke_without_command=True)
|
14
|
+
@click.option("--web", is_flag=True, help="Launch browser")
|
15
|
+
@click.pass_context
|
16
|
+
def nu(ctx, web):
|
17
|
+
"""Nucleus CLI (nu)
|
18
|
+
|
19
|
+
\b
|
20
|
+
███╗ ██╗██╗ ██╗ ██████╗██╗ ███████╗██╗ ██╗███████╗
|
21
|
+
████╗ ██║██║ ██║██╔════╝██║ ██╔════╝██║ ██║██╔════╝
|
22
|
+
██╔██╗ ██║██║ ██║██║ ██║ █████╗ ██║ ██║███████╗
|
23
|
+
██║╚██╗██║██║ ██║██║ ██║ ██╔══╝ ██║ ██║╚════██║
|
24
|
+
██║ ╚████║╚██████╔╝╚██████╗███████╗███████╗╚██████╔╝███████║
|
25
|
+
╚═╝ ╚═══╝ ╚═════╝ ╚═════╝╚══════╝╚══════╝ ╚═════╝ ╚══════╝
|
26
|
+
|
27
|
+
`nu` is a command line interface to interact with Scale Nucleus (https://dashboard.scale.com/nucleus)
|
28
|
+
"""
|
29
|
+
launch_web_or_show_help(sub_url="", ctx=ctx, launch_browser=web)
|
30
|
+
|
31
|
+
|
32
|
+
nu.add_command(datasets) # type: ignore
|
33
|
+
nu.add_command(install_completion) # type: ignore
|
34
|
+
nu.add_command(jobs) # type: ignore
|
35
|
+
nu.add_command(models) # type: ignore
|
36
|
+
nu.add_command(reference) # type: ignore
|
37
|
+
nu.add_command(slices) # type: ignore
|
38
|
+
nu.add_command(tests) # type: ignore
|
39
|
+
|
40
|
+
if __name__ == "__main__":
|
41
|
+
"""To debug, run this script followed by request command tree e.g. `cli/nu.py datasets list`"""
|
42
|
+
nu()
|
cli/reference.py
ADDED
cli/slices.py
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
import click
|
2
|
+
from rich.console import Console
|
3
|
+
from rich.live import Live
|
4
|
+
from rich.spinner import Spinner
|
5
|
+
from rich.table import Column, Table
|
6
|
+
from rich.tree import Tree
|
7
|
+
|
8
|
+
from cli.client import init_client
|
9
|
+
from cli.helpers.nucleus_url import nucleus_url
|
10
|
+
from cli.helpers.web_helper import launch_web_or_invoke
|
11
|
+
from nucleus import NucleusAPIError
|
12
|
+
|
13
|
+
|
14
|
+
@click.group("slices", invoke_without_command=True)
|
15
|
+
@click.option("--web", is_flag=True, help="Launch browser")
|
16
|
+
@click.pass_context
|
17
|
+
def slices(ctx, web):
|
18
|
+
"""Slices are named subsets of Datasets"""
|
19
|
+
# TODO(gunnar): We don't have a natural landing for slices overview, until then we land on "/"
|
20
|
+
launch_web_or_invoke("", ctx, web, list_slices)
|
21
|
+
|
22
|
+
|
23
|
+
@slices.command("list")
|
24
|
+
def list_slices():
|
25
|
+
"""List all available Slices"""
|
26
|
+
with Live(
|
27
|
+
Spinner("dots4", text="Finding your Slices!"),
|
28
|
+
vertical_overflow="visible",
|
29
|
+
) as live:
|
30
|
+
client = init_client()
|
31
|
+
datasets = client.datasets
|
32
|
+
table = Table(
|
33
|
+
Column("id", overflow="fold", min_width=24),
|
34
|
+
"name",
|
35
|
+
"dataset_name",
|
36
|
+
Column("url", overflow="fold"),
|
37
|
+
title=":cake: Slices",
|
38
|
+
title_justify="left",
|
39
|
+
)
|
40
|
+
errors = {}
|
41
|
+
for ds in datasets:
|
42
|
+
try:
|
43
|
+
ds_slices = ds.slices
|
44
|
+
if ds_slices:
|
45
|
+
for slc_id in ds_slices:
|
46
|
+
slice_url = nucleus_url(f"{ds.id}/{slc_id}")
|
47
|
+
slice_info = client.get_slice(slc_id).info()
|
48
|
+
table.add_row(
|
49
|
+
slc_id, slice_info["name"], ds.name, slice_url
|
50
|
+
)
|
51
|
+
live.update(table)
|
52
|
+
except NucleusAPIError as e:
|
53
|
+
errors[ds.id] = e
|
54
|
+
|
55
|
+
error_tree = Tree(
|
56
|
+
":x: Encountered the following errors while fetching information"
|
57
|
+
)
|
58
|
+
for ds_id, error in errors.items():
|
59
|
+
dataset_branch = error_tree.add(f"Dataset: {ds_id}")
|
60
|
+
dataset_branch.add(f"Error: {error}")
|
61
|
+
|
62
|
+
Console().print(error_tree)
|
cli/tests.py
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
import click
|
2
|
+
from rich.console import Console
|
3
|
+
from rich.live import Live
|
4
|
+
from rich.table import Column, Table
|
5
|
+
from rich.tree import Tree
|
6
|
+
|
7
|
+
from cli.client import init_client
|
8
|
+
from cli.helpers.nucleus_url import nucleus_url
|
9
|
+
from cli.helpers.web_helper import launch_web_or_invoke
|
10
|
+
from nucleus import NucleusAPIError
|
11
|
+
from nucleus.validate import (
|
12
|
+
AvailableEvalFunctions,
|
13
|
+
ScenarioTestMetric,
|
14
|
+
ThresholdComparison,
|
15
|
+
)
|
16
|
+
|
17
|
+
|
18
|
+
@click.group("tests", invoke_without_command=True)
|
19
|
+
@click.option("--web", is_flag=True, help="Launch browser")
|
20
|
+
@click.pass_context
|
21
|
+
def tests(ctx, web):
|
22
|
+
"""Scenario Tests allow you to test your Models
|
23
|
+
|
24
|
+
https://dashboard.scale.com/nucleus/scenario-tests
|
25
|
+
"""
|
26
|
+
launch_web_or_invoke("scenario-tests", ctx, web, list_tests)
|
27
|
+
|
28
|
+
|
29
|
+
@tests.command("list")
|
30
|
+
def list_tests():
|
31
|
+
"""List all your Scenario Tests"""
|
32
|
+
console = Console()
|
33
|
+
with console.status("Finding your Scenario Tests", spinner="dots4"):
|
34
|
+
client = init_client()
|
35
|
+
scenario_tests = client.validate.list_scenario_tests()
|
36
|
+
table = Table(
|
37
|
+
Column("id", overflow="fold", min_width=24),
|
38
|
+
"Name",
|
39
|
+
"slice_id",
|
40
|
+
Column("url", overflow="fold"),
|
41
|
+
title=":chart_with_upwards_trend: Scenario Tests",
|
42
|
+
title_justify="left",
|
43
|
+
)
|
44
|
+
for ut in scenario_tests:
|
45
|
+
table.add_row(ut.id, ut.name, ut.slice_id, nucleus_url(ut.id))
|
46
|
+
console.print(table)
|
47
|
+
|
48
|
+
|
49
|
+
def format_criterion(
|
50
|
+
criterion: ScenarioTestMetric, eval_functions: AvailableEvalFunctions
|
51
|
+
):
|
52
|
+
op_map = {
|
53
|
+
ThresholdComparison.GREATER_THAN: ">",
|
54
|
+
ThresholdComparison.GREATER_THAN_EQUAL_TO: ">=",
|
55
|
+
ThresholdComparison.LESS_THAN: "<",
|
56
|
+
ThresholdComparison.LESS_THAN_EQUAL_TO: "<=",
|
57
|
+
}
|
58
|
+
eval_function_name = eval_functions.from_id(
|
59
|
+
criterion.eval_function_id
|
60
|
+
).name
|
61
|
+
return f"{eval_function_name} {op_map[criterion.threshold_comparison]} {criterion.threshold}"
|
62
|
+
pass
|
63
|
+
|
64
|
+
|
65
|
+
@tests.command("describe")
|
66
|
+
@click.argument("scenario-test-id", default=None, required=False)
|
67
|
+
@click.option(
|
68
|
+
"--all", "-a", is_flag=True, help="View details about all Scenario Tests"
|
69
|
+
)
|
70
|
+
def describe_test(scenario_test_id, all):
|
71
|
+
"""View detailed information about a test or all tests"""
|
72
|
+
console = Console()
|
73
|
+
# scenario_test = client.validate.get_scenario_test(scenario_test_id)
|
74
|
+
assert scenario_test_id or all, "Must pass a scenario_test_id or --all"
|
75
|
+
client = init_client()
|
76
|
+
scenario_tests = client.validate.list_scenario_tests()
|
77
|
+
if all:
|
78
|
+
tree = Tree(":chart_with_upwards_trend: All Scenario Tests")
|
79
|
+
with Live(
|
80
|
+
"Fetching description of all Scenario Tests",
|
81
|
+
vertical_overflow="visible",
|
82
|
+
) as live:
|
83
|
+
for idx, ut in enumerate(scenario_tests):
|
84
|
+
test_branch = tree.add(f"{idx}: Scenario Test")
|
85
|
+
build_scenario_test_info_tree(client, ut, test_branch)
|
86
|
+
live.update(tree)
|
87
|
+
else:
|
88
|
+
with console.status("Fetching Scenario Test information"):
|
89
|
+
scenario_test = [
|
90
|
+
ut for ut in scenario_tests if ut.id == scenario_test_id
|
91
|
+
][0]
|
92
|
+
tree = Tree(":chart_with_upwards_trend: Scenario Test")
|
93
|
+
build_scenario_test_info_tree(client, scenario_test, tree)
|
94
|
+
console.print(tree)
|
95
|
+
|
96
|
+
|
97
|
+
def build_scenario_test_info_tree(client, scenario_test, tree):
|
98
|
+
try:
|
99
|
+
slc = client.get_slice(scenario_test.slice_id)
|
100
|
+
info_branch = tree.add(":mag: Details")
|
101
|
+
info_branch.add(f"id: '{scenario_test.id}'")
|
102
|
+
info_branch.add(f"name: '{scenario_test.name}'")
|
103
|
+
scenario_test_url = nucleus_url(scenario_test.id)
|
104
|
+
info_branch.add(f"url: {scenario_test_url}")
|
105
|
+
slice_url = nucleus_url(f"{slc.dataset_id}/{slc.slice_id}")
|
106
|
+
slice_branch = tree.add(":cake: Slice")
|
107
|
+
slice_branch.add(f"id: '{slc.id}'")
|
108
|
+
slice_info = slc.info()
|
109
|
+
slice_branch.add(f"name: '{slice_info['name']}'")
|
110
|
+
slice_branch.add(f"len: {len(slc.items)}")
|
111
|
+
slice_branch.add(f"url: {slice_url}")
|
112
|
+
criteria = scenario_test.get_criteria()
|
113
|
+
criteria_branch = tree.add(":crossed_flags: Criteria")
|
114
|
+
for criterion in criteria:
|
115
|
+
pretty_criterion = format_criterion(
|
116
|
+
criterion, client.validate.eval_functions
|
117
|
+
)
|
118
|
+
criteria_branch.add(pretty_criterion)
|
119
|
+
except NucleusAPIError as e:
|
120
|
+
error_branch = tree.add(":x: Error")
|
121
|
+
error_branch.add(f"detail: {str(e)}")
|