diff --git a/.github/workflows/sdk-build.yml b/.github/workflows/sdk-build.yml index 83263c51..c9746da4 100644 --- a/.github/workflows/sdk-build.yml +++ b/.github/workflows/sdk-build.yml @@ -28,6 +28,10 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt + - name: Lint with pydocstyle + run: pydocstyle --match-dir='^(?!build$).*' --match='^(?!(__init__\.py|setup\.py$)).*\.py$' src + - name: Lint with pydoclint + run: pydoclint --exclude='.*/build/.*' src - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names diff --git a/.pydocstyle b/.pydocstyle new file mode 100644 index 00000000..98367b56 --- /dev/null +++ b/.pydocstyle @@ -0,0 +1,3 @@ +[pydocstyle] +convention = numpy +add-ignore = D107, D207 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ccf5050..f71d924d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,13 +2,23 @@ All notable changes to the Zowe Client Python SDK will be documented in this file. +## Recent Changes + +### Enhancements + +- Supported for doc string enforcer [#309] (https://github.com/zowe/zowe-client-python-sdk/issues/309) + +- Add type annotations for all methods [#280] (https://github.com/zowe/zowe-client-python-sdk/issues/280) + +### Bug Fixes + ## `1.0.0-dev18` ### Enhancements - Included support for `AUTH_TYPE_CERT_PEM` and `AUTH_TYPE_NONE` in `session` [#291] (https://github.com/zowe/zowe-client-python-sdk/issues/291) and [#296] (https://github.com/zowe/zowe-client-python-sdk/issues/296) -- Updated all functions descriptions to be consitent [#279] (https://github.com/zowe/zowe-client-python-sdk/issues/279) +- Updated doc strings for all functions to be consistent [#279] (https://github.com/zowe/zowe-client-python-sdk/issues/279) - *Breaking*: Added Support for turning off loggers. Replaced `setLoggerLevel` in Logger class with `setAllLoggerLevel` [#278] (https://github.com/zowe/zowe-client-python-sdk/issues/278) @@ -16,7 +26,7 @@ All notable changes to the Zowe Client Python SDK will be documented in this fil - Fixed a bug on `create` in `Datasets` where the target dataset gets created with a different block size when `like` is specified [#295] (https://github.com/zowe/zowe-client-python-sdk/issues/295) -- Fixed a bug on `logger` that it would affect all Python application loggers. +- Fixed a bug on `logger` that it would affect all Python application loggers. [#314] (https://github.com/zowe/zowe-client-python-sdk/issues/314) ## `1.0.0-dev17` diff --git a/pyproject.toml b/pyproject.toml index 85c3b073..d3e38a07 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,3 +3,9 @@ line-length = 120 [tool.isort] profile = "black" + +[tool.pydoclint] +style = "numpy" +check-arg-order = false +require-return-section-when-returning-nothing = false +quiet = true diff --git a/requirements.txt b/requirements.txt index fc0b019a..45680bbd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,6 +16,7 @@ mccabe==0.7.0 nose2==0.10.0 pycodestyle==2.9.0 pydocstyle==5.1.1 +pydoclint==0.5.3 pyfakefs pyflakes==2.5.0 pylama==7.7.1 @@ -37,3 +38,4 @@ zipp==3.19.1 -e ./src/zos_jobs -e ./src/zos_tso -e ./src/zosmf + 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 353840ae..9bcd5665 100644 --- a/src/core/zowe/core_for_zowe_sdk/config_file.py +++ b/src/core/zowe/core_for_zowe_sdk/config_file.py @@ -38,6 +38,8 @@ class Profile(NamedTuple): + """Class to represent a profile.""" + data: dict = {} name: str = "" missing_secure_props: list = [] @@ -77,6 +79,7 @@ class ConfigFile: @property def filename(self) -> str: + # noqa: D102 if self.type == TEAM_CONFIG: return f"{self.name}.config.json" @@ -87,6 +90,7 @@ def filename(self) -> str: @property def filepath(self) -> Optional[str]: + # noqa: D102 if not self.location: return None @@ -94,10 +98,12 @@ def filepath(self) -> Optional[str]: @property def location(self) -> Optional[str]: + # noqa: D102 return self._location @location.setter def location(self, dirname: str) -> None: + # noqa: D102 if os.path.isdir(dirname): self._location = dirname else: @@ -110,14 +116,13 @@ def init_from_file( suppress_config_file_warnings: Optional[bool] = True, ) -> None: """ - Initializes the class variable after - setting filepath (or if not set, autodiscover the file) + Initialize the class variable after setting filepath (or if not set, autodiscover the file). Parameters - ------- - validate_schema: bool + ---------- + validate_schema: Optional[bool] True if validation is preferred, false otherwise - suppress_config_file_warnings: bool + suppress_config_file_warnings: Optional[bool] True if the property should be stored securely, False otherwise. """ if self.filepath is None: @@ -147,30 +152,27 @@ def init_from_file( self.__load_secure_properties() def validate_schema(self) -> None: - """ - Get the $schema_property from the config and load the schema - - Returns - ------- - file_path to the $schema property - """ + """Get the $schema_property from the config and load the schema.""" 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") else: validate_config_json(self.jsonc, self.schema_property, cwd=self.location) - def schema_list(self, cwd=None) -> list: + def schema_list(self, cwd: str = None) -> list: """ - Loads the schema properties - in a sorted order according to the priority + Load the schema properties in a sorted order according to the priority. + + Parameters + ---------- + cwd: str + current working directory Returns ------- - Dictionary - Returns the profile properties from schema (prop: value) + list + Return the profile properties from schema (prop: value) """ - schema = self.schema_property if schema is None: return [] @@ -208,17 +210,22 @@ def get_profile( validate_schema: Optional[bool] = True, ) -> Profile: """ - Load given profile including secure properties and excluding values from base profile + Load given profile including secure properties and excluding values from base profile. Parameters - ------- - profile_name: str + ---------- + profile_name: Optional[str] Name of the profile - profile_type: str + profile_type: Optional[str] Type of the profile - validate_shcema: bool + validate_schema: Optional[bool] True if validation is preferred + Raises + ------ + ProfileNotFound + Cannot find profile + Returns ------- Profile @@ -242,17 +249,15 @@ def get_profile( def autodiscover_config_dir(self) -> None: """ - Autodiscover Zowe z/OSMF Team Config files by going up the path from - current working directory + Autodiscover Zowe z/OSMF Team Config files by going up the path from current working directory. - Returns - ------- - None + Sets path if it finds the config directory, Else, it raises an Exception. - Sets path if it finds the config directory, - Else, it raises an Exception + Raises + ------ + FileNotFoundError + Cannot find file in directory. """ - current_dir = CURRENT_DIR while True: @@ -271,10 +276,10 @@ def autodiscover_config_dir(self) -> None: def get_profilename_from_profiletype(self, profile_type: str) -> str: """ - Returns profilename from given profiletype as defined in the team config profile + Return profilename from given profiletype as defined in the team config profile. Parameters - ------- + ---------- profile_type: str Type of the profile @@ -283,8 +288,10 @@ def get_profilename_from_profiletype(self, profile_type: str) -> str: str The exact profilename of the profile to load from the mentioned type. - First tries to look into the defaults, if not found, - then it tries to iterate through the profiles + Raises + ------ + ProfileNotFound + Cannot find profile """ # try to get the profilename from defaults try: @@ -318,12 +325,12 @@ def get_profilename_from_profiletype(self, profile_type: str) -> str: error_msg=f"No profile with matching profile_type '{profile_type}' found", ) - def find_profile(self, path: str, profiles: dict): + def find_profile(self, path: str, profiles: dict) -> Optional[dict]: """ - Find a profile at a specified location from within a set of nested profiles + Find a profile at a specified location from within a set of nested profiles. Parameters - ------- + ---------- path: str The location to look for the profile profiles: dict @@ -331,7 +338,7 @@ def find_profile(self, path: str, profiles: dict): Returns ------- - dictionary + Optional[dict] The profile object that was found, or None if not found """ segments = path.split(".") @@ -345,20 +352,20 @@ def find_profile(self, path: str, profiles: dict): def load_profile_properties(self, profile_name: str) -> dict: """ - Load profile properties given profile_name including secure properties + Load profile properties given profile_name including secure properties. + + Load exact profile properties (without prepopulated fields from base profile) + from the profile dict and populate fields from the secure credentials storage Parameters - ------- + ---------- profile_name: str Name of the profile Returns ------- - dictionary + dict Object containing profile properties - - Load exact profile properties (without prepopulated fields from base profile) - from the profile dict and populate fields from the secure credentials storage """ props = {} lst = profile_name.split(".") @@ -378,9 +385,7 @@ def load_profile_properties(self, profile_name: str) -> dict: return props def __load_secure_properties(self): - """ - Inject secure properties that have been loaded from the vault into the profiles object. - """ + """Inject secure properties that have been loaded from the vault into the profiles object.""" secure_props = CredentialManager.secure_props.get(self.filepath, {}) for key, value in secure_props.items(): segments = [name for i, name in enumerate(key.split(".")) if i % 2 == 1] @@ -395,9 +400,22 @@ def __load_secure_properties(self): else: break - def __extract_secure_properties(self, profiles_obj, json_path="profiles"): + def __extract_secure_properties(self, profiles_obj: dict, json_path: Optional[str] = "profiles") -> dict: """ - Extract secure properties from the profiles object so they can be saved to the vault. + Extract secure properties from the profiles object for storage in the vault. + + Parameters + ---------- + profiles_obj : dict + The profiles object from which secure properties are extracted. + json_path : Optional[str] + The JSON path used as a base in the vault for storing secure properties. + + Returns + ------- + dict + A dictionary of secure properties keyed by JSON path in the vault. + """ secure_props = {} for key, value in profiles_obj.items(): @@ -410,9 +428,16 @@ def __extract_secure_properties(self, profiles_obj, json_path="profiles"): secure_props.update(self.__extract_secure_properties(value["profiles"], f"{json_path}.{key}.profiles")) return secure_props - def __set_or_create_nested_profile(self, profile_name, profile_data): + def __set_or_create_nested_profile(self, profile_name: str, profile_data: dict): """ - Set or create a nested profile. + Set or create a nested profile within the profiles structure. + + Parameters + ---------- + profile_name : str + The dot-separated path name of the profile to set or create. + profile_data : dict + The data to set in the specified profile. """ path = self.get_profile_path_from_name(profile_name) keys = path.split(".")[1:] @@ -423,34 +448,36 @@ def __set_or_create_nested_profile(self, profile_name, profile_data): def __is_secure(self, json_path: str, property_name: str) -> bool: """ - Check whether the given JSON path corresponds to a secure property. + Determine if a property should be stored securely based on its presence in the secure list. - Parameters: - json_path (str): The JSON path of the property to check. - property_name (str): The name of the property to check. + Parameters + ---------- + json_path : str + The JSON path of the property within the profiles structure. + property_name : str + The name of the property to check for secure storage requirements. - Returns: - bool: True if the property should be stored securely, False otherwise. + Returns + ------- + bool + True if the property is listed to be stored securely, False otherwise. """ - profile = self.find_profile(json_path, self.profiles) if profile and profile.get("secure"): return property_name in profile["secure"] return False - def set_property(self, json_path, value, secure=None) -> None: + def set_property(self, json_path: str, value: str, secure: Optional[bool] = None) -> None: """ Set a property in the profile, storing it securely if necessary. Parameters - ------- + ---------- json_path: str The JSON path of the property to set. value: str The value to be set for the property. - profile_name: str - The name of the profile to set the property in. - secure: bool + secure: Optional[bool] If True, the property will be stored securely. Default is None. """ if self.profiles is None: @@ -481,7 +508,7 @@ def set_profile(self, profile_path: str, profile_data: dict) -> None: Set a profile in the config file. Parameters - ------- + ---------- profile_path: str The path of the profile to be set. eg: profiles.zosmf profile_data: dict @@ -506,17 +533,14 @@ def set_profile(self, profile_path: str, profile_data: dict) -> None: } self.__set_or_create_nested_profile(profile_name, profile_data) - def save(self, update_secure_props=True): + def save(self, update_secure_props: Optional[bool] = True) -> None: """ - Save the config file to disk. and secure props to vault - parameters - ------- - secure_props: bool - If True, the secure properties will be stored in the vault. Default is True. + Save the config file to disk. and secure props to vault. - Returns - ------- - None + Parameters + ---------- + update_secure_props: Optional[bool] + If True, the secure properties will be stored in the vault. Default is True. """ # Updating the config file with any changes if not any(self.profiles.values()): @@ -536,7 +560,7 @@ def get_profile_name_from_path(self, path: str) -> str: Get the name of the profile from the given path. Parameters - ------- + ---------- path: str The location to look for the profile @@ -554,7 +578,7 @@ def get_profile_path_from_name(self, short_path: str) -> str: Get the path of the profile from the given name. Parameters - ------- + ---------- short_path: str Partial path of profile diff --git a/src/core/zowe/core_for_zowe_sdk/connection.py b/src/core/zowe/core_for_zowe_sdk/connection.py index d13d2bec..97f94bfd 100644 --- a/src/core/zowe/core_for_zowe_sdk/connection.py +++ b/src/core/zowe/core_for_zowe_sdk/connection.py @@ -18,7 +18,7 @@ class ApiConnection: """ Class used to represent a connection with a REST API. - Attributes + Parameters ---------- host_url: str The base url of the rest api host @@ -27,11 +27,15 @@ class ApiConnection: password: str The password for the user ssl_verification: bool - """ + Options for ssl verification. True by default. - def __init__(self, host_url, user, password, ssl_verification=True): - """Construct an ApiConnection object.""" + Raises + ------ + MissingConnectionArgs + Missing connection argument. + """ + def __init__(self, host_url: str, user: str, password: str, ssl_verification: bool = True): __logger = Log.registerLogger(__name__) if not host_url or not user or not password: __logger.error("Missing connection argument") 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 4acd9c1d..007301a4 100644 --- a/src/core/zowe/core_for_zowe_sdk/credential_manager.py +++ b/src/core/zowe/core_for_zowe_sdk/credential_manager.py @@ -28,19 +28,22 @@ class CredentialManager: + """A class including static functions for managing credentials.""" + secure_props = {} __logger = Log.registerLogger(__name__) @staticmethod def load_secure_props() -> None: """ - load secure_props stored for the given config file - - Returns - ------- - None + Load secure_props stored for the given config file. if keyring is not initialized, set empty value + + Raises + ------ + SecureProfileLoadFailed + Fail to load secure profile """ if not HAS_KEYRING: CredentialManager.secure_props = {} @@ -64,13 +67,7 @@ def load_secure_props() -> None: @staticmethod def save_secure_props() -> None: - """ - Set secure_props for the given config file - - Returns - ------- - None - """ + """Set secure_props for the given config file.""" if not HAS_KEYRING: return @@ -88,18 +85,19 @@ def save_secure_props() -> None: @staticmethod def _get_credential(service_name: str, account_name: str) -> Optional[str]: """ - Retrieve the credential from the keyring or storage. - If the credential exceeds the maximum length, retrieve it in parts. + Retrieve the credential from the keyring or storage (in parts after maximum length). Parameters ---------- service_name: str The service name for the credential retrieval + account_name: str + The account name of the credential Returns ------- - str - The retrieved encoded credential + Optional[str] + The retrieved encoded credential """ encoded_credential = keyring.get_password(service_name, account_name) if encoded_credential is None and sys.platform == "win32": @@ -140,6 +138,7 @@ def _set_credential(service_name: str, account_name: str, encoded_credential: st def _delete_credential(service_name: str, account_name: str) -> None: """ Delete the credential from the keyring or storage. + If the keyring.delete_password function is not available, iterate through and delete credentials. Parameters @@ -148,12 +147,7 @@ def _delete_credential(service_name: str, account_name: str) -> None: The service name for the credential deletion account_name: str The account name for the credential deletion - - Returns - ------- - None """ - keyring.delete_password(service_name, account_name) # Handling multiple credentials stored when the operating system is Windows diff --git a/src/core/zowe/core_for_zowe_sdk/custom_warnings.py b/src/core/zowe/core_for_zowe_sdk/custom_warnings.py index 18146edf..23b14a73 100644 --- a/src/core/zowe/core_for_zowe_sdk/custom_warnings.py +++ b/src/core/zowe/core_for_zowe_sdk/custom_warnings.py @@ -1,30 +1,97 @@ +"""A public module for custom warnings.""" + + class ProfileNotFoundWarning(Warning): - def __init__(self, message): + """ + A warning that is raised when a user profile cannot be found. + + Parameters + ---------- + message : str + A string describing the warning. + """ + + def __init__(self, message: str): self.message = message - def __str__(self): + def __str__(self) -> str: + """Return a string representation of the warning message. + + Returns + ------- + str + a string representation of the warning message + """ return repr(self.message) class ProfileParsingWarning(Warning): - def __init__(self, message): + """ + A warning that is raised when there is an error while parsing a user profile. + + Parameters + ---------- + message : str + A human-readable string describing the warning. + """ + + def __init__(self, message: str): self.message = message - def __str__(self): + def __str__(self) -> str: + """Return a string representation of the warning message. + + Returns + ------- + str + a string representation of the warning message + """ return repr(self.message) class ConfigNotFoundWarning(Warning): - def __init__(self, message): + """ + A warning that is raised when a configuration file is not found. + + Parameters + ---------- + message : str + A human-readable string describing the warning. + """ + + def __init__(self, message: str): self.message = message - def __str__(self): + def __str__(self) -> str: + """Return a string representation of the warning message. + + Returns + ------- + str + a string representation of the warning message + """ return repr(self.message) class SecurePropsNotFoundWarning(Warning): - def __init__(self, message): + """ + A warning that is raised when secure properties are not found. + + Parameters + ---------- + message : str + A human-readable string describing the warning. + """ + + def __init__(self, message: str): self.message = message - def __str__(self): + def __str__(self) -> str: + """Return a string representation of the warning message. + + Returns + ------- + str + a string representation of the warning message + """ return repr(self.message) diff --git a/src/core/zowe/core_for_zowe_sdk/exceptions.py b/src/core/zowe/core_for_zowe_sdk/exceptions.py index 1c8ed618..fe80bc68 100644 --- a/src/core/zowe/core_for_zowe_sdk/exceptions.py +++ b/src/core/zowe/core_for_zowe_sdk/exceptions.py @@ -12,32 +12,34 @@ class InvalidRequestMethod(Exception): - """Class used to represent an invalid request method exception.""" - - def __init__(self, input_method): - """ - Parameters - ---------- - input_method: str - The invalid HTTP method used - """ + """ + Class used to represent an invalid request method exception. + + Parameters + ---------- + input_method: str + The invalid HTTP method used + """ + + def __init__(self, input_method: str): super().__init__("Invalid HTTP method input {}".format(input_method)) class UnexpectedStatus(Exception): - """Class used to represent an unexpected request response status exception.""" - - def __init__(self, expected, received, request_output): - """ - Parameters - ---------- - expected - The expected status code - received - The received status code - request_output - The output from the request - """ + """ + Class used to represent an unexpected request response status exception. + + Parameters + ---------- + expected: int + The expected status code + received: int + The received status code + request_output: int + The output from the request + """ + + def __init__(self, expected: int, received: int, request_output: int): super().__init__( "The status code from z/OSMF was: {}\nExpected: {}\nRequest output: {}".format( received, expected, request_output @@ -46,30 +48,32 @@ def __init__(self, expected, received, request_output): class RequestFailed(Exception): - """Class used to represent a request failure exception.""" - - def __init__(self, status_code, request_output): - """ - Parameters - ---------- - status_code - The status code from the failed request - request_output - The output from the request - """ + """ + Class used to represent a request failure exception. + + Parameters + ---------- + status_code: int + The status code from the failed request + request_output: str + The output from the request + """ + + def __init__(self, status_code: int, request_output: str): super().__init__("HTTP Request has failed with status code {}. \n {}".format(status_code, request_output)) class FileNotFound(Exception): - """Class used to represent a file not found exception.""" - - def __init__(self, input_path): - """ - Parameters - ---------- - input_path - The invalid input path - """ + """ + Class used to represent a file not found exception. + + Parameters + ---------- + input_path: str + The invalid input path + """ + + def __init__(self, input_path: str): super().__init__("The path {} provided is not a file.".format(input_path)) @@ -85,57 +89,60 @@ def __init__(self): class SecureProfileLoadFailed(Exception): - """Class used to represent a secure profile load failure exception.""" + """ + Class used to represent a secure profile load failure exception. + + Parameters + ---------- + profile_name: str + The name of the profile it failed to load + error_msg: str + The error message received while trying to load the profile + """ def __init__(self, profile_name: str = "unknown", error_msg: str = "error"): - """ - Parameters - ---------- - profile_name - The name of the profile it failed to load - error_msg - The error message received while trying to load the profile - """ super().__init__("Failed to load secure profile '{}' because '{}'".format(profile_name, error_msg)) class ProfileNotFound(Exception): - """Class used to represent a profile load failure exception.""" + """ + Class used to represent a profile load failure exception. - def __init__(self, profile_name: str = "unknown", error_msg: str = "error"): - """ - Parameters - ---------- - profile_name - The name of the profile it failed to load - error_msg - The error message received while trying to load the profile - """ + Parameters + ---------- + profile_name: str + The name of the profile it failed to load + error_msg: str + The error message received while trying to load the profile + """ + def __init__(self, profile_name: str = "unknown", error_msg: str = "error"): super().__init__("Failed to load profile '{}' because '{}'".format(profile_name, error_msg)) class SecureValuesNotFound(Exception): - """Class used to represent a profile load failure exception.""" + """ + Class used to represent a profile load failure exception. + + Parameters + ---------- + values: set + The list of secure values not found + """ def __init__(self, values: set): - """ - Parameters - ---------- - values - The list of secure values not found - """ super().__init__("Failed to load secure values: {}".format(str(values))) class UnsupportedAuthType(Exception): - """Class used to represent an unsupported authentication type exception.""" + """ + Class used to represent an unsupported authentication type exception. + + Parameters + ---------- + auth_type: str + The type of authentication on the session + """ def __init__(self, auth_type: str): - """ - Parameters - ---------- - auth_type - The type of authentication on the session - """ super().__init__("Unsupported authentication type: {}".format(auth_type)) diff --git a/src/core/zowe/core_for_zowe_sdk/logger.py b/src/core/zowe/core_for_zowe_sdk/logger.py index f7141c71..c239e9bd 100644 --- a/src/core/zowe/core_for_zowe_sdk/logger.py +++ b/src/core/zowe/core_for_zowe_sdk/logger.py @@ -1,12 +1,20 @@ +""" +Logger module for handling application logging. + +This module provides the `Log` class which allows for registering, +setting levels, opening, and closing loggers. +""" + import logging import os class Log: - """Class used to represent a logger + """ + Class used to represent a logger. Attributes - ------- + ---------- loggers: set The set of all loggers dirname: str @@ -28,27 +36,27 @@ class Log: file_handler.setFormatter( logging.Formatter("[%(asctime)s] [%(levelname)s] [%(name)s] - %(message)s", "%m/%d/%Y %I:%M:%S %p") ) - console_handler = logging.StreamHandler() + console_handler: logging.StreamHandler = logging.StreamHandler() file_output: bool = True console_output: bool = True - loggers = set() + loggers: set = set() @staticmethod - def registerLogger(name: str): + def registerLogger(name: str) -> logging.Logger: """ - Create and register a logger + Create and register a logger. Parameters ---------- name: str - name for the logger + The name for the logger. Returns ------- - Logger - A Logger object named after the file where it is created + logging.Logger + A Logger object named after the file where it is created. """ logger = logging.getLogger(name) if Log.console_output: @@ -62,7 +70,7 @@ def registerLogger(name: str): @staticmethod def setAllLoggerLevel(level: int): """ - Set display level for all loggers + Set display level for all loggers. Parameters ---------- @@ -75,25 +83,25 @@ def setAllLoggerLevel(level: int): handler.setLevel(level) @staticmethod - def close(logger): + def close(logger: logging.Logger): """ - Disable a logger + Disable a logger. Parameters ---------- - logger: Logger + logger: logging.Logger The logger to be turned off """ logger.disabled = True @staticmethod - def open(logger): + def open(logger: logging.Logger): """ - Enable a logger + Enable a logger. Parameters ---------- - logger: Logger + logger: logging.Logger The logger to be turned on """ logger.disabled = False diff --git a/src/core/zowe/core_for_zowe_sdk/profile_constants.py b/src/core/zowe/core_for_zowe_sdk/profile_constants.py index 85550476..b19b95fe 100644 --- a/src/core/zowe/core_for_zowe_sdk/profile_constants.py +++ b/src/core/zowe/core_for_zowe_sdk/profile_constants.py @@ -1,3 +1,15 @@ +"""Zowe Python Client SDK. + +This program and the accompanying materials are made available under the terms of the +Eclipse Public License v2.0 which accompanies this distribution, and is available at + +https://www.eclipse.org/legal/epl-v20.html + +SPDX-License-Identifier: EPL-2.0 + +Copyright Contributors to the Zowe Project. +""" + # Config Type USER_CONFIG = "user_config" TEAM_CONFIG = "team_config" 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 b017fb2f..df3e3248 100644 --- a/src/core/zowe/core_for_zowe_sdk/profile_manager.py +++ b/src/core/zowe/core_for_zowe_sdk/profile_manager.py @@ -46,12 +46,21 @@ class ProfileManager: """ + Class used to manage profiles. + Profile Manager contains the logic to merge the different properties of profiles (from the Project Config and the Project User Config as well as the Global Config and Global User Config). This class handles all the exceptions raised in the Config File to provide a smooth user experience. + + Parameters + ---------- + appname : Optional[str] + Name of the app + show_warnings : Optional[bool] + Indicates whether warnings are shown """ - def __init__(self, appname: str = "zowe", show_warnings: bool = True): + def __init__(self, appname: Optional[str] = "zowe", show_warnings: Optional[bool] = True): self.__appname = appname self.__show_warnings = show_warnings @@ -82,53 +91,106 @@ def __init__(self, appname: str = "zowe", show_warnings: bool = True): @property def config_appname(self) -> str: - """Returns the app name""" + """ + Return the application name. + + Returns + ------- + str + The name of the application as configured in the current instance. + """ return self.__appname @property def config_dir(self) -> Optional[str]: - """Returns the folder path to where the Zowe z/OSMF Team Project Config files are located.""" + """ + Return the folder path where the Zowe z/OSMF Team Project Config files are stored. + + Returns + ------- + Optional[str] + The directory path where the main project configuration files are located. This path can be None if not set. + """ return self.__project_config.location @config_dir.setter def config_dir(self, dirname: str) -> None: """ - Set directory/folder path to where Zowe z/OSMF Team Project Config files are located + Set the directory path for storing Zowe z/OSMF Team Project Config files. + + Parameters + ---------- + dirname : str + The directory path to set as the new location for the project configuration files. """ self.__project_config.location = dirname self.__project_user_config.location = dirname @property def user_config_dir(self) -> Optional[str]: - """Returns the folder path to where the Zowe z/OSMF User Project Config files are located.""" + """ + Return the folder path where the Zowe z/OSMF User Project Config files are stored. + + Returns + ------- + Optional[str] + The directory path where the user-specific project configuration files are located. + """ return self.__project_user_config.location @user_config_dir.setter def user_config_dir(self, dirname: str) -> None: - """Set directory/folder path to where Zowe z/OSMF User Project Config files are located""" + """ + Set the directory path for storing Zowe z/OSMF User Project Config files. + + Parameters + ---------- + dirname : str + The directory path to set as the new location for the user-specific project configuration files. + """ self.__project_user_config.location = dirname @property def config_filename(self) -> str: - """Return the filename for Zowe z/OSMF Team Project Config""" + """ + Return the filename for the Zowe z/OSMF Team Project Config. + + Returns + ------- + str + The filename of the main project configuration file. + """ return self.__project_config.filename @property def config_filepath(self) -> Optional[str]: - """Get the full Zowe z/OSMF Team Project Config filepath""" + """ + Get the full filepath for the Zowe z/OSMF Team Project Config file. + + Returns + ------- + Optional[str] + Filepath of configuration file or None if the location or filename is not set. + """ return self.__project_config.filepath @staticmethod - def get_env(cfg: ConfigFile, cwd=None) -> dict: + def get_env(cfg: ConfigFile, cwd: Optional[str] = None) -> dict: """ - Maps the env variables to the profile properties + Map the env variables to the profile properties. + + Parameters + ---------- + cfg : ConfigFile + A config file that contains the schema properties + cwd: Optional[str] + Path of current working diretory Returns ------- - Dictionary + dict Containing profile properties from env variables (prop: value) """ - props = cfg.schema_list(cwd) if props == []: return {} @@ -167,15 +229,38 @@ def get_profile( validate_schema: Optional[bool] = True, ) -> Profile: """ - Get just the profile from the config file (overriden with base props in the config file) + Retrieve a profile from the configuration file, optionally validating the schema. + + Parameters + ---------- + cfg : ConfigFile + The configuration file object which contains the profiles. + profile_name : Optional[str] + The name of the profile to retrieve. If None, the method attempts to fetch + the profile based only on the type. + profile_type : Optional[str] + The type of the profile to retrieve. If None, the method attempts to fetch the profile based only on the name. + validate_schema : Optional[bool] + Whether to validate the profile against the schema present in the configuration file. Returns ------- Profile - - NamedTuple (data, name, secure_props_not_found) + A NamedTuple containing the profile data, name, and any secure properties not found. + + Raises + ------ + ValidationError + If the instance is invalid under the provided schema. + SchemaError + If the provided schema itself is invalid. + UndefinedTypeCheck + If a type checker is asked to check a type it does not have registered. + UnknownType + If an unknown type is found in the schema. + FormatError + If validating a format in the configuration fails. """ - logger = Log.registerLogger(__name__) cfg_profile = Profile() @@ -236,12 +321,8 @@ def load( override_with_env: Optional[bool] = False, suppress_config_file_warnings: Optional[bool] = True, ) -> dict: - """Load connection details from a team config profile. - - Returns - ------- - dictionary - Object containing connection details + """ + Load connection details from a team config profile. We will load properties from config files in the following order, from highest to lowest priority: @@ -252,8 +333,34 @@ def load( If `profile_type` is not base, then we will load properties from both `profile_type` and base profiles and merge them together. - """ + Parameters + ---------- + profile_name : Optional[str] + The name of the profile to load. If None, profiles are loaded based only on profile type. + profile_type : Optional[str] + The type of the profile to load, e.g., 'zosmf', 'zftp'. If None, profiles are loaded based only on name. + check_missing_props : bool + Flag to indicate whether to check for missing secure properties. + validate_schema : Optional[bool] + Whether to validate the loaded profile against the schema defined in the configuration. + override_with_env : Optional[bool] + If True, overrides profile properties with values from environment variables. + suppress_config_file_warnings : Optional[bool] + Suppresses warnings from the configuration file loading process. + + Raises + ------ + ProfileNotFound + If both profile_name and profile_type are not provided, indicating which profile to load. + SecureValuesNotFound + If any secure properties are required but not found or loaded. + + Returns + ------- + dict + A dictionary containing the merged connection details from all relevant profiles. + """ 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( @@ -352,17 +459,23 @@ def load( def get_highest_priority_layer(self, json_path: str) -> Optional[ConfigFile]: """ - Get the highest priority layer (configuration file) based on the given profile name + Get the highest priority layer (configuration file) based on the given profile name. Parameters - ------- - profile_name: str - The name of the profile to look for in the layers. + ---------- + json_path: str + The path of the json. + + Raises + ------ + FileNotFoundError + File is not found in given path. Returns ------- Optional[ConfigFile] - The highest priority layer (configuration file) that contains the specified profile, or None if the profile is not found in any layer. + The highest priority layer (configuration file) that contains the specified profile, + or None if the profile is not found in any layer. """ highest_layer = None longest_match = "" @@ -400,20 +513,19 @@ def get_highest_priority_layer(self, json_path: str) -> Optional[ConfigFile]: return highest_layer - def set_property(self, json_path, value, secure=None) -> None: + def set_property(self, json_path: str, value: str, secure: Optional[bool] = None) -> None: """ Set a property in the profile, storing it securely if necessary. Parameters - ------- - json_path: str + ---------- + json_path : str The JSON path of the property to set. - value: str + value : str The value to be set for the property. - secure: bool + secure : Optional[bool] If True, the property will be stored securely. Default is None. """ - # highest priority layer for the given profile name highest_priority_layer = self.get_highest_priority_layer(json_path) @@ -423,10 +535,10 @@ def set_property(self, json_path, value, secure=None) -> None: def set_profile(self, profile_path: str, profile_data: dict) -> None: """ - Set a profile in the highest priority layer (configuration file) based on the given profile name + Set a profile in the highest priority layer (configuration file) based on the given profile name. Parameters - ------- + ---------- profile_path: str The path of the profile to be set. eg: profiles.zosmf profile_data: dict @@ -437,9 +549,7 @@ def set_profile(self, profile_path: str, profile_data: dict) -> None: highest_priority_layer.set_profile(profile_path, profile_data) def save(self) -> None: - """ - Save the layers (configuration files) to disk. - """ + """Save the layers (configuration files) to disk.""" layers = [self.__project_user_config, self.__project_config, self.__global_user_config, self.__global_config] for layer in layers: 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 b41d5818..f7e88838 100644 --- a/src/core/zowe/core_for_zowe_sdk/request_handler.py +++ b/src/core/zowe/core_for_zowe_sdk/request_handler.py @@ -10,6 +10,8 @@ Copyright Contributors to the Zowe Project. """ +from typing import Union + import requests import urllib3 @@ -21,26 +23,15 @@ class RequestHandler: """ Class used to handle HTTP/HTTPS requests. - Attributes + Parameters ---------- session_arguments: dict Zowe SDK session arguments - valid_methods: list - List of supported request methods + logger_name: str + The logger name of the modules calling request handler """ - def __init__(self, session_arguments, logger_name=__name__): - """ - Construct a RequestHandler object. - - Parameters - ---------- - session_arguments - The Zowe SDK session arguments - - logger_name - The logger name of the modules calling request handler - """ + def __init__(self, session_arguments: dict, logger_name: str = __name__): self.session = requests.Session() self.session_arguments = session_arguments self.__valid_methods = ["GET", "POST", "PUT", "DELETE"] @@ -52,7 +43,9 @@ def __handle_ssl_warnings(self): if not self.session_arguments["verify"]: urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) - def perform_request(self, method, request_arguments, expected_code=[200], stream=False): + def perform_request( + self, method: str, request_arguments: dict, expected_code: dict = [200], stream: bool = False + ) -> dict: """Execute an HTTP/HTTPS requests from given arguments and return validated response (JSON). Parameters @@ -61,14 +54,14 @@ def perform_request(self, method, request_arguments, expected_code=[200], stream 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 + expected_code: dict The list containing the acceptable response codes (default is [200]) - stream: boolean + stream: bool The boolean value whether the request is stream Returns ------- - normalized_response: json + dict normalized request response in json (dictionary) """ self.__method = method @@ -96,15 +89,22 @@ def __validate_method(self): self.__logger.error(f"Invalid HTTP method input {self.__method}") raise InvalidRequestMethod(self.__method) - def __send_request(self, stream=False): - """Build a custom session object, prepare it with a custom request and send it.""" + def __send_request(self, stream: bool = False): + """ + Build a custom session object, prepare it with a custom request and send it. + + Parameters + ---------- + stream : bool + Flag indicates whether it is a streaming requests. + """ session = self.session request_object = requests.Request(method=self.__method, **self.__request_arguments) prepared = session.prepare_request(request_object) self.__response = session.send(prepared, stream=stream, **self.session_arguments) def __del__(self): - """Clean up the REST session object once it is no longer needed anymore""" + """Clean up the REST session object once it is no longer needed anymore.""" self.session.close() def __validate_response(self): @@ -134,15 +134,17 @@ def __validate_response(self): ) raise RequestFailed(self.__response.status_code, output_str) - def __normalize_response(self): - """Normalize the response object to a JSON format. + def __normalize_response(self) -> Union[str, bytes, dict]: + """ + Normalize the response object to a JSON format. Returns ------- - Response object at the format based on Content-Type header: - - `bytes` when the response is binary data - - object when the response is JSON text - - `str` when the response is plain text + Union[str, bytes, dict] + Response object at the format based on Content-Type header: + - `bytes` when the response is binary data + - `str` when the response is plain text + - `dict` when the response is json """ contentType = self.__response.headers.get("Content-Type") if contentType == "application/octet-stream": 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 b3e1bd6d..1a2a8174 100644 --- a/src/core/zowe/core_for_zowe_sdk/sdk_api.py +++ b/src/core/zowe/core_for_zowe_sdk/sdk_api.py @@ -22,9 +22,18 @@ class SdkApi: """ Abstract class used to represent the base SDK API. + + Parameters + ---------- + profile : dict + Profile information in json (dict) format + default_url : str + Default url used for session + logger_name : str + Name of the logger (same as the filename by default) """ - def __init__(self, profile, default_url, logger_name=__name__): + def __init__(self, profile: dict, default_url: str, logger_name: str = __name__): session = Session(profile) self.session: ISession = session.load() @@ -58,26 +67,39 @@ def __init__(self, profile, default_url, logger_name=__name__): self.__session_arguments["cert"] = self.session.cert def __enter__(self): + """Return the SdkApi instance.""" return self def __exit__(self, exc_type, exception, traceback): + """Delete the request handler before exit.""" del self.request_handler - def _create_custom_request_arguments(self): - """Create a copy of the default request arguments dictionary. + def _create_custom_request_arguments(self) -> dict: + """ + Create a copy of the default request arguments dictionary. This method is required because the way that Python handles dictionary creation + + Returns + ------- + dict + A deepcopy of the request_arguments """ return copy.deepcopy(self._request_arguments) - def _encode_uri_component(self, str_to_adjust): - """Adjust string to be correct in a URL + def _encode_uri_component(self, str_to_adjust: str) -> str: + """ + Adjust string to be correct in a URL. + + Parameters + ---------- + str_to_adjust : str + The string to encode Returns ------- - adjusted_str + str A string with special characters, acceptable for a URL """ - return urllib.parse.quote(str_to_adjust, safe="!~*'()") if str_to_adjust is not None else None diff --git a/src/core/zowe/core_for_zowe_sdk/session.py b/src/core/zowe/core_for_zowe_sdk/session.py index 4c597f70..f3cff7ec 100644 --- a/src/core/zowe/core_for_zowe_sdk/session.py +++ b/src/core/zowe/core_for_zowe_sdk/session.py @@ -19,9 +19,7 @@ @dataclass class ISession: - """ - Class to represent session parameters - """ + """Class to represent session parameters.""" host: str port: int = session_constants.DEFAULT_HTTPS_PORT @@ -38,8 +36,17 @@ class ISession: class Session: """ - Class used to set connection details received from a ProfileManager or - manually set by passing and ISession object + Class used to set connection details received from a ProfileManager or manually set by passing and ISession object. + + Parameters + ---------- + props : dict + Profile and properties + + Raises + ------ + Exception + Exception thrown when cert key is not provided """ def __init__(self, props: dict) -> None: @@ -85,9 +92,25 @@ def __init__(self, props: dict) -> None: self.session.rejectUnauthorized = False if props.get("rejectUnauthorized") == False else True def load(self) -> ISession: + """ + Load a ISession object. + + Returns + ------- + ISession + A custom ISession object. + """ return self.session @property def host_url(self) -> str: + """ + Return the formatted host URL. + + Returns + ------- + str + the formatted host URL + """ basePath = self.session.basePath or "" return f"{self.session.protocol}://{self.session.host}:{self.session.port}{basePath}" diff --git a/src/core/zowe/core_for_zowe_sdk/validators.py b/src/core/zowe/core_for_zowe_sdk/validators.py index b7d1a97d..ab2f4b20 100644 --- a/src/core/zowe/core_for_zowe_sdk/validators.py +++ b/src/core/zowe/core_for_zowe_sdk/validators.py @@ -18,23 +18,19 @@ from jsonschema import validate -def validate_config_json(path_config_json: Union[str, dict], path_schema_json: str, cwd: str): +def validate_config_json(path_config_json: Union[str, dict], path_schema_json: str, cwd: str) -> None: """ - Function validating that zowe.config.json file matches zowe.schema.json. + Validate that zowe.config.json file matches zowe.schema.json. Parameters ---------- - path_config: str - Absolute path to zowe.config.json - - path_schema: str - Absolute path to zowe.schema.json - - Returns - ------- - Provides details if config.json doesn't match schema.json, otherwise it returns None. + path_config_json: Union[str, dict] + Absolute path to zowe.config.json + path_schema_json: str + Absolute path to zowe.schema.json + cwd: str + Path of the current working directory """ - # checks if the path_schema_json point to an internet URI and download the schema using the URI if path_schema_json.startswith("https://") or path_schema_json.startswith("http://"): schema_json = requests.get(path_schema_json).json() 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 31ded264..c940da6d 100644 --- a/src/core/zowe/core_for_zowe_sdk/zosmf_profile.py +++ b/src/core/zowe/core_for_zowe_sdk/zosmf_profile.py @@ -12,6 +12,7 @@ import base64 import os.path +from typing import Tuple import yaml @@ -37,36 +38,35 @@ class ZosmfProfile: and the user opted to use the profile instead of passing the credentials directly in the object constructor. - Attributes + Parameters ---------- profile_name: str Zowe z/OSMF profile name """ - def __init__(self, profile_name): - """ - Construct a ZosmfProfile object. - - Parameters - ---------- - profile_name - The name of the Zowe z/OSMF profile - """ + def __init__(self, profile_name: str): self.__profile_name = profile_name self.__logger = Log.registerLogger(__name__) @property - def profiles_dir(self): - """Return the os path for the Zowe z/OSMF profiles.""" + def profiles_dir(self) -> str: + """ + Return the os path for the Zowe z/OSMF profiles. + + Returns + ------- + str + the os path for the Zowe z/OSMF profiles + """ home_dir = os.path.expanduser("~") return os.path.join(home_dir, ".zowe", "profiles", "zosmf") - def load(self): + def load(self) -> ApiConnection: """Load z/OSMF connection details from a z/OSMF profile. Returns ------- - zosmf_connection + ApiConnection z/OSMF connection object """ profile_file = os.path.join(self.profiles_dir, "{}.yaml".format(self.__profile_name)) @@ -105,8 +105,20 @@ def __get_secure_value(self, name): return secret_value - def __load_secure_credentials(self): - """Load secure credentials for a z/OSMF profile.""" + def __load_secure_credentials(self) -> Tuple[str, str]: + """ + Load secure credentials for a z/OSMF profile. + + Raises + ------ + SecureProfileLoadFailed + Fail to load the profile. Cause would be provided. + + Returns + ------- + Tuple[str, str] + A tuple of username and password + """ if not HAS_KEYRING: self.__logger.error(f"{self.__profile_name} keyring module not installed") raise SecureProfileLoadFailed(self.__profile_name, "Keyring module not installed") 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 0bd5eb13..4deb850c 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 @@ -1,3 +1,7 @@ +"""Public module for class Console.""" + +from typing import Optional + from zowe.core_for_zowe_sdk import SdkApi @@ -5,38 +9,28 @@ class Console(SdkApi): """ Class used to represent the base z/OSMF Console API. - ... - - Attributes + Parameters ---------- - connection - Connection object + connection : dict + A profile in dict (json) format """ - def __init__(self, connection): - """ - Construct a Console object. - - Parameters - ---------- - connection - The connection object - """ + def __init__(self, connection: dict): super().__init__(connection, "/zosmf/restconsoles/consoles/defcn", logger_name=__name__) - def issue_command(self, command, console=None): + def issue_command(self, command: str, console: Optional[str] = None) -> dict: """Issues a command on z/OS Console. Parameters ---------- - command + command : str The z/OS command to be executed - console - The console that should be used to execute the command (default is None) + console : Optional[str] + Name of the console that should be used to execute the command (default is None) Returns ------- - json + dict A JSON containing the response from the console command """ custom_args = self._create_custom_request_arguments() @@ -46,19 +40,20 @@ def issue_command(self, command, console=None): response_json = self.request_handler.perform_request("PUT", custom_args) return response_json - def get_response(self, response_key, console=None): + def get_response(self, response_key: str, console: Optional[str] = None) -> dict: """ Collect outstanding synchronous z/OS Console response messages. Parameters ---------- - response_key + response_key : str The command response key from the Issue Command request. - console + console : Optional[str] The console that should be used to get the command response. + Returns ------- - json + dict A JSON containing the response to the command """ custom_args = self._create_custom_request_arguments() diff --git a/src/zos_files/zowe/zos_files_for_zowe_sdk/constants.py b/src/zos_files/zowe/zos_files_for_zowe_sdk/constants.py index 5f8268d6..51965f86 100644 --- a/src/zos_files/zowe/zos_files_for_zowe_sdk/constants.py +++ b/src/zos_files/zowe/zos_files_for_zowe_sdk/constants.py @@ -18,6 +18,8 @@ class FileType(Enum): + """Class used to represent type of files.""" + BINARY = "binary" EXECUTABLE = "executable" TEXT = "text" diff --git a/src/zos_files/zowe/zos_files_for_zowe_sdk/datasets.py b/src/zos_files/zowe/zos_files_for_zowe_sdk/datasets.py index 09fa8c84..d5551867 100644 --- a/src/zos_files/zowe/zos_files_for_zowe_sdk/datasets.py +++ b/src/zos_files/zowe/zos_files_for_zowe_sdk/datasets.py @@ -12,7 +12,7 @@ import os from dataclasses import dataclass -from typing import Optional +from typing import Dict, List, Optional from zowe.core_for_zowe_sdk import SdkApi from zowe.core_for_zowe_sdk.exceptions import FileNotFound @@ -23,41 +23,41 @@ @dataclass class DatasetOption: - """A dataclass that represents options for creating a dataset + """A dataclass that represents options for creating a dataset. - Attributes + Parameters ---------- - like: str + like : Optional[str] The dataset name to copy attributes from - volser: str + volser : Optional[str] The volume serial number that identifies where the dataset resides or should be allocated - unit: str + unit : Optional[str] Specifies the type of device on which the dataset is to be stored - dsorg: str + dsorg : Optional[str] Defines the organization of the dataset (PS for sequential, PO for partitioned, DA for direct access) - alcunit: str + alcunit : Optional[str] Specifies the unit of space allocation for the dataset (CYL for cylinders, TRK for tracks, BLK for blocks) - primary: int + primary : Optional[int] The amount of primary space to allocate for the dataset - secondary: int + secondary : Optional[int] The amount of secondary space to allocate if the primary space is exhausted - dirblk: int + dirblk : Optional[int] The number of directory blocks to allocate for a partitioned dataset - avgblk: int + avgblk : Optional[int] The average block size for the dataset - recfm: str + recfm : Optional[str] The format of the records in the dataset - blksize: int + blksize : Optional[int] The physical block size used for the dataset - lrecl: int + lrecl : Optional[int] The length of the logical records in the dataset - storclass: str + storclass : Optional[str] Specifies the storage class to be used for the dataset - mgnclass: str - Specifies the management class for the dataset - dataclass: str + mgmtclass : Optional[str] + Specifies the management class + dataclass : Optional[str] Specifies the data class for the dataset - dsntype: str + dsntype : Optional[str] Specifies the type of dataset """ @@ -76,7 +76,7 @@ def __init__( blksize: Optional[int] = None, lrecl: Optional[int] = None, storclass: Optional[str] = None, - mgntclass: Optional[str] = None, + mgmtclass: Optional[str] = None, dataclass: Optional[str] = None, dsntype: Optional[str] = None, ) -> None: @@ -93,42 +93,50 @@ def __init__( self.lrecl = lrecl self.blksize = blksize self.storclass = storclass - self.mgntclass = mgntclass + self.mgmtclass = mgmtclass self.dataclass = dataclass self.dsntype = dsntype @property def volser(self) -> Optional[str]: + """Get the volume serial number.""" return self.__volser @volser.setter def volser(self, volser: Optional[str]): + """Set the volume serial number.""" self.__volser = volser @property def unit(self) -> Optional[str]: + """Get the type of device.""" return self.__unit @unit.setter def unit(self, unit: Optional[str]): + """Set the type of device.""" self.__unit = unit @property def dsorg(self) -> Optional[str]: + """Get the organization of the dataset.""" return self.__dsorg @dsorg.setter def dsorg(self, dsorg: Optional[str]): + """Set the organization of the dataset.""" if dsorg not in ("PO", "PS", None): raise ValueError("'dsorg' must be 'PO', 'PS', or None") self.__dsorg = dsorg @property def alcunit(self) -> Optional[str]: + """Get the unit of space allocation.""" return self.__alcunit @alcunit.setter def alcunit(self, alcunit: Optional[str]): + """Set the unit of space allocation.""" if alcunit is None: if self.like is not None: self.__alcunit = None @@ -141,10 +149,12 @@ def alcunit(self, alcunit: Optional[str]): @property def primary(self) -> Optional[int]: + """Get the primary space allocation.""" return self.__primary @primary.setter def primary(self, primary: Optional[int]): + """Set the primary space allocation.""" if primary is not None: if primary > 16777215: raise ValueError("Given primary space allocation exceeds track limit of 16,777,215") @@ -152,10 +162,12 @@ def primary(self, primary: Optional[int]): @property def secondary(self) -> Optional[int]: + """Get the secondary space allocation.""" return self.__secondary @secondary.setter def secondary(self, secondary: Optional[int]): + """Set the secondary space allocation.""" if self.primary is not None: secondary = secondary if secondary is not None else int(self.primary / 10) if secondary > 16777215: @@ -164,26 +176,32 @@ def secondary(self, secondary: Optional[int]): @property def dirblk(self) -> Optional[int]: + """Get the number of directory blocks.""" return self.__dirblk @dirblk.setter def dirblk(self, dirblk: Optional[int]): + """Set the number of directory blocks.""" self.__dirblk = dirblk @property def avgblk(self) -> Optional[int]: + """Get the average block size.""" return self.__avgblk @avgblk.setter def avgblk(self, avgblk: Optional[int]): + """Set the average block size.""" self.__avgblk = avgblk @property def recfm(self) -> Optional[str]: + """Get the record format.""" return self.__recfm @recfm.setter def recfm(self, recfm: Optional[str]): + """Set the record format.""" if recfm is None: if self.like is not None: self.__recfm = None @@ -198,10 +216,12 @@ def recfm(self, recfm: Optional[str]): @property def blksize(self) -> Optional[int]: + """Get the physical block size.""" return self.__blksize @blksize.setter def blksize(self, blksize: Optional[int]): + """Set the physical block size.""" if blksize is None: if self.like is not None: self.__blksize = None @@ -213,96 +233,94 @@ def blksize(self, blksize: Optional[int]): @property def lrecl(self) -> Optional[int]: + """Get the length of logical records.""" return self.__lrecl @lrecl.setter def lrecl(self, lrecl: Optional[int]): + """Set the length of logical records.""" self.__lrecl = lrecl @property def storclass(self) -> Optional[str]: + """Get the storage class.""" return self.__storclass @storclass.setter def storclass(self, storclass: Optional[str]): + """Set the storage class.""" self.__storclass = storclass @property - def mgntclass(self) -> Optional[str]: - return self.__mgntclass + def mgmtclass(self) -> Optional[str]: + """Get the management class.""" + return self.__mgmtclass - @mgntclass.setter - def mgntclass(self, mgntclass: Optional[str]): - self.__mgntclass = mgntclass + @mgmtclass.setter + def mgmtclass(self, mgmtclass: Optional[str]): + """Set the management class.""" + self.__mgmtclass = mgmtclass @property def dataclass(self) -> Optional[str]: + """Get the data class.""" return self.__dataclass @dataclass.setter def dataclass(self, dataclass: Optional[str]): + """Set the data class.""" self.__dataclass = dataclass @property def dsntype(self) -> Optional[str]: + """Get the type of dataset.""" return self.__dsntype @dsntype.setter def dsntype(self, dsntype: Optional[str]): + """Set the type of dataset.""" self.__dsntype = dsntype @property def like(self) -> Optional[str]: + """Get the dataset name to copy attributes from.""" return self.__like def to_dict(self) -> dict: - """Return the DatasetOption as a dict - - Returns - ------- - dict - """ + """Return the DatasetOption as a dict.""" return {key.replace("_DatasetOption__", ""): value for key, value in self.__dict__.items() if value is not None} class Datasets(SdkApi): """ - Class used to represent the base z/OSMF Datasets API - which includes all operations related to datasets. + Class used to represent the base z/OSMF Datasets API. - Attributes + It includes all operations related to datasets. + + Parameters ---------- - connection - connection object + connection : dict + A profile for connection in dict (json) format """ - def __init__(self, connection): - """ - Construct a Datasets object. - - Parameters - ---------- - connection - The z/OSMF connection object (generated by the ZoweSDK object) - - Also update header to accept gzip encoded responses - """ + def __init__(self, connection: dict): super().__init__(connection, "/zosmf/restfiles/", logger_name=__name__) self._default_headers["Accept-Encoding"] = "gzip" - def list(self, name_pattern, return_attributes=False): - """Retrieve a list of datasets based on a given pattern. + def list(self, name_pattern: str, return_attributes: bool = False) -> List[Dict]: + """ + Retrieve a list of datasets based on a given pattern. Parameters ---------- name_pattern : str The pattern to match dataset names. - return_attributes : bool, optional + return_attributes : bool Whether to return dataset attributes along with the names. Defaults to False. Returns ------- - list of dict + List[Dict] A JSON with a list of dataset names (and attributes if specified) matching the given pattern. """ custom_args = self._create_custom_request_arguments() @@ -315,25 +333,33 @@ def list(self, name_pattern, return_attributes=False): response_json = self.request_handler.perform_request("GET", custom_args) return response_json - def list_members(self, dataset_name, member_pattern=None, member_start=None, limit=1000, attributes="member"): - """Retrieve the list of members on a given PDS/PDSE. + def list_members( + self, + dataset_name: str, + member_pattern: Optional[str] = None, + member_start: Optional[str] = None, + limit: int = 1000, + attributes: str = "member", + ) -> dict: + """ + Retrieve the list of members on a given PDS/PDSE. Parameters - ------- + ---------- dataset_name: str - The name of the dataset - member_pattern: str - Filters members by name pattern. - member_start: str - The starting point for listing members. + Name of the dataset + member_pattern: Optional[str] + Filters members by name pattern + member_start: Optional[str] + The starting point for listing members limit: int - The maximum number of members returned. + The maximum number of members returned attributes: str - The member attributes to retrieve. + The member attributes to retrieve Returns ------- - json + dict A JSON with a list of members from a given PDS/PDSE """ custom_args = self._create_custom_request_arguments() @@ -351,43 +377,47 @@ def list_members(self, dataset_name, member_pattern=None, member_start=None, lim def copy_data_set_or_member( self, - from_dataset_name, - to_dataset_name, - from_member_name=None, - volser=None, - alias=None, - to_member_name=None, - enq=None, - replace=False, - ): + from_dataset_name: str, + to_dataset_name: str, + from_member_name: Optional[str] = None, + volser: Optional[str] = None, + alias: Optional[bool] = None, + to_member_name: Optional[str] = None, + enq: Optional[str] = None, + replace: bool = False, + ) -> dict: """ Copy a dataset or member to another dataset or member. Parameters - ------- + ---------- from_dataset_name: str Name of the dataset to copy from to_dataset_name: str Name of the dataset to copy to - from_member_name: str + from_member_name: Optional[str] Name of the member to copy from - volser: str + volser: Optional[str] Volume serial number of the dataset to copy from - alias: bool + alias: Optional[bool] Alias of the dataset to copy from - to_member_name: str + to_member_name: Optional[str] Name of the member to copy to - enq: str + enq: Optional[str] Enqueue type for the dataset to copy from replace: bool - If true, members in the target data set are replaced. + If true, members in the target data set are replaced + + Raises + ------ + ValueError + Thrown when enq has an invalid value Returns ------- - json + dict A JSON containing the result of the operation """ - data = { "request": "copy", "from-dataset": {"dsn": from_dataset_name.strip(), "member": from_member_name}, @@ -412,7 +442,7 @@ def copy_data_set_or_member( response_json = self.request_handler.perform_request("PUT", custom_args, expected_code=[200]) return response_json - def create(self, dataset_name, options: Optional[DatasetOption] = None): + def create(self, dataset_name: str, options: Optional[DatasetOption] = None) -> dict: """ Create a sequential or partitioned dataset. @@ -420,12 +450,17 @@ def create(self, dataset_name, options: Optional[DatasetOption] = None): ---------- dataset_name: str Name of the dataset to be created - options: DatasetOption + options: Optional[DatasetOption] A DatasetOption class with property options of the dataset + Raises + ------ + ValueError + Thrown when a parameter has an invalid value + Returns ------- - json + dict A JSON containing the result of the operation """ if not options: @@ -458,9 +493,10 @@ def create(self, dataset_name, options: Optional[DatasetOption] = None): response_json = self.request_handler.perform_request("POST", custom_args, expected_code=[201]) return response_json - def create_default(self, dataset_name: str, default_type: str): + def create_default(self, dataset_name: str, default_type: str) -> dict: """ Create a dataset with default options set. + Default options depend on the requested type. Parameters @@ -470,12 +506,16 @@ def create_default(self, dataset_name: str, default_type: str): default_type: str The type of the dataset: "partitioned", "sequential", "classic", "c" or "binary" + Raises + ------ + ValueError + Thrown when a parameter is invalid + Returns ------- - json + dict A JSON containing the result of the operation """ - 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.") @@ -536,19 +576,20 @@ def create_default(self, dataset_name: str, default_type: str): response_json = self.request_handler.perform_request("POST", custom_args, expected_code=[201]) return response_json - def get_content(self, dataset_name, stream=False): - """Retrieve the contents of a given dataset. + def get_content(self, dataset_name: str, stream: bool = False) -> dict: + """ + Retrieve the contents of a given dataset. Parameters - ------- + ---------- dataset_name: str The name of the dataset stream: bool - Specifies + Specifies whether the response is streamed. Default: False Returns ------- - json + dict A JSON with the contents of a given dataset """ custom_args = self._create_custom_request_arguments() @@ -556,7 +597,7 @@ def get_content(self, dataset_name, stream=False): response_json = self.request_handler.perform_request("GET", custom_args, stream=stream) return response_json - def get_binary_content(self, dataset_name, stream=False, with_prefixes=False): + def get_binary_content(self, dataset_name: str, stream: bool = False, with_prefixes: bool = False) -> dict: """ Retrieve the contents of a given dataset as a binary bytes object. @@ -564,13 +605,15 @@ def get_binary_content(self, dataset_name, stream=False, with_prefixes=False): ---------- dataset_name: str Name of the dataset to retrieve - with_prefixes: boolean + stream: bool + Specifies whether the request is streaming + with_prefixes: bool If True include a 4 byte big endian record len prefix. Default: False Returns ------- - response - A response object from the requests library + dict + A JSON with the contents of a given dataset """ custom_args = self._create_custom_request_arguments() custom_args["url"] = "{}ds/{}".format(self._request_endpoint, self._encode_uri_component(dataset_name)) @@ -582,8 +625,9 @@ def get_binary_content(self, dataset_name, stream=False, with_prefixes=False): response = self.request_handler.perform_request("GET", custom_args, stream=stream) return response - def write(self, dataset_name, data, encoding=_ZOWE_FILES_DEFAULT_ENCODING): - """Write content to an existing dataset. + def write(self, dataset_name: str, data: str, encoding: str = _ZOWE_FILES_DEFAULT_ENCODING) -> dict: + """ + Write content to an existing dataset. Parameters ---------- @@ -591,12 +635,12 @@ def write(self, dataset_name, data, encoding=_ZOWE_FILES_DEFAULT_ENCODING): Name of the dataset to retrieve data: str Content to be written - encoding: - Specifies encoding schema + encoding: str + Specifies encoding name (e.g. IBM-1047) Returns ------- - json + dict A JSON containing the result of the operation """ custom_args = self._create_custom_request_arguments() @@ -606,11 +650,12 @@ def write(self, dataset_name, data, encoding=_ZOWE_FILES_DEFAULT_ENCODING): response_json = self.request_handler.perform_request("PUT", custom_args, expected_code=[204, 201]) return response_json - def download(self, dataset_name, output_file): - """Retrieve the contents of a dataset and saves it to a given file. + def download(self, dataset_name: str, output_file: str) -> None: + """ + Retrieve the contents of a dataset and saves it to a given file. Parameters - ------- + ---------- dataset_name: str Name of the dataset to be downloaded output_file: str @@ -621,36 +666,45 @@ def download(self, dataset_name, output_file): for chunk in response.iter_content(chunk_size=4096, decode_unicode=True): f.write(chunk) - def download_binary(self, dataset_name, output_file, with_prefixes=False): - """Retrieve the contents of a binary dataset and saves it to a given file. + def download_binary(self, dataset_name: str, output_file: str, with_prefixes: bool = False) -> None: + """ + Retrieve the contents of a binary dataset and saves it to a given file. Parameters ---------- - dataset_name:str + dataset_name : str Name of the dataset to download - output_file:str + output_file : str Name of the local file to create - with_prefixes:boolean - If true, include a four big endian bytes record length prefix. The default is False + with_prefixes : bool + If true, include a four big endian bytes record length prefix. Default: False """ response = self.get_binary_content(dataset_name, with_prefixes=with_prefixes, stream=True) with open(output_file, "wb") as f: for chunk in response.iter_content(chunk_size=4096): f.write(chunk) - def upload_file(self, input_file, dataset_name, encoding=_ZOWE_FILES_DEFAULT_ENCODING, binary=False): - """Upload contents of a given file and uploads it to a dataset. + def upload_file( + self, input_file: str, dataset_name: str, encoding: str = _ZOWE_FILES_DEFAULT_ENCODING, binary: bool = False + ) -> None: + """ + Upload contents of a given file and uploads it to a dataset. Parameters - ------- + ---------- input_file: str Name of the file to be uploaded dataset_name: str Name of the dataset to be created encoding: str - Specifies the encoding schema + Specifies the encoding name (e.g. IBM-1047) binary: bool specifies whether the file is binary + + Raises + ------ + FileNotFound + Thrown when a file is not found at provided location """ if os.path.isfile(input_file): if binary: @@ -663,24 +717,22 @@ def upload_file(self, input_file, dataset_name, encoding=_ZOWE_FILES_DEFAULT_ENC self.logger.error(f"File {input_file} not found.") raise FileNotFound(input_file) - def recall_migrated(self, dataset_name: str, wait=False): + def recall_migrated(self, dataset_name: str, wait: bool = False) -> dict: """ - Recalls a migrated data set. + Recall a migrated data set. Parameters ---------- dataset_name: str Name of the data set - wait: bool If true, the function waits for completion of the request, otherwise the request is queued Returns ------- - json + dict A JSON containing the result of the operation """ - data = {"request": "hrecall", "wait": wait} custom_args = self._create_custom_request_arguments() @@ -690,27 +742,24 @@ def recall_migrated(self, dataset_name: str, wait=False): response_json = self.request_handler.perform_request("PUT", custom_args, expected_code=[200]) return response_json - def delete_migrated(self, dataset_name: str, purge=False, wait=False): + def delete_migrated(self, dataset_name: str, purge: bool = False, wait: bool = False) -> dict: """ - Deletes migrated data set. + Delete migrated data set. Parameters ---------- dataset_name: str Name of the data set - purge: bool If true, the function uses the PURGE=YES on ARCHDEL request, otherwise it uses the PURGE=NO. - wait: bool If true, the function waits for completion of the request, otherwise the request is queued. Returns ------- - json + dict A JSON containing the result of the operation """ - data = { "request": "hdelete", "purge": purge, @@ -724,24 +773,22 @@ def delete_migrated(self, dataset_name: str, purge=False, wait=False): response_json = self.request_handler.perform_request("PUT", custom_args, expected_code=[200]) return response_json - def migrate(self, dataset_name: str, wait=False): + def migrate(self, dataset_name: str, wait: bool = False) -> dict: """ - Migrates the data set. + Migrate the data set. Parameters ---------- dataset_name: str Name of the data set - wait: bool If true, the function waits for completion of the request, otherwise the request is queued. Returns ------- - json + dict A JSON containing the result of the operation """ - data = {"request": "hmigrate", "wait": wait} custom_args = self._create_custom_request_arguments() @@ -751,9 +798,9 @@ def migrate(self, dataset_name: str, wait=False): response_json = self.request_handler.perform_request("PUT", custom_args, expected_code=[200]) return response_json - def rename(self, before_dataset_name: str, after_dataset_name: str): + def rename(self, before_dataset_name: str, after_dataset_name: str) -> dict: """ - Renames the data set. + Rename the data set. Parameters ---------- @@ -765,10 +812,9 @@ def rename(self, before_dataset_name: str, after_dataset_name: str): Returns ------- - json + dict A JSON containing the result of the operation """ - data = {"request": "rename", "from-dataset": {"dsn": before_dataset_name.strip()}} custom_args = self._create_custom_request_arguments() @@ -780,30 +826,31 @@ def rename(self, before_dataset_name: str, after_dataset_name: str): response_json = self.request_handler.perform_request("PUT", custom_args, expected_code=[200]) return response_json - def rename_member(self, dataset_name: str, before_member_name: str, after_member_name: str, enq=""): + def rename_member(self, dataset_name: str, before_member_name: str, after_member_name: str, enq: str = "") -> dict: """ - Renames the data set member. + Rename the data set member. Parameters ---------- dataset_name: str Name of the data set. - before_member_name: str The source member name. - after_member_name: str New name for the source member. - enq: str Values can be SHRW or EXCLU. SHRW is the default for PDS members, EXCLU otherwise. + Raises + ------ + ValueError + Thrown when a parameter is invalid + Returns ------- - json + dict A JSON containing the result of the operation """ - data = { "request": "rename", "from-dataset": { @@ -828,21 +875,22 @@ def rename_member(self, dataset_name: str, before_member_name: str, after_member response_json = self.request_handler.perform_request("PUT", custom_args, expected_code=[200]) return response_json - def delete(self, dataset_name, volume=None, member_name=None): - """Deletes a sequential or partitioned data. + def delete(self, dataset_name: str, volume: Optional[str] = None, member_name: Optional[str] = None) -> dict: + """ + Delete a sequential or partitioned data. Parameters ---------- dataset_name: str The name of the dataset - volume: str + volume: Optional[str] The optional volume serial number - member_name: str + member_name: Optional[str] The name of the member to be deleted Returns ------- - json + dict A JSON containing the result of the operation """ custom_args = self._create_custom_request_arguments() @@ -856,8 +904,13 @@ def delete(self, dataset_name, volume=None, member_name=None): return response_json def copy_uss_to_data_set( - self, from_filename, to_dataset_name, to_member_name=None, type=FileType.TEXT, replace=False - ): + self, + from_filename: str, + to_dataset_name: str, + to_member_name: Optional[str] = None, + type: str = FileType.TEXT, + replace: bool = False, + ) -> dict: """ Copy a USS file to dataset. @@ -867,19 +920,18 @@ def copy_uss_to_data_set( Name of the file to copy from. to_dataset_name: str Name of the dataset to copy to. - to_member_name: str + to_member_name: Optional[str] Name of the member to copy to. - type: FileType, optional + type: str Type of the file to copy from. Default is FileType.TEXT. - replace: bool, optional + replace: bool If true, members in the target dataset are replaced. Returns ------- - json + dict A JSON containing the result of the operation. """ - data = { "request": "copy", "from-file": {"filename": from_filename.strip(), "type": type.value}, diff --git a/src/zos_files/zowe/zos_files_for_zowe_sdk/exceptions.py b/src/zos_files/zowe/zos_files_for_zowe_sdk/exceptions.py index f079c0f5..936965a5 100644 --- a/src/zos_files/zowe/zos_files_for_zowe_sdk/exceptions.py +++ b/src/zos_files/zowe/zos_files_for_zowe_sdk/exceptions.py @@ -14,15 +14,16 @@ class InvalidPermsOption(Exception): - """Class used to represent an invalid permission value.""" + """ + Class used to represent an invalid permission value. + + Parameters + ---------- + value: int + The value of the permission option + """ def __init__(self, value: int): - """ - Parameters - ---------- - value - The value of the permission option - """ super().__init__("Invalid zos-files create command 'perms' option: {}".format(value)) diff --git a/src/zos_files/zowe/zos_files_for_zowe_sdk/file_system.py b/src/zos_files/zowe/zos_files_for_zowe_sdk/file_system.py index adfc4c54..f0a8aaa9 100644 --- a/src/zos_files/zowe/zos_files_for_zowe_sdk/file_system.py +++ b/src/zos_files/zowe/zos_files_for_zowe_sdk/file_system.py @@ -10,6 +10,8 @@ Copyright Contributors to the Zowe Project. """ +from typing import Optional + from zowe.core_for_zowe_sdk import SdkApi from zowe.zos_files_for_zowe_sdk import constants, exceptions @@ -18,43 +20,41 @@ class FileSystems(SdkApi): """ - Class used to represent the base z/OSMF FileSystems API - which includes all operations related to file systems. + Class used to represent the base z/OSMF FileSystems API. + + It includes all operations related to file systems. - Attributes + Parameters ---------- - connection - connection object + connection : dict + A profile for connection in dict (json) format """ - def __init__(self, connection): - """ - Construct a FileSystems object. - - Parameters - ---------- - connection - The z/OSMF connection object (generated by the ZoweSDK object) - - Also update header to accept gzip encoded responses - """ + def __init__(self, connection: dict): super().__init__(connection, "/zosmf/restfiles/", logger_name=__name__) self._default_headers["Accept-Encoding"] = "gzip" - def create(self, file_system_name, options={}): + def create(self, file_system_name: str, options: dict = {}) -> dict: """ Create a z/OS UNIX zFS Filesystem. Parameters - --------- + ---------- file_system_name: str - the name for the file system + Name of the file system options: dict - specifies file system attributes + Specifies file system attributes + + Raises + ------ + MaxAllocationQuantityExceeded + Thrown when file system exceeds max allocation quantity + InvalidPermsOption + Thrown when invalid permission option is provided Returns ------- - json + dict A JSON containing the result of the operation """ for key, value in options.items(): @@ -74,18 +74,18 @@ def create(self, file_system_name, options={}): response_json = self.request_handler.perform_request("POST", custom_args, expected_code=[201]) return response_json - def delete(self, file_system_name): + def delete(self, file_system_name: str) -> dict: """ - Deletes a zFS Filesystem + Delete a zFS Filesystem. Parameters - ------- + ---------- file_system_name: str Name of the file system Returns ------- - json + dict A JSON containing the result of the operation """ custom_args = self._create_custom_request_arguments() @@ -93,11 +93,14 @@ def delete(self, file_system_name): response_json = self.request_handler.perform_request("DELETE", custom_args, expected_code=[204]) return response_json - def mount(self, file_system_name, mount_point, options={}, encoding=_ZOWE_FILES_DEFAULT_ENCODING): - """Mounts a z/OS UNIX file system on a specified directory. + def mount( + self, file_system_name: str, mount_point: str, options: dict = {}, encoding: str = _ZOWE_FILES_DEFAULT_ENCODING + ) -> dict: + """ + Mount a z/OS UNIX file system on a specified directory. Parameters - --------- + ---------- file_system_name: str Name for the file system mount_point: str @@ -105,11 +108,11 @@ def mount(self, file_system_name, mount_point, options={}, encoding=_ZOWE_FILES_ options: dict A JSON of request body options encoding: str - Specifies optional encoding schema + Specifies optional encoding name (e.g. IBM-1047) Returns ------- - json + dict A JSON containing the result of the operation """ options["action"] = "mount" @@ -121,21 +124,22 @@ def mount(self, file_system_name, mount_point, options={}, encoding=_ZOWE_FILES_ response_json = self.request_handler.perform_request("PUT", custom_args, expected_code=[204]) return response_json - def unmount(self, file_system_name, options={}, encoding=_ZOWE_FILES_DEFAULT_ENCODING): - """Unmounts a z/OS UNIX file system on a specified directory. + def unmount(self, file_system_name: str, options: dict = {}, encoding: str = _ZOWE_FILES_DEFAULT_ENCODING) -> dict: + """ + Unmount a z/OS UNIX file system on a specified directory. Parameters - --------- + ---------- file_system_name: str Name for the file system options: dict A JSON of request body options encoding: str - Specifies optional encoding schema + Specifies optional encoding name (e.g. IBM-1047) Returns ------- - json + dict A JSON containing the result of the operation """ options["action"] = "unmount" @@ -146,21 +150,23 @@ def unmount(self, file_system_name, options={}, encoding=_ZOWE_FILES_DEFAULT_ENC response_json = self.request_handler.perform_request("PUT", custom_args, expected_code=[204]) return response_json - def list(self, file_path_name=None, file_system_name=None): + def list(self, file_path_name: Optional[str] = None, file_system_name: Optional[str] = None) -> dict: """ - list all mounted filesystems, or the specific filesystem mounted at a given path, or the + List all mounted filesystems. + + It could also list the specific filesystem mounted at a given path, or the filesystem with a given Filesystem name. Parameters - --------- - file_path: str - the UNIX directory that contains the files and directories to be listed. - file_system_name: str - the name for the file system to be listed + ---------- + file_path_name: Optional[str] + USS directory that contains the files and directories to be listed + file_system_name: Optional[str] + Name of the file system to be listed Returns ------- - json + dict A JSON containing the result of the operation """ custom_args = self._create_custom_request_arguments() 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 13c2f539..d91bf07a 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 @@ -28,26 +28,24 @@ class Files(SdkApi): Attributes ---------- - connection - connection object - """ - ds: Datasets + A Datasets class object uss: USSFiles + An USSFiles class object fs: FileSystems + A FileSystems class object - def __init__(self, connection): - """ - Construct a Files object, a composition of a Datasets object, - a USSFiles object, and a FileSystems object + Parameters + ---------- + connection: dict + The z/OSMF connection object (generated by the ZoweSDK object) + """ - Parameters - ---------- - connection - The z/OSMF connection object (generated by the ZoweSDK object) + ds: Datasets + uss: USSFiles + fs: FileSystems - Also update header to accept gzip encoded responses - """ + def __init__(self, connection: dict): super().__init__(connection, "/zosmf/restfiles/", logger_name=__name__) self._default_headers["Accept-Encoding"] = "gzip" self.ds = Datasets(connection) @@ -55,33 +53,33 @@ def __init__(self, connection): self.fs = FileSystems(connection) def list_files(self, path): - """Deprecated function. Please use uss.list() instead""" + """Use uss.list() instead of this deprecated function.""" return self.uss.list(path) def get_file_content_streamed(self, file_path, binary=False): - """Deprecated function. Please use uss.get_content_streamed() instead""" + """Use uss.get_content_streamed() instead of this deprecated function.""" return self.uss.get_content_streamed(file_path, binary) def get_file_content(self, filepath_name): - """Deprecated function. Please use uss.get_content() instead""" + """Use uss.get_content() instead of this deprecated function.""" return self.uss.get_content(filepath_name) def delete_uss(self, filepath_name, recursive=False): - """Deprecated function. Please use uss.delete() instead""" + """Use uss.delete() instead of this deprecated function.""" return self.uss.delete(filepath_name, recursive) def list_dsn(self, name_pattern, return_attributes=False): - """Deprecated function. Please use ds.list() instead""" + """Use ds.list() instead of this deprecated function.""" return self.ds.list(name_pattern, return_attributes) def list_dsn_members(self, dataset_name, member_pattern=None, member_start=None, limit=1000, attributes="member"): - """Deprecated function. Please use ds.list_members() instead""" + """Use ds.list_members() instead of this deprecated function.""" return self.ds.list_members(dataset_name, member_pattern, member_start, limit, attributes) def copy_uss_to_data_set( self, from_filename, to_dataset_name, to_member_name=None, type=FileType.TEXT, replace=False ): - """Deprecated function. Please use ds.copy_uss_to_data_set instead""" + """Use ds.copy_uss_to_data_set() instead of this deprecated function.""" return self.ds.copy_uss_to_data_set(from_filename, to_dataset_name, to_member_name, type, replace) def copy_data_set_or_member( @@ -95,107 +93,107 @@ def copy_data_set_or_member( enq=None, replace=False, ): - """Deprecated function. Please use ds.copy_data_set_or_member() instead""" + """Use ds.copy_data_set_or_member() instead of this deprecated function.""" return self.ds.copy_data_set_or_member( from_dataset_name, to_dataset_name, from_member_name, volser, alias, to_member_name, enq, replace ) def get_dsn_content(self, dataset_name): - """Deprecated function. Please use ds.get_content() instead""" + """Use ds.get_content() instead of this deprecated function.""" return self.ds.get_content(dataset_name) def create_data_set(self, dataset_name, options: Optional[DatasetOption] = None): - """Deprecated function. Please use ds.create() instead""" + """Use ds.create() instead of this deprecated function.""" return self.ds.create(dataset_name, options) def create_default_data_set(self, dataset_name: str, default_type: str): - """Deprecated function. Please use ds.create_default() instead""" + """Use ds.create_default() instead of this deprecated function.""" return self.ds.create_default(dataset_name, default_type) def create_uss(self, file_path, type, mode=None): - """Deprecated function. Please use uss.create() instead""" + """Use uss.create() instead of this deprecated function.""" return self.uss.create(file_path, type, mode) def get_dsn_content_streamed(self, dataset_name): - """Deprecated function. Please use ds.get_content() instead""" + """Use ds.get_content() instead of this deprecated function.""" return self.ds.get_content(dataset_name, stream=True) def get_dsn_binary_content(self, dataset_name, with_prefixes=False): - """Deprecated function. Please use ds.get_binary_content() instead""" + """Use ds.get_binary_content() instead of this deprecated function.""" return self.ds.get_binary_content(dataset_name, with_prefixes) def get_dsn_binary_content_streamed(self, dataset_name, with_prefixes=False): - """Deprecated function. Please use ds.get_binary_content() instead""" + """Use ds.get_binary_content() instead of this deprecated function.""" return self.ds.get_binary_content(dataset_name, stream=True, with_prefixes=with_prefixes) def write_to_dsn(self, dataset_name, data, encoding=_ZOWE_FILES_DEFAULT_ENCODING): - """Deprecated function. Please use ds.write() instead""" + """Use ds.write() instead of this deprecated function.""" return self.ds.write(dataset_name, data, encoding) def download_dsn(self, dataset_name, output_file): - """Deprecated function. Please use ds.download() instead""" + """Use ds.download() instead of this deprecated function.""" self.ds.download(dataset_name, output_file) def download_binary_dsn(self, dataset_name, output_file, with_prefixes=False): - """Deprecated function. Please use ds.download_binary() instead""" + """Use ds.download_binary() instead of this deprecated function.""" self.ds.download_binary(dataset_name, output_file, with_prefixes) def upload_file_to_dsn(self, input_file, dataset_name, encoding=_ZOWE_FILES_DEFAULT_ENCODING, binary=False): - """Deprecated function. Please use ds.upload_file() instead""" + """Use ds.upload_file() instead of this deprecated function.""" self.ds.upload_file(input_file, dataset_name, encoding, binary) def write_to_uss(self, filepath_name, data, encoding=_ZOWE_FILES_DEFAULT_ENCODING): - """Deprecated function. Please use uss.write() instead""" + """Use uss.write() instead of this deprecated function.""" return self.uss.write(filepath_name, data, encoding) def upload_file_to_uss(self, input_file, filepath_name, encoding=_ZOWE_FILES_DEFAULT_ENCODING): - """Deprecated function. Please use uss.upload() instead""" + """Use uss.upload() instead of this deprecated function.""" self.uss.upload(input_file, filepath_name, encoding) def download_uss(self, file_path, output_file, binary=False): - """Deprecated function. Please use uss.download() instead""" + """Use uss.download() instead of this deprecated function.""" self.uss.download(file_path, output_file, binary) def delete_data_set(self, dataset_name, volume=None, member_name=None): - """Deprecated function. Please use ds.delete() instead""" + """Use ds.delete() instead of this deprecated function.""" return self.ds.delete(dataset_name, volume, member_name) def create_zFS_file_system(self, file_system_name, options={}): - """Deprecated function. Please use fs.create() instead""" + """Use fs.create() instead of this deprecated function.""" return self.fs.create(file_system_name, options) def delete_zFS_file_system(self, file_system_name): - """Deprecated function. Please use fs.delete() instead""" + """Use fs.delete() instead of this deprecated function.""" return self.fs.delete(file_system_name) def mount_file_system(self, file_system_name, mount_point, options={}, encoding=_ZOWE_FILES_DEFAULT_ENCODING): - """Deprecated function. Please use fs.mount() instead""" + """Use fs.mount() instead of this deprecated function.""" return self.fs.mount(file_system_name, mount_point, options, encoding) def unmount_file_system(self, file_system_name, options={}, encoding=_ZOWE_FILES_DEFAULT_ENCODING): - """Deprecated function. Please use fs.unmount() instead""" + """Use fs.unmount() instead of this deprecated function.""" return self.fs.unmount(file_system_name, options, encoding) def list_unix_file_systems(self, file_path_name=None, file_system_name=None): - """Deprecated function. Please use fs.list() instead""" + """Use fs.list() instead of this deprecated function.""" return self.fs.list(file_path_name, file_system_name) def recall_migrated_data_set(self, dataset_name: str, wait=False): - """Deprecated function. Please use ds.recall_migrated() instead""" + """Use ds.recall_migrated() instead of this deprecated function.""" return self.ds.recall_migrated(dataset_name, wait) def delete_migrated_data_set(self, dataset_name: str, purge=False, wait=False): - """Deprecated function. Please use ds.delete_migrated() instead""" + """Use ds.delete_migrated() instead of this deprecated function.""" return self.ds.delete_migrated(dataset_name, purge, wait) def migrate_data_set(self, dataset_name: str, wait=False): - """Deprecated function. Please use ds.migrate() instead""" + """Use ds.migrate() instead of this deprecated function.""" return self.ds.migrate(dataset_name, wait) def rename_data_set(self, before_dataset_name: str, after_dataset_name: str): - """Deprecated function. Please use ds.rename() instead""" + """Use ds.rename() instead of this deprecated function.""" return self.ds.rename(before_dataset_name, after_dataset_name) def rename_data_set_member(self, dataset_name: str, before_member_name: str, after_member_name: str, enq=""): - """Deprecated function. Please use ds.rename_member() instead""" + """Use ds.rename_member() instead of this deprecated function.""" return self.ds.rename_member(dataset_name, before_member_name, after_member_name, enq) diff --git a/src/zos_files/zowe/zos_files_for_zowe_sdk/uss.py b/src/zos_files/zowe/zos_files_for_zowe_sdk/uss.py index 2f3670bf..0eb72258 100644 --- a/src/zos_files/zowe/zos_files_for_zowe_sdk/uss.py +++ b/src/zos_files/zowe/zos_files_for_zowe_sdk/uss.py @@ -11,6 +11,7 @@ """ import os +from typing import Optional from zowe.core_for_zowe_sdk import SdkApi from zowe.core_for_zowe_sdk.exceptions import FileNotFound @@ -21,40 +22,32 @@ class USSFiles(SdkApi): """ - Class used to represent the base z/OSMF USSFiles API - which includes all operations related to USS files. + Class used to represent the base z/OSMF USSFiles API. - Attributes + It includes all operations related to USS files. + + Parameters ---------- - connection - connection object + connection: dict + The z/OSMF connection object (generated by the ZoweSDK object) """ - def __init__(self, connection): - """ - Construct a USSFiles object. - - Parameters - ---------- - connection - The z/OSMF connection object (generated by the ZoweSDK object) - - Also update header to accept gzip encoded responses - """ + def __init__(self, connection: dict): super().__init__(connection, "/zosmf/restfiles/", logger_name=__name__) self._default_headers["Accept-Encoding"] = "gzip" - def list(self, path): - """Retrieve a list of USS files based on a given pattern. + def list(self, path: str) -> dict: + """ + Retrieve a list of USS files based on a given pattern. Parameters - ------- + ---------- path: str Path to retrieve the list Returns ------- - json + dict A JSON with a list of dataset names matching the given pattern """ custom_args = self._create_custom_request_arguments() @@ -63,21 +56,20 @@ def list(self, path): response_json = self.request_handler.perform_request("GET", custom_args) return response_json - def delete(self, filepath_name, recursive=False): + def delete(self, filepath_name: str, recursive: bool = False) -> dict: """ - Delete a file or directory + Delete a file or directory. Parameters ---------- filepath_name: str - filepath of the file to be deleted - + Path of the file to be deleted recursive: bool If specified as True, all the files and sub-directories will be deleted. Returns ------- - json + dict A JSON containing the operation results """ custom_args = self._create_custom_request_arguments() @@ -88,25 +80,24 @@ def delete(self, filepath_name, recursive=False): response_json = self.request_handler.perform_request("DELETE", custom_args, expected_code=[204]) return response_json - def create(self, file_path, type, mode=None): + def create(self, file_path: str, type: str, mode: Optional[str] = None) -> dict: """ - Add a file or directory + Add a file or directory. Parameters ---------- file_path: str - file_path of the file to add + Path of the file to add type: str - "file" or "dir" - mode: str - Ex:- rwxr-xr-x + Specify either "file" or "dir" + mode: Optional[str] + Unix permission string (e.g. `rwxr-xr-x`) Returns ------- - json + dict A JSON containing the operation results """ - data = {"type": type, "mode": mode} custom_args = self._create_custom_request_arguments() @@ -115,8 +106,9 @@ def create(self, file_path, type, mode=None): response_json = self.request_handler.perform_request("POST", custom_args, expected_code=[201]) return response_json - def write(self, filepath_name, data, encoding=_ZOWE_FILES_DEFAULT_ENCODING): - """Write content to an existing UNIX file. + def write(self, filepath_name: str, data: str, encoding: str = _ZOWE_FILES_DEFAULT_ENCODING) -> dict: + """ + Write content to an existing UNIX file. Parameters ---------- @@ -125,11 +117,11 @@ def write(self, filepath_name, data, encoding=_ZOWE_FILES_DEFAULT_ENCODING): data: str Contents to be written encoding: str - Specifies the encoding schema + Specifies the encoding name (e.g. IBM-1047) Returns ------- - json + dict A JSON containing the result of the operation """ custom_args = self._create_custom_request_arguments() @@ -139,8 +131,9 @@ def write(self, filepath_name, data, encoding=_ZOWE_FILES_DEFAULT_ENCODING): response_json = self.request_handler.perform_request("PUT", custom_args, expected_code=[204, 201]) return response_json - def get_content(self, filepath_name): - """Retrieve the content of a filename. The complete path must be specified. + def get_content(self, filepath_name: str) -> dict: + """ + Retrieve the content of a filename. The complete path must be specified. Parameters ---------- @@ -149,7 +142,7 @@ def get_content(self, filepath_name): Returns ------- - json + dict A JSON with the contents of the specified USS file """ custom_args = self._create_custom_request_arguments() @@ -157,8 +150,9 @@ def get_content(self, filepath_name): response_json = self.request_handler.perform_request("GET", custom_args) return response_json - def get_content_streamed(self, file_path, binary=False): - """Retrieve the contents of a given USS file streamed. + def get_content_streamed(self, file_path: str, binary: bool = False) -> dict: + """ + Retrieve the contents of a given USS file streamed. Parameters ---------- @@ -169,8 +163,8 @@ def get_content_streamed(self, file_path, binary=False): Returns ------- - response - A response object from the requests library + dict + A JSON response with results of the operation """ custom_args = self._create_custom_request_arguments() custom_args["url"] = "{}fs/{}".format(self._request_endpoint, self._encode_uri_component(file_path.lstrip("/"))) @@ -179,11 +173,12 @@ def get_content_streamed(self, file_path, binary=False): response = self.request_handler.perform_request("GET", custom_args, stream=True) return response - def download(self, file_path, output_file, binary=False): - """Retrieve the contents of a USS file and saves it to a local file. + def download(self, file_path: str, output_file: str, binary: bool = False): + """ + Retrieve the contents of a USS file and saves it to a local file. Parameters - ------- + ---------- file_path: str Path of the file to be downloaded output_file: str @@ -196,17 +191,23 @@ def download(self, file_path, output_file, binary=False): for chunk in response.iter_content(chunk_size=4096, decode_unicode=not binary): f.write(chunk) - def upload(self, input_file, filepath_name, encoding=_ZOWE_FILES_DEFAULT_ENCODING): - """Upload contents of a given file and uploads it to UNIX file + def upload(self, input_file: str, filepath_name: str, encoding: str = _ZOWE_FILES_DEFAULT_ENCODING): + """ + Upload contents of a given file and saves it to a file at the given USS path. Parameters - ------- + ---------- input_file: str Name of the file to be uploaded filepath_name: str Path of the file where it will be created encoding: str Specifies encoding schema + + Raises + ------ + FileNotFound + Thrown when specific file is not found. """ if os.path.isfile(input_file): with open(input_file, "r", encoding="utf-8") as in_file: 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 3a7d698e..8c6ebe16 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 @@ -11,6 +11,7 @@ """ import os +from typing import Optional from zowe.core_for_zowe_sdk import SdkApi @@ -19,25 +20,20 @@ class Jobs(SdkApi): """ Class used to represent the base z/OSMF Jobs API. - Attributes + It includes all operations related to datasets. + + Parameters ---------- - connection - Connection object + connection : dict + A profile for connection in dict (json) format """ - def __init__(self, connection): - """ - Construct a Jobs object. - - Parameters - ---------- - connection - The connection object - """ + def __init__(self, connection: dict): 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. + def get_job_status(self, jobname: str, jobid: str) -> dict: + """ + Retrieve the status of a given job on JES. Parameters ---------- @@ -48,7 +44,7 @@ def get_job_status(self, jobname, jobid): Returns ------- - response_json + dict A JSON object containing the status of the job on JES """ custom_args = self._create_custom_request_arguments() @@ -58,8 +54,9 @@ def get_job_status(self, jobname, jobid): response_json = self.request_handler.perform_request("GET", custom_args) return response_json - def cancel_job(self, jobname: str, jobid: str, modify_version="2.0"): - """Cancels the a job + def cancel_job(self, jobname: str, jobid: str, modify_version: str = "2.0") -> dict: + """ + Cancel a job. Parameters ---------- @@ -70,9 +67,14 @@ def cancel_job(self, jobname: str, jobid: str, modify_version="2.0"): modify_version: str Default ("2.0") specifies that the request is to be processed synchronously. For asynchronous processing - change the value to "1.0" + Raises + ------ + ValueError + Thrown if the modify_version is invalid + Returns ------- - response_json + dict A JSON object containing the result of the request execution """ if modify_version not in ("1.0", "2.0"): @@ -88,8 +90,9 @@ def cancel_job(self, jobname: str, jobid: str, modify_version="2.0"): response_json = self.request_handler.perform_request("PUT", custom_args, expected_code=[202, 200]) return response_json - def delete_job(self, jobname, jobid, modify_version="2.0"): - """Delete the given job on JES. + def delete_job(self, jobname: str, jobid: str, modify_version: str = "2.0") -> dict: + """ + Delete the given job on JES. Parameters ---------- @@ -100,9 +103,14 @@ def delete_job(self, jobname, jobid, modify_version="2.0"): modify_version: str Default ("2.0") specifies that the request is to be processed synchronously. For asynchronous processing - change the value to "1.0" + Raises + ------ + ValueError + Thrown if the modify_version is invalid + Returns ------- - response_json + dict A JSON object containing the result of the request execution """ if modify_version not in ("1.0", "2.0"): @@ -118,7 +126,26 @@ def delete_job(self, jobname, jobid, modify_version="2.0"): response_json = self.request_handler.perform_request("DELETE", custom_args, expected_code=[202, 200]) return response_json - def _issue_job_request(self, req: dict, jobname: str, jobid: str, modify_version): + def _issue_job_request(self, req: dict, jobname: str, jobid: str, modify_version: str) -> dict: + """ + Issue a job request. + + Parameters + ---------- + req: dict + A json representation of the request + jobname: str + The name of the job + jobid: str + The job id on JES + modify_version: str + "2.0" specifies that the request is to be processed synchronously. For asynchronous processing - change the value to "1.0" + + Returns + ------- + dict + A JSON object containing the result of the request execution + """ custom_args = self._create_custom_request_arguments() job_url = "{}/{}".format(jobname, jobid) request_url = "{}{}".format(self._request_endpoint, self._encode_uri_component(job_url)) @@ -130,8 +157,9 @@ def _issue_job_request(self, req: dict, jobname: str, jobid: str, modify_version response_json = self.request_handler.perform_request("PUT", custom_args, expected_code=[202, 200]) return response_json - def change_job_class(self, jobname: str, jobid: str, class_name: str, modify_version="2.0"): - """Changes the job class + def change_job_class(self, jobname: str, jobid: str, class_name: str, modify_version: str = "2.0") -> dict: + """ + Change the job class. Parameters ---------- @@ -139,12 +167,19 @@ def change_job_class(self, jobname: str, jobid: str, class_name: str, modify_ver The name of the job jobid: str The job id on JES + class_name: str + The name of class to be set to modify_version: str Default ("2.0") specifies that the request is to be processed synchronously. For asynchronous processing - change the value to "1.0" + Raises + ------ + ValueError + Thrown if the modify_version is invalid + Returns ------- - response_json + dict A JSON object containing the result of the request execution """ if modify_version not in ("1.0", "2.0"): @@ -154,8 +189,9 @@ def change_job_class(self, jobname: str, jobid: str, class_name: str, modify_ver response_json = self._issue_job_request({"class": class_name}, jobname, jobid, modify_version) return response_json - def hold_job(self, jobname: str, jobid: str, modify_version="2.0"): - """Hold the given job on JES + def hold_job(self, jobname: str, jobid: str, modify_version: str = "2.0") -> dict: + """ + Hold the given job on JES. Parameters ---------- @@ -166,9 +202,14 @@ def hold_job(self, jobname: str, jobid: str, modify_version="2.0"): modify_version: str Default ("2.0") specifies that the request is to be processed synchronously. For asynchronous processing - change the value to "1.0" + Raises + ------ + ValueError + Thrown if the modify_version is invalid + Returns ------- - response_json + dict A JSON object containing the result of the request execution """ if modify_version not in ("1.0", "2.0"): @@ -178,8 +219,9 @@ def hold_job(self, jobname: str, jobid: str, modify_version="2.0"): response_json = self._issue_job_request({"request": "hold"}, jobname, jobid, modify_version) return response_json - def release_job(self, jobname: str, jobid: str, modify_version="2.0"): - """Release the given job on JES + def release_job(self, jobname: str, jobid: str, modify_version: str = "2.0") -> dict: + """ + Release the given job on JES. Parameters ---------- @@ -190,9 +232,14 @@ def release_job(self, jobname: str, jobid: str, modify_version="2.0"): modify_version: str Default ("2.0") specifies that the request is to be processed synchronously. For asynchronous processing - change the value to "1.0" + Raises + ------ + ValueError + Thrown if the modify_version is invalid + Returns ------- - response_json + dict A JSON object containing the result of the request execution """ if modify_version not in ("1.0", "2.0"): @@ -202,23 +249,30 @@ def release_job(self, jobname: str, jobid: str, modify_version="2.0"): response_json = self._issue_job_request({"request": "release"}, jobname, jobid, modify_version) return response_json - def list_jobs(self, owner=None, prefix="*", max_jobs=1000, user_correlator=None): - """Retrieve list of jobs on JES based on the provided arguments. + def list_jobs( + self, + owner: Optional[str] = None, + prefix: str = "*", + max_jobs: int = 1000, + user_correlator: Optional[str] = None, + ) -> dict: + """ + Retrieve list of jobs on JES based on the provided arguments. Parameters ---------- - owner: str, optional + owner: Optional[str] The job owner (default is zosmf user) - prefix: str, optional + prefix: str The job name prefix (default is `*`) - max_jobs: int, optional + max_jobs: int The maximum number of jobs in the output (default is 1000) - user_correlator: str, optional + user_correlator: Optional[str] The z/OSMF user correlator attribute (default is None) Returns ------- - json + dict A JSON object containing a list of jobs on JES queue based on the given parameters """ custom_args = self._create_custom_request_arguments() @@ -231,8 +285,9 @@ def list_jobs(self, owner=None, prefix="*", max_jobs=1000, user_correlator=None) response_json = self.request_handler.perform_request("GET", custom_args) return response_json - def submit_from_mainframe(self, jcl_path): - """Submit a job from a given dataset. + def submit_from_mainframe(self, jcl_path: str) -> dict: + """ + Submit a job from a given dataset. Parameters ---------- @@ -241,7 +296,7 @@ def submit_from_mainframe(self, jcl_path): Returns ------- - json + dict A JSON object containing the result of the request execution """ custom_args = self._create_custom_request_arguments() @@ -250,8 +305,9 @@ def submit_from_mainframe(self, jcl_path): response_json = self.request_handler.perform_request("PUT", custom_args, expected_code=[201]) return response_json - def submit_from_local_file(self, jcl_path): - """Submit a job from local file. + def submit_from_local_file(self, jcl_path: str) -> dict: + """ + Submit a job from local file. This function will internally call the `submit_plaintext` function in order to submit the contents of the given input @@ -269,7 +325,7 @@ def submit_from_local_file(self, jcl_path): Returns ------- - json + dict A JSON object containing the result of the request execution """ if os.path.isfile(jcl_path): @@ -280,8 +336,9 @@ def submit_from_local_file(self, jcl_path): 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): - """Submit a job from plain text input. + def submit_plaintext(self, jcl: str) -> dict: + """ + Submit a job from plain text input. Parameters ---------- @@ -290,7 +347,7 @@ def submit_plaintext(self, jcl): Returns ------- - json + dict A JSON object containing the result of the request execution """ custom_args = self._create_custom_request_arguments() @@ -299,8 +356,9 @@ def submit_plaintext(self, jcl): response_json = self.request_handler.perform_request("PUT", custom_args, expected_code=[201]) return response_json - def get_spool_files(self, correlator): - """Retrieve the spool files for a job identified by the correlator. + def get_spool_files(self, correlator: str) -> dict: + """ + Retrieve the spool files for a job identified by the correlator. Parameters ---------- @@ -309,7 +367,7 @@ def get_spool_files(self, correlator): Returns ------- - json + dict A JSON object containing the result of the request execution """ custom_args = self._create_custom_request_arguments() @@ -319,8 +377,9 @@ def get_spool_files(self, correlator): response_json = self.request_handler.perform_request("GET", custom_args) return response_json - def get_jcl_text(self, correlator): - """Retrieve the input JCL text for job with specified correlator + def get_jcl_text(self, correlator: str) -> dict: + """ + Retrieve the input JCL text for job with specified correlator. Parameters ---------- @@ -329,7 +388,7 @@ def get_jcl_text(self, correlator): Returns ------- - json + dict A JSON object containing the result of the request execution """ custom_args = self._create_custom_request_arguments() @@ -339,8 +398,9 @@ def get_jcl_text(self, correlator): response_json = self.request_handler.perform_request("GET", custom_args) return response_json - def get_spool_file_contents(self, correlator, id): - """Retrieve the contents of a single spool file from a job + def get_spool_file_contents(self, correlator: str, id: str) -> dict: + """ + Retrieve the contents of a single spool file from a job. Parameters ---------- @@ -352,7 +412,7 @@ def get_spool_file_contents(self, correlator, id): Returns ------- - json + dict A JSON object containing the result of the request execution """ custom_args = self._create_custom_request_arguments() @@ -362,10 +422,11 @@ def get_spool_file_contents(self, correlator, id): response_json = self.request_handler.perform_request("GET", custom_args) return response_json - def get_job_output_as_files(self, status, output_dir): - """This method will get all the spool files as well as the submitted jcl text in separate files in the specified - output directory. The structure will be as follows: + def get_job_output_as_files(self, status: dict, output_dir: str): + """ + Get all spool files and submitted jcl text in separate files in the specified output directory. + The structure will be as follows: -- | file: jcl.txt @@ -379,21 +440,13 @@ def get_job_output_as_files(self, status, output_dir): file: spool file ... - Parameters ---------- - status: json + status: dict The response json describing the job to be used. (i.e. from the last get_status call) - output_dir: str The output directory where the output files will be stored. The directory does not have to exist yet - - Returns - ------- - json - A JSON object containing the result of the request execution """ - job_name = status["jobname"] job_id = status["jobid"] job_correlator = status["job-correlator"] @@ -419,5 +472,3 @@ def get_job_output_as_files(self, status, output_dir): dataset_content = data_spool_file with open(output_file, "w", encoding="utf-8") as out_file: out_file.write(dataset_content) - - return 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 9496bd05..d8968471 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 @@ -11,6 +11,7 @@ """ import json +from typing import Optional from zowe.core_for_zowe_sdk import SdkApi, constants @@ -19,29 +20,22 @@ class Tso(SdkApi): """ Class used to represent the base z/OSMF TSO API. - Attributes + Parameters ---------- - connection + connection: dict Connection object - session_not_found - Constant for the session not found tso message id + tso_profile: Optional[dict] + Profile used for tso connection """ - def __init__(self, connection, tso_profile=None): - """ - Construct a Tso object. - - Parameters - ---------- - connection - The connection object - """ + def __init__(self, connection: dict, tso_profile: Optional[dict] = None): super().__init__(connection, "/zosmf/tsoApp/tso", logger_name=__name__) self.session_not_found = constants["TsoSessionNotFound"] self.tso_profile = tso_profile or {} - def issue_command(self, command): - """Issues a TSO command. + def issue_command(self, command: str) -> list: + """ + Issue a TSO command. This function will first initiate a TSO session, retrieve the session key, send the command and finally terminate the session @@ -67,31 +61,31 @@ def issue_command(self, command): def start_tso_session( self, - proc=None, - chset=None, - cpage=None, - rows=None, - cols=None, - rsize=None, - acct=None, - ): + proc: Optional[str] = None, + chset: Optional[str] = None, + cpage: Optional[str] = None, + rows: Optional[str] = None, + cols: Optional[str] = None, + rsize: Optional[str] = None, + acct: Optional[str] = None, + ) -> str: """Start a TSO session. Parameters ---------- - proc: str, optional + proc: Optional[str] Proc parameter for the TSO session (default is "IZUFPROC") - chset: str, optional + chset: Optional[str] Chset parameter for the TSO session (default is "697") - cpage: str, optional + cpage: Optional[str] Cpage parameter for the TSO session (default is "1047") - rows: str, optional + rows: Optional[str] Rows parameter for the TSO session (default is "204") - cols: str, optional + cols: Optional[str] Cols parameter for the TSO session (default is "160") - rsize: str, optional + rsize: Optional[str] Rsize parameter for the TSO session (default is "4096") - acctL str, optional + acct: Optional[str] Acct parameter for the TSO session (default is "DEFAULT") Returns @@ -112,8 +106,9 @@ def start_tso_session( response_json = self.request_handler.perform_request("POST", custom_args) return response_json["servletKey"] - def send_tso_message(self, session_key, message): - """Send a command to an existing TSO session. + def send_tso_message(self, session_key: str, message: str) -> list: + """ + Send a command to an existing TSO session. Parameters ---------- @@ -135,8 +130,9 @@ def send_tso_message(self, session_key, message): response_json = self.request_handler.perform_request("PUT", custom_args) return response_json["tsoData"] - def ping_tso_session(self, session_key): - """Ping an existing TSO session and returns if it is still available. + def ping_tso_session(self, session_key: str) -> str: + """ + Ping an existing TSO session and returns if it is still available. Parameters ---------- @@ -155,8 +151,9 @@ def ping_tso_session(self, session_key): message_id_list = self.parse_message_ids(response_json) return "Ping successful" if self.session_not_found not in message_id_list else "Ping failed" - def end_tso_session(self, session_key): - """Terminates an existing TSO session. + def end_tso_session(self, session_key: str) -> str: + """ + Terminates an existing TSO session. Parameters ---------- @@ -174,8 +171,9 @@ def end_tso_session(self, session_key): message_id_list = self.parse_message_ids(response_json) return "Session ended" if self.session_not_found not in message_id_list else "Session already ended" - def parse_message_ids(self, response_json): - """Parse TSO response and retrieve only the message ids. + def parse_message_ids(self, response_json: dict) -> list: + """ + Parse TSO response and retrieve only the message ids. Parameters ---------- @@ -189,8 +187,9 @@ def parse_message_ids(self, response_json): """ return [message["messageId"] for message in response_json["msgData"]] if "msgData" in response_json else [] - def retrieve_tso_messages(self, response_json): - """Parse the TSO response and retrieve all messages. + def retrieve_tso_messages(self, response_json: dict) -> list: + """ + Parse the TSO response and retrieve all messages. Parameters ---------- @@ -204,7 +203,20 @@ def retrieve_tso_messages(self, response_json): """ return [message["TSO MESSAGE"]["DATA"] for message in response_json if "TSO MESSAGE" in message] - def __get_tso_data(self, session_key): + def __get_tso_data(self, session_key: str) -> dict: + """ + Get data from a tso session. + + Parameters + ---------- + session_key: str + The session key of an existing TSO session + + Returns + ------- + dict + A json response of the operation result + """ custom_args = self._create_custom_request_arguments() custom_args["url"] = "{}/{}".format(self._request_endpoint, session_key) command_output = self.request_handler.perform_request("GET", custom_args)["tsoData"] diff --git a/src/zosmf/zowe/zosmf_for_zowe_sdk/zosmf.py b/src/zosmf/zowe/zosmf_for_zowe_sdk/zosmf.py index 554facf0..ed956d20 100644 --- a/src/zosmf/zowe/zosmf_for_zowe_sdk/zosmf.py +++ b/src/zosmf/zowe/zosmf_for_zowe_sdk/zosmf.py @@ -17,42 +17,36 @@ class Zosmf(SdkApi): """ Class used to represent the base z/OSMF API. - Attributes + Parameters ---------- - connection - Connection object + connection: dict + The z/OSMF connection object (generated by the ZoweSDK object) """ - def __init__(self, connection): - """ - Construct a Zosmf object. - - Parameters - ---------- - connection - The z/OSMF connection object (generated by the ZoweSDK object) - """ + def __init__(self, connection: dict): 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. + def get_info(self) -> dict: + """ + Return a JSON response from the GET request to z/OSMF info endpoint. Returns ------- - json + dict A JSON containing the z/OSMF Info REST API data """ response_json = self.request_handler.perform_request("GET", self._request_arguments) return response_json - def list_systems(self): - """Return a JSON response from the GET request to z/OSMF info endpoint. + def list_systems(self) -> dict: + """ + Return a JSON response from the GET request to z/OSMF info endpoint. + Returns ------- - json + dict Return a list of the systems that are defined to a z/OSMF instance """ - custom_args = self._create_custom_request_arguments() custom_args["url"] = "{}/systems".format(self._request_endpoint) response_json = self.request_handler.perform_request("GET", custom_args, expected_code=[200])