class Numbers(CheckedPVector): __type__ = optional(int, float)
class NaturalsVector(CheckedPVector): __type__ = optional(Naturals)
class LinkedList(PClass): value = field(type='class_test.Numbers') next = field(type=optional('class_test.LinkedList'))
class OptionalNaturals(CheckedPVector): __type__ = optional(int) __invariant__ = lambda value: (value is None or value >= 0, 'Negative value')
class WrittenAction(PClass): """ An Action that has been logged. This class is intended to provide a definition within Eliot of what an action actually is, and a means of constructing actions that are known to be valid. @ivar WrittenMessage start_message: A start message whose task UUID and level match this action, or C{None} if it is not yet set on the action. @ivar WrittenMessage end_message: An end message hose task UUID and level match this action. Can be C{None} if the action is unfinished. @ivar TaskLevel task_level: The action's task level, e.g. if start message has level C{[2, 3, 1]} it will be C{TaskLevel(level=[2, 3])}. @ivar UUID task_uuid: The UUID of the task to which this action belongs. @ivar _children: A L{pmap} from L{TaskLevel} to the L{WrittenAction} and L{WrittenMessage} objects that make up this action. """ start_message = field(type=optional(WrittenMessage), mandatory=True, initial=None) end_message = field(type=optional(WrittenMessage), mandatory=True, initial=None) task_level = field(type=TaskLevel, mandatory=True) task_uuid = field(type=unicode, mandatory=True, factory=unicode) # Pyrsistent doesn't support pmap_field with recursive types. _children = pmap_field(TaskLevel, object) @classmethod def from_messages(cls, start_message=None, children=pvector(), end_message=None): """ Create a C{WrittenAction} from C{WrittenMessage}s and other C{WrittenAction}s. @param WrittenMessage start_message: A message that has C{ACTION_STATUS_FIELD}, C{ACTION_TYPE_FIELD}, and a C{task_level} that ends in C{1}, or C{None} if unavailable. @param children: An iterable of C{WrittenMessage} and C{WrittenAction} @param WrittenMessage end_message: A message that has the same C{action_type} as this action. @raise WrongTask: If C{end_message} has a C{task_uuid} that differs from C{start_message.task_uuid}. @raise WrongTaskLevel: If any child message or C{end_message} has a C{task_level} that means it is not a direct child. @raise WrongActionType: If C{end_message} has an C{ACTION_TYPE_FIELD} that differs from the C{ACTION_TYPE_FIELD} of C{start_message}. @raise InvalidStatus: If C{end_message} doesn't have an C{action_status}, or has one that is not C{SUCCEEDED_STATUS} or C{FAILED_STATUS}. @raise InvalidStartMessage: If C{start_message} does not have a C{ACTION_STATUS_FIELD} of C{STARTED_STATUS}, or if it has a C{task_level} indicating that it is not the first message of an action. @return: A new C{WrittenAction}. """ actual_message = [ message for message in [start_message, end_message] + list(children) if message ][0] action = cls( task_level=actual_message.task_level.parent(), task_uuid=actual_message.task_uuid, ) if start_message: action = action._start(start_message) for child in children: if action._children.get(child.task_level, child) != child: raise DuplicateChild(action, child) action = action._add_child(child) if end_message: action = action._end(end_message) return action @property def action_type(self): """ The type of this action, e.g. C{"yourapp:subsystem:dosomething"}. """ if self.start_message: return self.start_message.contents[ACTION_TYPE_FIELD] elif self.end_message: return self.end_message.contents[ACTION_TYPE_FIELD] else: return None @property def status(self): """ One of C{STARTED_STATUS}, C{SUCCEEDED_STATUS}, C{FAILED_STATUS} or C{None}. """ message = self.end_message if self.end_message else self.start_message if message: return message.contents[ACTION_STATUS_FIELD] else: return None @property def start_time(self): """ The Unix timestamp of when the action started, or C{None} if there has been no start message added so far. """ if self.start_message: return self.start_message.timestamp @property def end_time(self): """ The Unix timestamp of when the action ended, or C{None} if there has been no end message. """ if self.end_message: return self.end_message.timestamp @property def exception(self): """ If the action failed, the name of the exception that was raised to cause it to fail. If the action succeeded, or hasn't finished yet, then C{None}. """ if self.end_message: return self.end_message.contents.get(EXCEPTION_FIELD, None) @property def reason(self): """ The reason the action failed. If the action succeeded, or hasn't finished yet, then C{None}. """ if self.end_message: return self.end_message.contents.get(REASON_FIELD, None) @property def children(self): """ The list of child messages and actions sorted by task level, excluding the start and end messages. """ return pvector( sorted(self._children.values(), key=lambda m: m.task_level)) def _validate_message(self, message): """ Is C{message} a valid direct child of this action? @param message: Either a C{WrittenAction} or a C{WrittenMessage}. @raise WrongTask: If C{message} has a C{task_uuid} that differs from the action's C{task_uuid}. @raise WrongTaskLevel: If C{message} has a C{task_level} that means it's not a direct child. """ if message.task_uuid != self.task_uuid: raise WrongTask(self, message) if not message.task_level.parent() == self.task_level: raise WrongTaskLevel(self, message) def _add_child(self, message): """ Return a new action with C{message} added as a child. Assumes C{message} is not an end message. @param message: Either a C{WrittenAction} or a C{WrittenMessage}. @raise WrongTask: If C{message} has a C{task_uuid} that differs from the action's C{task_uuid}. @raise WrongTaskLevel: If C{message} has a C{task_level} that means it's not a direct child. @return: A new C{WrittenAction}. """ self._validate_message(message) level = message.task_level return self.transform(("_children", level), message) def _start(self, start_message): """ Start this action given its start message. @param WrittenMessage start_message: A start message that has the same level as this action. @raise InvalidStartMessage: If C{start_message} does not have a C{ACTION_STATUS_FIELD} of C{STARTED_STATUS}, or if it has a C{task_level} indicating that it is not the first message of an action. """ if start_message.contents.get(ACTION_STATUS_FIELD, None) != STARTED_STATUS: raise InvalidStartMessage.wrong_status(start_message) if start_message.task_level.level[-1] != 1: raise InvalidStartMessage.wrong_task_level(start_message) return self.set(start_message=start_message) def _end(self, end_message): """ End this action with C{end_message}. Assumes that the action has not already been ended. @param WrittenMessage end_message: An end message that has the same level as this action. @raise WrongTask: If C{end_message} has a C{task_uuid} that differs from the action's C{task_uuid}. @raise WrongTaskLevel: If C{end_message} has a C{task_level} that means it's not a direct child. @raise InvalidStatus: If C{end_message} doesn't have an C{action_status}, or has one that is not C{SUCCEEDED_STATUS} or C{FAILED_STATUS}. @return: A new, completed C{WrittenAction}. """ action_type = end_message.contents.get(ACTION_TYPE_FIELD, None) if self.action_type not in (None, action_type): raise WrongActionType(self, end_message) self._validate_message(end_message) status = end_message.contents.get(ACTION_STATUS_FIELD, None) if status not in (FAILED_STATUS, SUCCEEDED_STATUS): raise InvalidStatus(self, end_message) return self.set(end_message=end_message)