Migrating to a version of TChannel with breaking changes? This guide documents what broke and how to safely migrate to newer versions.
tchannel.thrift.register
returns the original function as-is instead of the wrapped version. This allows writing unit tests that call the handler function directly.Previously, if you used the
tchannel.thrift.register
decorator to register a Thrift endpoint and then called that function directly from a test, it would return aResponse
object if the call succeeded or failed with an expected exception (defined in the Thrift IDL). For example,# service KeyValue { # string getValue(1: string key) # throws (1: ValidationError invalid) # } @tchannel.thrift.register(kv.KeyValue) def getValue(request): key = request.body.key if key == 'invalid': raise kv.ValidationError() result = # ... return result response = getValue(make_request(key='invalid')) if response.body.invalid: # ... else: result = response.body.success
With 0.21, we have changed
tchannel.thrift.register
to return the unmodified function so that you can call it directly and it will behave as expected.@tchannel.thrift.register(kv.KeyValue) def getValue(request): # ... try: result = getValue(make_request(key='invalid')) except kv.ValidationError: # ...
- No breaking changes.
- No breaking changes.
request.headers
in a JSON handler is no longer a JSON blob. Instead it is a dictionary mapping strings to strings. This matches the Thrift implementation. If your headers include richer types like lists or ints, you'll need to coordinate with your callers to no longer pass headers as JSON blobs. The same applies to JSON requsts; rich headers will now fail to serialize.If you were accessing
request_cls
orresponse_cls
directly from a service method in a module generated bytchannel.thrift.load
, you can no longer do that. Therequest_cls
andresponse_cls
attributes are internal details of the implementation and have been changed to protected. You should only ever use the service method directly.Before:
my_service.doSomething.request_cls(..)
After:
my_service.doSomething(..)
Note that
request_cls
gives you just an object containing the method arguments. It does not include any of the other information needed to make the request. So if you were using it to make requests, it wouldn't have worked anyway.
- No breaking changes.
tchannel.TChannel.register
no longer mimickstchannel.tornado.TChannel.register
, instead it exposes the new server API like so:Before:
from tchannel.tornado import TChannel tchannel = TChannel('my-service-name') @tchannel.register('endpoint', 'json') def endpoint(request, response, proxy): response.write({'resp': 'body'})
After:
from tchannel import TChannel tchannel = TChannel('my-service-name') @tchannel.json.register def endpoint(request): return {'resp': 'body'} # Or, if you need to return headers with your response: from tchannel import Response return Response({'resp': 'body'}, {'header': 'foo'})
TChannelSyncClient
has been replaced withtchannel.sync.TChannel
. This new synchronous client has been significantly re-worked to more closely match the asynchronousTChannel
API.tchannel.sync.thrift.client_for
has been removed andtchannel.thrift_request_builder
should be used instead (tchannel.thrift.client_for
still exists for backwards compatibility but is not recommended). This new API allows specifying headers, timeouts, and retry behavior with Thrift requests.Before:
from tchannel.sync import TChannelSyncClient from tchannel.sync.thrift import client_for from generated.thrift.code import MyThriftService tchannel_thrift_client = client_for('foo', MyThriftService) tchannel = TChannelSyncClient(name='bar') future = tchannel_thrift_client.someMethod(...) result = future.result()
After:
from tchannel import thrift_request_builder from tchannel.sync import TChannel from tchannel.retry import CONNECTION_ERROR_AND_TIMEOUT from generated.thrift.code import MyThriftService tchannel_thrift_client = thrift_request_builder( service='foo', thrift_module=MyThriftService, ) tchannel = TChannel(name='bar') future = tchannel.thrift( tchannel_thrift_client.someMethod(...) headers={'foo': 'bar'}, retry_on=CONNECTION_ERROR_AND_TIMEOUT, timeout=1000, ) result = future.result()
from tchannel.tornado import TChannel
is deprecated.Removed
retry_delay
option fromtchannel.tornado.peer.PeerClientOperation.send
method.Before:
tchannel.tornado.TChannel.request.send(retry_delay=300)
After: no more
retry_delay
intchannel.tornado.TChannel.request.send()
If you were catching
ProtocolError
you will need to catch a more specific type, such asTimeoutError
,BadRequestError
,NetworkError
,UnhealthyError
, orUnexpectedError
.If you were catching
AdvertiseError
, it has been replaced byTimeoutError
.If you were catching
BadRequest
, it may have been masking checksum errors and fatal streaming errors. These are now raised asFatalProtocolError
, but in practice should not need to be handled when interacting with a well-behaved TChannel implementation.TChannelApplicationError
was unused and removed.Three error types have been introduced to simplify retry handling:
NotRetryableError
(for requests should never be retried),RetryableError
(for requests that are always safe to retry), andMaybeRetryableError
(for requests that are safe to retry on idempotent endpoints).
- No breaking changes.
- No breaking changes.
- No breaking changes.
- Removed
print_arg
. Userequest.get_body()
instead.
Renamed
tchannel.tornado.TChannel.advertise
argumentrouter
torouters
. Since this is a required arg and the first positional arg, only clients who are using as kwarg will break.Before:
tchannel.advertise(router=['localhost:21300'])
After:
tchannel.advertise(routers=['localhost:21300'])