API Documentation

TChannel

class tchannel.TChannel(name, hostport=None, process_name=None, known_peers=None, trace=False)[source]

Manages connections and requests to other TChannel services.

Usage for a JSON client/server:

tchannel = TChannel(name='foo')

@tchannel.json.register
def handler(request):
    return {'foo': 'bar'}

response = yield tchannel.json(
    service='some-service',
    endpoint='endpoint',
    headers={'req': 'headers'},
    body={'req': 'body'},
)
Variables:
  • thrift (ThriftArgScheme) – Make Thrift requests over TChannel and register Thrift handlers.
  • json (JsonArgScheme) – Make JSON requests over TChannel and register JSON handlers.
  • raw (RawArgScheme) – Make requests and register handles that pass raw bytes.
__init__(name, hostport=None, process_name=None, known_peers=None, trace=False)[source]

Note: In general only one TChannel instance should be used at a time. Multiple TChannel instances are not advisable and could result in undefined behavior.

Parameters:
  • name (string) – How this application identifies itself. This is the name callers will use to make contact, it is also what your downstream services will see in their metrics.
  • hostport (string) – An optional host/port to serve on, e.g., "127.0.0.1:5555. If not provided an ephemeral port will be used. When advertising on Hyperbahn you callers do not need to know your port.
call(*args, **kwargs)[source]

Make low-level requests to TChannel services.

Note: Usually you would interact with a higher-level arg scheme like tchannel.schemes.JsonArgScheme or tchannel.schemes.ThriftArgScheme.

advertise(*args, **kwargs)[source]

Advertise with Hyperbahn.

After a successful advertisement, Hyperbahn will establish long-lived connections with your application. These connections are used to load balance inbound and outbound requests to other applications on the Hyperbahn network.

Re-advertisement happens periodically after calling this method (every minute). Hyperbahn will eject us from the network if it doesn’t get a re-advertise from us after 5 minutes.

Parameters:
  • routers (list) – A seed list of known Hyperbahn addresses to attempt contact with. Entries should be of the form "host:port".
  • name (string) – The name your application identifies itself as. This is usually unneeded because in the common case it will match the name you initialized the TChannel instance with. This is the identifier other services will use to make contact with you.
  • timeout – The timeout (in seconds) for the initial advertise attempt. Defaults to 30 seconds.
  • router_file – The host file that contains the routers information. The file should contain a JSON stringified format of the routers parameter. Either routers or router_file should be provided. If both provided, a ValueError will be raised.
Returns:

A future that resolves to the remote server’s response after the first advertise finishes.

Raises TimeoutError:
 

When unable to make our first advertise request to Hyperbahn. Subsequent requests may fail but will be ignored.

class tchannel.Request(body=None, headers=None, transport=None, endpoint=None)[source]

A TChannel request.

This is sent by callers and received by registered handlers.

Variables:
  • body – The payload of this request. The type of this attribute depends on the scheme being used (e.g., JSON, Thrift, etc.).
  • headers – A dictionary of application headers. This should be a mapping of strings to strings.
  • transport

    Protocol-level transport headers. These are used for routing over Hyperbahn.

    The most useful piece of information here is probably request.transport.caller_name, which is the identity of the application that created this request.

class tchannel.Response(body=None, headers=None, transport=None, status=None)[source]

A TChannel response.

This is sent by handlers and received by callers.

Variables:
  • body – The payload of this response. The type of this attribute depends on the scheme being used (e.g., JSON, Thrift, etc.).
  • headers – A dictionary of application headers. This should be a mapping of strings to strings.
  • transport – Protocol-level transport headers. These are used for routing over Hyperbahn.
class tchannel.context.RequestContext(parent_tracing=None)[source]

Tracks the Request currently being handled.

The asynchronous nature of Tornado means that multiple requests can be in-flight at any given moment. It’s often useful to be able to see some information about the request that triggered the current method invocation.

There are two ways to do this:

  • Pass the tchannel.Request to every method that may need to use it. This is performant but breaks MVC boundaries.
  • Use RequestContext – in particular get_current_context() – to see this info from any point in your code. This can be “easier” (read: magical).

RequestContext uses Tornado’s StackContext functionality, which hurts throughput. There’s currently no way to disable RequestContext tracking (for cases when you want to pass the tchannel.Request explicity), although it is planned.

Variables:parent_tracing – Tracing information (trace id, span id) for this request.
tchannel.context.get_current_context()[source]
Returns:The current RequestContext for this thread.

Serialization Schemes

Thrift

class tchannel.schemes.ThriftArgScheme(tchannel)[source]

Handler registration and serialization for Thrift.

To register a Thrift handler:

