File: TYPE_ANNOTATIONS.md

package info (click to toggle)
python-hug 2.6.0-2.4
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 4,072 kB
  • sloc: python: 8,938; sh: 99; makefile: 17
file content (100 lines) | stat: -rw-r--r-- 5,649 bytes parent folder | download
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
Type annotations in hug
=======================

hug leverages Python3 type annotations for validation and API specification. Within the context of hug, annotations should be set to one of 4 things:

 - A cast function, built-in, or your own (str, int, etc) that takes a value casts it and then returns it, raising an exception if it is not in a format that can be cast into the desired type
 - A hug type (hug.types.text, hug.types.number, etc.). These are essentially built-in cast functions that provide more contextual information, and good default error messages
 - A [marshmallow](https://marshmallow.readthedocs.org/en/latest/) type and/or schema. In hug 2.0.0 Marshmallow is a first class citizen in hug, and all fields and schemas defined with it can be used in hug as type annotations
 - A string. When a basic Python string is set as the type annotation it is used by hug to generate documentation, but does not get applied during the validation phase

For example:

    import hug


    @hug.get()
    def hello(first_name: hug.types.text, last_name: 'Family Name', age: int):
        print("Hi {0} {1}!".format(first_name, last_name)

is a valid hug endpoint.

Any time a type annotation raises an exception during casting of a type, it is seen as a failure. Otherwise the cast is assumed successful with the returned type replacing the passed-in parameter. By default, all errors are collected in an errors dictionary and returned as the output of the endpoint before the routed function ever gets called. To change how errors are returned you can transform them via the `on_invalid` route option, and specify a specific output format for errors by specifying the `output_invalid` route option. Or, if you prefer, you can keep hug from handling the validation errors at all by passing in `raise_on_invalid=True` to the route.

Built in hug types
==================

hug provides several built-in types for common API use cases:

 - `number`: Validates that a whole number was passed in
 - `float_number`: Validates that a valid floating point number was passed in
 - `decimal`: Validates and converts the provided value into a Python Decimal object
 - `uuid`: Validates that the provided value is a valid UUID
 - `text`: Validates that the provided value is a single string parameter
 - `multiple`: Ensures the parameter is passed in as a list (even if only one value is passed in)
 - `boolean`: A basic naive HTTP style boolean where no value passed in is seen as `False` and any value passed in (even if its `false`) is seen as `True`
 - `smart_boolean`: A smarter, but more computentionally expensive, boolean that checks the content of the value for common true / false formats (true, True, t, 1) or (false, False, f, 0)
 - `delimited_list(delimiter)`: splits up the passed in value based on the provided delimiter and then passes it to the function as a list
 - `one_of(values)`: Validates that the passed in value is one of those specified
 - `mapping(dict_of_passed_in_to_desired_values)`: Like `one_of`, but with a dictionary of acceptable values, to converted value.
 - `multi(types)`: Allows passing in multiple acceptable types for a parameter, short circuiting on the first acceptable one
 - `in_range(lower, upper, convert=number)`: Accepts a number within a lower and upper bound of acceptable values
 - `less_than(limit, convert=number)`: Accepts a number within a lower and upper bound of acceptable values
 - `greater_than(minimum, convert=number)`: Accepts a value above a given minimum
 - `length(lower, upper, convert=text)`: Accepts a a value that is within a specific length limit
 - `shorter_than(limit, convert=text)`: Accepts a text value shorter than the specified length limit
 - `longer_than(limit, convert=text)`: Accepts a value up to the specified limit
 - `cut_off(limit, convert=text)`: Cuts off the provided value at the specified index

Extending and creating new hug types
====================================

The most obvious way to extend a hug type is to simply inherit from the base type defined in `hug.types` and then override `__call__` to override how the cast function, or override `__init__` to override what parameters the type takes:

    import hug


    class TheAnswer(hug.types.Text):
        """My new documentation"""

        def __call__(self, value):
            value = super().__call__(value)
            if value != 'fourty-two':
                raise ValueError('Value is not the answer to everything.')
            return value

If you simply want to perform additional conversion after a base type is finished, or modify its documentation, the most succinct way is the `hug.type` decorator:

    import hug


    @hug.type(extend=hug.types.number)
    def the_answer(value):
        """My new documentation"""
        if value != 42:
            raise ValueError('Value is not the answer to everything.')
        return value


Marshmallow integration
=======================

[Marshmallow](https://marshmallow.readthedocs.org/en/latest/) is an advanced serialization, deserialization, and validation library. Hug supports using marshmallow fields and schemas as input types.

Here is a simple example of an API that does datetime addition.


    import datetime as dt

    import hug
    from marshmallow import fields
    from marshmallow.validate import Range


    @hug.get('/dateadd', examples="value=1973-04-10&addend=63")
    def dateadd(value: fields.Date(),
                addend: fields.Int(validate=Range(min=1))):
        """Add a value to a date."""
        delta = dt.timedelta(days=addend)
        result = value + delta
        return {'result': result}