From adcfe126310f02b07be1cf74c34bf06f313b08cd Mon Sep 17 00:00:00 2001 From: pem70 Date: Thu, 23 May 2024 12:32:42 -0400 Subject: [PATCH 01/22] Adding logger modules Signed-off-by: pem70 --- .vscode/settings.json | 10 ++++++- src/core/zowe/core_for_zowe_sdk/__init__.py | 15 ++++++++++ .../zowe/core_for_zowe_sdk/config_file.py | 19 +++++++++++-- src/core/zowe/core_for_zowe_sdk/connection.py | 5 ++++ .../core_for_zowe_sdk/credential_manager.py | 9 +++++- .../zowe/core_for_zowe_sdk/profile_manager.py | 15 ++++++++-- .../zowe/core_for_zowe_sdk/request_handler.py | 5 ++++ src/core/zowe/core_for_zowe_sdk/sdk_api.py | 4 +++ src/core/zowe/core_for_zowe_sdk/session.py | 5 ++++ .../zowe/core_for_zowe_sdk/zosmf_profile.py | 5 ++++ .../zowe/zos_files_for_zowe_sdk/files.py | 28 ++++++++++++++----- .../zowe/zos_jobs_for_zowe_sdk/jobs.py | 7 +++++ 12 files changed, 113 insertions(+), 14 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 57c96298..5079d79f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,5 +9,13 @@ "python.testing.pytestArgs": ["tests"], "python.testing.pytestEnabled": true, "python.testing.unittestEnabled": false, - "rust-analyzer.linkedProjects": ["./src/secrets/Cargo.toml"], + "rust-analyzer.linkedProjects": [ + "./src/secrets/Cargo.toml" + ], + "python.analysis.extraPaths": [ + "./src/core", + "./src/zos_console", + "./src/zos_files", + "./src/zos_jobs" + ], } diff --git a/src/core/zowe/core_for_zowe_sdk/__init__.py b/src/core/zowe/core_for_zowe_sdk/__init__.py index 615b966f..58fc34a0 100644 --- a/src/core/zowe/core_for_zowe_sdk/__init__.py +++ b/src/core/zowe/core_for_zowe_sdk/__init__.py @@ -13,3 +13,18 @@ from .session import Session from .session_constants import * from .zosmf_profile import ZosmfProfile + +import logging +import os + +dirname = os.path.join(os.path.expanduser("~"), ".zowe/logs") + +if not os.path.isdir(dirname): + os.makedirs(dirname) + +logging.basicConfig( + filename=os.path.join(dirname, "python_sdk_logs.log"), + level=logging.INFO, + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", + datefmt="%m/%d/%Y %I:%M:%S %p", +) diff --git a/src/core/zowe/core_for_zowe_sdk/config_file.py b/src/core/zowe/core_for_zowe_sdk/config_file.py index 581927ce..d873628c 100644 --- a/src/core/zowe/core_for_zowe_sdk/config_file.py +++ b/src/core/zowe/core_for_zowe_sdk/config_file.py @@ -21,6 +21,8 @@ import commentjson import requests +import logging + from .credential_manager import CredentialManager from .custom_warnings import ProfileNotFoundWarning, ProfileParsingWarning from .exceptions import ProfileNotFound @@ -71,6 +73,8 @@ class ConfigFile: jsonc: Optional[dict] = None _missing_secure_props: list = field(default_factory=list) + __logger = logging.getLogger(__name__) + @property def filename(self) -> str: if self.type == TEAM_CONFIG: @@ -101,11 +105,13 @@ def location(self, dirname: str) -> None: if os.path.isdir(dirname): self._location = dirname else: + self.__logger.error(f"given path {dirname} is not valid") raise FileNotFoundError(f"given path {dirname} is not valid") def init_from_file( self, validate_schema: Optional[bool] = True, + suppress_config_file_warnings: Optional[bool] = True, ) -> None: """ Initializes the class variable after @@ -118,7 +124,9 @@ def init_from_file( pass if self.filepath is None or not os.path.isfile(self.filepath): - warnings.warn(f"Config file does not exist at {self.filepath}") + if not suppress_config_file_warnings: + self.__logger.warning(f"Config file does not exist at {self.filepath}") + warnings.warn(f"Config file does not exist at {self.filepath}") return with open(self.filepath, encoding="UTF-8", mode="r") as fileobj: @@ -148,7 +156,8 @@ def validate_schema(self) -> None: path_schema_json = self.schema_path if path_schema_json is None: # check if the $schema property is not defined - warnings.warn(f"$schema property could not found") + self.__logger.warning(f"Could not find $schema property") + warnings.warn(f"Could not find $schema property") # validate the $schema property if path_schema_json: @@ -213,6 +222,7 @@ def get_profile( self.init_from_file(validate_schema) if profile_name is None and profile_type is None: + self.__logger.error(f"Failed to load profile '{profile_name}' because Could not find profile as both profile_name and profile_type is not set") raise ProfileNotFound( profile_name=profile_name, error_msg="Could not find profile as both profile_name and profile_type is not set.", @@ -250,7 +260,6 @@ def autodiscover_config_dir(self) -> None: break current_dir = os.path.dirname(current_dir) - raise FileNotFoundError(f"Could not find the file {self.filename}") def get_profilename_from_profiletype(self, profile_type: str) -> str: @@ -268,6 +277,7 @@ def get_profilename_from_profiletype(self, profile_type: str) -> str: try: profilename = self.defaults[profile_type] except KeyError: + self.__logger.warn(f"Given profile type '{profile_type}' has no default profilename") warnings.warn( f"Given profile type '{profile_type}' has no default profilename", ProfileParsingWarning, @@ -282,12 +292,14 @@ def get_profilename_from_profiletype(self, profile_type: str) -> str: if profile_type == temp_profile_type: return key except KeyError: + self.__logger.warning(f"Profile '{key}' has no type attribute") warnings.warn( f"Profile '{key}' has no type attribute", ProfileParsingWarning, ) # if no profile with matching type found, we raise an exception + self.__logger.error(f"No profile with matching profile_type '{profile_type}' found") raise ProfileNotFound( profile_name=profile_type, error_msg=f"No profile with matching profile_type '{profile_type}' found", @@ -334,6 +346,7 @@ def load_profile_properties(self, profile_name: str) -> dict: props = {**profile.get("properties", {}), **props} secure_fields.extend(profile.get("secure", [])) else: + self.__logger.warning(f"Profile {profile_name} not found") warnings.warn(f"Profile {profile_name} not found", ProfileNotFoundWarning) lst.pop() diff --git a/src/core/zowe/core_for_zowe_sdk/connection.py b/src/core/zowe/core_for_zowe_sdk/connection.py index fa97609c..2f764a99 100644 --- a/src/core/zowe/core_for_zowe_sdk/connection.py +++ b/src/core/zowe/core_for_zowe_sdk/connection.py @@ -9,7 +9,9 @@ Copyright Contributors to the Zowe Project. """ + from .exceptions import MissingConnectionArgs +import logging class ApiConnection: @@ -28,8 +30,11 @@ class ApiConnection: """ def __init__(self, host_url, user, password, ssl_verification=True): + logger = logging.getLogger(__name__) + """Construct an ApiConnection object.""" if not host_url or not user or not password: + logger.error("Missing connection argument") raise MissingConnectionArgs() self.host_url = host_url diff --git a/src/core/zowe/core_for_zowe_sdk/credential_manager.py b/src/core/zowe/core_for_zowe_sdk/credential_manager.py index 3459619c..b640d9b1 100644 --- a/src/core/zowe/core_for_zowe_sdk/credential_manager.py +++ b/src/core/zowe/core_for_zowe_sdk/credential_manager.py @@ -9,12 +9,15 @@ Copyright Contributors to the Zowe Project. """ + import base64 import sys from typing import Optional import commentjson +import logging + from .constants import constants from .exceptions import SecureProfileLoadFailed @@ -27,6 +30,7 @@ class CredentialManager: secure_props = {} + __logger = logging.getLogger(__name__) @staticmethod def load_secure_props() -> None: @@ -49,6 +53,7 @@ def load_secure_props() -> None: return except Exception as exc: + CredentialManager.__logger.error(f"Fail to load secure profile {constants["ZoweServiceName"]}") raise SecureProfileLoadFailed(constants["ZoweServiceName"], error_msg=str(exc)) from exc secure_config: str @@ -75,7 +80,9 @@ def save_secure_props() -> None: if sys.platform == "win32": # Delete the existing credential CredentialManager._delete_credential(constants["ZoweServiceName"], constants["ZoweAccountName"]) - CredentialManager._set_credential(constants["ZoweServiceName"], constants["ZoweAccountName"], encoded_credential) + CredentialManager._set_credential( + constants["ZoweServiceName"], constants["ZoweAccountName"], encoded_credential + ) @staticmethod def _get_credential(service_name: str, account_name: str) -> Optional[str]: diff --git a/src/core/zowe/core_for_zowe_sdk/profile_manager.py b/src/core/zowe/core_for_zowe_sdk/profile_manager.py index 7c0c78d1..20a34a64 100644 --- a/src/core/zowe/core_for_zowe_sdk/profile_manager.py +++ b/src/core/zowe/core_for_zowe_sdk/profile_manager.py @@ -15,6 +15,7 @@ import warnings from copy import deepcopy from typing import Optional +import logging import jsonschema from deepmerge import always_merger @@ -57,10 +58,13 @@ def __init__(self, appname: str = "zowe", show_warnings: bool = True): self.project_config = ConfigFile(type=TEAM_CONFIG, name=appname) self.project_user_config = ConfigFile(type=USER_CONFIG, name=appname) + self.__logger = logging.getLogger(__name__) + self.global_config = ConfigFile(type=TEAM_CONFIG, name=GLOBAL_CONFIG_NAME) try: self.global_config.location = GLOBAL_CONFIG_LOCATION except Exception: + self.__logger.warning("Could not find Global Config Directory") warnings.warn( "Could not find Global Config Directory, please provide one.", ConfigNotFoundWarning, @@ -70,6 +74,7 @@ def __init__(self, appname: str = "zowe", show_warnings: bool = True): try: self.global_user_config.location = GLOBAL_CONFIG_LOCATION except Exception: + self.__logger.warning("Could not find Global User Config Directory") warnings.warn( "Could not find Global User Config Directory, please provide one.", ConfigNotFoundWarning, @@ -218,6 +223,7 @@ def load( check_missing_props: bool = True, validate_schema: Optional[bool] = True, override_with_env: Optional[bool] = False, + suppress_config_file_warnings: Optional[bool] = True, ) -> dict: """Load connection details from a team config profile. Returns @@ -236,7 +242,9 @@ def load( If `profile_type` is not base, then we will load properties from both `profile_type` and base profiles and merge them together. """ + if profile_name is None and profile_type is None: + self.__logger.error(f"Failed to load profile as both profile_name and profile_type are not set") raise ProfileNotFound( profile_name=profile_name, error_msg="Could not find profile as both profile_name and profile_type is not set.", @@ -254,12 +262,13 @@ def load( cfg_name = None cfg_schema = None cfg_schema_dir = None - + for cfg_layer in (self.project_user_config, self.project_config, self.global_user_config, self.global_config): if cfg_layer.profiles is None: try: - cfg_layer.init_from_file(validate_schema) + cfg_layer.init_from_file(validate_schema, suppress_config_file_warnings) except SecureProfileLoadFailed: + self.__logger.warning(f"Could not load secure properties for {cfg_layer.filepath}") warnings.warn( f"Could not load secure properties for {cfg_layer.filepath}", SecurePropsNotFoundWarning, @@ -314,6 +323,7 @@ def load( missing_props.add(item) if len(missing_props) > 0: + self.__logger.error(f"Failed to load secure values: {missing_props}") raise SecureValuesNotFound(values=missing_props) warnings.resetwarnings() @@ -366,6 +376,7 @@ def get_highest_priority_layer(self, json_path: str) -> Optional[ConfigFile]: highest_layer = layer if highest_layer is None: + self.__logger.error(f"Could not find a valid layer for {json_path}") raise FileNotFoundError(f"Could not find a valid layer for {json_path}") return highest_layer diff --git a/src/core/zowe/core_for_zowe_sdk/request_handler.py b/src/core/zowe/core_for_zowe_sdk/request_handler.py index 062bfd02..7f3ac1c6 100644 --- a/src/core/zowe/core_for_zowe_sdk/request_handler.py +++ b/src/core/zowe/core_for_zowe_sdk/request_handler.py @@ -12,6 +12,7 @@ import requests import urllib3 +import logging from .exceptions import InvalidRequestMethod, RequestFailed, UnexpectedStatus @@ -40,6 +41,7 @@ def __init__(self, session_arguments): self.session_arguments = session_arguments self.valid_methods = ["GET", "POST", "PUT", "DELETE"] self.__handle_ssl_warnings() + self.__logger = logging.getLogger(__name__) def __handle_ssl_warnings(self): """Turn off warnings if the SSL verification argument if off.""" @@ -104,6 +106,7 @@ def __validate_method(self): If the input request method is not supported """ if self.method not in self.valid_methods: + self.__logger.error(f"Invalid HTTP method input {self.method}") raise InvalidRequestMethod(self.method) def __send_request(self, stream=False): @@ -126,12 +129,14 @@ def __validate_response(self): # Automatically checks if status code is between 200 and 400 if self.response: if self.response.status_code not in self.expected_code: + self.__logger.error(f"The status code from z/OSMF was: {self.expected_code}\nExpected: {self.response.status_code}\nRequest output:{self.response.text}") raise UnexpectedStatus(self.expected_code, self.response.status_code, self.response.text) else: output_str = str(self.response.request.url) output_str += "\n" + str(self.response.request.headers) output_str += "\n" + str(self.response.request.body) output_str += "\n" + str(self.response.text) + self.__logger.error(f"HTTP Request has failed with status code {self.response.status_code}. \n {output_str}") raise RequestFailed(self.response.status_code, output_str) def __normalize_response(self): diff --git a/src/core/zowe/core_for_zowe_sdk/sdk_api.py b/src/core/zowe/core_for_zowe_sdk/sdk_api.py index 146f29aa..65751ce1 100644 --- a/src/core/zowe/core_for_zowe_sdk/sdk_api.py +++ b/src/core/zowe/core_for_zowe_sdk/sdk_api.py @@ -11,6 +11,7 @@ """ import urllib +import logging from . import session_constants from .exceptions import UnsupportedAuthType @@ -28,6 +29,8 @@ def __init__(self, profile, default_url): session = Session(profile) self.session: ISession = session.load() + self.logger = logging.getLogger(__name__) + self.default_service_url = default_url self.default_headers = { "Content-Type": "application/json", @@ -53,6 +56,7 @@ def __init__(self, profile, default_url): elif self.session.type == session_constants.AUTH_TYPE_TOKEN: self.default_headers["Cookie"] = f"{self.session.tokenType}={self.session.tokenValue}" else: + self.logger.error("Unsupported authorization type") raise UnsupportedAuthType(self.session.type) def _create_custom_request_arguments(self): diff --git a/src/core/zowe/core_for_zowe_sdk/session.py b/src/core/zowe/core_for_zowe_sdk/session.py index 54647f38..fe2ac533 100644 --- a/src/core/zowe/core_for_zowe_sdk/session.py +++ b/src/core/zowe/core_for_zowe_sdk/session.py @@ -15,6 +15,7 @@ from . import session_constants +import logging @dataclass class ISession: @@ -42,9 +43,12 @@ class Session: def __init__(self, props: dict) -> None: # set host and port + self.__logger = logging.getLogger(__name__) + if props.get("host") is not None: self.session: ISession = ISession(host=props.get("host")) else: + self.__logger.error("Host not supplied") raise Exception("Host must be supplied") # determine authentication type @@ -61,6 +65,7 @@ def __init__(self, props: dict) -> None: self.session.tokenValue = props.get("tokenValue") self.session.type = session_constants.AUTH_TYPE_BEARER else: + self.__logger.error("Authentication method not supplied") raise Exception("An authentication method must be supplied") # set additional parameters diff --git a/src/core/zowe/core_for_zowe_sdk/zosmf_profile.py b/src/core/zowe/core_for_zowe_sdk/zosmf_profile.py index 5ab3ea41..b877fdea 100644 --- a/src/core/zowe/core_for_zowe_sdk/zosmf_profile.py +++ b/src/core/zowe/core_for_zowe_sdk/zosmf_profile.py @@ -16,6 +16,8 @@ import yaml +import logging + from .connection import ApiConnection from .constants import constants from .exceptions import SecureProfileLoadFailed @@ -53,6 +55,7 @@ def __init__(self, profile_name): The name of the Zowe z/OSMF profile """ self.profile_name = profile_name + self.__logger = logging.getLogger(__name__) @property def profiles_dir(self): @@ -107,12 +110,14 @@ def __get_secure_value(self, name): def __load_secure_credentials(self): """Load secure credentials for a z/OSMF profile.""" if not HAS_KEYRING: + self.__logger.error(f"{self.profile_name} keyring module not installed") raise SecureProfileLoadFailed(self.profile_name, "Keyring module not installed") try: zosmf_user = self.__get_secure_value("user") zosmf_password = self.__get_secure_value("password") except Exception as e: + self.__logger.error(f"Failed to load secure profile '{self.profile_name}' because '{e}'") raise SecureProfileLoadFailed(self.profile_name, e) else: return (zosmf_user, zosmf_password) diff --git a/src/zos_files/zowe/zos_files_for_zowe_sdk/files.py b/src/zos_files/zowe/zos_files_for_zowe_sdk/files.py index efe9d539..f45ad57e 100644 --- a/src/zos_files/zowe/zos_files_for_zowe_sdk/files.py +++ b/src/zos_files/zowe/zos_files_for_zowe_sdk/files.py @@ -10,7 +10,6 @@ Copyright Contributors to the Zowe Project. """ - import os from zowe.core_for_zowe_sdk import SdkApi @@ -232,6 +231,7 @@ def copy_dataset_or_member( if enq in ("SHR", "SHRW", "EXCLU"): data["enq"] = enq else: + self.logger.error("Invalid value for enq.") raise ValueError("Invalid value for enq.") if volser: data["from-dataset"]["volser"] = volser @@ -270,6 +270,7 @@ def create_data_set(self, dataset_name, options={}): if options.get("like") is None: if options.get("primary") is None or options.get("lrecl") is None: + self.logger.error("If 'like' is not specified, you must specify 'primary' or 'lrecl'.") raise ValueError("If 'like' is not specified, you must specify 'primary' or 'lrecl'.") for opt in ( @@ -292,44 +293,51 @@ def create_data_set(self, dataset_name, options={}): ): if opt == "dsorg": if options.get(opt) is not None and options[opt] not in ("PO", "PS"): + self.logger.error(f"{opt} is not 'PO' or 'PS'.") raise KeyError - if opt == "alcunit": + elif opt == "alcunit": if options.get(opt) is None: options[opt] = "TRK" else: if options[opt] not in ("CYL", "TRK"): + self.logger.error(f"{opt} is not 'CYL' or 'TRK'.") raise KeyError - if opt == "primary": + elif opt == "primary": if options.get(opt) is not None: if options["primary"] > 16777215: + self.logger.error("Specified value exceeds limit.") raise ValueError - if opt == "secondary": + elif opt == "secondary": if options.get("primary") is not None: if options.get(opt) is None: options["secondary"] = int(options["primary"] / 10) if options["secondary"] > 16777215: + self.logger.error("Specified value exceeds limit.") raise ValueError - if opt == "dirblk": + elif opt == "dirblk": if options.get(opt) is not None: if options.get("dsorg") == "PS": if options["dirblk"] != 0: + self.logger.error("Can't allocate directory blocks for files.") raise ValueError elif options.get("dsorg") == "PO": if options["dirblk"] == 0: + self.logger.error("Can't allocate empty directory blocks.") raise ValueError - if opt == "recfm": + elif opt == "recfm": if options.get(opt) is None: options[opt] = "F" else: if options[opt] not in ("F", "FB", "V", "VB", "U", "FBA", "FBM", "VBA", "VBM"): + self.logger.error("Invalid record format.") raise KeyError - if opt == "blksize": + elif opt == "blksize": if options.get(opt) is None and options.get("lrecl") is not None: options[opt] = options["lrecl"] @@ -356,6 +364,7 @@ def create_default_data_set(self, dataset_name: str, default_type: str): """ if default_type not in ("partitioned", "sequential", "classic", "c", "binary"): + self.logger.error("Invalid type for default data set.") raise ValueError("Invalid type for default data set.") custom_args = self._create_custom_request_arguments() @@ -537,6 +546,7 @@ def upload_file_to_dsn(self, input_file, dataset_name, encoding=_ZOWE_FILES_DEFA with open(input_file, "rb") as in_file: response_json = self.write_to_dsn(dataset_name, in_file) else: + self.logger.error(f"File {input_file} not found.") raise FileNotFound(input_file) def write_to_uss(self, filepath_name, data, encoding=_ZOWE_FILES_DEFAULT_ENCODING): @@ -559,6 +569,7 @@ def upload_file_to_uss(self, input_file, filepath_name, encoding=_ZOWE_FILES_DEF with open(input_file, "r", encoding="utf-8") as in_file: response_json = self.write_to_uss(filepath_name, in_file) else: + self.logger.error(f"File {input_file} not found.") raise FileNotFound(input_file) def get_file_content_streamed(self, file_path, binary=False): @@ -610,10 +621,12 @@ def create_zFS_file_system(self, file_system_name, options={}): for key, value in options.items(): if key == "perms": if value < 0 or value > 777: + self.logger.error("Invalid Permissions Option.") raise exceptions.InvalidPermsOption(value) if key == "cylsPri" or key == "cylsSec": if value > constants.zos_file_constants["MaxAllocationQuantity"]: + self.logger.error("Maximum allocation quantity exceeded.") raise exceptions.MaxAllocationQuantityExceeded custom_args = self._create_custom_request_arguments() @@ -843,6 +856,7 @@ def rename_dataset_member(self, dataset_name: str, before_member_name: str, afte if enq in ("SHRW", "EXCLU"): data["enq"] = enq.strip() else: + self.logger.error("Invalid value for enq.") raise ValueError("Invalid value for enq.") custom_args = self._create_custom_request_arguments() diff --git a/src/zos_jobs/zowe/zos_jobs_for_zowe_sdk/jobs.py b/src/zos_jobs/zowe/zos_jobs_for_zowe_sdk/jobs.py index b9e13fca..ae4319e1 100644 --- a/src/zos_jobs/zowe/zos_jobs_for_zowe_sdk/jobs.py +++ b/src/zos_jobs/zowe/zos_jobs_for_zowe_sdk/jobs.py @@ -9,6 +9,7 @@ Copyright Contributors to the Zowe Project. """ + import os from zowe.core_for_zowe_sdk import SdkApi @@ -75,6 +76,7 @@ def cancel_job(self, jobname: str, jobid: str, modify_version="2.0"): A JSON object containing the result of the request execution """ if modify_version not in ("1.0", "2.0"): + self.logger.error('Modify version not accepted; Must be "1.0" or "2.0"') raise ValueError('Accepted values for modify_version: "1.0" or "2.0"') custom_args = self._create_custom_request_arguments() @@ -104,6 +106,7 @@ def delete_job(self, jobname, jobid, modify_version="2.0"): A JSON object containing the result of the request execution """ if modify_version not in ("1.0", "2.0"): + self.logger.error('Modify version not accepted; Must be "1.0" or "2.0"') raise ValueError('Accepted values for modify_version: "1.0" or "2.0"') custom_args = self._create_custom_request_arguments() @@ -145,6 +148,7 @@ def change_job_class(self, jobname: str, jobid: str, class_name: str, modify_ver A JSON object containing the result of the request execution """ if modify_version not in ("1.0", "2.0"): + self.logger.error('Accepted values for modify_version: "1.0" or "2.0"') raise ValueError('Accepted values for modify_version: "1.0" or "2.0"') response_json = self._issue_job_request({"class": class_name}, jobname, jobid, modify_version) @@ -168,6 +172,7 @@ def hold_job(self, jobname: str, jobid: str, modify_version="2.0"): A JSON object containing the result of the request execution """ if modify_version not in ("1.0", "2.0"): + self.logger.error('Accepted values for modify_version: "1.0" or "2.0"') raise ValueError('Accepted values for modify_version: "1.0" or "2.0"') response_json = self._issue_job_request({"request": "hold"}, jobname, jobid, modify_version) @@ -191,6 +196,7 @@ def release_job(self, jobname: str, jobid: str, modify_version="2.0"): A JSON object containing the result of the request execution """ if modify_version not in ("1.0", "2.0"): + self.logger.error('Modify version not accepted; Must be "1.0" or "2.0"') raise ValueError('Accepted values for modify_version: "1.0" or "2.0"') response_json = self._issue_job_request({"request": "release"}, jobname, jobid, modify_version) @@ -271,6 +277,7 @@ def submit_from_local_file(self, jcl_path): file_content = jcl_file.read() return self.submit_plaintext(file_content) else: + self.logger.error("Provided argument is not a file path {}".format(jcl_path)) raise FileNotFoundError("Provided argument is not a file path {}".format(jcl_path)) def submit_plaintext(self, jcl): From 4d1fa813daf2c56ed746ac365283b6818a74a35e Mon Sep 17 00:00:00 2001 From: pem70 Date: Thu, 23 May 2024 13:35:47 -0400 Subject: [PATCH 02/22] modified logger logic and stream request Signed-off-by: pem70 --- .../zowe/core_for_zowe_sdk/profile_manager.py | 5 +++ .../zowe/core_for_zowe_sdk/request_handler.py | 35 ++++--------------- src/core/zowe/core_for_zowe_sdk/sdk_api.py | 6 ++-- .../zowe/zos_console_for_zowe_sdk/console.py | 2 +- .../zowe/zos_files_for_zowe_sdk/files.py | 8 ++--- .../zowe/zos_jobs_for_zowe_sdk/jobs.py | 2 +- src/zos_tso/zowe/zos_tso_for_zowe_sdk/tso.py | 2 +- src/zosmf/zowe/zosmf_for_zowe_sdk/zosmf.py | 2 +- tests/unit/test_zowe_core.py | 2 +- 9 files changed, 24 insertions(+), 40 deletions(-) diff --git a/src/core/zowe/core_for_zowe_sdk/profile_manager.py b/src/core/zowe/core_for_zowe_sdk/profile_manager.py index 20a34a64..76daa13f 100644 --- a/src/core/zowe/core_for_zowe_sdk/profile_manager.py +++ b/src/core/zowe/core_for_zowe_sdk/profile_manager.py @@ -177,6 +177,8 @@ def get_profile( NamedTuple (data, name, secure_props_not_found) """ + logger = logging.getLogger(__name__) + cfg_profile = Profile() try: cfg_profile = cfg.get_profile( @@ -198,17 +200,20 @@ def get_profile( raise jsonschema.exceptions.FormatError(f"Validating a format config_json failed for schema_json, {exc}") except ProfileNotFound: if profile_name: + logger.warning(f"Profile '{profile_name}' not found in file '{cfg.filename}'") warnings.warn( f"Profile '{profile_name}' not found in file '{cfg.filename}', returning empty profile instead.", ProfileNotFoundWarning, ) else: + logger.warning(f"Profile of type '{profile_type}' not found in file '{cfg.filename}'") warnings.warn( f"Profile of type '{profile_type}' not found in file '{cfg.filename}', returning empty profile" f" instead.", ProfileNotFoundWarning, ) except Exception as exc: + logger.warning(f"Could not load '{cfg.filename}' at '{cfg.filepath}'" f"because {type(exc).__name__}'{exc}'") warnings.warn( f"Could not load '{cfg.filename}' at '{cfg.filepath}'" f"because {type(exc).__name__}'{exc}'.", ConfigNotFoundWarning, diff --git a/src/core/zowe/core_for_zowe_sdk/request_handler.py b/src/core/zowe/core_for_zowe_sdk/request_handler.py index 7f3ac1c6..395bbe6e 100644 --- a/src/core/zowe/core_for_zowe_sdk/request_handler.py +++ b/src/core/zowe/core_for_zowe_sdk/request_handler.py @@ -29,7 +29,7 @@ class RequestHandler: List of supported request methods """ - def __init__(self, session_arguments): + def __init__(self, session_arguments, logger_name = __name__): """ Construct a RequestHandler object. @@ -41,14 +41,14 @@ def __init__(self, session_arguments): self.session_arguments = session_arguments self.valid_methods = ["GET", "POST", "PUT", "DELETE"] self.__handle_ssl_warnings() - self.__logger = logging.getLogger(__name__) + self.__logger = logging.getLogger(logger_name) def __handle_ssl_warnings(self): """Turn off warnings if the SSL verification argument if off.""" if not self.session_arguments["verify"]: urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) - def perform_request(self, method, request_arguments, expected_code=[200]): + def perform_request(self, method, request_arguments, expected_code=[200], stream = False): """Execute an HTTP/HTTPS requests from given arguments and return validated response (JSON). Parameters @@ -68,35 +68,14 @@ def perform_request(self, method, request_arguments, expected_code=[200]): self.method = method self.request_arguments = request_arguments self.expected_code = expected_code + self.__logger.debug(f"Request method: {self.method}, Request arguments: {self.request_arguments}, Expected code: {expected_code}") self.__validate_method() - self.__send_request() + self.__send_request(stream = stream) self.__validate_response() + if stream: + return self.response return self.__normalize_response() - def perform_streamed_request(self, method, request_arguments, expected_code=[200]): - """Execute a streamed HTTP/HTTPS requests from given arguments and return a raw response. - - Parameters - ---------- - method: str - The request method that should be used - request_arguments: dict - The dictionary containing the required arguments for the execution of the request - expected_code: int - The list containing the acceptable response codes (default is [200]) - - Returns - ------- - A raw response data - """ - self.method = method - self.request_arguments = request_arguments - self.expected_code = expected_code - self.__validate_method() - self.__send_request(stream=True) - self.__validate_response() - return self.response - def __validate_method(self): """Check if the input request method for the request is supported. diff --git a/src/core/zowe/core_for_zowe_sdk/sdk_api.py b/src/core/zowe/core_for_zowe_sdk/sdk_api.py index 65751ce1..edf24c11 100644 --- a/src/core/zowe/core_for_zowe_sdk/sdk_api.py +++ b/src/core/zowe/core_for_zowe_sdk/sdk_api.py @@ -24,12 +24,12 @@ class SdkApi: Abstract class used to represent the base SDK API. """ - def __init__(self, profile, default_url): + def __init__(self, profile, default_url, logger_name = __name__): self.profile = profile session = Session(profile) self.session: ISession = session.load() - self.logger = logging.getLogger(__name__) + self.logger = logging.getLogger(logger_name) self.default_service_url = default_url self.default_headers = { @@ -47,7 +47,7 @@ def __init__(self, profile, default_url): "verify": self.session.rejectUnauthorized, "timeout": 30, } - self.request_handler = RequestHandler(self.session_arguments) + self.request_handler = RequestHandler(self.session_arguments, logger_name = logger_name) if self.session.type == session_constants.AUTH_TYPE_BASIC: self.request_arguments["auth"] = (self.session.user, self.session.password) diff --git a/src/zos_console/zowe/zos_console_for_zowe_sdk/console.py b/src/zos_console/zowe/zos_console_for_zowe_sdk/console.py index 59817c86..42e5a3d8 100644 --- a/src/zos_console/zowe/zos_console_for_zowe_sdk/console.py +++ b/src/zos_console/zowe/zos_console_for_zowe_sdk/console.py @@ -22,7 +22,7 @@ def __init__(self, connection): connection The connection object """ - super().__init__(connection, "/zosmf/restconsoles/consoles/defcn") + super().__init__(connection, "/zosmf/restconsoles/consoles/defcn", logger_name=__name__) def issue_command(self, command, console=None): """Issues a command on z/OS Console. diff --git a/src/zos_files/zowe/zos_files_for_zowe_sdk/files.py b/src/zos_files/zowe/zos_files_for_zowe_sdk/files.py index f45ad57e..688c24e8 100644 --- a/src/zos_files/zowe/zos_files_for_zowe_sdk/files.py +++ b/src/zos_files/zowe/zos_files_for_zowe_sdk/files.py @@ -43,7 +43,7 @@ def __init__(self, connection): Also update header to accept gzip encoded responses """ - super().__init__(connection, "/zosmf/restfiles/") + super().__init__(connection, "/zosmf/restfiles/", logger_name=__name__) self.default_headers["Accept-Encoding"] = "gzip" def list_files(self, path): @@ -452,7 +452,7 @@ def get_dsn_content_streamed(self, dataset_name): """ custom_args = self._create_custom_request_arguments() custom_args["url"] = "{}ds/{}".format(self.request_endpoint, self._encode_uri_component(dataset_name)) - response = self.request_handler.perform_streamed_request("GET", custom_args) + response = self.request_handler.perform_request("GET", custom_args, stream = True) return response def get_dsn_binary_content(self, dataset_name, with_prefixes=False): @@ -500,7 +500,7 @@ def get_dsn_binary_content_streamed(self, dataset_name, with_prefixes=False): custom_args["headers"]["X-IBM-Data-Type"] = "record" else: custom_args["headers"]["X-IBM-Data-Type"] = "binary" - response = self.request_handler.perform_streamed_request("GET", custom_args) + response = self.request_handler.perform_request("GET", custom_args, stream = True) return response def write_to_dsn(self, dataset_name, data, encoding=_ZOWE_FILES_DEFAULT_ENCODING): @@ -584,7 +584,7 @@ def get_file_content_streamed(self, file_path, binary=False): custom_args["url"] = "{}fs/{}".format(self.request_endpoint, self._encode_uri_component(file_path.lstrip("/"))) if binary: custom_args["headers"]["X-IBM-Data-Type"] = "binary" - response = self.request_handler.perform_streamed_request("GET", custom_args) + response = self.request_handler.perform_request("GET", custom_args, stream=True) return response def download_uss(self, file_path, output_file, binary=False): diff --git a/src/zos_jobs/zowe/zos_jobs_for_zowe_sdk/jobs.py b/src/zos_jobs/zowe/zos_jobs_for_zowe_sdk/jobs.py index ae4319e1..bcceb8d1 100644 --- a/src/zos_jobs/zowe/zos_jobs_for_zowe_sdk/jobs.py +++ b/src/zos_jobs/zowe/zos_jobs_for_zowe_sdk/jobs.py @@ -34,7 +34,7 @@ def __init__(self, connection): connection The connection object """ - super().__init__(connection, "/zosmf/restjobs/jobs/") + super().__init__(connection, "/zosmf/restjobs/jobs/", logger_name=__name__) def get_job_status(self, jobname, jobid): """Retrieve the status of a given job on JES. diff --git a/src/zos_tso/zowe/zos_tso_for_zowe_sdk/tso.py b/src/zos_tso/zowe/zos_tso_for_zowe_sdk/tso.py index 2a882f3f..53e273c8 100644 --- a/src/zos_tso/zowe/zos_tso_for_zowe_sdk/tso.py +++ b/src/zos_tso/zowe/zos_tso_for_zowe_sdk/tso.py @@ -38,7 +38,7 @@ def __init__(self, connection, tso_profile=None): connection The connection object """ - super().__init__(connection, "/zosmf/tsoApp/tso") + super().__init__(connection, "/zosmf/tsoApp/tso", logger_name=__name__) self.session_not_found = constants["TsoSessionNotFound"] self.tso_profile = tso_profile or {} diff --git a/src/zosmf/zowe/zosmf_for_zowe_sdk/zosmf.py b/src/zosmf/zowe/zosmf_for_zowe_sdk/zosmf.py index 22e4fa65..577e3fd3 100644 --- a/src/zosmf/zowe/zosmf_for_zowe_sdk/zosmf.py +++ b/src/zosmf/zowe/zosmf_for_zowe_sdk/zosmf.py @@ -31,7 +31,7 @@ def __init__(self, connection): connection The z/OSMF connection object (generated by the ZoweSDK object) """ - super().__init__(connection, "/zosmf/info") + super().__init__(connection, "/zosmf/info", logger_name=__name__) def get_info(self): """Return a JSON response from the GET request to z/OSMF info endpoint. diff --git a/tests/unit/test_zowe_core.py b/tests/unit/test_zowe_core.py index 7b1d1188..2c31da64 100644 --- a/tests/unit/test_zowe_core.py +++ b/tests/unit/test_zowe_core.py @@ -155,7 +155,7 @@ def test_perform_streamed_request(self, mock_send_request): """Performing a streamed request should call 'send_request' method""" mock_send_request.return_value = mock.Mock(status_code=200) request_handler = RequestHandler(self.session_arguments) - request_handler.perform_streamed_request("GET", {"url": "https://www.zowe.org"}) + request_handler.perform_request("GET", {"url": "https://www.zowe.org"}, stream = True) mock_send_request.assert_called_once() self.assertTrue(mock_send_request.call_args[1]["stream"]) From 43513105670f901cefda7a877ccddd7d798e0cf6 Mon Sep 17 00:00:00 2001 From: pem70 Date: Thu, 23 May 2024 14:20:14 -0400 Subject: [PATCH 03/22] Add log class and set logger level Signed-off-by: pem70 --- src/core/zowe/core_for_zowe_sdk/__init__.py | 16 +------------- .../core_for_zowe_sdk/credential_manager.py | 2 +- src/core/zowe/core_for_zowe_sdk/log.py | 21 +++++++++++++++++++ 3 files changed, 23 insertions(+), 16 deletions(-) create mode 100644 src/core/zowe/core_for_zowe_sdk/log.py diff --git a/src/core/zowe/core_for_zowe_sdk/__init__.py b/src/core/zowe/core_for_zowe_sdk/__init__.py index 58fc34a0..153ea8cc 100644 --- a/src/core/zowe/core_for_zowe_sdk/__init__.py +++ b/src/core/zowe/core_for_zowe_sdk/__init__.py @@ -13,18 +13,4 @@ from .session import Session from .session_constants import * from .zosmf_profile import ZosmfProfile - -import logging -import os - -dirname = os.path.join(os.path.expanduser("~"), ".zowe/logs") - -if not os.path.isdir(dirname): - os.makedirs(dirname) - -logging.basicConfig( - filename=os.path.join(dirname, "python_sdk_logs.log"), - level=logging.INFO, - format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", - datefmt="%m/%d/%Y %I:%M:%S %p", -) +from .log import log \ No newline at end of file diff --git a/src/core/zowe/core_for_zowe_sdk/credential_manager.py b/src/core/zowe/core_for_zowe_sdk/credential_manager.py index b640d9b1..fac1f631 100644 --- a/src/core/zowe/core_for_zowe_sdk/credential_manager.py +++ b/src/core/zowe/core_for_zowe_sdk/credential_manager.py @@ -53,7 +53,7 @@ def load_secure_props() -> None: return except Exception as exc: - CredentialManager.__logger.error(f"Fail to load secure profile {constants["ZoweServiceName"]}") + CredentialManager.__logger.error(f"Fail to load secure profile {constants['ZoweServiceName']}") raise SecureProfileLoadFailed(constants["ZoweServiceName"], error_msg=str(exc)) from exc secure_config: str diff --git a/src/core/zowe/core_for_zowe_sdk/log.py b/src/core/zowe/core_for_zowe_sdk/log.py new file mode 100644 index 00000000..d35a93eb --- /dev/null +++ b/src/core/zowe/core_for_zowe_sdk/log.py @@ -0,0 +1,21 @@ +import logging +import os + +class log: + """root logger setup and a funtion to customize logger level""" + + dirname = os.path.join(os.path.expanduser("~"), ".zowe/logs") + + if not os.path.isdir(dirname): + os.makedirs(dirname) + + logging.basicConfig( + filename=os.path.join(dirname, "python_sdk_logs.log"), + level=logging.INFO, + format="[%(asctime)s] [%(levelname)s] [%(name)s] - %(message)s", + datefmt="%m/%d/%Y %I:%M:%S %p", + ) + + @staticmethod + def setLoggerLevel(level: int): + logging.getLogger().setLevel(level) \ No newline at end of file From c1f9f1e7cd9f5175f72595dad215393b5dd3f324 Mon Sep 17 00:00:00 2001 From: pem70 Date: Fri, 24 May 2024 09:39:37 -0400 Subject: [PATCH 04/22] Basic unittesting Signed-off-by: pem70 --- .vscode/settings.json | 3 ++- src/core/zowe/core_for_zowe_sdk/__init__.py | 2 +- .../core_for_zowe_sdk/{log.py => logger.py} | 4 +-- .../zowe/core_for_zowe_sdk/request_handler.py | 5 ++++ tests/unit/test_zowe_core.py | 26 ++++++++++++++++++- 5 files changed, 35 insertions(+), 5 deletions(-) rename src/core/zowe/core_for_zowe_sdk/{log.py => logger.py} (90%) diff --git a/.vscode/settings.json b/.vscode/settings.json index 5079d79f..b378a106 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -16,6 +16,7 @@ "./src/core", "./src/zos_console", "./src/zos_files", - "./src/zos_jobs" + "./src/zos_jobs", + "./src/zosmf" ], } diff --git a/src/core/zowe/core_for_zowe_sdk/__init__.py b/src/core/zowe/core_for_zowe_sdk/__init__.py index 153ea8cc..55bf9068 100644 --- a/src/core/zowe/core_for_zowe_sdk/__init__.py +++ b/src/core/zowe/core_for_zowe_sdk/__init__.py @@ -13,4 +13,4 @@ from .session import Session from .session_constants import * from .zosmf_profile import ZosmfProfile -from .log import log \ No newline at end of file +from .logger import Log \ No newline at end of file diff --git a/src/core/zowe/core_for_zowe_sdk/log.py b/src/core/zowe/core_for_zowe_sdk/logger.py similarity index 90% rename from src/core/zowe/core_for_zowe_sdk/log.py rename to src/core/zowe/core_for_zowe_sdk/logger.py index d35a93eb..f2f96677 100644 --- a/src/core/zowe/core_for_zowe_sdk/log.py +++ b/src/core/zowe/core_for_zowe_sdk/logger.py @@ -1,7 +1,7 @@ import logging import os -class log: +class Log: """root logger setup and a funtion to customize logger level""" dirname = os.path.join(os.path.expanduser("~"), ".zowe/logs") @@ -18,4 +18,4 @@ class log: @staticmethod def setLoggerLevel(level: int): - logging.getLogger().setLevel(level) \ No newline at end of file + logging.root.setLevel(level) \ No newline at end of file diff --git a/src/core/zowe/core_for_zowe_sdk/request_handler.py b/src/core/zowe/core_for_zowe_sdk/request_handler.py index 395bbe6e..73966654 100644 --- a/src/core/zowe/core_for_zowe_sdk/request_handler.py +++ b/src/core/zowe/core_for_zowe_sdk/request_handler.py @@ -37,6 +37,9 @@ def __init__(self, session_arguments, logger_name = __name__): ---------- session_arguments The Zowe SDK session arguments + + logger_name + The logger name of the modules calling request handler """ self.session_arguments = session_arguments self.valid_methods = ["GET", "POST", "PUT", "DELETE"] @@ -59,6 +62,8 @@ def perform_request(self, method, request_arguments, expected_code=[200], stream The dictionary containing the required arguments for the execution of the request expected_code: int The list containing the acceptable response codes (default is [200]) + stream: boolean + The boolean value whether the request is stream Returns ------- diff --git a/tests/unit/test_zowe_core.py b/tests/unit/test_zowe_core.py index 2c31da64..56dd0108 100644 --- a/tests/unit/test_zowe_core.py +++ b/tests/unit/test_zowe_core.py @@ -8,6 +8,7 @@ import shutil import unittest from unittest import mock +import logging import commentjson from jsonschema import SchemaError, ValidationError, validate @@ -24,6 +25,7 @@ custom_warnings, exceptions, session_constants, + logger ) from zowe.core_for_zowe_sdk.validators import validate_config_json from zowe.secrets_for_zowe_sdk import keyring @@ -150,12 +152,25 @@ def test_object_should_be_instance_of_class(self): request_handler = RequestHandler(self.session_arguments) self.assertIsInstance(request_handler, RequestHandler) + @mock.patch("logging.Logger.debug") + @mock.patch("logging.Logger.error") @mock.patch("requests.Session.send") - def test_perform_streamed_request(self, mock_send_request): + def test_perform_streamed_request(self, mock_send_request, mock_logger_error: mock.MagicMock, mock_logger_debug: mock.MagicMock): """Performing a streamed request should call 'send_request' method""" mock_send_request.return_value = mock.Mock(status_code=200) request_handler = RequestHandler(self.session_arguments) request_handler.perform_request("GET", {"url": "https://www.zowe.org"}, stream = True) + + mock_logger_error.assert_not_called() + mock_logger_debug.assert_called() + + # + # # This will fail because parameter also contains the arguments of perform_request + # mock_logger_debug.assert_called_with("Request method: GET") + # + # But this will pass because we are partially looking for the request method in the first argument + self.assertIn("Request method: GET", mock_logger_debug.call_args[0][0]) + mock_send_request.assert_called_once() self.assertTrue(mock_send_request.call_args[1]["stream"]) @@ -949,3 +964,12 @@ def test_validate_config_json_invalid(self): validate_config_json(path_to_invalid_config, path_to_invalid_schema, cwd=FIXTURES_PATH) self.assertEqual(str(actual_info.exception), str(expected_info.exception)) + + +class test_logger_setLoggerLevel(TestCase): + + def test_logger_setLoggerLevel(self): + test_logging = logger.Log() + test_value = logging.INFO + test_logging.setLoggerLevel(test_value) + self.assertEqual(logging.root.level, test_value) \ No newline at end of file From f7d261ed02c20f3f3e9f0bd6e79885a9ed0e305d Mon Sep 17 00:00:00 2001 From: pem70 Date: Fri, 24 May 2024 10:20:33 -0400 Subject: [PATCH 05/22] update unittest Signed-off-by: pem70 --- tests/unit/test_zowe_core.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/unit/test_zowe_core.py b/tests/unit/test_zowe_core.py index 56dd0108..404fcc1e 100644 --- a/tests/unit/test_zowe_core.py +++ b/tests/unit/test_zowe_core.py @@ -95,6 +95,14 @@ def test_object_should_be_instance_of_class(self): sdk_api = SdkApi(self.basic_props, self.default_url) self.assertIsInstance(sdk_api, SdkApi) + @mock.patch("logging.Logger.error") + def test_session_no_host_logger(self, mock_logger_error: mock.MagicMock): + props = {} + try: + sdk_api = SdkApi(props, self.default_url) + except Exception: + mock_logger_error.assert_called() + def test_should_handle_basic_auth(self): """Created object should handle basic authentication.""" sdk_api = SdkApi(self.basic_props, self.default_url) From c7060a73952120dfd65c169de6711b19fd7d50d5 Mon Sep 17 00:00:00 2001 From: pem70 Date: Fri, 24 May 2024 14:51:05 -0400 Subject: [PATCH 06/22] Add more unittests on logger Signed-off-by: pem70 --- .../zowe/core_for_zowe_sdk/profile_manager.py | 5 ++ .../zowe/core_for_zowe_sdk/request_handler.py | 2 +- src/core/zowe/core_for_zowe_sdk/sdk_api.py | 3 - tests/integration/fixtures/sample.jcl.tmp | 5 ++ tests/unit/test_zowe_core.py | 55 +++++++++++++++++-- 5 files changed, 62 insertions(+), 8 deletions(-) create mode 100644 tests/integration/fixtures/sample.jcl.tmp diff --git a/src/core/zowe/core_for_zowe_sdk/profile_manager.py b/src/core/zowe/core_for_zowe_sdk/profile_manager.py index 76daa13f..6e9ed111 100644 --- a/src/core/zowe/core_for_zowe_sdk/profile_manager.py +++ b/src/core/zowe/core_for_zowe_sdk/profile_manager.py @@ -185,18 +185,23 @@ def get_profile( profile_name=profile_name, profile_type=profile_type, validate_schema=validate_schema ) except jsonschema.exceptions.ValidationError as exc: + logger.error(f"Instance was invalid under the provided $schema property, {exc}") raise jsonschema.exceptions.ValidationError( f"Instance was invalid under the provided $schema property, {exc}" ) except jsonschema.exceptions.SchemaError as exc: + logger.error(f"The provided schema is invalid, {exc}") raise jsonschema.exceptions.SchemaError(f"The provided schema is invalid, {exc}") except jsonschema.exceptions.UndefinedTypeCheck as exc: + logger.error(f"A type checker was asked to check a type it did not have registered, {exc}") raise jsonschema.exceptions.UndefinedTypeCheck( f"A type checker was asked to check a type it did not have registered, {exc}" ) except jsonschema.exceptions.UnknownType as exc: + logger.error(f"Unknown type is found in schema_json, exc") raise jsonschema.exceptions.UnknownType(f"Unknown type is found in schema_json, exc") except jsonschema.exceptions.FormatError as exc: + logger.error(f"Validating a format config_json failed for schema_json, {exc}") raise jsonschema.exceptions.FormatError(f"Validating a format config_json failed for schema_json, {exc}") except ProfileNotFound: if profile_name: diff --git a/src/core/zowe/core_for_zowe_sdk/request_handler.py b/src/core/zowe/core_for_zowe_sdk/request_handler.py index 73966654..47455286 100644 --- a/src/core/zowe/core_for_zowe_sdk/request_handler.py +++ b/src/core/zowe/core_for_zowe_sdk/request_handler.py @@ -111,7 +111,7 @@ def __validate_response(self): If the HTTP/HTTPS request fails """ # Automatically checks if status code is between 200 and 400 - if self.response: + if self.response.ok: if self.response.status_code not in self.expected_code: self.__logger.error(f"The status code from z/OSMF was: {self.expected_code}\nExpected: {self.response.status_code}\nRequest output:{self.response.text}") raise UnexpectedStatus(self.expected_code, self.response.status_code, self.response.text) diff --git a/src/core/zowe/core_for_zowe_sdk/sdk_api.py b/src/core/zowe/core_for_zowe_sdk/sdk_api.py index edf24c11..acb83ac3 100644 --- a/src/core/zowe/core_for_zowe_sdk/sdk_api.py +++ b/src/core/zowe/core_for_zowe_sdk/sdk_api.py @@ -55,9 +55,6 @@ def __init__(self, profile, default_url, logger_name = __name__): self.default_headers["Authorization"] = f"Bearer {self.session.tokenValue}" elif self.session.type == session_constants.AUTH_TYPE_TOKEN: self.default_headers["Cookie"] = f"{self.session.tokenType}={self.session.tokenValue}" - else: - self.logger.error("Unsupported authorization type") - raise UnsupportedAuthType(self.session.type) def _create_custom_request_arguments(self): """Create a copy of the default request arguments dictionary. diff --git a/tests/integration/fixtures/sample.jcl.tmp b/tests/integration/fixtures/sample.jcl.tmp new file mode 100644 index 00000000..39020987 --- /dev/null +++ b/tests/integration/fixtures/sample.jcl.tmp @@ -0,0 +1,5 @@ +//IEFBR14T JOB (AUTOMATION),CLASS=A,MSGCLASS=0, + +// MSGLEVEL=(1,1),REGION=0M,NOTIFY=&SYSUID + +//STEP1 EXEC PGM=IEFBR14 diff --git a/tests/unit/test_zowe_core.py b/tests/unit/test_zowe_core.py index 404fcc1e..9b8b0174 100644 --- a/tests/unit/test_zowe_core.py +++ b/tests/unit/test_zowe_core.py @@ -102,6 +102,16 @@ def test_session_no_host_logger(self, mock_logger_error: mock.MagicMock): sdk_api = SdkApi(props, self.default_url) except Exception: mock_logger_error.assert_called() + self.assertIn("Host", mock_logger_error.call_args[0][0]) + + @mock.patch("logging.Logger.error") + def test_session_no_authentication_logger(self, mock_logger_error: mock.MagicMock): + props = {"host": "test"} + try: + sdk_api = SdkApi(props, self.default_url) + except Exception: + mock_logger_error.assert_called() + self.assertIn("Authentication", mock_logger_error.call_args[0][0]) def test_should_handle_basic_auth(self): """Created object should handle basic authentication.""" @@ -183,6 +193,37 @@ def test_perform_streamed_request(self, mock_send_request, mock_logger_error: mo self.assertTrue(mock_send_request.call_args[1]["stream"]) + @mock.patch("logging.Logger.error") + def test_logger_unmatched_status_code(self, mock_logger_error: mock.MagicMock): + request_handler = RequestHandler(self.session_arguments) + try: + request_handler.perform_request("GET", {"url": "https://www.zowe.org"}, expected_code= [0], stream = True) + except exceptions.UnexpectedStatus: + mock_logger_error.assert_called_once() + self.assertIn("The status code", mock_logger_error.call_args[0][0]) + + @mock.patch("logging.Logger.error") + def test_logger_perform_request_invalid_method(self, mock_logger_error: mock.MagicMock): + request_handler = RequestHandler(self.session_arguments) + try: + request_handler.perform_request("Invalid method", {"url": "https://www.zowe.org"}, stream = True) + except exceptions.InvalidRequestMethod: + mock_logger_error.assert_called_once() + self.assertIn("Invalid HTTP method input", mock_logger_error.call_args[0][0]) + + @mock.patch("logging.Logger.error") + @mock.patch("requests.Session.send") + def test_logger_invalid_status_code(self, mock_send_request, mock_logger_error: mock.MagicMock): + mock_send_request.return_value = mock.Mock(ok=False) + request_handler = RequestHandler(self.session_arguments) + try: + request_handler.perform_request("GET", {"url": "https://www.zowe.org"}, stream = True) + except exceptions.RequestFailed: + mock_logger_error.assert_called_once() + self.assertIn("HTTP Request has failed", mock_logger_error.call_args[0][0]) + mock_logger_error.assert_called_once + + class TestZosmfProfileClass(unittest.TestCase): """ZosmfProfile class unit tests.""" @@ -377,8 +418,9 @@ def test_profile_loading_with_user_overridden_properties(self, get_pass_func): } self.assertEqual(props, expected_props) + @mock.patch("logging.Logger.warning") @mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=keyring_get_password) - def test_profile_loading_exception(self, get_pass_func): + def test_profile_loading_exception(self, get_pass_func, mock_logger_warning: mock.MagicMock): """ Test correct exceptions are being thrown when a profile is not found. @@ -394,6 +436,7 @@ def test_profile_loading_exception(self, get_pass_func): # Test config_file = ConfigFile(name=self.custom_appname, type="team_config") props: dict = config_file.get_profile(profile_name="non_existent_profile", validate_schema=False) + mock_logger_warning.assert_called() @mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=keyring_get_password_exception) def test_secure_props_loading_warning(self, get_pass_func): @@ -614,8 +657,9 @@ def test_profile_loading_with_valid_schema(self, get_pass_func): prof_manager.config_dir = self.custom_dir props: dict = prof_manager.load(profile_name="zosmf") + @mock.patch("logging.Logger.error") @mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=keyring_get_password) - def test_profile_loading_with_invalid_schema(self, get_pass_func): + def test_profile_loading_with_invalid_schema(self, get_pass_func, mock_logger_error: mock.MagicMock): """ Test Validation, no error should be raised for valid schema """ @@ -639,14 +683,16 @@ def test_profile_loading_with_invalid_schema(self, get_pass_func): "profiles.zosmf.properties.password": "password", }, ) - + # Test prof_manager = ProfileManager(appname="invalid.zowe") prof_manager.config_dir = self.custom_dir props: dict = prof_manager.load(profile_name="zosmf", validate_schema=True) + self.assertIn("Instance was invalid under the provided $schema property", mock_logger_error.call_args[0][0]) + @mock.patch("logging.Logger.error") @mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=keyring_get_password) - def test_profile_loading_with_invalid_schema_internet_URI(self, get_pass_func): + def test_profile_loading_with_invalid_schema_internet_URI(self, get_pass_func, mock_logger_error: mock.MagicMock): """ Test Validation, no error should be raised for valid schema """ @@ -676,6 +722,7 @@ def test_profile_loading_with_invalid_schema_internet_URI(self, get_pass_func): prof_manager = ProfileManager(appname="invalidUri.zowe") prof_manager.config_dir = self.custom_dir props: dict = prof_manager.load(profile_name="zosmf", validate_schema=True) + self.assertIn("The provided schema is invalid", mock_logger_error.call_args[0][0]) @mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=keyring_get_password) def test_profile_loading_with_env_variables(self, get_pass_func): From c99b0b06df11174b0994826109e44fde262fa2ac Mon Sep 17 00:00:00 2001 From: pem70 Date: Tue, 28 May 2024 10:40:28 -0400 Subject: [PATCH 07/22] unit test on config_file.py Signed-off-by: pem70 --- .../zowe/core_for_zowe_sdk/config_file.py | 22 +++----- tests/unit/test_zowe_core.py | 53 ++++++++++++++++++- 2 files changed, 58 insertions(+), 17 deletions(-) diff --git a/src/core/zowe/core_for_zowe_sdk/config_file.py b/src/core/zowe/core_for_zowe_sdk/config_file.py index d873628c..60cab57e 100644 --- a/src/core/zowe/core_for_zowe_sdk/config_file.py +++ b/src/core/zowe/core_for_zowe_sdk/config_file.py @@ -96,10 +96,6 @@ def filepath(self) -> Optional[str]: def location(self) -> Optional[str]: return self._location - @property - def schema_path(self) -> Optional[str]: - return self.schema_property - @location.setter def location(self, dirname: str) -> None: if os.path.isdir(dirname): @@ -137,7 +133,7 @@ def init_from_file( self.defaults = profile_jsonc.get("defaults", {}) self.jsonc = profile_jsonc - if self.schema_property and validate_schema: + if validate_schema: self.validate_schema() CredentialManager.load_secure_props() @@ -151,17 +147,11 @@ def validate_schema(self) -> None: ------- file_path to the $schema property """ - - path_schema_json = None - - path_schema_json = self.schema_path - if path_schema_json is None: # check if the $schema property is not defined + if self.schema_property is None: # check if the $schema property is not defined self.__logger.warning(f"Could not find $schema property") warnings.warn(f"Could not find $schema property") - - # validate the $schema property - if path_schema_json: - validate_config_json(self.jsonc, path_schema_json, cwd=self.location) + else: + validate_config_json(self.jsonc, self.schema_property, cwd=self.location) def schema_list(self, cwd=None) -> list: """ @@ -277,9 +267,9 @@ def get_profilename_from_profiletype(self, profile_type: str) -> str: try: profilename = self.defaults[profile_type] except KeyError: - self.__logger.warn(f"Given profile type '{profile_type}' has no default profilename") + self.__logger.warn(f"Given profile type '{profile_type}' has no default profile name") warnings.warn( - f"Given profile type '{profile_type}' has no default profilename", + f"Given profile type '{profile_type}' has no default profile name", ProfileParsingWarning, ) else: diff --git a/tests/unit/test_zowe_core.py b/tests/unit/test_zowe_core.py index 9b8b0174..660c64e1 100644 --- a/tests/unit/test_zowe_core.py +++ b/tests/unit/test_zowe_core.py @@ -434,9 +434,60 @@ def test_profile_loading_exception(self, get_pass_func, mock_logger_warning: moc shutil.copy(self.original_file_path, cwd_up_file_path) # Test + self.setUpCreds(cwd_up_file_path, secure_props={}) config_file = ConfigFile(name=self.custom_appname, type="team_config") props: dict = config_file.get_profile(profile_name="non_existent_profile", validate_schema=False) - mock_logger_warning.assert_called() + self.assertEqual(mock_logger_warning.call_args[0][0], "Profile non_existent_profile not found") + + @mock.patch("logging.Logger.error") + @mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=keyring_get_password) + def test_profile_empty_exception(self, get_pass_func, mock_logger_error: mock.MagicMock): + """ + Test correct exceptions are being thrown when a profile is + not found. + + Filename and Filetype will be set to None. + """ + with self.assertRaises(exceptions.ProfileNotFound): + # Setup + cwd_up_dir_path = os.path.dirname(CWD) + cwd_up_file_path = os.path.join(cwd_up_dir_path, f"{self.custom_appname}.config.json") + shutil.copy(self.original_file_path, cwd_up_file_path) + + # Test + self.setUpCreds(cwd_up_file_path, secure_props={}) + config_file = ConfigFile(name=self.custom_appname, type="team_config") + props: dict = config_file.get_profile(profile_name=None,profile_type=None,validate_schema=False) + self.assertEqual(mock_logger_error.call_args[0][0], "Failed to load profile 'None' because Could not find profile as both profile_name and profile_type is not set") + + @mock.patch("logging.Logger.error") + @mock.patch("logging.Logger.warning") + @mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=keyring_get_password) + def test_get_profilename_from_profiletype_invalid_profile_type(self, get_pass_func, mock_logger_warning: mock.MagicMock, mock_logger_error: mock.MagicMock): + """ + Test correct warnings and exceptions are being thrown with + empty default, invalid profile type. + + """ + with self.assertRaises(exceptions.ProfileNotFound): + config_file = ConfigFile(name=self.custom_appname, type="team_config", defaults={}, profiles={'a': {'type' : 'none'}}) + config_file.get_profilename_from_profiletype('test') + self.assertEqual(mock_logger_warning.call_args[0][0], "Given profile type 'test' has no default profile name") + self.assertEqual(mock_logger_warning.call_args[1][0], "Profile 'a' has no type attribute") + self.assertEqual(mock_logger_error.call_args[0][0], "No profile with matching profile_type 'test' found") + + @mock.patch("logging.Logger.warning") + @mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=keyring_get_password) + def test_validate_schema(self, get_pass_func, mock_logger_warning: mock.MagicMock): + """ + Test correct exceptions are being thrown when schema property is empty. + + Schema property will be initialized to None. + """ + with self.assertWarns(UserWarning): + config_file = ConfigFile(name=self.custom_appname, type="team_config") + config_file.validate_schema() + self.assertEqual(mock_logger_warning.call_args[0][0], "Could not find $schema property") @mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=keyring_get_password_exception) def test_secure_props_loading_warning(self, get_pass_func): From d6c6c314664d09b7fc55bcfb9cd26f11206e4d95 Mon Sep 17 00:00:00 2001 From: pem70 Date: Tue, 28 May 2024 11:46:42 -0400 Subject: [PATCH 08/22] update unit test Signed-off-by: pem70 --- tests/unit/test_zowe_core.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/unit/test_zowe_core.py b/tests/unit/test_zowe_core.py index 660c64e1..7d1b4249 100644 --- a/tests/unit/test_zowe_core.py +++ b/tests/unit/test_zowe_core.py @@ -458,7 +458,7 @@ def test_profile_empty_exception(self, get_pass_func, mock_logger_error: mock.Ma self.setUpCreds(cwd_up_file_path, secure_props={}) config_file = ConfigFile(name=self.custom_appname, type="team_config") props: dict = config_file.get_profile(profile_name=None,profile_type=None,validate_schema=False) - self.assertEqual(mock_logger_error.call_args[0][0], "Failed to load profile 'None' because Could not find profile as both profile_name and profile_type is not set") + self.assertEqual(mock_logger_error.call_args[0][0], "Failed to load profile 'None' because Could not find profile as both profile_name and profile_type is not set") @mock.patch("logging.Logger.error") @mock.patch("logging.Logger.warning") @@ -470,11 +470,13 @@ def test_get_profilename_from_profiletype_invalid_profile_type(self, get_pass_fu """ with self.assertRaises(exceptions.ProfileNotFound): - config_file = ConfigFile(name=self.custom_appname, type="team_config", defaults={}, profiles={'a': {'type' : 'none'}}) - config_file.get_profilename_from_profiletype('test') - self.assertEqual(mock_logger_warning.call_args[0][0], "Given profile type 'test' has no default profile name") - self.assertEqual(mock_logger_warning.call_args[1][0], "Profile 'a' has no type attribute") - self.assertEqual(mock_logger_error.call_args[0][0], "No profile with matching profile_type 'test' found") + config_file = ConfigFile(name="name", type="team_config", defaults={}, profiles={'a': {'none' : 'none'}}) + config_file.get_profilename_from_profiletype('test') + + mock_logger_warning.assert_any_call("Given profile type 'test' has no default profile name") + mock_logger_warning.assert_any_call("Profile 'a' has no type attribute") + mock_logger_error.assert_called_once_with("No profile with matching profile_type 'test' found") + @mock.patch("logging.Logger.warning") @mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=keyring_get_password) @@ -485,7 +487,7 @@ def test_validate_schema(self, get_pass_func, mock_logger_warning: mock.MagicMoc Schema property will be initialized to None. """ with self.assertWarns(UserWarning): - config_file = ConfigFile(name=self.custom_appname, type="team_config") + config_file = ConfigFile(name="name", type="team_config") config_file.validate_schema() self.assertEqual(mock_logger_warning.call_args[0][0], "Could not find $schema property") From 47e34f4517313674971fe0789b9e16071d93afe3 Mon Sep 17 00:00:00 2001 From: pem70 Date: Tue, 28 May 2024 12:18:57 -0400 Subject: [PATCH 09/22] update unit test Signed-off-by: pem70 --- tests/unit/test_zowe_core.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/tests/unit/test_zowe_core.py b/tests/unit/test_zowe_core.py index 7d1b4249..f7a41bcc 100644 --- a/tests/unit/test_zowe_core.py +++ b/tests/unit/test_zowe_core.py @@ -437,7 +437,7 @@ def test_profile_loading_exception(self, get_pass_func, mock_logger_warning: moc self.setUpCreds(cwd_up_file_path, secure_props={}) config_file = ConfigFile(name=self.custom_appname, type="team_config") props: dict = config_file.get_profile(profile_name="non_existent_profile", validate_schema=False) - self.assertEqual(mock_logger_warning.call_args[0][0], "Profile non_existent_profile not found") + self.assertEqual(mock_logger_warning.call_args[0][0], "Profile non_existent_profile not found") @mock.patch("logging.Logger.error") @mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=keyring_get_password) @@ -480,7 +480,7 @@ def test_get_profilename_from_profiletype_invalid_profile_type(self, get_pass_fu @mock.patch("logging.Logger.warning") @mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=keyring_get_password) - def test_validate_schema(self, get_pass_func, mock_logger_warning: mock.MagicMock): + def test_validate_schema_logger(self, get_pass_func, mock_logger_warning: mock.MagicMock): """ Test correct exceptions are being thrown when schema property is empty. @@ -489,7 +489,7 @@ def test_validate_schema(self, get_pass_func, mock_logger_warning: mock.MagicMoc with self.assertWarns(UserWarning): config_file = ConfigFile(name="name", type="team_config") config_file.validate_schema() - self.assertEqual(mock_logger_warning.call_args[0][0], "Could not find $schema property") + self.assertEqual(mock_logger_warning.call_args[0][0], "Could not find $schema property") @mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=keyring_get_password_exception) def test_secure_props_loading_warning(self, get_pass_func): @@ -710,9 +710,9 @@ def test_profile_loading_with_valid_schema(self, get_pass_func): prof_manager.config_dir = self.custom_dir props: dict = prof_manager.load(profile_name="zosmf") - @mock.patch("logging.Logger.error") + @mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=keyring_get_password) - def test_profile_loading_with_invalid_schema(self, get_pass_func, mock_logger_error: mock.MagicMock): + def test_profile_loading_with_invalid_schema(self, get_pass_func): """ Test Validation, no error should be raised for valid schema """ @@ -741,11 +741,9 @@ def test_profile_loading_with_invalid_schema(self, get_pass_func, mock_logger_er prof_manager = ProfileManager(appname="invalid.zowe") prof_manager.config_dir = self.custom_dir props: dict = prof_manager.load(profile_name="zosmf", validate_schema=True) - self.assertIn("Instance was invalid under the provided $schema property", mock_logger_error.call_args[0][0]) - @mock.patch("logging.Logger.error") @mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=keyring_get_password) - def test_profile_loading_with_invalid_schema_internet_URI(self, get_pass_func, mock_logger_error: mock.MagicMock): + def test_profile_loading_with_invalid_schema_internet_URI(self, get_pass_func): """ Test Validation, no error should be raised for valid schema """ @@ -775,7 +773,6 @@ def test_profile_loading_with_invalid_schema_internet_URI(self, get_pass_func, m prof_manager = ProfileManager(appname="invalidUri.zowe") prof_manager.config_dir = self.custom_dir props: dict = prof_manager.load(profile_name="zosmf", validate_schema=True) - self.assertIn("The provided schema is invalid", mock_logger_error.call_args[0][0]) @mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=keyring_get_password) def test_profile_loading_with_env_variables(self, get_pass_func): From 046a4c789c597404d029cefa13011905cea3977a Mon Sep 17 00:00:00 2001 From: pem70 Date: Tue, 28 May 2024 12:53:46 -0400 Subject: [PATCH 10/22] Add description for test functions Signed-off-by: pem70 --- tests/unit/test_zowe_core.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/tests/unit/test_zowe_core.py b/tests/unit/test_zowe_core.py index f7a41bcc..4b845dbe 100644 --- a/tests/unit/test_zowe_core.py +++ b/tests/unit/test_zowe_core.py @@ -181,20 +181,14 @@ def test_perform_streamed_request(self, mock_send_request, mock_logger_error: mo mock_logger_error.assert_not_called() mock_logger_debug.assert_called() - - # - # # This will fail because parameter also contains the arguments of perform_request - # mock_logger_debug.assert_called_with("Request method: GET") - # - # But this will pass because we are partially looking for the request method in the first argument self.assertIn("Request method: GET", mock_logger_debug.call_args[0][0]) - mock_send_request.assert_called_once() self.assertTrue(mock_send_request.call_args[1]["stream"]) @mock.patch("logging.Logger.error") def test_logger_unmatched_status_code(self, mock_logger_error: mock.MagicMock): + """Test logger with unexpeceted status code""" request_handler = RequestHandler(self.session_arguments) try: request_handler.perform_request("GET", {"url": "https://www.zowe.org"}, expected_code= [0], stream = True) @@ -204,6 +198,7 @@ def test_logger_unmatched_status_code(self, mock_logger_error: mock.MagicMock): @mock.patch("logging.Logger.error") def test_logger_perform_request_invalid_method(self, mock_logger_error: mock.MagicMock): + """Test logger with invalid request method""" request_handler = RequestHandler(self.session_arguments) try: request_handler.perform_request("Invalid method", {"url": "https://www.zowe.org"}, stream = True) @@ -482,7 +477,7 @@ def test_get_profilename_from_profiletype_invalid_profile_type(self, get_pass_fu @mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=keyring_get_password) def test_validate_schema_logger(self, get_pass_func, mock_logger_warning: mock.MagicMock): """ - Test correct exceptions are being thrown when schema property is empty. + Test correct exceptions are being thrown when schema property is not set. Schema property will be initialized to None. """ @@ -1074,7 +1069,8 @@ def test_validate_config_json_invalid(self): class test_logger_setLoggerLevel(TestCase): def test_logger_setLoggerLevel(self): + """Test setLoggerLevel""" test_logging = logger.Log() - test_value = logging.INFO + test_value = logging.DEBUG test_logging.setLoggerLevel(test_value) self.assertEqual(logging.root.level, test_value) \ No newline at end of file From 0f36770a65767de6dfbf05ee12670822f07f2f36 Mon Sep 17 00:00:00 2001 From: pem70 Date: Tue, 28 May 2024 13:17:30 -0400 Subject: [PATCH 11/22] update changelog Signed-off-by: pem70 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c81886d..76d2df36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to the Zowe Client Python SDK will be documented in this file. +## Recent Changes + +### Enhancements + +- Added logger class to core SDK + ## `1.0.0-dev15` ### Bug Fixes From 1597f36d2a209e27cbea6d0273a2252d8bbdc68b Mon Sep 17 00:00:00 2001 From: pem70 Date: Tue, 28 May 2024 13:18:16 -0400 Subject: [PATCH 12/22] Update CHANGELOG.md Signed-off-by: pem70 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76d2df36..583bac7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ All notable changes to the Zowe Client Python SDK will be documented in this fil ### Enhancements -- Added logger class to core SDK +- Added logger class to core SDK [#185] https://github.com/zowe/zowe-client-python-sdk/issues/185 ## `1.0.0-dev15` From 7be42b12f7870f6c97bf9272c6e18e5e33656701 Mon Sep 17 00:00:00 2001 From: pem70 <105866197+pem70@users.noreply.github.com> Date: Wed, 29 May 2024 08:56:36 -0400 Subject: [PATCH 13/22] Update CHANGELOG.md Co-authored-by: Timothy Johnson Signed-off-by: pem70 <105866197+pem70@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 583bac7a..8bc3d0ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ All notable changes to the Zowe Client Python SDK will be documented in this fil ### Enhancements -- Added logger class to core SDK [#185] https://github.com/zowe/zowe-client-python-sdk/issues/185 +- Added logger class to core SDK [#185](https://github.com/zowe/zowe-client-python-sdk/issues/185) ## `1.0.0-dev15` From 231296f588d9c1928d9d2eb92a667c1b9075165f Mon Sep 17 00:00:00 2001 From: pem70 <105866197+pem70@users.noreply.github.com> Date: Wed, 29 May 2024 08:59:52 -0400 Subject: [PATCH 14/22] Update src/core/zowe/core_for_zowe_sdk/logger.py Co-authored-by: Timothy Johnson Signed-off-by: pem70 <105866197+pem70@users.noreply.github.com> --- src/core/zowe/core_for_zowe_sdk/logger.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/core/zowe/core_for_zowe_sdk/logger.py b/src/core/zowe/core_for_zowe_sdk/logger.py index f2f96677..f0d81bde 100644 --- a/src/core/zowe/core_for_zowe_sdk/logger.py +++ b/src/core/zowe/core_for_zowe_sdk/logger.py @@ -6,8 +6,7 @@ class Log: dirname = os.path.join(os.path.expanduser("~"), ".zowe/logs") - if not os.path.isdir(dirname): - os.makedirs(dirname) + os.makedirs(dirname, exist_ok=True) logging.basicConfig( filename=os.path.join(dirname, "python_sdk_logs.log"), From cf4e914f8075a42cc1fc3b682817d50b14f65aaa Mon Sep 17 00:00:00 2001 From: pem70 <105866197+pem70@users.noreply.github.com> Date: Wed, 29 May 2024 09:02:39 -0400 Subject: [PATCH 15/22] Update src/core/zowe/core_for_zowe_sdk/logger.py Co-authored-by: Timothy Johnson Signed-off-by: pem70 <105866197+pem70@users.noreply.github.com> --- src/core/zowe/core_for_zowe_sdk/logger.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/core/zowe/core_for_zowe_sdk/logger.py b/src/core/zowe/core_for_zowe_sdk/logger.py index f0d81bde..85276ddf 100644 --- a/src/core/zowe/core_for_zowe_sdk/logger.py +++ b/src/core/zowe/core_for_zowe_sdk/logger.py @@ -15,6 +15,12 @@ class Log: datefmt="%m/%d/%Y %I:%M:%S %p", ) + loggers = [] + @staticmethod + def registerLogger(name: string): + Log.loggers.append(logging.getLogger(name)) + @staticmethod def setLoggerLevel(level: int): - logging.root.setLevel(level) \ No newline at end of file + for logger in Log.loggers: + logger.setLevel(level) \ No newline at end of file From fd6aaa8c1f28403d303f2e4eb8ddd2deb7829298 Mon Sep 17 00:00:00 2001 From: pem70 <105866197+pem70@users.noreply.github.com> Date: Wed, 29 May 2024 09:18:17 -0400 Subject: [PATCH 16/22] fix typo in def registerLogger Signed-off-by: pem70 <105866197+pem70@users.noreply.github.com> --- src/core/zowe/core_for_zowe_sdk/logger.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/zowe/core_for_zowe_sdk/logger.py b/src/core/zowe/core_for_zowe_sdk/logger.py index 85276ddf..3ef9b3b6 100644 --- a/src/core/zowe/core_for_zowe_sdk/logger.py +++ b/src/core/zowe/core_for_zowe_sdk/logger.py @@ -17,10 +17,10 @@ class Log: loggers = [] @staticmethod - def registerLogger(name: string): + def registerLogger(name: str): Log.loggers.append(logging.getLogger(name)) @staticmethod def setLoggerLevel(level: int): for logger in Log.loggers: - logger.setLevel(level) \ No newline at end of file + logger.setLevel(level) From 2f36fc25379b21f3b585285be401c90b96878feb Mon Sep 17 00:00:00 2001 From: pem70 Date: Wed, 29 May 2024 09:33:49 -0400 Subject: [PATCH 17/22] update testing for setLoggerLevel Signed-off-by: pem70 --- src/core/zowe/core_for_zowe_sdk/profile_manager.py | 2 ++ tests/unit/test_zowe_core.py | 7 ++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/core/zowe/core_for_zowe_sdk/profile_manager.py b/src/core/zowe/core_for_zowe_sdk/profile_manager.py index 6e9ed111..fc639c82 100644 --- a/src/core/zowe/core_for_zowe_sdk/profile_manager.py +++ b/src/core/zowe/core_for_zowe_sdk/profile_manager.py @@ -21,6 +21,7 @@ from deepmerge import always_merger from .config_file import ConfigFile, Profile +from .logger import Log from .credential_manager import CredentialManager from .custom_warnings import ( ConfigNotFoundWarning, @@ -59,6 +60,7 @@ def __init__(self, appname: str = "zowe", show_warnings: bool = True): self.project_user_config = ConfigFile(type=USER_CONFIG, name=appname) self.__logger = logging.getLogger(__name__) + Log.registerLogger(__name__) self.global_config = ConfigFile(type=TEAM_CONFIG, name=GLOBAL_CONFIG_NAME) try: diff --git a/tests/unit/test_zowe_core.py b/tests/unit/test_zowe_core.py index 4b845dbe..4e216fec 100644 --- a/tests/unit/test_zowe_core.py +++ b/tests/unit/test_zowe_core.py @@ -1070,7 +1070,8 @@ class test_logger_setLoggerLevel(TestCase): def test_logger_setLoggerLevel(self): """Test setLoggerLevel""" - test_logging = logger.Log() + profile = ProfileManager() + test_logger = logging.getLogger("zowe.core_for_zowe_sdk.profile_manager") test_value = logging.DEBUG - test_logging.setLoggerLevel(test_value) - self.assertEqual(logging.root.level, test_value) \ No newline at end of file + logger.Log.setLoggerLevel(test_value) + self.assertEqual(test_logger.level, test_value) \ No newline at end of file From ec272f293dfaf27c2fea188c9c1d5b00ece41c99 Mon Sep 17 00:00:00 2001 From: pem70 <105866197+pem70@users.noreply.github.com> Date: Thu, 30 May 2024 10:10:14 -0400 Subject: [PATCH 18/22] Update src/core/zowe/core_for_zowe_sdk/config_file.py Co-authored-by: Trae Yelovich Signed-off-by: pem70 <105866197+pem70@users.noreply.github.com> --- src/core/zowe/core_for_zowe_sdk/config_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/zowe/core_for_zowe_sdk/config_file.py b/src/core/zowe/core_for_zowe_sdk/config_file.py index 60cab57e..f0431256 100644 --- a/src/core/zowe/core_for_zowe_sdk/config_file.py +++ b/src/core/zowe/core_for_zowe_sdk/config_file.py @@ -212,7 +212,7 @@ def get_profile( self.init_from_file(validate_schema) if profile_name is None and profile_type is None: - self.__logger.error(f"Failed to load profile '{profile_name}' because Could not find profile as both profile_name and profile_type is not set") + self.__logger.error(f"Failed to load profile: profile_name and profile_type were not provided.") raise ProfileNotFound( profile_name=profile_name, error_msg="Could not find profile as both profile_name and profile_type is not set.", From 04d7e4d9f110b7f4b1ced6554d0299cb25e3aa40 Mon Sep 17 00:00:00 2001 From: pem70 <105866197+pem70@users.noreply.github.com> Date: Thu, 30 May 2024 10:10:41 -0400 Subject: [PATCH 19/22] Update src/core/zowe/core_for_zowe_sdk/logger.py Co-authored-by: Trae Yelovich Signed-off-by: pem70 <105866197+pem70@users.noreply.github.com> --- src/core/zowe/core_for_zowe_sdk/logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/zowe/core_for_zowe_sdk/logger.py b/src/core/zowe/core_for_zowe_sdk/logger.py index 3ef9b3b6..e79ed93e 100644 --- a/src/core/zowe/core_for_zowe_sdk/logger.py +++ b/src/core/zowe/core_for_zowe_sdk/logger.py @@ -2,7 +2,7 @@ import os class Log: - """root logger setup and a funtion to customize logger level""" + """root logger setup and a function to customize logger level""" dirname = os.path.join(os.path.expanduser("~"), ".zowe/logs") From c8f9a8478002ae4c6401cb899a8e232aa84fae0e Mon Sep 17 00:00:00 2001 From: pem70 <105866197+pem70@users.noreply.github.com> Date: Thu, 30 May 2024 10:11:03 -0400 Subject: [PATCH 20/22] Update src/core/zowe/core_for_zowe_sdk/profile_manager.py Co-authored-by: Trae Yelovich Signed-off-by: pem70 <105866197+pem70@users.noreply.github.com> --- src/core/zowe/core_for_zowe_sdk/profile_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/zowe/core_for_zowe_sdk/profile_manager.py b/src/core/zowe/core_for_zowe_sdk/profile_manager.py index fc639c82..2ea645fd 100644 --- a/src/core/zowe/core_for_zowe_sdk/profile_manager.py +++ b/src/core/zowe/core_for_zowe_sdk/profile_manager.py @@ -200,7 +200,7 @@ def get_profile( f"A type checker was asked to check a type it did not have registered, {exc}" ) except jsonschema.exceptions.UnknownType as exc: - logger.error(f"Unknown type is found in schema_json, exc") + logger.error(f"Unknown type is found in schema_json, {exc}") raise jsonschema.exceptions.UnknownType(f"Unknown type is found in schema_json, exc") except jsonschema.exceptions.FormatError as exc: logger.error(f"Validating a format config_json failed for schema_json, {exc}") From 0c70479901f568bc48962204208142cbc899cf32 Mon Sep 17 00:00:00 2001 From: pem70 Date: Thu, 30 May 2024 10:15:39 -0400 Subject: [PATCH 21/22] Update unit test Signed-off-by: pem70 --- tests/unit/test_zowe_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/test_zowe_core.py b/tests/unit/test_zowe_core.py index 4e216fec..459a52ad 100644 --- a/tests/unit/test_zowe_core.py +++ b/tests/unit/test_zowe_core.py @@ -453,7 +453,7 @@ def test_profile_empty_exception(self, get_pass_func, mock_logger_error: mock.Ma self.setUpCreds(cwd_up_file_path, secure_props={}) config_file = ConfigFile(name=self.custom_appname, type="team_config") props: dict = config_file.get_profile(profile_name=None,profile_type=None,validate_schema=False) - self.assertEqual(mock_logger_error.call_args[0][0], "Failed to load profile 'None' because Could not find profile as both profile_name and profile_type is not set") + self.assertEqual(mock_logger_error.call_args[0][0], "Failed to load profile: profile_name and profile_type were not provided.") @mock.patch("logging.Logger.error") @mock.patch("logging.Logger.warning") From c7a64ff7a6d79a00f5a12e4e78f73be961c02ee7 Mon Sep 17 00:00:00 2001 From: pem70 Date: Thu, 30 May 2024 14:39:25 -0400 Subject: [PATCH 22/22] Update registerLogger and related code Signed-off-by: pem70 --- src/core/zowe/core_for_zowe_sdk/config_file.py | 3 ++- src/core/zowe/core_for_zowe_sdk/connection.py | 3 ++- src/core/zowe/core_for_zowe_sdk/credential_manager.py | 3 ++- src/core/zowe/core_for_zowe_sdk/logger.py | 5 ++++- src/core/zowe/core_for_zowe_sdk/profile_manager.py | 3 +-- src/core/zowe/core_for_zowe_sdk/request_handler.py | 3 ++- src/core/zowe/core_for_zowe_sdk/sdk_api.py | 3 ++- src/core/zowe/core_for_zowe_sdk/session.py | 4 ++-- src/core/zowe/core_for_zowe_sdk/zosmf_profile.py | 5 ++--- 9 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/core/zowe/core_for_zowe_sdk/config_file.py b/src/core/zowe/core_for_zowe_sdk/config_file.py index f0431256..b0a4910e 100644 --- a/src/core/zowe/core_for_zowe_sdk/config_file.py +++ b/src/core/zowe/core_for_zowe_sdk/config_file.py @@ -28,6 +28,7 @@ from .exceptions import ProfileNotFound from .profile_constants import GLOBAL_CONFIG_NAME, TEAM_CONFIG, USER_CONFIG from .validators import validate_config_json +from .logger import Log HOME = os.path.expanduser("~") GLOBAL_CONFIG_LOCATION = os.path.join(HOME, ".zowe") @@ -73,7 +74,7 @@ class ConfigFile: jsonc: Optional[dict] = None _missing_secure_props: list = field(default_factory=list) - __logger = logging.getLogger(__name__) + __logger = Log.registerLogger(__name__) @property def filename(self) -> str: diff --git a/src/core/zowe/core_for_zowe_sdk/connection.py b/src/core/zowe/core_for_zowe_sdk/connection.py index 2f764a99..41c7e553 100644 --- a/src/core/zowe/core_for_zowe_sdk/connection.py +++ b/src/core/zowe/core_for_zowe_sdk/connection.py @@ -11,6 +11,7 @@ """ from .exceptions import MissingConnectionArgs +from .logger import Log import logging @@ -30,7 +31,7 @@ class ApiConnection: """ def __init__(self, host_url, user, password, ssl_verification=True): - logger = logging.getLogger(__name__) + logger = Log.registerLogger(__name__) """Construct an ApiConnection object.""" if not host_url or not user or not password: diff --git a/src/core/zowe/core_for_zowe_sdk/credential_manager.py b/src/core/zowe/core_for_zowe_sdk/credential_manager.py index fac1f631..74787194 100644 --- a/src/core/zowe/core_for_zowe_sdk/credential_manager.py +++ b/src/core/zowe/core_for_zowe_sdk/credential_manager.py @@ -20,6 +20,7 @@ from .constants import constants from .exceptions import SecureProfileLoadFailed +from .logger import Log HAS_KEYRING = True try: @@ -30,7 +31,7 @@ class CredentialManager: secure_props = {} - __logger = logging.getLogger(__name__) + __logger = Log.registerLogger(__name__) @staticmethod def load_secure_props() -> None: diff --git a/src/core/zowe/core_for_zowe_sdk/logger.py b/src/core/zowe/core_for_zowe_sdk/logger.py index e79ed93e..3549ec11 100644 --- a/src/core/zowe/core_for_zowe_sdk/logger.py +++ b/src/core/zowe/core_for_zowe_sdk/logger.py @@ -18,7 +18,10 @@ class Log: loggers = [] @staticmethod def registerLogger(name: str): - Log.loggers.append(logging.getLogger(name)) + """A function to get Logger and registered for level setting""" + logger = logging.getLogger(name) + Log.loggers.append(logger) + return logger @staticmethod def setLoggerLevel(level: int): diff --git a/src/core/zowe/core_for_zowe_sdk/profile_manager.py b/src/core/zowe/core_for_zowe_sdk/profile_manager.py index 2ea645fd..0cea790d 100644 --- a/src/core/zowe/core_for_zowe_sdk/profile_manager.py +++ b/src/core/zowe/core_for_zowe_sdk/profile_manager.py @@ -59,8 +59,7 @@ def __init__(self, appname: str = "zowe", show_warnings: bool = True): self.project_config = ConfigFile(type=TEAM_CONFIG, name=appname) self.project_user_config = ConfigFile(type=USER_CONFIG, name=appname) - self.__logger = logging.getLogger(__name__) - Log.registerLogger(__name__) + self.__logger = Log.registerLogger(__name__) self.global_config = ConfigFile(type=TEAM_CONFIG, name=GLOBAL_CONFIG_NAME) try: diff --git a/src/core/zowe/core_for_zowe_sdk/request_handler.py b/src/core/zowe/core_for_zowe_sdk/request_handler.py index 47455286..20791ceb 100644 --- a/src/core/zowe/core_for_zowe_sdk/request_handler.py +++ b/src/core/zowe/core_for_zowe_sdk/request_handler.py @@ -15,6 +15,7 @@ import logging from .exceptions import InvalidRequestMethod, RequestFailed, UnexpectedStatus +from .logger import Log class RequestHandler: @@ -44,7 +45,7 @@ def __init__(self, session_arguments, logger_name = __name__): self.session_arguments = session_arguments self.valid_methods = ["GET", "POST", "PUT", "DELETE"] self.__handle_ssl_warnings() - self.__logger = logging.getLogger(logger_name) + self.__logger = Log.registerLogger(__name__) def __handle_ssl_warnings(self): """Turn off warnings if the SSL verification argument if off.""" diff --git a/src/core/zowe/core_for_zowe_sdk/sdk_api.py b/src/core/zowe/core_for_zowe_sdk/sdk_api.py index acb83ac3..dae4fc29 100644 --- a/src/core/zowe/core_for_zowe_sdk/sdk_api.py +++ b/src/core/zowe/core_for_zowe_sdk/sdk_api.py @@ -17,6 +17,7 @@ from .exceptions import UnsupportedAuthType from .request_handler import RequestHandler from .session import ISession, Session +from .logger import Log class SdkApi: @@ -29,7 +30,7 @@ def __init__(self, profile, default_url, logger_name = __name__): session = Session(profile) self.session: ISession = session.load() - self.logger = logging.getLogger(logger_name) + self.logger = Log.registerLogger(logger_name) self.default_service_url = default_url self.default_headers = { diff --git a/src/core/zowe/core_for_zowe_sdk/session.py b/src/core/zowe/core_for_zowe_sdk/session.py index fe2ac533..fa2861bc 100644 --- a/src/core/zowe/core_for_zowe_sdk/session.py +++ b/src/core/zowe/core_for_zowe_sdk/session.py @@ -14,8 +14,8 @@ from typing import Optional from . import session_constants +from .logger import Log -import logging @dataclass class ISession: @@ -43,7 +43,7 @@ class Session: def __init__(self, props: dict) -> None: # set host and port - self.__logger = logging.getLogger(__name__) + self.__logger = Log.registerLogger(__name__) if props.get("host") is not None: self.session: ISession = ISession(host=props.get("host")) diff --git a/src/core/zowe/core_for_zowe_sdk/zosmf_profile.py b/src/core/zowe/core_for_zowe_sdk/zosmf_profile.py index b877fdea..d9aa1893 100644 --- a/src/core/zowe/core_for_zowe_sdk/zosmf_profile.py +++ b/src/core/zowe/core_for_zowe_sdk/zosmf_profile.py @@ -16,11 +16,10 @@ import yaml -import logging - from .connection import ApiConnection from .constants import constants from .exceptions import SecureProfileLoadFailed +from .logger import Log HAS_KEYRING = True try: @@ -55,7 +54,7 @@ def __init__(self, profile_name): The name of the Zowe z/OSMF profile """ self.profile_name = profile_name - self.__logger = logging.getLogger(__name__) + self.__logger = Log.registerLogger(__name__) @property def profiles_dir(self):