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 for OnlyAllowContract #75

Open
iwanbolzern opened this issue Mar 27, 2020 · 2 comments
Open

Support for OnlyAllowContract #75

iwanbolzern opened this issue Mar 27, 2020 · 2 comments

Comments

@iwanbolzern
Copy link

Thank you for your great linter. This is exactly what I was looking for.

It would be great if it would support an OnlyAllow Contract, which should only allow the defined imports and no others. This would especially be handy with include_external_packages=False. This way you could simple code your architecture and don't have to define the inverse of your architecture every time.

Thanks in advance!

@seddonym
Copy link
Owner

Hi Iwan,

Thanks for your message - great idea!

If you'd find this useful, you may be interested that you can create custom contract types - so you don't need to wait around for me to write one! https://import-linter.readthedocs.io/en/stable/custom_contract_types.html

Let me know if you have any questions.

@tom-dudley
Copy link

I needed the same functionality so leaving this here in case it's of use to anyone. Note the use of find_modules_that_directly_import.

from grimp import ImportGraph
from importlinter.application import output
from importlinter.domain import fields
from importlinter.domain.contract import Contract, ContractCheck


class OnlyAllowContract(Contract):
    """
    OnlyAllow contract checks that only a set of allowed modules can import another set of target modules.
    Configuration options:
        - allowed_modules: A list of Modules that are allowed to import the target modules.
        - target_modules: A list of Modules that can be imported by the allowed modules.
    """

    type_name = "only_allow"

    allowed_module = fields.StringField
    allowed_modules = fields.ListField(subfield=fields.ModuleField())
    target_modules = fields.ListField(subfield=fields.ModuleField())

    def check(self, graph: ImportGraph, verbose: bool) -> ContractCheck:
        is_kept = True
        invalid_chains = []

        for target_module in self.target_modules:
            importing_modules = graph.find_modules_that_directly_import(target_module.name)
            forbidden_importers = importing_modules - {allowed_module.name for allowed_module in self.allowed_modules}

            if forbidden_importers:
                is_kept = False
                invalid_chains.append({
                    "target_module": target_module.name,
                    "forbidden_importers": forbidden_importers,
                })

        return ContractCheck(
            kept=is_kept, metadata={"invalid_chains": invalid_chains}
        )

    def render_broken_contract(self, check: "ContractCheck") -> None:
        for chain_data in check.metadata["invalid_chains"]:
            target_module = chain_data["target_module"]
            output.print_error(f"{target_module} is not allowed to be imported by:", bold=True)
            output.new_line()
            for forbidden_importer in chain_data["forbidden_importers"]:
                output.indent_cursor()
                output.print_error(forbidden_importer, bold=False)
                output.new_line()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants