Skip to content
Snippets Groups Projects
model.py 35.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • @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(str))
    
                "name": "error",
    
                "finished": self.finished.isoformat(),
    
                "details": self.details,
    
    @attr.s(frozen=True)
    
    Jean-Paul Calderone's avatar
    Jean-Paul Calderone committed
        """
    
        :ivar bytes number: The byte string which gives this voucher its
    
    Jean-Paul Calderone's avatar
    Jean-Paul Calderone committed
            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_(
    
                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),
    
    Tom Prince's avatar
    Tom Prince committed
            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 == "pending":
    
                    return Pending(counter=row[3])
    
                if state == "double-spend":
    
                        parse_datetime(row[0], delimiter=" "),
    
                if state == "redeemed":
    
                        parse_datetime(row[0], delimiter=" "),
    
                        row[1],
                    )
                raise ValueError("Unknown voucher state {}".format(state))
    
    
            number, created, expected_tokens, state = row[:4]
    
    
                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=" "),
    
                state=state_from_row(state, row[4:]),
    
        @classmethod
        def from_json(cls, json):
            values = loads(json)
    
            version = values.pop("version")
    
            return getattr(cls, "from_json_v{}".format(version))(values)
    
        @classmethod
        def from_json_v1(cls, values):
    
            state_json = values["state"]
            state_name = state_json["name"]
            if state_name == "pending":
                state = Pending(counter=state_json["counter"])
            elif state_name == "redeeming":
    
                state = Redeeming(
    
                    started=parse_datetime(state_json["started"]),
                    counter=state_json["counter"],
    
            elif state_name == "double-spend":
    
                    finished=parse_datetime(state_json["finished"]),
    
            elif state_name == "redeemed":
    
                    finished=parse_datetime(state_json["finished"]),
                    token_count=state_json["token-count"],
    
            elif state_name == "unpaid":
    
                    finished=parse_datetime(state_json["finished"]),
    
            elif state_name == "error":
    
                    finished=parse_datetime(state_json["finished"]),
                    details=state_json["details"],
    
                raise ValueError("Unrecognized state {!r}".format(state_json))
    
                number=values["number"].encode("ascii"),
                expected_tokens=values["expected-tokens"],
    
    Tom Prince's avatar
    Tom Prince committed
                created=None
    
                if values["created"] is None
                else parse_datetime(values["created"]),
    
            return dumps_utf8(self.marshal())
    
    
        def marshal(self):
            return self.to_json_v1()
    
            state = self.state.to_json_v1()
    
                "number": self.number.decode("ascii"),
                "expected-tokens": self.expected_tokens,
    
                "created": None if self.created is None else self.created.isoformat(),
    
                "state": state,
                "version": 1,