From 90413a07752583fab8676730797ba3467add27a5 Mon Sep 17 00:00:00 2001 From: Stefan Hellander Date: Fri, 23 Feb 2024 12:20:37 +0100 Subject: [PATCH 1/8] Adds option to pass auth token when initializing APIClient (for use with Studio) --- .vscode/settings.json | 2 +- fedn/fedn/network/api/client.py | 65 ++++++++++++++++++++------------- 2 files changed, 41 insertions(+), 26 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 07cfc57ae..d4c2ea8ad 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,7 @@ { "editor.formatOnSave": true, "editor.codeActionsOnSave": { - "source.organizeImports": true + "source.organizeImports": "explicit" }, "python.linting.enabled": true, "python.linting.flake8Enabled": true, diff --git a/fedn/fedn/network/api/client.py b/fedn/fedn/network/api/client.py index 16429825f..4cd053c42 100644 --- a/fedn/fedn/network/api/client.py +++ b/fedn/fedn/network/api/client.py @@ -1,7 +1,11 @@ import json +import os import requests +from fedn.common.log_config import (logger, set_log_level_from_string, + set_log_stream) + __all__ = ['APIClient'] @@ -18,18 +22,29 @@ class APIClient: :type verify: bool """ - def __init__(self, host, port, secure=False, verify=False): + def __init__(self, host, port=None, secure=False, verify=False, token=None, verbosity="INFO"): self.host = host self.port = port self.secure = secure self.verify = verify + self.header = {} + # Check if auth token is set by environment variable + env_token = os.environ("FEDN_AUTH_TOKEN", False) + if env_token: + self.header = {"Authorization": "Token {}".format(token)} + # Override potential env variable if token is passed as argument. + if token: + self.header = {"Authorization": "Token {}".format(token)} + set_log_level_from_string(verbosity) def _get_url(self, endpoint): if self.secure: protocol = 'https' else: protocol = 'http' - return f'{protocol}://{self.host}:{self.port}/{endpoint}' + if self.port: + return f'{protocol}://{self.host}:{self.port}/{endpoint}' + return f'{protocol}://{self.host}/{endpoint}' def get_model_trail(self): """ Get the model trail. @@ -37,7 +52,7 @@ def get_model_trail(self): :return: The model trail as dict including commit timestamp. :rtype: dict """ - response = requests.get(self._get_url('get_model_trail'), verify=self.verify) + response = requests.get(self._get_url('get_model_trail'), verify=self.verify, headers=self.header) return response.json() def list_models(self, session_id=None): @@ -46,7 +61,7 @@ def list_models(self, session_id=None): :return: All models. :rtype: dict """ - response = requests.get(self._get_url('list_models'), params={'session_id': session_id}, verify=self.verify) + response = requests.get(self._get_url('list_models'), params={'session_id': session_id}, verify=self.verify, headers=self.header) return response.json() def list_clients(self): @@ -55,7 +70,7 @@ def list_clients(self): return: All clients. rtype: dict """ - response = requests.get(self._get_url('list_clients')) + response = requests.get(self._get_url('list_clients'), verify=self.verify, headers=self.header) return response.json() def get_active_clients(self, combiner_id): @@ -66,7 +81,7 @@ def get_active_clients(self, combiner_id): :return: All active clients. :rtype: dict """ - response = requests.get(self._get_url('get_active_clients'), params={'combiner': combiner_id}, verify=self.verify) + response = requests.get(self._get_url('get_active_clients'), params={'combiner': combiner_id}, verify=self.verify, headers=self.header) return response.json() def get_client_config(self, checksum=True): @@ -78,7 +93,7 @@ def get_client_config(self, checksum=True): :return: The client configuration. :rtype: dict """ - response = requests.get(self._get_url('get_client_config'), params={'checksum': checksum}, verify=self.verify) + response = requests.get(self._get_url('get_client_config'), params={'checksum': checksum}, verify=self.verify, headers=self.header) return response.json() def list_combiners(self): @@ -87,7 +102,7 @@ def list_combiners(self): :return: All combiners with info. :rtype: dict """ - response = requests.get(self._get_url('list_combiners')) + response = requests.get(self._get_url('list_combiners'), verify=self.verify, headers=self.header) return response.json() def get_combiner(self, combiner_id): @@ -98,7 +113,7 @@ def get_combiner(self, combiner_id): :return: The combiner info. :rtype: dict """ - response = requests.get(self._get_url(f'get_combiner?combiner={combiner_id}'), verify=self.verify) + response = requests.get(self._get_url(f'get_combiner?combiner={combiner_id}'), verify=self.verify, headers=self.header) return response.json() def list_rounds(self): @@ -107,7 +122,7 @@ def list_rounds(self): :return: All rounds with config and metrics. :rtype: dict """ - response = requests.get(self._get_url('list_rounds')) + response = requests.get(self._get_url('list_rounds'), verify=self.verify, headers=self.header) return response.json() def get_round(self, round_id): @@ -118,7 +133,7 @@ def get_round(self, round_id): :return: The round config and metrics. :rtype: dict """ - response = requests.get(self._get_url(f'get_round?round_id={round_id}'), verify=self.verify) + response = requests.get(self._get_url(f'get_round?round_id={round_id}'), verify=self.verify, headers=self.header) return response.json() def start_session(self, session_id=None, aggregator='fedavg', model_id=None, round_timeout=180, rounds=5, round_buffer_size=-1, delete_models=True, @@ -162,7 +177,7 @@ def start_session(self, session_id=None, aggregator='fedavg', model_id=None, rou 'helper': helper, 'min_clients': min_clients, 'requested_clients': requested_clients - }, verify=self.verify + }, verify=self.verify, headers=self.header ) return response.json() @@ -172,7 +187,7 @@ def list_sessions(self): :return: All sessions in dict. :rtype: dict """ - response = requests.get(self._get_url('list_sessions'), verify=self.verify) + response = requests.get(self._get_url('list_sessions'), verify=self.verify, headers=self.header) return response.json() def get_session(self, session_id): @@ -183,7 +198,7 @@ def get_session(self, session_id): :return: The session as a json object. :rtype: dict """ - response = requests.get(self._get_url(f'get_session?session_id={session_id}'), self.verify) + response = requests.get(self._get_url(f'get_session?session_id={session_id}'), self.verify, headers=self.header) return response.json() def session_is_finished(self, session_id): @@ -218,7 +233,7 @@ def set_package(self, path: str, helper: str, name: str = None, description: str """ with open(path, 'rb') as file: response = requests.post(self._get_url('set_package'), files={'file': file}, data={ - 'helper': helper, 'name': name, 'description': description}, verify=self.verify) + 'helper': helper, 'name': name, 'description': description}, verify=self.verify, headers=self.header) return response.json() def get_package(self): @@ -227,7 +242,7 @@ def get_package(self): :return: The compute package with info. :rtype: dict """ - response = requests.get(self._get_url('get_package'), verify=self.verify) + response = requests.get(self._get_url('get_package'), verify=self.verify, headers=self.header) return response.json() def list_compute_packages(self): @@ -236,7 +251,7 @@ def list_compute_packages(self): :return: All compute packages with info. :rtype: dict """ - response = requests.get(self._get_url('list_compute_packages'), verify=self.verify) + response = requests.get(self._get_url('list_compute_packages'), verify=self.verify, headers=self.header) return response.json() def download_package(self, path): @@ -247,7 +262,7 @@ def download_package(self, path): :return: Message with success or failure. :rtype: dict """ - response = requests.get(self._get_url('download_package'), verify=self.verify) + response = requests.get(self._get_url('download_package'), verify=self.verify, headers=self.header) if response.status_code == 200: with open(path, 'wb') as file: file.write(response.content) @@ -261,7 +276,7 @@ def get_package_checksum(self): :return: The checksum. :rtype: dict """ - response = requests.get(self._get_url('get_package_checksum'), verify=self.verify) + response = requests.get(self._get_url('get_package_checksum'), verify=self.verify, headers=self.header) return response.json() def get_latest_model(self): @@ -270,7 +285,7 @@ def get_latest_model(self): :return: The latest model id. :rtype: dict """ - response = requests.get(self._get_url('get_latest_model'), verify=self.verify) + response = requests.get(self._get_url('get_latest_model'), verify=self.verify, headers=self.header) return response.json() def get_initial_model(self): @@ -279,7 +294,7 @@ def get_initial_model(self): :return: The initial model id. :rtype: dict """ - response = requests.get(self._get_url('get_initial_model'), verify=self.verify) + response = requests.get(self._get_url('get_initial_model'), verify=self.verify, headers=self.header) return response.json() def set_initial_model(self, path): @@ -291,7 +306,7 @@ def set_initial_model(self, path): :rtype: dict """ with open(path, 'rb') as file: - response = requests.post(self._get_url('set_initial_model'), files={'file': file}, verify=self.verify) + response = requests.post(self._get_url('set_initial_model'), files={'file': file}, verify=self.verify, headers=self.header) return response.json() def get_controller_status(self): @@ -300,7 +315,7 @@ def get_controller_status(self): :return: The status of the controller. :rtype: dict """ - response = requests.get(self._get_url('get_controller_status'), verify=self.verify) + response = requests.get(self._get_url('get_controller_status'), verify=self.verify, headers=self.header) return response.json() def get_events(self, **kwargs): @@ -309,7 +324,7 @@ def get_events(self, **kwargs): :return: The events in dict :rtype: dict """ - response = requests.get(self._get_url('get_events'), params=kwargs, verify=self.verify) + response = requests.get(self._get_url('get_events'), params=kwargs, verify=self.verify, headers=self.header) return response.json() def list_validations(self, **kwargs): @@ -318,5 +333,5 @@ def list_validations(self, **kwargs): :return: All validations in dict. :rtype: dict """ - response = requests.get(self._get_url('list_validations'), params=kwargs, verify=self.verify) + response = requests.get(self._get_url('list_validations'), params=kwargs, verify=self.verify, headers=self.header) return response.json() From c36449c9fcd7fcd0721b76e75e6fdff0fdab85b6 Mon Sep 17 00:00:00 2001 From: Stefan Hellander Date: Fri, 23 Feb 2024 12:26:10 +0100 Subject: [PATCH 2/8] Revert settings.json --- .vscode/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index d4c2ea8ad..07cfc57ae 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,7 @@ { "editor.formatOnSave": true, "editor.codeActionsOnSave": { - "source.organizeImports": "explicit" + "source.organizeImports": true }, "python.linting.enabled": true, "python.linting.flake8Enabled": true, From dc2dea615c452506adec8ff62145c82f4461b33d Mon Sep 17 00:00:00 2001 From: Stefan Hellander Date: Fri, 23 Feb 2024 12:28:56 +0100 Subject: [PATCH 3/8] Fix unused import --- fedn/fedn/network/api/client.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/fedn/fedn/network/api/client.py b/fedn/fedn/network/api/client.py index 4cd053c42..c973c74b3 100644 --- a/fedn/fedn/network/api/client.py +++ b/fedn/fedn/network/api/client.py @@ -3,9 +3,6 @@ import requests -from fedn.common.log_config import (logger, set_log_level_from_string, - set_log_stream) - __all__ = ['APIClient'] @@ -35,7 +32,6 @@ def __init__(self, host, port=None, secure=False, verify=False, token=None, verb # Override potential env variable if token is passed as argument. if token: self.header = {"Authorization": "Token {}".format(token)} - set_log_level_from_string(verbosity) def _get_url(self, endpoint): if self.secure: From 1014f52f8f6bc8b7297a71b335fed669df1fc18b Mon Sep 17 00:00:00 2001 From: Stefan Hellander Date: Fri, 23 Feb 2024 12:39:08 +0100 Subject: [PATCH 4/8] Fix read of env variable --- fedn/fedn/network/api/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fedn/fedn/network/api/client.py b/fedn/fedn/network/api/client.py index c973c74b3..cc726a30c 100644 --- a/fedn/fedn/network/api/client.py +++ b/fedn/fedn/network/api/client.py @@ -26,7 +26,7 @@ def __init__(self, host, port=None, secure=False, verify=False, token=None, verb self.verify = verify self.header = {} # Check if auth token is set by environment variable - env_token = os.environ("FEDN_AUTH_TOKEN", False) + env_token = os.environ.get("FEDN_AUTH_TOKEN", False) if env_token: self.header = {"Authorization": "Token {}".format(token)} # Override potential env variable if token is passed as argument. From a5fcc2a2dd943fa60f627bccfe83f4b1d66c3149 Mon Sep 17 00:00:00 2001 From: Stefan Hellander Date: Thu, 29 Feb 2024 22:19:10 +0100 Subject: [PATCH 5/8] Made auth scheme configurable --- .vscode/settings.json | 2 +- fedn/fedn/network/api/client.py | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 07cfc57ae..d4c2ea8ad 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,7 @@ { "editor.formatOnSave": true, "editor.codeActionsOnSave": { - "source.organizeImports": true + "source.organizeImports": "explicit" }, "python.linting.enabled": true, "python.linting.flake8Enabled": true, diff --git a/fedn/fedn/network/api/client.py b/fedn/fedn/network/api/client.py index cc726a30c..1dab2bd0b 100644 --- a/fedn/fedn/network/api/client.py +++ b/fedn/fedn/network/api/client.py @@ -19,19 +19,23 @@ class APIClient: :type verify: bool """ - def __init__(self, host, port=None, secure=False, verify=False, token=None, verbosity="INFO"): + def __init__(self, host, port=None, secure=False, verify=False, token=None, auth_scheme=None): self.host = host self.port = port self.secure = secure self.verify = verify self.header = {} + # Auth scheme passed as argument overrides environment variable. + # "Token" is the default auth scheme. + if not auth_scheme: + auth_scheme = os.environ.get("FEDN_AUTH_SCHEME", "Token") # Check if auth token is set by environment variable env_token = os.environ.get("FEDN_AUTH_TOKEN", False) if env_token: - self.header = {"Authorization": "Token {}".format(token)} + self.header = {"Authorization": f"{auth_scheme} {env_token}"} # Override potential env variable if token is passed as argument. if token: - self.header = {"Authorization": "Token {}".format(token)} + self.header = {"Authorization": f"{auth_scheme} {token}"} def _get_url(self, endpoint): if self.secure: From 263e83d59eac22221d866a96c7efd3a832b118ba Mon Sep 17 00:00:00 2001 From: Stefan Hellander Date: Thu, 29 Feb 2024 22:20:32 +0100 Subject: [PATCH 6/8] Revert unintentional change of vscode settings. --- .vscode/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index d4c2ea8ad..07cfc57ae 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,7 @@ { "editor.formatOnSave": true, "editor.codeActionsOnSave": { - "source.organizeImports": "explicit" + "source.organizeImports": true }, "python.linting.enabled": true, "python.linting.flake8Enabled": true, From 84f33689baf233ef1b1b88c592db94002d89ecc8 Mon Sep 17 00:00:00 2001 From: Stefan Hellander Date: Thu, 29 Feb 2024 22:24:03 +0100 Subject: [PATCH 7/8] Small improvement. --- fedn/fedn/network/api/client.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/fedn/fedn/network/api/client.py b/fedn/fedn/network/api/client.py index 1dab2bd0b..14f144473 100644 --- a/fedn/fedn/network/api/client.py +++ b/fedn/fedn/network/api/client.py @@ -29,11 +29,10 @@ def __init__(self, host, port=None, secure=False, verify=False, token=None, auth # "Token" is the default auth scheme. if not auth_scheme: auth_scheme = os.environ.get("FEDN_AUTH_SCHEME", "Token") - # Check if auth token is set by environment variable - env_token = os.environ.get("FEDN_AUTH_TOKEN", False) - if env_token: - self.header = {"Authorization": f"{auth_scheme} {env_token}"} # Override potential env variable if token is passed as argument. + if not token: + token = os.environ.get("FEDN_AUTH_TOKEN", False) + if token: self.header = {"Authorization": f"{auth_scheme} {token}"} From 64417830d68e4bf47edf95659af5bee02b17bba0 Mon Sep 17 00:00:00 2001 From: Stefan Hellander Date: Fri, 1 Mar 2024 10:45:40 +0100 Subject: [PATCH 8/8] Linting --- fedn/fedn/network/api/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fedn/fedn/network/api/client.py b/fedn/fedn/network/api/client.py index 14f144473..00532d2da 100644 --- a/fedn/fedn/network/api/client.py +++ b/fedn/fedn/network/api/client.py @@ -32,7 +32,7 @@ def __init__(self, host, port=None, secure=False, verify=False, token=None, auth # Override potential env variable if token is passed as argument. if not token: token = os.environ.get("FEDN_AUTH_TOKEN", False) - + if token: self.header = {"Authorization": f"{auth_scheme} {token}"}