Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] PoC for Rust-like error handling #8852

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/syft/src/syft/service/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ def reconstruct_args_kwargs(
final_kwargs[param_key] = param.default
else:
raise Exception(f"Missing {param_key} not in kwargs.")
final_kwargs['context'] = kwargs['context'] if 'context' in kwargs else None
return (args, final_kwargs)


Expand Down
145 changes: 87 additions & 58 deletions packages/syft/src/syft/service/settings/settings_service.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
# stdlib

# stdlib

# stdlib
# type: ignore

# third party
from result import Err
from result import Ok
from result import Result

# relative
from ...serde.serializable import serializable
Expand Down Expand Up @@ -36,56 +31,89 @@ def __init__(self, store: DocumentStore) -> None:
self.stash = SettingsStash(store=store)

@service_method(path="settings.get", name="get")
def get(self, context: UnauthedServiceContext) -> Result[Ok, Err]:
"""Get Settings"""

result = self.stash.get_all(context.node.signing_key.verify_key)
if result.is_ok():
settings = result.ok()
# check if the settings list is empty
if len(settings) == 0:
def get(self, context: UnauthedServiceContext) -> NodeSettings | SyftError:
"""
Get the Node Settings
Returns:
NodeSettings | SyftError : The Node Settings or an error if no settings are found.
"""

result = self.stash.get(context.node.signing_key.verify_key)

match result: # type: ignore
case Ok(None):
return SyftError(message="No settings found")
result = settings[0]
return Ok(result)
else:
return SyftError(message=result.err())
case Ok(NodeSettings() as settings):
return settings
case Err(err_message):
return SyftError(message=err_message)

@service_method(path="settings.set", name="set")
def set(
self, context: AuthedServiceContext, settings: NodeSettings
) -> Result[Ok, Err]:
"""Set a new the Node Settings"""
) -> NodeSettings | SyftError:
"""
Set a new the Node Settings
Returns:
NodeSettings | SyftError : The new Node Settings or an error if the settings could not be set.
"""
result = self.stash.set(context.credentials, settings)
if result.is_ok():
return result
else:
return SyftError(message=result.err())

@service_method(path="settings.update", name="update")
match result:
case Ok(settings):
return settings
case Err(err_message):
return SyftError(message=err_message)

@service_method(path="settings.update", name="update", autosplat=["settings"])
def update(
self, context: AuthedServiceContext, settings: NodeSettingsUpdate
) -> Result[SyftSuccess, SyftError]:
result = self.stash.get_all(context.credentials)
if result.is_ok():
current_settings = result.ok()
if len(current_settings) > 0:
new_settings = current_settings[0].model_copy(
) -> SyftSuccess | SyftError:
"""
Update the Node Settings using the provided values.

Args:
name: Optional[str]
Node name
organization: Optional[str]
Organization name
description: Optional[str]
Node description
on_board: Optional[bool]
Show onboarding panel when a user logs in for the first time
signup_enabled: Optional[bool]
Enable/Disable registration
admin_email: Optional[str]
Administrator email
association_request_auto_approval: Optional[bool]

Returns:
SyftSuccess | SyftError: A result indicating the success or failure of the update operation.

Example:
>>> node_client.update(name='foo', organization='bar', description='baz', signup_enabled=True)
SyftSuccess: Settings updated successfully.
"""