@tchannel.thrift(GeneratedThriftModule)
def method(request):
    print request.body.some_arg

When calling a remote service, generated Thrift types need to be wrapped with thrift_request_builder() to provide TChannel compatibility:

thrift_service = thrift_request_builder(
    service='service-identifier',
    thrift_module=GeneratedThriftModule,
)

response = yield tchannel.thrift(
    thrift_service.method(some_arg='foo'),
)
tchannel.thrift_request_builder(service, thrift_module, hostport=None, thrift_class_name=None)[source]

Provide TChannel compatibility with Thrift-generated modules.

The service this creates is meant to be used with TChannel like so:

from tchannel import TChannel, thrift_request_builder
from some_other_service_thrift import some_other_service

tchannel = TChannel('my-service')

some_service = thrift_request_builder(
    service='some-other-service',
    thrift_module=some_other_service
)

resp = tchannel.thrift(
    some_service.fetchPotatoes()
)
Parameters:
  • service (string) – Name of Thrift service to call. This is used internally for grouping and stats, but also to route requests over Hyperbahn.
  • thrift_module – The top-level module of the Apache Thrift generated code for the service that will be called.
  • hostport (string) – When calling the Thrift service directly, and not over Hyperbahn, this ‘host:port’ value should be provided.
  • thrift_class_name (string) – When the Apache Thrift generated Iface class name does not match thrift_module, then this should be provided.
tchannel.thrift.load(path, service=None, hostport=None, module_name=None)[source]

Loads the Thrift file at the specified path.

Note

This functionality is experimental and subject to change. We expect to mark it as stable in a future version.

The file is compiled in-memory and a Python module containing the result is returned. It may be used with TChannel.thrift. For example,

from tchannel import TChannel, thrift

# Load our server's interface definition.
donuts = thrift.load('donuts.thrift')

# We need to specify a service name or hostport because this is a
# downstream service we'll be calling.
coffee = thrift.load('coffee.thrift', 'coffee')

tchannel = TChannel('donuts')

@tchannel.thrift.register(donuts.DonutsService)
@tornado.gen.coroutine
def submitOrder(request):
    args = request.body

    if args.coffee:
        yield tchannel.thrift(
            coffee.CoffeeService.order(args.coffee)
        )

    # ...

The returned module contains, one top-level type for each struct, enum, union, exeption, and service defined in the Thrift file. For each service, the corresponding class contains a classmethod for each function defined in that service that accepts the arguments for that function and returns a ThriftRequest capable of being sent via TChannel.thrift.

Note that the path accepted by load must be either an absolute path or a path relative to the the current directory. If you need to refer to Thrift files relative to the Python module in which load was called, use the __file__ magic variable.

# Given,
#
#   foo/
#     myservice.thrift
#     bar/
#       x.py
#
# Inside foo/bar/x.py,

path = os.path.join(
    os.path.dirname(__file__), '../myservice.thrift'
)

The returned value is a valid Python module. You can install the module by adding it to the sys.modules dictionary. This will allow importing items from this module directly. You can use the __name__ magic variable to make the generated module a submodule of the current module. For example,

# foo/bar.py

import sys
from tchannel import thrift

donuts = = thrift.load('donuts.thrift')
sys.modules[__name__ + '.donuts'] = donuts

This installs the module generated for donuts.thrift as the module foo.bar.donuts. Callers can then import items from that module directly. For example,

# foo/baz.py

from foo.bar.donuts import DonutsService, Order

def baz(tchannel):
    return tchannel.thrift(
        DonutsService.submitOrder(Order(..))
    )
Parameters:
  • service (str) – Name of the service that the Thrift file represents. This name will be used to route requests through Hyperbahn.
  • path (str) – Path to the Thrift file. If this is a relative path, it must be relative to the current directory.
  • hostport (str) – Clients can use this to specify the hostport at which the service can be found. If omitted, TChannel will route the requests through known peers. This value is ignored by servers.
  • module_name (str) – Name used for the generated Python module. Defaults to the name of the Thrift file.

JSON

class tchannel.schemes.JsonArgScheme(tchannel)[source]

Semantic params and serialization for json.

__call__(*args, **kwargs)[source]

Make JSON TChannel Request.

Parameters:
  • service (string) – Name of the service to call.
  • endpoint (string) – Endpoint to call on service.
  • body (string) – A raw body to provide to the endpoint.
  • headers (string) – A raw headers block to provide to the endpoint.
  • timeout (int) – How long to wait (in ms) before raising a TimeoutError - this defaults to tchannel.glossary.DEFAULT_TIMEOUT.
  • retry_on (string) – What events to retry on - valid values can be found in tchannel.retry.
  • retry_limit (string) – How many times to retry before
  • hostport (string) – A ‘host:port’ value to use when making a request directly to a TChannel service, bypassing Hyperbahn.
