"""
Copyright 2017 Inmanta
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Contact: code@inmanta.com
"""
from typing import List, Optional, Tuple
from inmanta.ast import CompilerException, Locatable, RuntimeException, TypingException
from inmanta.ast.type import NullableType, TypedList
from inmanta.execute.runtime import (
AttributeVariable,
DeprecatedOptionVariable,
ListVariable,
OptionVariable,
QueueScheduler,
ResultVariable,
)
from inmanta.execute.util import Unknown
try:
from typing import TYPE_CHECKING
except ImportError:
TYPE_CHECKING = False
if TYPE_CHECKING:
from inmanta.ast.entity import Entity # noqa: F401
from inmanta.ast.type import Type # noqa: F401
from inmanta.execute.runtime import Instance # noqa: F401
[docs]class Attribute(Locatable):
"""
The attribute base class for entity attributes.
:param entity: The entity this attribute belongs to
"""
def __init__(self, entity: "Entity", value_type: "Type", name: str, multi: bool = False, nullable: bool = False) -> None:
Locatable.__init__(self)
self.__name: str = name
entity.add_attribute(self)
self.__entity = entity
self.__multi = multi
self.__nullable = nullable
self.__type: Type = value_type
if multi:
self.__type = TypedList(self.__type)
if nullable:
self.__type = NullableType(self.__type)
self.low = 0 if nullable else 1
self.comment = None # type: Optional[str]
self.end: Optional[RelationAttribute] = None
[docs] def get_type(self) -> "Type":
"""
Get the type of this attribute.
"""
return self.__type
type: "Type" = property(get_type)
def get_name(self) -> str:
"""
Get the name of the attribute. This is the name this attribute
is associated with in the entity.
"""
return self.__name
name = property(get_name)
def __hash__(self) -> "int":
"""
The hash of this object is based on the name of the attribute
"""
return hash(self.__name)
def __repr__(self) -> str:
return self.__name
def get_entity(self) -> "Entity":
"""
Return the entity this attribute belongs to
"""
return self.__entity
entity = property(get_entity)
[docs] def validate(self, value: object) -> None:
"""
Validate a value that is going to be assigned to this attribute. Raises a :py:class:`inmanta.ast.RuntimeException`
if validation fails.
"""
if isinstance(value, Unknown):
return
self.type.validate(value)
def get_new_result_variable(self, instance: "Instance", queue: QueueScheduler) -> ResultVariable:
out: ResultVariable["Instance"]
if self.is_optional():
# be a 0-1 relation
self.end = None
self.low = 0
self.high = 1
out = DeprecatedOptionVariable(self, instance, queue)
else:
out = ResultVariable()
out.set_type(self.type)
return out
def is_optional(self) -> bool:
"""
Returns true iff this attribute accepts null values.
Deprecated but still used internally.
"""
return self.__nullable
def is_multi(self) -> bool:
"""
Returns true iff this attribute expects a list of values of its base type.
Deprecated but still used internally.
"""
return self.__multi
def final(self, excns: List[CompilerException]) -> None:
pass
class RelationAttribute(Attribute):
"""
An attribute that is a relation
"""
def __init__(self, entity: "Entity", value_type: "Type", name: str) -> None:
Attribute.__init__(self, entity, value_type, name)
self.end: Optional[RelationAttribute] = None
self.low = 1
self.high = 1
self.depends = False
self.source_annotations = []
self.target_annotations = []
def __str__(self) -> str:
return "%s.%s" % (self.get_entity().get_full_name(), self.name)
def __repr__(self) -> str:
return "[%d:%s] %s" % (self.low, self.high if self.high is not None else "", self.name)
def set_multiplicity(self, values: "Tuple[int, int]") -> None:
"""
Set the multiplicity of this end
"""
self.low = values[0]
self.high = values[1]
def get_new_result_variable(self, instance: "Instance", queue: QueueScheduler) -> ResultVariable:
if self.low == 1 and self.high == 1:
out = AttributeVariable(self, instance) # type: ResultVariable
elif self.low == 0 and self.high == 1:
out = OptionVariable(self, instance, queue) # type: ResultVariable
else:
out = ListVariable(self, instance, queue) # type: ResultVariable
out.set_type(self.type)
return out
def is_optional(self) -> bool:
return self.low == 0
def is_multi(self) -> bool:
return self.high != 1
def final(self, excns: List[CompilerException]) -> None:
for rv in self.source_annotations:
try:
if isinstance(rv.get_value(), Unknown):
excns.append(TypingException(self, "Relation annotation can not be Unknown"))
except RuntimeException as e:
excns.append(e)
for rv in self.target_annotations:
try:
if isinstance(rv.get_value(), Unknown):
excns.append(TypingException(self, "Relation annotation can not be Unknown"))
except RuntimeException as e:
excns.append(e)