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

Support 'exclusive' interfaces #674

Merged
merged 4 commits into from
Mar 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .api import MyApi
from .api import something

from module3 import content

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ visibility = ["<domain_root>", "//module5"]
layer = "mid"

[[interfaces]]
expose = [".*"]
expose = ["something"]
from = ["api"]
visibility = ["<domain_root>"]

[[interfaces]]
expose = ["MyAPI"]
from = ["api"]
visibility = ["//module5"]
exclusive = true

[[modules]]
path = "submodule1"
Expand Down
4 changes: 3 additions & 1 deletion src/checks/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ impl<'a> InterfaceChecker<'a> {
return InterfaceCheckResult::TopLevelModule;
}

let matching_interfaces = self.interfaces.get_interfaces(definition_module);
let matching_interfaces = self
.interfaces
.get_visible_interfaces(definition_module, usage_module);

if matching_interfaces.is_empty() {
return InterfaceCheckResult::NoInterfaces;
Expand Down
16 changes: 3 additions & 13 deletions src/config/cache.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
use pyo3::prelude::*;
use serde::{Deserialize, Serialize};

use super::utils;

#[derive(Debug, Serialize, Default, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum CacheBackend {
#[default]
Disk,
}

impl CacheBackend {
fn is_default(&self) -> bool {
*self == Self::default()
}
}

impl IntoPy<PyObject> for CacheBackend {
fn into_py(self, py: Python) -> PyObject {
match self {
Expand All @@ -25,16 +21,10 @@ impl IntoPy<PyObject> for CacheBackend {
#[derive(Debug, Serialize, Default, Deserialize, Clone, PartialEq)]
#[pyclass(get_all, module = "tach.extension")]
pub struct CacheConfig {
#[serde(default, skip_serializing_if = "CacheBackend::is_default")]
#[serde(default, skip_serializing_if = "utils::is_default")]
pub backend: CacheBackend,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub file_dependencies: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub env_dependencies: Vec<String>,
}

impl CacheConfig {
pub fn is_default(&self) -> bool {
*self == Self::default()
}
}
9 changes: 5 additions & 4 deletions src/config/domain.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::iter;
use std::ops::Not;
use std::path::{Path, PathBuf};

use pyo3::prelude::*;
Expand All @@ -9,7 +10,6 @@ use crate::filesystem::file_to_module_path;
use super::edit::{ConfigEdit, ConfigEditor, EditError};
use super::interfaces::InterfaceConfig;
use super::modules::{deserialize_modules, serialize_modules, DependencyConfig, ModuleConfig};
use super::utils::*;
use crate::parsing::error::ParsingError;

#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
Expand All @@ -23,9 +23,9 @@ pub struct DomainRootConfig {
pub layer: Option<String>,
#[serde(default)]
pub visibility: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "is_false")]
#[serde(default, skip_serializing_if = "Not::not")]
pub utility: bool,
#[serde(default, skip_serializing_if = "is_false")]
#[serde(default, skip_serializing_if = "Not::not")]
pub unchecked: bool,
}

Expand Down Expand Up @@ -183,7 +183,8 @@ impl Resolvable<InterfaceConfig> for InterfaceConfig {
})
.collect(),
visibility: self.visibility.clone().map(|vis| vis.resolve(location)),
data_types: self.data_types.clone(),
data_types: self.data_types,
exclusive: self.exclusive,
}
}
}
Expand Down
6 changes: 0 additions & 6 deletions src/config/external.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,3 @@ pub struct ExternalDependencyConfig {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub rename: Vec<String>,
}

impl ExternalDependencyConfig {
pub fn is_default(&self) -> bool {
*self == Self::default()
}
}
15 changes: 7 additions & 8 deletions src/config/interfaces.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use std::fmt::Display;
use std::ops::Not;

use pyo3::prelude::*;
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Default, Deserialize, Clone, PartialEq)]
use super::utils;

#[derive(Debug, Serialize, Default, Deserialize, Clone, Copy, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum InterfaceDataTypes {
#[default]
Expand All @@ -20,12 +23,6 @@ impl Display for InterfaceDataTypes {
}
}

