.. _input-annotations: Input parameter annotations =========================== .. py:module:: horetu.annotations .. _input-annotations-concept: Concept ------- If you call a horetu interface on a function with input annotations, the input annotations are be treated as types. You can usually think of the annotation as a callable that takes one argument. (It is in fact more complicated, as discussed in :ref:`input-annotation-kind`.) horetu calls the annotation on the raw input value, usually a :py:class:`str`, and passes the returned value to as an input to the function whose parameter was annotated. Consider this function with an input annotation. :: def logarithm(number: float): return math.log(number, 10) When run with horetu, it works very similarly to this next function. :: def logarithm(number): return math.log(float(number), 10) The difference is only in the exception handling. If the number is converted to a float without error, the resulting programs will work the same. If calling :py:class:`float` on the number raises an error, however, the first version will print the ordinary Python traceback, and the second version will produce an error that is appropriate for the specified interface. .. _input-annotation-kind: Kinds of annotations -------------------- Let us group annotations into three kinds, which are all converted internally into :py:class:`horetu.annotations.Annotation` subclasses. 1. Special objects and the empty annotation 2. :py:class:`horetu.annotations.Annotation` subclasses 3. Other callables I discuss these in reverse order, because I think that is easiest. Other callables ^^^^^^^^^^^^^^^ I have pretty much explained how other callables work already (:ref:`input-annotations-concept`): * They parse raw input values. * The parsed values are used as function inputs. * Errors are printed nicely. Annotations ^^^^^^^^^^^ :py:class:`horetu.annotations.Annotation` subclasses are conceptually similar, with only extra capabilities. * They can dump function inputs back to raw input values, which is necessary for some interfaces. For example, :py:func:`horetu.config_default` uses this to generate default configuration files. * They can load and dump from non-:py:class:`str` sources. For example, :py:func:`horetu.wsgi_form` uses this to create web form widgets, parse load uploaded files, and populate default values for the web form. Here are some :py:class:`horetu.annotations.Annotation` subclasses. .. autoclass:: horetu.annotations.Encoder .. autoclass:: horetu.annotations.Flag .. autoclass:: horetu.annotations.Boolean .. autoclass:: horetu.annotations.Help .. autoclass:: horetu.annotations.Text .. autoclass:: horetu.annotations.Identity .. autoclass:: horetu.annotations.Config .. autoclass:: horetu.annotations.String .. autoclass:: horetu.annotations.Regex .. autoclass:: horetu.annotations.Bytes .. autoclass:: horetu.annotations.InputDirectory .. autoclass:: horetu.annotations.OutputDirectory .. autoclass:: horetu.annotations.Port .. autoclass:: horetu.annotations.Decimal .. autoclass:: horetu.annotations.Integer .. autoclass:: horetu.annotations.Float .. autoclass:: horetu.annotations.Range .. autoclass:: horetu.annotations.FactorMapping .. autoclass:: horetu.annotations.Factor .. autoclass:: horetu.annotations.File .. autoclass:: horetu.annotations.InputFile .. autoclass:: horetu.annotations.InputBinaryFile .. autoclass:: horetu.annotations.OutputFile .. autoclass:: horetu.annotations.OutputBinaryFile Special objects and the empty annotation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Special objects are all shorthand for :py:class:`horetu.annotations.Annotation` subclasses. Some are direct replacements. * :py:class:`bool` is converted to :py:class:`horetu.annotations.Boolean`. * :py:class:`bytes` is converted to :py:class:`horetu.annotations.Bytes`. * :py:class:`decimal.Decimal` is converted to :py:class:`horetu.annotations.Decimal`. * :py:class:`float` is converted to :py:class:`horetu.annotations.Float`. * :py:class:`int` is converted to :py:class:`horetu.annotations.Integer`. * :py:class:`str` is converted to :py:class:`horetu.annotations.Identity`. And if no annotation is set, :py:class:`horetu.annotations.Identity` is used. Passing a :py:class:`tuple` of :py:class:`str` elements as an annotation (an instance of :py:class:`tuple`, not the :py:class:`tuple` class itself) indicates that only certain values are allowed as input. It is internally converted to a :py:class:`horetu.annotations.Factor` object. :: def validate(x: ('elephant', 'giraffe')): return x A :py:class:`dict` with :py:class:`str` keys is converted to a :py:class:`horetu.annotations.FactorMapping` object, which works similarly to a :py:class:`horetu.annotations.Factor` object: Only the keys in the dictionary are allowed as inputs, and the corresponding dictionary value (rather than the original input) is used as the function input. :: def validate(x: {'elephant': 1, 'giraffe': 2}): return x*10 The annotation can be a list of one element, with the one element being a valid annotation itself. This is treated like if the element were the annotation, except that many values for the parameter may be specified and that these values will be combined into one list and passed as a single input to the function. For example, if the :py:func:`horetu.wsgi_form` interface were called on this function, the resulting form would allow for the selection of values and would enforce that each be either "elephant" or "giraffe". :: def validate(xs: [{'elephant': 1, 'giraffe': 2}]): for x in xs: yield x*10 Annotations with ``dumps`` and ``loads`` or ``dump`` and ``load`` methods are used as :py:class:`horetu.annotations.Encoder` type. :: import json def read_thingy(thingy: json): print(thingy['documents']) I still need to document :py:class:`horetu.annotations.Help` and :py:class:`horetu.annotations.Config`. Other argument characteristics sometimes matter ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Other characteristics of arguments sometimes matter in determining the annotation. They usually only imposes constraints. For example, :py:class:`horetu.annotations.Flag` annotations can only be set on keyword arguments, and the default value must be ``False``. The main place where it affects the annotation is when a parameter has a default of ``False`` and no annotation; in this case, the annotation is set to :py:class:`horetu.annotations.Boolean`.