Skip to content

Commit f6d00cb

Browse files
authored
Chore/upgrade cli to v0.5.0 (#245)
chore: update cli to odtp version v0.5.0 CLI commands are improved in regards to validation and output so that cli validation matches gui validation. Co-authored-by: sabinem <[email protected]>
1 parent d0c847e commit f6d00cb

File tree

11 files changed

+194
-80
lines changed

11 files changed

+194
-80
lines changed

odtp/cli/new.py

+86-30
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from typing_extensions import Annotated
66
import logging
77
import requests
8+
from rich.console import Console
9+
from rich.markdown import Markdown
810
import odtp.mongodb.db as db
911
import odtp.mongodb.utils as mongodb_utils
1012
import odtp.helpers.parse as odtp_parse
@@ -13,6 +15,7 @@
1315
import traceback
1416

1517
app = typer.Typer()
18+
console = Console()
1619

1720
log = logging.getLogger(__name__)
1821

@@ -23,8 +26,31 @@ def user_entry(
2326
github: str = typer.Option(..., "--github", help="Specify the github"),
2427
):
2528
"""Add new user in the MongoDB"""
26-
user_id = db.add_user(name=name, github=github, email=email)
27-
log.info(f"A user has been added {user_id}")
29+
try:
30+
if name is None or email is None or github is None:
31+
console.print("[bold red]❌ ERROR:[/bold red] Please provide either --name or --email and --github")
32+
raise typer.Exit(code=1)
33+
34+
if not validation_helpers.validate_user_name_unique(name):
35+
console.print("[bold red]❌ ERROR:[/bold red] --name must be unique.")
36+
raise typer.Exit(code=1)
37+
38+
if not validation_helpers.validate_github_user_name(github):
39+
console.print("[bold red]❌ ERROR:[/bold red] --github must be a valid github user name.")
40+
raise typer.Exit(code=1)
41+
42+
user_id = db.add_user(name=name, github=github, email=email)
43+
44+
except typer.Exit:
45+
pass
46+
47+
except Exception as e:
48+
log.exception("Unexpected error in user_entry")
49+
console.print(f"[bold red]❌ ERROR:[/bold red] Failed to add user. Details: {str(e)}")
50+
raise typer.Exit(code=1)
51+
else:
52+
success_message = f"[bold green]✅ SUCCESS: User with User Id {user_id} has been added![/bold green]"
53+
console.print(success_message)
2854

2955

3056
@app.command()
@@ -53,28 +79,28 @@ def odtp_component_entry(
5379

5480
except git_helpers.OdtpGithubException as e:
5581
typer.echo(f"Error: An error occurred while fetching information from GitHub: {e}")
56-
raise typer.Exit(code=2)
82+
raise typer.Exit(code=1)
5783

5884
except mongodb_utils.OdtpDbMongoDBValidationException as e:
5985
typer.echo(f"Error: Component version could not be added: {e}")
60-
raise typer.Exit(code=3)
86+
raise typer.Exit(code=1)
6187

6288
except validation_helpers.OdtpYmlException as e:
6389
log.error(f"Validation error occurred when parsing odtp.yml")
6490
typer.echo(f"Error: Validation of odtp.yml failed: {e}")
65-
raise typer.Exit(code=4)
91+
raise typer.Exit(code=1)
6692

6793
except requests.RequestException as e:
6894
log.error(f"Network error: {e}")
6995
typer.echo(f"Error: A network error occurred while communicating with GitHub.: {e}")
70-
raise typer.Exit(code=5)
96+
raise typer.Exit(code=1)
7197

7298
except Exception as e:
7399
error_message = f"Error: An unexpected error occurred: {e}"
74100
if debug:
75101
error_message += f"\n{traceback.format_exc()}"
76102
typer.echo(error_message, err=True)
77-
raise typer.Exit(code=99)
103+
raise typer.Exit(code=1)
78104

79105
else:
80106
if component_id and version_id:
@@ -94,14 +120,39 @@ def digital_twin_entry(
94120
user_email: str = typer.Option(None, "--user-email", help="Specify the email"),
95121
name: str = typer.Option(..., "--name", help="Specify the digital twin name"),
96122
):
97-
if user_id is None and user_email is None:
98-
raise typer.Exit("Please provide either --user-id or --user-email")
123+
try:
124+
if user_id is None and user_email is None:
125+
console.print("[bold red]❌ ERROR:[/bold red] Please provide either --user-id or --user-email")
126+
raise typer.Exit(code=1)
127+
128+
if user_email:
129+
user_id = db.get_document_id_by_field_value("email", user_email, "users")
130+
131+
if not user_id:
132+
console.print("[bold red]❌ ERROR:[/bold red] User does not exist. Please add the user first.")
133+
raise typer.Exit(code=1)
134+
135+
if not validation_helpers.validate_digital_twin_name_unique(digital_twin_name=name, user_id=user_id):
136+
console.print("[bold red]❌ ERROR:[/bold red] Digital Twin name must be unique and 6 characters long. Choose a different name.")
137+
raise typer.Exit(code=1)
138+
139+
dt_id = db.add_digital_twin(userRef=user_id, name=name)
99140

100-
if user_email:
101-
user_id = db.get_document_id_by_field_value("email", user_email, "users")
141+
if not dt_id:
142+
console.print("[bold red]❌ ERROR:[/bold red] Failed to create Digital Twin due to a database issue.")
143+
raise typer.Exit(code=1)
102144

103-
dt_id = db.add_digital_twin(userRef=user_id, name=name)
104-
log.info(f"Digital Twin added with ID {dt_id}")
145+
except typer.Exit:
146+
pass
147+
148+
except Exception as e:
149+
console.print(f"[bold red]❌ ERROR:[/bold red] An unexpected error occurred: {str(e)}")
150+
log.exception("Unexpected error in digital_twin_entry")
151+
raise typer.Exit(code=1)
152+
153+
else:
154+
success_message = f"[bold green]✅ SUCCESS: Digital Twin with id {dt_id} has been added![/bold green]"
155+
console.print(success_message)
105156

106157

107158
@app.command()
@@ -116,7 +167,8 @@ def workflow_entry(
116167
):
117168
try:
118169
if component_tags is None and component_versions is None:
119-
raise typer.Exit("Please provide either --component-tags or --component-versions")
170+
console.print("[bold red]❌ ERROR:[/bold red] Please provide either --component-tags or --component-versions")
171+
raise typer.Exit(code=1)
120172
if component_tags:
121173
component_versions = ",".join(odtp_parse.parse_component_tags(component_tags))
122174
versions = odtp_parse.parse_versions(component_versions)
@@ -125,14 +177,14 @@ def workflow_entry(
125177
workflow=versions,
126178
)
127179
except Exception as e:
128-
log.error(f"ERROR: {e}")
129-
traceback.print_exc()
180+
console.print(f"[bold red]❌ ERROR:[/bold red] An unexpected error occurred: {str(e)}")
181+
log.exception("Unexpected error in workflow_entry")
182+
raise typer.Exit(code=1)
130183
success_message = (
131184
f"SUCCESS: Workflow has been added:\n"
132185
f" - Workflow ID: {workflow_id}\n"
133186
)
134-
log.info(success_message)
135-
typer.echo(f"✅ {success_message}")
187+
console.print(f"✅ {success_message}")
136188

137189

138190
@app.command()
@@ -141,6 +193,9 @@ def execution_entry(
141193
wf_id: str = typer.Option(
142194
None, "--workflow-id", help="Specify the workflow ID"
143195
),
196+
wf_name: str = typer.Option(
197+
None, "--workflow-name", help="Specify the workflow name"
198+
),
144199
component_tags: str = typer.Option(
145200
None, "--component-tags", help="Specify the components-tags (component-name:version) separated by commas"
146201
),
@@ -157,11 +212,11 @@ def execution_entry(
157212
):
158213
try:
159214
if dt_name is None and dt_id is None:
160-
raise typer.Exit("Please provide either --digital-twin-name or --digital-twin-id")
161-
215+
print("Please provide either --digital-twin-name or --digital-twin-id")
216+
raise typer.Exit(code=1)
162217
if wf_id is None and component_tags is None:
163-
raise typer.Exit("Please provide either --workflow-id or --component-tags")
164-
218+
print("Please provide either --workflow-id or --component-tags")
219+
raise typer.Exit(code=1)
165220
if dt_name:
166221
dt_id = db.get_document_id_by_field_value("name", dt_name, "digitalTwins")
167222

@@ -171,7 +226,7 @@ def execution_entry(
171226
if component_tags:
172227
component_versions = ",".join(odtp_parse.parse_component_tags(component_tags))
173228
versions = odtp_parse.parse_versions(component_versions)
174-
workflow = db.get_or_create_workflow_by_versions(execution_name, versions)
229+
workflow = db.get_workflow_or_create_by_versions(execution_name, versions)
175230
step_count = len(workflow.get("versions"))
176231
if parameter_files is None:
177232
parameters = db.get_default_parameters_for_workflow(workflow["versions"])
@@ -195,13 +250,14 @@ def execution_entry(
195250
except Exception as e:
196251
log.error(f"ERROR: {e}")
197252
traceback.print_exc()
198-
success_message = (
199-
f"SUCCESS: execution has been added: see above for the details.\n"
200-
f" - execution id: {execution_id}\n"
201-
f" - step_ids: {step_ids}\n"
202-
)
203-
log.info(success_message)
204-
typer.echo(f"✅ {success_message}")
253+
else:
254+
success_message = (
255+
f"SUCCESS: execution has been added: see above for the details.\n"
256+
f" - execution id: {execution_id}\n"
257+
f" - step_ids: {step_ids}\n"
258+
)
259+
log.info(success_message)
260+
typer.echo(f"✅ {success_message}")
205261

206262

207263
if __name__ == "__main__":

odtp/dashboard/page_digital_twins/add.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from nicegui import ui
33
import odtp.mongodb.db as db
44
import odtp.dashboard.page_digital_twins.storage as storage
5-
import odtp.dashboard.page_digital_twins.validation as validation
5+
import odtp.helpers.validation as validation
66

77

88
class DigitalTwinAddForm:

odtp/dashboard/page_digital_twins/validation.py

-11
This file was deleted.

odtp/dashboard/page_executions/add.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import odtp.mongodb.db as db
44
from odtp.helpers.settings import ODTP_SECRETS_DIR
55
import odtp.dashboard.utils.helpers as helpers
6-
import odtp.dashboard.page_executions.validation as validation
6+
import odtp.helpers.validation as validation
77
import odtp.dashboard.utils.ui_theme as ui_theme
88

99

odtp/dashboard/page_executions/validation.py

-12
This file was deleted.

odtp/dashboard/page_users/add.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import odtp.mongodb.db as db
44
import odtp.dashboard.page_users.workdir as workdir
55
import odtp.dashboard.page_users.storage as storage
6-
import odtp.dashboard.page_users.validation as validation
6+
import odtp.helpers.validation as validation
77

88

99
log = logging.getLogger("__name__")

odtp/dashboard/page_users/validation.py

-22
This file was deleted.

odtp/helpers/environment.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ class OdtpLocalEnvironmentException(Exception):
1313

1414

1515
def check_project_folder_empty(project_folder):
16+
if not os.path.exists(project_folder):
17+
raise OdtpLocalEnvironmentException(
18+
f"project folder {project_folder} does not exist. Please create it first."
19+
)
1620
if not project_folder_is_empty(project_folder):
1721
raise OdtpLocalEnvironmentException(
1822
f"project folder {project_folder} was not empty"
@@ -22,7 +26,7 @@ def check_project_folder_empty(project_folder):
2226
def check_directory_folder_matches_execution(execution_id, project_folder):
2327
if not directory_folder_matches_execution(execution_id, project_folder):
2428
raise OdtpLocalEnvironmentException(
25-
f"""project folder {project_folder} does not match execution.
29+
f"""project folder {project_folder} does not match execution.
2630
First run 'prepare' on an empty folder to prepare for the run of the execution"""
2731
)
2832

odtp/helpers/validation.py

+45
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
from odtp.helpers.models import OdtpDotYamlSchema
22
from pydantic import ValidationError
3+
import requests
4+
import odtp.mongodb.db as db
5+
from odtp.helpers.settings import GITHUB_TOKEN
6+
7+
GITHUB_API_REPOS_URL = "https://api.github.com/users"
38

49

510
class OdtpYmlException(Exception):
@@ -22,3 +27,43 @@ def validate_odtp_yml_file(yaml_data):
2227
raise OdtpYmlException(f"Validation failed for odtp.yml:\n{error_details}")
2328
except Exception as e:
2429
raise OdtpYmlException(f"Unexpected error during odtp.yml validation: {e}")
30+
31+
32+
import odtp.mongodb.db as db
33+
34+
35+
def validate_digital_twin_name_unique(digital_twin_name, user_id):
36+
if len(digital_twin_name) < 6:
37+
return False
38+
digital_twins = db.get_collection(db.collection_digital_twins, query={"userRef": user_id})
39+
digital_twin_names = {digital_twin.get("name") for digital_twin in digital_twins}
40+
if digital_twin_name in digital_twin_names:
41+
return False
42+
return True
43+
44+
45+
def validate_github_user_name(github_user_name):
46+
headers = {"Authorization": "token " + GITHUB_TOKEN}
47+
git_api_user_url = f"{GITHUB_API_REPOS_URL}/{github_user_name}"
48+
response = requests.get(git_api_user_url, headers=headers)
49+
if not response.status_code == 200:
50+
return False
51+
return True
52+
53+
54+
def validate_user_name_unique(user_name):
55+
users = db.get_collection(db.collection_users)
56+
user_names = {user.get("displayName") for user in users}
57+
if user_name in user_names:
58+
return False
59+
return True
60+
61+
62+
def validate_execution_name_unique(execution_name, digital_twin_id):
63+
if len(execution_name) < 6:
64+
return False
65+
executions = db.get_collection(db.collection_executions, query={"digitalTwinRef": ObjectId(digital_twin_id)})
66+
execution_names = {execution.get("title") for execution in executions}
67+
if execution_name in execution_names:
68+
return False
69+
return True

0 commit comments

Comments
 (0)