Skip to content

Commit 2e81e58

Browse files
committed
[FLPY-37] Refine ChooseAccount feature, add configurable warning prompts when submitting resources from shared accounts
1 parent 6c748b3 commit 2e81e58

File tree

9 files changed

+184
-44
lines changed

9 files changed

+184
-44
lines changed
Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,18 @@
1-
import os
2-
31
import flow360 as fl
4-
5-
# Temporary workaround for API access from dev environment as apikey in config.toml does not appear to work
6-
os.environ["FLOW360_APIKEY"] = (
7-
"QUlEQVU3N0k2QloyUVlaTExWU1JXOmZsb3czNjA6eWNmdGNpNUFRN1pRdDVBbDRPN1M4YXdt"
8-
"UzRabUZrYURkRFB4WkdWdVdVb0ZORVc0VE40MG1DVDVJOFRxenp1QW44ME4zT3ZjcVE4ODZLe"
9-
"llGZlBoWnIxNHFPaU1CaFp4cmd3djFUSUQwREpZTmlTS0l6M2NzeUxZMEprSk5mU2c="
10-
)
2+
from flow360.examples import OM6wing
113

124
fl.Env.dev.active()
5+
fl.UserConfig.set_profile("dev")
136

14-
mail1 = fl.MyCases()[0].info.userEmail
15-
16-
fl.Accounts.shared_account_info()
17-
18-
fl.Accounts.choose_shared_account("maciej@flexcompute.com")
7+
# choose shared account interactively
8+
fl.Accounts.choose_shared_account()
199

20-
mail2 = fl.MyCases()[0].info.userEmail
10+
# retrieve mesh files
11+
OM6wing.get_files()
2112

22-
fl.Accounts.shared_account_info()
13+
# submit mesh
14+
volume_mesh = fl.VolumeMesh.from_file(OM6wing.mesh_filename, name="OM6wing-mesh")
15+
volume_mesh = volume_mesh.submit()
2316

24-
print(f"Email before sharing: {mail1}, email after sharing: {mail2}")
17+
# leave the account
18+
fl.Accounts.leave_shared_account()

flow360/accounts_utils.py

Lines changed: 60 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
1+
"""
2+
This module provides utility functions for managing access between interconnected accounts.
3+
4+
Functions:
5+
- choose_shared_account(None) -> None - select account from the list of client and organization accounts interactively
6+
- choose_shared_account(email: str, optional) -> None - select account matching the provided email (if exists)
7+
- shared_account_info(None) -> str - return current shared account email address (if exists, None otherwise)
8+
- leave_shared_account(None) -> None - leave current shared account
9+
"""
10+
111
from requests import HTTPError
212

313
from flow360.cloud.http_util import http
414
from flow360.environment import Env
15+
from flow360.log import log
516

617
from .exceptions import WebError
718

8-
"""
9-
This module provides utility functions for managing access between interconnected accounts.
10-
11-
Functions:
12-
- choose_account(None) -> None - select account from the list of supported and organization accounts interactively
13-
- choose_account(email: str, optional) -> None - select account matching the provided email (if exists)
14-
"""
15-
1619

1720
class AccountsUtils:
21+
"""
22+
Current account info and utility functions.
23+
"""
24+
1825
def __init__(self):
1926
self._current_email = None
2027
self._current_user_identity = None
@@ -37,11 +44,10 @@ def _interactive_selection(users):
3744
)
3845
if value == "q":
3946
return None
40-
elif int(value) in range(0, user_count):
47+
if int(value) in range(0, user_count):
4148
return int(value)
42-
else:
43-
print(f"Value out of range [0 - {user_count - 1}]")
44-
continue
49+
print(f"Value out of range [0 - {user_count - 1}]")
50+
continue
4551
except ValueError:
4652
print("Invalid input type, please input an integer value:")
4753
continue
@@ -71,11 +77,32 @@ def _get_company_users():
7177
except HTTPError as error:
7278
raise WebError("Failed to retrieve company user data from server") from error
7379

80+
def _check_state_consistency(self):
81+
if Env.impersonate != self._current_user_identity:
82+
log.warning(
83+
(
84+
f"Environment impersonation ({Env.impersonate}) does "
85+
f"not match current account ({self._current_user_identity}), "
86+
"this may be caused by explicit modification of impersonation "
87+
"in the environment, use choose_shared_account() instead."
88+
)
89+
)
90+
self._current_email = None
91+
self._current_user_identity = None
92+
7493
def choose_shared_account(self, email=None):
94+
"""choose a shared account to impersonate
95+
96+
Parameters
97+
----------
98+
email : str, optional
99+
user email to impersonate (if email exists among shared accounts),
100+
if email is not provided user can select the account interactively
101+
"""
75102
company_users = self._get_company_users()
76103

