Skip to content

Commit

Permalink
Ignore unresolved requirements
Browse files Browse the repository at this point in the history
  • Loading branch information
bwhmather committed Jan 26, 2024
1 parent 8f7e380 commit 18a1d77
Show file tree
Hide file tree
Showing 10 changed files with 32 additions and 267 deletions.
10 changes: 1 addition & 9 deletions src/ssort/__init__.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,13 @@
"""
The python source code statement sorter.
"""
from ssort._exceptions import (
DecodingError,
ParseError,
ResolutionError,
UnknownEncodingError,
WildcardImportError,
)
from ssort._exceptions import DecodingError, ParseError, UnknownEncodingError
from ssort._ssort import ssort

# Let linting tools know that we do mean to re-export exception classes.
assert DecodingError is not None
assert ParseError is not None
assert ResolutionError is not None
assert UnknownEncodingError is not None
assert WildcardImportError is not None

try:
from ssort._version import VERSION as __version__ # type: ignore
Expand Down
49 changes: 2 additions & 47 deletions src/ssort/_dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from ssort._graphs import Graph


def module_statements_graph(statements, *, on_unresolved, on_wildcard_import):
def module_statements_graph(statements):
"""
Constructs a graph of the interdependencies in a list of module level
statements.
Expand All @@ -11,16 +11,6 @@ def module_statements_graph(statements, *, on_unresolved, on_wildcard_import):
An ordered list of opaque `Statement` objects from which to construct
the graph.
:param on_unresolved:
An callback that should be invoked for each unresolved dependency. Can
safely raise any arbitrary exception to abort constructing the graph.
If no exception is raised, the graph returned by this function will not
contain a link for the missing requirement.
:param on_wildcard_import:
A callback that should be invoked if ssort detects a `*` import. If no
exception is raised, all dangling references will be pointed back to the
last `*` import.
:returns:
A `Graph` mapping from statements to the set of statements that they
depend on.
Expand All @@ -35,7 +25,6 @@ def module_statements_graph(statements, *, on_unresolved, on_wildcard_import):
for requirement in statement.requirements():
all_requirements.append(requirement)

# TODO error if requirement is not deferred.
if requirement.name in scope:
resolved[requirement] = scope[requirement.name]
continue
Expand All @@ -45,12 +34,6 @@ def module_statements_graph(statements, *, on_unresolved, on_wildcard_import):
continue

for name in statement.bindings():
if name == "*":
on_wildcard_import(
lineno=statement.node.lineno,
col_offset=statement.node.col_offset,
)

scope[name] = statement

# Patch up dependencies that couldn't be resolved immediately.
Expand All @@ -63,41 +46,13 @@ def module_statements_graph(statements, *, on_unresolved, on_wildcard_import):

resolved[requirement] = scope[requirement.name]

if "*" in scope:
for requirement in all_requirements:
if requirement in resolved:
continue

resolved[requirement] = scope["*"]

else:
unresolved = [
requirement
for requirement in all_requirements
if requirement not in resolved
]

for requirement in unresolved:
on_unresolved(
f"could not resolve {requirement.name!r}",
name=requirement.name,
lineno=requirement.lineno,
col_offset=requirement.col_offset,
)

if unresolved:
# Not safe to attempt to re-order in the event of unresolved
# dependencies. A typo could cause a statement to be moved ahead of
# something that it should depend on.
return None

graph = Graph()
for statement in statements:
graph.add_node(statement)

for statement in statements:
for requirement in statement.requirements():
if resolved[requirement] is not None:
if resolved.get(requirement) is not None:
graph.add_dependency(statement, resolved[requirement])

# Add links between statements that overwrite the same binding to make sure
Expand Down
15 changes: 0 additions & 15 deletions src/ssort/_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,3 @@ def __init__(self, msg, *, lineno, col_offset):
super().__init__(msg)
self.lineno = lineno
self.col_offset = col_offset


class ResolutionError(Exception):
def __init__(self, msg, *, name, lineno, col_offset):
super().__init__(msg)
self.name = name
self.lineno = lineno
self.col_offset = col_offset


class WildcardImportError(Exception):
def __init__(self, msg, *, lineno, col_offset):
super().__init__(msg)
self.lineno = lineno
self.col_offset = col_offset
17 changes: 0 additions & 17 deletions src/ssort/_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,28 +104,11 @@ def _on_parse_error(message, *, lineno, col_offset, **kwargs):
+ f"line {lineno}, column {col_offset}\n"
)

def _on_unresolved(message, *, name, lineno, col_offset, **kwargs):
nonlocal errors
errors = True

sys.stderr.write(
f"ERROR: unresolved dependency {name!r} "
+ f"in {escape_path(path)}: "
+ f"line {lineno}, column {col_offset}\n"
)

def _on_wildcard_import(**kwargs):
sys.stderr.write(
"WARNING: can't determine dependencies on * import\n"
)

try:
updated = ssort(
original,
filename=escape_path(path),
on_parse_error=_on_parse_error,
on_unresolved=_on_unresolved,
on_wildcard_import=_on_wildcard_import,
)

if errors:
Expand Down
67 changes: 2 additions & 65 deletions src/ssort/_ssort.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,7 @@
class_statements_runtime_graph,
module_statements_graph,
)
from ssort._exceptions import (
DecodingError,
ParseError,
ResolutionError,
UnknownEncodingError,
WildcardImportError,
)
from ssort._exceptions import DecodingError, ParseError, UnknownEncodingError
from ssort._graphs import (
is_topologically_sorted,
replace_cycles,
Expand Down Expand Up @@ -388,70 +382,19 @@ def _interpret_on_parse_error_action(on_parse_error):
return on_parse_error


def _on_unresolved_ignore(message, *, name, lineno, col_offset, **kwargs):
pass


def _on_unresolved_raise(message, *, name, lineno, col_offset, **kwargs):
raise ResolutionError(
message,
name=name,
lineno=lineno,
col_offset=col_offset,
)


def _interpret_on_unresolved_action(on_unresolved):
if on_unresolved == "ignore":
return _on_unresolved_ignore

if on_unresolved == "raise":
return _on_unresolved_raise

return on_unresolved


def _on_wildcard_import_ignore(**kwargs):
pass


def _on_wildcard_import_raise(*, lineno, col_offset, **kwargs):
raise WildcardImportError(
"can't reliably determine dependencies on * import",
lineno=lineno,
col_offset=col_offset,
)


def _interpret_on_wildcard_import_action(on_wildcard_import):
if on_wildcard_import == "ignore":
return _on_wildcard_import_ignore

if on_wildcard_import == "raise":
return _on_wildcard_import_raise

return on_wildcard_import


def ssort(
text,
*,
filename="<unknown>",
on_unknown_encoding_error="raise",
on_decoding_error="raise",
on_parse_error="raise",
on_unresolved="raise",
on_wildcard_import="raise",
):
on_unknown_encoding_error = _interpret_on_unknown_encoding_action(
on_unknown_encoding_error
)
on_decoding_error = _interpret_on_decoding_error_action(on_decoding_error)
on_parse_error = _interpret_on_parse_error_action(on_parse_error)
on_unresolved = _interpret_on_unresolved_action(on_unresolved)
on_wildcard_import = _interpret_on_wildcard_import_action(
on_wildcard_import
)

try:
encoding = None
Expand All @@ -478,13 +421,7 @@ def ssort(
if not statements:
return text

graph = module_statements_graph(
statements,
on_unresolved=on_unresolved,
on_wildcard_import=on_wildcard_import,
)
if graph is None:
return text
graph = module_statements_graph(statements)

replace_cycles(graph, key=sort_key_from_iter(statements))

Expand Down
8 changes: 1 addition & 7 deletions tests/test_dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@ def _clean(source):
return textwrap.dedent(source).strip() + "\n"


def _unreachable(*args, **kwargs):
raise AssertionError("unreachable")


def test_dependencies_ordered_by_first_use():
source = _clean(
"""
Expand All @@ -28,8 +24,6 @@ def b():
"""
)
c, a, b = statements = list(parse(source, filename="<unknown>"))
graph = module_statements_graph(
statements, on_unresolved=_unreachable, on_wildcard_import=_unreachable
)
graph = module_statements_graph(statements)

assert list(graph.dependencies[a]) == [b, c]
86 changes: 1 addition & 85 deletions tests/test_error_hooks.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
import pytest

from ssort import (
DecodingError,
ParseError,
ResolutionError,
UnknownEncodingError,
WildcardImportError,
ssort,
)
from ssort import DecodingError, ParseError, UnknownEncodingError, ssort


class _DummyException(Exception):
Expand Down Expand Up @@ -104,80 +97,3 @@ def on_parse_error(message, *, lineno, col_offset):
with pytest.raises(_DummyException) as exc_info:
ssort(original, on_parse_error=on_parse_error)
assert exc_info.value.args == ("invalid syntax", 1, 4)


# === Unresolved ===============================================================


def test_on_unresolved_raise():
original = "def fun():\n unresolved()"

with pytest.raises(ResolutionError) as exc_info:
ssort(original, on_unresolved="raise")

assert str(exc_info.value) == "could not resolve 'unresolved'"
assert exc_info.value.name == "unresolved"
assert exc_info.value.lineno == 2
assert exc_info.value.col_offset == 4


def test_on_unresolved_ignore():
original = "def fun():\n unresolved()"

actual = ssort(original, on_unresolved="ignore")

assert actual == original


def test_on_unresolved_callback():
original = "def fun():\n unresolved()"

def on_unresolved(message, *, name, lineno, col_offset):
raise _DummyException(message, name, lineno, col_offset)

with pytest.raises(_DummyException) as exc_info:
ssort(original, on_unresolved=on_unresolved)

assert exc_info.value.args == (
"could not resolve 'unresolved'",
"unresolved",
2,
4,
)


# === Wildcard Import ==========================================================


def test_on_wildcard_import_raise():
original = "from module import *"

with pytest.raises(WildcardImportError) as exc_info:
ssort(original, on_wildcard_import="raise")

assert (
str(exc_info.value)
== "can't reliably determine dependencies on * import"
)
assert exc_info.value.lineno == 1
assert exc_info.value.col_offset == 0


def test_on_wildcard_import_ignore():
original = "from module import *\n"

actual = ssort(original, on_wildcard_import="ignore")

assert actual == original


def test_on_wildcard_import_callback():
original = "from module import *"

def on_wildcard_import(*, lineno, col_offset):
raise _DummyException(lineno, col_offset)

with pytest.raises(_DummyException) as exc_info:
ssort(original, on_wildcard_import=on_wildcard_import)

assert exc_info.value.args == (1, 0)
Loading

0 comments on commit 18a1d77

Please sign in to comment.