result = self.get(context)
match result: # type: ignore
case NodeSettings():
new_settings = result.model_copy(
update=settings.to_dict(exclude_empty=True)
)
update_result = self.stash.update(context.credentials, new_settings)
if update_result.is_ok():
return SyftSuccess(
message=(
"Settings updated successfully. "
+ "You must call <client>.refresh() to sync your client with the changes."
match update_result:
case Ok():
return SyftSuccess(
message=(
"Settings updated successfully. "
+ "You must call <client>.refresh() to sync your client with the changes."
)
)
)
else:
return SyftError(message=update_result.err())
else:
return SyftError(message="No settings found")
else:
return SyftError(message=result.err())
case Err(err_message):
return SyftError(message=err_message)
case SyftError():
return result

@service_method(
path="settings.enable_notifications",
Expand Down Expand Up @@ -134,16 +162,15 @@ def allow_guest_signup(
"""Enable/Disable Registration for Data Scientist or Guest Users."""
flags.CAN_REGISTER = enable

method = context.node.get_service_method(SettingsService.update)
settings = NodeSettingsUpdate(signup_enabled=enable)

result = method(context=context, settings=settings)

if result.is_err():
return SyftError(message=f"Failed to update settings: {result.err()}")
new_settings = NodeSettingsUpdate(signup_enabled=enable)
result = self.update(context, settings=new_settings)

message = "enabled" if enable else "disabled"
return SyftSuccess(message=f"Registration feature successfully {message}")
match result: # type: ignore
case SyftSuccess():
flag = "enabled" if enable else "disabled"
return SyftSuccess(message=f"Registration feature successfully {flag}")
case SyftError():
return SyftError(message=f"Failed to update settings: {result}")

@service_method(
path="settings.allow_association_request_auto_approval",
Expand All @@ -154,10 +181,12 @@ def allow_association_request_auto_approval(
) -> SyftSuccess | SyftError:
new_settings = NodeSettingsUpdate(association_request_auto_approval=enable)
result = self.update(context, settings=new_settings)
if isinstance(result, SyftError):
return result

message = "enabled" if enable else "disabled"
return SyftSuccess(
message="Association request auto-approval successfully " + message
)
match result: # type: ignore
case SyftSuccess():
flag = "enabled" if enable else "disabled"
return SyftSuccess(
message=f"Association request auto-approval successfully {flag}"
)
case SyftError():
return SyftError(message=f"Failed to update settings: {result}")
42 changes: 34 additions & 8 deletions packages/syft/src/syft/service/settings/settings_stash.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# stdlib

# stdlib
from typing import Any

# third party
from result import Err
from result import Ok
from result import Result

# relative
Expand Down Expand Up @@ -30,6 +35,22 @@ class SettingsStash(BaseUIDStoreStash):
def __init__(self, store: DocumentStore) -> None:
super().__init__(store=store)

def check_type(self, obj: Any) -> Result[NodeSettings, str]:
if isinstance(obj, NodeSettings):
return Ok(obj)
else:
return Err(f"{type(obj)} does not match required type: {NodeSettings}")

def get(self, credentials: SyftVerifyKey) -> Result[NodeSettings | None, str]:
result = self.get_all(credentials=credentials)
match result:
case Ok(settings) if settings:
return Ok(None)
case Ok(settings):
return Ok(settings[0]) # type: ignore
case Err(err_message):
return Err(err_message)

def set(
self,
credentials: SyftVerifyKey,
Expand All @@ -38,20 +59,25 @@ def set(
add_storage_permission: bool = True,
ignore_duplicates: bool = False,
) -> Result[NodeSettings, str]:
res = self.check_type(settings, self.object_type)
result = self.check_type(settings)
# we dont use and_then logic here as it is hard because of the order of the arguments
if res.is_err():
return res
return super().set(credentials=credentials, obj=res.ok())
match result:
case Ok(obj):
return super().set(credentials=credentials, obj=obj) # type: ignore
case Err(error):
return Err(error)

def update(
self,
credentials: SyftVerifyKey,
settings: NodeSettings,
has_permission: bool = False,
) -> Result[NodeSettings, str]:
res = self.check_type(settings, self.object_type)
result = self.check_type(settings)
# we dont use and_then logic here as it is hard because of the order of the arguments
if res.is_err():
return res
return super().update(credentials=credentials, obj=res.ok())

match result:
case Ok(obj):
return super().update(credentials=credentials, obj=obj) # type: ignore
case Err(error):
return Err(error)
8 changes: 4 additions & 4 deletions packages/syft/tests/syft/settings/settings_service_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ def mock_stash_get_all(root_verify_key) -> Ok:
monkeypatch.setattr(settings_service.stash, "get_all", mock_stash_get_all)

# update the settings in the settings stash using settings_service
response = settings_service.update(authed_context, update_settings)
response = settings_service.update(context=authed_context, settings=update_settings)

# not_updated_settings = response.ok()[1]

Expand All @@ -174,7 +174,7 @@ def mock_stash_get_all_error(credentials) -> Err:
return Err(mock_error_message)

monkeypatch.setattr(settings_service.stash, "get_all", mock_stash_get_all_error)
response = settings_service.update(authed_context, update_settings)
response = settings_service.update(context=authed_context, settings=update_settings)

assert isinstance(response, SyftError)
assert response.message == mock_error_message
Expand All @@ -185,7 +185,7 @@ def test_settingsservice_update_stash_empty(
update_settings: NodeSettingsUpdate,
authed_context: AuthedServiceContext,
) -> None:
response = settings_service.update(authed_context, update_settings)
response = settings_service.update(context=authed_context, settings=update_settings)

assert isinstance(response, SyftError)
assert response.message == "No settings found"
Expand Down Expand Up @@ -214,7 +214,7 @@ def mock_stash_update_error(credentials, update_settings: NodeSettings) -> Err:

monkeypatch.setattr(settings_service.stash, "update", mock_stash_update_error)

response = settings_service.update(authed_context, update_settings)
response = settings_service.update(context=authed_context, settings=update_settings)

assert isinstance(response, SyftError)
assert response.message == mock_update_error_message
Expand Down
Loading