77104
if len(company_users) == 0:
78-
print("There are no accounts shared with the current user")
105+
log.info("There are no accounts shared with the current user")
79106
return
80107

81108
selected = None
@@ -99,17 +126,31 @@ def choose_shared_account(self, email=None):
99126
self._current_user_identity = user_id
100127

101128
def shared_account_info(self):
102-
# Impersonate has been modified from the outside, discard current state
103-
if Env.impersonate != self._current_user_identity:
104-
self._current_email = None
105-
self._current_user_identity = None
129+
"""
130+
retrieve current shared account name, if possible
131+
"""
132+
self._check_state_consistency()
106133

107134
if self._current_email is not None:
108-
print(f"Currently operating as {self._current_email}")
135+
log.info(f"Currently operating as {self._current_email}")
109136
else:
110-
print("Currently not logged into a shared account")
137+
log.info("Currently not logged into a shared account")
111138

112139
return self._current_email
113140

141+
def leave_shared_account(self):
142+
"""
143+
leave current shared account name, if possible
144+
"""
145+
self._check_state_consistency()
146+
147+
if Env.impersonate is None:
148+
log.warning("You are not currently logged into any shared account")
149+
else:
150+
log.info(f"Leaving shared account {self._current_email}")
151+
self._current_email = None
152+
self._current_user_identity = None
153+
Env.impersonate = None
154+
114155

115156
Accounts = AccountsUtils()

flow360/cli/app.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,26 @@ def flow360():
4242
is_flag=True,
4343
help='Whether to show warnings for "submit()" when creating new Case, new VolumeMesh etc.',
4444
)
45-
def configure(apikey, profile, suppress_submit_warning, show_submit_warning):
45+
@click.option(
46+
"--suppress-shared-account-prompt",
47+
type=bool,
48+
is_flag=True,
49+
help='Do not prompt for confirmation for "submit()" when creating new resources while logged into a shared account',
50+
)
51+
@click.option(
52+
"--show-shared-account-prompt",
53+
type=bool,
54+
is_flag=True,
55+
help='Always prompt for confirmation for "submit()" when creating new resources while logged into a shared account',
56+
)
57+
def configure(
58+
apikey,
59+
profile,
60+
suppress_submit_warning,
61+
show_submit_warning,
62+
suppress_shared_account_prompt,
63+
show_shared_account_prompt,
64+
):
4665
"""
4766
Configure flow360.
4867
"""
@@ -72,6 +91,19 @@ def configure(apikey, profile, suppress_submit_warning, show_submit_warning):
7291
config.update({"user": {"config": {"suppress_submit_warning": False}}})
7392
changed = True
7493

94+
if suppress_submit_warning and show_submit_warning:
95+
raise click.ClickException(
96+
"You cannot use both --suppress-shared-account-prompt AND --show-shared-account-prompt"
97+
)
98+
99+
if suppress_shared_account_prompt:
100+
config.update({"user": {"config": {"suppress_shared_account_prompt": True}}})
101+
changed = True
102+
103+
if show_shared_account_prompt:
104+
config.update({"user": {"config": {"suppress_shared_account_prompt": False}}})
105+
changed = True
106+
75107
with open(config_file, "w", encoding="utf-8") as file_handler:
76108
file_handler.write(toml.dumps(config))
77109

flow360/component/case.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
before_submit_only,
2929
is_object_cloud_resource,
3030
)
31-
from .utils import is_valid_uuid, validate_type
31+
from .utils import confirm_proceed, is_valid_uuid, validate_type
3232
from .validator import Validator
3333

3434

@@ -241,6 +241,9 @@ def submit(self, force_submit: bool = False) -> Case:
241241

242242
self.validate_case_inputs(pre_submit_checks=True)
243243

244+
if not confirm_proceed():
245+
raise FlValueError("User aborted resource submit.")
246+
244247
volume_mesh_id = self.volume_mesh_id
245248
parent_id = self.parent_id
246249
if parent_id is not None:

flow360/component/surface_mesh.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
Flow360ResourceListBase,
2323
ResourceDraft,
2424
)
25-
from .utils import validate_type
25+
from .utils import confirm_proceed, validate_type
2626
from .validator import Validator
2727
from .volume_mesh import VolumeMeshDraft
2828

@@ -121,6 +121,9 @@ def submit(self, progress_callback=None) -> SurfaceMesh:
121121
if name is None:
122122
name = os.path.splitext(os.path.basename(self.geometry_file))[0]
123123

