Input parameter annotations

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 Kinds of annotations.) horetu calls the annotation on the raw input value, usually a 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 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.

Kinds of annotations

Let us group annotations into three kinds, which are all converted internally into horetu.annotations.Annotation subclasses.

  1. Special objects and the empty annotation
  2. 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 (Concept):

  • They parse raw input values.
  • The parsed values are used as function inputs.
  • Errors are printed nicely.

Annotations

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, horetu.config_default() uses this to generate default configuration files.
  • They can load and dump from non-str sources. For example, 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 horetu.annotations.Annotation subclasses.

class horetu.annotations.Encoder(x)[source]
class horetu.annotations.Flag(loads)[source]
horetu.annotations.Boolean[source]
class horetu.annotations.Help[source]
class horetu.annotations.Text(*args, **kwargs)[source]
class horetu.annotations.Identity(type=<class 'str'>)[source]
class horetu.annotations.Config(filename)[source]
class horetu.annotations.String(loads=<function String.<lambda>>)[source]
class horetu.annotations.Regex(expr)[source]
class horetu.annotations.Bytes(loads=<function Bytes.<lambda>>, dumps=<function Bytes.<lambda>>, encoding='ascii')[source]
horetu.annotations.InputDirectory[source]
horetu.annotations.OutputDirectory[source]
horetu.annotations.Port[source]
horetu.annotations.Decimal
horetu.annotations.Integer
horetu.annotations.Float
class horetu.annotations.Range(minimum, maximum, loads=<class 'float'>, dumps=<class 'str'>, step='any')[source]
class horetu.annotations.FactorMapping(options)[source]
class horetu.annotations.Factor(options)[source]
class horetu.annotations.File(mode, encoding=None, **kwargs)[source]
horetu.annotations.InputFile
horetu.annotations.InputBinaryFile
horetu.annotations.OutputFile
horetu.annotations.OutputBinaryFile

Special objects and the empty annotation

Special objects are all shorthand for horetu.annotations.Annotation subclasses. Some are direct replacements.

And if no annotation is set, horetu.annotations.Identity is used.

Passing a tuple of str elements as an annotation (an instance of tuple, not the tuple class itself) indicates that only certain values are allowed as input. It is internally converted to a horetu.annotations.Factor object.

def validate(x: ('elephant', 'giraffe')):
    return x

A dict with str keys is converted to a horetu.annotations.FactorMapping object, which works similarly to a 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 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 horetu.annotations.Encoder type.

import json
def read_thingy(thingy: json):
    print(thingy['documents'])

I still need to document horetu.annotations.Help and 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, 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 horetu.annotations.Boolean.