From 2bd1f45bef9728197962a7881789d27022e7cb1f Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone <exarkun@twistedmatrix.com> Date: Mon, 27 Dec 2021 14:18:37 -0500 Subject: [PATCH] initial porting changes --- src/_zkapauthorizer/_storage_client.py | 2 +- src/_zkapauthorizer/_storage_server.py | 19 ++- src/_zkapauthorizer/controller.py | 83 ++++++----- src/_zkapauthorizer/eliot.py | 67 +++++---- src/_zkapauthorizer/model.py | 130 ++++++++++-------- src/_zkapauthorizer/resource.py | 72 +++++----- src/_zkapauthorizer/storage_common.py | 40 ++++-- src/_zkapauthorizer/tests/strategies.py | 65 +++++---- .../tests/test_client_resource.py | 121 ++++++++-------- src/_zkapauthorizer/tests/test_controller.py | 53 +++++++ src/_zkapauthorizer/tests/test_model.py | 2 +- src/_zkapauthorizer/tests/test_plugin.py | 35 +++-- .../tests/test_pricecalculator.py | 10 ++ 13 files changed, 418 insertions(+), 281 deletions(-) diff --git a/src/_zkapauthorizer/_storage_client.py b/src/_zkapauthorizer/_storage_client.py index 5f4e568..2867065 100644 --- a/src/_zkapauthorizer/_storage_client.py +++ b/src/_zkapauthorizer/_storage_client.py @@ -373,7 +373,7 @@ class ZKAPAuthorizerStorageClient(object): None, ) ).values() - num_passes = required_passes(self._pass_value, share_sizes) + num_passes = required_passes(self._pass_value, list(share_sizes)) result = yield call_with_passes( lambda passes: rref.callRemote( diff --git a/src/_zkapauthorizer/_storage_server.py b/src/_zkapauthorizer/_storage_server.py index 809b385..2d774ac 100644 --- a/src/_zkapauthorizer/_storage_server.py +++ b/src/_zkapauthorizer/_storage_server.py @@ -22,6 +22,13 @@ implemented in ``_storage_client.py``. """ from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from future.utils import PY2 +if PY2: + from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401 from datetime import timedelta from errno import ENOENT @@ -122,13 +129,13 @@ class _ValidationResult(object): """ Cryptographically check the validity of a single pass. - :param unicode message: The shared message for pass validation. + :param str message: The shared message for pass validation. :param Pass pass_: The pass to validate. :return bool: ``False`` (invalid) if the pass includes a valid signature, ``True`` (valid) otherwise. """ - assert isinstance(message, unicode), "message %r not unicode" % (message,) + assert isinstance(message, str), "message %r not str" % (message,) assert isinstance(pass_, Pass), "pass %r not a Pass" % (pass_,) try: preimage = TokenPreimage.decode_base64(pass_.preimage) @@ -148,7 +155,7 @@ class _ValidationResult(object): """ Check all of the given passes for validity. - :param unicode message: The shared message for pass validation. + :param str message: The shared message for pass validation. :param list[bytes] passes: The encoded passes to validate. :param SigningKey signing_key: The signing key to use to check the passes. @@ -398,7 +405,7 @@ class ZKAPAuthorizerStorageServer(Referenceable): def remote_share_sizes(self, storage_index_or_slot, sharenums): with start_action( - action_type=u"zkapauthorizer:storage-server:remote:share-sizes", + action_type="zkapauthorizer:storage-server:remote:share-sizes", storage_index_or_slot=storage_index_or_slot, ): return dict( @@ -443,7 +450,7 @@ class ZKAPAuthorizerStorageServer(Referenceable): Note that the lease is *not* renewed in this case (see #254). """ with start_action( - action_type=u"zkapauthorizer:storage-server:remote:slot-testv-and-readv-and-writev", + action_type="zkapauthorizer:storage-server:remote:slot-testv-and-readv-and-writev", storage_index=b2a(storage_index), path=storage_index_to_dir(storage_index), ): @@ -877,7 +884,7 @@ def get_share_path(storage_server, storage_index, sharenum): return ( FilePath(storage_server.sharedir) .preauthChild(storage_index_to_dir(storage_index)) - .child(u"{}".format(sharenum)) + .child("{}".format(sharenum)) ) diff --git a/src/_zkapauthorizer/controller.py b/src/_zkapauthorizer/controller.py index ab52c5b..c4f202e 100644 --- a/src/_zkapauthorizer/controller.py +++ b/src/_zkapauthorizer/controller.py @@ -18,6 +18,13 @@ for the client side of the storage plugin. """ from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from future.utils import PY2 +if PY2: + from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401 from base64 import b64decode, b64encode from datetime import timedelta @@ -97,7 +104,7 @@ class RedemptionResult(object): :ivar list[UnblindedToken] unblinded_tokens: The tokens which resulted from the redemption. - :ivar unicode public_key: The public key which the server proved was + :ivar str public_key: The public key which the server proved was involved in the redemption process. """ @@ -238,13 +245,13 @@ class ErrorRedeemer(object): configured error. """ - details = attr.ib(validator=attr.validators.instance_of(unicode)) + details = attr.ib(validator=attr.validators.instance_of(str)) @classmethod def make(cls, section_name, node_config, announcement, reactor): details = node_config.get_config( section=section_name, - option=u"details", + option="details", ).decode("ascii") return cls(details) @@ -325,7 +332,7 @@ def dummy_random_tokens(voucher, counter, count): # Padding is 96 (random token length) - 32 (decoded voucher # length) - 4 (fixed-width counter) b64encode( - v + u"{:0>4}{:0>60}".format(counter, n).encode("ascii"), + v + "{:0>4}{:0>60}".format(counter, n).encode("ascii"), ), ) @@ -340,7 +347,7 @@ class DummyRedeemer(object): really redeeming them, it makes up some fake ZKAPs and pretends those are the result. - :ivar unicode _public_key: The base64-encoded public key to return with + :ivar str _public_key: The base64-encoded public key to return with all successful redemption results. As with the tokens returned by this redeemer, chances are this is not actually a valid public key. Its corresponding private key certainly has not been used to sign @@ -348,7 +355,7 @@ class DummyRedeemer(object): """ _public_key = attr.ib( - validator=attr.validators.instance_of(unicode), + validator=attr.validators.instance_of(str), ) @classmethod @@ -356,8 +363,8 @@ class DummyRedeemer(object): return cls( node_config.get_config( section=section_name, - option=u"issuer-public-key", - ).decode(u"utf-8"), + option="issuer-public-key", + ).decode("utf-8"), ) def random_tokens_for_voucher(self, voucher, counter, count): @@ -461,7 +468,7 @@ class RistrettoRedeemer(object): def make(cls, section_name, node_config, announcement, reactor): configured_issuer = node_config.get_config( section=section_name, - option=u"ristretto-issuer-root-url", + option="ristretto-issuer-root-url", ).decode("ascii") if announcement is not None: # Don't let us talk to a storage server that has a different idea @@ -473,7 +480,7 @@ class RistrettoRedeemer(object): # If we aren't given an announcement then we're not being used in # the context of a specific storage server so the check is # unnecessary and impossible. - announced_issuer = announcement[u"ristretto-issuer-root-url"] + announced_issuer = announcement["ristretto-issuer-root-url"] if announced_issuer != configured_issuer: raise IssuerConfigurationMismatch(announced_issuer, configured_issuer) @@ -498,12 +505,12 @@ class RistrettoRedeemer(object): ) blinded_tokens = list(token.blind() for token in random_tokens) response = yield self._treq.post( - self._api_root.child(u"v1", u"redeem").to_text(), + self._api_root.child("v1", "redeem").to_text(), dumps( { - u"redeemVoucher": voucher.number.decode("ascii"), - u"redeemCounter": counter, - u"redeemTokens": list( + "redeemVoucher": voucher.number.decode("ascii"), + "redeemCounter": counter, + "redeemTokens": list( token.encode_base64() for token in blinded_tokens ), } @@ -517,26 +524,26 @@ class RistrettoRedeemer(object): except ValueError: raise UnexpectedResponse(response.code, response_body) - success = result.get(u"success", False) + success = result.get("success", False) if not success: - reason = result.get(u"reason", None) - if reason == u"double-spend": + reason = result.get("reason", None) + if reason == "double-spend": raise AlreadySpent(voucher) - elif reason == u"unpaid": + elif reason == "unpaid": raise Unpaid(voucher) raise UnrecognizedFailureReason(result) self._log.info( "Redeemed: {public_key} {proof} {count}", - public_key=result[u"public-key"], - proof=result[u"proof"], - count=len(result[u"signatures"]), + public_key=result["public-key"], + proof=result["proof"], + count=len(result["signatures"]), ) - marshaled_signed_tokens = result[u"signatures"] - marshaled_proof = result[u"proof"] - marshaled_public_key = result[u"public-key"] + marshaled_signed_tokens = result["signatures"] + marshaled_proof = result["proof"] + marshaled_public_key = result["public-key"] public_key = challenge_bypass_ristretto.PublicKey.decode_base64( marshaled_public_key.encode("ascii"), @@ -660,17 +667,17 @@ class PaymentController(object): redeeming a voucher, if no other count is given when the redemption is started. - :ivar set[unicode] allowed_public_keys: The base64-encoded public keys for + :ivar set[str] allowed_public_keys: The base64-encoded public keys for which to accept tokens. - :ivar dict[unicode, Redeeming] _active: A mapping from voucher identifiers + :ivar dict[str, Redeeming] _active: A mapping from voucher identifiers which currently have redemption attempts in progress to a ``Redeeming`` state representing the attempt. - :ivar dict[unicode, datetime] _error: A mapping from voucher identifiers + :ivar dict[str, datetime] _error: A mapping from voucher identifiers which have recently failed with an unrecognized, transient error. - :ivar dict[unicode, datetime] _unpaid: A mapping from voucher identifiers + :ivar dict[str, datetime] _unpaid: A mapping from voucher identifiers which have recently failed a redemption attempt due to an unpaid response from the redemption server to timestamps when the failure was observed. @@ -732,7 +739,7 @@ class PaymentController(object): ) def _retry_redemption(self): - for voucher in self._error.keys() + self._unpaid.keys(): + for voucher in list(self._error.keys()) + list(self._unpaid.keys()): if voucher in self._active: continue if self.get_voucher(voucher).state.should_start_redemption(): @@ -982,22 +989,22 @@ class PaymentController(object): def get_redeemer(plugin_name, node_config, announcement, reactor): - section_name = u"storageclient.plugins.{}".format(plugin_name) + section_name = "storageclient.plugins.{}".format(plugin_name) redeemer_kind = node_config.get_config( section=section_name, - option=u"redeemer", - default=u"ristretto", + option="redeemer", + default="ristretto", ) return _REDEEMERS[redeemer_kind](section_name, node_config, announcement, reactor) _REDEEMERS = { - u"non": NonRedeemer.make, - u"dummy": DummyRedeemer.make, - u"double-spend": DoubleSpendRedeemer.make, - u"unpaid": UnpaidRedeemer.make, - u"error": ErrorRedeemer.make, - u"ristretto": RistrettoRedeemer.make, + "non": NonRedeemer.make, + "dummy": DummyRedeemer.make, + "double-spend": DoubleSpendRedeemer.make, + "unpaid": UnpaidRedeemer.make, + "error": ErrorRedeemer.make, + "ristretto": RistrettoRedeemer.make, } diff --git a/src/_zkapauthorizer/eliot.py b/src/_zkapauthorizer/eliot.py index 8f607d8..ccd8472 100644 --- a/src/_zkapauthorizer/eliot.py +++ b/src/_zkapauthorizer/eliot.py @@ -17,90 +17,97 @@ Eliot field, message, and action definitions for ZKAPAuthorizer. """ from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from future.utils import PY2 +if PY2: + from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401 from eliot import ActionType, Field, MessageType PRIVACYPASS_MESSAGE = Field( - u"message", - unicode, - u"The PrivacyPass request-binding data associated with a pass.", + "message", + str, + "The PrivacyPass request-binding data associated with a pass.", ) INVALID_REASON = Field( - u"reason", - unicode, - u"The reason given by the server for rejecting a pass as invalid.", + "reason", + str, + "The reason given by the server for rejecting a pass as invalid.", ) PASS_COUNT = Field( - u"count", + "count", int, - u"A number of passes.", + "A number of passes.", ) GET_PASSES = MessageType( - u"zkapauthorizer:get-passes", + "zkapauthorizer:get-passes", [PRIVACYPASS_MESSAGE, PASS_COUNT], - u"An attempt to spend passes is beginning.", + "An attempt to spend passes is beginning.", ) SPENT_PASSES = MessageType( - u"zkapauthorizer:spent-passes", + "zkapauthorizer:spent-passes", [PASS_COUNT], - u"An attempt to spend passes has succeeded.", + "An attempt to spend passes has succeeded.", ) INVALID_PASSES = MessageType( - u"zkapauthorizer:invalid-passes", + "zkapauthorizer:invalid-passes", [INVALID_REASON, PASS_COUNT], - u"An attempt to spend passes has found some to be invalid.", + "An attempt to spend passes has found some to be invalid.", ) RESET_PASSES = MessageType( - u"zkapauthorizer:reset-passes", + "zkapauthorizer:reset-passes", [PASS_COUNT], - u"Some passes involved in a failed spending attempt have not definitely been spent and are being returned for future use.", + "Some passes involved in a failed spending attempt have not definitely been spent and are being returned for future use.", ) SIGNATURE_CHECK_FAILED = MessageType( - u"zkapauthorizer:storage-client:signature-check-failed", + "zkapauthorizer:storage-client:signature-check-failed", [PASS_COUNT], - u"Some passes the client tried to use were rejected for having invalid signatures.", + "Some passes the client tried to use were rejected for having invalid signatures.", ) CALL_WITH_PASSES = ActionType( - u"zkapauthorizer:storage-client:call-with-passes", + "zkapauthorizer:storage-client:call-with-passes", [PASS_COUNT], [], - u"A storage operation is being started which may spend some passes.", + "A storage operation is being started which may spend some passes.", ) CURRENT_SIZES = Field( - u"current_sizes", + "current_sizes", dict, - u"A dictionary mapping the numbers of existing shares to their existing sizes.", + "A dictionary mapping the numbers of existing shares to their existing sizes.", ) TW_VECTORS_SUMMARY = Field( - u"tw_vectors_summary", + "tw_vectors_summary", dict, - u"A dictionary mapping share numbers from tw_vectors to test and write vector summaries.", + "A dictionary mapping share numbers from tw_vectors to test and write vector summaries.", ) NEW_SIZES = Field( - u"new_sizes", + "new_sizes", dict, - u"A dictionary like that of CURRENT_SIZES but for the sizes computed for the shares after applying tw_vectors.", + "A dictionary like that of CURRENT_SIZES but for the sizes computed for the shares after applying tw_vectors.", ) NEW_PASSES = Field( - u"new_passes", + "new_passes", int, - u"The number of passes computed as being required for the change in size.", + "The number of passes computed as being required for the change in size.", ) MUTABLE_PASSES_REQUIRED = MessageType( - u"zkapauthorizer:storage:mutable-passes-required", + "zkapauthorizer:storage:mutable-passes-required", [CURRENT_SIZES, TW_VECTORS_SUMMARY, NEW_SIZES, NEW_PASSES], - u"Some number of passes has been computed as the cost of updating a mutable.", + "Some number of passes has been computed as the cost of updating a mutable.", ) diff --git a/src/_zkapauthorizer/model.py b/src/_zkapauthorizer/model.py index 7530d2b..57b7cb4 100644 --- a/src/_zkapauthorizer/model.py +++ b/src/_zkapauthorizer/model.py @@ -17,6 +17,16 @@ This module implements models (in the MVC sense) for the client side of the storage plugin. """ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from future.utils import PY2 +if PY2: + from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401 +from past.builtins import long + from datetime import datetime from functools import wraps from json import dumps, loads @@ -41,12 +51,12 @@ from .validators import greater_than, has_length, is_base64_encoded def parse_datetime(s, **kw): """ - Like ``aniso8601.parse_datetime`` but accept unicode as well. + Like ``aniso8601.parse_datetime`` but accept str as well. """ - if isinstance(s, unicode): + if isinstance(s, str): s = s.encode("utf-8") assert isinstance(s, bytes) - if "delimiter" in kw and isinstance(kw["delimiter"], unicode): + if "delimiter" in kw and isinstance(kw["delimiter"], str): kw["delimiter"] = kw["delimiter"].encode("utf-8") return _parse_datetime(s, **kw) @@ -86,7 +96,7 @@ class NotEnoughTokens(Exception): """ -CONFIG_DB_NAME = u"privatestorageio-zkapauthz-v1.sqlite3" +CONFIG_DB_NAME = "privatestorageio-zkapauthz-v1.sqlite3" def open_and_initialize(path, connect=None): @@ -380,7 +390,7 @@ class VoucherStore(object): Store some unblinded tokens, for example as part of a backup-restore process. - :param list[unicode] unblinded_tokens: The unblinded tokens to store. + :param list[str] unblinded_tokens: The unblinded tokens to store. :param int group_id: The unique identifier of the redemption group to which these tokens belong. @@ -398,7 +408,7 @@ class VoucherStore(object): tokens. This voucher will be marked as redeemed to indicate it has fulfilled its purpose and has no further use for us. - :param unicode public_key: The encoded public key for the private key + :param str public_key: The encoded public key for the private key which was used to sign these tokens. :param list[UnblindedToken] unblinded_tokens: The unblinded tokens to @@ -411,9 +421,9 @@ class VoucherStore(object): inserted tokens, ``False`` otherwise. """ if completed: - voucher_state = u"redeemed" + voucher_state = "redeemed" else: - voucher_state = u"pending" + voucher_state = "pending" if spendable: token_count_increase = len(unblinded_tokens) @@ -683,7 +693,7 @@ class VoucherStore(object): ) tokens = cursor.fetchall() return { - u"unblinded-tokens": list(token for (token,) in tokens), + "unblinded-tokens": list(token for (token,) in tokens), } def start_lease_maintenance(self): @@ -720,9 +730,9 @@ class VoucherStore(object): return None [(started, count, finished)] = activity return LeaseMaintenanceActivity( - parse_datetime(started, delimiter=u" "), + parse_datetime(started, delimiter=" "), count, - parse_datetime(finished, delimiter=u" "), + parse_datetime(finished, delimiter=" "), ) @@ -923,8 +933,8 @@ class Pending(object): def to_json_v1(self): return { - u"name": u"pending", - u"counter": self.counter, + "name": "pending", + "counter": self.counter, } @@ -944,9 +954,9 @@ class Redeeming(object): def to_json_v1(self): return { - u"name": u"redeeming", - u"started": self.started.isoformat(), - u"counter": self.counter, + "name": "redeeming", + "started": self.started.isoformat(), + "counter": self.counter, } @@ -969,9 +979,9 @@ class Redeemed(object): def to_json_v1(self): return { - u"name": u"redeemed", - u"finished": self.finished.isoformat(), - u"token-count": self.token_count, + "name": "redeemed", + "finished": self.finished.isoformat(), + "token-count": self.token_count, } @@ -984,8 +994,8 @@ class DoubleSpend(object): def to_json_v1(self): return { - u"name": u"double-spend", - u"finished": self.finished.isoformat(), + "name": "double-spend", + "finished": self.finished.isoformat(), } @@ -1004,8 +1014,8 @@ class Unpaid(object): def to_json_v1(self): return { - u"name": u"unpaid", - u"finished": self.finished.isoformat(), + "name": "unpaid", + "finished": self.finished.isoformat(), } @@ -1018,16 +1028,16 @@ class Error(object): """ finished = attr.ib(validator=attr.validators.instance_of(datetime)) - details = attr.ib(validator=attr.validators.instance_of(unicode)) + details = attr.ib(validator=attr.validators.instance_of(str)) def should_start_redemption(self): return True def to_json_v1(self): return { - u"name": u"error", - u"finished": self.finished.isoformat(), - u"details": self.details, + "name": "error", + "finished": self.finished.isoformat(), + "details": self.details, } @@ -1089,15 +1099,15 @@ class Voucher(object): @classmethod def from_row(cls, row): def state_from_row(state, row): - if state == u"pending": + if state == "pending": return Pending(counter=row[3]) - if state == u"double-spend": + if state == "double-spend": return DoubleSpend( - parse_datetime(row[0], delimiter=u" "), + parse_datetime(row[0], delimiter=" "), ) - if state == u"redeemed": + if state == "redeemed": return Redeemed( - parse_datetime(row[0], delimiter=u" "), + parse_datetime(row[0], delimiter=" "), row[1], ) raise ValueError("Unknown voucher state {}".format(state)) @@ -1112,54 +1122,54 @@ class Voucher(object): # 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" "), + created=parse_datetime(created, delimiter=" "), state=state_from_row(state, row[4:]), ) @classmethod def from_json(cls, json): values = loads(json) - version = values.pop(u"version") + version = values.pop("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_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[u"started"]), - counter=state_json[u"counter"], + started=parse_datetime(state_json["started"]), + counter=state_json["counter"], ) - elif state_name == u"double-spend": + elif state_name == "double-spend": state = DoubleSpend( - finished=parse_datetime(state_json[u"finished"]), + finished=parse_datetime(state_json["finished"]), ) - elif state_name == u"redeemed": + elif state_name == "redeemed": state = Redeemed( - finished=parse_datetime(state_json[u"finished"]), - token_count=state_json[u"token-count"], + finished=parse_datetime(state_json["finished"]), + token_count=state_json["token-count"], ) - elif state_name == u"unpaid": + elif state_name == "unpaid": state = Unpaid( - finished=parse_datetime(state_json[u"finished"]), + finished=parse_datetime(state_json["finished"]), ) - elif state_name == u"error": + elif state_name == "error": state = Error( - finished=parse_datetime(state_json[u"finished"]), - details=state_json[u"details"], + finished=parse_datetime(state_json["finished"]), + details=state_json["details"], ) else: raise ValueError("Unrecognized state {!r}".format(state_json)) return cls( - number=values[u"number"].encode("ascii"), - expected_tokens=values[u"expected-tokens"], + number=values["number"].encode("ascii"), + expected_tokens=values["expected-tokens"], created=None - if values[u"created"] is None - else parse_datetime(values[u"created"]), + if values["created"] is None + else parse_datetime(values["created"]), state=state, ) @@ -1172,9 +1182,9 @@ class Voucher(object): def to_json_v1(self): state = self.state.to_json_v1() return { - u"number": self.number.decode("ascii"), - u"expected-tokens": self.expected_tokens, - u"created": None if self.created is None else self.created.isoformat(), - u"state": state, - u"version": 1, + "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, } diff --git a/src/_zkapauthorizer/resource.py b/src/_zkapauthorizer/resource.py index f5db053..89b0a11 100644 --- a/src/_zkapauthorizer/resource.py +++ b/src/_zkapauthorizer/resource.py @@ -21,6 +21,16 @@ vouchers for fresh tokens. In the future it should also allow users to read statistics about token usage. """ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from future.utils import PY2 +if PY2: + from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401 +from past.builtins import long + from itertools import islice from json import dumps, load, loads from sys import maxint @@ -65,18 +75,18 @@ def get_token_count( Retrieve the configured voucher value, in number of tokens, from the given configuration. - :param unicode plugin_name: The plugin name to use to choose a + :param str plugin_name: The plugin name to use to choose a configuration section. :param _Config node_config: See ``from_configuration``. :param int default: The value to return if none is configured. """ - section_name = u"storageclient.plugins.{}".format(plugin_name) + section_name = "storageclient.plugins.{}".format(plugin_name) return int( node_config.get_config( section=section_name, - option=u"default-token-count", + option="default-token-count", default=NUM_TOKENS, ) ) @@ -109,7 +119,7 @@ def from_configuration( :return IZKAPRoot: The root of the resource hierarchy presented by the client side of the plugin. """ - plugin_name = u"privatestorageio-zkapauthz-v1" + plugin_name = "privatestorageio-zkapauthz-v1" if redeemer is None: redeemer = get_redeemer( plugin_name, @@ -219,7 +229,7 @@ class _CalculatePrice(Resource): Calculate the price in ZKAPs to store or continue storing files specified sizes. """ - if wrong_content_type(request, u"application/json"): + if wrong_content_type(request, "application/json"): return NOT_DONE_YET application_json(request) @@ -235,8 +245,8 @@ class _CalculatePrice(Resource): ) try: - version = body_object[u"version"] - sizes = body_object[u"sizes"] + version = body_object["version"] + sizes = body_object["sizes"] except (TypeError, KeyError): request.setResponseCode(BAD_REQUEST) return dumps( @@ -268,8 +278,8 @@ class _CalculatePrice(Resource): price = self._price_calculator.calculate(sizes) return dumps( { - u"price": price, - u"period": self._lease_period, + "price": price, + "period": self._lease_period, } ) @@ -280,14 +290,14 @@ def wrong_content_type(request, required_type): :param request: The request object to check. - :param unicode required_type: The required content-type (eg - ``u"application/json"``). + :param str required_type: The required content-type (eg + ``"application/json"``). :return bool: ``True`` if the content-type is wrong and an error response has been generated. ``False`` otherwise. """ actual_type = request.requestHeaders.getRawHeaders( - u"content-type", + "content-type", [None], )[0] if actual_type != required_type: @@ -303,7 +313,7 @@ def application_json(request): :param twisted.web.iweb.IRequest request: The request to modify. """ - request.responseHeaders.setRawHeaders(u"content-type", [u"application/json"]) + request.responseHeaders.setRawHeaders("content-type", ["application/json"]) class _ProjectVersion(Resource): @@ -339,7 +349,7 @@ class _UnblindedTokenCollection(Resource): """ application_json(request) state = self._store.backup() - unblinded_tokens = state[u"unblinded-tokens"] + unblinded_tokens = state["unblinded-tokens"] limit = request.args.get(b"limit", [None])[0] if limit is not None: @@ -349,14 +359,14 @@ class _UnblindedTokenCollection(Resource): return dumps( { - u"total": len(unblinded_tokens), - u"spendable": self._store.count_unblinded_tokens(), - u"unblinded-tokens": list( + "total": len(unblinded_tokens), + "spendable": self._store.count_unblinded_tokens(), + "unblinded-tokens": list( islice( (token for token in unblinded_tokens if token > position), limit ) ), - u"lease-maintenance-spending": self._lease_maintenance_activity(), + "lease-maintenance-spending": self._lease_maintenance_activity(), } ) @@ -365,7 +375,7 @@ class _UnblindedTokenCollection(Resource): Store some unblinded tokens. """ application_json(request) - unblinded_tokens = load(request.content)[u"unblinded-tokens"] + unblinded_tokens = load(request.content)["unblinded-tokens"] self._store.insert_unblinded_tokens(unblinded_tokens, group_id=0) return dumps({}) @@ -374,8 +384,8 @@ class _UnblindedTokenCollection(Resource): if activity is None: return activity return { - u"when": activity.finished.isoformat(), - u"count": activity.passes_required, + "when": activity.finished.isoformat(), + "count": activity.passes_required, } @@ -401,14 +411,14 @@ class _VoucherCollection(Resource): try: payload = loads(request.content.read()) except Exception: - return bad_request(u"json request body required").render(request) - if payload.keys() != [u"voucher"]: + return bad_request("json request body required").render(request) + if payload.keys() != ["voucher"]: return bad_request( - u"request object must have exactly one key: 'voucher'" + "request object must have exactly one key: 'voucher'" ).render(request) - voucher = payload[u"voucher"] + voucher = payload["voucher"] if not is_syntactic_voucher(voucher): - return bad_request(u"submitted voucher is syntactically invalid").render( + return bad_request("submitted voucher is syntactically invalid").render( request ) @@ -422,7 +432,7 @@ class _VoucherCollection(Resource): application_json(request) return dumps( { - u"vouchers": list( + "vouchers": list( self._controller.incorporate_transient_state(voucher).marshal() for voucher in self._store.list() ), @@ -444,12 +454,12 @@ def is_syntactic_voucher(voucher): """ :param voucher: A candidate object to inspect. - :return bool: ``True`` if and only if ``voucher`` is a unicode string + :return bool: ``True`` if and only if ``voucher`` is a text string containing a syntactically valid voucher. This says **nothing** about the validity of the represented voucher itself. A ``True`` result - only means the unicode string can be **interpreted** as a voucher. + only means the string can be **interpreted** as a voucher. """ - if not isinstance(voucher, unicode): + if not isinstance(voucher, str): return False if len(voucher) != 44: # TODO. 44 is the length of 32 bytes base64 encoded. This model @@ -480,7 +490,7 @@ class VoucherView(Resource): return self._voucher.to_json() -def bad_request(reason=u"Bad Request"): +def bad_request(reason="Bad Request"): """ :return IResource: A resource which can be rendered to produce a **BAD REQUEST** response. diff --git a/src/_zkapauthorizer/storage_common.py b/src/_zkapauthorizer/storage_common.py index bbe326a..aeb8618 100644 --- a/src/_zkapauthorizer/storage_common.py +++ b/src/_zkapauthorizer/storage_common.py @@ -16,7 +16,15 @@ Functionality shared between the storage client and server. """ +from __future__ import absolute_import from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from future.utils import PY2 +if PY2: + from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401 +from past.builtins import long from base64 import b64encode @@ -50,7 +58,7 @@ class MorePassesRequired(Exception): def _message_maker(label): def make_message(storage_index): - return u"{label} {storage_index}".format( + return "{label} {storage_index}".format( label=label, storage_index=b64encode(storage_index), ) @@ -60,10 +68,10 @@ def _message_maker(label): # Functions to construct the PrivacyPass request-binding message for pass # construction for different Tahoe-LAFS storage operations. -allocate_buckets_message = _message_maker(u"allocate_buckets") -add_lease_message = _message_maker(u"add_lease") +allocate_buckets_message = _message_maker("allocate_buckets") +add_lease_message = _message_maker("add_lease") slot_testv_and_readv_and_writev_message = _message_maker( - u"slot_testv_and_readv_and_writev" + "slot_testv_and_readv_and_writev" ) # The number of bytes we're willing to store for a lease period for each pass @@ -80,8 +88,8 @@ def get_configured_shares_needed(node_config): """ return int( node_config.get_config( - section=u"client", - option=u"shares.needed", + section="client", + option="shares.needed", default=3, ) ) @@ -96,8 +104,8 @@ def get_configured_shares_total(node_config): """ return int( node_config.get_config( - section=u"client", - option=u"shares.total", + section="client", + option="shares.total", default=10, ) ) @@ -111,11 +119,11 @@ def get_configured_pass_value(node_config): value is read from the **pass-value** option of the ZKAPAuthorizer plugin client section. """ - section_name = u"storageclient.plugins.privatestorageio-zkapauthz-v1" + section_name = "storageclient.plugins.privatestorageio-zkapauthz-v1" return int( node_config.get_config( section=section_name, - option=u"pass-value", + option="pass-value", default=BYTES_PER_PASS, ) ) @@ -125,17 +133,19 @@ def get_configured_allowed_public_keys(node_config): """ Read the set of allowed issuer public keys from the given configuration. """ - section_name = u"storageclient.plugins.privatestorageio-zkapauthz-v1" + section_name = "storageclient.plugins.privatestorageio-zkapauthz-v1" return set( node_config.get_config( section=section_name, - option=u"allowed-public-keys", + option="allowed-public-keys", ) .strip() .split(",") ) +_dict_values = type(dict().values()) + def required_passes(bytes_per_pass, share_sizes): """ Calculate the number of passes that are required to store shares of the @@ -148,9 +158,9 @@ def required_passes(bytes_per_pass, share_sizes): :return int: The number of passes required to cover the storage cost. """ - if not isinstance(share_sizes, list): + if not isinstance(share_sizes, (list, _dict_values)): raise TypeError( - "Share sizes must be a list of integers, got {!r} instead".format( + "Share sizes must be a list (or dict_values) of integers, got {!r} instead".format( share_sizes, ), ) @@ -253,7 +263,7 @@ def get_required_new_passes_for_mutable_write(pass_value, current_sizes, tw_vect """ current_passes = required_passes( pass_value, - current_sizes.values(), + list(current_sizes.values()), ) new_sizes = current_sizes.copy() diff --git a/src/_zkapauthorizer/tests/strategies.py b/src/_zkapauthorizer/tests/strategies.py index 61b4019..6ce17b9 100644 --- a/src/_zkapauthorizer/tests/strategies.py +++ b/src/_zkapauthorizer/tests/strategies.py @@ -16,6 +16,15 @@ Hypothesis strategies for property testing. """ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from future.utils import PY2 +if PY2: + from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401 + from base64 import b64encode, urlsafe_b64encode from datetime import datetime, timedelta from urllib import quote @@ -142,7 +151,7 @@ def tahoe_config_texts(storage_client_plugins, shares): def merge_shares(shares, the_rest): for (k, v) in zip(("needed", "happy", "total"), shares): if v is not None: - the_rest["shares." + k] = u"{}".format(v) + the_rest["shares." + k] = "{}".format(v) return the_rest client_section = builds( @@ -151,7 +160,7 @@ def tahoe_config_texts(storage_client_plugins, shares): fixed_dictionaries( { "storage.plugins": just( - u",".join(storage_client_plugins.keys()), + ",".join(storage_client_plugins.keys()), ), }, ), @@ -186,8 +195,8 @@ def minimal_tahoe_configs(storage_client_plugins=None, shares=just((None, None, :param shares: See ``tahoe_config_texts``. - :return SearchStrategy[unicode]: A strategy that builds unicode strings - which are Tahoe-LAFS configuration file contents. + :return SearchStrategy[str]: A strategy that builds text strings which are + Tahoe-LAFS configuration file contents. """ if storage_client_plugins is None: storage_client_plugins = {} @@ -207,9 +216,9 @@ def node_nicknames(): alphabet=characters( blacklist_categories={ # Surrogates - u"Cs", + "Cs", # Unnamed and control characters - u"Cc", + "Cc", }, ), ) @@ -241,23 +250,23 @@ def server_configurations(signing_key_path): """ Build configuration values for the server-side plugin. - :param unicode signing_key_path: A value to insert for the + :param str signing_key_path: A value to insert for the **ristretto-signing-key-path** item. """ return one_of( fixed_dictionaries( { - u"pass-value": + "pass-value": # The configuration is ini so everything is always a byte string! - integers(min_value=1).map(bytes), + integers(min_value=1).map(lambda v: u"{}".format(v).encode("ascii")), } ), just({}), ).map( lambda config: config.update( { - u"ristretto-issuer-root-url": u"https://issuer.example.invalid/", - u"ristretto-signing-key-path": signing_key_path.path, + "ristretto-issuer-root-url": "https://issuer.example.invalid/", + "ristretto-signing-key-path": signing_key_path.path, } ) or config, @@ -294,8 +303,8 @@ def zkapauthz_configuration( allowed_public_keys, ): config = { - u"default-token-count": u"32", - u"allowed-public-keys": u",".join(allowed_public_keys), + "default-token-count": "32", + "allowed-public-keys": ",".join(allowed_public_keys), } config.update(extra_configuration) return config @@ -314,8 +323,8 @@ def client_ristrettoredeemer_configurations(): return zkapauthz_configuration( just( { - u"ristretto-issuer-root-url": u"https://issuer.example.invalid/", - u"redeemer": u"ristretto", + "ristretto-issuer-root-url": "https://issuer.example.invalid/", + "redeemer": "ristretto", } ) ) @@ -351,10 +360,10 @@ def client_dummyredeemer_configurations( extra_config = lease_configs.map( lambda config: config.update( { - u"redeemer": u"dummy", + "redeemer": "dummy", # Pick out one of the allowed public keys so that the dummy # appears to produce usable tokens. - u"issuer-public-key": next(iter(allowed_keys)), + "issuer-public-key": next(iter(allowed_keys)), } ) or config, @@ -382,7 +391,7 @@ def client_doublespendredeemer_configurations(default_token_counts=token_counts( return zkapauthz_configuration( just( { - u"redeemer": u"double-spend", + "redeemer": "double-spend", } ) ) @@ -395,7 +404,7 @@ def client_unpaidredeemer_configurations(): return zkapauthz_configuration( just( { - u"redeemer": u"unpaid", + "redeemer": "unpaid", } ) ) @@ -408,7 +417,7 @@ def client_nonredeemer_configurations(): return zkapauthz_configuration( just( { - u"redeemer": u"non", + "redeemer": "non", } ) ) @@ -421,8 +430,8 @@ def client_errorredeemer_configurations(details): return zkapauthz_configuration( just( { - u"redeemer": u"error", - u"details": details, + "redeemer": "error", + "details": details, } ) ) @@ -492,14 +501,14 @@ def direct_tahoe_configs( """ config_texts = minimal_tahoe_configs( { - u"privatestorageio-zkapauthz-v1": zkapauthz_v1_configuration, + "privatestorageio-zkapauthz-v1": zkapauthz_v1_configuration, }, shares, ) return config_texts.map( lambda config_text: config_from_string( - u"/dev/null/illegal", - u"", + "/dev/null/illegal", + "", config_text.encode("utf-8"), ), ) @@ -845,7 +854,7 @@ def bytes_for_share(sharenum, size): given share number """ if 0 <= sharenum <= 255: - return (unichr(sharenum) * size).encode("latin-1") + return (chr(sharenum) * size).encode("latin-1") raise ValueError("Sharenum must be between 0 and 255 inclusive.") @@ -949,7 +958,7 @@ def announcements(): """ return just( { - u"ristretto-issuer-root-url": u"https://issuer.example.invalid/", + "ristretto-issuer-root-url": "https://issuer.example.invalid/", } ) @@ -977,7 +986,7 @@ class _DirectoryNode(object): _storage_index = attr.ib() _children = attr.ib() - def list(self): + def list(self): # noqa: F811 return succeed(self._children) def get_storage_index(self): diff --git a/src/_zkapauthorizer/tests/test_client_resource.py b/src/_zkapauthorizer/tests/test_client_resource.py index 7e3e9ab..26f0786 100644 --- a/src/_zkapauthorizer/tests/test_client_resource.py +++ b/src/_zkapauthorizer/tests/test_client_resource.py @@ -18,6 +18,13 @@ plugin. """ from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from future.utils import PY2 +if PY2: + from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401 from datetime import datetime from io import BytesIO @@ -110,7 +117,7 @@ from .strategies import ( vouchers, ) -TRANSIENT_ERROR = u"something went wrong, who knows what" +TRANSIENT_ERROR = "something went wrong, who knows what" # Helper to work-around https://github.com/twisted/treq/issues/161 def uncooperator(started=True): @@ -137,7 +144,7 @@ def is_not_json(bytestring): def not_vouchers(): """ - Builds unicode strings which are not legal vouchers. + Builds text strings which are not legal vouchers. """ return one_of( text().filter( @@ -147,7 +154,7 @@ def not_vouchers(): # Turn a valid voucher into a voucher that is invalid only by # containing a character from the base64 alphabet in place of one # from the urlsafe-base64 alphabet. - lambda voucher: u"/" + lambda voucher: "/" + voucher[1:], ), ) @@ -155,7 +162,7 @@ def not_vouchers(): def is_urlsafe_base64(text): """ - :param unicode text: A candidate unicode string to inspect. + :param str text: A candidate text string to inspect. :return bool: ``True`` if and only if ``text`` is urlsafe-base64 encoded """ @@ -174,13 +181,13 @@ def invalid_bodies(): # The wrong key but the right kind of value. fixed_dictionaries( { - u"some-key": vouchers(), + "some-key": vouchers(), } ).map(dumps), # The right key but the wrong kind of value. fixed_dictionaries( { - u"voucher": one_of( + "voucher": one_of( integers(), not_vouchers(), ), @@ -242,7 +249,7 @@ def authorized_request(api_auth_token, agent, method, uri, headers=None, data=No else: headers = Headers(headers) headers.setRawHeaders( - u"authorization", + "authorization", [b"tahoe-lafs {}".format(api_auth_token)], ) return agent.request( @@ -323,24 +330,24 @@ class GetTokenCountTests(TestCase): ``get_token_count`` returns the integer value of the ``default-token-count`` item from the given configuration object. """ - plugin_name = u"hello-world" + plugin_name = "hello-world" if token_count is None: expected_count = NUM_TOKENS token_config = {} else: expected_count = token_count - token_config = {u"default-token-count": u"{}".format(expected_count)} + token_config = {"default-token-count": "{}".format(expected_count)} config_text = config_string_from_sections( [ { - u"storageclient.plugins." + plugin_name: token_config, + "storageclient.plugins." + plugin_name: token_config, } ] ) node_config = config_from_string( self.useFixture(TempDir()).join(b"tahoe"), - u"tub.port", + "tub.port", config_text.encode("utf-8"), ) self.assertThat( @@ -492,7 +499,7 @@ class UnblindedTokenTests(TestCase): data = BytesIO( dumps( { - u"unblinded-tokens": list( + "unblinded-tokens": list( token.unblinded_token.decode("ascii") for token in unblinded_tokens ) @@ -514,7 +521,7 @@ class UnblindedTokenTests(TestCase): ), ) - stored_tokens = root.controller.store.backup()[u"unblinded-tokens"] + stored_tokens = root.controller.store.backup()["unblinded-tokens"] self.assertThat( stored_tokens, @@ -560,8 +567,8 @@ class UnblindedTokenTests(TestCase): b"http://127.0.0.1/unblinded-token", ) self.addDetail( - u"requesting result", - text_content(u"{}".format(vars(requesting.result))), + "requesting result", + text_content("{}".format(vars(requesting.result))), ) self.assertThat( requesting, @@ -608,8 +615,8 @@ class UnblindedTokenTests(TestCase): b"http://127.0.0.1/unblinded-token?limit={}".format(limit), ) self.addDetail( - u"requesting result", - text_content(u"{}".format(vars(requesting.result))), + "requesting result", + text_content("{}".format(vars(requesting.result))), ) self.assertThat( requesting, @@ -663,8 +670,8 @@ class UnblindedTokenTests(TestCase): ), ) self.addDetail( - u"requesting result", - text_content(u"{}".format(vars(requesting.result))), + "requesting result", + text_content("{}".format(vars(requesting.result))), ) self.assertThat( requesting, @@ -674,7 +681,7 @@ class UnblindedTokenTests(TestCase): AllMatch( MatchesAll( GreaterThan(position), - IsInstance(unicode), + IsInstance(str), ), ), matches_lease_maintenance_spending(), @@ -715,7 +722,7 @@ class UnblindedTokenTests(TestCase): ) d.addCallback(readBody) d.addCallback( - lambda body: loads(body)[u"unblinded-tokens"], + lambda body: loads(body)["unblinded-tokens"], ) return d @@ -756,7 +763,7 @@ class UnblindedTokenTests(TestCase): succeeded( MatchesPredicate( check_tokens, - u"initial, after (%s): initial[1:] != after", + "initial, after (%s): initial[1:] != after", ), ), ) @@ -803,7 +810,7 @@ class UnblindedTokenTests(TestCase): ) d.addCallback(readBody) d.addCallback( - lambda body: loads(body)[u"lease-maintenance-spending"], + lambda body: loads(body)["lease-maintenance-spending"], ) self.assertThat( d, @@ -845,10 +852,10 @@ def succeeded_with_unblinded_tokens_with_matcher( succeeded( ContainsDict( { - u"total": Equals(all_token_count), - u"spendable": match_spendable_token_count, - u"unblinded-tokens": match_unblinded_tokens, - u"lease-maintenance-spending": match_lease_maint_spending, + "total": Equals(all_token_count), + "spendable": match_spendable_token_count, + "unblinded-tokens": match_unblinded_tokens, + "lease-maintenance-spending": match_lease_maint_spending, } ), ), @@ -873,7 +880,7 @@ def succeeded_with_unblinded_tokens(all_token_count, returned_token_count): match_spendable_token_count=Equals(all_token_count), match_unblinded_tokens=MatchesAll( HasLength(returned_token_count), - AllMatch(IsInstance(unicode)), + AllMatch(IsInstance(str)), ), match_lease_maint_spending=matches_lease_maintenance_spending(), ) @@ -889,8 +896,8 @@ def matches_lease_maintenance_spending(): Is(None), ContainsDict( { - u"when": matches_iso8601_datetime(), - u"amount": matches_positive_integer(), + "when": matches_iso8601_datetime(), + "amount": matches_positive_integer(), } ), ) @@ -905,11 +912,11 @@ def matches_positive_integer(): def matches_iso8601_datetime(): """ - :return: A matcher which matches unicode strings which can be parsed as an + :return: A matcher which matches text strings which can be parsed as an ISO8601 datetime string. """ return MatchesAll( - IsInstance(unicode), + IsInstance(str), AfterPreprocessing( parse_datetime, lambda d: Always(), @@ -942,7 +949,7 @@ class VoucherTests(TestCase): ) root = root_from_config(config, datetime.now) agent = RequestTraversalAgent(root) - data = BytesIO(dumps({u"voucher": voucher.decode("ascii")})) + data = BytesIO(dumps({"voucher": voucher.decode("ascii")})) requesting = authorized_request( api_auth_token, agent, @@ -951,8 +958,8 @@ class VoucherTests(TestCase): data=data, ) self.addDetail( - u"requesting result", - text_content(u"{}".format(vars(requesting.result))), + "requesting result", + text_content("{}".format(vars(requesting.result))), ) self.assertThat( requesting, @@ -983,8 +990,8 @@ class VoucherTests(TestCase): data=BytesIO(body), ) self.addDetail( - u"requesting result", - text_content(u"{}".format(vars(requesting.result))), + "requesting result", + text_content("{}".format(vars(requesting.result))), ) self.assertThat( requesting, @@ -1006,7 +1013,7 @@ class VoucherTests(TestCase): ) root = root_from_config(config, datetime.now) agent = RequestTraversalAgent(root) - url = u"http://127.0.0.1/voucher/{}".format( + url = "http://127.0.0.1/voucher/{}".format( quote( not_voucher.encode("utf-8"), safe=b"", @@ -1043,7 +1050,7 @@ class VoucherTests(TestCase): api_auth_token, agent, b"GET", - u"http://127.0.0.1/voucher/{}".format(voucher).encode("ascii"), + "http://127.0.0.1/voucher/{}".format(voucher).encode("ascii"), ) self.assertThat( requesting, @@ -1235,7 +1242,7 @@ class VoucherTests(TestCase): agent, b"PUT", b"http://127.0.0.1/voucher", - data=BytesIO(dumps({u"voucher": voucher.decode("ascii")})), + data=BytesIO(dumps({"voucher": voucher.decode("ascii")})), ) self.assertThat( putting, @@ -1248,7 +1255,7 @@ class VoucherTests(TestCase): api_auth_token, agent, b"GET", - u"http://127.0.0.1/voucher/{}".format( + "http://127.0.0.1/voucher/{}".format( quote( voucher.encode("utf-8"), safe=b"", @@ -1292,7 +1299,7 @@ class VoucherTests(TestCase): vouchers, Equals( { - u"vouchers": list( + "vouchers": list( Voucher( number=voucher, expected_tokens=count, @@ -1329,7 +1336,7 @@ class VoucherTests(TestCase): vouchers, Equals( { - u"vouchers": list( + "vouchers": list( Voucher( number=voucher, expected_tokens=count, @@ -1362,7 +1369,7 @@ class VoucherTests(TestCase): note("{} vouchers".format(len(vouchers))) for voucher in vouchers: - data = BytesIO(dumps({u"voucher": voucher.decode("ascii")})) + data = BytesIO(dumps({"voucher": voucher.decode("ascii")})) putting = authorized_request( api_auth_token, agent, @@ -1479,22 +1486,22 @@ def bad_calculate_price_requests(): good_data = fixed_dictionaries( { - u"version": good_version, - u"sizes": good_sizes, + "version": good_version, + "sizes": good_sizes, } ).map(dumps) bad_data_version = fixed_dictionaries( { - u"version": bad_version, - u"sizes": good_sizes, + "version": bad_version, + "sizes": good_sizes, } ).map(dumps) bad_data_sizes = fixed_dictionaries( { - u"version": good_version, - u"sizes": bad_sizes, + "version": good_version, + "sizes": bad_sizes, } ).map(dumps) @@ -1638,7 +1645,7 @@ class CalculatePriceTests(TestCase): b"POST", self.url, headers={b"content-type": [b"application/json"]}, - data=BytesIO(dumps({u"version": 1, u"sizes": sizes})), + data=BytesIO(dumps({"version": 1, "sizes": sizes})), ), succeeded( matches_response( @@ -1648,8 +1655,8 @@ class CalculatePriceTests(TestCase): loads, Equals( { - u"price": expected_price, - u"period": 60 * 60 * 24 * 31 - min_time_remaining, + "price": expected_price, + "period": 60 * 60 * 24 * 31 - min_time_remaining, } ), ), @@ -1660,8 +1667,8 @@ class CalculatePriceTests(TestCase): def application_json(): return AfterPreprocessing( - lambda h: h.getRawHeaders(u"content-type"), - Equals([u"application/json"]), + lambda h: h.getRawHeaders("content-type"), + Equals(["application/json"]), ) @@ -1701,8 +1708,8 @@ class _MatchResponse(object): def match(self, response): self._details.update( { - u"code": response.code, - u"headers": response.headers.getAllRawHeaders(), + "code": response.code, + "headers": response.headers.getAllRawHeaders(), } ) return MatchesStructure( diff --git a/src/_zkapauthorizer/tests/test_controller.py b/src/_zkapauthorizer/tests/test_controller.py index e4f08b1..1dc1794 100644 --- a/src/_zkapauthorizer/tests/test_controller.py +++ b/src/_zkapauthorizer/tests/test_controller.py @@ -43,6 +43,7 @@ from testtools.matchers import ( Equals, HasLength, IsInstance, + Is, MatchesAll, MatchesStructure, ) @@ -73,6 +74,7 @@ from ..controller import ( UnpaidRedeemer, UnrecognizedFailureReason, token_count_for_group, + bracket, ) from ..model import DoubleSpend as model_DoubleSpend from ..model import Error as model_Error @@ -1289,3 +1291,54 @@ def bad_content_type(request): b"Unsupported media type", b"Unsupported media type", ).render(request) + + +class BracketTests(TestCase): + """ + Tests for ``bracket``. + """ + def test_success(self): + """ + ``bracket`` calls ``first`` then ``between`` then ``last`` and returns a + ``Deferred`` that fires with the result of ``between``. + """ + result = object() + actions = [] + first = partial(actions.append, "first") + def between(): + actions.append("between") + return result + last = partial(actions.append, "last") + self.assertThat( + bracket(first, last, between), + succeeded( + Is(result), + ), + ) + self.assertThat( + actions, + Equals(["first", "between", "last"]), + ) + + def test_failure(self): + """ + ``bracket`` calls ``first`` then ``between`` then ``last`` and returns a + ``Deferred`` that fires with the failure result of ``between``. + """ + class SomeException(Exception): + pass + actions = [] + first = partial(actions.append, "first") + def between(): + actions.append("between") + raise SomeException() + last = partial(actions.append, "last") + self.assertThat( + bracket(first, last, between), + failed( + AfterPreprocessing( + lambda failure: failure.value, + IsInstance(SomeException), + ), + ), + ) diff --git a/src/_zkapauthorizer/tests/test_model.py b/src/_zkapauthorizer/tests/test_model.py index 93605cd..6c0e360 100644 --- a/src/_zkapauthorizer/tests/test_model.py +++ b/src/_zkapauthorizer/tests/test_model.py @@ -269,7 +269,7 @@ class VoucherStoreTests(TestCase): ), Raises( AfterPreprocessing( - lambda (type, exc, tb): exc, + lambda exc_info: exc_info[1], MatchesAll( IsInstance(StoreOpenError), MatchesStructure( diff --git a/src/_zkapauthorizer/tests/test_plugin.py b/src/_zkapauthorizer/tests/test_plugin.py index f300d3a..56c63aa 100644 --- a/src/_zkapauthorizer/tests/test_plugin.py +++ b/src/_zkapauthorizer/tests/test_plugin.py @@ -17,6 +17,13 @@ Tests for the Tahoe-LAFS plugin. """ from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from future.utils import PY2 +if PY2: + from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401 from datetime import timedelta from functools import partial @@ -99,7 +106,7 @@ from .strategies import ( vouchers, ) -SIGNING_KEY_PATH = FilePath(__file__).sibling(u"testing-signing.key") +SIGNING_KEY_PATH = FilePath(__file__).sibling("testing-signing.key") def get_rref(interface=None): @@ -281,12 +288,12 @@ class ServerPluginTests(TestCase): and an interval how often to do so, test that metrics are actually written there after the configured interval. """ - metrics_path = self.useFixture(TempDir()).join(u"metrics") + metrics_path = self.useFixture(TempDir()).join("metrics") configuration = { - u"prometheus-metrics-path": metrics_path, - u"prometheus-metrics-interval": str(int(metrics_interval.total_seconds())), - u"ristretto-issuer-root-url": "foo", - u"ristretto-signing-key-path": SIGNING_KEY_PATH.path, + "prometheus-metrics-path": metrics_path, + "prometheus-metrics-interval": str(int(metrics_interval.total_seconds())), + "ristretto-issuer-root-url": "foo", + "ristretto-signing-key-path": SIGNING_KEY_PATH.path, } announceable = extract_result( storage_server.get_storage_server( @@ -341,8 +348,8 @@ tahoe_configs_with_dummy_redeemer = tahoe_configs(client_dummyredeemer_configura tahoe_configs_with_mismatched_issuer = minimal_tahoe_configs( { - u"privatestorageio-zkapauthz-v1": just( - {u"ristretto-issuer-root-url": u"https://another-issuer.example.invalid/"} + "privatestorageio-zkapauthz-v1": just( + {"ristretto-issuer-root-url": "https://another-issuer.example.invalid/"} ), } ) @@ -401,8 +408,8 @@ class ClientPluginTests(TestCase): # switch to an io.StringIO here. config_text = StringIO() node_config.config.write(config_text) - self.addDetail(u"config", text_content(config_text.getvalue())) - self.addDetail(u"announcement", text_content(unicode(announcement))) + self.addDetail("config", text_content(config_text.getvalue())) + self.addDetail("announcement", text_content(str(announcement))) self.assertThat( lambda: storage_server.get_storage_client( node_config, @@ -521,12 +528,12 @@ class ClientPluginTests(TestCase): # tests, at least until creating a real server doesn't involve so much # complex setup. So avoid using any of the client APIs that make a # remote call ... which is all of them. - pass_group = storage_client._get_passes(u"request binding message", num_passes) + pass_group = storage_client._get_passes("request binding message", num_passes) pass_group.mark_spent() # There should be no unblinded tokens left to extract. self.assertThat( - lambda: storage_client._get_passes(u"request binding message", 1), + lambda: storage_client._get_passes("request binding message", 1), raises(NotEnoughTokens), ) @@ -540,8 +547,8 @@ class ClientPluginTests(TestCase): lambda logged_message: logged_message.message, ContainsDict( { - u"message": Equals(u"request binding message"), - u"count": Equals(num_passes), + "message": Equals("request binding message"), + "count": Equals(num_passes), } ), ), diff --git a/src/_zkapauthorizer/tests/test_pricecalculator.py b/src/_zkapauthorizer/tests/test_pricecalculator.py index baadd91..2dd7ab9 100644 --- a/src/_zkapauthorizer/tests/test_pricecalculator.py +++ b/src/_zkapauthorizer/tests/test_pricecalculator.py @@ -17,6 +17,16 @@ Tests for ``_zkapauthorizer.pricecalculator``. """ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from future.utils import PY2 +if PY2: + from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401 +from past.builtins import long + from functools import partial from hypothesis import given -- GitLab