示例#1
0
def memoized_classproperty(func: Optional[Callable[..., T]] = None,
                           key_factory=per_instance,
                           cache_factory=dict) -> T:
    return classproperty(
        memoized_classmethod(func,
                             key_factory=key_factory,
                             cache_factory=cache_factory))
示例#2
0
def memoized_classproperty(*args, **kwargs):
    return classproperty(memoized_classmethod(*args, **kwargs))
示例#3
0
文件: objects.py 项目: jsirois/pants
def enum(all_values):
  """A datatype which can take on a finite set of values. This method is experimental and unstable.

  Any enum subclass can be constructed with its create() classmethod. This method will use the first
  element of `all_values` as the default value, but enum classes can override this behavior by
  setting `default_value` in the class body.

  If `all_values` contains only strings, then each variant is made into an attribute on the
  generated enum class object. This allows code such as the following:

  class MyResult(enum(['success', 'not-success'])):
    pass

  MyResult.success # The same as: MyResult('success')
  MyResult.not_success # The same as: MyResult('not-success')

  Note that like with option names, hyphenated ('-') enum values are converted into attribute names
  with underscores ('_').

  :param Iterable all_values: A nonempty iterable of objects representing all possible values for
                              the enum.  This argument must be a finite, non-empty iterable with
                              unique values.
  :raises: :class:`ValueError`
  """
  # namedtuple() raises a ValueError if you try to use a field with a leading underscore.
  field_name = 'value'

  # This call to list() will eagerly evaluate any `all_values` which would otherwise be lazy, such
  # as a generator.
  all_values_realized = list(all_values)

  unique_values = OrderedSet(all_values_realized)
  if len(unique_values) == 0:
    raise ValueError("all_values must be a non-empty iterable!")
  elif len(unique_values) < len(all_values_realized):
    raise ValueError("When converting all_values ({}) to a set, at least one duplicate "
                     "was detected. The unique elements of all_values were: {}."
                     .format(all_values_realized, list(unique_values)))

  class ChoiceDatatype(datatype([field_name]), ChoicesMixin):
    # Overriden from datatype() so providing an invalid variant is catchable as a TypeCheckError,
    # but more specific.
    type_check_error_type = EnumVariantSelectionError

    @memoized_classproperty
    def _singletons(cls):
      """Generate memoized instances of this enum wrapping each of this enum's allowed values.

      NB: The implementation of enum() should use this property as the source of truth for allowed
      values and enum instances from those values.
      """
      return OrderedDict((value, cls._make_singleton(value)) for value in all_values_realized)

    @classmethod
    def _make_singleton(cls, value):
      """
      We convert uses of the constructor to call create(), so we then need to go around __new__ to
      bootstrap singleton creation from datatype()'s __new__.
      """
      return super(ChoiceDatatype, cls).__new__(cls, value)

    @classproperty
    def _allowed_values(cls):
      """The values provided to the enum() type constructor, for use in error messages."""
      return list(cls._singletons.keys())

    def __new__(cls, value):
      """Create an instance of this enum.

      :param value: Use this as the enum value. If `value` is an instance of this class, return it,
                    otherwise it is checked against the enum's allowed values.
      """
      if isinstance(value, cls):
        return value

      if value not in cls._singletons:
        raise cls.make_type_error(
          "Value {!r} must be one of: {!r}."
          .format(value, cls._allowed_values))

      return cls._singletons[value]

    # TODO: figure out if this will always trigger on primitives like strings, and what situations
    # won't call this __eq__ (and therefore won't raise like we want). Also look into whether there
    # is a way to return something more conventional like `NotImplemented` here that maintains the
    # extra caution we're looking for.
    def __eq__(self, other):
      """Redefine equality to avoid accidentally comparing against a non-enum."""
      if other is None:
        return False
      if type(self) != type(other):
        raise self.make_type_error(
          "when comparing {!r} against {!r} with type '{}': "
          "enum equality is only defined for instances of the same enum class!"
          .format(self, other, type(other).__name__))
      return super(ChoiceDatatype, self).__eq__(other)
    # Redefine the canary so datatype __new__ doesn't raise.
    __eq__._eq_override_canary = None

    # NB: as noted in datatype(), __hash__ must be explicitly implemented whenever __eq__ is
    # overridden. See https://docs.python.org/3/reference/datamodel.html#object.__hash__.
    def __hash__(self):
      return super(ChoiceDatatype, self).__hash__()

    def resolve_for_enum_variant(self, mapping):
      """Return the object in `mapping` with the key corresponding to the enum value.

      `mapping` is a dict mapping enum variant value -> arbitrary object. All variant values must be
      provided.

      NB: The objects in `mapping` should be made into lambdas if lazy execution is desired, as this
      will "evaluate" all of the values in `mapping`.
      """
      keys = frozenset(mapping.keys())
      if keys != frozenset(self._allowed_values):
        raise self.make_type_error(
          "pattern matching must have exactly the keys {} (was: {})"
          .format(self._allowed_values, list(keys)))
      match_for_variant = mapping[self.value]
      return match_for_variant

    @classproperty
    def all_variants(cls):
      """Iterate over all instances of this enum, in the declared order.

      NB: resolve_for_enum_variant() should be used instead of this method for performing
      conditional logic based on an enum instance's value.
      """
      return cls._singletons.values()

  # Python requires creating an explicit closure to save the value on each loop iteration.
  accessor_generator = lambda case: lambda cls: cls(case)
  for case in all_values_realized:
    if _string_type_constraint.satisfied_by(case):
      accessor = classproperty(accessor_generator(case))
      attr_name = re.sub(r'-', '_', case)
      setattr(ChoiceDatatype, attr_name, accessor)

  return ChoiceDatatype
示例#4
0
def memoized_classproperty(*args, **kwargs):
  return classproperty(memoized_classmethod(*args, **kwargs))