Return type:

Response

Raw

class tchannel.schemes.RawArgScheme(tchannel)[source]

Semantic params and serialization for raw.

__call__(service, endpoint, body=None, headers=None, timeout=None, retry_on=None, retry_limit=None, hostport=None, shard_key=None, trace=None)[source]

Make a raw TChannel request.

The request’s headers and body are treated as raw bytes and not serialized/deserialized.

The request’s headers and body are treated as raw bytes and not serialized/deserialized.

Parameters:
  • service (string) – Name of the service to call.
  • endpoint (string) – Endpoint to call on service.
  • body (string) – A raw body to provide to the endpoint.
  • headers (string) – A raw headers block to provide to the endpoint.
  • timeout (int) – How long to wait (in ms) before raising a TimeoutError - this defaults to tchannel.glossary.DEFAULT_TIMEOUT.
  • retry_on (string) – What events to retry on - valid values can be found in tchannel.retry.
  • retry_limit (string) – How many times to retry before
  • hostport (string) – A ‘host:port’ value to use when making a request directly to a TChannel service, bypassing Hyperbahn.
Return type:

Response

Exception Handling

Errors

tchannel.errors.TIMEOUT = 1

The request timed out.

tchannel.errors.CANCELED = 2

The request was canceled.

tchannel.errors.BUSY = 3

The server was busy.

tchannel.errors.BAD_REQUEST = 6

The request was bad.

tchannel.errors.NETWORK_ERROR = 7

There was a network error when sending the request.

tchannel.errors.UNHEALTHY = 8

The server handling the request is unhealthy.

tchannel.errors.FATAL = 255

There was a fatal protocol-level error.

exception tchannel.errors.TChannelError(description=None, id=None, tracing=None)[source]

Bases: exceptions.Exception

A TChannel-generated exception.

Variables:code – The error code for this error. See the Specification for a description of these codes.
classmethod from_code(code, **kw)[source]

Construct a TChannelError instance from an error code.

This will return the appropriate class type for the given code.

exception tchannel.errors.RetryableError(description=None, id=None, tracing=None)[source]

Bases: tchannel.errors.TChannelError

An error where the original request is always safe to retry.

It is always safe to retry a request with this category of errors. The original request was never handled.

exception tchannel.errors.MaybeRetryableError(description=None, id=None, tracing=None)[source]

Bases: tchannel.errors.TChannelError

An error where the original request may be safe to retry.

The original request may have reached the intended service. Hence, the request should only be retried if it is known to be idempotent.

exception tchannel.errors.NotRetryableError(description=None, id=None, tracing=None)[source]

Bases: tchannel.errors.TChannelError

An error where the original request should not be re-sent.

Something was fundamentally wrong with the request and it should not be retried.

exception tchannel.errors.ReadError(description=None, id=None, tracing=None)[source]

Bases: tchannel.errors.FatalProtocolError

Raised when there is an error while reading input.

exception tchannel.errors.InvalidChecksumError(description=None, id=None, tracing=None)[source]

Bases: tchannel.errors.FatalProtocolError

Represent invalid checksum type in the message

exception tchannel.errors.NoAvailablePeerError(description=None, id=None, tracing=None)[source]

Bases: tchannel.errors.RetryableError

Represents a failure to find any peers for a request.

exception tchannel.errors.AlreadyListeningError(description=None, id=None, tracing=None)[source]

Bases: tchannel.errors.FatalProtocolError

Raised when attempting to listen multiple times.

exception tchannel.errors.OneWayNotSupportedError(description=None, id=None, tracing=None)[source]

Bases: tchannel.errors.BadRequestError

Raised when a one-way Thrift procedure is called.

exception tchannel.errors.ValueExpectedError(description=None, id=None, tracing=None)[source]

Bases: tchannel.errors.BadRequestError

Raised when a non-void Thrift response contains no value.

Retry Behavior

These values can be passed as the retry_on behavior to tchannel.TChannel.call().

tchannel.retry.CONNECTION_ERROR = u'c'

Retry the request on failures to connect to a remote host. This is the default retry behavior.

tchannel.retry.NEVER = u'n'

Never retry the request.

tchannel.retry.TIMEOUT = u't'

Retry the request on timeouts waiting for a response.

tchannel.retry.CONNECTION_ERROR_AND_TIMEOUT = u'ct'

Retry the request on failures to connect and timeouts after connecting.

tchannel.retry.DEFAULT_RETRY_LIMIT = 4

