Skip to content

Commit

Permalink
added exception helper for modified after freeze (#881)
Browse files Browse the repository at this point in the history
* added exception helper for modified after freeze 

fixes #876
  • Loading branch information
wouterdb authored Feb 7, 2019
2 parents 1d43fab + 9a3eb75 commit 877e0ac
Show file tree
Hide file tree
Showing 18 changed files with 782 additions and 34 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Changes in this release:
- Improved lazy execution for attributes
- Updated autogenerated config file for agents with correct server hostname (#892)
- Add support to run the compiler on windows
- Added exception explainer to compiler for 'modified after freeze' (#876)

v 2018.3 (2018-12-07)
Changes in this release:
Expand Down
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ graft docs
include tox.ini
include setup.py

recursive-include src *.j2
recursive-include docs *
recursive-include misc *
recursive-include tests *
Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@ tornado==5.1.1
tox==3.7.0
tox-venv==0.3.1
typing==3.6.6
jinja2==2.10
pep8-naming==0.8.2
flake8==3.7.5
flake8==3.7.5
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"typing",
"PyJWT",
"cryptography",
"jinja2",
]

setup(
Expand Down
14 changes: 12 additions & 2 deletions src/inmanta/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,10 @@ def cmd_parser():
return parser


def _is_on_tty() -> bool:
return (hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()) or const.ENVIRON_FORCE_TTY in os.environ


def _get_default_stream_handler():
stream_handler = logging.StreamHandler(stream=sys.stdout)
stream_handler.setLevel(logging.INFO)
Expand All @@ -380,13 +384,13 @@ def _get_watched_file_handler(options):

def _convert_to_log_level(level):
if level >= len(log_levels):
level = 3
level = len(log_levels) - 1
return log_levels[level]


def _get_log_formatter_for_stream_handler(timed):
log_format = "%(asctime)s " if timed else ""
if (hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()) or const.ENVIRON_FORCE_TTY in os.environ:
if _is_on_tty():
log_format += "%(log_color)s%(levelname)-8s%(reset)s %(blue)s%(message)s"
formatter = colorlog.ColoredFormatter(
log_format,
Expand Down Expand Up @@ -452,6 +456,12 @@ def report(e):
else:
sys.excepthook(*sys.exc_info())

if isinstance(e, CompilerException):
from inmanta.compiler.help.explainer import ExplainerFactory
helpmsg = ExplainerFactory().explain_and_format(e, plain=not _is_on_tty())
if helpmsg is not None:
print(helpmsg)

try:
options.func(options)
except CLIException as e:
Expand Down
73 changes: 55 additions & 18 deletions src/inmanta/ast/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@
Contact: code@inmanta.com
"""

from inmanta import util

from typing import Dict, Sequence, List, Optional, Union # noqa: F401
from abc import abstractmethod
import traceback
from functools import lru_cache


try:
Expand All @@ -31,11 +30,12 @@

if TYPE_CHECKING:
import inmanta.ast.statements # noqa: F401
from inmanta.ast.attribute import Attribute # noqa: F401
from inmanta.ast.type import Type, NamedType # noqa: F401
from inmanta.execute.runtime import ExecutionContext, Instance # noqa: F401
from inmanta.ast.statements import Statement # noqa: F401
from inmanta.execute.runtime import ExecutionContext, Instance, DelayedResultVariable # noqa: F401
from inmanta.ast.statements import Statement, AssignStatement # noqa: F401
from inmanta.ast.entity import Entity # noqa: F401
from inmanta.ast.statements.define import DefineImport # noqa: F401
from inmanta.ast.statements.define import DefineImport, DefineEntity # noqa: F401


class Location(object):
Expand Down Expand Up @@ -77,7 +77,7 @@ def merge(self, other: Location) -> Location:
assert isinstance(other, Location)
assert self.file == other.file

if isinstance(other, Location):
if not isinstance(other, Range):
return Location(self.file, min(self.lnr, other.lnr))
else:
if other.lnr < self.lnr:
Expand Down Expand Up @@ -135,7 +135,7 @@ class LocatableString(object):
2. in the constructors of other statements
"""

def __init__(self, value, location: Range, lexpos: "int", namespace: "Namespace") -> None:
def __init__(self, value: str, location: Range, lexpos: "int", namespace: "Namespace") -> None:
self.value = value
self.location = location

Expand All @@ -147,13 +147,13 @@ def __init__(self, value, location: Range, lexpos: "int", namespace: "Namespace"
self.lexpos = lexpos
self.namespace = namespace

def get_value(self):
def get_value(self) -> str:
return self.value

def get_location(self):
def get_location(self) -> Location:
return self.location

def __str__(self):
def __str__(self) -> str:
return self.value


Expand Down Expand Up @@ -438,7 +438,7 @@ def _get_ns(self, ns_parts: List[str]) -> "Optional[Namespace]":
return None
return child._get_ns(ns_parts[1:])

@util.memoize
@lru_cache()
def to_path(self) -> List[str]:
"""
Return a list with the namespace path elements in it.
Expand All @@ -456,6 +456,7 @@ def get_location(self) -> Location:


class CompilerException(Exception):
""" Base class for exceptions generated by the compiler"""

def __init__(self, msg: str) -> None:
Exception.__init__(self, msg)
Expand Down Expand Up @@ -483,7 +484,7 @@ def format(self) -> str:
else:
return self.get_message()

def format_trace(self, indent="", indent_level=0):
def format_trace(self, indent: str="", indent_level: int=0) -> str:
"""Make a representation of this exception and its causes"""
out = indent * indent_level + self.format()

Expand All @@ -494,11 +495,12 @@ def format_trace(self, indent="", indent_level=0):

return out

def __str__(self):
def __str__(self) -> str:
return self.format()


class RuntimeException(CompilerException):
"""Baseclass for exceptions raised by the compiler after parsing is complete."""

def __init__(self, stmt: "Optional[Locatable]", msg: str) -> None:
CompilerException.__init__(self, msg)
Expand All @@ -507,7 +509,10 @@ def __init__(self, stmt: "Optional[Locatable]", msg: str) -> None:
self.set_location(stmt.get_location())
self.stmt = stmt

def set_statement(self, stmt: "Locatable", replace: bool = True):
def set_statement(self, stmt: "Locatable", replace: bool = True) -> None:
for cause in self.get_causes():
cause.set_statement(stmt, replace)

if replace or self.stmt is None:
self.set_location(stmt.get_location())
self.stmt = stmt
Expand All @@ -520,6 +525,7 @@ def format(self) -> str:


class TypeNotFoundException(RuntimeException):
"""Exception raised when a type is referenced that does not exist"""

def __init__(self, type: str, ns: Namespace) -> None:
RuntimeException.__init__(self, stmt=None, msg="could not find type %s in namespace %s" % (type, ns))
Expand All @@ -534,6 +540,10 @@ def stringify_exception(exn: Exception) -> str:


class ExternalException(RuntimeException):
"""
When a plugin call produces an exception that is not a RuntimeException,
it is wrapped in an ExternalException to make it conform to the expected interface
"""

def __init__(self, stmt: Locatable, msg: str, cause: Exception) -> None:
RuntimeException.__init__(self, stmt=stmt, msg=msg)
Expand All @@ -543,7 +553,7 @@ def __init__(self, stmt: Locatable, msg: str, cause: Exception) -> None:
def get_causes(self) -> List[CompilerException]:
return []

def format_trace(self, indent="", indent_level=0):
def format_trace(self, indent: str="", indent_level: int=0) -> str:
"""Make a representation of this exception and its causes"""
out = indent * indent_level + self.format()

Expand All @@ -556,6 +566,7 @@ def format_trace(self, indent="", indent_level=0):


class WrappingRuntimeException(RuntimeException):
""" Baseclass for RuntimeExceptions wrapping other RuntimeException """

def __init__(self, stmt: Locatable, msg: str, cause: RuntimeException) -> None:
if stmt is None and isinstance(cause, RuntimeException):
Expand All @@ -570,6 +581,7 @@ def get_causes(self) -> List[CompilerException]:


class AttributeException(WrappingRuntimeException):
""" Exception raise when an attribute could not be set, always wraps another exception """

def __init__(self, stmt: "Locatable", instance: "Instance", attribute: str, cause: RuntimeException) -> None:
WrappingRuntimeException.__init__(
Expand All @@ -579,6 +591,7 @@ def __init__(self, stmt: "Locatable", instance: "Instance", attribute: str, caus


class OptionalValueException(RuntimeException):
"""Exception raised when an optional value is accessed that has no value (and is frozen)"""

def __init__(self, instance: "Instance", attribute: str) -> None:
RuntimeException.__init__(self, instance, "Optional variable accessed that has no value (%s.%s)" %
Expand All @@ -588,10 +601,12 @@ def __init__(self, instance: "Instance", attribute: str) -> None:


class IndexException(RuntimeException):
"""Exception raised when an index definition is invalid"""
pass


class TypingException(RuntimeException):
"""Base class for exceptions raised during the typing phase of compilation"""
pass


Expand All @@ -600,14 +615,16 @@ class KeyException(RuntimeException):


class CycleExcpetion(TypingException):
"""Exception raised when a type is its own parent (type cycle)"""

def __init__(self, first_type, final_name):
def __init__(self, first_type: "DefineEntity", final_name: str) -> None:
super(CycleExcpetion, self).__init__(first_type, None)
self.types = []
self.types = [] # type: List[DefineEntity]
self.complete = False
self.final_name = final_name

def add(self, element):
def add(self, element: "DefineEntity") -> None:
"""Collect parent entities while traveling up the stack"""
if(self.complete):
return
if element.get_full_name() == self.final_name:
Expand Down Expand Up @@ -649,7 +666,26 @@ def __init__(self, stmt: "Statement", value: object, location: Location, newvalu
RuntimeException.__init__(self, stmt, msg)


class ModifiedAfterFreezeException(RuntimeException):

def __init__(self,
rv: "DelayedResultVariable",
instance: "Entity",
attribute: "Attribute",
value: object,
location: Location,
reverse: bool) -> None:
RuntimeException.__init__(self, None, "List modified after freeze")
self.instance = instance
self.attribute = attribute
self.value = value
self.location = location
self.resultvariable = rv
self.reverse = reverse


class DuplicateException(TypingException):
""" Exception raise when something is defined twice """

def __init__(self, stmt: Locatable, other: Locatable, msg: str) -> None:
TypingException.__init__(self, stmt, msg)
Expand All @@ -665,6 +701,7 @@ class CompilerError(Exception):


class MultiException(CompilerException):
"""A single exception collecting multiple CompilerExceptions"""

def __init__(self, others: List[CompilerException]) -> None:
CompilerException.__init__(self, "")
Expand Down
3 changes: 3 additions & 0 deletions src/inmanta/ast/statements/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,9 @@ def __init__(self,
self._indirect_attributes = {} # type: Dict[str,ExpressionStatement]
self._use_default = set() # type: Set[str]

def pretty_print(self) -> str:
return "%s(%s)" % (self.class_type, ",".join(("%s=%s" % (k, v.pretty_print()) for k, v in self.attributes.items())))

def normalize(self) -> None:
mytype = self.namespace.get_type(self.class_type)

Expand Down
Loading

0 comments on commit 877e0ac

Please sign in to comment.