Adapters and Wrappers¶
The Iota
class defines the API methods that are available for
interacting with the node, but it delegates the actual interaction to
another set of classes: Adapters and Wrappers.
The API instance’s methods contain the logic and handle PyOTA-specific types, construct and translate objects, while the API instance’s adapter deals with the networking, communicating with a node.
You can choose and configure the available adapters to be used with the API:
HttpAdapter,
MockAdapter.
AdapterSpec¶
In a few places in the PyOTA codebase, you may see references to a
meta-type called AdapterSpec
.
-
iota.adapter.
AdapterSpec
= typing.Union[str, ForwardRef('BaseAdapter')]¶ Placeholder that means “URI or adapter instance”.
Will be resolved to a correctly-configured adapter instance upon API instance creation.
For example, when creating an Iota
object, the first argument
of Iota.__init__()
is an AdapterSpec
. This means that you can
initialize an Iota
object using either a node URI, or an adapter
instance:
Node URI:
api = Iota('http://localhost:14265')
Adapter instance:
api = Iota(HttpAdapter('http://localhost:14265'))
Adapters¶
Adapters are responsible for sending requests to the node and returning the response.
PyOTA ships with a few adapters:
HttpAdapter¶
from iota import Iota, HttpAdapter
# Use HTTP:
api = Iota('http://localhost:14265')
api = Iota(HttpAdapter('http://localhost:14265'))
# Use HTTPS:
api = Iota('https://nodes.thetangle.org:443')
api = Iota(HttpAdapter('https://nodes.thetangle.org:443'))
# Use HTTPS with basic authentication and 60 seconds timeout:
api = Iota(
HttpAdapter(
'https://nodes.thetangle.org:443',
authentication=('myusername', 'mypassword'),
timeout=60))
-
class
iota.
HttpAdapter
(uri: Union[str, urllib.parse.SplitResult], timeout: Optional[int] = None, authentication: Optional[Tuple[str, str]] = None)¶ Sends standard HTTP(S) requests to the node.
- Parameters
uri (AdapterSpec) –
URI or adapter instance.
If
uri
is astr
, it is parsed to extractscheme
,hostname
andport
.timeout (Optional[int]) – Connection timeout in seconds.
authentication (Optional[Tuple(str,str)]) – Credetentials for basic authentication with the node.
- Returns
HttpAdapter
object.- Raises
InvalidUri –
if protocol is unsupported.
if hostname is empty.
if non-numeric port is supplied.
To configure an Iota
instance to use HttpAdapter
,
specify an http://
or https://
URI, or provide an
HttpAdapter
instance.
The HttpAdapter
raises a BadApiResponse
exception if the server
sends back an error response (due to invalid request parameters, for
example).
Debugging HTTP Requests¶
To see all HTTP requests and responses as they happen, attach a
logging.Logger
instance to the adapter via its set_logger
method.
Any time the HttpAdapter
sends a request or receives a response, it
will first generate a log message. Note: if the response is an error
response (e.g., due to invalid request parameters), the HttpAdapter
will log the request before raising BadApiResponse
.
Note
HttpAdapter
generates log messages with DEBUG
level, so make
sure that your logger’s level
attribute is set low enough that it
doesn’t filter these messages!
Logging to console with default format
from logging import getLogger, basicConfig, DEBUG
from iota import Iota
api = Iota("https://nodes.thetangle.org:443")
# Sets the logging level for the root logger (and for its handlers)
basicConfig(level=DEBUG)
# Get a new logger derived from the root logger
logger = getLogger(__name__)
# Attach the logger to the adapter
api.adapter.set_logger(logger)
# Execute a command that sends request to the node
api.get_node_info()
# Log messages should be printed to console
Logging to a file with custom format
from logging import getLogger, DEBUG, FileHandler, Formatter
from iota import Iota
# Create a custom logger
logger = getLogger(__name__)
# Set logging level to DEBUG
logger.setLevel(DEBUG)
# Create handler to write to a log file
f_handler = FileHandler(filename='pyota.log',mode='a')
f_handler.setLevel(DEBUG)
# Create formatter and add it to handler
f_format = Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
f_handler.setFormatter(f_format)
# Add handler to the logger
logger.addHandler(f_handler)
# Create API instance
api = Iota("https://nodes.thetangle.org:443")
# Add logger to the adapter of the API instance
api.adapter.set_logger(logger)
# Sends a request to the node
api.get_node_info()
# Open 'pyota.log' file and observe the logs
Logging to console with custom format
from logging import getLogger, DEBUG, StreamHandler, Formatter
from iota import Iota
# Create a custom logger
logger = getLogger(__name__)
# Set logging level to DEBUG
logger.setLevel(DEBUG)
# Create handler to write to sys.stderr
s_handler = StreamHandler()
s_handler.setLevel(DEBUG)
# Create formatter and add it to handler
s_format = Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
s_handler.setFormatter(s_format)
# Add handler to the logger
logger.addHandler(s_handler)
# Create API instance
api = Iota("https://nodes.thetangle.org:443")
# Add logger to the adapter of the API instance
api.adapter.set_logger(logger)
# Sends a request to the node
api.get_node_info()
# Observe log messages in console
MockAdapter¶
from iota import Iota, MockAdapter
# Inject a mock adapter.
api = Iota('mock://')
api = Iota(MockAdapter())
# Seed responses from the node.
api.adapter.seed_response('getNodeInfo', {'message': 'Hello, world!'})
api.adapter.seed_response('getNodeInfo', {'message': 'Hello, IOTA!'})
# Invoke API commands, using the adapter.
print(api.get_node_info()) # {'message': 'Hello, world!'}
print(api.get_node_info()) # {'message': 'Hello, IOTA!'}
print(api.get_node_info()) # raises BadApiResponse exception
-
class
iota.
MockAdapter
¶ A mock adapter used for simulating API responses without actually sending any requests to the node.
This is particularly useful in unit and functional tests where you want to verify that your code works correctly in specific scenarios, without having to engineer your own subtangle.
To use this adapter, you must first “seed” the responses that the adapter should return for each request. The adapter will then return the appropriate seeded response each time it “sends” a request.
- Parameters
None – To construct a
MockAdapter
, you don’t need to supply any arguments.- Returns
MockAdapter
object.
To configure an
Iota
instance to useMockAdapter
, specifymock://
as the node URI, or provide aMockAdapter
instance.Example usage:
from iota import Iota, MockAdapter # Create API with a mock adapter. api = Iota('mock://') api = Iota(MockAdapter())
To use MockAdapter
, you must first seed the responses that you want
it to return by calling its MockAdapter.seed_response()
method.
seed_response¶
-
MockAdapter.
seed_response
(command: str, response: dict) → iota.adapter.MockAdapter¶ Sets the response that the adapter will return for the specified command.
You can seed multiple responses per command; the adapter will put them into a FIFO queue. When a request comes in, the adapter will pop the corresponding response off of the queue.
Note that you have to call
seed_response()
once for each request you expect it to process. IfMockAdapter
does not have a seeded response for a particular command, it will raise aBadApiResponse
exception (simulates a 404 response).- Parameters
command (str) – The name of the command. Note that this is the camelCase version of the command name (e.g.,
getNodeInfo
, notget_node_info
).response (dict) – The response that the adapter will return.
Example usage:
adapter.seed_response('sayHello', {'message': 'Hi!'}) adapter.seed_response('sayHello', {'message': 'Hello!'}) adapter.send_request({'command': 'sayHello'}) # {'message': 'Hi!'} adapter.send_request({'command': 'sayHello'}) # {'message': 'Hello!'}
Wrappers¶
Wrappers act like decorators for adapters; they are used to enhance or otherwise modify the behavior of adapters.
RoutingWrapper¶
-
class
iota.adapter.wrappers.
RoutingWrapper
(default_adapter: Union[str, BaseAdapter])¶ Routes commands (API requests) to different nodes depending on the command name.
This allows you to, for example, send POW requests to a local node, while routing all other requests to a remote one.
Once you’ve initialized the
RoutingWrapper
, invoke itsadd_route()
method to specify a different adapter to use for a particular command.- Parameters
default_adapter (AdapterSpec) – RoutingWrapper must be initialized with a default URI/adapter. This is the adapter that will be used for any command that doesn’t have a route associated with it.
- Returns
RoutingWrapper
object.
Example usage:
from iota import Iota from iota.adapter.wrappers import RoutingWrapper # Route POW to localhost, everything else to 'https://nodes.thetangle.org:443'. api = Iota( RoutingWrapper('https://nodes.thetangle.org:443.'') .add_route('attachToTangle', 'http://localhost:14265') .add_route('interruptAttachingToTangle', 'http://localhost:14265') )
Note
A common use case for
RoutingWrapper
is to perform proof-of-work on a specific (local) node, but let all other requests go to another node. Take care when you useRoutingWrapper
adapter andlocal_pow
parameter together in an API instance (seeiota.Iota
), because the behavior might not be obvious.local_pow
tells the API to perform proof-of-work (iota.Iota.attach_to_tangle()
) without relying on an actual node. It does this by calling an extension package PyOTA-PoW that does the job. In PyOTA, this means the request doesn’t reach the adapter, it is redirected before. As a consequence,local_pow
has precedence over the route that is defined inRoutingWrapper
.
add_route¶
-
RoutingWrapper.
add_route
(command: str, adapter: Union[str, BaseAdapter]) → iota.adapter.wrappers.RoutingWrapper¶ Adds a route to the wrapper.
- Parameters
command (str) – The name of the command. Note that this is the camelCase version of the command name (e.g.,
attachToTangle
, notattach_to_tangle
).adapter (AdapterSpec) – The adapter object or URI to route requests to.
- Returns
The
RoutingWrapper
object it was called on. Useful for chaining the operation of adding routes in code.
See
RoutingWrapper
for example usage.