mirror of https://github.com/streamlink/streamlink
253 lines
11 KiB
ReStructuredText
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
|