diff --git a/letsencrypt/acme/interfaces.py b/letsencrypt/acme/interfaces.py index b6e40a436..164ffcea2 100644 --- a/letsencrypt/acme/interfaces.py +++ b/letsencrypt/acme/interfaces.py @@ -1,4 +1,16 @@ -"""ACME interfaces.""" +"""ACME interfaces. + +Separation between :class:`IJSONSerializable` and :class:`IJSONDeserializable` +is necessary because we want to use ``cls.from_valid_json`` +classmethod on class and ``cls().to_json()`` on object, i.e. class +instance. ``cls.to_json()`` doesn't make much sense. Therefore a class +definition that requires both must call +``zope.interface.implements(IJSONSerializable)`` and +``zope.interface.classImplements(IJSONDeSerializable)`` (note the +difference btween `implements` and `classImplements`) and +:class:`letsencrypt.acme.util.ACMEObject` definition is an example. + +""" import zope.interface # pylint: disable=no-self-argument,no-method-argument,no-init,inherit-non-class @@ -12,12 +24,31 @@ class IJSONSerializable(zope.interface.Interface): def to_json(): """Prepare JSON serializable object. - :returns: JSON object ready to be serialized. Note, however, that - this might return other - :class:`letsencrypt.acme.interfaces.IJSONSerializable` - objects, that haven't been serialized yet, which is fine as - long as :func:`letsencrypt.acme.util.dump_ijsonserializable` - is used. + Note, however, that this method might return other + :class:`letsencrypt.acme.interfaces.IJSONSerializable` + objects that haven't been serialized yet, which is fine as + long as :func:`letsencrypt.acme.util.dump_ijsonserializable` + is used. For example:: + + class Foo(object): + zope.interface.implements(IJSONSerializable) + + def to_json(self): + return 'foo' + + class Bar(object): + zope.interface.implemeents(IJSONSerializable) + + def to_json(self): + return [Foo(), Foo()] + + bar = Bar() + assert isinstance(bar.to_json()[0], Foo) + assert isinstance(bar.to_json()[1], Foo) + assert json.dumps( + bar, default=dump_ijsonserializable) == ['foo', 'foo'] + + :returns: JSON object ready to be serialized. """ diff --git a/letsencrypt/acme/messages.py b/letsencrypt/acme/messages.py index 4f0b43759..777a5b7d8 100644 --- a/letsencrypt/acme/messages.py +++ b/letsencrypt/acme/messages.py @@ -16,6 +16,13 @@ class Message(util.TypedACMEObject): TYPES = {} schema = NotImplemented + """JSON schema the object is tested against in :meth:`from_json`. + + Subclasses must overrride it with a value that is acceptable by + :func:`jsonschema.validate`, most probably using + :func:`letsencrypt.acme.util.load_schema`. + + """ @classmethod def get_msg_cls(cls, jobj): @@ -49,8 +56,11 @@ class Message(util.TypedACMEObject): :param jobj: JSON object. - :raises letsencrypt.acme.errors.SchemaValidationError: if ``validate`` - was ``True`` and object couldn't be validated. + :raises letsencrypt.acme.errors.SchemaValidationError: if the input + JSON object could not be validated against JSON schema specified + in :attr:`schema`. + :raises letsencrypt.acme.errors.ValidationError: for any other generic + error in decoding. :returns: instance of the class diff --git a/letsencrypt/acme/util.py b/letsencrypt/acme/util.py index 0969ceb64..819ed2e86 100644 --- a/letsencrypt/acme/util.py +++ b/letsencrypt/acme/util.py @@ -40,7 +40,10 @@ def dump_ijsonserializable(python_object): """Serialize IJSONSerializable to JSON. This is meant to be passed to :func:`json.dumps` as ``default`` - argument. + argument in order to facilitate recursive calls to + :meth:`~letsencrypt.acme.interfaces.IJSONSerializable.to_json`. + Please see :meth:`letsencrypt.acme.interfaces.IJSONSerializable.to_json` + for an example. """ # providedBy | pylint: disable=no-member @@ -164,8 +167,6 @@ class TypedACMEObject(ACMEObject): """Get JSON serializable object. :returns: Serializable JSON object representing ACME typed object. - :meth:`validate` will almost certianly not work, due to reasons - explained in :class:`letsencrypt.acme.interfaces.IJSONSerializable`. :rtype: dict """ diff --git a/letsencrypt/client/auth_handler.py b/letsencrypt/client/auth_handler.py index e63a7baf2..70cd89889 100644 --- a/letsencrypt/client/auth_handler.py +++ b/letsencrypt/client/auth_handler.py @@ -191,7 +191,7 @@ class AuthHandler(object): # pylint: disable=too-many-instance-attributes :param list flat_list: flat_list of responses from an IAuthenticator :param dict ichall_dict: Master dict mapping all domains to a list of - their associated 'client' and 'dv' Indexed challengesenges, or their + their associated 'client' and 'dv' Indexed challenges, or their :class:`letsencrypt.client.achallenges.Indexed` list """ diff --git a/letsencrypt/client/tests/auth_handler_test.py b/letsencrypt/client/tests/auth_handler_test.py index f35c61a55..91874dc0c 100644 --- a/letsencrypt/client/tests/auth_handler_test.py +++ b/letsencrypt/client/tests/auth_handler_test.py @@ -584,11 +584,12 @@ def gen_auth_resp(chall_list): def gen_path(required, challs): - """Generate a path for challenge messages + """Generate a combination by picking ``required`` from ``challs``. - :param required: - :param list str_list: challenge message types (:class:`str`) - :param challs: ACME challenge messages + :param required: Required types of challenges (subclasses of + :class:`~letsencrypt.acme.challenges.Challenge`). + :param challs: Sequence of ACME challenge messages, corresponding to + :attr:`letsencrypt.acme.messages.Challenge.challenges`. :return: :class:`list` of :class:`int`