def toResolvedSequence(self, resolver: "Resolver", varArgs: bool, onlyTypes: bool = False, onlyValues: bool = False) -> Sequence: """ Build the resolved sequence of those parameters. Must be called after mergeDefaults. """ sequence = SequenceBuilder() entries = self.itemsValuesOrTypes(resolver=resolver, varArgs=varArgs) if self.isNamed: entries = sorted(entries, key=lambda k: k[2].order) # Build the sequence from tools.bdl.entities.impl.fragment.type import Type for key, item, metadata in entries: if onlyTypes: Error.assertTrue(element=self.element, condition=isinstance(item, Type), message="Parameter '{}' is not a type.".format(key)) if onlyValues: Error.assertTrue(element=self.element, condition=not isinstance(item, Type), message="Parameter '{}' is not a value.".format(key)) if isinstance(item, str): element = ElementBuilder().setAttr(key="value", value=item) else: element = item.element.copy() if self.isNamed: ElementBuilder.cast(element, ElementBuilder).setAttr(key="key", value=key) sequence.pushBackElement(element) return sequence
def addParents(self, fqn: typing.Optional[str], parents: typing.List[str]) -> None: """ Add parents to the current entity. """ if fqn is None: updatedParents = {*self.getParents(), *parents} else: updatedParents = {*self.getParents(), fqn, *parents} ElementBuilder.cast(self.element, ElementBuilder).setAttr("parents", ";".join(updatedParents))
def resolve(self, contracts: "Contracts") -> None: """ Merge a base contract into this one. The order is important, as type are inherited from the deepest base. """ elementBuilder = ElementBuilder.cast(self.element, ElementBuilder) for contract in reversed([*contracts]): # Check if the type already exists, if so attempt to merge the 2 types. existing = self.get(contract.type) if existing is not None: contractTraits = AllContracts.all.get(contract.type) assert contractTraits, "All contracts should have been validated already." try: contract = contractTraits.resolveConflict(base=contract, derived=existing) except Exception as e: Error.handleFromElement(element=self.element, message=str(e)) # Remove the existing contract if resolved into a new contract if contract is not None: self.remove(contract.type) if contract is not None: elementBuilder.pushFrontElementToNestedSequence( self.sequenceKind, contract.element) # Construct a dummy Validation to check the arguments maybeSchema = self.validationForAll if maybeSchema: Validation(schema=[maybeSchema])
def _setUnderlyingValue(self, entity: "Entity", fqn: typing.Optional[str] = None) -> None: elementBuilder = ElementBuilder.cast(self.element, ElementBuilder) if fqn is not None: elementBuilder.setAttr("fqn_value", fqn) elif entity.underlyingValue is not None: elementBuilder.setAttr("fqn_value", entity.underlyingValue) if entity.literal is not None: elementBuilder.setAttr("literal", entity.literal)
def testEqual(self) -> None: element1 = ElementBuilder().setAttrs({"a": "1", "b": "2"}) self.assertEqual(element1, element1) element2 = ElementBuilder().setAttrs({"b": "2", "a": "1"}) self.assertEqual(element1, element2) element2.setAttr("c", "3") self.assertNotEqual(element1, element2) self.assertNotEqual(element1, ElementBuilder()) self.assertNotEqual(ElementBuilder(), element1)
def add(element: Element, kind: str, values: typing.Optional[typing.List[str]] = None) -> None: """ Add a contract to the element. """ contractElement = ElementBuilder().setAttr("type", kind) if values is not None: for value in values: valueElement = ElementBuilder().setAttr("value", value) contractElement.pushBackElementToNestedSequence( kind="values", element=valueElement) ElementBuilder.cast(element, ElementBuilder).pushBackElementToNestedSequence( kind="contract", element=contractElement)
def resolve(self, resolver: "Resolver") -> "EntityType": """ Resolve the types and nested templates by updating their symbol to fqn. """ fqns = resolver.resolveFQN(name=self.kind).assertValue(element=self.element, attr=self.kindAttr) ElementBuilder.cast(self.element, ElementBuilder).updateAttr(self.kindAttr, ";".join(fqns)) # Resolve the templates if available self.templates.resolve(resolver=resolver) templates = self.templates # Get and save the underlying type underlying = self.getEntityResolved(resolver=resolver) # Resolve the entity, this is needed only if the entity is defined after the one holding this type. underlying.resolveMemoized(resolver=resolver) if self.underlyingTypeAttr is not None and underlying.underlyingType is not None: ElementBuilder.cast(self.element, ElementBuilder).setAttr(self.underlyingTypeAttr, underlying.underlyingType) # Validate template arguments configTypes = underlying.getConfigTemplateTypes(resolver=resolver) if not configTypes: Error.assertTrue(element=self.element, condition=(not bool(self.templates)), attr=self.kindAttr, message="Type '{}' does not support template type arguments.".format(self.kind)) else: templates.mergeDefaults(configTypes) # Validate the template arguments values = templates.getValuesOrTypesAsDict(resolver=resolver, varArgs=False) validation = underlying.makeValidationForTemplate(resolver=resolver, parameters=configTypes) assert validation, "Cannot be empty, already checked by the condition." resultValidate = validation.validate(values, output="return") Error.assertTrue(element=self.element, attr=self.kindAttr, condition=bool(resultValidate), message=str(resultValidate)) # Save the resolved template only after the validation is completed. sequence = templates.toResolvedSequence(resolver=resolver, varArgs=False, onlyTypes=True) ElementBuilder.cast(self.element, ElementBuilder).setNestedSequence("{}_resolved".format(self.templateAttr), sequence) # Resolve contract self.contracts.resolve(underlying.contracts) return underlying
def itemsValuesOrTypes(self, resolver: "Resolver", varArgs: bool) -> typing.List[typing.Tuple[str, ResolvedType, Metadata]]: """ Iterate through the list and return values or types. """ values: typing.List[typing.Tuple[str, ResolvedType, Metadata]] = [] for key, expression, metadata in self.itemsMetadata(): if expression.isVarArgs and not varArgs: continue if expression.literal is not None: values.append((key, expression.literal, metadata)) elif expression.underlyingValue is not None: value: ResolvedType = resolver.getEntityResolved(fqn=expression.underlyingValue).assertValue( element=expression.element) # If these are default arguments, use the default value. if metadata.default: from tools.bdl.entities.impl.expression import Expression assert isinstance(value, Expression) if value.literal: # I beleive that this code path is never used. value = value.literal elif value.isName: # Create an unamed value element = ElementBuilder.cast(value.element.copy(), ElementBuilder).removeAttr(key="name").get() value = Expression(element) values.append((key, value, metadata)) else: values.append((key, expression.type, metadata)) return values
def resolve(self, resolver: "Resolver") -> None: """ Resolve entities. """ if self.isValue: return entity = self.type.resolve(resolver=resolver) # Set the underlying value if self.isParameters: # The type must represent a type (not a value) and have a valid FQN. self.assertTrue( condition=entity.isRoleType, message="Cannot instantiate a value from another value.") self.assertTrue(condition=self.isFQN, message="The value must have a FQN.") # TODO: it is possible that the expression does not have a FQN, in case the expression is resolved through another entity. self._setUnderlyingValue(entity=self, fqn=self.fqn) else: self._setUnderlyingValue(entity=entity) # Generate the argument list and resolve it if self.isParameters: parameters = self.parameters assert parameters is not None parameters.resolve(resolver=resolver) else: parameters = Parameters(element=self.element) # Merge its default values defaults = self.getConfigValues(resolver=resolver) parameters.mergeDefaults(defaults) # Read the validation for the value. it comes in part from the direct underlying type, contract information # directly associated with this expression do not apply to the current validation. validation = self._makeValueValidation(resolver=resolver, parameters=defaults, contracts=self.contracts) if validation is not None: arguments = parameters.getValuesOrTypesAsDict(resolver=resolver, varArgs=False) result = validation.validate(arguments, output="return") Error.assertTrue(element=self.element, attr="type", condition=bool(result), message=str(result)) # Save the resolved parameters (values and templates), only after the validation is completed. argumentValues = parameters.copy(template=False) sequence = argumentValues.toResolvedSequence(resolver=resolver, varArgs=False, onlyValues=True) ElementBuilder.cast(self.element, ElementBuilder).setNestedSequence( "argument_resolved", sequence) argumentTemplates = parameters.copy(template=True) sequence = argumentTemplates.toResolvedSequence(resolver=resolver, varArgs=False, onlyValues=True) ElementBuilder.cast(self.element, ElementBuilder).setNestedSequence( "argument_template_resolved", sequence)
def _setUnderlyingType(self, fqn: str) -> None: ElementBuilder.cast(self.element, ElementBuilder).setAttr("fqn_type", fqn)
def markAsResolved(self) -> None: """ Mark an entity as resolved. """ ElementBuilder.cast(self.element, ElementBuilder).setAttr("resolved", "1")