Advanced: PyOTA Commands¶
Note
This page contains information about how PyOTA works under the hood.
It is absolutely not necessary to be familiar with the content described below if you just want to use the library.
However, if you are a curious mind or happen to do development on the library, the following information might be useful.
PyOTA provides the API interface (Core API Methods and Extended API Methods) for users of the library. These handle constructing and sending HTTP requests to the specified node through adapters, furthermore creating, transforming and translating between PyOTA-specific types and (JSON-encoded) raw data. They also filter outgoing requests and incoming responses to ensure that only appropriate data is communicated with the node.
PyOTA implements the Command Design Pattern. High level API interface methods (Core API Methods and Extended API Methods) internally call PyOTA commands to get the job done.
Most PyOTA commands are sub-classed from FilterCommand
class, which
is in turn sub-classed from BaseCommand
class. The reason for the
2-level inheritance is simple: separating functionality. As the name implies,
FilterCommand
adds filtering capabilities to
BaseCommand
, that contains the logic of constructing the request
and using its adapter to send it and receive a response.
Command Flow¶
As mentioned earlier, API methods rely on PyOTA commands to carry out specific operations. It is important to understand what happens during command execution so you are able to implement new methods that extend the current capabilities of PyOTA.
Let’s investigate the process through an example of a core API method, for
instance find_transactions()
, that calls
FindTransactionCommand
PyOTA command internally.
Note
FindTransactionCommand
is sub-classed from FilterCommand
.
To illustrate what the happens inside the API method, take a look at the following figure
When you call
find_transactions()
core API method, it initializes aFindTransactionCommand
object with the adapter of the API instance it belongs to.Then calls this command with the keyword arguments it was provided with.
The command prepares the request by applying a
RequestFilter
on the payload. The command specificRequestFilter
validates that the payload has correct types, in some cases it is even able to convert the payload to the required type and format.Command execution injects the name of the API command (see IRI API Reference for command names) in the request and sends it to the adapter.
The adapter communicates with the node and returns its response.
The response is prepared by going through a command-specific
ResponseFilter
.The response is returned to the high level API method as a
dict
, ready to be returned to the main application.
Note
A command object can only be called once without resetting it. When you use the high level API methods, you don’t need to worry about resetting commands as each call to an API method will initialize a new command object.
Filters¶
If you take a look at the actual implementation of
FindTransactionsCommand
, you notice that you have to define your
own request and response filter classes.
Filters in PyOTA are based on the Filters library. Read more about how they work at the filters documentation site.
In short, you can create filter chains through which the filtered value passes,
and generates errors if something failed validation. Filter chains are specified
in the custom filter class’s __init__()
function. If you also want to
modify the filtered value before returning it, override the _apply()
method of its base class. Read more about how to create custom filters.
PyOTA offers you some custom filters for PyOTA-specific types:
Trytes¶
-
class
iota.filters.
Trytes
(result_type: type = <class 'iota.types.TryteString'>)¶ Validates a sequence as a sequence of trytes.
When a value doesn’t pass the filter, a
ValueError
is raised with lots of contextual info attached to it.- Parameters
result_type (TryteString) – Any subclass of
TryteString
that you want the filter to validate.- Raises
TypeError – if value is not of
result_type
.ValueError – if
result_type
is not ofTryteString
type.
- Returns
Trytes
object.
StringifiedTrytesArray¶
-
filters.
StringifiedTrytesArray
(**runtime_kwargs) → filters.base.FilterChain¶ Validates that the incoming value is an array containing tryte strings corresponding to the specified type (e.g.,
TransactionHash
).When a value doesn’t pass the filter, a
ValueError
is raised with lots of contextual info attached to it.- Parameters
trytes_type (TryteString) – Any subclass of
TryteString
that you want the filter to validate.- Returns
filters.FilterChain
object.
Important
This filter will return string values, suitable for inclusion in an API request. If you are expecting objects (e.g.,
Address
), then this is not the filter to use!Note
This filter will allow empty arrays and None. If this is not desirable, chain this filter with
f.NotEmpty
orf.Required
, respectively.
AddressNoChecksum¶
-
class
iota.filters.
AddressNoChecksum
¶ Validates a sequence as an
Address
, then chops off the checksum if present.When a value doesn’t pass the filter, a
ValueError
is raised with lots of contextual info attached to it.- Returns
AddressNoChecksum
object.
GeneratedAddress¶
-
class
iota.filters.
GeneratedAddress
¶ Validates an incoming value as a generated
Address
(must havekey_index
andsecurity_level
set).When a value doesn’t pass the filter, a
ValueError
is raised with lots of contextual info attached to it.- Returns
GeneratedAddress
object.
NodeUri¶
SecurityLevel¶
-
filters.
SecurityLevel
(**runtime_kwargs) → filters.base.FilterChain¶ Generates a filter chain for validating a security level.
- Returns
filters.FilterChain
object.
Important
The general rule in PyOTA is that all requests going to a node are validated, but only responses that contain transaction/bundle trytes or hashes are checked.
Also note, that for extended commands, ResponseFilter
is usually
implemented with just a “pass” statement. The reason being that these
commands do not directly receive their result a node, but rather from
core commands that do have their ResponseFilter
implemented.
More about this topic in the next section.
Extended Commands¶
Core commands, like find_transactions()
in the example above,
are for direct communication with the node for simple tasks such
as finding a transaction on the Tangle or getting info about the node.
Extended commands (that serve Extended API Methods) on the other hand
carry out more complex operations such as combining core commands, building
objects, etc…
As a consequence, extended commands override the default execution phase of their base class.
Observe for example FindTransactionObjectsCommand
extended command
that is called in find_transaction_objects()
extended API
method. It overrides the _execute()
method of its base class.
Let’s take a closer look at the implementation:
...
def _execute(self, request):
bundles = request\
.get('bundles') # type: Optional[Iterable[BundleHash]]
addresses = request\
.get('addresses') # type: Optional[Iterable[Address]]
tags = request\
.get('tags') # type: Optional[Iterable[Tag]]
approvees = request\
.get('approvees') # type: Optional[Iterable[TransactionHash]]
ft_response = FindTransactionsCommand(adapter=self.adapter)(
bundles=bundles,
addresses=addresses,
tags=tags,
approvees=approvees,
)
hashes = ft_response['hashes']
transactions = []
if hashes:
gt_response = GetTrytesCommand(adapter=self.adapter)(hashes=hashes)
transactions = list(map(
Transaction.from_tryte_string,
gt_response.get('trytes') or [],
)) # type: List[Transaction]
return {
'transactions': transactions,
}
...
Instead of sending the request to the adapter,
FindTransactionObjectsCommand._execute()
calls
FindTransactionsCommand
core command, gathers the transaction hashes
that it found, and collects the trytes of those transactions by calling
GetTrytesCommand
core command. Finally, using the obtained trytes,
it constructs a list of transaction objects that are returned to
find_transaction_objects()
.
Important
If you come up with a new functionality for the PyOTA API, please raise an issue in the PyOTA Bug Tracker to facilitate discussion.
Once the community agrees on your proposal, you may start implementing a new extended API method and the corresponding extended PyOTA command.
Contributions are always welcome! :)
Visit the Contributing to PyOTA page to find out how you can make a difference!