Skip to content
Snippets Groups Projects
model.py 34.7 KiB
Newer Older
  • Learn to ignore specific revisions
  •             u"finished": self.finished.isoformat(),
            }
    
    
    @attr.s(frozen=True)
    class Error(object):
        """
        This is a non-persistent state in which a voucher exists when the database
        state is **pending** but the most recent redemption attempt has failed due
        to an error that is not handled by any other part of the system.
        """
        finished = attr.ib(validator=attr.validators.instance_of(datetime))
        details = attr.ib(validator=attr.validators.instance_of(unicode))
    
    
        def to_json_v1(self):
            return {
                u"name": u"error",
                u"finished": self.finished.isoformat(),
                u"details": self.details,
            }
    
    
    
    @attr.s(frozen=True)
    
    Jean-Paul Calderone's avatar
    Jean-Paul Calderone committed
        """
        :ivar unicode number: The text string which gives this voucher its
            identity.
    
        :ivar datetime created: The time at which this voucher was added to this
            node.
    
    
        :ivar expected_tokens: The total number of tokens for which we expect to
            be able to redeem this voucher.  Tokens are redeemed in smaller
            groups, progress of which is tracked in ``state``.  This only gives
            the total we expect to reach at completion.
    
    
        :ivar state: An indication of the current state of this voucher.  This is
            an instance of ``Pending``, ``Redeeming``, ``Redeemed``,
            ``DoubleSpend``, ``Unpaid``, or ``Error``.
    
    Jean-Paul Calderone's avatar
    Jean-Paul Calderone committed
        """
    
        number = attr.ib(
            validator=attr.validators.and_(
                attr.validators.instance_of(unicode),
                is_base64_encoded(urlsafe_b64decode),
                has_length(44),
            ),
        )
    
    
        expected_tokens = attr.ib(
            validator=attr.validators.optional(
                attr.validators.and_(
                    attr.validators.instance_of((int, long)),
                    greater_than(0),
                ),
            ),
        )
    
    
        created = attr.ib(
            default=None,
            validator=attr.validators.optional(attr.validators.instance_of(datetime)),
        )
    
        state = attr.ib(
            default=Pending(counter=0),
            validator=attr.validators.instance_of((
                Pending,
                Redeeming,
                Redeemed,
                DoubleSpend,
                Unpaid,
                Error,
            )),
        )
    
        @classmethod
        def from_row(cls, row):
    
            def state_from_row(state, row):
                if state == u"pending":
    
                    return Pending(counter=row[3])
    
                if state == u"double-spend":
                    return DoubleSpend(
                        parse_datetime(row[0], delimiter=u" "),
                    )
                if state == u"redeemed":
                    return Redeemed(
                        parse_datetime(row[0], delimiter=u" "),
                        row[1],
    
                    )
                raise ValueError("Unknown voucher state {}".format(state))
    
    
            number, created, expected_tokens, state = row[:4]
    
    
                number=number,
                expected_tokens=expected_tokens,
    
                # All Python datetime-based date/time libraries fail to handle
                # leap seconds.  This parse call might raise an exception of the
                # value represents a leap second.  However, since we also use
                # Python to generate the data in the first place, it should never
                # represent a leap second... I hope.
    
                created=parse_datetime(created, delimiter=u" "),
                state=state_from_row(state, row[4:]),
    
        @classmethod
        def from_json(cls, json):
            values = loads(json)
            version = values.pop(u"version")
            return getattr(cls, "from_json_v{}".format(version))(values)
    
    
        @classmethod
        def from_json_v1(cls, values):
    
            state_json = values[u"state"]
            state_name = state_json[u"name"]
            if state_name == u"pending":
    
                state = Pending(counter=state_json[u"counter"])
    
            elif state_name == u"redeeming":
                state = Redeeming(
                    started=parse_datetime(state_json[u"started"]),
    
                    counter=state_json[u"counter"],
    
            elif state_name == u"double-spend":
                state = DoubleSpend(
                    finished=parse_datetime(state_json[u"finished"]),
                )
            elif state_name == u"redeemed":
                state = Redeemed(
                    finished=parse_datetime(state_json[u"finished"]),
                    token_count=state_json[u"token-count"],
    
                    public_key=state_json[u"public-key"],
    
            elif state_name == u"unpaid":
                state = Unpaid(
                    finished=parse_datetime(state_json[u"finished"]),
                )
    
            elif state_name == u"error":
                state = Error(
                    finished=parse_datetime(state_json[u"finished"]),
                    details=state_json[u"details"],
                )
    
                raise ValueError("Unrecognized state {!r}".format(state_json))
    
            return cls(
                number=values[u"number"],
    
                expected_tokens=values[u"expected-tokens"],
    
                created=None if values[u"created"] is None else parse_datetime(values[u"created"]),
    
            return dumps(self.marshal())
    
    
        def marshal(self):
            return self.to_json_v1()
    
            state = self.state.to_json_v1()
    
            return {
                u"number": self.number,
    
                u"expected-tokens": self.expected_tokens,
    
                u"created": None if self.created is None else self.created.isoformat(),