Skip to content

Commit

Permalink
Merge pull request #445 from gauge-sh/add-check-result-to-upload
Browse files Browse the repository at this point in the history
add check result to upload
  • Loading branch information
caelean authored Nov 29, 2024
2 parents c3265c3 + 54eb91c commit b705d79
Show file tree
Hide file tree
Showing 17 changed files with 130 additions and 49 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "tach"
version = "0.16.0"
version = "0.16.1"
edition = "2021"

[lib]
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "tach"
version = "0.16.0"
version = "0.16.1"
authors = [
{ name = "Caelean Barnes", email = "[email protected]" },
{ name = "Evan Doyle", email = "[email protected]" },
Expand Down
2 changes: 1 addition & 1 deletion python/tach/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from __future__ import annotations

__version__ = "0.16.0"
__version__ = "0.16.1"

__all__ = ["__version__"]
26 changes: 1 addition & 25 deletions python/tach/cli.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from __future__ import annotations

import argparse
import re
import sys
from dataclasses import dataclass, field
from enum import Enum
Expand All @@ -27,7 +26,7 @@
from tach.filesystem import install_pre_commit
from tach.logging import LogDataModel, logger
from tach.modularity import export_report, upload_report_to_gauge
from tach.parsing import parse_project_config
from tach.parsing import extend_and_validate, parse_project_config
from tach.report import external_dependency_report, report
from tach.show import (
generate_module_graph_dot_file,
Expand Down Expand Up @@ -538,29 +537,6 @@ def check_cache_for_action(
return CachedOutput(key=cache_key)


def extend_and_validate(
exclude_paths: list[str] | None,
project_excludes: list[str],
use_regex_matching: bool,
) -> list[str]:
if exclude_paths is not None:
exclude_paths.extend(project_excludes)
else:
exclude_paths = project_excludes

if not use_regex_matching:
return exclude_paths

for exclude_path in exclude_paths:
try:
re.compile(exclude_path)
except re.error:
raise ValueError(
f"Invalid regex pattern: {exclude_path}. If you meant to use glob matching, set 'use_regex_matching' to false in your .toml file."
)
return exclude_paths


def tach_check(
project_root: Path,
exact: bool = False,
Expand Down
4 changes: 2 additions & 2 deletions python/tach/extension.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def check(
dependencies: bool,
interfaces: bool,
exclude_paths: list[str],
) -> CheckResult: ...
) -> CheckDiagnostics: ...
def sync_dependency_constraints(
project_root: Path,
project_config: ProjectConfig,
Expand Down Expand Up @@ -94,7 +94,7 @@ class BoundaryError:
import_mod_path: str
error_info: ErrorInfo

class CheckResult:
class CheckDiagnostics:
errors: list[BoundaryError]
deprecated_warnings: list[BoundaryError]
warnings: list[str]
Expand Down
94 changes: 85 additions & 9 deletions python/tach/modularity.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,21 @@
import re
from dataclasses import asdict, dataclass, field
from http.client import HTTPConnection, HTTPSConnection
from typing import TYPE_CHECKING, Any
from pathlib import Path
from typing import Any
from urllib import parse

from tach import filesystem as fs
from tach.colors import BCOLORS
from tach.errors import TachClosedBetaError, TachError
from tach.extension import (
CheckDiagnostics,
ProjectConfig,
check,
get_project_imports,
)
from tach.filesystem.git_ops import get_current_branch_info

if TYPE_CHECKING:
from pathlib import Path
from tach.parsing import extend_and_validate


def export_report(
Expand Down Expand Up @@ -47,7 +48,7 @@ def upload_report_to_gauge(


GAUGE_API_KEY = os.getenv("GAUGE_API_KEY", "")
GAUGE_API_BASE_URL = os.getenv("GAUGE_API_BASE_URL", "https://app.gauge.sh")
GAUGE_API_BASE_URL = os.getenv("GAUGE_API_BASE_URL", "http://localhost:8000")


def build_modularity_upload_path(repo: str) -> str:
Expand Down Expand Up @@ -106,6 +107,12 @@ class Usage:
containing_module_path: str | None


@dataclass
class Dependency:
path: str
deprecated: bool = False


@dataclass
class Module:
path: str
Expand All @@ -114,9 +121,11 @@ class Module:
# [1.2] Replaces 'is_strict'
has_interface: bool = False
interface_members: list[str] = field(default_factory=list)
# [1.3] Adds 'depends_on'
depends_on: list[Dependency] = field(default_factory=list)


REPORT_VERSION = "1.2"
REPORT_VERSION = "1.3"


@dataclass
Expand All @@ -125,6 +134,27 @@ class ReportMetadata:
configuration_format: str = "json"


@dataclass
class ErrorInfo:
is_deprecated: bool
pystring: str


@dataclass
class BoundaryError:
file_path: Path
line_number: int
import_mod_path: str
error_info: ErrorInfo


@dataclass
class CheckResult:
errors: list[BoundaryError] = field(default_factory=list)
deprecated_warnings: list[BoundaryError] = field(default_factory=list)
warnings: list[str] = field(default_factory=list)


@dataclass
class Report:
repo: str
Expand All @@ -134,6 +164,8 @@ class Report:
full_configuration: str
modules: list[Module] = field(default_factory=list)
usages: list[Usage] = field(default_factory=list)
check_result: CheckResult = field(default_factory=CheckResult)
metadata: ReportMetadata = field(default_factory=ReportMetadata)
# [1.2] Deprecated
interface_rules: list[Any] = field(default_factory=list)
metadata: ReportMetadata = field(default_factory=ReportMetadata)
Expand All @@ -145,6 +177,7 @@ def build_modules(project_config: ProjectConfig) -> list[Module]:
if module.mod_path() == ".":
# Skip <root>
continue

has_interface = False
interface_members: set[str] = set()
for interface in project_config.interfaces:
Expand All @@ -153,15 +186,18 @@ def build_modules(project_config: ProjectConfig) -> list[Module]:
):
has_interface = True
interface_members.update(interface.expose)

dependencies = [
Dependency(path=dep.path, deprecated=dep.deprecated)
for dep in module.depends_on
]
modules.append(
Module(
path=module.path,
has_interface=has_interface,
interface_members=list(interface_members),
depends_on=dependencies,
)
)

return modules


Expand Down Expand Up @@ -223,6 +259,36 @@ def get_containing_module(mod_path: str) -> str | None:
return usages


def process_check_result(check_diagnostics: CheckDiagnostics) -> CheckResult:
return CheckResult(
errors=[
BoundaryError(
file_path=error.file_path,
line_number=error.line_number,
import_mod_path=error.import_mod_path,
error_info=ErrorInfo(
is_deprecated=error.error_info.is_deprecated(),
pystring=error.error_info.to_pystring(),
),
)
for error in check_diagnostics.errors
],
deprecated_warnings=[
BoundaryError(
file_path=Path(warning.file_path),
line_number=warning.line_number,
import_mod_path=warning.import_mod_path,
error_info=ErrorInfo(
is_deprecated=warning.error_info.is_deprecated(),
pystring=warning.error_info.to_pystring(),
),
)
for warning in check_diagnostics.deprecated_warnings
],
warnings=check_diagnostics.warnings,
)


def generate_modularity_report(
project_root: Path, project_config: ProjectConfig, force: bool = False
) -> Report:
Expand All @@ -238,7 +304,17 @@ def generate_modularity_report(

report.modules = build_modules(project_config)
report.usages = build_usages(project_root, source_roots, project_config)

exclude_paths = extend_and_validate(
None, project_config.exclude, project_config.use_regex_matching
)
check_diagnostics = check(
project_root=project_root,
project_config=project_config,
exclude_paths=exclude_paths,
dependencies=True,
interfaces=False, # for now leave this as a separate concern
)
report.check_result = process_check_result(check_diagnostics)
print(f"{BCOLORS.OKGREEN} > Report generated!{BCOLORS.ENDC}")
return report

Expand Down
6 changes: 2 additions & 4 deletions python/tach/parsing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@

from tach.parsing.config import (
dump_project_config_to_toml,
extend_and_validate,
parse_project_config,
)

__all__ = [
"parse_project_config",
"dump_project_config_to_toml",
]
__all__ = ["parse_project_config", "dump_project_config_to_toml", "extend_and_validate"]
24 changes: 24 additions & 0 deletions python/tach/parsing/config.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import re
from pathlib import Path
from typing import TYPE_CHECKING, Any

Expand Down Expand Up @@ -98,3 +99,26 @@ def parse_project_config(root: Path | None = None) -> ProjectConfig | None:
# Return right away, this is a final ProjectConfig
project_config = migrate_deprecated_yaml_config(file_path)
return project_config


def extend_and_validate(
exclude_paths: list[str] | None,
project_excludes: list[str],
use_regex_matching: bool,
) -> list[str]:
if exclude_paths is not None:
exclude_paths.extend(project_excludes)
else:
exclude_paths = project_excludes

if not use_regex_matching:
return exclude_paths

for exclude_path in exclude_paths:
try:
re.compile(exclude_path)
except re.error:
raise ValueError(
f"Invalid regex pattern: {exclude_path}. If you meant to use glob matching, set 'use_regex_matching' to false in your .toml file."
)
return exclude_paths
1 change: 1 addition & 0 deletions python/tests/example/valid/domain_two/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from domain_three import x

from .. import domain_three

y = domain_three

__all__ = ["x", "ok"]
2 changes: 2 additions & 0 deletions python/tests/example/valid/domain_two/some_file.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from . import other

__all__ = ["other"]
1 change: 1 addition & 0 deletions python/tests/example/valid/leftover/mod.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from __future__ import annotations

import typing as t

from domain_one import x
Expand Down
6 changes: 3 additions & 3 deletions python/tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class BoundaryError:


@dataclass
class CheckResult:
class CheckDiagnostics:
errors: list[BoundaryError]
deprecated_warnings: list[BoundaryError]
warnings: list[str]
Expand All @@ -48,7 +48,7 @@ class CheckResult:
@pytest.fixture
def mock_check(mocker) -> Mock:
mock = Mock(
return_value=CheckResult(errors=[], deprecated_warnings=[], warnings=[])
return_value=CheckDiagnostics(errors=[], deprecated_warnings=[], warnings=[])
) # default to a return with no errors
mocker.patch("tach.cli.check", mock)
return mock
Expand Down Expand Up @@ -78,7 +78,7 @@ def test_execute_with_error(capfd, mock_check, mock_project_config):
# Mock an error returned from check
location = Path("valid_dir/file.py")
message = "Import valid_dir in valid_dir/file.py is blocked by boundary"
mock_check.return_value = CheckResult(
mock_check.return_value = CheckDiagnostics(
deprecated_warnings=[],
warnings=[],
errors=[
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ fn extension(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<core::config::InterfaceConfig>()?;
m.add_class::<core::config::RulesConfig>()?;
m.add_class::<core::config::DependencyConfig>()?;
m.add_class::<check_int::CheckDiagnostics>()?;
m.add_class::<test::TachPytestPluginHandler>()?;
m.add_function(wrap_pyfunction_bound!(parse_project_config, m)?)?;
m.add_function(wrap_pyfunction_bound!(get_project_imports, m)?)?;
Expand Down
2 changes: 1 addition & 1 deletion src/pattern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ impl PatternMatcher {
}

pub fn from_regex(pattern: &str) -> Result<Self, PathExclusionError> {
let pattern_from_start = if pattern.starts_with(r"^") {
let pattern_from_start = if pattern.starts_with('^') {
pattern.to_string()
} else {
format!(r"^{}", pattern)
Expand Down
Loading

0 comments on commit b705d79

Please sign in to comment.