Sample ReportΒΆ

Traceability Key Meta data File Path Function Line Range Contains Raw Code Source Code History (Commits)
PYTRACEABILITY-1 {'info': 'pytraceability searches a directory for traceability decorators'} pytraceability/collector.py PyTraceabilityCollector._get_file_paths 38 to 44 No
def _get_file_paths(self) -> Generator[Path, None, None]:
        _log.info("Using exclude patterns %s", self.config.exclude_patterns)
        for file_path in self.config.base_directory.rglob("*.py"):
            if file_is_excluded(file_path, self.config.exclude_patterns):
                _log.debug("Skipping %s", file_path)
                continue
            yield file_path
PYTRACEABILITY-2 {'info': 'pytraceability extracts traceability info from the decorators statically'} pytraceability/ast_processing.py extract_traceability_from_file_using_ast 143 to 156 No
def extract_traceability_from_file_using_ast(
    file_path: Path, decorator_name: str
) -> list[TraceabilityReport]:
    _log.info("Extracting traceability from file: %s", file_path)
    with open(file_path, "r") as f:
        source_code = f.read()
        try:
            tree = ast.parse(source_code, filename=file_path)
        except SyntaxError:
            _log.warning(f"Ignoring file due to syntax error: {file_path}")
            return []
        return TraceabilityVisitor(
            decorator_name, file_path=file_path, source_code=source_code
        ).visit(tree)
PYTRACEABILITY-3 {'info': "If pytraceability can't extract data statically, it has the option to try to extract it dynamically by importing the module."} pytraceability/collector.py PyTraceabilityCollector.collect 51 to 100 No
def collect(self) -> list[TraceabilityReport]:
        traceability_reports: dict[str, TraceabilityReport] = {}
        for file_path in self._get_file_paths():
            for report in extract_traceability_from_file_using_ast(
                file_path, self.config.decorator_name
            ):
                traceability_reports[report.key] = report

        incomplete_reports = [
            t for t in traceability_reports.values() if t.contains_raw_source_code
        ]
        _log.info(
            "%s traceability decorators contain raw source code.",
            len(incomplete_reports),
        )
        if (
            self.config.mode == PyTraceabilityMode.MODULE_IMPORT
            and len(incomplete_reports) > 0
        ):
            if self.config.python_root is None:  # pragma: no cover
                # Should never actually end up here, because the model_validator will
                # default this to base_directory, but we can't set it as non-optional
                # because it would break typing checking at model creation
                raise ValueError(
                    f"Python root directory must be set in {PyTraceabilityMode.MODULE_IMPORT} mode"
                )
            for file_path, traceabilities in groupby(
                incomplete_reports, attrgetter("file_path")
            ):
                for (
                    extracted_traceability
                ) in extract_traceabilities_using_module_import(
                    file_path, self.config.python_root, traceabilities
                ):
                    traceability_reports[
                        extracted_traceability.key
                    ].metadata = extracted_traceability.metadata

        if self.config.git_history_mode == GitHistoryMode.FUNCTION_HISTORY:
            _log.info("Collecting git history for traceability reports")
            git_histories = get_line_based_history(
                list(traceability_reports.values()), self.config
            )
            for traceability_key, git_history in git_histories.items():
                traceability_reports[traceability_key].history = git_history
        elif self.config.git_history_mode != GitHistoryMode.NONE:
            raise ValueError(
                f"Unsupported git history mode: {self.config.git_history_mode}"
            )
        return list(traceability_reports.values())
PYTRACEABILITY-4 {'info': "If pytraceability can't extract the key either statically or dynamically, anInvalidTraceabilityError is raised. This might happen for a closure where the traceability key is stored in a variable."} pytraceability/import_processing.py extract_traceabilities_using_module_import 69 to 77 No
def extract_traceabilities_using_module_import(
    file_path: Path,
    python_root: Path,
    traceability_reports: Iterator[TraceabilityReport],
) -> Generator[Traceability, None, None]:
    _log.info("Extracting traceability from %s using module import", file_path)
    module = _load_python_module(file_path, python_root)
    for traceability_report in traceability_reports:
        yield from _extract_traceability(module, traceability_report.function_name)
PYTRACEABILITY-5 {'info': 'pytraceability can extract a history of the code decorated by a given key from git'} pytraceability/history.py get_line_based_history 52 to 107 No
def get_line_based_history(
    traceability_reports: list[TraceabilityReport], config: PyTraceabilityConfig
) -> dict[str, list[TraceabilityGitHistory]]:
    current_file_for_key = CurrentFileForKey.from_traceability_reports(
        traceability_reports, config
    )

    history: dict[str, list[TraceabilityGitHistory]] = {}
    repo_root = get_repo_root(config.base_directory)
    for commit in Repository(
        str(repo_root),
        order="reverse",
        only_in_branch=config.git_branch,
    ).traverse_commits():
        current_file_set = set(current_file_for_key.values())
        relevant_files_first = sorted(
            commit.modified_files,
            key=lambda f: f.new_path in current_file_set,
        )

        current_file_for_key.reset_keys_for_relevant_files(relevant_files_first)
        for modified_file in relevant_files_first:
            if (
                modified_file.source_code is None
                or modified_file.new_path is None
                or not modified_file.new_path.endswith("py")
                or file_is_excluded(
                    Path(modified_file.new_path), config.exclude_patterns
                )
            ):
                continue
            _log.debug("Processing file %s", modified_file.new_path)
            tree = ast.parse(modified_file.source_code, filename=modified_file.new_path)
            traceability_reports = TraceabilityVisitor(
                config.decorator_name,
                file_path=Path(modified_file.new_path),
                source_code=modified_file.source_code,
            ).visit(tree)
            for traceability_report in traceability_reports:
                if traceability_report.key not in history:
                    history[traceability_report.key] = []
                history[traceability_report.key].append(
                    TraceabilityGitHistory(
                        commit=commit.hash,
                        author_name=commit.author.name,
                        author_date=commit.author_date,
                        message=commit.msg.strip(),
                        source_code=traceability_report.source_code,
                    )
                )
                current_file_for_key[traceability_report.key] = modified_file.new_path

            if all(current_file_for_key.values()):
                _log.info("All traceability decorators located for commit")
                break
    return history