1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
|
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.
.. admonition:: Public interface
:class: caution
While the internals are implemented in the ``streamlink.validate`` package,
:ref:`streamlink.plugin.api.validate <api/validate:Validation schemas>` provides the main public interface
for plugin implementors.
.. 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.validate._schemas
:exclude-members: SchemaContainer
:member-order: bysource
:no-show-inheritance:
.. autoexception:: streamlink.validate._exception.ValidationError
|