codecov-cli 11.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. codecov_cli/__init__.py +3 -0
  2. codecov_cli/commands/__init__.py +0 -0
  3. codecov_cli/commands/base_picking.py +75 -0
  4. codecov_cli/commands/commit.py +72 -0
  5. codecov_cli/commands/create_report_result.py +41 -0
  6. codecov_cli/commands/empty_upload.py +80 -0
  7. codecov_cli/commands/get_report_results.py +50 -0
  8. codecov_cli/commands/labelanalysis.py +269 -0
  9. codecov_cli/commands/process_test_results.py +273 -0
  10. codecov_cli/commands/report.py +65 -0
  11. codecov_cli/commands/send_notifications.py +46 -0
  12. codecov_cli/commands/staticanalysis.py +62 -0
  13. codecov_cli/commands/upload.py +316 -0
  14. codecov_cli/commands/upload_coverage.py +186 -0
  15. codecov_cli/commands/upload_process.py +133 -0
  16. codecov_cli/fallbacks.py +41 -0
  17. codecov_cli/helpers/__init__.py +0 -0
  18. codecov_cli/helpers/args.py +31 -0
  19. codecov_cli/helpers/ci_adapters/__init__.py +63 -0
  20. codecov_cli/helpers/ci_adapters/appveyor_ci.py +54 -0
  21. codecov_cli/helpers/ci_adapters/azure_pipelines.py +44 -0
  22. codecov_cli/helpers/ci_adapters/base.py +102 -0
  23. codecov_cli/helpers/ci_adapters/bitbucket_ci.py +42 -0
  24. codecov_cli/helpers/ci_adapters/bitrise_ci.py +37 -0
  25. codecov_cli/helpers/ci_adapters/buildkite.py +45 -0
  26. codecov_cli/helpers/ci_adapters/circleci.py +47 -0
  27. codecov_cli/helpers/ci_adapters/cirrus_ci.py +36 -0
  28. codecov_cli/helpers/ci_adapters/cloudbuild.py +70 -0
  29. codecov_cli/helpers/ci_adapters/codebuild.py +49 -0
  30. codecov_cli/helpers/ci_adapters/droneci.py +36 -0
  31. codecov_cli/helpers/ci_adapters/github_actions.py +90 -0
  32. codecov_cli/helpers/ci_adapters/gitlab_ci.py +56 -0
  33. codecov_cli/helpers/ci_adapters/heroku.py +36 -0
  34. codecov_cli/helpers/ci_adapters/jenkins.py +38 -0
  35. codecov_cli/helpers/ci_adapters/local.py +39 -0
  36. codecov_cli/helpers/ci_adapters/teamcity.py +37 -0
  37. codecov_cli/helpers/ci_adapters/travis_ci.py +44 -0
  38. codecov_cli/helpers/ci_adapters/woodpeckerci.py +36 -0
  39. codecov_cli/helpers/config.py +66 -0
  40. codecov_cli/helpers/encoder.py +49 -0
  41. codecov_cli/helpers/folder_searcher.py +114 -0
  42. codecov_cli/helpers/git.py +97 -0
  43. codecov_cli/helpers/git_services/__init__.py +14 -0
  44. codecov_cli/helpers/git_services/github.py +40 -0
  45. codecov_cli/helpers/glob.py +146 -0
  46. codecov_cli/helpers/logging_utils.py +77 -0
  47. codecov_cli/helpers/options.py +51 -0
  48. codecov_cli/helpers/request.py +198 -0
  49. codecov_cli/helpers/upload_type.py +15 -0
  50. codecov_cli/helpers/validators.py +13 -0
  51. codecov_cli/helpers/versioning_systems.py +201 -0
  52. codecov_cli/main.py +99 -0
  53. codecov_cli/opentelemetry.py +26 -0
  54. codecov_cli/plugins/__init__.py +92 -0
  55. codecov_cli/plugins/compress_pycoverage_contexts.py +141 -0
  56. codecov_cli/plugins/gcov.py +69 -0
  57. codecov_cli/plugins/pycoverage.py +134 -0
  58. codecov_cli/plugins/types.py +8 -0
  59. codecov_cli/plugins/xcode.py +117 -0
  60. codecov_cli/runners/__init__.py +80 -0
  61. codecov_cli/runners/dan_runner.py +64 -0
  62. codecov_cli/runners/pytest_standard_runner.py +184 -0
  63. codecov_cli/runners/types.py +33 -0
  64. codecov_cli/services/__init__.py +0 -0
  65. codecov_cli/services/commit/__init__.py +86 -0
  66. codecov_cli/services/commit/base_picking.py +24 -0
  67. codecov_cli/services/empty_upload/__init__.py +42 -0
  68. codecov_cli/services/report/__init__.py +169 -0
  69. codecov_cli/services/upload/__init__.py +169 -0
  70. codecov_cli/services/upload/file_finder.py +320 -0
  71. codecov_cli/services/upload/legacy_upload_sender.py +132 -0
  72. codecov_cli/services/upload/network_finder.py +49 -0
  73. codecov_cli/services/upload/upload_collector.py +198 -0
  74. codecov_cli/services/upload/upload_sender.py +232 -0
  75. codecov_cli/services/upload_completion/__init__.py +38 -0
  76. codecov_cli/services/upload_coverage/__init__.py +93 -0
  77. codecov_cli/types.py +88 -0
  78. codecov_cli-11.0.0.dist-info/METADATA +298 -0
  79. codecov_cli-11.0.0.dist-info/RECORD +83 -0
  80. codecov_cli-11.0.0.dist-info/WHEEL +5 -0
  81. codecov_cli-11.0.0.dist-info/entry_points.txt +3 -0
  82. codecov_cli-11.0.0.dist-info/licenses/LICENSE +201 -0
  83. codecov_cli-11.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,90 @@