impl InterfaceDataTypes {
fn is_default(&self) -> bool {
*self == Self::default()
}
}

impl IntoPy<PyObject> for InterfaceDataTypes {
fn into_py(self, py: Python) -> PyObject {
self.to_string().to_object(py)
Expand All @@ -45,8 +42,10 @@ pub struct InterfaceConfig {
pub from_modules: Vec<String>,
#[serde(default)]
pub visibility: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "InterfaceDataTypes::is_default")]
#[serde(default, skip_serializing_if = "utils::is_default")]
pub data_types: InterfaceDataTypes,
#[serde(default, skip_serializing_if = "Not::not")]
pub exclusive: bool,
}

fn default_from_modules() -> Vec<String> {
Expand Down
27 changes: 14 additions & 13 deletions src/config/modules.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
use crate::filesystem::module_path_is_included_in_paths;

use super::root_module::ROOT_MODULE_SENTINEL_TAG;
use super::utils::*;
use crate::resolvers::ModuleGlob;
use globset::GlobMatcher;
use pyo3::prelude::*;
use serde::ser::{Error, SerializeSeq, SerializeStruct};
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use std::hash::{Hash, Hasher};
use std::ops::Not;
use std::path::PathBuf;
use std::{
collections::{HashMap, HashSet},
fmt,
};

use globset::GlobMatcher;
use pyo3::prelude::*;
use serde::ser::{Error, SerializeSeq, SerializeStruct};
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};

use super::root_module::ROOT_MODULE_SENTINEL_TAG;
use crate::filesystem::module_path_is_included_in_paths;
use crate::resolvers::ModuleGlob;

#[derive(Clone, Debug, Default)]
#[pyclass(module = "tach.extension")]
pub struct DependencyConfig {
Expand Down Expand Up @@ -169,7 +170,7 @@ pub struct ModuleConfig {
#[serde(default)]
#[pyo3(get)]
pub visibility: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "is_false")]
#[serde(default, skip_serializing_if = "Not::not")]
#[pyo3(get)]
pub utility: bool,
// TODO: Remove this in a future version
Expand All @@ -179,7 +180,7 @@ pub struct ModuleConfig {
#[serde(default, skip_serializing)]
#[pyo3(get)]
pub strict: bool,
#[serde(default, skip_serializing_if = "is_false")]
#[serde(default, skip_serializing_if = "Not::not")]
#[pyo3(get)]
pub unchecked: bool,
// Hidden field to track grouping
Expand Down Expand Up @@ -390,9 +391,9 @@ struct BulkModule {
layer: Option<String>,
#[serde(default)]
visibility: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "is_false")]
#[serde(default, skip_serializing_if = "Not::not")]
utility: bool,
#[serde(default, skip_serializing_if = "is_false")]
#[serde(default, skip_serializing_if = "Not::not")]
unchecked: bool,
}

Expand Down
6 changes: 0 additions & 6 deletions src/config/plugins/all.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,3 @@ pub struct PluginsConfig {
#[serde(default)]
pub django: Option<DjangoConfig>,
}

impl PluginsConfig {
pub fn is_default(&self) -> bool {
*self == Self::default()
}
}
6 changes: 0 additions & 6 deletions src/config/plugins/django.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,3 @@ pub struct DjangoConfig {
#[serde(default)]
pub settings_module: String,
}

impl DjangoConfig {
pub fn is_default(&self) -> bool {
*self == Self::default()
}
}
30 changes: 17 additions & 13 deletions src/config/project.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use pyo3::prelude::*;
use serde::{Deserialize, Serialize};
use std::iter;
use std::ops::Not;
use std::path::PathBuf;