124+
if not confirm_proceed():
125+
raise FlValueError("User aborted resource submit.")
126+
124127
data = {
125128
"name": self.name,
126129
"tags": self.tags,

flow360/component/utils.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@
88

99
import zstandard as zstd
1010

11+
from ..accounts_utils import Accounts
1112
from ..cloud.utils import _get_progress, _S3Action
13+
from ..error_messages import shared_submit_warning
1214
from ..exceptions import TypeError as FlTypeError
1315
from ..exceptions import ValueError as FlValueError
1416
from ..log import log
17+
from ..user_config import UserConfig
1518

1619

1720
# pylint: disable=redefined-builtin
@@ -48,6 +51,32 @@ def wrapper_func(*args, **kwargs):
4851
return wrapper
4952

5053

54+
def confirm_proceed():
55+
"""
56+
Prompts confirmation from user when submitting a resource from a shared account
57+
"""
58+
if (
59+
Accounts.shared_account_info() is not None
60+
and not UserConfig.is_suppress_shared_account_prompt()
61+
):
62+
log.warning(shared_submit_warning)
63+
print("Are you sure you want to proceed? (y/n): ")
64+
while True:
65+
try:
66+
value = input()
67+
if value.lower() == "y":
68+
return True
69+
if value.lower() == "n":
70+
return False
71+
print("Enter a valid value (y/n): ")
72+
continue
73+
except ValueError:
74+
print("Invalid input type")
75+
continue
76+
else:
77+
return True
78+
79+
5180
# pylint: disable=bare-except
5281
def _get_value_or_none(callable):
5382
try:

flow360/component/volume_mesh.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
ResourceDraft,
3838
)
3939
from .types import COMMENTS
40-
from .utils import validate_type, zstd_compress
40+
from .utils import confirm_proceed, validate_type, zstd_compress
4141
from .validator import Validator
4242

4343
try:
@@ -503,6 +503,9 @@ def submit(self, progress_callback=None) -> VolumeMesh:
503503
VolumeMesh object with id
504504
"""
505505

506+
if not confirm_proceed():
507+
raise FlValueError("User aborted resource submit.")
508+
506509
if self.file_name is not None:
507510
return self._submit_upload_mesh(progress_callback)
508511

flow360/error_messages.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@
1010
"""
1111

1212

13+
shared_submit_warning = """\
14+
You are submitting a resource from a shared account.
15+
To suppress this message run: flow360 configure --suppress-shared-account-prompt
16+
"""
17+
18+
1319
def change_solver_version_error(from_version, to_version):
1420
return f"""\
1521
Cannot change solver version from parent to child.

flow360/user_config.py

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ def __init__(self):
2626
self._check_env_apikey()
2727
self._do_validation = True
2828
self._suppress_submit_warning = None
29+
self._suppress_shared_account_prompt = None
2930

3031
def _check_env_profile(self):
3132
simcloud_profile = os.environ.get("SIMCLOUD_PROFILE", None)
@@ -76,14 +77,34 @@ def apikey(self):
7677
return self._apikey
7778
return self.config.get(self.profile, {}).get("apikey", "")
7879

80+
def suppress_shared_account_prompt(self):
81+
"""locally suppress warning when trying to submit from a shared account"""
82+
self._suppress_shared_account_prompt = True
83+
84+
def show_shared_account_prompt(self):
85+
"""locally show warning when trying to submit from a shared account"""
86+
self._suppress_shared_account_prompt = False
87+
88+
def is_suppress_shared_account_prompt(self):
89+
"""suppress shared account submit warning config
90+
91+
Returns
92+
-------
93+
bool
94+
whether to suppress shared account submit warnings
95+
"""
96+
if self._suppress_shared_account_prompt is not None:
97+
return self._suppress_shared_account_prompt
98+
return (
99+
self.config.get("user", {})
100+
.get("config", {})
101+
.get("suppress_shared_account_prompt", False)
102+
)
103+
79104
def suppress_submit_warning(self):
80105
"""locally suppress submit warning"""
81106
self._suppress_submit_warning = True
82107

83-
def cancel_local_submit_warning_settings(self):
84-
"""cancel local submit warning settings"""
85-
self._suppress_submit_warning = None
86-
87108
def show_submit_warning(self):
88109
"""locally show submit warning"""
89110
self._suppress_submit_warning = False
@@ -100,6 +121,14 @@ def is_suppress_submit_warning(self):
100121
return self._suppress_submit_warning
101122
return self.config.get("user", {}).get("config", {}).get("suppress_submit_warning", False)
102123

124+
def cancel_local_submit_warning_settings(self):
125+
"""cancel local submit warning settings"""
126+
self._suppress_submit_warning = None
127+
128+
def cancel_local_shared_account_prompt_settings(self):
129+
"""cancel local shared account submit warning settings"""
130+
self._suppress_shared_account_prompt = None
131+
103132
@property
104133
def do_validation(self):
105134
"""for handling user side validation (pydantic)

0 commit comments

Comments
 (0)