Types and casting
TinyChain's Python client uses type declarations in an unusual way. Here's what to expect.
1
import os
2
import tinychain as tc
3
4
# use the demo host at demo.tinychain.net, unless overridden by the environment variable `TC_HOST`
5
HOST = tc.host.Host(os.getenv("TC_HOST", "http://demo.tinychain.net"))
6
# this endpoint will attempt to resolve whatever state you send it, without committing any changes
7
ENDPOINT = "/transact/hypothetical"
8
9
# this assumes that `x` is of type `tc.tensor.Tensor`
10
def average(x):
11
return x.sum() / x.size
12
13
if __name__ == "__main__":
14
cxt = tc.Context() # initialize a new Op context
15
cxt.x = tc.tensor.Dense.ones([3]) # initialize a new Dense tensor
16
cxt.result = average(cxt.x) # call our custom function
17
18
actual = HOST.post(ENDPOINT, cxt) # execute the `Op` defined by `cxt`
19
assert actual == 1 # verify the result
Copied!
The average function in this example works well enough, but in a public library it might not handle every situation it should. For example, if a user calls average(tc.URI("$x")), the sum method and size property won't be available because a URI doesn't have a sum method or size property. To handle cases like this, we can use TinyChain's built-in reflection annotations:
1
# ...
2
3
# the `post_op` annotation tells TinyChain to reflect over this function
4
# and assume that it defines a POST Op
5
@tc.post_op
6
# the annotation on `x` tells TinyChain to expect a `Tensor`
7
def average(x: tc.tensor.Tensor) -> tc.Number:
8
# the return annotation tells the calling code to expect a `Number`
9
return x.sum() / x.size
10
11
if __name__ == "__main__":
12
cxt = tc.Context() # initialize a new Op context
13
# `average` is now a TinyChain `Op`, not a native Python function,
14
# so it needs to be made addressable by the calling context
15
cxt.average = average
16
cxt.x = tc.tensor.Dense.ones([3]) # initialize a new Dense tensor
17
cxt.result = cxt.average(x=tc.URI("$x")) # call our custom `Op`
18
actual = HOST.post(ENDPOINT, cxt) # execute the `Op` defined by `cxt`
19
assert actual == 1 # verify the result
20
Copied!
Now, even though the type of x in the calling code is a URI, the average Op still works as the caller expects because the type annotations tell the code in def average what to expect.
Important! In the TinyChain Python client, types define what to expect. They don't necessarily instantiate or cast types, like they do in native Python code.
To illustrate this, let's change the return type of average to a String:
1
# ...
2
3
@tc.post_op
4
def average(x: tc.tensor.Tensor) -> tc.String: # <-- return type `String`
5
return x.sum() / x.size
6
7
if __name__ == "__main__":
8
# ...
9
actual = HOST.post(ENDPOINT, cxt)
10
assert actual == "1" # this assertion fails!
Copied!
Now, the return annotation tc.String tells the calling code to expect a String but the Op still actually returns a Number, so the assert fails. To fix this, we'll have to explicitly cast the return value:
1
# ...
2
3
@tc.post_op
4
def average(x: tc.tensor.Tensor) -> tc.String:
5
avg = x.sum() / x.size
6
return avg.cast(tc.String) # explicitly cast `avg` to a `String`
7
8
if __name__ == "__main__":
9
# ...
10
actual = HOST.post(ENDPOINT, cxt)
11
assert actual == "1" # assert that the result is in fact a string
12
Copied!
Copy link