How to use load_poeditor_texts method in toolium

Best Python code snippet using toolium_python

dataset.py

Source:dataset.py Github

copy

Full Screen

1# -*- coding: utf-8 -*-2"""3Copyright 2022 Telefónica Investigación y Desarrollo, S.A.U.4This file is part of Toolium.5Licensed under the Apache License, Version 2.0 (the "License");6you may not use this file except in compliance with the License.7You may obtain a copy of the License at8 http://www.apache.org/licenses/LICENSE-2.09Unless required by applicable law or agreed to in writing, software10distributed under the License is distributed on an "AS IS" BASIS,11WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12See the License for the specific language governing permissions and13limitations under the License.14"""15import base6416import datetime17import json18import logging19import os20import random as r21import re22import string23from ast import literal_eval24from copy import deepcopy25logger = logging.getLogger(__name__)26# Base path for BASE64 and FILE conversions27base_base64_path = ''28base_file_path = ''29# Global variables used in map_param replacements30# Language terms and project config are not set by toolium, they must be set from test project31language = None32language_terms = None33project_config = None34# Toolium config and behave context are set when toolium before_all method is called in behave tests35toolium_config = None36behave_context = None37# POEditor terms are set when load_poeditor_texts or export_poeditor_project poeditor.py methods are called38poeditor_terms = None39def replace_param(param, language='es', infer_param_type=True):40 """41 Apply transformations to the given param based on specific patterns.42 Available replacements:43 [STRING_WITH_LENGTH_XX] Generates a fixed length string44 [INTEGER_WITH_LENGTH_XX] Generates a fixed length integer45 [STRING_ARRAY_WITH_LENGTH_XX] Generates a fixed length array of strings46 [INTEGER_ARRAY_WITH_LENGTH_XX] Generates a fixed length array of integers47 [JSON_WITH_LENGTH_XX] Generates a fixed length JSON48 [MISSING_PARAM] Generates a None object49 [NULL] Generates a None object50 [TRUE] Generates a boolean True51 [FALSE] Generates a boolean False52 [EMPTY] Generates an empty string53 [B] Generates a blank space54 [RANDOM] Generates a random value55 [RANDOM_PHONE_NUMBER] Generates a random phone number following the pattern +34654XXXXXX56 [TIMESTAMP] Generates a timestamp from the current time57 [DATETIME] Generates a datetime from the current time58 [NOW] Similar to DATETIME without milliseconds; the format depends on the language59 [NOW(%Y-%m-%dT%H:%M:%SZ)] Same as NOW but using an specific format by the python strftime function of the datetime module60 [NOW + 2 DAYS] Similar to NOW but two days later61 [NOW - 1 MINUTES] Similar to NOW but one minute earlier62 [NOW(%Y-%m-%dT%H:%M:%SZ) - 7 DAYS] Similar to NOW but seven days before and with the indicated format63 [TODAY] Similar to NOW without time; the format depends on the language64 [TODAY + 2 DAYS] Similar to NOW, but two days later65 [STR:xxxx] Cast xxxx to a string66 [INT:xxxx] Cast xxxx to an int67 [FLOAT:xxxx] Cast xxxx to a float68 [LIST:xxxx] Cast xxxx to a list69 [DICT:xxxx] Cast xxxx to a dict70 [UPPER:xxxx] Converts xxxx to upper case71 [LOWER:xxxx] Converts xxxx to lower case72 If infer_param_type is True and the result of the replacement process is a string,73 this function also tries to infer and cast the result to the most appropriate data type,74 attempting first the direct conversion to a Python built-in data type and then,75 if not possible, the conversion to a dict/list parsing the string as a JSON object/list.76 :param param: parameter value77 :param language: language to configure date format for NOW and TODAY ('es' or other)78 :param infer_param_type: whether to infer and change the data type of the result or not79 :return: data with the correct replacements80 """81 if not isinstance(param, str):82 return param83 # Replacements that imply a specific data type and do not admit further transformations84 new_param, param_replaced = _replace_param_type(param)85 if not param_replaced:86 new_param, param_replaced = _replace_param_fixed_length(new_param)87 if not param_replaced:88 # Replacements that return new strings that can be transformed later89 new_param, _ = _replace_param_replacement(new_param, language)90 # String transformations that do not allow type inference91 new_param, param_replaced = _replace_param_transform_string(new_param)92 if not param_replaced and infer_param_type:93 # Type inference94 new_param = _infer_param_type(new_param)95 if param != new_param:96 if type(new_param) == str:97 logger.debug(f'Replaced param from "{param}" to "{new_param}"')98 else:99 logger.debug(f'Replaced param from "{param}" to {new_param}')100 return new_param101def _replace_param_type(param):102 """103 Replace param with a new param type.104 Available replacements: [MISSING_PARAM], [TRUE], [FALSE], [NULL]105 :param param: parameter value106 :return: tuple with replaced value and boolean to know if replacement has been done107 """108 param_types = {109 '[MISSING_PARAM]': None,110 '[TRUE]': True,111 '[FALSE]': False,112 '[NULL]': None113 }114 new_param = param115 param_replaced = False116 for key in param_types.keys():117 if key in param:118 new_param = param_types[key]119 param_replaced = True120 break121 return new_param, param_replaced122def _find_param_date_expressions(param):123 """124 Finds in a param one or several date expressions. 125 For example, for a param like "it happened on [NOW - 1 MONTH] of the last year and will happen [TODAY('%d/%m')]",126 this method returns an array with two string elements: "[NOW - 1 MONTH]" and [TODAY('%d/%m')]"127 The kind of expressions to search are based on these rules:128 - expression is sorrounded by [ and ]129 - first word of the expression is either NOW or TODAY130 - when first word is NOW, it can have an addtional format for the date between parenthesis, 131 like NOW(%Y-%m-%dT%H:%M:%SZ). The definition of the format is the same as considered by the132 python strftime function of the datetime module133 - and optional offset can be given by indicating how many days, hours, etc.. to add or remove to the current datetime.134 This part of the expression includes a +/- symbol plus a number and a unit135 Some valid expressions are:136 [NOW]137 [TODAY]138 [NOW(%Y-%m-%dT%H:%M:%SZ)]139 [NOW(%Y-%m-%dT%H:%M:%SZ) - 180 DAYS]140 [NOW(%H:%M:%S) + 4 MINUTES]141 :param param: parameter value142 :param language: language to configure date format for NOW and TODAY143 :return: An array with all the matching date expressions found in the param144 """145 return re.findall(r"\[(?:NOW(?:\((?:[^\(\)]*)\))?|TODAY)(?:\s*[\+|-]\s*\d+\s*\w+\s*)?\]", param)146def _replace_param_replacement(param, language):147 """148 Replace param with a new param value.149 Available replacements: [EMPTY], [B], [RANDOM], [TIMESTAMP], [DATETIME], [NOW], [TODAY]150 :param param: parameter value151 :param language: language to configure date format for NOW and TODAY152 :return: tuple with replaced value and boolean to know if replacement has been done153 """154 date_format = '%d/%m/%Y %H:%M:%S' if language == 'es' else '%Y/%m/%d %H:%M:%S'155 date_day_format = '%d/%m/%Y' if language == 'es' else '%Y/%m/%d'156 alphanums = ''.join([string.ascii_lowercase, string.digits]) # abcdefghijklmnopqrstuvwxyz0123456789157 replacements = {158 '[EMPTY]': '',159 '[B]': ' ',160 # make sure random is not made up of digits only, by forcing the first char to be a letter161 '[RANDOM]': ''.join([r.choice(string.ascii_lowercase), *(r.choice(alphanums) for i in range(7))]),162 '[RANDOM_PHONE_NUMBER]': ''.join(['+', '3', '4', '6', '5', '4'] + [str(r.randint(0, 9)) for i in range(1, 7)]),163 '[TIMESTAMP]': str(int(datetime.datetime.timestamp(datetime.datetime.utcnow()))),164 '[DATETIME]': str(datetime.datetime.utcnow()),165 '[NOW]': str(datetime.datetime.utcnow().strftime(date_format)),166 '[TODAY]': str(datetime.datetime.utcnow().strftime(date_day_format))167 }168 169 # append date expressions found in param to the replacement dict 170 date_expressions = _find_param_date_expressions(param)171 for date_expr in date_expressions:172 replacements[date_expr] = _replace_param_date(date_expr, language)[0]173 new_param = param174 param_replaced = False175 for key in replacements.keys():176 if key in new_param:177 new_param = new_param.replace(key, replacements[key])178 param_replaced = True179 return new_param, param_replaced180def _replace_param_transform_string(param):181 """182 Transform param value according to the specified prefix.183 Available transformations: DICT, LIST, INT, FLOAT, STR, UPPER, LOWER184 :param param: parameter value185 :return: tuple with replaced value and boolean to know if replacement has been done186 """187 type_mapping_regex = r'\[(DICT|LIST|INT|FLOAT|STR|UPPER|LOWER):(.*)\]'188 type_mapping_match_group = re.match(type_mapping_regex, param)189 new_param = param190 param_transformed = False191 if type_mapping_match_group:192 param_transformed = True193 if type_mapping_match_group.group(1) == 'STR':194 new_param = type_mapping_match_group.group(2)195 elif type_mapping_match_group.group(1) in ['LIST', 'DICT', 'INT', 'FLOAT']:196 exec('exec_param = {type}({value})'.format(type=type_mapping_match_group.group(1).lower(),197 value=type_mapping_match_group.group(2)))198 new_param = locals()['exec_param']199 elif type_mapping_match_group.group(1) == 'UPPER':200 new_param = type_mapping_match_group.group(2).upper()201 elif type_mapping_match_group.group(1) == 'LOWER':202 new_param = type_mapping_match_group.group(2).lower()203 return new_param, param_transformed204def _replace_param_date(param, language):205 """206 Transform param value in a date after applying the specified delta.207 E.g. [TODAY - 2 DAYS], [NOW - 10 MINUTES]208 An specific format could be defined in the case of NOW this way: NOW('THEFORMAT')209 where THEFORMAT is any valid format accepted by the python 210 [datetime.strftime](https://docs.python.org/3/library/datetime.html#datetime.date.strftime) function 211 :param param: parameter value212 :param language: language to configure date format for NOW and TODAY213 :return: tuple with replaced value and boolean to know if replacement has been done214 """215 def _date_matcher():216 return re.match(r'\[(NOW(?:\((?:.*)\)|)|TODAY)(?:\s*([\+|-]\s*\d+)\s*(\w+)\s*)?\]', param)217 def _offset_datetime(amount, units):218 now = datetime.datetime.utcnow()219 if not amount or not units:220 return now221 the_amount = int(amount.replace(' ',''))222 the_units = units.lower()223 return now + datetime.timedelta(**dict([(the_units, the_amount)]))224 def _is_only_date(base):225 return 'TODAY' in base226 def _default_format(base):227 date_format = '%d/%m/%Y' if language == 'es' else '%Y/%m/%d'228 if _is_only_date(base):229 return date_format230 return f'{date_format} %H:%M:%S'231 def _get_format(base):232 format_matcher = re.match(r'.*\((.*)\).*', base)233 if format_matcher and len(format_matcher.groups()) == 1:234 return format_matcher.group(1)235 return _default_format(base)236 matcher = _date_matcher()237 if not matcher:238 return param, False239 base, amount, units = list(matcher.groups())240 format_str = _get_format(base)241 date = _offset_datetime(amount, units)242 return date.strftime(format_str), True243def _replace_param_fixed_length(param):244 """245 Generate a fixed length data element if param matches the expression [<type>_WITH_LENGTH_<length>]246 where <type> can be: STRING, INTEGER, STRING_ARRAY, INTEGER_ARRAY, JSON.247 E.g. [STRING_WITH_LENGTH_15]248 :param param: parameter value249 :return: tuple with replaced value and boolean to know if replacement has been done250 """251 new_param = param252 param_replaced = False253 if param.startswith('[') and param.endswith(']'):254 if any(x in param for x in ['STRING_ARRAY_WITH_LENGTH_', 'INTEGER_ARRAY_WITH_LENGTH_']):255 seeds = {'STRING': 'a', 'INTEGER': 1}256 seed, length = param[1:-1].split('_ARRAY_WITH_LENGTH_')257 new_param = list(seeds[seed] for x in range(int(length)))258 param_replaced = True259 elif 'JSON_WITH_LENGTH_' in param:260 length = int(param[1:-1].split('JSON_WITH_LENGTH_')[1])261 new_param = dict((str(x), str(x)) for x in range(length))262 param_replaced = True263 elif any(x in param for x in ['STRING_WITH_LENGTH_', 'INTEGER_WITH_LENGTH_']):264 seeds = {'STRING': 'a', 'INTEGER': '1'}265 # The chain to be generated can be just a part of param266 start = param.find('[')267 end = param.find(']')268 seed, length = param[start + 1:end].split('_WITH_LENGTH_')269 generated_part = seeds[seed] * int(length)270 placeholder = '[' + seed + '_WITH_LENGTH_' + length + ']'271 new_param = param.replace(placeholder, generated_part)272 param_replaced = True273 if seed == 'INTEGER':274 new_param = int(new_param)275 return new_param, param_replaced276def _infer_param_type(param):277 """278 Transform the param from a string representing a built-in data type or a JSON object/list to the279 corresponding built-in data type. If no conversion is possible, the original param is returned.280 E.g. "1234" -> 1234, "0.50" -> 0.5, "True" -> True, "None" -> None,281 "['a', None]" -> ['a', None], "{'a': None}" -> {'a': None},282 '["a", null]' -> ["a", None], '{"a": null}' -> {"a": None}283 :param param: data to be transformed284 :return data with the inferred type285 """286 new_param = param287 # attempt direct conversion to a built-in data type288 try:289 new_param = literal_eval(param)290 except Exception:291 # it may still be a JSON object/list that can be converted to a dict/list292 try:293 if param.startswith('{') or param.startswith('['):294 new_param = json.loads(param)295 except Exception:296 pass297 return new_param298# Ignore flake8 warning until deprecated context parameter is removed299# flake8: noqa: C901300def map_param(param, context=None):301 """302 Transform the given string by replacing specific patterns containing keys with their values,303 which can be obtained from the Behave context or from environment files or variables.304 See map_one_param function for a description of the available tags and replacement logic.305 :param param: string parameter306 :param context: Behave context object (deprecated parameter)307 :return: string with the applied replacements308 """309 if context:310 logger.warning('Deprecated context parameter has been sent to map_param method. Please, configure dataset'311 ' global variables instead of passing context to map_param.')312 global language, language_terms, project_config, toolium_config, poeditor_terms, behave_context313 if hasattr(context, 'language'):314 language = context.language315 if hasattr(context, 'language_dict'):316 language_terms = context.language_dict317 if hasattr(context, 'project_config'):318 project_config = context.project_config319 if hasattr(context, 'toolium_config'):320 toolium_config = context.toolium_config321 if hasattr(context, 'poeditor_export'):322 poeditor_terms = context.poeditor_export323 behave_context = context324 if not isinstance(param, str):325 return param326 map_regex = r"[\[CONF:|\[LANG:|\[POE:|\[ENV:|\[BASE64:|\[TOOLIUM:|\[CONTEXT:|\[FILE:][a-zA-Z\.\:\/\_\-\ 0-9]*\]"327 map_expressions = re.compile(map_regex)328 mapped_param = param329 if map_expressions.split(param) == ['', '']:330 # The parameter is just one config value331 mapped_param = map_one_param(param)332 else:333 # The parameter is a combination of text and configuration parameters.334 for match in map_expressions.findall(param):335 mapped_param = mapped_param.replace(match, str(map_one_param(match)))336 if mapped_param != param:337 # Calling to map_param recursively to replace parameters that include another parameters338 mapped_param = map_param(mapped_param, context)339 return mapped_param340def map_one_param(param):341 """342 Analyze the pattern in the given string and find out its transformed value.343 Available tags and replacement values:344 [CONF:xxxx] Value from the config dict in project_config global variable for the key xxxx (dot notation is used345 for keys, e.g. key_1.key_2.0.key_3)346 [LANG:xxxx] String from the texts dict in language_terms global variable for the key xxxx, using the language347 specified in language global variable (dot notation is used for keys, e.g. button.label)348 [POE:xxxx] Definition(s) from the POEditor terms list in poeditor_terms global variable for the term xxxx349 [TOOLIUM:xxxx] Value from the toolium config in toolium_config global variable for the key xxxx (key format is350 section_option, e.g. Driver_type)351 [CONTEXT:xxxx] Value from the behave context storage dict in behave_context global variable for the key xxxx, or352 value of the behave context attribute xxxx, if the former does not exist353 [ENV:xxxx] Value of the OS environment variable xxxx354 [FILE:xxxx] String with the content of the file in the path xxxx355 [BASE64:xxxx] String with the base64 representation of the file content in the path xxxx356 :param param: string parameter357 :return: transformed value or the original string if no transformation could be applied358 """359 if not isinstance(param, str):360 return param361 type, key = _get_mapping_type_and_key(param)362 mapping_functions = {363 "CONF": {364 "prerequisites": project_config,365 "function": map_json_param,366 "args": [key, project_config]367 },368 "TOOLIUM": {369 "prerequisites": toolium_config,370 "function": map_toolium_param,371 "args": [key, toolium_config]372 },373 "CONTEXT": {374 "prerequisites": behave_context,375 "function": get_value_from_context,376 "args": [key, behave_context]377 },378 "LANG": {379 "prerequisites": language_terms and language,380 "function": get_message_property,381 "args": [key, language_terms, language]382 },383 "POE": {384 "prerequisites": poeditor_terms,385 "function": get_translation_by_poeditor_reference,386 "args": [key, poeditor_terms]387 },388 "ENV": {389 "prerequisites": True,390 "function": os.environ.get,391 "args": [key]392 },393 "FILE": {394 "prerequisites": True,395 "function": get_file,396 "args": [key]397 },398 "BASE64": {399 "prerequisites": True,400 "function": convert_file_to_base64,401 "args": [key]402 }403 }404 if key and mapping_functions[type]["prerequisites"]:405 param = mapping_functions[type]["function"](*mapping_functions[type]["args"])406 return param407def _get_mapping_type_and_key(param):408 """409 Get the type and the key of the given string parameter to be mapped to the appropriate value.410 :param param: string parameter to be parsed411 :return: a tuple with the type and the key to be mapped412 """413 types = ["CONF", "LANG", "POE", "ENV", "BASE64", "TOOLIUM", "CONTEXT", "FILE"]414 for type in types:415 match_group = re.match(r"\[%s:(.*)\]" % type, param)416 if match_group:417 return type, match_group.group(1)418 return None, None419def map_json_param(param, config, copy=True):420 """421 Find the value of the given param using it as a key in the given dictionary. Dot notation is used,422 so for example "service.vamps.user" could be used to retrieve the email in the following config example:423 {424 "services":{425 "vamps":{426 "user": "cyber-sec-user@11paths.com",427 "password": "MyPassword"428 }429 }430 }431 :param param: key to be searched (dot notation is used, e.g. "service.vamps.user").432 :param config: configuration dictionary433 :param copy: boolean value to indicate whether to work with a copy of the given dictionary or not,434 in which case, the dictionary content might be changed by this function (True by default)435 :return: mapped value436 """437 properties_list = param.split(".")438 aux_config_json = deepcopy(config) if copy else config439 try:440 for property in properties_list:441 if type(aux_config_json) is list:442 aux_config_json = aux_config_json[int(property)]443 else:444 aux_config_json = aux_config_json[property]445 hidden_value = hide_passwords(param, aux_config_json)446 logger.debug(f"Mapping param '{param}' to its configured value '{hidden_value}'")447 except TypeError:448 msg = f"Mapping chain not found in the given configuration dictionary. '{param}'"449 logger.error(msg)450 raise TypeError(msg)451 except KeyError:452 msg = f"Mapping chain not found in the given configuration dictionary. '{param}'"453 logger.error(msg)454 raise KeyError(msg)455 except ValueError:456 msg = f"Specified value is not a valid index. '{param}'"457 logger.error(msg)458 raise ValueError(msg)459 except IndexError:460 msg = f"Mapping index not found in the given configuration dictionary. '{param}'"461 logger.error(msg)462 raise IndexError(msg)463 return os.path.expandvars(aux_config_json) \464 if aux_config_json and type(aux_config_json) not in [int, bool, float, list, dict] else aux_config_json465def hide_passwords(key, value):466 """467 Return asterisks when the given key is a password that should be hidden.468 :param key: key name469 :param value: value470 :return: hidden value471 """472 hidden_keys = ['key', 'pass', 'secret', 'code', 'token']473 hidden_value = '*****'474 return hidden_value if any(hidden_key in key for hidden_key in hidden_keys) else value475def map_toolium_param(param, config):476 """477 Find the value of the given param using it as a key in the given toolium configuration.478 The param is expected to be in the form <section>_<property>, so for example "TextExecution_environment" could be479 used to retrieve the value of this toolium property (i.e. the string "QA"):480 [TestExecution]481 environment: QA482 :param param: key to be searched (e.g. "TextExecution_environment")483 :param config: toolium config object484 :return: mapped value485 """486 try:487 section = param.split("_", 1)[0]488 property_name = param.split("_", 1)[1]489 except IndexError:490 msg = f"Invalid format in Toolium config param '{param}'. Valid format: 'Section_property'."491 logger.error(msg)492 raise IndexError(msg)493 try:494 mapped_value = config.get(section, property_name)495 logger.info(f"Mapping Toolium config param 'param' to its configured value '{mapped_value}'")496 except Exception:497 msg = f"'{param}' param not found in Toolium config file"498 logger.error(msg)499 raise Exception(msg)500 return mapped_value501def get_value_from_context(param, context):502 """503 Find the value of the given param using it as a key in the context storage dictionary (context.storage) or in the504 context object itself. The key might be comprised of dotted tokens. In such case, the searched key is the first505 token. The rest of the tokens are considered nested properties/objects.506 So, for example, in the basic case, "last_request_result" could be used as key that would be searched into context507 storage or the context object itself. In a dotted case, "last_request.result" is searched as a "last_request" key508 in the context storage or as a property of the context object whose name is last_request. In both cases, when found,509 "result" is considered (and resolved) as a property into the returned value.510 511 There is not limit in the nested levels of dotted tokens, so a key like a.b.c.d will be tried to be resolved as:512 context.storage['a'].b.c.d513 or514 context.a.b.c.d515 :param param: key to be searched (e.g. "last_request_result" / "last_request.result")516 :param context: Behave context object517 :return: mapped value518 """519 parts = param.split('.')520 value = None521 if context.storage and parts[0] in context.storage:522 value = context.storage[parts[0]]523 else:524 logger.info(f"'{parts[0]}' key not found in context storage, searching in context")525 try:526 value = getattr(context, parts[0])527 except AttributeError:528 msg = f"'{parts[0]}' not found neither in context storage nor in context"529 logger.error(msg)530 raise AttributeError(msg)531 if len(parts) > 1:532 try:533 for part in parts[1:]:534 value = getattr(value, part)535 except AttributeError:536 msg = f"'{part}' is not an attribute of {value}"537 logger.error(msg)538 raise AttributeError(msg)539 return value540def get_message_property(param, language_terms, language_key):541 """542 Return the message for the given param, using it as a key in the list of language properties.543 Dot notation is used (e.g. "home.button.send").544 :param param: message key545 :param language_terms: dict with language terms546 :param language_key: language key547 :return: the message mapped to the given key in the given language548 """549 key_list = param.split(".")550 language_terms_aux = deepcopy(language_terms)551 try:552 for key in key_list:553 language_terms_aux = language_terms_aux[key]554 logger.info(f"Mapping language param '{param}' to its configured value '{language_terms_aux[language_key]}'")555 except KeyError:556 msg = f"Mapping chain '{param}' not found in the language properties file"557 logger.error(msg)558 raise KeyError(msg)559 return language_terms_aux[language_key]560def get_translation_by_poeditor_reference(reference, poeditor_terms):561 """562 Return the translation(s) for the given POEditor reference from the given terms in poeditor_terms.563 :param reference: POEditor reference564 :param poeditor_terms: poeditor terms565 :return: list of strings with the translations from POEditor or string with the translation if only one was found566 """567 poeditor_config = project_config['poeditor'] if 'poeditor' in project_config else {}568 key = poeditor_config['key_field'] if 'key_field' in poeditor_config else 'reference'569 search_type = poeditor_config['search_type'] if 'search_type' in poeditor_config else 'contains'570 # Get POEditor prefixes and add no prefix option571 poeditor_prefixes = poeditor_config['prefixes'] if 'prefixes' in poeditor_config else []572 poeditor_prefixes.append('')573 translation = []574 for prefix in poeditor_prefixes:575 if len(reference.split(':')) > 1 and prefix != '':576 # If there are prefixes and the resource contains ':' apply prefix in the correct position577 complete_reference = '%s:%s%s' % (reference.split(':')[0], prefix, reference.split(':')[1])578 else:579 complete_reference = '%s%s' % (prefix, reference)580 if search_type == 'exact':581 translation = [term['definition'] for term in poeditor_terms582 if complete_reference == term[key] and term['definition'] is not None]583 else:584 translation = [term['definition'] for term in poeditor_terms585 if complete_reference in term[key] and term['definition'] is not None]586 if len(translation) > 0:587 break588 assert len(translation) > 0, 'No translations found in POEditor for reference %s' % reference589 translation = translation[0] if len(translation) == 1 else translation590 return translation591def set_base64_path(path):592 """593 Set a relative path to be used as the base for the file path specified in the BASE64 mapping pattern.594 :param path: relative path to be used as base for base64 mapping595 """596 global base_base64_path597 base_base64_path = path598def set_file_path(path):599 """600 Set a relative path to be used as the base for the file path specified in the FILE mapping pattern.601 :param path: relative path to be used as base for file mapping602 """603 global base_file_path604 base_file_path = path605def get_file(file_path):606 """607 Return the content of a file given its path. If a base path was previously set by using608 the set_file_path() function, the file path specified must be relative to that path.609 :param file path: file path using slash as separator (e.g. "resources/files/doc.txt")610 :return: string with the file content611 """612 file_path_parts = (base_file_path + file_path).split("/")613 file_path = os.path.abspath(os.path.join(*file_path_parts))614 if not os.path.exists(file_path):615 raise Exception(f' ERROR - Cannot read file "{file_path}". Does not exist.')616 with open(file_path, 'r') as f:617 return f.read()618def convert_file_to_base64(file_path):619 """620 Return the content of a file given its path encoded in Base64. If a base path was previously set by using621 the set_file_path() function, the file path specified must be relative to that path.622 :param file path: file path using slash as separator (e.g. "resources/files/doc.txt")623 :return: string with the file content encoded in Base64624 """625 file_path_parts = (base_base64_path + file_path).split("/")626 file_path = os.path.abspath(os.path.join(*file_path_parts))627 if not os.path.exists(file_path):628 raise Exception(f' ERROR - Cannot read file "{file_path}". Does not exist.')629 try:630 with open(file_path, "rb") as f:631 file_content = base64.b64encode(f.read()).decode()632 except Exception as e:633 raise Exception(f' ERROR - converting the "{file_path}" file to Base64...: {e}')...