use crate::exclusion::PathExclusions;
Expand All @@ -17,7 +18,7 @@ use super::modules::{deserialize_modules, serialize_modules, DependencyConfig, M
use super::plugins::PluginsConfig;
use super::root_module::RootModuleTreatment;
use super::rules::RulesConfig;
use super::utils::*;
use super::utils;

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct PyProjectWrapper {
Expand Down Expand Up @@ -49,13 +50,13 @@ pub struct ProjectConfig {
#[serde(default)]
#[pyo3(get)]
pub interfaces: Vec<InterfaceConfig>,
#[serde(default, skip_serializing_if = "is_empty")]
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[pyo3(get)]
pub layers: Vec<String>,
#[serde(default, skip_serializing_if = "CacheConfig::is_default")]
#[serde(default, skip_serializing_if = "utils::is_default")]
#[pyo3(get)]
pub cache: CacheConfig,
#[serde(default, skip_serializing_if = "ExternalDependencyConfig::is_default")]
#[serde(default, skip_serializing_if = "utils::is_default")]
#[pyo3(get)]
pub external: ExternalDependencyConfig,
#[serde(default)]
Expand All @@ -64,31 +65,34 @@ pub struct ProjectConfig {
#[serde(default = "default_source_roots")]
#[pyo3(get, set)]
pub source_roots: Vec<PathBuf>,
#[serde(default, skip_serializing_if = "is_false")]
#[serde(default, skip_serializing_if = "Not::not")]
#[pyo3(get)]
pub exact: bool,
#[serde(default, skip_serializing_if = "is_false")]
#[serde(default, skip_serializing_if = "Not::not")]
#[pyo3(get)]
pub disable_logging: bool,
#[serde(default = "default_true", skip_serializing_if = "is_true")]
#[serde(
default = "utils::default_true",
skip_serializing_if = "utils::is_true"
)]
#[pyo3(get, set)]
pub ignore_type_checking_imports: bool,
#[serde(default, skip_serializing_if = "is_false")]
#[serde(default, skip_serializing_if = "Not::not")]
#[pyo3(get, set)]
pub include_string_imports: bool,
#[serde(default, skip_serializing_if = "is_false")]
#[serde(default, skip_serializing_if = "Not::not")]
#[pyo3(get)]
pub forbid_circular_dependencies: bool,
#[serde(default, skip_serializing_if = "is_false")]
#[serde(default, skip_serializing_if = "Not::not")]
#[pyo3(get, set)]
pub use_regex_matching: bool,
#[serde(default, skip_serializing_if = "RootModuleTreatment::is_default")]
#[serde(default, skip_serializing_if = "utils::is_default")]
#[pyo3(get)]
pub root_module: RootModuleTreatment,
#[serde(default, skip_serializing_if = "RulesConfig::is_default")]
#[serde(default, skip_serializing_if = "utils::is_default")]
#[pyo3(get)]
pub rules: RulesConfig,
#[serde(default, skip_serializing_if = "PluginsConfig::is_default")]
#[serde(default, skip_serializing_if = "utils::is_default")]
#[pyo3(get)]
pub plugins: PluginsConfig,
#[serde(skip)]
Expand Down
6 changes: 0 additions & 6 deletions src/config/root_module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,6 @@ pub enum RootModuleTreatment {
DependenciesOnly,
}

impl RootModuleTreatment {
pub fn is_default(&self) -> bool {
*self == Self::default()
}
}

impl IntoPy<PyObject> for RootModuleTreatment {
fn into_py(self, py: Python) -> PyObject {
match self {
Expand Down
6 changes: 0 additions & 6 deletions src/config/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,3 @@ impl Default for RulesConfig {
}
}
}

impl RulesConfig {
pub fn is_default(&self) -> bool {
*self == Self::default()
}
}
12 changes: 2 additions & 10 deletions src/config/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,10 @@ pub fn default_true() -> bool {
true
}

pub fn global_visibility() -> Vec<String> {
vec!["*".to_string()]
}

pub fn is_true(value: &bool) -> bool {
*value
}

pub fn is_false(value: &bool) -> bool {
!*value
}

pub fn is_empty<T>(value: &[T]) -> bool {
value.is_empty()
pub fn is_default<T: Default + PartialEq>(value: &T) -> bool {
value == &T::default()
}
Loading