From e6496f55bae64a5ed95eeafad427fd160e438f78 Mon Sep 17 00:00:00 2001 From: Chris Davis <24f353h6zk@liamekaens.com> Date: Tue, 12 Dec 2023 10:13:15 -0500 Subject: [PATCH] Added python 3.8 support --- tests/test_cli.py | 3 ++- trcli/api/api_client.py | 4 ++-- trcli/api/api_request_handler.py | 20 ++++++++++---------- trcli/api/results_uploader.py | 2 +- trcli/backports.py | 6 ++++++ trcli/data_classes/data_parsers.py | 4 ++-- trcli/data_classes/dataclass_testrail.py | 10 +++++----- trcli/data_providers/api_data_provider.py | 14 +++++++------- trcli/readers/file_parser.py | 4 ++-- trcli/readers/junit_xml.py | 6 +++--- trcli/readers/openapi_yml.py | 3 ++- trcli/readers/robot_xml.py | 8 +++++--- 12 files changed, 47 insertions(+), 37 deletions(-) create mode 100644 trcli/backports.py diff --git a/tests/test_cli.py b/tests/test_cli.py index 5e95013..ecb7783 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -8,6 +8,7 @@ import trcli.cli from shutil import copyfile from trcli.cli import cli +from trcli.backports import removeprefix from trcli.constants import FAULT_MAPPING from tests.helpers.cli_helpers import CLIParametersHelper @@ -241,7 +242,7 @@ def test_default_config_does_not_override_environments(self, mocker, cli_resourc for arg_name, arg_value in ENVIRONMENT_VARIABLES.items(): setattr_mock.assert_any_call( - mocker.ANY, arg_name.removeprefix("TR_CLI_").lower(), arg_value + mocker.ANY, removeprefix(arg_name, "TR_CLI_").lower(), arg_value ) @pytest.mark.cli diff --git a/trcli/api/api_client.py b/trcli/api/api_client.py index 659f4a9..e1f3900 100644 --- a/trcli/api/api_client.py +++ b/trcli/api/api_client.py @@ -2,7 +2,7 @@ from pathlib import Path import requests -from typing import Union, Callable +from typing import Union, Callable, Dict, List from time import sleep import urllib3 @@ -22,7 +22,7 @@ class APIClientResult: error_message - custom error message when -1 was returned in status_code""" status_code: int - response_text: Union[dict, str, list] + response_text: Union[Dict, str, List] error_message: str diff --git a/trcli/api/api_request_handler.py b/trcli/api/api_request_handler.py index 6983a3d..96f5a91 100644 --- a/trcli/api/api_request_handler.py +++ b/trcli/api/api_request_handler.py @@ -1,6 +1,6 @@ import html from concurrent.futures import ThreadPoolExecutor, as_completed -from typing import List, Union, Tuple +from typing import List, Union, Tuple, Dict from trcli.api.api_client import APIClient, APIClientResult from trcli.api.api_response_verify import ApiResponseVerify @@ -46,7 +46,7 @@ def check_automation_id_field(self, project_id: int) -> Union[str, None]: """ response = self.client.send_get("get_case_fields") if not response.error_message: - fields: list = response.response_text + fields: List = response.response_text automation_id_field = next( filter(lambda x: x["system_name"] == "custom_automation_id", fields), None @@ -181,7 +181,7 @@ def get_suite_ids(self, project_id: int) -> Tuple[List[int], str]: ) > 0 else "Update skipped" return available_suites, error_message - def add_suites(self, project_id: int) -> Tuple[List[dict], str]: + def add_suites(self, project_id: int) -> Tuple[List[Dict], str]: """ Adds suites that doesn't have ID's in DataProvider. Runs update_data in data_provider for successfully created resources. @@ -244,7 +244,7 @@ def check_missing_section_ids(self, project_id: int) -> Tuple[bool, str]: else: return False, error_message - def add_sections(self, project_id: int) -> Tuple[List[dict], str]: + def add_sections(self, project_id: int) -> Tuple[List[Dict], str]: """ Add sections that doesn't have ID in DataProvider. Runs update_data in data_provider for successfully created resources. @@ -373,7 +373,7 @@ def add_run( run_name: str, milestone_id: int = None, plan_id: int = None, - config_ids: list[int] = None + config_ids: List[int] = None ) -> Tuple[int, str]: """ Creates a new test run. @@ -435,7 +435,7 @@ def update_run(self, run_id: int, run_name: str, milestone_id: int = None) -> Tu run_response = self.client.send_get(f"get_run/{run_id}") return run_response.response_text, update_response.error_message - def upload_attachments(self, report_results: [dict], results: list[dict], run_id: int): + def upload_attachments(self, report_results: [Dict], results: List[Dict], run_id: int): """ Getting test result id and upload attachments for it. """ tests_in_run, error = self.__get_all_tests_in_run(run_id) if not error: @@ -452,7 +452,7 @@ def upload_attachments(self, report_results: [dict], results: list[dict], run_id else: self.environment.elog(f"Unable to upload attachments due to API request error: {error}") - def add_results(self, run_id: int) -> Tuple[list, str, int]: + def add_results(self, run_id: int) -> Tuple[List, str, int]: """ Adds one or more new test results. :run_id: run id @@ -564,7 +564,7 @@ def delete_suite(self, suite_id: int) -> Tuple[dict, str]: response = self.client.send_post(f"delete_suite/{suite_id}", payload={}) return response.response_text, response.error_message - def delete_sections(self, added_sections: List[dict]) -> Tuple[list, str]: + def delete_sections(self, added_sections: List[Dict]) -> Tuple[List, str]: """ Delete section given add_sections response :suite_id: section id @@ -583,7 +583,7 @@ def delete_sections(self, added_sections: List[dict]) -> Tuple[list, str]: break return responses, error_message - def delete_cases(self, suite_id: int, added_cases: List[dict]) -> Tuple[dict, str]: + def delete_cases(self, suite_id: int, added_cases: List[Dict]) -> Tuple[Dict, str]: """ Delete cases given add_cases response :suite_id: section id @@ -654,7 +654,7 @@ def __get_all_projects(self) -> Tuple[List[dict], str]: """ return self.__get_all_entities('projects', f"get_projects") - def __get_all_entities(self, entity: str, link=None, entities=[]) -> Tuple[List[dict], str]: + def __get_all_entities(self, entity: str, link=None, entities=[]) -> Tuple[List[Dict], str]: """ Get all entities from all pages if number of entities is too big to return in single response. Function using next page field in API response. diff --git a/trcli/api/results_uploader.py b/trcli/api/results_uploader.py index 5318ff6..6a4cd9c 100644 --- a/trcli/api/results_uploader.py +++ b/trcli/api/results_uploader.py @@ -262,7 +262,7 @@ def check_suite_id(self, project_id: int) -> int: self.environment.elog(error_message) return result_code - def add_missing_sections(self, project_id: int) -> Tuple[list, int]: + def add_missing_sections(self, project_id: int) -> Tuple[List, int]: """ Checks for missing sections in specified project. Add missing sections if user agrees to do so. Returns list of added section IDs if succeeds or empty list with result_code set to diff --git a/trcli/backports.py b/trcli/backports.py new file mode 100644 index 0000000..6c96a3a --- /dev/null +++ b/trcli/backports.py @@ -0,0 +1,6 @@ +def removeprefix(text, prefix): + """Backport of python 3.9 str.removeprefix""" + + if text.startswith(prefix): + return text[len(prefix):] + return text diff --git a/trcli/data_classes/data_parsers.py b/trcli/data_classes/data_parsers.py index 10e257a..047f3d0 100644 --- a/trcli/data_classes/data_parsers.py +++ b/trcli/data_classes/data_parsers.py @@ -1,5 +1,5 @@ import re -from typing import Union +from typing import Union, List, Dict class MatchersParser: @@ -49,7 +49,7 @@ def parse_name_with_id(case_name: str) -> (int, str): class FieldsParser: @staticmethod - def resolve_fields(fields: Union[list[str], dict]) -> (dict, str): + def resolve_fields(fields: Union[List[str], Dict]) -> (Dict, str): error = None fields_dictionary = {} try: diff --git a/trcli/data_classes/dataclass_testrail.py b/trcli/data_classes/dataclass_testrail.py index 4166489..1c4fb01 100644 --- a/trcli/data_classes/dataclass_testrail.py +++ b/trcli/data_classes/dataclass_testrail.py @@ -36,8 +36,8 @@ class TestRailResult: assignedto_id: int = field(default=None, skip_if_default=True) attachments: Optional[List[str]] = field(default_factory=list, skip_if_default=True) result_fields: Optional[dict] = field(default_factory=dict, skip=True) - junit_result_unparsed: list = field(default=None, metadata={"serde_skip": True}) - custom_step_results: list[TestRailSeparatedStep] = field(default_factory=list, skip_if_default=True) + junit_result_unparsed: List = field(default=None, metadata={"serde_skip": True}) + custom_step_results: List[TestRailSeparatedStep] = field(default_factory=list, skip_if_default=True) def __post_init__(self): if self.junit_result_unparsed is not None: @@ -51,7 +51,7 @@ def __post_init__(self): self.elapsed = self.proper_format_for_elapsed(self.elapsed) @staticmethod - def calculate_status_id_from_junit_element(junit_result: list) -> int: + def calculate_status_id_from_junit_element(junit_result: List) -> int: """ Calculate id for first result. In junit no result mean pass 1 - Passed @@ -68,7 +68,7 @@ def calculate_status_id_from_junit_element(junit_result: list) -> int: return 5 @staticmethod - def get_comment_from_junit_element(junit_result: list) -> str: + def get_comment_from_junit_element(junit_result: List) -> str: if len(junit_result) == 0: return "" elif not any( @@ -136,7 +136,7 @@ class TestRailCase: result: TestRailResult = field(default=None, metadata={"serde_skip": True}) custom_automation_id: str = field(default=None, skip_if_default=True) # Uncomment if we want to support separated steps in cases in the future - # custom_steps_separated: list[TestRailSeparatedStep] = field(default_factory=list, skip_if_default=True) + # custom_steps_separated: List[TestRailSeparatedStep] = field(default_factory=list, skip_if_default=True) def __int__(self): return int(self.case_id) if self.case_id is not None else -1 diff --git a/trcli/data_providers/api_data_provider.py b/trcli/data_providers/api_data_provider.py index 3702e5a..dd5ab5f 100644 --- a/trcli/data_providers/api_data_provider.py +++ b/trcli/data_providers/api_data_provider.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Dict from serde.json import to_dict @@ -109,9 +109,9 @@ def add_results_for_cases(self, bulk_size): def update_data( self, - suite_data: List[dict] = None, - section_data: List[dict] = None, - case_data: List[dict] = None, + suite_data: List[Dict] = None, + section_data: List[Dict] = None, + case_data: List[Dict] = None, ): """Here you can provide responses from service after creating resources. This way TestRailSuite data will be updated by ID's of new created resources. @@ -123,7 +123,7 @@ def update_data( if case_data is not None: self.__update_case_data(case_data) - def __update_suite_data(self, suite_data: List[dict]): + def __update_suite_data(self, suite_data: List[Dict]): """suite_data comes from add_suite API response example: { @@ -146,7 +146,7 @@ def check_section_names_duplicates(self): else: return True - def __update_section_data(self, section_data: List[dict]): + def __update_section_data(self, section_data: List[Dict]): """section_data comes from add_section API response example: { @@ -173,7 +173,7 @@ def __update_parent_section(self, parent_section_id: int): for section in self.suites_input.testsections: section.parent_id = parent_section_id - def __update_case_data(self, case_data: List[dict]): + def __update_case_data(self, case_data: List[Dict]): """case_data comes from add_case API response example: { diff --git a/trcli/readers/file_parser.py b/trcli/readers/file_parser.py index 8767de4..432cfc3 100644 --- a/trcli/readers/file_parser.py +++ b/trcli/readers/file_parser.py @@ -1,6 +1,6 @@ from pathlib import Path from abc import abstractmethod -from typing import Union +from typing import Union, List from trcli.cli import Environment from trcli.data_classes.dataclass_testrail import TestRailSuite @@ -24,5 +24,5 @@ def check_file(filepath: Union[str, Path]) -> Path: return filepath @abstractmethod - def parse_file(self) -> list[TestRailSuite]: + def parse_file(self) -> List[TestRailSuite]: raise NotImplementedError diff --git a/trcli/readers/junit_xml.py b/trcli/readers/junit_xml.py index c946929..3c2ae52 100644 --- a/trcli/readers/junit_xml.py +++ b/trcli/readers/junit_xml.py @@ -1,6 +1,6 @@ import glob from pathlib import Path -from typing import Union +from typing import Union, List from unittest import TestCase, TestSuite from xml.etree import ElementTree as etree @@ -75,7 +75,7 @@ def check_file(filepath: Union[str, Path]) -> Path: suite.write(merged_report_path) return merged_report_path - def parse_file(self) -> list[TestRailSuite]: + def parse_file(self) -> List[TestRailSuite]: self.env.log(f"Parsing JUnit report.") suite = JUnitXml.fromfile( self.filepath, parse_func=self._add_root_element_to_tree @@ -188,7 +188,7 @@ def parse_file(self) -> list[TestRailSuite]: return testrail_suites - def split_sauce_report(self, suite) -> list[JUnitXml]: + def split_sauce_report(self, suite) -> List[JUnitXml]: self.env.log(f"Processing SauceLabs report.") subsuites = {} for section in suite: diff --git a/trcli/readers/openapi_yml.py b/trcli/readers/openapi_yml.py index 086de66..aeb289c 100644 --- a/trcli/readers/openapi_yml.py +++ b/trcli/readers/openapi_yml.py @@ -1,5 +1,6 @@ import json from pathlib import Path +from typing import List import yaml @@ -133,7 +134,7 @@ def _format_text(title, details): class OpenApiParser(FileParser): - def parse_file(self) -> list[TestRailSuite]: + def parse_file(self) -> List[TestRailSuite]: self.env.log(f"Parsing OpenAPI specification.") spec = self.resolve_openapi_spec() sections = { diff --git a/trcli/readers/robot_xml.py b/trcli/readers/robot_xml.py index 90fd4b7..0c9d098 100644 --- a/trcli/readers/robot_xml.py +++ b/trcli/readers/robot_xml.py @@ -1,6 +1,8 @@ from datetime import datetime +from typing import List from xml.etree import ElementTree +from trcli.backports import removeprefix from trcli.cli import Environment from trcli.data_classes.data_parsers import MatchersParser, FieldsParser from trcli.data_classes.dataclass_testrail import ( @@ -18,7 +20,7 @@ def __init__(self, environment: Environment): super().__init__(environment) self.case_matcher = environment.case_matcher - def parse_file(self) -> list[TestRailSuite]: + def parse_file(self) -> List[TestRailSuite]: self.env.log(f"Parsing Robot Framework report.") tree = ElementTree.parse(self.filepath) root = tree.getroot() @@ -38,7 +40,7 @@ def parse_file(self) -> list[TestRailSuite]: return testrail_suites - def _find_suites(self, suite_element, sections_list: list, namespace=""): + def _find_suites(self, suite_element, sections_list: List, namespace=""): name = suite_element.get("name") namespace += f".{name}" if namespace else name tests = suite_element.findall("test") @@ -124,4 +126,4 @@ def _parse_rf_time(time_str: str) -> datetime: @staticmethod def _remove_tr_prefix(text: str, tr_prefix: str) -> str: - return text.strip().removeprefix(tr_prefix).strip() + return removeprefix(text, tr_prefix).strip()