Full Screen

Full Screen

poeditor.py

Source:poeditor.py Github

copy

Full Screen

...53ENDPOINT_POEDITOR_EXPORT_PROJECT = "v2/projects/export"54ENDPOINT_POEDITOR_DOWNLOAD_FILE = "v2/download/file"55# Configure logger56logger = logging.getLogger(__name__)57def download_poeditor_texts(context=None, file_type='json'):58 """59 Executes all steps to download texts from POEditor and saves them to a file in output dir60 :param context: behave context (deprecated)61 :param file_type: file type (only json supported)62 :return: N/A63 """64 if context:65 logger.warning('Deprecated context parameter has been sent to download_poeditor_texts method. Please, configure'66 ' dataset global variables instead of passing context to download_poeditor_texts.')67 project_info = get_poeditor_project_info_by_name()68 language_codes = get_poeditor_language_codes(project_info)69 language = get_valid_lang(language_codes)70 poeditor_terms = export_poeditor_project(project_info, language, file_type)71 save_downloaded_file(poeditor_terms)72 # Save terms in dataset to be used in [POE:] map_param replacements73 dataset.poeditor_terms = poeditor_terms74 if context:75 # Save terms in context for backwards compatibility76 context.poeditor_export = dataset.poeditor_terms77def get_poeditor_project_info_by_name(project_name=None):78 """79 Get POEditor project info from project name from config or parameter80 :param project_name: POEditor project name81 :return: project info82 """83 projects = get_poeditor_projects()84 project_name = project_name if project_name else map_param('[CONF:poeditor.project_name]')85 projects_by_name = [project for project in projects if project['name'] == project_name]86 assert len(projects_by_name) == 1, "ERROR: Project name %s not found, available projects: %s" % \87 (project_name, [project['name'] for project in projects])88 return projects_by_name[0]89def get_poeditor_language_codes(project_info):90 """91 Get language codes available for a given project ID92 :param project_info: project info93 :return: project language codes94 """95 params = {"api_token": get_poeditor_api_token(),96 "id": project_info['id']}97 r = send_poeditor_request(ENDPOINT_POEDITOR_LIST_LANGUAGES, "POST", params, 200)98 response_data = r.json()99 assert_poeditor_response_code(response_data, "200")100 language_codes = [lang['code'] for lang in response_data['result']['languages']]101 assert not len(language_codes) == 0, "ERROR: Not languages found in POEditor"102 logger.info('POEditor languages in "%s" project: %s %s' % (project_info['name'], len(language_codes),103 language_codes))104 return language_codes105def search_terms_with_string(context=None, lang=None):106 """107 Saves POEditor terms for a given existing language in that project108 :param context: behave context (deprecated)109 :param lang: a valid language existing in that POEditor project110 :return: N/A (saves it to context.poeditor_terms)111 """112 if context:113 logger.warning('Deprecated context parameter has been sent to search_terms_with_string method. Please, '114 'configure dataset global variables instead of passing context to search_terms_with_string.')115 project_info = get_poeditor_project_info_by_name()116 language_codes = get_poeditor_language_codes(project_info)117 language = get_valid_lang(language_codes, lang)118 dataset.poeditor_terms = get_all_terms(project_info, language)119 if context:120 # Save terms in context for backwards compatibility121 context.poeditor_terms = dataset.poeditor_terms122def export_poeditor_project(project_info, lang, file_type):123 """124 Export all texts in project to a given file type125 :param project_info: project info126 :param lang: language configured in POEditor project that will be exported127 :param file_type: There are more available formats to download but only one is supported now: json128 :return: poeditor terms129 """130 assert file_type in ['json'], "Only json file type is supported at this moment"131 params = {"api_token": get_poeditor_api_token(),132 "id": project_info['id'],133 "language": lang,134 "type": file_type}135 r = send_poeditor_request(ENDPOINT_POEDITOR_EXPORT_PROJECT, "POST", params, 200)136 response_data = r.json()137 assert_poeditor_response_code(response_data, "200")138 filename = response_data['result']['url'].split('/')[-1]139 r = send_poeditor_request(ENDPOINT_POEDITOR_DOWNLOAD_FILE + '/' + filename, "GET", {}, 200)140 poeditor_terms = r.json()141 logger.info('POEditor terms in "%s" project with "%s" language: %s' % (project_info['name'], lang,142 len(poeditor_terms)))143 return poeditor_terms144def save_downloaded_file(poeditor_terms):145 """146 Saves POEditor terms to a file in output dir147 :param poeditor_terms: POEditor terms148 """149 file_path = get_poeditor_file_path()150 with open(file_path, 'w') as f:151 json.dump(poeditor_terms, f, indent=4)152 logger.info('POEditor terms have been saved in "%s" file' % file_path)153def assert_poeditor_response_code(response_data, status_code):154 """155 Check status code returned in POEditor response156 :param response_data: data received in poeditor API response as a dictionary157 :param status_code: expected status code158 """159 assert response_data['response']['code'] == status_code, f"{response_data['response']['code']} status code \160 has been received instead of {status_code} in POEditor response body. Response body: {response_data}"161def get_country_from_config_file():162 """163 Gets the country to use later from config checking if it's a valid one in POEditor164 :return: country165 """166 try:167 country = dataset.toolium_config.get('TestExecution', 'language').lower()168 except NoOptionError:169 assert False, "There is no language configured in test, add it to config or use step with parameter lang_id"170 return country171def get_valid_lang(language_codes, lang=None):172 """173 Check if language provided is a valid one configured and returns the POEditor matched lang174 :param language_codes: valid POEditor language codes175 :param lang: a language from config or from lang parameter176 :return: lang matched from POEditor177 """178 lang = lang if lang else get_country_from_config_file()179 if lang in language_codes:180 matching_lang = lang181 elif lang.split('-')[0] in language_codes:182 matching_lang = lang.split('-')[0]183 else:184 assert False, f"Language {lang} is not included in valid codes: {', '.join(language_codes)}"185 return matching_lang186def get_poeditor_projects():187 """188 Get the list of the projects configured in POEditor189 :return: POEditor projects list190 """191 params = {"api_token": get_poeditor_api_token()}192 r = send_poeditor_request(ENDPOINT_POEDITOR_LIST_PROJECTS, "POST", params, 200)193 response_data = r.json()194 assert_poeditor_response_code(response_data, "200")195 projects = response_data['result']['projects']196 projects_names = [project['name'] for project in projects]197 logger.info('POEditor projects: %s %s' % (len(projects_names), projects_names))198 return projects199def send_poeditor_request(endpoint, method, params, status_code):200 """201 Send a request to the POEditor API202 :param endpoint: endpoint path203 :param method: HTTP method to be used in the request204 :param params: parameters to be sent in the request205 :param code: expected status code206 :return: response207 """208 try:209 base_url = dataset.project_config['poeditor']['base_url']210 except KeyError:211 base_url = 'https://api.poeditor.com'212 url = f'{base_url}/{endpoint}'213 r = requests.request(method, url, data=params)214 assert r.status_code == status_code, f"{r.status_code} status code has been received instead of {status_code} \215 in POEditor response calling to {url}. Response body: {r.json()}"216 return r217def get_all_terms(project_info, lang):218 """219 Get all terms for a given language configured in POEditor220 :param project_info: project_info221 :param lang: a valid language configured in POEditor project222 :return: the list of terms223 """224 params = {"api_token": get_poeditor_api_token(),225 "id": project_info['id'],226 "language": lang}227 r = send_poeditor_request(ENDPOINT_POEDITOR_LIST_TERMS, "POST", params, 200)228 response_data = r.json()229 assert_poeditor_response_code(response_data, "200")230 terms = response_data['result']['terms']231 logger.info('POEditor terms in "%s" project with "%s" language: %s' % (project_info['name'], lang, len(terms)))232 return terms233def load_poeditor_texts(context=None):234 """235 Download POEditor texts and save in output folder if the config exists or use previously downloaded texts236 :param context: behave context (deprecated parameter)237 """238 if context:239 logger.warning('Deprecated context parameter has been sent to load_poeditor_texts method. Please, '240 'configure POEditor global variables instead of passing context to load_poeditor_texts.')241 if get_poeditor_api_token():242 # Try to get poeditor mode param from toolium config first243 poeditor_mode = dataset.toolium_config.get_optional('TestExecution', 'poeditor_mode')244 if poeditor_mode:245 dataset.project_config['poeditor']['mode'] = poeditor_mode246 if 'mode' in dataset.project_config['poeditor'] and map_param('[CONF:poeditor.mode]') == 'offline':247 file_path = get_poeditor_file_path()248 # With offline POEditor mode, file must exist249 if not os.path.exists(file_path):250 error_message = 'You are using offline POEditor mode but poeditor file has not been found in %s' % \251 file_path252 logger.error(error_message)253 assert False, error_message254 with open(file_path, 'r') as f:255 dataset.poeditor_terms = json.load(f)256 if context:257 # Workaround for backwards compatibility258 context.poeditor_export = dataset.poeditor_terms259 last_mod_time = time.strftime('%d/%m/%Y %H:%M:%S', time.localtime(os.path.getmtime(file_path)))260 logger.info('Using local POEditor file "%s" with date: %s' % (file_path, last_mod_time))261 else: # without mode configured or mode = 'online'262 download_poeditor_texts(context)263 else:264 logger.info("POEditor is not configured")265def get_poeditor_file_path():266 """267 Get configured POEditor file path or default file path268 :return: POEditor file path269 """270 try:271 file_path = dataset.project_config['poeditor']['file_path']272 except KeyError:273 file_path = os.path.join(DriverWrappersPool.output_directory, 'poeditor_terms.json')274 return file_path275def get_poeditor_api_token():276 """...

Full Screen

Full Screen

test_poeditor.py

Source:test_poeditor.py Github

copy

Full Screen

...52 "project_name": "My-Bot"53 }54 }55 poeditor.logger = mock.MagicMock()56 load_poeditor_texts()57 poeditor.logger.info.assert_called_with("POEditor is not configured")58def test_load_poeditor_texts_without_api_token_deprecated():59 """60 Verification of POEditor texts load abortion when api_token is not configured and deprecated message is logged61 """62 context = mock.MagicMock()63 poeditor.logger = mock.MagicMock()64 load_poeditor_texts(context)65 poeditor.logger.info.assert_called_with("POEditor is not configured")66 poeditor.logger.warning.assert_called_with("Deprecated context parameter has been sent to load_poeditor_texts "67 "method. Please, configure POEditor global variables instead of passing "...

Full Screen

Full Screen

Automation Testing Tutorials

Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.

LambdaTest Learning Hubs:

YouTube

You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.

Run toolium automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful