Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add check result to upload #445

Merged
merged 37 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
d8ac854
Initial changes for modularity reporting, string import detection; v0…
emdoyle Oct 16, 2024
6a41572
Merge upstream
emdoyle Oct 16, 2024
81daece
Merge main
emdoyle Oct 17, 2024
0e46118
Merge upstream
emdoyle Oct 24, 2024
67d2af3
Report v1.1; containing module in Usage, full config encoded JSON
emdoyle Oct 24, 2024
633c19e
Include metadata in report body to support non-remote uploads
emdoyle Oct 24, 2024
9943cd4
Bump to 0.14.alpha2
emdoyle Oct 24, 2024
19e5af0
Fix output cli arg
emdoyle Oct 24, 2024
4e493d8
Merge upstream main
emdoyle Nov 6, 2024
7e4ecf2
Check for path exclusion on individual filenames to fix missing exclu…
emdoyle Nov 6, 2024
b958af8
Merge upstream main
emdoyle Nov 6, 2024
29036e3
Bump to 0.14.alpha3
emdoyle Nov 6, 2024
4355bc7
ruff
caelean Nov 15, 2024
f713604
upload tach check diag info
caelean Nov 15, 2024
dd2b499
dataclass it
caelean Nov 16, 2024
1d3e5f4
remove dc
caelean Nov 16, 2024
9f791d4
update check diag
caelean Nov 21, 2024
a3db59e
working upload
caelean Nov 21, 2024
39ff527
working check report upload
caelean Nov 21, 2024
d3912ec
Merge branch 'main' of github.com:gauge-sh/tach into add-check-result…
caelean Nov 21, 2024
29d5f75
revert to main
caelean Nov 21, 2024
49abd4c
revert readme
caelean Nov 21, 2024
0a3301c
revert readme link
caelean Nov 21, 2024
270e30f
report version
caelean Nov 21, 2024
0779685
fix up strict tach
caelean Nov 21, 2024
b31d644
Merge branch 'main' of github.com:gauge-sh/tach into add-check-result…
caelean Nov 21, 2024
b608733
Merge branch 'main' of github.com:gauge-sh/tach into add-check-result…
caelean Nov 22, 2024
b2cb8b7
include deps
caelean Nov 23, 2024
1c8153d
just single addition error
caelean Nov 25, 2024
dc41d8a
Merge branch 'main' of github.com:gauge-sh/tach into add-check-result…
caelean Nov 26, 2024
a4238df
move extend/validate
caelean Nov 26, 2024
41618d0
Merge branch 'main' of github.com:gauge-sh/tach into add-check-result…
caelean Nov 28, 2024
5125a99
bump version
caelean Nov 28, 2024
e17f646
ruff fixes
caelean Nov 29, 2024
eab6653
ruff format
caelean Nov 29, 2024
6cdc708
clippy suggestions
caelean Nov 29, 2024
54eb91c
update tach config
caelean Nov 29, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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