Skip to content

Commit fddbcbd

Browse files
Fix when 2 function have the same name, run in parallel (#135)
* Fix when 2 function have the same name, run in parallel * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix typo, do not use shell=True to run grep command * use built-in types * use built-in types * check that we run from git repositoyrt * Remove commented line * remove deplicate debug log --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 96f33c7 commit fddbcbd

File tree

12 files changed

+136
-90
lines changed

12 files changed

+136
-90
lines changed

apps/jira_utils/jira_information.py

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
1+
from __future__ import annotations
2+
3+
import concurrent.futures
14
import logging
5+
import os
26
import re
37
import sys
4-
import os
5-
import concurrent.futures
68
from functools import lru_cache
9+
from typing import Any
710

811
import click
9-
from jira import JIRA, JIRAError, Issue
10-
12+
from jira import JIRA, Issue, JIRAError
1113
from simple_logger.logger import get_logger
12-
13-
from apps.utils import ListParamType
14-
from typing import Dict, List, Set, Tuple, Any
15-
1614
from tenacity import retry, retry_if_exception_type, stop_after_attempt, wait_fixed
17-
from apps.utils import all_python_files, get_util_config
15+
16+
from apps.utils import ListParamType, all_python_files, get_util_config
1817

1918
LOGGER = get_logger(name=__name__)
2019

@@ -29,7 +28,7 @@ def get_issue(
2928
return jira.issue(id=jira_id, fields="status, issuetype, fixVersions")
3029

3130

32-
def get_jira_ids_from_file_content(file_content: str, issue_pattern: str, jira_url: str) -> Set[str]:
31+
def get_jira_ids_from_file_content(file_content: str, issue_pattern: str, jira_url: str) -> set[str]:
3332
"""
3433
Try to find all Jira tickets in a given file content.
3534
@@ -55,7 +54,7 @@ def get_jira_ids_from_file_content(file_content: str, issue_pattern: str, jira_u
5554
return set(_pytest_jira_marker_bugs + _jira_id_arguments + _jira_url_jiras)
5655

5756

58-
def get_jiras_from_python_files(issue_pattern: str, jira_url: str) -> Dict[str, Set[str]]:
57+
def get_jiras_from_python_files(issue_pattern: str, jira_url: str) -> dict[str, set[str]]:
5958
"""
6059
Get all python files from the current directory and get list of jira ids from each of them
6160
@@ -68,7 +67,7 @@ def get_jiras_from_python_files(issue_pattern: str, jira_url: str) -> Dict[str,
6867
6968
Note: any line containing <skip-jira_utils-check> would be not be checked for presence of a jira id
7069
"""
71-
jira_found: Dict[str, Set[str]] = {}
70+
jira_found: dict[str, set[str]] = {}
7271
for filename in all_python_files():
7372
file_content = []
7473
with open(filename) as fd:
@@ -91,12 +90,12 @@ def get_jiras_from_python_files(issue_pattern: str, jira_url: str) -> Dict[str,
9190
def get_jira_information(
9291
jira_object: JIRA,
9392
jira_id: str,
94-
skip_project_ids: List[str],
95-
resolved_status: List[str],
96-
jira_target_versions: List[str],
93+
skip_project_ids: list[str],
94+
resolved_status: list[str],
95+
jira_target_versions: list[str],
9796
target_version_str: str,
9897
file_name: str,
99-
) -> Tuple[str, str]:
98+
) -> tuple[str, str]:
10099
jira_error_string = ""
101100
try:
102101
# check resolved status:
@@ -133,11 +132,11 @@ def process_jira_command_line_config_file(
133132
url: str,
134133
token: str,
135134
issue_pattern: str,
136-
resolved_statuses: List[str],
135+
resolved_statuses: list[str],
137136
version_string_not_targeted_jiras: str,
138-
target_versions: List[str],
139-
skip_projects: List[str],
140-
) -> Dict[str, Any]:
137+
target_versions: list[str],
138+
skip_projects: list[str],
139+
) -> dict[str, Any]:
141140
# Process all the arguments passed from command line or config file or environment variable
142141
config_dict = get_util_config(util_name="pyutils-jira", config_file_path=config_file_path)
143142
url = url or config_dict.get("url", "")
@@ -201,11 +200,11 @@ def process_jira_command_line_config_file(
201200
@click.option("--verbose", default=False, is_flag=True)
202201
def get_jira_mismatch(
203202
config_file_path: str,
204-
target_versions: List[str],
203+
target_versions: list[str],
205204
url: str,
206205
token: str,
207-
skip_projects: List[str],
208-
resolved_statuses: List[str],
206+
skip_projects: list[str],
207+
resolved_statuses: list[str],
209208
issue_pattern: str,
210209
version_string_not_targeted_jiras: str,
211210
verbose: bool,
@@ -228,7 +227,7 @@ def get_jira_mismatch(
228227
)
229228

230229
jira_obj = JIRA(token_auth=jira_config_dict["token"], options={"server": jira_config_dict["url"]})
231-
jira_error: Dict[str, str] = {}
230+
jira_error: dict[str, str] = {}
232231

233232
if jira_id_dict := get_jiras_from_python_files(
234233
issue_pattern=jira_config_dict["issue_pattern"], jira_url=jira_config_dict["url"]

apps/polarion/polarion_set_automated.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
11
from __future__ import annotations
2+
23
import logging
34
import os
4-
from simple_logger.logger import get_logger
55
import sys
6-
import click
76

8-
from apps.polarion.polarion_utils import get_polarion_project_id, find_polarion_ids, update_polarion_ids
9-
from typing import List, Dict, Optional
7+
import click
8+
from simple_logger.logger import get_logger
109

10+
from apps.polarion.polarion_utils import find_polarion_ids, get_polarion_project_id, update_polarion_ids
1111

1212
LOGGER = get_logger(name=__name__)
1313

1414

15-
def approve_tests(polarion_project_id: str, added_ids: List[str]) -> Dict[str, List[str]]:
15+
def approve_tests(polarion_project_id: str, added_ids: list[str]) -> dict[str, list[str]]:
1616
LOGGER.debug(f"Following polarion ids were added: {added_ids}")
1717
return update_polarion_ids(
1818
polarion_ids=list(added_ids), project_id=polarion_project_id, is_automated=True, is_approved=True
1919
)
2020

2121

2222
def remove_approved_tests(
23-
polarion_project_id: str, branch: str, added_ids: Optional[List[str]] = None
24-
) -> Dict[str, List[str]]:
23+
polarion_project_id: str, branch: str, added_ids: list[str] | None = None
24+
) -> dict[str, list[str]]:
2525
removed_polarions = {}
2626
added_ids = added_ids or []
2727
if removed_ids := set(

apps/polarion/polarion_utils.py

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
from __future__ import annotations
2+
23
import re
3-
import sys
4-
from simple_logger.logger import get_logger
54
import shlex
65
import subprocess
6+
import sys
7+
8+
from simple_logger.logger import get_logger
79

810
from apps.utils import get_util_config
9-
from typing import Dict, List
1011

1112
LOGGER = get_logger(name=__name__)
1213
AUTOMATED = "automated"
@@ -19,8 +20,8 @@ def git_diff(branch: str) -> str:
1920
return data.decode()
2021

2122

22-
def git_diff_lines(branch: str) -> Dict[str, List[str]]:
23-
diff: Dict[str, List[str]] = {}
23+
def git_diff_lines(branch: str) -> dict[str, list[str]]:
24+
diff: dict[str, list[str]] = {}
2425
for line in git_diff(branch=branch).splitlines():
2526
LOGGER.debug(line)
2627
if line.startswith("+"):
@@ -31,13 +32,13 @@ def git_diff_lines(branch: str) -> Dict[str, List[str]]:
3132

3233

3334
def validate_polarion_requirements(
34-
polarion_test_ids: List[str],
35+
polarion_test_ids: list[str],
3536
polarion_project_id: str,
36-
) -> List[str]:
37-
tests_with_missing_requirements: List[str] = []
37+
) -> list[str]:
38+
tests_with_missing_requirements: list[str] = []
3839
if polarion_test_ids:
39-
from pylero.work_item import TestCase, Requirement
4040
from pylero.exceptions import PyleroLibException
41+
from pylero.work_item import Requirement, TestCase
4142

4243
for _id in polarion_test_ids:
4344
has_req = False
@@ -57,7 +58,7 @@ def validate_polarion_requirements(
5758
return tests_with_missing_requirements
5859

5960

60-
def find_polarion_ids(polarion_project_id: str, string_to_match: str, branch: str) -> List[str]:
61+
def find_polarion_ids(polarion_project_id: str, string_to_match: str, branch: str) -> list[str]:
6162
return re.findall(
6263
rf"pytest.mark.polarion.*({polarion_project_id}-[0-9]+)",
6364
"\n".join(git_diff_lines(branch=branch).get(string_to_match, [])),
@@ -74,14 +75,14 @@ def get_polarion_project_id(util_name: str, config_file_path: str) -> str:
7475

7576

7677
def update_polarion_ids(
77-
project_id: str, is_automated: bool, polarion_ids: List[str], is_approved: bool = False
78-
) -> Dict[str, List[str]]:
79-
updated_ids: Dict[str, List[str]] = {}
78+
project_id: str, is_automated: bool, polarion_ids: list[str], is_approved: bool = False
79+
) -> dict[str, list[str]]:
80+
updated_ids: dict[str, list[str]] = {}
8081
if polarion_ids:
8182
automation_status = AUTOMATED if is_automated else NOT_AUTOMATED
8283

83-
from pylero.work_item import TestCase
8484
from pylero.exceptions import PyleroLibException
85+
from pylero.work_item import TestCase
8586

8687
for id in polarion_ids:
8788
try:

apps/polarion/polarion_verify_tc_requirements.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import logging
2-
from simple_logger.logger import get_logger
32
import os
4-
import click
53
import sys
64

7-
from apps.polarion.polarion_utils import validate_polarion_requirements, find_polarion_ids, get_polarion_project_id
5+
import click
6+
from simple_logger.logger import get_logger
7+
8+
from apps.polarion.polarion_utils import find_polarion_ids, get_polarion_project_id, validate_polarion_requirements
89

910
LOGGER = get_logger(name="polarion-verify-tc-requirements")
1011

apps/unused_code/unused_code.py

Lines changed: 69 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
1+
from __future__ import annotations
2+
13
import ast
24
import logging
35
import os
46
import subprocess
57
import sys
8+
from concurrent.futures import Future, ThreadPoolExecutor, as_completed
9+
from typing import Any, Iterable
610

711
import click
812
from simple_logger.logger import get_logger
913

10-
from apps.utils import all_python_files, ListParamType, get_util_config
11-
from typing import Any, Iterable, List
14+
from apps.utils import ListParamType, all_python_files, get_util_config
1215

1316
LOGGER = get_logger(name=__name__)
1417

1518

1619
def is_fixture_autouse(func: ast.FunctionDef) -> bool:
17-
deco_list: List[Any] = func.decorator_list
20+
deco_list: list[Any] = func.decorator_list
1821
for deco in deco_list or []:
1922
if not hasattr(deco, "func"):
2023
continue
@@ -39,17 +42,55 @@ def _iter_functions(tree: ast.Module) -> Iterable[ast.FunctionDef]:
3942
yield elm
4043

4144

42-
def is_ignore_function_list(ignore_prefix_list: List[str], function: ast.FunctionDef) -> bool:
45+
def is_ignore_function_list(ignore_prefix_list: list[str], function: ast.FunctionDef) -> bool:
4346
ignore_function_lists = [
4447
function.name for ignore_prefix in ignore_prefix_list if function.name.startswith(ignore_prefix)
4548
]
4649
if ignore_function_lists:
47-
LOGGER.debug(f"Following functions are getting skipped: {ignore_function_lists}")
4850
return True
4951

5052
return False
5153

5254

55+
def process_file(py_file: str, func_ignore_prefix: list[str], file_ignore_list: list[str]) -> str:
56+
if os.path.basename(py_file) in file_ignore_list:
57+
LOGGER.debug(f"Skipping file: {py_file}")
58+
return ""
59+
60+
with open(py_file) as fd:
61+
tree = ast.parse(source=fd.read())
62+
63+
for func in _iter_functions(tree=tree):
64+
if func_ignore_prefix and is_ignore_function_list(ignore_prefix_list=func_ignore_prefix, function=func):
65+
LOGGER.debug(f"Skipping function: {func.name}")
66+
continue
67+
68+
if is_fixture_autouse(func=func):
69+
LOGGER.debug(f"Skipping `autouse` fixture function: {func.name}")
70+
continue
71+
72+
used = False
73+
_func_grep_found = subprocess.check_output(["git", "grep", "-w", func.name], shell=False)
74+
75+
for entry in _func_grep_found.decode().splitlines():
76+
_, _line = entry.split(":", 1)
77+
78+
if f"def {func.name}" in _line:
79+
continue
80+
81+
if _line.strip().startswith("#"):
82+
continue
83+
84+
if func.name in _line:
85+
used = True
86+
break
87+
88+
if not used:
89+
return f"{os.path.relpath(py_file)}:{func.name}:{func.lineno}:{func.col_offset} Is not used anywhere in the code."
90+
91+
return ""
92+
93+
5394
@click.command()
5495
@click.option(
5596
"--config-file-path",
@@ -67,42 +108,39 @@ def is_ignore_function_list(ignore_prefix_list: List[str], function: ast.Functio
67108
help="Provide a comma-separated string or list of function prefixes to exclude",
68109
type=ListParamType(),
69110
)
70-
@click.option("--verbose", default=False, is_flag=True)
111+
@click.option("--verbose", "-v", default=False, is_flag=True)
71112
def get_unused_functions(
72-
config_file_path: Any, exclude_files: Any, exclude_function_prefixes: Any, verbose: bool
73-
) -> Any:
113+
config_file_path: str, exclude_files: list[str], exclude_function_prefixes: list[str], verbose: bool
114+
) -> None:
74115
LOGGER.setLevel(logging.DEBUG if verbose else logging.INFO)
75116

76-
_unused_functions = []
117+
unused_functions: list[str] = []
77118
unused_code_config = get_util_config(util_name="pyutils-unusedcode", config_file_path=config_file_path)
78119
func_ignore_prefix = exclude_function_prefixes or unused_code_config.get("exclude_function_prefix", [])
79120
file_ignore_list = exclude_files or unused_code_config.get("exclude_files", [])
80121

81-
for py_file in all_python_files():
82-
if os.path.basename(py_file) in file_ignore_list:
83-
continue
84-
with open(py_file) as fd:
85-
tree = ast.parse(source=fd.read())
122+
jobs: list[Future] = []
123+
if not os.path.exists(".git"):
124+
LOGGER.error("Must be run from a git repository")
125+
sys.exit(1)
86126

87-
for func in _iter_functions(tree=tree):
88-
if func_ignore_prefix and is_ignore_function_list(ignore_prefix_list=func_ignore_prefix, function=func):
89-
continue
127+
with ThreadPoolExecutor() as executor:
128+
for py_file in all_python_files():
129+
jobs.append(
130+
executor.submit(
131+
process_file,
132+
py_file=py_file,
133+
func_ignore_prefix=func_ignore_prefix,
134+
file_ignore_list=file_ignore_list,
135+
)
136+
)
90137

91-
if is_fixture_autouse(func=func):
92-
continue
138+
for result in as_completed(jobs):
139+
if unused_func := result.result():
140+
unused_functions.append(unused_func)
93141

94-
_used = subprocess.check_output(
95-
f"git grep -w '{func.name}' | wc -l",
96-
shell=True,
97-
)
98-
used = int(_used.strip())
99-
if used < 2:
100-
_unused_functions.append(
101-
f"{os.path.relpath(py_file)}:{func.name}:{func.lineno}:{func.col_offset} Is"
102-
" not used anywhere in the code.",
103-
)
104-
if _unused_functions:
105-
click.echo("\n".join(_unused_functions))
142+
if unused_functions:
143+
click.echo("\n".join(unused_functions))
106144
sys.exit(1)
107145

108146

0 commit comments

Comments
 (0)