-
Notifications
You must be signed in to change notification settings - Fork 49
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 wildcards in module ignores #103
Changes from all commits
a335233
afebf8b
a06d169
2c8e146
1879b9d
48de6ef
9d04da4
df7d5d5
42d6e34
6dbaf11
7b2d9a8
7342383
47b8618
9a8f390
fcdec67
ba6d956
41e38b9
d25b872
386a3e0
090b10a
008746a
60108d0
95df7fe
5264c33
b8f8865
64c6aa3
2188047
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,8 @@ | ||
from typing import Dict, Iterable, List, Union | ||
import itertools | ||
from typing import Dict, Iterable, List, Tuple, Union, Pattern | ||
import re | ||
|
||
from importlinter.domain.imports import DirectImport | ||
from importlinter.domain.imports import ImportExpression, Module, DirectImport | ||
from importlinter.domain.ports.graph import ImportGraph | ||
|
||
|
||
|
@@ -13,10 +15,8 @@ def pop_imports( | |
) -> List[Dict[str, Union[str, int]]]: | ||
""" | ||
Removes the supplied direct imports from the graph. | ||
|
||
Returns: | ||
The list of import details that were removed, including any additional metadata. | ||
|
||
Raises: | ||
MissingImport if the import is not present in the graph. | ||
""" | ||
|
@@ -34,6 +34,33 @@ def pop_imports( | |
return removed_imports | ||
|
||
|
||
def import_expressions_to_imports( | ||
graph: ImportGraph, expressions: Iterable[ImportExpression] | ||
) -> List[DirectImport]: | ||
imports: List[DirectImport] = [] | ||
for expression in expressions: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice. I think it would be worth extracting lines 41 - 53 into a separate It would be great to have unit test coverage too. If I were you I would focus the unit testing on the pure function There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added some basic tests |
||
matched = False | ||
for (importer, imported) in _expression_to_modules(expression, graph): | ||
import_details = graph.get_import_details( | ||
importer=importer.name, imported=imported.name | ||
) | ||
if import_details: | ||
imports.append(DirectImport(importer=importer, imported=imported)) | ||
matched = True | ||
if not matched: | ||
raise MissingImport( | ||
f"Ignored import expression {expression} didn't match anything in the graph." | ||
) | ||
return imports | ||
|
||
|
||
def pop_import_expressions( | ||
graph: ImportGraph, expressions: Iterable[ImportExpression] | ||
) -> List[Dict[str, Union[str, int]]]: | ||
imports = import_expressions_to_imports(graph, expressions) | ||
return pop_imports(graph, imports) | ||
|
||
|
||
def add_imports(graph: ImportGraph, import_details: List[Dict[str, Union[str, int]]]) -> None: | ||
""" | ||
Adds the supplied import details to the graph. | ||
|
@@ -55,3 +82,38 @@ def add_imports(graph: ImportGraph, import_details: List[Dict[str, Union[str, in | |
line_number=details["line_number"], | ||
line_contents=details["line_contents"], | ||
) | ||
|
||
|
||
def _to_pattern(expression: str) -> Pattern: | ||
kasium marked this conversation as resolved.
Show resolved
Hide resolved
|
||
""" | ||
Function which translates an import expression into a regex pattern. | ||
""" | ||
pattern_parts = [] | ||
for part in expression.split("."): | ||
if "*" == part: | ||
pattern_parts.append(part.replace("*", r"[^\.]+")) | ||
else: | ||
pattern_parts.append(part) | ||
return re.compile(r"\.".join(pattern_parts)) | ||
|
||
|
||
def _expression_to_modules( | ||
expression: ImportExpression, graph: ImportGraph | ||
) -> Iterable[Tuple[Module, Module]]: | ||
if not expression.has_wildcard_expression(): | ||
return [(Module(expression.importer), Module(expression.imported))] | ||
|
||
importer = [] | ||
imported = [] | ||
|
||
importer_pattern = _to_pattern(expression.importer) | ||
imported_expression = _to_pattern(expression.imported) | ||
|
||
for module in graph.modules: | ||
kasium marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if importer_pattern.match(module): | ||
importer.append(Module(module)) | ||
if imported_expression.match(module): | ||
imported.append(Module(module)) | ||
|
||
return itertools.product(importer, imported) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -86,3 +86,29 @@ def __str__(self) -> str: | |
|
||
def __hash__(self) -> int: | ||
return hash((str(self), self.line_contents)) | ||
|
||
|
||
class ImportExpression(ValueObject): | ||
""" | ||
A user-submitted expression describing an import or set of imports. | ||
|
||
Sets of imports are notated using * wildcards. | ||
kasium marked this conversation as resolved.
Show resolved
Hide resolved
|
||
These wildcards can stand in for a module name or part of a name, but they do | ||
not extend to subpackages. | ||
|
||
For example, "mypackage.*" refers to every child subpackage of mypackage. | ||
It does not, however, include more distant descendants such as mypackage.foo.bar. | ||
""" | ||
|
||
def __init__(self, importer: str, imported: str) -> None: | ||
self.importer = importer | ||
self.imported = imported | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps we should raise a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess, then it make sense to extract the import expression validationto a util function or NOT to validate in ImportExpressionField |
||
|
||
def has_wildcard_expression(self) -> bool: | ||
return "*" in self.imported or "*" in self.importer | ||
|
||
def __str__(self) -> str: | ||
return "{} -> {}".format(self.importer, self.imported) | ||
|
||
def __hash__(self) -> int: | ||
return hash((self.importer, self.imported)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Not related to documentation, but had to write this somewhere!) It would be great to have the following test coverage:
ignore_imports
that covers it, to the contract insetup.cfg
in that folder (which is used for checking contract adherence).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@seddonym I can work further on the pure functions but I guess I don't have the time for extended tests. I really see the point of them, but it consumes a lot of time. Would you maybe just add them?