Source code for scenic.core.specifiers

"""Specifiers and associated objects."""

import itertools

from scenic.core.distributions import toDistribution
from scenic.core.errors import InvalidScenarioError, SpecifierError
from scenic.core.lazy_eval import (
    DelayedArgument,
    needsLazyEvaluation,
    requiredProperties,
    toLazyValue,
    valueInContext,
)

## Specifiers themselves


[docs]class Specifier: """Specifier providing values for properties. Each property is set to a value, at a given priority, given dependencies. Args: name: The name of this specifier. priorities: A dictionary mapping properties to the priority they are being specified with. value: A dictionary mapping properties to the values they are being specified as. deps: An iterable containing all properties that this specifier relies on. """ def __init__(self, name, priorities, value, deps=None): assert isinstance(priorities, dict) assert isinstance(value, (dict, DelayedArgument)) self.priorities = priorities self.value = toLazyValue(value) if deps is None: deps = set() deps |= requiredProperties(self.value) for p in priorities: if p in deps: raise SpecifierError(f"specifier for property {p} depends on itself") self.requiredProperties = tuple(sorted(deps)) self.name = name
[docs] def getValuesFor(self, obj): """Get the values specified for a given object.""" val = valueInContext(self.value, obj) assert isinstance(val, dict) return val
def __str__(self): return f"<{self.name} Specifier for {self.priorities}>"
[docs]class ModifyingSpecifier(Specifier): """Specifier providing values (or modifying) properties. Args: name: The name of this specifier. priorities: A dictionary mapping properties to the priority they are being specified with. value: A dictionary mapping properties to the values they are being specified as. modifiable_props: What properties specified by this specifier can be modified. deps: An iterable containing all properties that this specifier relies on. """ def __init__(self, name, priorities, value, modifiable_props, deps=None): self.modifiable_props = modifiable_props super().__init__(name, priorities, value, deps)
## Support for property defaults
[docs]class PropertyDefault: """A default value, possibly with dependencies.""" def __init__(self, requiredProperties, attributes, value): self.requiredProperties = set(requiredProperties) self.value = value def enabled(thing, default): if thing in attributes: attributes.remove(thing) return True else: return default self.isAdditive = enabled("additive", False) self.isDynamic = enabled("dynamic", False) self.isFinal = enabled("final", False) for attr in attributes: raise RuntimeError(f'unknown property attribute "{attr}"') if self.isAdditive and self.isDynamic: raise InvalidScenarioError("additive properties cannot be dynamic") @staticmethod def forValue(value): if isinstance(value, PropertyDefault): return value else: return PropertyDefault(set(), set(), lambda self: value)
[docs] def resolveFor(self, prop, overriddenDefs): """Create a Specifier for a property from this default and any superclass defaults.""" for other in overriddenDefs: if other.isFinal: msg = f'"{prop}" property cannot be overridden' if prop == "heading": msg += " (perhaps this scenario requires the --2d flag?)" raise InvalidScenarioError(msg) if self.isAdditive: allReqs = self.requiredProperties for other in overriddenDefs: allReqs |= other.requiredProperties def concatenator(context): allVals = [self.value(context)] for other in overriddenDefs: allVals.append(other.value(context)) return tuple(allVals) val = DelayedArgument(allReqs, concatenator, _internal=True) else: val = DelayedArgument(self.requiredProperties, self.value, _internal=True) return Specifier("PropertyDefault", {prop: -1}, {prop: val})