The default number of times to retry a request. This is in addition to the original request.

Synchronous Client

class tchannel.sync.TChannel(name, hostport=None, process_name=None, known_peers=None, trace=False, threadloop=None)[source]

Make synchronous TChannel requests.

This client does not support incoming requests – it is a uni-directional client only.

The client is implemented on top of the Tornado-based implementation and offloads IO to a thread running an IOLoop next to your process.

Usage mirrors the TChannel class.

tchannel = TChannel(name='my-synchronous-service')

# Advertise with Hyperbahn.
# This returns a future. You may want to block on its result,
# particularly if you want you app to die on unsuccessful
# advertisement.
tchannel.advertise(routers)

# thrift_service is the result of a call to ``thrift_request_builder``
future = tchannel.thrift(
    thrift_service.getItem('foo'),
    timeout=1,  #  1 second
)

result = future.result()
advertise(*args, **kwargs)[source]

Advertise with Hyperbahn.

After a successful advertisement, Hyperbahn will establish long-lived connections with your application. These connections are used to load balance inbound and outbound requests to other applications on the Hyperbahn network.

Re-advertisement happens periodically after calling this method (every minute). Hyperbahn will eject us from the network if it doesn’t get a re-advertise from us after 5 minutes.

Parameters:
  • routers (list) – A seed list of known Hyperbahn addresses to attempt contact with. Entries should be of the form "host:port".
  • name (string) – The name your application identifies itself as. This is usually unneeded because in the common case it will match the name you initialized the TChannel instance with. This is the identifier other services will use to make contact with you.
  • timeout – The timeout (in seconds) for the initial advertise attempt. Defaults to 30 seconds.
  • router_file – The host file that contains the routers information. The file should contain a JSON stringified format of the routers parameter. Either routers or router_file should be provided. If both provided, a ValueError will be raised.
Returns:

A future that resolves to the remote server’s response after the first advertise finishes.

Raises TimeoutError:
 

When unable to make our first advertise request to Hyperbahn. Subsequent requests may fail but will be ignored.

call(*args, **kwargs)[source]

Make low-level requests to TChannel services.

Note: Usually you would interact with a higher-level arg scheme like tchannel.schemes.JsonArgScheme or tchannel.schemes.ThriftArgScheme.

Testing

VCR

tchannel.testing.vcr provides VCR-like functionality for TChannel. Its API is heavily inspired by the vcrpy library.

This allows recording TChannel requests and their responses into YAML files during integration tests and replaying those recorded responses when the tests are run next time.

The simplest way to use this is with the use_cassette() function.

tchannel.testing.vcr.use_cassette(path, record_mode=None, inject=False)[source]

Use or create a cassette to record/replay TChannel requests.

This may be used as a context manager or a decorator.

from tchannel.testing import vcr

@pytest.mark.gen_test
@vcr.use_cassette('tests/data/bar.yaml')
def test_bar():
    channel = TChannel('test-client')
    service_client = MyServiceClient(channel)

    yield service_client.myMethod()


def test_bar():
    with vcr.use_cassette('tests/data/bar.yaml', record_mode='none'):
        # ...

Note that when used as a decorator on a coroutine, the use_cassette decorator must be applied BEFORE gen.coroutine or pytest.mark.gen_test.

Parameters:
  • path – Path to the cassette. If the cassette did not already exist, it will be created. If it existed, its contents will be replayed (depending on the record mode).
  • record_mode – The record mode dictates whether a cassette is allowed to record or replay interactions. This may be a string specifying the record mode name or an element from the tchannel.testing.vcr.RecordMode object. This parameter defaults to tchannel.testing.vcr.RecordMode.ONCE. See tchannel.testing.vcr.RecordMode for details on supported record modes and how to use them.
  • inject – If True, when use_cassette is used as a decorator, the cassette object will be injected into the function call as the first argument. Defaults to False.

Configuration

Record Modes
class tchannel.testing.vcr.RecordMode[source]

Record modes dictate how a cassette behaves when interactions are replayed or recorded. The following record modes are supported.

ONCE = 'once'

If the YAML file did not exist, record new interactions and save them. If the YAML file already existed, replay existing interactions but disallow any new interactions. This is the default and usually what you want.

NEW_EPISODES = 'new_episodes'

Replay existing interactions and allow recording new ones. This is usually undesirable since it reduces predictability in tests.

NONE = 'none'

Replay existing interactions and disallow any new interactions. This is a good choice for tests whose behavior is unlikely to change in the near future. It ensures that those tests don’t accidentally start making new requests.

ALL = 'all'

Do not replay anything and record all new interactions. Forget all existing interactions. This may be used to record everything anew.