TinyChain's Python client uses type declarations in an unusual way. Here's what to expect.
import osimport tinychain as tc# use the demo host at demo.tinychain.net, unless overridden by the environment variable `TC_HOST`HOST = tc.host.Host(os.getenv("TC_HOST", "http://demo.tinychain.net"))# this endpoint will attempt to resolve whatever state you send it, without committing any changesENDPOINT ="/transact/hypothetical"# this assumes that `x` is of type `tc.tensor.Tensor`defaverage(x):return x.sum()/ x.sizeif__name__=="__main__": cxt = tc.Context()# initialize a new Op context cxt.x = tc.tensor.Dense.ones([3])# initialize a new Dense tensor cxt.result =average(cxt.x)# call our custom function actual = HOST.post(ENDPOINT, cxt)# execute the `Op` defined by `cxt`assert actual ==1# verify the result
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:
# ...# the `post_op` annotation tells TinyChain to reflect over this function# and assume that it defines a POST Op @tc.post_op# the annotation on `x` tells TinyChain to expect a `Tensor`defaverage(x: tc.tensor.Tensor) -> tc.Number:# the return annotation tells the calling code to expect a `Number`return x.sum()/ x.sizeif__name__=="__main__": cxt = tc.Context()# initialize a new Op context# `average` is now a TinyChain `Op`, not a native Python function,# so it needs to be made addressable by the calling context cxt.average = average cxt.x = tc.tensor.Dense.ones([3])# initialize a new Dense tensor cxt.result = cxt.average(x=tc.URI("$x"))# call our custom `Op` actual = HOST.post(ENDPOINT, cxt)# execute the `Op` defined by `cxt`assert actual ==1# verify the result
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:
# ...@tc.post_opdefaverage(x: tc.tensor.Tensor) -> tc.String: # <-- return type `String`return x.sum()/ x.sizeif__name__=="__main__":# ... actual = HOST.post(ENDPOINT, cxt)assert actual =="1"# this assertion fails!
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:
# ...@tc.post_opdefaverage(x: tc.tensor.Tensor) -> tc.String: avg = x.sum()/ x.sizereturn avg.cast(tc.String)# explicitly cast `avg` to a `String`if__name__=="__main__":# ... actual = HOST.post(ENDPOINT, cxt)assert actual =="1"# assert that the result is in fact a string