1
+ import os
2
+ import re
3
+ import subprocess
4
+
5
+ from codecov_cli.helpers.ci_adapters.base import CIAdapterBase
6
+
7
+
8
+ class GithubActionsCIAdapter(CIAdapterBase):
9
+ # https://docs.github.com/en/actions/learn-github-actions/environment-variables
10
+ def detect(self) -> bool:
11
+ return bool(os.getenv("GITHUB_ACTIONS"))
12
+
13
+ def _get_commit_sha(self):
14
+ pr = self._get_pull_request_number()
15
+ commit = os.getenv("GITHUB_SHA")
16
+
17
+ if not pr:
18
+ return commit
19
+
20
+ # actions/checkout should be run with fetch-depth > 1 or set to 0 for this to work
21
+ completed_subprocess = subprocess.run(
22
+ ["git", "rev-parse", "HEAD^@"], capture_output=True
23
+ )
24
+
25
+ parents_hash = completed_subprocess.stdout.decode().strip().splitlines()
26
+ if len(parents_hash) == 2:
27
+ return parents_hash[1]
28
+
29
+ return commit
30
+
31
+ def _get_build_url(self):
32
+ server_url = os.getenv("GITHUB_SERVER_URL")
33
+ slug = self._get_slug()
34
+ build_code = self._get_build_code()
35
+
36
+ if server_url and slug and build_code:
37
+ return f"{server_url}/{slug}/actions/runs/{build_code}"
38
+
39
+ return None
40
+
41
+ def _get_build_code(self):
42
+ return os.getenv("GITHUB_RUN_ID")
43
+
44
+ def _get_job_code(self):
45
+ return os.getenv("GITHUB_WORKFLOW")
46
+
47
+ def _get_pull_request_number(self):
48
+ if not os.getenv("GITHUB_HEAD_REF"):
49
+ return None
50
+
51
+ pr_ref = os.getenv("GITHUB_REF")
52
+
53
+ if not pr_ref:
54
+ return None
55
+
56
+ match = re.search(r"refs/pull/(\d+)/merge", pr_ref)
57
+
58
+ if match is None:
59
+ return None
60
+
61
+ pr = match.group(1)
62
+
63
+ return pr or None
64
+
65
+ def _get_slug(self):
66
+ return os.getenv("GITHUB_REPOSITORY")
67
+
68
+ def _get_branch(self):
69
+ branch = os.getenv("GITHUB_HEAD_REF")
70
+ if branch:
71
+ return branch
72
+
73
+ branch_ref = os.getenv("GITHUB_REF")
74
+
75
+ if not branch_ref:
76
+ return None
77
+
78
+ match = re.search(r"refs/heads/(.*)", branch_ref)
79
+
80
+ if match is None:
81
+ return None
82
+
83
+ branch = match.group(1)
84
+ return branch or None
85
+
86
+ def _get_service(self):
87
+ return "github-actions"
88
+
89
+ def get_service_name(self):
90
+ return "GithubActions"
@@ -0,0 +1,56 @@
1
+ import os
2
+
3
+ from codecov_cli.helpers.ci_adapters.base import CIAdapterBase
4
+ from codecov_cli.helpers.git import parse_slug
5
+
6
+
7
+ class GitlabCIAdapter(CIAdapterBase):
8
+ # https://docs.gitlab.com/ee/ci/variables/predefined_variables.html
9
+ def detect(self) -> bool:
10
+ return bool(os.getenv("GITLAB_CI"))
11
+
12
+ def _get_commit_sha(self):
13
+ return (
14
+ os.getenv("CI_MERGE_REQUEST_SOURCE_BRANCH_SHA")
15
+ or os.getenv("CI_BUILD_REF")
16
+ or os.getenv("CI_COMMIT_SHA")
17
+ )
18
+
19
+ def _get_build_url(self):
20
+ return os.getenv("CI_JOB_URL")
21
+
22
+ def _get_build_code(self):
23
+ return os.getenv("CI_BUILD_ID") or os.getenv("CI_JOB_ID")
24
+
25
+ def _get_job_code(self):
26
+ return os.getenv("CI_JOB_ID")
27
+
28
+ def _get_pull_request_number(self):
29
+ return os.getenv("CI_MERGE_REQUEST_IID")
30
+
31
+ def _get_slug(self):
32
+ slug = os.getenv("CI_PROJECT_PATH")
33
+ if slug:
34
+ return slug
35
+
36
+ owner = os.getenv("CI_PROJECT_NAMESPACE")
37
+ project_name = os.getenv("CI_PROJECT_NAME")
38
+
39
+ if owner and project_name:
40
+ return f"{owner}/{project_name}"
41
+
42
+ remote_address = os.getenv("CI_BUILD_REPO") or os.getenv("CI_REPOSITORY_URL")
43
+
44
+ if remote_address:
45
+ return parse_slug(remote_address)
46
+
47
+ return None
48
+
49
+ def _get_branch(self):
50
+ return os.getenv("CI_BUILD_REF_NAME") or os.getenv("CI_COMMIT_REF_NAME")
51
+
52
+ def _get_service(self):
53
+ return "gitlab"
54
+
55
+ def get_service_name(self):
56
+ return "GitlabCI"
@@ -0,0 +1,36 @@
1
+ import os
2
+
3
+ from codecov_cli.helpers.ci_adapters.base import CIAdapterBase
4
+
5
+
6
+ class HerokuCIAdapter(CIAdapterBase):
7
+ # https://devcenter.heroku.com/articles/heroku-ci#immutable-environment-variables
8
+ def detect(self) -> bool:
9
+ return bool(os.getenv("CI")) and bool(os.getenv("HEROKU_TEST_RUN_BRANCH"))
10
+
11
+ def _get_branch(self):
12
+ return os.getenv("HEROKU_TEST_RUN_BRANCH")
13
+
14
+ def _get_commit_sha(self):
15
+ return os.getenv("HEROKU_TEST_RUN_COMMIT_VERSION")
16
+
17
+ def _get_slug(self):
18
+ return None
19
+
20
+ def _get_service(self):
21
+ return "heroku"
22
+
23
+ def _get_build_url(self):
24
+ return None
25
+
26
+ def _get_build_code(self):
27
+ return os.getenv("HEROKU_TEST_RUN_ID")
28
+
29
+ def _get_job_code(self):
30
+ return None
31
+
32
+ def _get_pull_request_number(self):
33
+ return None
34
+
35
+ def get_service_name(self):
36
+ return "Heroku"
@@ -0,0 +1,38 @@
1
+ import os
2
+
3
+ from codecov_cli.helpers.ci_adapters.base import CIAdapterBase
4
+
5
+
6
+ class JenkinsAdapter(CIAdapterBase):
7
+ # https://www.jenkins.io/doc/book/pipeline/jenkinsfile/#using-environment-variables
8
+ # https://www.jenkins.io/doc/book/pipeline/multibranch/
9
+
10
+ def detect(self) -> bool:
11
+ return bool(os.getenv("JENKINS_URL"))
12
+
13
+ def _get_commit_sha(self):
14
+ return None
15
+
16
+ def _get_build_url(self):
17
+ return os.getenv("BUILD_URL")
18
+
19
+ def _get_build_code(self):
20
+ return os.getenv("BUILD_NUMBER")
21
+
22
+ def _get_job_code(self):
23
+ return None
24
+
25
+ def _get_pull_request_number(self):
26
+ return os.getenv("CHANGE_ID")
27
+
28
+ def _get_slug(self):
29
+ return None
30
+
31
+ def _get_branch(self):
32
+ return os.getenv("BRANCH_NAME")
33
+
34
+ def _get_service(self):
35
+ return "jenkins"
36
+
37
+ def get_service_name(self):
38
+ return "Jenkins"
@@ -0,0 +1,39 @@
1
+ import os
2
+ import subprocess
3
+
4
+ from codecov_cli.helpers.ci_adapters.base import CIAdapterBase
5
+
6
+ SUCCESS = 0
7
+
8
+
9
+ class LocalAdapter(CIAdapterBase):
10
+ def detect(self) -> bool:
11
+ s = subprocess.run(["git", "help"], capture_output=True)
12
+ return s.returncode == SUCCESS
13
+
14
+ def _get_branch(self):
15
+ return os.getenv("GIT_BRANCH") or os.getenv("BRANCH_NAME")
16
+
17
+ def _get_build_code(self):
18
+ return None
19
+
20
+ def _get_build_url(self):
21
+ return None
22
+
23
+ def _get_commit_sha(self):
24
+ return os.getenv("GIT_COMMIT")
25
+
26
+ def _get_slug(self):
27
+ return None
28
+
29
+ def _get_service(self):
30
+ return "local"
31
+
32
+ def _get_pull_request_number(self):
33
+ return None
34
+
35
+ def _get_job_code(self):
36
+ return None
37
+
38
+ def get_service_name(self):
39
+ return "Local"
@@ -0,0 +1,37 @@
1
+ import os
2
+
3
+ from codecov_cli.helpers.ci_adapters.base import CIAdapterBase
4
+
5
+
6
+ class TeamcityAdapter(CIAdapterBase):
7
+ # https://www.jetbrains.com/help/teamcity/predefined-build-parameters.html#Predefined+Server+Build+Parameters
8
+
9
+ def detect(self) -> bool:
10
+ return bool(os.getenv("TEAMCITY_VERSION"))
11
+
12
+ def _get_branch(self):
13
+ return os.getenv("BRANCH_NAME")
14
+
15
+ def _get_build_code(self):
16
+ return os.getenv("BUILD_NUMBER")
17
+
18
+ def _get_build_url(self):
19
+ return None
20
+
21
+ def _get_commit_sha(self):
22
+ return os.getenv("BUILD_VCS_NUMBER")
23
+
24
+ def _get_slug(self):
25
+ return None
26
+
27
+ def _get_service(self):
28
+ return "teamcity"
29
+
30
+ def _get_pull_request_number(self):
31
+ return None
32
+
33
+ def _get_job_code(self):
34
+ return None
35
+
36
+ def get_service_name(self):
37
+ return "Teamcity"
@@ -0,0 +1,44 @@
1
+ import os
2
+
3
+ from codecov_cli.helpers.ci_adapters.base import CIAdapterBase
4
+
5
+
6
+ class TravisCIAdapter(CIAdapterBase):
7
+ # https://docs.travis-ci.com/user/environment-variables/#default-environment-variables
8
+ def detect(self) -> bool:
9
+ return (
10
+ bool(os.getenv("CI"))
11
+ and bool(os.getenv("TRAVIS"))
12
+ and not bool(os.getenv("SHIPPABLE"))
13
+ )
14
+
15
+ def _get_commit_sha(self):
16
+ return os.getenv("TRAVIS_PULL_REQUEST_SHA") or os.getenv("TRAVIS_COMMIT")
17
+
18
+ def _get_build_url(self):
19
+ return os.getenv("TRAVIS_BUILD_WEB_URL")
20
+
21
+ def _get_build_code(self):
22
+ return os.getenv("TRAVIS_JOB_NUMBER")
23
+
24
+ def _get_job_code(self):
25
+ return os.getenv("TRAVIS_JOB_ID")
26
+
27
+ def _get_pull_request_number(self):
28
+ # The pull request number if the current job is a pull request, “false” if it’s not a pull request.
29
+ pr_num = os.getenv("TRAVIS_PULL_REQUEST")
30
+ return pr_num if pr_num != "false" else None
31
+
32
+ def _get_slug(self):
33
+ return os.getenv("TRAVIS_REPO_SLUG")
34
+
35
+ def _get_branch(self):
36
+ if os.getenv("TRAVIS_BRANCH") != os.getenv("TRAVIS_TAG"):
37
+ return os.getenv("TRAVIS_PULL_REQUEST_BRANCH") or os.getenv("TRAVIS_BRANCH")
38
+ return None
39
+
40
+ def _get_service(self):
41
+ return "travis"
42
+
43
+ def get_service_name(self):
44
+ return "Travis"
@@ -0,0 +1,36 @@
1
+ import os
2
+
3
+ from codecov_cli.helpers.ci_adapters.base import CIAdapterBase
4
+
5
+
6
+ class WoodpeckerCIAdapter(CIAdapterBase):
7
+ # https://woodpecker-ci.org/docs/usage/environment
8
+ def detect(self) -> bool:
9
+ return os.getenv("CI") == "woodpecker"
10
+
11
+ def _get_branch(self):
12
+ return os.getenv("CI_COMMIT_SOURCE_BRANCH") or os.getenv("CI_COMMIT_BRANCH")
13
+
14
+ def _get_build_code(self):
15
+ return os.getenv("CI_BUILD_NUMBER")
16
+
17
+ def _get_build_url(self):
18
+ return os.getenv("CI_BUILD_LINK")
19
+
20
+ def _get_commit_sha(self):
21
+ return os.getenv("CI_COMMIT_SHA")
22
+
23
+ def _get_slug(self):
24
+ return os.getenv("CI_REPO")
25
+
26
+ def _get_service(self):
27
+ return "woodpecker"
28
+
29
+ def _get_pull_request_number(self):
30
+ return os.getenv("CI_COMMIT_PULL_REQUEST")
31
+
32
+ def _get_job_code(self):
33
+ return os.getenv("CI_JOB_NUMBER")
34
+
35
+ def get_service_name(self):
36
+ return "Woodpecker"
@@ -0,0 +1,66 @@
1
+ import logging
2
+ import pathlib
3
+ import typing as t
4
+
5
+ import yaml
6
+
7
+ from codecov_cli.helpers.versioning_systems import get_versioning_system
8
+
9
+ logger = logging.getLogger("codecovcli")
10
+
11
+ CODECOV_API_URL = "https://api.codecov.io"
12
+ CODECOV_INGEST_URL = "https://ingest.codecov.io"
13
+ LEGACY_CODECOV_API_URL = "https://codecov.io"
14
+
15
+ # Relative to the project root
16
+ CODECOV_YAML_RECOGNIZED_DIRECTORIES = [
17
+ "",
18
+ ".github/",
19
+ "dev/",
20
+ ]
21
+
22
+ CODECOV_YAML_RECOGNIZED_FILENAMES = [
23
+ "codecov.yml",
24
+ "codecov.yaml",
25
+ ".codecov.yml",
26
+ ".codecov.yaml",
27
+ ]
28
+
29
+
30
+ def _find_codecov_yamls():
31
+ vcs = get_versioning_system()
32
+ vcs_root = vcs.get_network_root() if vcs else None
33
+ project_root = vcs_root if vcs_root else pathlib.Path.cwd()
34
+
35
+ yamls = []
36
+ for directory in CODECOV_YAML_RECOGNIZED_DIRECTORIES:
37
+ dir_candidate = project_root / directory
38
+ if not dir_candidate.exists() or not dir_candidate.is_dir():
39
+ continue
40
+
41
+ for filename in CODECOV_YAML_RECOGNIZED_FILENAMES:
42
+ file_candidate = dir_candidate / filename
43
+ if file_candidate.exists() and file_candidate.is_file():
44
+ yamls.append(file_candidate)
45
+
46
+ return yamls
47
+
48
+
49
+ def load_cli_config(codecov_yml_path: t.Optional[pathlib.Path]) -> t.Optional[dict]:
50
+ if not codecov_yml_path:
51
+ yamls = _find_codecov_yamls()
52
+ codecov_yml_path = yamls[0] if yamls else None
53
+
54
+ if not codecov_yml_path:
55
+ logger.warning("No config file could be found. Ignoring config.")
56
+ return None
57
+
58
+ if not codecov_yml_path.exists() or not codecov_yml_path.is_file():
59
+ logger.warning(
60
+ f"Config file {codecov_yml_path} not found, or is not a file. Ignoring config."
61
+ )
62
+ return None
63
+
64
+ logger.debug(f"Loading config from {codecov_yml_path}")
65
+ with open(codecov_yml_path, "r") as file_stream:
66
+ return yaml.safe_load(file_stream.read())
@@ -0,0 +1,49 @@
1
+ import re
2
+
3
+ slug_without_subgroups_regex = re.compile(r"[^/\s]+\/[^/\s]+$")
4
+ slug_with_subgroups_regex = re.compile(r"[^/\s]+(\/[^/\s]+)+$")
5
+ encoded_slug_regex = re.compile(r"[^:\s]+(:::[^:\s]+)*(::::[^:\s]+){1}$")
6
+
7
+
8
+ def encode_slug(slug: str):
9
+ if slug_with_subgroups_is_invalid(slug):
10
+ raise ValueError("The provided slug is invalid")
11
+ owner, repo = slug.rsplit("/", 1)
12
+ encoded_owner = ":::".join(owner.split("/"))
13
+ encoded_slug = "::::".join([encoded_owner, repo])
14
+ return encoded_slug
15
+
16
+
17
+ def decode_slug(slug: str):
18
+ if slug_encoded_incorrectly(slug):
19
+ raise ValueError("The slug is not encoded correctly")
20
+
21
+ owner, repo = slug.split("::::", 1)
22
+ decoded_owner = "/".join(owner.split(":::"))
23
+ decoded_slug = "/".join([decoded_owner, repo])
24
+ return decoded_slug
25
+
26
+
27
+ def slug_without_subgroups_is_invalid(slug: str):
28
+ """
29
+ Checks if slug is in the form of owner/repo
30
+ Returns True if it's invalid, otherwise return False
31
+ """
32
+ return not slug or not slug_without_subgroups_regex.match(slug)
33
+
34
+
35
+ def slug_with_subgroups_is_invalid(slug: str):
36
+ """
37
+ Checks if slug is in the form of owner/repo or owner/subgroup/repo
38
+ Returns True if it's invalid, otherwise return False
39
+ """
40
+ return not slug or not slug_with_subgroups_regex.match(slug)
41
+
42
+
43
+ def slug_encoded_incorrectly(slug: str):
44
+ """
45
+ Checks if slug is encoded incorrectly based on the encoding mechanism we use.
46
+ Checks if slug is in the form of owner:::subowner::::repo or owner::::repo
47
+ Returns True if invalid, otherwise returns False
48
+ """
49
+ return not slug or not encoded_slug_regex.match(slug)
@@ -0,0 +1,114 @@
1
+ import functools
2
+ import logging
3
+ import os
4
+ import pathlib
5
+ import re
6
+ from typing import Generator, List, Optional, Pattern
7
+
8
+ from codecov_cli.helpers.glob import translate
9
+
10
+ logger = logging.getLogger("codecovcli")
11
+
12
+
13
+ def _is_included(
14
+ filename_include_regex: Pattern,
15
+ multipart_include_regex: Optional[Pattern],
16
+ path: pathlib.Path,
17
+ ):
18
+ return filename_include_regex.match(path.name) and (
19
+ multipart_include_regex is None
20
+ or multipart_include_regex.match(path.resolve().as_posix())
21
+ )
22
+
23
+
24
+ def _is_excluded(
25
+ filename_exclude_regex: Optional[Pattern],
26
+ multipart_exclude_regex: Optional[Pattern],
27
+ path: pathlib.Path,
28
+ ):
29
+ return (
30
+ filename_exclude_regex is not None and filename_exclude_regex.match(path.name)
31
+ ) or (
32
+ multipart_exclude_regex is not None
33
+ and multipart_exclude_regex.match(path.as_posix())
34
+ )
35
+
36
+
37
+ def search_files(
38
+ folder_to_search: pathlib.Path,
39
+ folders_to_ignore: List[str],
40
+ *,
41
+ filename_include_regex: Pattern,
42
+ filename_exclude_regex: Optional[Pattern] = None,
43
+ multipart_include_regex: Optional[Pattern] = None,
44
+ multipart_exclude_regex: Optional[Pattern] = None,
45
+ search_for_directories: bool = False,
46
+ ) -> Generator[pathlib.Path, None, None]:
47
+ """ "
48
+ Searches for files or directories in a given folder
49
+
50
+ Parameters:
51
+ folder_to_search (pathlib.Path): in which folder you want the search to be
52
+ folders_to_ignore (list of str): what folders inside the folder_to_search to ignore and not search inside
53
+ filename_include_regex (regex): Regex for filenames only, this does not include the full path of the file
54
+ filename_exclude_regex (regex): Regex for filenames only, this does not include the full path of the file
55
+ multipart_include_regex (regex): Regex for full path of the files you want to include
56
+ multipart_exclude_regex (regex): Regex for full path of the files you want to exclude
57
+ search_for_directories (bool)
58
+
59
+ """
60
+ this_is_included = functools.partial(
61
+ _is_included, filename_include_regex, multipart_include_regex
62
+ )
63
+ this_is_excluded = functools.partial(
64
+ _is_excluded, filename_exclude_regex, multipart_exclude_regex
65
+ )
66
+ for dirpath, dirnames, filenames in os.walk(folder_to_search):
67
+ dirs_to_remove = set(d for d in dirnames if d in folders_to_ignore)
68
+
69
+ if multipart_exclude_regex is not None:
70
+ dirs_to_remove.union(
71
+ directory
72
+ for directory in dirnames
73
+ if multipart_exclude_regex.match(
74
+ (pathlib.Path(dirpath) / directory).as_posix()
75
+ )
76
+ )
77
+
78
+ for directory in dirs_to_remove:
79
+ # Removing to ensure we don't even try to search those
80
+ # This is the documented way of doing this on python docs
81
+ dirnames.remove(directory)
82
+
83
+ if search_for_directories:
84
+ for directory in dirnames:
85
+ dir_path = pathlib.Path(dirpath) / directory
86
+ if not this_is_excluded(dir_path) and this_is_included(dir_path):
87
+ yield dir_path
88
+ else:
89
+ for single_filename in filenames:
90
+ file_path = pathlib.Path(dirpath) / single_filename
91
+ if not this_is_excluded(file_path) and this_is_included(file_path):
92
+ yield file_path
93
+
94
+
95
+ def globs_to_regex(patterns: List[str]) -> Optional[Pattern]:
96
+ """
97
+ Converts a list of glob patterns to a combined ORed regex
98
+
99
+ Parameters:
100
+ patterns (List[str]): a list of globs, possibly empty
101
+
102
+ Returns:
103
+ (Pattern): a combined ORed regex, or None if patterns is an empty list
104
+ """
105
+ # if patterns is an empty list, avoid returning re.compile("") since it matches everything
106
+ if not patterns:
107
+ return None
108
+
109
+ regex_patterns = []
110
+ for pattern in patterns:
111
+ regex_pattern = translate(pattern, recursive=True, include_hidden=True)
112
+ logger.debug(f"Translating `{pattern}` into `{regex_pattern}`")
113
+ regex_patterns.append(regex_pattern)
114
+ return re.compile("|".join(regex_patterns))