streamlink/docs/api/validate.rst

253 lines
11 KiB
ReStructuredText

Validation schemas
------------------
.. ..
Sphinx's autodoc doesn't properly document imported module members and it just outputs "alias of" for re-exported classes.
This means we'll have to run `automodule` twice if we want to document the original classes:
1. the main public interface (which contains aliases, like `all` for `AllSchema` for example)
2. the original schema classes with their full signatures and docstrings
.
Ignore unneeded classes like `SchemaContainer` which are not useful for the API docs.
.
Ignore `validate` as well, as `functools.singledispatch` functions are not fully supported by autodoc.
Instead, manually document `validate` and its overloading functions for base schema types here at the top,
just below the manually imported `Schema` (the main validation schema interface).
The documentations for any custom schemas like `AllSchema` for example is done on the schemas themselves.
.
Ideally, we'd just run autodoc on the main module and configure the order of items. :(
Please see the :ref:`validation schema guides <api_guide/validate:Validation schemas>`
for an introduction to this API and a list of examples.
.. autoclass:: streamlink.plugin.api.validate.Schema
:members:
:undoc-members:
.. py:function:: validate(schema, value)
:module: streamlink.plugin.api.validate
The core of the :mod:`streamlink.plugin.api.validate` module.
It validates the given input ``value`` and returns a value according to the specific validation rules of the ``schema``.
If the validation fails, a :exc:`ValidationError <_exception.ValidationError>` is raised with a detailed error message.
The ``schema`` can be any kind of object. Depending on the ``schema``, different validation rules apply.
Simple schema objects like ``"abc"`` or ``123`` for example test the equality of ``value`` and ``schema``
and return ``value`` again, while type schema objects like ``str`` test whether ``value`` is an instance of ``schema``.
``schema`` objects which are callable receive ``value`` as a single argument and must return a truthy value, otherwise the
validation fails. These are just a few examples.
The ``validate`` module implements lots of special schemas, like :class:`validate.all <all>` or :class:`validate.any <any>`
for example, which are schema containers that receive a sequence of sub-schemas as arguments and where each sub-schema
then gets validated one after another.
:class:`validate.all <all>` requires each sub-schema to successfully validate. It passes the return value of each
sub-schema to the next one and then returns the return value of the last sub-schema.
:class:`validate.any <any>` on the other hand requires at least one sub-schema to be valid and returns the return value of
the first valid sub-schema. Any validation failures prior will be ignored, but at least one must succeed.
Other special ``schema`` cases for example are instances of sequences like ``list`` or ``tuple``, or mappings like ``dict``.
Here, each sequence item or key-value mapping pair is validated against the input ``value``
and a new sequence/mapping object according to the ``schema`` validation is returned.
:func:`validate()` should usually not be called directly when validating schemas. Instead, the wrapper method
:meth:`Schema.validate() <Schema.validate>` of the main :class:`Schema` class should be called. Other Streamlink APIs
like the methods of the :class:`HTTPSession <streamlink.session.Streamlink.http>` or the various
:mod:`streamlink.utils.parse` functions for example expect this interface when the ``schema`` keyword is set,
which allows for immediate validation of the data using a :class:`Schema` object.
:func:`validate()` is implemented using the stdlib's :func:`functools.singledispatch` decorator, where more specific
schemas overload the default implementation with more validation logic.
----
By default, :func:`validate()` compares ``value`` and ``schema`` for equality. This means that simple schema objects
like booleans, strings, numbers, None, etc. are validated here, as well as anything unknown.
Example:
.. code-block:: python
schema = validate.Schema(123)
assert schema.validate(123) == 123
assert schema.validate(123.0) == 123.0
schema.validate(456) # raises ValidationError
schema.validate(None) # raises ValidationError
:param Any schema: Any kind of object not handled by a more specific validation function
:param Any value: The input value
:raise ValidationError: If ``value`` and ``schema`` are not equal
:return: Unmodified ``value``
.. py:function:: _validate_type(schema, value)
:module: streamlink.plugin.api.validate
:class:`type` validation.
Checks if ``value`` is an instance of ``schema``.
Example:
.. code-block:: python
schema = validate.Schema(int)
assert schema.validate(123) == 123
assert schema.validate(True) is True # `bool` is a subclass of `int`
schema.validate("123") # raises ValidationError
*This function is included for documentation purposes only! (singledispatch overload)*
:param type schema: A :class:`type` object
:param Any value: The input value
:raise ValidationError: If ``value`` is not an instance of ``schema``
:return: Unmodified ``value``
.. py:function:: _validate_callable(schema, value)
:module: streamlink.plugin.api.validate
``Callable`` validation.
Validates a ``schema`` function where ``value`` gets passed as a single argument.
Must return a truthy value.
Example:
.. code-block:: python
schema = validate.Schema(
lambda val: val < 2,
)
assert schema.validate(1) == 1
schema.validate(2) # raises ValidationError
*This function is included for documentation purposes only! (singledispatch overload)*
:param Callable schema: A function with one argument
:param Any value: The input value
:raise ValidationError: If ``schema`` returns a non-truthy value
:return: Unmodified ``value``
.. py:function:: _validate_sequence(schema, value)
:module: streamlink.plugin.api.validate
:class:`list <builtins.list>`, :class:`tuple`, :class:`set` and :class:`frozenset` validation.
Each item of ``value`` gets validated against **any** of the items of ``schema``.
Please note the difference between :class:`list <builtins.list>`
and the :class:`ListSchema <_schemas.ListSchema>` validation.
Example:
.. code-block:: python
schema = validate.Schema([1, 2, 3])
assert schema.validate([]) == []
assert schema.validate([1, 2]) == [1, 2]
assert schema.validate([3, 2, 1]) == [3, 2, 1]
schema.validate({1, 2, 3}) # raises ValidationError
schema.validate([1, 2, 3, 4]) # raises ValidationError
*This function is included for documentation purposes only! (singledispatch overload)*
:param Union[list, tuple, set, frozenset] schema: A sequence of validation schemas
:param Any value: The input value
:raise ValidationError: If ``value`` is not an instance of the ``schema``'s own type
:return: A new sequence of the same type as ``schema`` with each item of ``value`` being validated
.. py:function:: _validate_dict(schema, value)
:module: streamlink.plugin.api.validate
:class:`dict` validation.
Each key-value pair of ``schema`` gets validated against the respective key-value pair of ``value``.
Additional keys in ``value`` are ignored and not included in the validation result.
If a ``schema`` key is an instance of :class:`OptionalSchema <_schemas.OptionalSchema>`, then ``value`` may omit it.
If one of the ``schema``'s keys is a :class:`type`,
:class:`AllSchema <_schemas.AllSchema>`, :class:`AnySchema <_schemas.AnySchema>`,
:class:`TransformSchema <_schemas.TransformSchema>`, or :class:`UnionSchema <_schemas.UnionSchema>`,
then all key-value pairs of ``value`` are validated against the ``schema``'s key-value pair.
Example:
.. code-block:: python
schema = validate.Schema({
"key": str,
validate.optional("opt"): 123,
})
assert schema.validate({"key": "val", "other": 123}) == {"key": "val"}
assert schema.validate({"key": "val", "opt": 123}) == {"key": "val", "opt": 123}
schema.validate(None) # raises ValidationError
schema.validate({}) # raises ValidationError
schema.validate({"key": 123}) # raises ValidationError
schema.validate({"key": "val", "opt": 456}) # raises ValidationError
.. code-block:: python
schema = validate.Schema({
validate.any("a", "b"): int,
})
assert schema.validate({}) == {}
assert schema.validate({"a": 1}) == {"a": 1}
assert schema.validate({"b": 2}) == {"b": 2}
assert schema.validate({"a": 1, "b": 2}) == {"a": 1, "b": 2}
schema.validate({"a": 1, "b": 2, "other": 0}) # raises ValidationError
schema.validate({"a": None}) # raises ValidationError
*This function is included for documentation purposes only! (singledispatch overload)*
:param dict schema: A :class:`dict`
:param Any value: The input value
:raise ValidationError: If ``value`` is not a :class:`dict`
:raise ValidationError: If any of the ``schema``'s non-optional keys are not part of the input ``value``
:return: A new :class:`dict`
.. py:function:: _validate_pattern(schema, value)
:module: streamlink.plugin.api.validate
:class:`re.Pattern` validation.
Calls the :meth:`re.Pattern.search()` method on the ``schema`` pattern.
Please note the difference between :class:`re.Pattern` and the :class:`RegexSchema <_schemas.RegexSchema>` validation.
Example:
.. code-block:: python
schema = validate.Schema(
re.compile(r"^Hello, (?P<name>\w+)!$"),
)
assert schema.validate("Does not match") is None
assert schema.validate("Hello, world!")["name"] == "world"
schema.validate(123) # raises ValidationError
schema.validate(b"Hello, world!") # raises ValidationError
*This function is included for documentation purposes only! (singledispatch overload)*
:param re.Pattern schema: A compiled :class:`re.Pattern` object (:func:`re.compile()` return value)
:param Any value: The input value
:raise ValidationError: If ``value`` is not an instance of :class:`str` or :class:`bytes`
:raise ValidationError: If the type of ``value`` doesn't match ``schema``'s :class:`str`/:class:`bytes` type
:return: ``None`` if ``value`` doesn't match ``schema``, or the resulting :class:`re.Match` object
.. automodule:: streamlink.plugin.api.validate
:imported-members:
:exclude-members: Schema, SchemaContainer, validate
:member-order: bysource
.. automodule:: streamlink.plugin.api.validate._schemas
:exclude-members: SchemaContainer
:member-order: bysource
:no-show-inheritance:
.. autoexception:: streamlink.plugin.api.validate._exception.ValidationError