├── .coveragerc ├── .gitignore ├── .travis.yml ├── CHANGELOG.rst ├── GUIDE.rst ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.md ├── UPGRADE.rst ├── benchmarks ├── test_concurrent_requests.py ├── test_peer_selection.py └── test_roundtrip.py ├── crossdock ├── Dockerfile ├── __init__.py ├── docker-compose.yml ├── rules.mk ├── server │ ├── __init__.py │ ├── api.py │ ├── server.py │ └── simple-service.thrift └── setup.py ├── docs ├── .gitignore ├── Makefile ├── api.rst ├── changelog.rst ├── conf.py ├── faq.rst ├── guide.rst └── index.rst ├── examples ├── benchmark │ └── thrift │ │ ├── client.py │ │ └── server.py ├── guide │ └── keyvalue │ │ ├── keyvalue │ │ ├── __init__.py │ │ ├── client.py │ │ └── server.py │ │ ├── service.thrift │ │ └── setup.py ├── simple │ ├── json │ │ ├── client.py │ │ └── server.py │ ├── raw │ │ ├── client.py │ │ └── server.py │ └── thrift │ │ ├── client.py │ │ └── server.py └── sync │ └── fanout │ ├── client.py │ └── server.py ├── hooks └── pre-commit ├── requirements-docs.in ├── requirements-docs.txt ├── requirements-test.txt ├── requirements.in ├── requirements.txt ├── scripts └── install-hooks.sh ├── setup.cfg ├── setup.py ├── tchannel ├── __init__.py ├── _future.py ├── _queue.py ├── container │ ├── __init__.py │ └── heap.py ├── context.py ├── deprecate.py ├── enum.py ├── errors.py ├── event.py ├── frame.py ├── glossary.py ├── health │ ├── __init__.py │ ├── health.py │ └── meta.thrift ├── io.py ├── messages │ ├── __init__.py │ ├── base.py │ ├── call_continue.py │ ├── call_request.py │ ├── call_request_continue.py │ ├── call_response.py │ ├── call_response_continue.py │ ├── cancel.py │ ├── claim.py │ ├── common.py │ ├── error.py │ ├── init_request.py │ ├── init_response.py │ ├── ping_request.py │ ├── ping_response.py │ └── types.py ├── net.py ├── peer_heap.py ├── peer_strategy.py ├── request.py ├── response.py ├── retry.py ├── rw.py ├── schemes │ ├── __init__.py │ ├── json.py │ ├── raw.py │ └── thrift.py ├── serializer │ ├── __init__.py │ ├── json.py │ ├── raw.py │ └── thrift.py ├── singleton.py ├── statsd.py ├── status.py ├── sync │ ├── __init__.py │ ├── client.py │ ├── singleton.py │ └── thrift.py ├── tchannel.py ├── tcurl.py ├── testing │ ├── __init__.py │ └── vcr │ │ ├── __init__.py │ │ ├── cassette.py │ │ ├── config.py │ │ ├── exceptions.py │ │ ├── patch.py │ │ ├── proxy.py │ │ ├── proxy.thrift │ │ ├── record_modes.py │ │ ├── server.py │ │ └── yaml.py ├── thrift │ ├── __init__.py │ ├── client.py │ ├── module.py │ ├── reflection.py │ ├── rw.py │ └── server.py ├── tornado │ ├── __init__.py │ ├── connection.py │ ├── dispatch.py │ ├── hyperbahn.py │ ├── message_factory.py │ ├── peer.py │ ├── request.py │ ├── response.py │ ├── stream.py │ ├── tchannel.py │ ├── tombstone.py │ └── util.py ├── tracing.py ├── transport.py └── zipkin │ ├── __init__.py │ └── zipkin_trace.py ├── tests ├── __init__.py ├── conftest.py ├── container │ └── test_heap.py ├── data │ ├── __init__.py │ ├── generated │ │ ├── ThriftTest │ │ │ ├── SecondService-remote │ │ │ ├── SecondService.py │ │ │ ├── ThriftTest-remote │ │ │ ├── ThriftTest.py │ │ │ ├── __init__.py │ │ │ ├── constants.py │ │ │ └── ttypes.py │ │ └── __init__.py │ ├── hosts.json │ └── idls │ │ └── ThriftTest.thrift ├── integration │ ├── __init__.py │ ├── json │ │ ├── __init__.py │ │ └── test_json_server.py │ ├── test_client_server.py │ ├── test_error_handling.py │ ├── test_retry.py │ ├── thrift │ │ ├── __init__.py │ │ ├── test_thriftrw.py │ │ └── test_tornado_client.py │ └── tornado │ │ ├── __init__.py │ │ └── test_connection_reuse.py ├── messages │ ├── __init__.py │ └── test_common.py ├── mock_server.py ├── schemes │ ├── test_json.py │ ├── test_raw.py │ └── test_thrift.py ├── serializer │ ├── test_serializer_json.py │ ├── test_serializer_raw.py │ └── test_serializer_thrift.py ├── sync │ ├── __init__.py │ ├── test_client.py │ ├── test_singleton.py │ └── test_thrift.py ├── test_checksum.py ├── test_context.py ├── test_crossdock.py ├── test_dispatch.py ├── test_event.py ├── test_examples.py ├── test_forwarding.py ├── test_frame.py ├── test_future.py ├── test_health.py ├── test_hyperbahn.py ├── test_hyperbahn_blackhole.py ├── test_message_factory.py ├── test_messages.py ├── test_peer_heap.py ├── test_peer_strategy.py ├── test_queue.py ├── test_rw.py ├── test_singleton.py ├── test_statsd.py ├── test_stream.py ├── test_tchannel.py ├── test_tcurl.py ├── test_tracing.py ├── test_types.py ├── testing │ └── vcr │ │ ├── __init__.py │ │ ├── integration │ │ ├── data │ │ │ ├── old_with_tracing.yaml │ │ │ └── old_without_tracing.yaml │ │ ├── test_sync_client.py │ │ └── test_vcr.py │ │ ├── strategies.py │ │ ├── test_cassette.py │ │ ├── test_patcher.py │ │ └── test_server.py ├── thrift │ ├── test_module.py │ ├── test_multiple_services.py │ ├── test_reflection.py │ ├── test_rw.py │ └── test_server.py ├── tornado │ ├── __init__.py │ ├── conftest.py │ ├── test_connection.py │ ├── test_dispatch.py │ ├── test_hyperbahn.py │ ├── test_peer.py │ ├── test_request.py │ ├── test_tchannel.py │ └── test_tombstone.py └── util.py └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = true 3 | 4 | [report] 5 | exclude_lines = 6 | pragma: no cover 7 | raise NotImplementedError 8 | def __repr__ 9 | def __str__ 10 | omit = 11 | tchannel/health/thrift/* 12 | tchannel/zipkin/thrift/* 13 | tchannel/testing/vcr/proxy/* 14 | tchannel/testing/data/* 15 | examples/benchmark/thrift/service/* 16 | examples/guide/keyvalue/keyvalue/service/* 17 | 18 | [xml] 19 | output = coverage.xml 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | env/ 3 | *.xml 4 | *.egg-info 5 | *.coverage* 6 | *.pyc 7 | .phutil_module_cache 8 | build/ 9 | dist/ 10 | htmlcov 11 | .tox 12 | *.swp 13 | .hypothesis/ 14 | .DS_Store 15 | .idea/ 16 | .cache/ 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | sudo: required 3 | services: 4 | - docker 5 | 6 | # 7 | # testing multiple subdirectories 8 | # @see https://lord.io/blog/2014/travis-multiple-subdirs/ 9 | # @see https://www.dominicrodger.com/tox-and-travis.html 10 | # 11 | language: python 12 | python: 13 | - "2.7" 14 | - "3.4" 15 | - "3.6" 16 | - "3.7" 17 | env: 18 | global: 19 | - DOCKER_COMPOSE_VERSION=1.8.0 20 | - COMMIT=${TRAVIS_COMMIT::8} 21 | # DOCKER_PASS 22 | - secure: jmujcA176F+2qIGThWJi2RVmAUf8bhqjri6FJJenknwHsl5gs3UXkgtux6h2C02IOvyj97Ng6GN5WIDRObhKcnke2M6KBKguWh9CapX5XgAEci9i1HYUG9bWrlzwYvWuj/VLZJwmzoprCVJExzRVghw9jWmDv8TTQs6T8RJuWORMN9nn51ovm+4FrMoOVQj4mUBEvU+mag+0i5INsJ2JZyXUpx2qTu05urjsEUtJxFq3dLSsEKM2y9oKRmJTO57xhGSS0ew+QRvr8h15HRUydqW5kmaTbLRCX2vuXLSlU9iMUdQxYsrht5i8NoGriv+uoxHaa/s904BwefMXWqmV76EXSKSypXBzBu2QhTvTX/wusnibiriMwkLLd0Xl+1bRqs5A+U+csi8u2R7Uim6wuWOCpg/ce6S/BWMWQtdkt90IY0a2cm2furp7g5kSqPShLz3wbKeD7d7GoYAcL53jI713ruaJBO+KJkr8IqyP7yk2KXa3l97OoaA9h9BYYgYd2fX9NQ6wHUA2qsNloJFydgGoGQ5jbG3da3x7kN7wHzOyNYOiB0LPejnQG66OrxWBvkcGR7zeEGIm+GicbwGZ03EebLl0LnXnvmSE5jJtcnjFQRLc4QLIdWViwTaZgll9h9A43GEum08JMqpKg/tWg1h+CnpC/dyizNSPGNqA5yg= 23 | # DOCKER_USER 24 | - secure: zTYWdKexitzOizfPCxZ/O7LwSy0B8P0iwwzHueXnVC/Z/8tR0lPorqfy8pY1Ctc5yRuFMDMrmk+HHcb1ox+QoBcWrSZwmR5GAe5Uf7Hy/qzngqGb4qg6cAtUEIzPQN8PGNI0sun50WiiZV4/M05pCL5IKYgilsfKE8ecBXmEu9iMyH7f1U9h7KVQoxe1VaOZHhSz549xUnug5H4Xq+ou62MxDhkLHMkBf9BsFyHWNbw06YIbbArho8V8Hmz28y3ejsjtRzPGS+BHhsX9lCek7MI9NNM5MWar1ZRhmU6qShmoRl9H0eG3bgs5/8iUQWLQQeEgEr8DgFcVYfHAavJqBenyir7Qok+9PBuP5lc/SvNgLmG5pHsUX+Py0luJzyTqDoFn+2TV+zfFYVdWpMnXrYKyE+NGL7QSvhfPby4EzAMA1KeS3QkK9F+1LPU7U6+Q92+cQ69GZGaVG8tAdy/XpJw+q+PSjflNxRVXkJp1mqGkyU07h5bGwCj6GE1FvE160QiFVg0Tz7rNGLRmYPRLAxk8gCc6E+rMljF+/LoLWdYohFfRoPe2K0l8rsi/wqsX4xLp22oWr29woKF40lyIokTLcv1sDo8xwMSeNA0ledUTREGb33fWoWoB/ZJDAnKZMY1SWUEi+owGtW3aRJlN5auOoPoe7LxMf3HlzyojEaQ= 25 | matrix: 26 | - TOX_ENV=crossdock 27 | - TOX_ENV=cover 28 | - TOX_ENV=flake8 29 | - TOX_ENV=docs 30 | 31 | before_install: 32 | - make crossdock_install_ci 33 | 34 | install: 35 | - make install 36 | 37 | script: 38 | - make test_ci 39 | - make crossdock_logs_ci 40 | 41 | after_success: 42 | - coveralls -v 43 | 44 | #after_success: 45 | #- export REPO=tchannelhub/xdock-py 46 | #- export BRANCH=$(if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then echo $TRAVIS_BRANCH; else echo $TRAVIS_PULL_REQUEST_BRANCH; fi) 47 | #- export TAG=`if [ "$BRANCH" == "master" ]; then echo "latest"; else echo $BRANCH; fi` 48 | #- export DOCKER=$(if [ "$TOX_ENV" == "crossdock" ]; then echo docker; else echo true; fi) 49 | #- $DOCKER login -u $DOCKER_USER -p $DOCKER_PASS 50 | #- $DOCKER build -f crossdock/Dockerfile -t $REPO:$COMMIT . 51 | #- $DOCKER tag $REPO:$COMMIT $REPO:$TAG 52 | #- $DOCKER tag $REPO:$COMMIT $REPO:travis-$TRAVIS_BUILD_NUMBER 53 | #- $DOCKER push $REPO 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Uber Technologies, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | The above copyright notice and this permission notice shall be included in 10 | all copies or substantial portions of the Software. 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 17 | THE SOFTWARE. 18 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include tchannel * 2 | include README.rst 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | project := tchannel 2 | 3 | flake8 := flake8 4 | pytest := PYTHONDONTWRITEBYTECODE=1 py.test --tb short \ 5 | --cov-config .coveragerc --cov $(project) \ 6 | --async-test-timeout=1 --timeout=30 tests 7 | 8 | html_report := --cov-report html 9 | test_args := --cov-report term-missing 10 | 11 | TEST_HOST=127.0.0.1 12 | TEST_PORT=0 13 | TEST_LOG_FILE=test-server.log 14 | 15 | .DEFAULT_GOAL := test-lint 16 | 17 | -include crossdock/rules.mk 18 | 19 | 20 | env/bin/activate: 21 | virtualenv env 22 | 23 | env_install: env/bin/activate 24 | ./env/bin/pip install -r requirements-test.txt 25 | ./env/bin/python setup.py develop 26 | 27 | .PHONY: tox_install 28 | tox_install: 29 | pip install -r requirements-test.txt 30 | python setup.py develop 31 | 32 | .PHONY: install 33 | install: clean 34 | ifdef TOX_ENV 35 | make tox_install 36 | else 37 | make env_install 38 | endif 39 | 40 | .PHONY: test_server 41 | test_server: 42 | # TODO: use ${TEST_LOG_FILE} 43 | ./env/bin/python examples/tchannel_server.py --host ${TEST_HOST} --port ${TEST_PORT} 44 | 45 | .PHONY: test 46 | test: clean 47 | $(pytest) $(test_args) 48 | 49 | .PHONY: test_ci 50 | test_ci: clean 51 | ifeq ($(TOX_ENV), crossdock) 52 | $(MAKE) crossdock 53 | else 54 | pip install --upgrade setuptools 55 | tox -e $(TOX_ENV) -- tests 56 | endif 57 | 58 | .PHONY: benchmark 59 | benchmark: 60 | py.test benchmarks --benchmark-autosave --benchmark-save-data --benchmark-warmup --benchmark-disable-gc --benchmark-histogram 61 | 62 | .PHONY: testhtml 63 | testhtml: clean 64 | $(pytest) $(html_report) && open htmlcov/index.html 65 | 66 | .PHONY: clean 67 | clean: 68 | rm -rf dist/ 69 | rm -rf build/ 70 | @find $(project) tests -name "*.pyc" -delete 71 | 72 | .PHONY: lint 73 | lint: 74 | @$(flake8) $(project) tests examples setup.py 75 | 76 | .PHONY: test-lint 77 | test-lint: test lint 78 | 79 | .PHONY: docs 80 | docs: 81 | make -C docs html 82 | 83 | .PHONY: docsopen 84 | docsopen: docs 85 | open docs/_build/html/index.html 86 | 87 | .PHONY: vcr-thrift 88 | vcr-thrift: 89 | make -C ./tchannel/testing/vcr all 90 | 91 | .PHONY: gen_thrift 92 | gen_thrift: 93 | thrift --gen py:new_style,slots,dynamic -out tests/data/generated tests/data/idls/ThriftTest.thrift 94 | 95 | .PHONY: deps 96 | deps: requirements.txt requirements-docs.txt requirements-test.txt 97 | pip-sync requirements.txt requirements-docs.txt requirements-test.txt 98 | 99 | requirements.txt: requirements.in 100 | pip-compile --no-index requirements.in 101 | # Workaround for https://github.com/nvie/pip-tools/issues/325 102 | sed -i .txt '/-e /c\ ' requirements.txt 103 | 104 | requirements-docs.txt: requirements-docs.in 105 | pip-compile --no-index requirements-docs.in 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TChannel for Python 2 | 3 | ### This project is now End Of Life. 4 | 5 | The Open Source team at Uber has marked this project as no longer active, and so it is now **End of Life** here at Uber. 6 | 7 | We are actively searching for contributors or organizations who would like to adopt this project. If you or your organizaiton is interested in taking over this project, or wants to build something similar, please reach out to us: ospo@uber.com. 8 | 9 | The project is archived to prevent any confusion about it's status. 10 | 11 | More details can be found on this issue: [Bug 497: This repository will be archived...](https://github.com/uber/tchannel-python/issues/497). 12 | 13 | ### Original Documentation 14 | 15 | A Python implementation of [TChannel](https://github.com/uber/tchannel). 16 | 17 | [Documentation](http://tchannel-python.readthedocs.org/en/latest/) is available on Read the Docs. 18 | 19 | -------------------------------------------------------------------------------- /benchmarks/test_concurrent_requests.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import ( 22 | absolute_import, unicode_literals, print_function, division 23 | ) 24 | 25 | from tornado import gen 26 | from tornado.ioloop import IOLoop 27 | 28 | from tchannel import TChannel 29 | 30 | 31 | def setup_servers(num): 32 | servers = [] 33 | 34 | for i in range(num): 35 | server = TChannel('server' + str(i)) 36 | 37 | @server.raw.register 38 | def hello(request): 39 | return 'hello' 40 | 41 | server.listen() 42 | servers.append(server) 43 | 44 | return servers 45 | 46 | 47 | @gen.coroutine 48 | def setup_client(servers): 49 | known_peers = [] 50 | for i in range(1000): 51 | known_peers.append('1.1.1.1:'+str(i)) 52 | client = TChannel('client', known_peers=known_peers) 53 | # Add a bunch of unconnected peers 54 | 55 | @client.raw.register 56 | def hello(request): 57 | return 'hello' 58 | client.listen() 59 | 60 | # Open incoming connection from the server to the client. 61 | for server in servers: 62 | yield server.raw( 63 | service='server', 64 | endpoint='hello', 65 | body='hi', 66 | hostport=client.hostport, 67 | ) 68 | 69 | raise gen.Return(client) 70 | 71 | 72 | @gen.coroutine 73 | def peer_test(client): 74 | fs = [] 75 | for _ in range(100): 76 | fs.append(client.raw( 77 | service='server', 78 | endpoint='hello', 79 | body='hi', 80 | timeout=1000 81 | )) 82 | yield fs 83 | 84 | 85 | def stress_test(client): 86 | IOLoop.current().run_sync(lambda: peer_test(client)) 87 | 88 | 89 | def test_peer_heap(benchmark): 90 | servers = setup_servers(100) 91 | client = IOLoop.current().run_sync(lambda: setup_client(servers)) 92 | 93 | benchmark(stress_test, client) 94 | -------------------------------------------------------------------------------- /benchmarks/test_roundtrip.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from tornado import ioloop, gen 22 | 23 | from tchannel import TChannel, thrift 24 | 25 | 26 | service = thrift.load( 27 | path='examples/guide/keyvalue/service.thrift', 28 | service='benchmark-server', 29 | ) 30 | 31 | 32 | def test_roundtrip(benchmark): 33 | loop = ioloop.IOLoop.current() 34 | 35 | server = TChannel('benchmark-server') 36 | server.listen() 37 | 38 | clients = [TChannel('benchmark-client') for _ in range(10)] 39 | 40 | @server.thrift.register(service.KeyValue) 41 | def getValue(request): 42 | return 'bar' 43 | 44 | def roundtrip(): 45 | @gen.coroutine 46 | def doit(): 47 | futures = [] 48 | # 10 clients send 10 requests concurrently 49 | for client in clients: 50 | for _ in range(10): 51 | futures.append( 52 | client.thrift( 53 | service.KeyValue.getValue("foo"), 54 | hostport=server.hostport, 55 | ) 56 | ) 57 | yield futures 58 | 59 | return loop.run_sync(doit) 60 | 61 | # Establish initial connection 62 | roundtrip() 63 | 64 | benchmark(roundtrip) 65 | -------------------------------------------------------------------------------- /crossdock/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:2.7 2 | 3 | ENV APPDIR /usr/src/app/ 4 | RUN mkdir -p ${APPDIR} 5 | WORKDIR ${APPDIR} 6 | 7 | # Application installation 8 | COPY requirements-test.txt ${APPDIR} 9 | COPY requirements.txt ${APPDIR} 10 | 11 | COPY setup.py ${APPDIR} 12 | COPY setup.cfg ${APPDIR} 13 | COPY tchannel ${APPDIR}/tchannel/ 14 | 15 | # RUN pip install -U 'pip>=7,<8' 16 | RUN pip install --no-cache-dir -r requirements-test.txt 17 | RUN pip install --no-cache-dir -r requirements.txt 18 | RUN python setup.py install 19 | 20 | COPY crossdock ${APPDIR}/crossdock/ 21 | COPY crossdock/setup.py ${APPDIR}/setup_crossdock.py 22 | RUN python setup_crossdock.py install 23 | 24 | CMD ["crossdock"] 25 | EXPOSE 8080-8082 26 | -------------------------------------------------------------------------------- /crossdock/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | -------------------------------------------------------------------------------- /crossdock/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | crossdock: 5 | image: crossdock/crossdock 6 | links: 7 | - go 8 | - python 9 | environment: 10 | - WAIT_FOR=go,python 11 | 12 | - AXIS_CLIENT=go 13 | - AXIS_S1NAME=go 14 | - AXIS_SAMPLED=true,false 15 | - AXIS_S2NAME=go,python 16 | - AXIS_S2ENCODING=json,thrift 17 | - AXIS_S3NAME=go,python 18 | - AXIS_S3ENCODING=json,thrift 19 | 20 | - BEHAVIOR_TRACE=client,s1name,sampled,s2name,s2encoding,s3name,s3encoding 21 | - REPORT=list 22 | 23 | go: 24 | image: tchannelhub/xdock-go:dev 25 | ports: 26 | - "8080-8082" 27 | 28 | python: 29 | build: 30 | context: ../. 31 | dockerfile: crossdock/Dockerfile 32 | ports: 33 | - "8080-8082" 34 | -------------------------------------------------------------------------------- /crossdock/rules.mk: -------------------------------------------------------------------------------- 1 | XDOCK_YAML=crossdock/docker-compose.yml 2 | 3 | .PHONY: clean-compile 4 | clean-compile: 5 | find . -name '*.pyc' -exec rm {} \; 6 | 7 | .PHONY: docker 8 | docker: clean-compile 9 | docker build -f crossdock/Dockerfile -t jaeger-client-python . 10 | 11 | .PHONY: crossdock 12 | crossdock: 13 | docker-compose -f $(XDOCK_YAML) kill python 14 | docker-compose -f $(XDOCK_YAML) rm -f python 15 | docker-compose -f $(XDOCK_YAML) build python 16 | docker-compose -f $(XDOCK_YAML) run crossdock 17 | 18 | .PHONY: crossdock-fresh 19 | crossdock-fresh: 20 | docker-compose -f $(XDOCK_YAML) kill 21 | docker-compose -f $(XDOCK_YAML) rm --force 22 | docker-compose -f $(XDOCK_YAML) pull 23 | docker-compose -f $(XDOCK_YAML) build 24 | docker-compose -f $(XDOCK_YAML) run crossdock 25 | 26 | .PHONY: crossdock-logs 27 | crossdock-logs: 28 | docker-compose -f $(XDOCK_YAML) logs 29 | 30 | .PHONY: crossdock_install_ci 31 | crossdock_install_ci: 32 | ifeq ($(TOX_ENV), crossdock) 33 | docker version 34 | @echo "Installing docker-compose $${DOCKER_COMPOSE_VERSION:?'DOCKER_COMPOSE_VERSION env not set'}" 35 | sudo rm -f /usr/local/bin/docker-compose 36 | curl -L https://github.com/docker/compose/releases/download/$${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose 37 | chmod +x docker-compose 38 | sudo mv docker-compose /usr/local/bin 39 | docker-compose version 40 | else 41 | true 42 | endif 43 | 44 | .PHONY: crossdock_logs_ci 45 | crossdock_logs_ci: 46 | ifeq ($(TOX_ENV), crossdock) 47 | docker-compose -f $(XDOCK_YAML) logs 48 | else 49 | true 50 | endif 51 | -------------------------------------------------------------------------------- /crossdock/server/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uber/tchannel-python/248741146cb25f32fc3a1a3fabfb13d1d6b62355/crossdock/server/__init__.py -------------------------------------------------------------------------------- /crossdock/server/simple-service.thrift: -------------------------------------------------------------------------------- 1 | struct Data { 2 | 1: required bool b1, 3 | 2: required string s2, 4 | 3: required i32 i3 5 | } 6 | 7 | service SimpleService { 8 | Data Call(1: Data arg) 9 | } 10 | 11 | -------------------------------------------------------------------------------- /crossdock/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from setuptools import setup, find_packages 5 | 6 | setup( 7 | name='crossdock', 8 | version='1.0.0', 9 | include_package_data=True, 10 | zip_safe=False, 11 | packages=find_packages(exclude=['tests', 'example', 'tests.*']), 12 | entry_points={ 13 | 'console_scripts': [ 14 | 'crossdock = crossdock.server.server:serve', 15 | ] 16 | }, 17 | install_requires=[ 18 | # all dependencies are included in tchannel already 19 | ], 20 | ) 21 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | API Documentation 2 | ================= 3 | 4 | 5 | TChannel 6 | -------- 7 | 8 | .. autoclass:: tchannel.TChannel 9 | :special-members: __init__ 10 | :members: 11 | 12 | .. autoclass:: tchannel.singleton.TChannel 13 | :members: 14 | 15 | .. autoclass:: tchannel.Request 16 | :members: 17 | 18 | .. autoclass:: tchannel.Response 19 | :members: 20 | 21 | 22 | Serialization Schemes 23 | --------------------- 24 | 25 | Thrift 26 | ~~~~~~ 27 | 28 | .. autoclass:: tchannel.schemes.ThriftArgScheme 29 | :members: __call__, register 30 | 31 | .. autofunction:: tchannel.thrift.load 32 | 33 | .. autofunction:: tchannel.thrift_request_builder 34 | 35 | JSON 36 | ~~~~ 37 | 38 | .. autoclass:: tchannel.schemes.JsonArgScheme 39 | :members: __call__, register 40 | 41 | Raw 42 | ~~~ 43 | .. autoclass:: tchannel.schemes.RawArgScheme 44 | :members: __call__, register 45 | 46 | 47 | Exception Handling 48 | ------------------ 49 | 50 | Errors 51 | ~~~~~~ 52 | .. automodule:: tchannel.errors 53 | :members: 54 | :show-inheritance: 55 | 56 | Retry Behavior 57 | ~~~~~~~~~~~~~~ 58 | 59 | These values can be passed as the ``retry_on`` behavior to 60 | :py:meth:`tchannel.TChannel.call`. 61 | 62 | .. automodule:: tchannel.retry 63 | :members: 64 | 65 | 66 | Synchronous Client 67 | ------------------ 68 | 69 | .. autoclass:: tchannel.sync.TChannel 70 | :inherited-members: 71 | :members: 72 | 73 | .. autoclass:: tchannel.sync.singleton.TChannel 74 | :inherited-members: 75 | :members: 76 | 77 | 78 | Testing 79 | ------- 80 | 81 | .. automodule:: tchannel.testing.vcr 82 | 83 | .. 84 | This automodule directive intentionally doesn't include :members: because 85 | the module documentation for it explicitly calls out members that should be 86 | documented. 87 | 88 | -------------------------------------------------------------------------------- /docs/changelog.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | Changelog 3 | ========= 4 | 5 | .. include:: ../CHANGELOG.rst 6 | .. include:: ../UPGRADE.rst 7 | -------------------------------------------------------------------------------- /docs/faq.rst: -------------------------------------------------------------------------------- 1 | FAQ 2 | === 3 | 4 | .. _fallback-endpoint: 5 | 6 | Can I register an endpoint that accepts all requests? 7 | ----------------------------------------------------- 8 | 9 | The fallback endpoint is the endpoint called when an unrecognized request is 10 | received. By default, the fallback endpoint simply returns a 11 | ``BadRequestError`` to the caller. This behavior may be changed by 12 | registering an endpoint with ``TChannel.FALLBACK``. 13 | 14 | .. code-block:: python 15 | 16 | from tchannel import TChannel 17 | 18 | server = TChannel(name='myservice') 19 | 20 | @server.register(TChannel.FALLBACK) 21 | def handler(request): 22 | # ... 23 | 24 | This may be used to implement a TChannel server that can handle requests to all 25 | endpoints. Note that for the fallback endpoint, you have access to the raw 26 | bytes of the headers and the body. These must be serialized/deserialized 27 | manually. 28 | 29 | Why do I keep getting a "Cannot serialize MyType into a 'MyType'" error? 30 | ------------------------------------------------------------------------ 31 | 32 | You are trying to mix code generated by Apache Thrift with the module generated 33 | by :py:func:`tchannel.thrift.load`. These are two separate ways of using Thrift 34 | with TChannel and the classes generated by either cannot be mixed and matched. 35 | You should be using only one of these approaches to interact with a specific 36 | service. 37 | -------------------------------------------------------------------------------- /docs/guide.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../GUIDE.rst 2 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | TChannel for Python 2 | =================== 3 | 4 | 5 | |build-status| |coverage| 6 | 7 | 8 | A Python implementation of `TChannel`_. 9 | 10 | 11 | .. _TChannel: http://tchannel.readthedocs.org/ 12 | 13 | .. |build-status| image:: https://travis-ci.org/uber/tchannel-python.svg?branch=master 14 | :alt: build status 15 | :scale: 100% 16 | :target: https://travis-ci.org/uber/tchannel-python 17 | 18 | .. |coverage| image:: https://coveralls.io/repos/uber/tchannel-python/badge.svg?branch=master&service=github 19 | :alt: coverage 20 | :scale: 100% 21 | :target: https://coveralls.io/github/uber/tchannel-python?branch=master 22 | 23 | 24 | .. toctree:: 25 | :maxdepth: 2 26 | 27 | guide 28 | api 29 | faq 30 | changelog 31 | -------------------------------------------------------------------------------- /examples/benchmark/thrift/client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright (c) 2016 Uber Technologies, Inc. 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | # THE SOFTWARE. 21 | 22 | from __future__ import absolute_import 23 | from __future__ import print_function 24 | import sys 25 | import threading 26 | 27 | from tornado import gen, ioloop 28 | from tchannel import TChannel, thrift 29 | from six.moves import range 30 | 31 | tchannel = TChannel('thrift-benchmark-client') 32 | service = thrift.load( 33 | path='examples/guide/keyvalue/service.thrift', 34 | service='thrift-benchmark', 35 | hostport='localhost:12345', 36 | ) 37 | 38 | local = threading.local() 39 | local.requests = 0 40 | 41 | 42 | def report_work(): 43 | print(local.requests) 44 | sys.stdout.flush() 45 | local.requests = 0 46 | 47 | 48 | @gen.coroutine 49 | def do_work(): 50 | 51 | data = "a" * 4096 52 | 53 | while True: 54 | yield tchannel.thrift( 55 | request=service.KeyValue.setValue("key", data), 56 | ) 57 | local.requests += 1 58 | 59 | 60 | if __name__ == '__main__': 61 | if len(sys.argv) > 1: 62 | concurrency = int(sys.argv[1]) 63 | else: 64 | concurrency = 100 65 | 66 | sys.stderr.write('using concurrency %s\n' % concurrency) 67 | sys.stderr.flush() 68 | 69 | for _ in range(concurrency): 70 | do_work() 71 | 72 | ioloop.PeriodicCallback(report_work, 1000).start() 73 | 74 | try: 75 | ioloop.IOLoop.current().start() 76 | except KeyboardInterrupt: 77 | pass 78 | -------------------------------------------------------------------------------- /examples/benchmark/thrift/server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright (c) 2016 Uber Technologies, Inc. 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | # THE SOFTWARE. 21 | 22 | from __future__ import absolute_import 23 | from tornado import ioloop 24 | from tchannel import TChannel, thrift 25 | 26 | tchannel = TChannel('keyvalue-server', hostport='localhost:12345') 27 | service = thrift.load('examples/guide/keyvalue/service.thrift') 28 | 29 | values = {'hello': 'world'} 30 | 31 | 32 | @tchannel.thrift.register(service.KeyValue) 33 | def getValue(request): 34 | key = request.body.key 35 | value = values.get(key) 36 | 37 | if value is None: 38 | raise service.NotFoundError(key) 39 | 40 | return value 41 | 42 | 43 | @tchannel.thrift.register(service.KeyValue) 44 | def setValue(request): 45 | key = request.body.key 46 | value = request.body.value 47 | values[key] = value 48 | 49 | 50 | def run(): 51 | tchannel.listen() 52 | 53 | 54 | if __name__ == '__main__': 55 | run() 56 | ioloop.IOLoop.current().start() 57 | -------------------------------------------------------------------------------- /examples/guide/keyvalue/keyvalue/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uber/tchannel-python/248741146cb25f32fc3a1a3fabfb13d1d6b62355/examples/guide/keyvalue/keyvalue/__init__.py -------------------------------------------------------------------------------- /examples/guide/keyvalue/keyvalue/client.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | from __future__ import print_function 23 | from tornado import gen, ioloop 24 | from tchannel import TChannel, thrift 25 | 26 | tchannel = TChannel('keyvalue-consumer') 27 | service = thrift.load( 28 | path='examples/guide/keyvalue/service.thrift', 29 | service='keyvalue-server', 30 | hostport='localhost:8889', 31 | ) 32 | 33 | 34 | @gen.coroutine 35 | def run(): 36 | 37 | yield tchannel.thrift( 38 | service.KeyValue.setValue("foo", "Hello, world!"), 39 | ) 40 | 41 | response = yield tchannel.thrift( 42 | service.KeyValue.getValue("foo"), 43 | ) 44 | 45 | print(response.body) 46 | 47 | 48 | if __name__ == '__main__': 49 | ioloop.IOLoop.current().run_sync(run) 50 | -------------------------------------------------------------------------------- /examples/guide/keyvalue/keyvalue/server.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | from tornado import ioloop 23 | from tchannel import TChannel, thrift 24 | 25 | tchannel = TChannel('keyvalue-service', hostport='localhost:8889') 26 | service = thrift.load('examples/guide/keyvalue/service.thrift') 27 | 28 | values = {'hello': 'world'} 29 | 30 | 31 | @tchannel.thrift.register(service.KeyValue) 32 | def getValue(request): 33 | key = request.body.key 34 | value = values.get(key) 35 | 36 | if value is None: 37 | raise service.NotFoundError(key) 38 | 39 | return value 40 | 41 | 42 | @tchannel.thrift.register(service.KeyValue) 43 | def setValue(request): 44 | key = request.body.key 45 | value = request.body.value 46 | values[key] = value 47 | 48 | 49 | def run(): 50 | tchannel.listen() 51 | 52 | 53 | if __name__ == '__main__': 54 | run() 55 | ioloop.IOLoop.current().start() 56 | -------------------------------------------------------------------------------- /examples/guide/keyvalue/service.thrift: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | exception NotFoundError { 22 | 1: required string key, 23 | } 24 | 25 | service KeyValue { 26 | 27 | string getValue( 28 | 1: string key, 29 | ) throws ( 30 | 1: NotFoundError notFound, 31 | ) 32 | 33 | void setValue( 34 | 1: string key, 35 | 2: string value, 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /examples/guide/keyvalue/setup.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | from setuptools import setup, find_packages 23 | 24 | setup(name='keyvalue', packages=find_packages()) 25 | -------------------------------------------------------------------------------- /examples/simple/json/client.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | from __future__ import print_function 23 | import json 24 | 25 | from tornado import gen, ioloop 26 | 27 | from tchannel import TChannel 28 | 29 | 30 | tchannel = TChannel('json-client') 31 | 32 | 33 | @gen.coroutine 34 | def make_request(): 35 | 36 | resp = yield tchannel.json( 37 | service='json-server', 38 | endpoint='endpoint', 39 | body={ 40 | 'req': 'body' 41 | }, 42 | headers={ 43 | 'req': 'header' 44 | }, 45 | hostport='localhost:54496', 46 | ) 47 | 48 | raise gen.Return(resp) 49 | 50 | 51 | resp = ioloop.IOLoop.current().run_sync(make_request) 52 | 53 | assert resp.headers == { 54 | 'resp': 'header', 55 | } 56 | assert resp.body == { 57 | 'resp': 'body', 58 | } 59 | 60 | print(json.dumps(resp.body)) 61 | print(json.dumps(resp.headers)) 62 | -------------------------------------------------------------------------------- /examples/simple/json/server.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | from __future__ import print_function 23 | from tornado import ioloop 24 | 25 | from tchannel import TChannel, Response 26 | 27 | 28 | tchannel = TChannel('json-server', hostport='localhost:54496') 29 | 30 | 31 | @tchannel.json.register 32 | def endpoint(request): 33 | 34 | assert request.headers == {'req': 'header'} 35 | assert request.body == {'req': 'body'} 36 | 37 | return Response({'resp': 'body'}, headers={'resp': 'header'}) 38 | 39 | 40 | tchannel.listen() 41 | 42 | print(tchannel.hostport) 43 | 44 | ioloop.IOLoop.current().start() 45 | -------------------------------------------------------------------------------- /examples/simple/raw/client.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | from __future__ import print_function 23 | from tornado import gen, ioloop 24 | 25 | from tchannel import TChannel 26 | 27 | 28 | tchannel = TChannel('raw-client') 29 | 30 | 31 | @gen.coroutine 32 | def make_request(): 33 | 34 | resp = yield tchannel.raw( 35 | service='raw-server', 36 | endpoint='endpoint', 37 | body='req body', 38 | headers='req headers', 39 | hostport='localhost:54495', 40 | ) 41 | 42 | raise gen.Return(resp) 43 | 44 | 45 | resp = ioloop.IOLoop.current().run_sync(make_request) 46 | 47 | assert resp.headers == b'resp headers' 48 | assert resp.body == b'resp body' 49 | 50 | print(resp.body.decode('utf8')) 51 | print(resp.headers.decode('utf8')) 52 | -------------------------------------------------------------------------------- /examples/simple/raw/server.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import print_function 22 | 23 | from __future__ import absolute_import 24 | from tornado import gen, ioloop 25 | 26 | from tchannel import TChannel, Response 27 | 28 | 29 | tchannel = TChannel('raw-server', hostport='localhost:54495') 30 | 31 | 32 | @tchannel.raw.register 33 | @gen.coroutine 34 | def endpoint(request): 35 | 36 | assert request.headers == b'req headers' 37 | assert request.body == b'req body' 38 | 39 | return Response(b'resp body', headers=b'resp headers') 40 | 41 | 42 | tchannel.listen() 43 | 44 | print(tchannel.hostport) 45 | 46 | ioloop.IOLoop.current().start() 47 | -------------------------------------------------------------------------------- /examples/simple/thrift/client.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | from __future__ import print_function 23 | import json 24 | 25 | from tornado import gen 26 | from tornado import ioloop 27 | 28 | from tchannel import TChannel, thrift 29 | 30 | tchannel = TChannel('thrift-client') 31 | service = thrift.load( 32 | path='tests/data/idls/ThriftTest.thrift', 33 | service='thrift-server', 34 | hostport='localhost:54497', 35 | ) 36 | 37 | 38 | @gen.coroutine 39 | def make_request(): 40 | 41 | resp = yield tchannel.thrift( 42 | request=service.ThriftTest.testString(thing="req"), 43 | headers={ 44 | 'req': 'header', 45 | }, 46 | ) 47 | 48 | raise gen.Return(resp) 49 | 50 | 51 | resp = ioloop.IOLoop.current().run_sync(make_request) 52 | 53 | assert resp.headers == { 54 | 'resp': 'header', 55 | } 56 | assert resp.body == 'resp' 57 | 58 | print(resp.body) 59 | print(json.dumps(resp.headers)) 60 | -------------------------------------------------------------------------------- /examples/simple/thrift/server.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | from __future__ import print_function 24 | from tornado import gen, ioloop 25 | from tchannel import TChannel, Response, thrift 26 | 27 | tchannel = TChannel('thrift-server', hostport='localhost:54497') 28 | service = thrift.load('tests/data/idls/ThriftTest.thrift') 29 | 30 | 31 | @tchannel.thrift.register(service.ThriftTest) 32 | @gen.coroutine 33 | def testString(request): 34 | 35 | assert request.headers == {'req': 'header'} 36 | assert request.body.thing == 'req' 37 | 38 | return Response('resp', headers={'resp': 'header'}) 39 | 40 | 41 | tchannel.listen() 42 | 43 | print(tchannel.hostport) 44 | 45 | ioloop.IOLoop.current().start() 46 | -------------------------------------------------------------------------------- /examples/sync/fanout/client.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | from __future__ import print_function 23 | import json 24 | 25 | from tchannel import thrift 26 | from tchannel.sync import TChannel 27 | from six.moves import range 28 | 29 | tchannel = TChannel('thrift-client') 30 | service = thrift.load( 31 | path='tests/data/idls/ThriftTest.thrift', 32 | service='thrift-server', 33 | hostport='localhost:54498', 34 | ) 35 | 36 | 37 | def make_requests(): 38 | 39 | # Fan-out 40 | futures = [tchannel.thrift( 41 | request=service.ThriftTest.testString(thing="req"), 42 | headers={ 43 | 'req': 'header', 44 | }, 45 | ) for _ in range(20)] 46 | 47 | # Fan-in 48 | for future in futures: 49 | response = future.result() 50 | 51 | return response 52 | 53 | 54 | resp = make_requests() 55 | 56 | assert resp.headers == { 57 | 'resp': 'header', 58 | } 59 | assert resp.body == 'resp' * 100000 60 | 61 | print(resp.body[:4]) 62 | print(json.dumps(resp.headers)) 63 | -------------------------------------------------------------------------------- /examples/sync/fanout/server.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | from __future__ import print_function 24 | from tornado import gen, ioloop 25 | from tchannel import TChannel, Response, thrift 26 | 27 | tchannel = TChannel('thrift-server', hostport='localhost:54498') 28 | service = thrift.load('tests/data/idls/ThriftTest.thrift') 29 | 30 | 31 | @tchannel.thrift.register(service.ThriftTest) 32 | @gen.coroutine 33 | def testString(request): 34 | 35 | assert request.headers == {'req': 'header'} 36 | assert request.body.thing == 'req' 37 | 38 | return Response('resp' * 100000, headers={'resp': 'header'}) 39 | 40 | 41 | tchannel.listen() 42 | 43 | print(tchannel.hostport) 44 | 45 | ioloop.IOLoop.current().start() 46 | -------------------------------------------------------------------------------- /hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Ensures that the license blurb is added to all source code. 4 | 5 | findup() { 6 | if [[ -f "$1/$2" ]]; then 7 | echo "$1" 8 | else 9 | findUp "$1/.." "$2" 10 | fi 11 | } 12 | 13 | ROOT=$(realpath "$(findup "." "setup.py")") 14 | LICENSE_EXEC="$ROOT/node_modules/.bin/uber-licence" 15 | 16 | ensure_license() { 17 | cwd="$(pwd)" 18 | # This is ugly but it basically executes the uber-license tool, parses the 19 | # output to figure out which files were changed, and stages them into the 20 | # commit. 21 | ("$LICENSE_EXEC" --file '*.py' || exit 1) \ 22 | | grep '^fixed file ' | perl -F"'" -ane 'print "$F[1]\n"' \ 23 | | while read fileName; do 24 | echo "License added to $cwd/$fileName" 25 | git add "$fileName" 26 | done 27 | } 28 | 29 | unset GIT_DIR 30 | 31 | # Ensure uber-licence is installed. 32 | if [[ ! -x "$LICENSE_EXEC" ]]; then 33 | echo "uber-licence is not installed. Installing." 34 | pushd "$ROOT" &>/dev/null 35 | npm i uber-licence 36 | popd &>/dev/null 37 | fi 38 | 39 | for d in tchannel tests benchmarks examples; do 40 | pushd "$ROOT/$d" &>/dev/null 41 | ensure_license 42 | popd &>/dev/null 43 | done 44 | -------------------------------------------------------------------------------- /requirements-docs.in: -------------------------------------------------------------------------------- 1 | wrapt 2 | thrift==0.9.2 3 | sphinx 4 | sphinx_rtd_theme 5 | -------------------------------------------------------------------------------- /requirements-docs.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile 3 | # Make changes in requirements-docs.in, then run this to update: 4 | # 5 | # pip-compile --no-index requirements-docs.in 6 | # 7 | alabaster==0.7.7 # via sphinx 8 | babel==2.2.0 # via sphinx 9 | docutils==0.12 # via sphinx 10 | jinja2==2.8 # via sphinx 11 | markupsafe==0.23 # via jinja2 12 | pygments==2.1.2 # via sphinx 13 | pytz==2015.7 # via babel 14 | six==1.10.0 # via sphinx 15 | snowballstemmer==1.2.1 # via sphinx 16 | sphinx-rtd-theme==0.1.9 17 | sphinx==1.3.6 18 | thrift==0.9.2 19 | wrapt==1.10.8 20 | -------------------------------------------------------------------------------- /requirements-test.txt: -------------------------------------------------------------------------------- 1 | # Testing and coverage 2 | pytest<5 3 | pytest-cov 4 | pytest-timeout 5 | pytest-tornado 6 | # bound pygal due to https://github.com/ionelmc/pytest-benchmark/issues/50 7 | pygal<2.1 8 | pytest-benchmark[histogram]>=3,<4 9 | 10 | # Property based tests 11 | hypothesis>=1.14,<2 12 | 13 | # Integration test utilities 14 | sh 15 | psutil 16 | 17 | # Test all the pythons 18 | tox 19 | 20 | # 21 | # futures is installed implicitly through threadloop, 22 | # but tox has an issue where it doesn't pick that up, 23 | # causing tests to fail. This can be reproduced locally 24 | # by removing futures from this file and running: 25 | # 26 | # tox -e py27 tests 27 | # 28 | # @see https://travis-ci.org/uber/tchannel-python/jobs/86710564 29 | # 30 | futures 31 | 32 | # Syntax checker 33 | flake8==2.2.5 34 | 35 | # Optional dependency, but must be tested er'ry time 36 | thrift==0.11.0 37 | 38 | # Smarter decorators 39 | wrapt>=1.10,<1.11 40 | 41 | # OpenTracing reference implementation 42 | jaeger_client>4 43 | 44 | # Mocks 45 | mock==1.0.1 46 | doubles 47 | 48 | # for releases 49 | pip-tools 50 | zest.releaser[recommended] 51 | twine 52 | 53 | # for debugging 54 | ipdb 55 | coveralls 56 | 57 | PyYAML 58 | -------------------------------------------------------------------------------- /requirements.in: -------------------------------------------------------------------------------- 1 | # High-level dependencies are read from setup.py. 2 | -e . 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile 3 | # To update, run: 4 | # 5 | # pip-compile requirements.in 6 | # 7 | backports-abc==0.4 # via tornado 8 | contextlib2==0.5.1 # via opentracing-instrumentation 9 | crcmod==1.7 10 | future==0.17.1 # via opentracing-instrumentation 11 | futures==3.0.5 12 | opentracing-instrumentation==3.2.0 13 | opentracing==2.2.0 # via opentracing-instrumentation 14 | ply==3.8 # via thriftrw 15 | six==1.10.0 # via opentracing-instrumentation, thriftrw 16 | threadloop==1.0.1 17 | thriftrw==1.2.3 18 | tornado==4.3 # via opentracing-instrumentation, threadloop 19 | wrapt==1.10.8 # via opentracing-instrumentation 20 | -------------------------------------------------------------------------------- /scripts/install-hooks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This installs all hooks inside ./hooks into .git/hooks, skipping anything 4 | # that's already there. 5 | 6 | set -e 7 | 8 | ROOT=$(pwd) 9 | 10 | if [[ ! -d "$ROOT/.git" ]]; then 11 | echo "Please run this from the project root." 12 | exit 1 13 | fi 14 | 15 | find "$ROOT/hooks" -type file | while read hook; do 16 | name=$(basename "$hook") 17 | dest="$ROOT/.git/hooks/$name" 18 | if [[ -f "$dest" ]]; then 19 | echo "Skipping hook $name because it's already installed." 20 | else 21 | ln -s "$hook" "$dest" 22 | echo "Installed hook $name." 23 | fi 24 | done 25 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [zest.releaser] 2 | create-wheel = yes 3 | python-file-with-version = tchannel/__init__.py 4 | 5 | [flake8] 6 | # Ignore files generated by Thrift 7 | exclude = 8 | examples/guide/keyvalue/keyvalue/service/*, 9 | examples/benchmark/thrift/service/*, 10 | tchannel/health/thrift/*, 11 | tchannel/zipkin/thrift/*, 12 | tchannel/testing/vcr/proxy/*, 13 | tests/data/generated/ThriftTest/* 14 | 15 | [tool:pytest] 16 | markers = 17 | call: mark a test as using the client's calling API. 18 | integration: mark a test as a full client/server interaction between separate processes. 19 | heapfuzz: mark a test for heapfuzz 20 | concurrency_test: mark a test for concurrency 21 | hypothesis: mark a test for hypothesis 22 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages, setup 2 | 3 | import re 4 | 5 | version = None 6 | with open('tchannel/__init__.py', 'r') as f: 7 | for line in f: 8 | m = re.match(r'^__version__\s*=\s*(["\'])([^"\']+)\1', line) 9 | if m: 10 | version = m.group(2) 11 | break 12 | 13 | if not version: 14 | raise Exception( 15 | 'Could not determine version number from tchannel/__init__.py' 16 | ) 17 | 18 | 19 | setup( 20 | name='tchannel', 21 | version=version, 22 | author=', '.join([ 23 | 'Abhinav Gupta', 24 | 'Aiden Scandella', 25 | 'Bryce Lampe', 26 | 'Grayson Koonce', 27 | 'Junchao Wu', 28 | ]), 29 | author_email='abg@uber.com', 30 | description='Network multiplexing and framing protocol for RPC', 31 | license='MIT', 32 | url='https://github.com/uber/tchannel-python', 33 | keywords=['rpc'], 34 | classifiers=[ 35 | 'Development Status :: 4 - Beta', 36 | 'Intended Audience :: Developers', 37 | 'License :: OSI Approved :: MIT License', 38 | 'Programming Language :: Python :: 2', 39 | 'Programming Language :: Python :: Implementation :: PyPy', 40 | 'Topic :: Software Development :: Libraries :: Python Modules', 41 | ], 42 | packages=find_packages(exclude=['crossdock', 'tests', 'tests.*']), 43 | package_data={ 44 | '': ['*.thrift'], 45 | }, 46 | install_requires=[ 47 | # stdlib backports, no constraints needed 48 | 'contextlib2', 49 | 'futures', 50 | 51 | # external deps 52 | 'crcmod>=1,<2', 53 | 'tornado>=4.3,<5', 54 | 55 | # tchannel deps 56 | 'thriftrw>=0.4,<2', 57 | 'threadloop>=1,<2', 58 | 59 | # tracing deps 60 | 'opentracing>2', 61 | 'opentracing_instrumentation>3', 62 | 63 | # python 3 compat 64 | 'six', 65 | 'future' 66 | ], 67 | extras_require={ 68 | 'vcr': ['PyYAML', 'mock', 'wrapt'], 69 | }, 70 | entry_points={ 71 | 'console_scripts': [ 72 | 'tcurl.py = tchannel.tcurl:start_ioloop' 73 | ] 74 | }, 75 | ) 76 | -------------------------------------------------------------------------------- /tchannel/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import ( 22 | absolute_import, division, print_function, unicode_literals 23 | ) 24 | 25 | __version__ = '2.0.2.dev0' 26 | # Update setup.py when changing this. zest.releaser doesn't support updating 27 | # both of them yet. 28 | 29 | 30 | from .response import Response # noqa 31 | from .request import Request # noqa 32 | from .tchannel import TChannel # noqa 33 | from .thrift import thrift_request_builder # noqa 34 | -------------------------------------------------------------------------------- /tchannel/_future.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import ( 22 | absolute_import, unicode_literals, division, print_function 23 | ) 24 | 25 | import sys 26 | from functools import wraps 27 | 28 | from tornado.gen import is_future 29 | 30 | 31 | def fail_to(future): 32 | """A decorator for function callbacks to catch uncaught non-async 33 | exceptions and forward them to the given future. 34 | 35 | The primary use for this is to catch exceptions in async callbacks and 36 | propagate them to futures. For example, consider, 37 | 38 | .. code-block:: python 39 | 40 | answer = Future() 41 | 42 | def on_done(future): 43 | foo = bar() 44 | answer.set_result(foo) 45 | 46 | some_async_operation().add_done_callback(on_done) 47 | 48 | If ``bar()`` fails, ``answer`` will never get filled with an exception or 49 | a result. Now if we change ``on_done`` to, 50 | 51 | .. code-block:: python 52 | 53 | @fail_to(answer) 54 | def on_done(future): 55 | foo = bar() 56 | answer.set_result(foo) 57 | 58 | Uncaught exceptions in ``on_done`` will be caught and propagated to 59 | ``answer``. Note that ``on_done`` will return None if an exception was 60 | caught. 61 | 62 | :param answer: 63 | Future to which the result will be written. 64 | """ 65 | assert is_future(future), 'you forgot to pass a future' 66 | 67 | def decorator(f): 68 | 69 | @wraps(f) 70 | def new_f(*args, **kwargs): 71 | try: 72 | return f(*args, **kwargs) 73 | except Exception: 74 | future.set_exc_info(sys.exc_info()) 75 | 76 | return new_f 77 | 78 | return decorator 79 | -------------------------------------------------------------------------------- /tchannel/container/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uber/tchannel-python/248741146cb25f32fc3a1a3fabfb13d1d6b62355/tchannel/container/__init__.py -------------------------------------------------------------------------------- /tchannel/deprecate.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import ( 22 | absolute_import, division, print_function, unicode_literals 23 | ) 24 | 25 | import functools 26 | import warnings 27 | 28 | 29 | def deprecate(message): 30 | """Loudly prints warning.""" 31 | warnings.simplefilter('default') 32 | warnings.warn(message, category=DeprecationWarning) 33 | warnings.resetwarnings() 34 | 35 | 36 | def deprecated(message): 37 | """Warn every time a fn is called.""" 38 | def decorator(fn): 39 | @functools.wraps(fn) 40 | def new_fn(*args, **kwargs): 41 | deprecate(message) 42 | return fn(*args, **kwargs) 43 | return new_fn 44 | return decorator 45 | -------------------------------------------------------------------------------- /tchannel/enum.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | import collections 24 | 25 | 26 | def enum(class_name, **values): 27 | class_type = collections.namedtuple(class_name, list(values.keys())) 28 | instance = class_type(**values) 29 | return instance 30 | -------------------------------------------------------------------------------- /tchannel/frame.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | from collections import namedtuple 24 | 25 | from . import rw 26 | from .errors import ReadError 27 | from .io import BytesIO 28 | 29 | FrameHeader = namedtuple('FrameHeader', 'message_type message_id') 30 | Frame = namedtuple('Frame', 'header payload') 31 | 32 | 33 | class FrameReadWriter(rw.ReadWriter): 34 | 35 | # ReadWriter for Frame size 36 | size_rw = rw.number(2) # size:2 37 | 38 | # ReadWriter for FrameHeaders 39 | header_rw = rw.instance( 40 | FrameHeader, 41 | ('message_type', rw.number(1)), # type:1 42 | (rw.skip, rw.constant(rw.number(1), 0)), # reserved:1 43 | ('message_id', rw.number(4)), # id:4 44 | (rw.skip, rw.constant(rw.number(8), 0)), # reserved:8 45 | ) 46 | 47 | def read(self, stream, size=None): 48 | if not size: 49 | try: 50 | size = self.size_rw.read(stream) 51 | except ReadError: 52 | return None 53 | if not size: 54 | return None 55 | 56 | body = self.take(stream, size - self.size_rw.width()) 57 | 58 | header_width = self.header_rw.width() 59 | header_body, payload = body[:header_width], body[header_width:] 60 | 61 | header = self.header_rw.read(BytesIO(header_body)) 62 | return Frame(header, payload) 63 | 64 | def write(self, frame, stream): 65 | prelude_size = self.size_rw.width() + self.header_rw.width() 66 | size = prelude_size + len(frame.payload) 67 | 68 | self.size_rw.write(size, stream) 69 | self.header_rw.write(frame.header, stream) 70 | stream.write(frame.payload) 71 | 72 | return stream 73 | 74 | def width(self): 75 | return self.size_rw.width() + self.header_rw.width() 76 | 77 | frame_rw = FrameReadWriter() 78 | -------------------------------------------------------------------------------- /tchannel/glossary.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | import platform 24 | 25 | from . import __version__ 26 | 27 | # Largest message ID supported by the system. 28 | # Message ID 0xffffffff is reserved 29 | MAX_MESSAGE_ID = 0xfffffffe 30 | 31 | # CallRequestMessage uses it as the default TTL value for the message. 32 | DEFAULT_TIMEOUT = 30 # seconds 33 | 34 | TCHANNEL_LANGUAGE = 'python' 35 | 36 | # python environment, eg 'CPython-2.7.10' 37 | TCHANNEL_LANGUAGE_VERSION = ( 38 | platform.python_implementation() + '-' + platform.python_version() 39 | ) 40 | 41 | # version format x.y.z 42 | TCHANNEL_VERSION = __version__ 43 | -------------------------------------------------------------------------------- /tchannel/health/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | from .health import health # noqa 24 | from .health.meta import HealthStatus # noqa 25 | from .health.meta import Meta # noqa 26 | -------------------------------------------------------------------------------- /tchannel/health/health.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | import os 24 | import sys 25 | 26 | from .. import thrift 27 | 28 | 29 | base = os.path.dirname(__file__) 30 | meta = thrift.load(os.path.join(base, 'meta.thrift')) 31 | sys.modules[__name__ + '.meta'] = meta 32 | 33 | 34 | def health(request): 35 | return meta.HealthStatus(ok=True) 36 | -------------------------------------------------------------------------------- /tchannel/health/meta.thrift: -------------------------------------------------------------------------------- 1 | struct HealthStatus { 2 | 1: required bool ok 3 | 2: optional string message 4 | } 5 | 6 | service Meta { 7 | HealthStatus health() 8 | } 9 | -------------------------------------------------------------------------------- /tchannel/io.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | try: 24 | from cStringIO import StringIO as BytesIO 25 | except ImportError: # pragma: no cover 26 | from io import BytesIO # noqa 27 | -------------------------------------------------------------------------------- /tchannel/messages/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | from .call_request import CallRequestMessage, call_req_rw 24 | from .call_request_continue import call_req_c_rw 25 | from .call_response import CallResponseMessage, call_res_rw 26 | from .call_response_continue import call_res_c_rw 27 | from .cancel import CancelMessage, cancel_rw 28 | from .claim import ClaimMessage, claim_rw 29 | from .common import Tracing, ChecksumType 30 | from .error import ErrorMessage, ErrorCode, error_rw 31 | from .init_request import InitRequestMessage, init_req_rw 32 | from .init_response import InitResponseMessage, init_res_rw 33 | from .ping_request import PingRequestMessage, ping_req_rw 34 | from .ping_response import PingResponseMessage, ping_res_rw 35 | from .types import Types 36 | 37 | RW = { 38 | Types.CALL_REQ: call_req_rw, 39 | Types.CALL_REQ_CONTINUE: call_req_c_rw, 40 | Types.CALL_RES: call_res_rw, 41 | Types.CALL_RES_CONTINUE: call_res_c_rw, 42 | Types.CANCEL: cancel_rw, 43 | Types.CLAIM: claim_rw, 44 | Types.ERROR: error_rw, 45 | Types.INIT_REQ: init_req_rw, 46 | Types.INIT_RES: init_res_rw, 47 | Types.PING_REQ: ping_req_rw, 48 | Types.PING_RES: ping_res_rw, 49 | } 50 | 51 | __all__ = [ 52 | "RW", 53 | "ChecksumType", 54 | "CallRequestMessage", 55 | "CallRequestContinueMessage", 56 | "CallResponseMessage", 57 | "CallResponseContinueMessage", 58 | "CancelMessage", 59 | "ClaimMessage", 60 | "ErrorMessage", 61 | "ErrorCode", 62 | "InitRequestMessage", 63 | "InitResponseMessage", 64 | "PingRequestMessage", 65 | "PingResponseMessage", 66 | "Tracing", 67 | ] 68 | -------------------------------------------------------------------------------- /tchannel/messages/base.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | 24 | class BaseMessage(object): 25 | """Represent common functionality across all TChannel messages.""" 26 | message_type = None 27 | 28 | __slots__ = ('id',) 29 | 30 | def __init__(self, id=0): 31 | self.id = id 32 | 33 | def __eq__(self, other): 34 | if other is None: 35 | return False 36 | return all( 37 | getattr(self, attr) == getattr(other, attr) 38 | for attr in self.__slots__ 39 | ) 40 | 41 | def __str__(self): 42 | attrs = [ 43 | "%s=%s" % (attr, str(getattr(self, attr))) 44 | for attr in self.__slots__ 45 | ] 46 | 47 | return "%s(%s)" % ( 48 | str(self.__class__.__name__), 49 | ", ".join(attrs) 50 | ) 51 | 52 | def __repr__(self): 53 | return self.__str__() 54 | -------------------------------------------------------------------------------- /tchannel/messages/call_request.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | from . import common 24 | from .. import rw 25 | from ..glossary import DEFAULT_TIMEOUT 26 | from .call_request_continue import CallRequestContinueMessage 27 | from .types import Types 28 | 29 | 30 | class CallRequestMessage(CallRequestContinueMessage): 31 | """Initiate an RPC call.""" 32 | message_type = Types.CALL_REQ 33 | 34 | __slots__ = CallRequestContinueMessage.__slots__ + ( 35 | 'ttl', 36 | 'tracing', 37 | 'service', 38 | 'headers', 39 | ) 40 | 41 | def __init__( 42 | self, 43 | flags=0, 44 | ttl=DEFAULT_TIMEOUT, 45 | tracing=None, 46 | service=None, 47 | headers=None, 48 | checksum=None, 49 | args=None, 50 | id=0, 51 | ): 52 | args = args or [b"", b"", b""] 53 | super(CallRequestMessage, self).__init__(flags, checksum, args, id) 54 | self.ttl = ttl 55 | self.tracing = tracing or common.Tracing(0, 0, 0, 0) 56 | self.service = service or '' 57 | self.headers = dict(headers) if headers else {} 58 | 59 | call_req_rw = rw.instance( 60 | CallRequestMessage, 61 | ("flags", rw.number(1)), # flags:1 62 | ("ttl", rw.number(4)), # ttl:4 63 | ("tracing", common.tracing_rw), # tracing:24 64 | # traceflags: 1 65 | ("service", rw.len_prefixed_string(rw.number(1))), # service~1 66 | ("headers", rw.headers( # nh:1 (hk~1 hv~1){nh} 67 | rw.number(1), 68 | rw.len_prefixed_string(rw.number(1)) 69 | )), 70 | ("checksum", common.checksum_rw), # csumtype:1 (csum:4){0, 1} 71 | ("args", 72 | rw.args(rw.number(2))), # [arg1~2, arg2~2, arg3~2] 73 | ) 74 | -------------------------------------------------------------------------------- /tchannel/messages/call_request_continue.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | from . import common 24 | from .. import rw 25 | from .call_continue import CallContinueMessage 26 | from .types import Types 27 | 28 | 29 | class CallRequestContinueMessage(CallContinueMessage): 30 | """Represent a continuation of a call request (across multiple frames).""" 31 | message_type = Types.CALL_REQ_CONTINUE 32 | 33 | def __init__( 34 | self, 35 | flags=0, 36 | checksum=None, 37 | args=None, 38 | id=0, 39 | ): 40 | super(CallRequestContinueMessage, self).__init__( 41 | flags, checksum, args, id) 42 | 43 | def fragment(self, space_left): 44 | fragment_msg = CallRequestContinueMessage( 45 | flags=self.flags, 46 | checksum=self.checksum, 47 | ) 48 | return super(CallRequestContinueMessage, self).\ 49 | fragment(space_left, fragment_msg) 50 | 51 | 52 | call_req_c_rw = rw.instance( 53 | CallRequestContinueMessage, 54 | ("flags", rw.number(1)), # flags:1 55 | ("checksum", common.checksum_rw), # csumtype:1 (csum:4){0, 1} 56 | ("args", rw.args(rw.number(2))), # [arg1~2, arg2~2, arg3~2] 57 | ) 58 | -------------------------------------------------------------------------------- /tchannel/messages/call_response.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | from . import common 24 | from .. import rw 25 | from .call_response_continue import CallResponseContinueMessage 26 | from .types import Types 27 | 28 | 29 | class CallResponseMessage(CallResponseContinueMessage): 30 | """Respond to an RPC call.""" 31 | message_type = Types.CALL_RES 32 | 33 | __slots__ = CallResponseContinueMessage.__slots__ + ( 34 | 'code', 35 | 'tracing', 36 | 'headers', 37 | ) 38 | 39 | def __init__( 40 | self, 41 | flags=0, 42 | code=0, 43 | tracing=None, 44 | headers=None, 45 | checksum=None, 46 | args=None, 47 | id=0, 48 | ): 49 | args = args or [b"", b"", b""] 50 | super(CallResponseMessage, self).__init__(flags, checksum, args, id) 51 | self.code = code 52 | self.tracing = tracing or common.Tracing(0, 0, 0, 0) 53 | self.headers = dict(headers) if headers else {} 54 | 55 | 56 | call_res_rw = rw.instance( 57 | CallResponseMessage, 58 | ("flags", rw.number(1)), # flags:1 59 | ("code", rw.number(1)), # code:1 60 | ("tracing", common.tracing_rw), # tracing:24 61 | # traceflags: 1 62 | ("headers", rw.headers( # nh:1 (hk~1 hv~1){nh} 63 | rw.number(1), 64 | rw.len_prefixed_string(rw.number(1)) 65 | )), 66 | ("checksum", common.checksum_rw), # csumtype:1 (csum:4){0, 1} 67 | ("args", 68 | rw.args(rw.number(2))), # [arg1~2, arg2~2, arg3~2] 69 | ) 70 | -------------------------------------------------------------------------------- /tchannel/messages/call_response_continue.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | from . import common 24 | from .. import rw 25 | from .call_continue import CallContinueMessage 26 | from .types import Types 27 | 28 | 29 | class CallResponseContinueMessage(CallContinueMessage): 30 | """Represent a continuation of a call response (across multiple frames).""" 31 | message_type = Types.CALL_RES_CONTINUE 32 | 33 | def __init__( 34 | self, 35 | flags=0, 36 | checksum=None, 37 | args=None, 38 | id=0, 39 | ): 40 | super(CallResponseContinueMessage, self).__init__( 41 | flags, checksum, args, id) 42 | 43 | def fragment(self, space_left): 44 | fragment_msg = CallResponseContinueMessage( 45 | flags=self.flags, 46 | checksum=self.checksum, 47 | ) 48 | return super(CallResponseContinueMessage, self).fragment( 49 | space_left, fragment_msg) 50 | 51 | call_res_c_rw = rw.instance( 52 | CallResponseContinueMessage, 53 | ("flags", rw.number(1)), # flags:1 54 | ("checksum", common.checksum_rw), # csumtype:1 (csum:4){0, 1} 55 | ("args", rw.args(rw.number(2))), # [arg1~2, arg2~2, arg3~2] 56 | ) 57 | -------------------------------------------------------------------------------- /tchannel/messages/cancel.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | from . import common 24 | from .. import rw 25 | from ..glossary import DEFAULT_TIMEOUT 26 | from .base import BaseMessage 27 | 28 | 29 | class CancelMessage(BaseMessage): 30 | __slots__ = BaseMessage.__slots__ + ( 31 | 'ttl', 32 | 'tracing', 33 | 'why', 34 | ) 35 | 36 | def __init__(self, ttl=DEFAULT_TIMEOUT, tracing=None, why=None, id=0): 37 | super(CancelMessage, self).__init__(id) 38 | self.ttl = ttl 39 | self.tracing = tracing or common.Tracing(0, 0, 0, 0) 40 | self.why = why or '' 41 | 42 | 43 | cancel_rw = rw.instance( 44 | CancelMessage, 45 | ('ttl', rw.number(4)), # ttl:4 46 | ('tracing', common.tracing_rw), # tracing:24 47 | ('why', rw.len_prefixed_string(rw.number(2))), # why:2 48 | ) 49 | -------------------------------------------------------------------------------- /tchannel/messages/claim.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | from . import common 24 | from .. import rw 25 | from ..glossary import DEFAULT_TIMEOUT 26 | from .base import BaseMessage 27 | 28 | 29 | class ClaimMessage(BaseMessage): 30 | __slots__ = BaseMessage.__slots__ + ( 31 | 'ttl', 32 | 'tracing', 33 | ) 34 | 35 | def __init__(self, ttl=DEFAULT_TIMEOUT, tracing=None, id=0): 36 | super(ClaimMessage, self).__init__(id) 37 | self.ttl = ttl 38 | self.tracing = tracing or common.Tracing(0, 0, 0, 0) 39 | 40 | 41 | claim_rw = rw.instance( 42 | ClaimMessage, 43 | ('ttl', rw.number(4)), # ttl:4 44 | ('tracing', common.tracing_rw), # tracing:24 45 | ) 46 | -------------------------------------------------------------------------------- /tchannel/messages/error.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | from . import common 24 | from .. import rw 25 | from ..enum import enum 26 | from .base import BaseMessage 27 | from .types import Types 28 | 29 | ErrorCode = enum( 30 | 'ErrorCode', 31 | timeout=0x01, 32 | cancelled=0x02, 33 | busy=0x03, 34 | declined=0x04, 35 | unexpected=0x05, 36 | bad_request=0x06, 37 | network_error=0x07, 38 | unhealthy=0x08, 39 | fatal=0xff, 40 | ) 41 | 42 | error_code_rw = rw.number(1) 43 | 44 | 45 | class ErrorMessage(BaseMessage): 46 | """Respond to a CALL_REQ with a failure at the protocol level.""" 47 | message_type = Types.ERROR 48 | 49 | __slots__ = BaseMessage.__slots__ + ( 50 | 'code', 51 | 'tracing', 52 | 'description', 53 | ) 54 | 55 | ERROR_CODES = { 56 | ErrorCode.timeout: 'timeout', 57 | ErrorCode.cancelled: 'cancelled', 58 | ErrorCode.busy: 'busy', 59 | ErrorCode.declined: 'declined', 60 | ErrorCode.unexpected: 'unexpected', 61 | ErrorCode.bad_request: 'bad request', 62 | ErrorCode.network_error: 'network error', 63 | ErrorCode.unhealthy: 'unhealthy error', 64 | ErrorCode.fatal: 'fatal protocol error' 65 | } 66 | 67 | def __init__(self, code=None, tracing=None, description=None, id=0): 68 | super(ErrorMessage, self).__init__(id) 69 | self.code = code if code else ErrorCode.unexpected 70 | self.description = description or '' 71 | self.tracing = tracing or common.Tracing(0, 0, 0, 0) 72 | 73 | def error_name(self): 74 | """Get a friendly error message.""" 75 | return self.ERROR_CODES.get(self.code) 76 | 77 | 78 | error_rw = rw.instance( 79 | ErrorMessage, 80 | ('code', error_code_rw), # code:1 81 | ('tracing', common.tracing_rw), # tracing:24 82 | ('description', rw.len_prefixed_string(rw.number(2))) # message~2 83 | ) 84 | -------------------------------------------------------------------------------- /tchannel/messages/init_request.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | from .. import rw 24 | from .base import BaseMessage 25 | from .common import PROTOCOL_VERSION 26 | from .types import Types 27 | 28 | 29 | class InitRequestMessage(BaseMessage): 30 | """Initialize a connection to a TChannel server.""" 31 | message_type = Types.INIT_REQ 32 | HOST_PORT = 'host_port' 33 | PROCESS_NAME = 'process_name' 34 | 35 | # Micro-optimizations are the best kinds of optimizations 36 | __slots__ = BaseMessage.__slots__ + ( 37 | 'version', 38 | 'headers', 39 | ) 40 | 41 | def __init__( 42 | self, 43 | version=None, 44 | headers=None, 45 | id=0, 46 | ): 47 | super(InitRequestMessage, self).__init__(id) 48 | self.version = version or PROTOCOL_VERSION 49 | self.headers = dict(headers) if headers else {} 50 | 51 | @property 52 | def host_port(self): 53 | return self.headers.get(self.HOST_PORT) 54 | 55 | @host_port.setter 56 | def host_port(self, value): 57 | self.headers[self.HOST_PORT] = value 58 | 59 | @property 60 | def process_name(self): 61 | return self.headers.get(self.PROCESS_NAME) 62 | 63 | @process_name.setter 64 | def process_name(self, value): 65 | self.headers[self.PROCESS_NAME] = value 66 | 67 | 68 | init_req_rw = rw.instance( 69 | InitRequestMessage, 70 | ('version', rw.number(2)), # version:2 71 | ('headers', rw.headers( # nh:2 (key~2 value~2){nh} 72 | rw.number(2), 73 | rw.len_prefixed_string(rw.number(2)), 74 | rw.len_prefixed_string(rw.number(2)), 75 | )), 76 | ) 77 | -------------------------------------------------------------------------------- /tchannel/messages/init_response.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | from .. import rw 24 | from .init_request import InitRequestMessage 25 | from .types import Types 26 | 27 | 28 | class InitResponseMessage(InitRequestMessage): 29 | """Respond to an initialization request message.""" 30 | message_type = Types.INIT_RES 31 | 32 | 33 | init_res_rw = rw.instance( 34 | InitResponseMessage, 35 | ('version', rw.number(2)), # version:2 36 | ('headers', rw.headers( # nh:2 (key~2 value~2){nh} 37 | rw.number(2), 38 | rw.len_prefixed_string(rw.number(2)), 39 | rw.len_prefixed_string(rw.number(2)), 40 | )), 41 | ) 42 | -------------------------------------------------------------------------------- /tchannel/messages/ping_request.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | from .. import rw 24 | from .base import BaseMessage 25 | from .types import Types 26 | 27 | 28 | class PingRequestMessage(BaseMessage): 29 | """Initiate a ping request.""" 30 | message_type = Types.PING_REQ 31 | 32 | 33 | ping_req_rw = rw.instance(PingRequestMessage) # no body 34 | -------------------------------------------------------------------------------- /tchannel/messages/ping_response.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | from .. import rw 24 | from .base import BaseMessage 25 | from .types import Types 26 | 27 | 28 | class PingResponseMessage(BaseMessage): 29 | """Respond to a ping request.""" 30 | message_type = Types.PING_RES 31 | 32 | 33 | ping_res_rw = rw.instance(PingResponseMessage) # no body 34 | -------------------------------------------------------------------------------- /tchannel/messages/types.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | """TChannel type definitions.""" 22 | 23 | 24 | class Types(object): 25 | INIT_REQ = 0x01 26 | INIT_RES = 0x02 27 | 28 | CALL_REQ = 0x03 29 | CALL_RES = 0x04 30 | 31 | CALL_REQ_CONTINUE = 0x13 32 | CALL_RES_CONTINUE = 0x14 33 | 34 | CANCEL = 0xc0 35 | CLAIM = 0xc1 36 | 37 | PING_REQ = 0xd0 38 | PING_RES = 0xd1 39 | 40 | ERROR = 0xff 41 | -------------------------------------------------------------------------------- /tchannel/net.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | import fcntl 24 | import socket 25 | import struct 26 | 27 | 28 | # TODO This module is unix-only. Figure out something for Windows. 29 | 30 | 31 | def interface_ip(interface): 32 | """Determine the IP assigned to us by the given network interface.""" 33 | sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 34 | return socket.inet_ntoa( 35 | fcntl.ioctl( 36 | sock.fileno(), 0x8915, 37 | struct.pack('256s', interface[:15].encode('utf-8')) 38 | )[20:24] 39 | ) 40 | # Explanation: 41 | # http://stackoverflow.com/questions/11735821/python-get-localhost-ip 42 | # http://stackoverflow.com/questions/24196932/how-can-i-get-the-ip-address-of-eth0-in-python 43 | 44 | 45 | def local_ip(): 46 | """Get the local network IP of this machine""" 47 | ip = '127.0.0.1' 48 | try: 49 | ip = socket.gethostbyname(socket.gethostname()) 50 | except socket.gaierror: 51 | try: 52 | ip = socket.gethostbyname(socket.gethostname() + '.local') 53 | except socket.gaierror: 54 | pass 55 | if ip.startswith('127.'): 56 | # Check eth0, eth1, eth2, en0, ... 57 | interfaces = [ 58 | i + str(n) for i in ("eth", "en", "wlan") for n in range(3) 59 | ] # :( 60 | for interface in interfaces: 61 | try: 62 | ip = interface_ip(interface) 63 | break 64 | except IOError: 65 | pass 66 | return ip 67 | -------------------------------------------------------------------------------- /tchannel/peer_strategy.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | import sys 24 | 25 | 26 | class RankCalculator(object): 27 | """RankCalculator calculates the rank of a peer.""" 28 | 29 | def get_rank(self, peer): 30 | raise NotImplementedError() 31 | 32 | 33 | class PreferIncomingCalculator(RankCalculator): 34 | 35 | # TIERS lists three ranges for three different kinds of peers. 36 | # 0: ephemeral peers or unconnected peers 37 | # 1: peers with only outgoing connections 38 | # 2: peers with incoming connections 39 | TIERS = [sys.maxsize, sys.maxsize / 2, 0] 40 | 41 | def get_rank(self, peer): 42 | """Calculate the peer rank based on connections. 43 | 44 | If the peer has no incoming connections, it will have largest rank. 45 | In our peer selection strategy, the largest number has least priority 46 | in the heap. 47 | 48 | If the peer has incoming connections, we will return number of outbound 49 | pending requests and responses. 50 | 51 | :param peer: instance of `tchannel.tornado.peer.Peer` 52 | :return: rank of the peer 53 | """ 54 | if not peer.connections: 55 | return self.TIERS[0] 56 | 57 | if not peer.has_incoming_connections: 58 | return self.TIERS[1] + peer.total_outbound_pendings 59 | 60 | return self.TIERS[2] + peer.total_outbound_pendings 61 | -------------------------------------------------------------------------------- /tchannel/retry.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import ( 22 | absolute_import, division, print_function, unicode_literals 23 | ) 24 | 25 | #: Retry the request on failures to connect to a remote host. This is the 26 | #: default retry behavior. 27 | CONNECTION_ERROR = 'c' 28 | 29 | #: Never retry the request. 30 | NEVER = 'n' 31 | 32 | #: Retry the request on timeouts waiting for a response. 33 | TIMEOUT = 't' 34 | 35 | #: Retry the request on failures to connect and timeouts after connecting. 36 | CONNECTION_ERROR_AND_TIMEOUT = 'ct' 37 | 38 | DEFAULT = CONNECTION_ERROR 39 | 40 | #: The default number of times to retry a request. This is in addition to the 41 | #: original request. 42 | DEFAULT_RETRY_LIMIT = 4 43 | -------------------------------------------------------------------------------- /tchannel/schemes/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import ( 22 | absolute_import, division, print_function, unicode_literals 23 | ) 24 | 25 | RAW = 'raw' 26 | JSON = 'json' 27 | THRIFT = 'thrift' 28 | DEFAULT = RAW 29 | 30 | DEFAULT_NAMES = ( 31 | RAW, 32 | JSON, 33 | THRIFT 34 | ) 35 | 36 | from .raw import RawArgScheme # noqa 37 | from .json import JsonArgScheme # noqa 38 | from .thrift import ThriftArgScheme # noqa 39 | 40 | DEFAULT_SCHEMES = ( 41 | RawArgScheme, 42 | JsonArgScheme, 43 | ThriftArgScheme 44 | ) 45 | 46 | # TODO move constants to schemes/glossary and import here 47 | -------------------------------------------------------------------------------- /tchannel/serializer/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uber/tchannel-python/248741146cb25f32fc3a1a3fabfb13d1d6b62355/tchannel/serializer/__init__.py -------------------------------------------------------------------------------- /tchannel/serializer/json.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | import json 24 | 25 | from tchannel.schemes import JSON 26 | import six 27 | 28 | 29 | class JsonSerializer(object): 30 | name = JSON 31 | 32 | def serialize_header(self, headers): 33 | headers = headers or {} 34 | 35 | for k, v in six.iteritems(headers): 36 | if not (isinstance(k, six.string_types) and 37 | isinstance(v, six.string_types)): 38 | raise ValueError( 39 | 'headers must be a map[string]string (a shallow dict ' 40 | 'where keys and values are strings)' 41 | ) 42 | 43 | return json.dumps(headers) 44 | 45 | def deserialize_header(self, headers): 46 | if not headers: 47 | return {} 48 | if six.PY3 and isinstance(headers, bytes): 49 | headers = headers.decode('utf8') 50 | return json.loads(headers) 51 | 52 | def deserialize_body(self, obj): 53 | if six.PY3 and isinstance(obj, bytes): 54 | obj = obj.decode('utf8') 55 | if not obj: 56 | return {} 57 | return json.loads(obj) 58 | 59 | def serialize_body(self, obj): 60 | if six.PY3 and isinstance(obj, bytes): 61 | obj = obj.decode('utf8') 62 | return json.dumps(obj) 63 | -------------------------------------------------------------------------------- /tchannel/serializer/raw.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | from tchannel.schemes import RAW 24 | 25 | 26 | class RawSerializer(object): 27 | name = RAW 28 | 29 | def deserialize_header(self, obj): 30 | return obj 31 | 32 | def serialize_header(self, obj): 33 | return obj 34 | 35 | def deserialize_body(self, obj): 36 | return obj 37 | 38 | def serialize_body(self, obj): 39 | return obj 40 | -------------------------------------------------------------------------------- /tchannel/singleton.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import ( 22 | absolute_import, division, print_function, unicode_literals 23 | ) 24 | 25 | from threading import local 26 | 27 | from . import TChannel as AsyncTChannel 28 | from .errors import SingletonNotPreparedError 29 | 30 | 31 | class TChannel(object): 32 | """Maintain a single TChannel instance per-thread.""" 33 | 34 | tchannel_cls = AsyncTChannel 35 | 36 | local = local() 37 | local.tchannel = None 38 | 39 | prepared = False 40 | args = None 41 | kwargs = None 42 | 43 | @classmethod 44 | def prepare(cls, *args, **kwargs): 45 | """Set arguments to be used when instantiating a TChannel instance. 46 | 47 | Arguments are the same as :py:meth:`tchannel.TChannel.__init__`. 48 | """ 49 | cls.args = args 50 | cls.kwargs = kwargs 51 | cls.prepared = True 52 | 53 | @classmethod 54 | def reset(cls, *args, **kwargs): 55 | """Undo call to prepare, useful for testing.""" 56 | cls.local.tchannel = None 57 | cls.args = None 58 | cls.kwargs = None 59 | cls.prepared = False 60 | 61 | @classmethod 62 | def get_instance(cls): 63 | """Get a configured, thread-safe, singleton TChannel instance. 64 | 65 | :returns tchannel.TChannel: 66 | """ 67 | if not cls.prepared: 68 | raise SingletonNotPreparedError( 69 | "prepare must be called before get_instance" 70 | ) 71 | 72 | if hasattr(cls.local, 'tchannel') and cls.local.tchannel is not None: 73 | return cls.local.tchannel 74 | 75 | cls.local.tchannel = cls.tchannel_cls(*cls.args, **cls.kwargs) 76 | 77 | return cls.local.tchannel 78 | -------------------------------------------------------------------------------- /tchannel/status.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import ( 22 | absolute_import, division, print_function, unicode_literals 23 | ) 24 | 25 | #: The request succeeded. 26 | OK = 0 27 | 28 | #: The request failed with an application error. 29 | FAILED = 1 30 | -------------------------------------------------------------------------------- /tchannel/sync/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | from .client import TChannel # noqa 24 | -------------------------------------------------------------------------------- /tchannel/sync/singleton.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import ( 22 | absolute_import, division, print_function, unicode_literals 23 | ) 24 | 25 | from threading import local 26 | 27 | from tchannel.singleton import TChannel as TChannelSingleton 28 | from .client import TChannel as SyncTChannel 29 | 30 | 31 | class TChannel(TChannelSingleton): 32 | 33 | tchannel_cls = SyncTChannel 34 | 35 | local = local() 36 | local.tchannel = None 37 | 38 | prepared = False 39 | args = None 40 | kwargs = None 41 | 42 | @classmethod 43 | def get_instance(cls): 44 | """Get a configured, thread-safe, singleton TChannel instance. 45 | 46 | :returns: tchannel.sync.TChannel 47 | """ 48 | return super(TChannel, cls).get_instance() 49 | -------------------------------------------------------------------------------- /tchannel/testing/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | __all__ = ['vcr'] 22 | -------------------------------------------------------------------------------- /tchannel/testing/vcr/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | """ 22 | VCR 23 | === 24 | 25 | ``tchannel.testing.vcr`` provides VCR-like functionality for TChannel. Its 26 | API is heavily inspired by the `vcrpy `_ 27 | library. 28 | 29 | This allows recording TChannel requests and their responses into YAML files 30 | during integration tests and replaying those recorded responses when the tests 31 | are run next time. 32 | 33 | The simplest way to use this is with the :py:func:`use_cassette` function. 34 | 35 | .. autofunction:: use_cassette 36 | 37 | Configuration 38 | ------------- 39 | 40 | .. py:data:: tchannel.testing.vcr.DEFAULT_MATCHERS 41 | 42 | A tuple containing the default list of matchers used by 43 | :py:func:`tchannel.testing.vcr.use_cassette`. 44 | 45 | Record Modes 46 | ~~~~~~~~~~~~ 47 | 48 | .. autoclass:: RecordMode 49 | 50 | """ 51 | 52 | from __future__ import absolute_import 53 | 54 | from .config import use_cassette 55 | from .record_modes import RecordMode 56 | from .cassette import DEFAULT_MATCHERS 57 | 58 | 59 | __all__ = ['use_cassette', 'RecordMode', 'DEFAULT_MATCHERS'] 60 | -------------------------------------------------------------------------------- /tchannel/testing/vcr/exceptions.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | 24 | class VCRError(Exception): 25 | "Base class for exceptions raised by the VCR library." 26 | 27 | 28 | class RequestNotFoundError(VCRError): 29 | "Raised when a request doesn't have a recorded response." 30 | 31 | 32 | class UnsupportedVersionError(VCRError): 33 | "Raised when the version of a recording is not supported." 34 | -------------------------------------------------------------------------------- /tchannel/testing/vcr/proxy.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | import os 23 | import sys 24 | 25 | from tchannel import thrift 26 | 27 | base = os.path.dirname(__file__) 28 | 29 | proxy = thrift.load( 30 | path=os.path.join(base, 'proxy.thrift'), 31 | service='proxy-server', 32 | ) 33 | 34 | sys.modules[__name__] = proxy 35 | -------------------------------------------------------------------------------- /tchannel/testing/vcr/proxy.thrift: -------------------------------------------------------------------------------- 1 | typedef string UUID; 2 | 3 | enum StatusCode { 4 | SUCCESS = 0 5 | FAILURE = 1 6 | } 7 | 8 | enum ArgScheme { 9 | RAW 10 | JSON 11 | THRIFT 12 | } 13 | 14 | struct TransportHeader { 15 | 1: required binary key 16 | 2: required binary value 17 | } 18 | 19 | struct Request { 20 | 1: required string serviceName 21 | 2: required string endpoint 22 | 3: optional binary headers = "" 23 | 4: required binary body 24 | 25 | /** 26 | * Remote hostport to which the request will be sent. 27 | * 28 | * If specified, the request will be made to this host only. 29 | */ 30 | 5: optional binary hostPort = "" 31 | 6: optional ArgScheme argScheme = ArgScheme.RAW 32 | 7: optional list transportHeaders = [] 33 | 34 | /** 35 | * List of known peers of the TChannel at the time the request was made. 36 | * 37 | * This MUST be specified if hostPort wasn't. The request will be sent to 38 | * a random peer from the list. 39 | */ 40 | 8: optional list knownPeers = [] 41 | // TODO: retry flags 42 | // TODO: timeout 43 | // TODO: tracing information 44 | } 45 | 46 | struct Response { 47 | 1: required StatusCode code 48 | 2: optional binary headers = "" 49 | 3: required binary body 50 | } 51 | 52 | /** 53 | * Raised when the record mode for a cassette prevents recording new 54 | * interactions for it. 55 | */ 56 | exception CannotRecordInteractionsError { 57 | 1: optional string message 58 | } 59 | 60 | /** 61 | * Raised when the remote service throws a protocol error. 62 | */ 63 | exception RemoteServiceError { 64 | 1: required byte code 65 | 2: required string message 66 | 3: optional string traceback 67 | } 68 | 69 | /** 70 | * Raised when both, hostPort and knownPeers were empty so the system couldn't 71 | * make a request. 72 | */ 73 | exception NoPeersAvailableError { 74 | 1: required string message 75 | } 76 | 77 | /** 78 | * A generic error for VCR exceptions not covered elsewhere. 79 | */ 80 | exception VCRServiceError { 81 | 1: optional string message 82 | } 83 | 84 | 85 | /** 86 | * The VCRProxy service is responsible for forwarding requests to the remote 87 | * server, recording the interactions, and replaying responses for known 88 | * requests. 89 | */ 90 | service VCRProxy { 91 | /** 92 | * Send the given request through the system. 93 | * 94 | * If the request is known, replay its response. Otherwise, forward it to 95 | * the remote server and return the remote server's response. 96 | */ 97 | Response send( 98 | 1: Request request, 99 | ) throws ( 100 | /** 101 | * Thrown if the request was unrecognized and the record mode for the 102 | * current cassette disallows recording new interactions. 103 | */ 104 | 1: CannotRecordInteractionsError cannotRecord, 105 | 2: RemoteServiceError remoteServiceError, 106 | 3: VCRServiceError serviceError, 107 | 4: NoPeersAvailableError noPeersAvailable, 108 | ); 109 | } 110 | -------------------------------------------------------------------------------- /tchannel/testing/vcr/yaml.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | import yaml 24 | 25 | 26 | def load(s): 27 | return yaml.load(s) 28 | 29 | 30 | def dump(d): 31 | return yaml.dump(d, default_flow_style=False) 32 | 33 | 34 | __all__ = ['load', 'dump'] 35 | -------------------------------------------------------------------------------- /tchannel/thrift/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | from .client import client_for # noqa 24 | from .module import thrift_request_builder # noqa 25 | from .server import register # noqa 26 | from .rw import load # noqa 27 | -------------------------------------------------------------------------------- /tchannel/thrift/reflection.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | import six 24 | import inspect 25 | 26 | 27 | def get_service_methods(iface): 28 | """Get a list of methods defined in the interface for a Thrift service. 29 | 30 | :param iface: 31 | The Thrift-generated Iface class defining the interface for the 32 | service. 33 | :returns: 34 | A set containing names of the methods defined for the service. 35 | """ 36 | 37 | if six.PY3: 38 | methods = [func 39 | for func in dir(iface) 40 | if callable(getattr(iface, 41 | func)) and not func.startswith("__")] 42 | return set(methods) 43 | 44 | methods = inspect.getmembers(iface, predicate=inspect.ismethod) 45 | 46 | return set( 47 | name for (name, method) in methods if not name.startswith('__') 48 | ) 49 | 50 | 51 | def get_module_name(module): 52 | name = module.__name__.rsplit('.', 1)[-1] 53 | 54 | return name 55 | -------------------------------------------------------------------------------- /tchannel/tornado/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | from .tchannel import TChannel # noqa 24 | from .dispatch import RequestDispatcher # noqa 25 | from .request import Request # noqa 26 | from .response import Response # noqa 27 | -------------------------------------------------------------------------------- /tchannel/tornado/util.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | import tornado 23 | import tornado.gen 24 | 25 | from ..errors import TChannelError 26 | 27 | 28 | @tornado.gen.coroutine 29 | def get_arg(context, index): 30 | """get value from arg stream in async way""" 31 | if index < len(context.argstreams): 32 | arg = b"" 33 | chunk = yield context.argstreams[index].read() 34 | while chunk: 35 | arg += chunk 36 | chunk = yield context.argstreams[index].read() 37 | 38 | raise tornado.gen.Return(arg) 39 | else: 40 | raise TChannelError() 41 | -------------------------------------------------------------------------------- /tchannel/transport.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import ( 22 | absolute_import, division, print_function, unicode_literals 23 | ) 24 | 25 | CALLER_NAME = "cn" 26 | CLAIM_AT_START = "cas" 27 | CLAIM_AT_FINISH = "caf" 28 | FAILURE_DOMAIN = "fd" 29 | RETRY_FLAGS = "re" 30 | ROUTING_DELEGATE = "rd" 31 | SCHEME = "as" 32 | SHARD_KEY = "sk" 33 | SPECULATIVE_EXE = "se" 34 | -------------------------------------------------------------------------------- /tchannel/zipkin/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uber/tchannel-python/248741146cb25f32fc3a1a3fabfb13d1d6b62355/tchannel/zipkin/__init__.py -------------------------------------------------------------------------------- /tchannel/zipkin/zipkin_trace.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | from tchannel.event import EventHook 24 | 25 | 26 | class ZipkinTraceHook(EventHook): 27 | """ 28 | .. deprecated:: 0.27.0 29 | Deprecated no-op hook kept only for backwards compatibility. 30 | """ 31 | 32 | DEFAULT_RATE = 0.01 33 | 34 | def __init__(self, tchannel=None, dst=None, sample_rate=None): 35 | pass 36 | 37 | def before_send_request(self, request): 38 | pass 39 | 40 | def before_receive_request(self, request): 41 | pass 42 | 43 | def after_send_response(self, response): 44 | pass 45 | 46 | def after_receive_response(self, request, response): 47 | pass 48 | 49 | def after_receive_error(self, request, error): 50 | pass 51 | 52 | def after_send_error(self, error): 53 | pass 54 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uber/tchannel-python/248741146cb25f32fc3a1a3fabfb13d1d6b62355/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | import pytest 24 | import threadloop 25 | 26 | from .mock_server import MockServer 27 | from .util import get_thrift_service_module 28 | 29 | 30 | class _MockConnection(object): 31 | def __init__(self): 32 | self.buff = bytearray() 33 | self.remote_host = "0.0.0.0" 34 | self.remote_host_port = "0" 35 | self.closed = False 36 | 37 | def write(self, payload, callback=None): 38 | self.buff.extend(payload) 39 | 40 | def getvalue(self): 41 | return self.buff 42 | 43 | def set_outbound_pending_change_callback(self, cb): 44 | pass 45 | 46 | def set_close_callback(self, cb): 47 | pass 48 | 49 | 50 | @pytest.yield_fixture(autouse=True) 51 | def _reduce_ad_jitter(): 52 | from tchannel.tornado import hyperbahn 53 | # For all tests, reduce jitter to 0.5 seconds so they don't take too long 54 | # to run. 55 | original = hyperbahn.DEFAULT_INTERVAL_MAX_JITTER_SECS 56 | try: 57 | hyperbahn.DEFAULT_INTERVAL_MAX_JITTER_SECS = 0.5 58 | yield 59 | finally: 60 | hyperbahn.DEFAULT_INTERVAL_MAX_JITTER_SECS = original 61 | 62 | 63 | @pytest.fixture 64 | def connection(): 65 | """Make a mock connection.""" 66 | return _MockConnection() 67 | 68 | 69 | @pytest.yield_fixture 70 | def mock_server(io_loop): 71 | with MockServer() as server: 72 | yield server 73 | 74 | 75 | @pytest.yield_fixture 76 | def thrift_service(tmpdir): 77 | with get_thrift_service_module(tmpdir, True) as m: 78 | yield m 79 | 80 | 81 | @pytest.yield_fixture 82 | def loop(): 83 | tl = threadloop.ThreadLoop() 84 | tl.start() 85 | yield tl 86 | tl.stop() 87 | -------------------------------------------------------------------------------- /tests/data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uber/tchannel-python/248741146cb25f32fc3a1a3fabfb13d1d6b62355/tests/data/__init__.py -------------------------------------------------------------------------------- /tests/data/generated/ThriftTest/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ['ttypes', 'constants', 'ThriftTest', 'SecondService'] 2 | -------------------------------------------------------------------------------- /tests/data/generated/ThriftTest/constants.py: -------------------------------------------------------------------------------- 1 | # 2 | # Autogenerated by Thrift Compiler (0.12.0) 3 | # 4 | # DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING 5 | # 6 | # options string: py:new_style,slots,dynamic 7 | # 8 | 9 | from thrift.Thrift import TType, TMessageType, TFrozenDict, TException, TApplicationException 10 | from thrift.protocol.TProtocol import TProtocolException 11 | from thrift.TRecursive import fix_spec 12 | 13 | import sys 14 | from .ttypes import * 15 | myNumberz = 1 16 | -------------------------------------------------------------------------------- /tests/data/generated/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uber/tchannel-python/248741146cb25f32fc3a1a3fabfb13d1d6b62355/tests/data/generated/__init__.py -------------------------------------------------------------------------------- /tests/data/hosts.json: -------------------------------------------------------------------------------- 1 | ["127.0.0.1:21300", "127.0.0.1:21301", "127.0.0.1:21302", "127.0.0.1:21303", "127.0.0.1:21304", "127.0.0.1:21305"] -------------------------------------------------------------------------------- /tests/integration/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uber/tchannel-python/248741146cb25f32fc3a1a3fabfb13d1d6b62355/tests/integration/__init__.py -------------------------------------------------------------------------------- /tests/integration/json/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uber/tchannel-python/248741146cb25f32fc3a1a3fabfb13d1d6b62355/tests/integration/json/__init__.py -------------------------------------------------------------------------------- /tests/integration/thrift/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uber/tchannel-python/248741146cb25f32fc3a1a3fabfb13d1d6b62355/tests/integration/thrift/__init__.py -------------------------------------------------------------------------------- /tests/integration/tornado/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uber/tchannel-python/248741146cb25f32fc3a1a3fabfb13d1d6b62355/tests/integration/tornado/__init__.py -------------------------------------------------------------------------------- /tests/messages/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uber/tchannel-python/248741146cb25f32fc3a1a3fabfb13d1d6b62355/tests/messages/__init__.py -------------------------------------------------------------------------------- /tests/messages/test_common.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | import random 24 | 25 | import pytest 26 | 27 | from tchannel.io import BytesIO 28 | from tchannel.messages.common import ChecksumType 29 | from tchannel.messages.common import Tracing 30 | from tchannel.messages.common import checksum_rw 31 | from tchannel.messages.common import tracing_rw 32 | from six.moves import range 33 | 34 | 35 | @pytest.mark.parametrize('typ, value', [ 36 | (ChecksumType.none, None), 37 | (ChecksumType.crc32, 1234), 38 | (ChecksumType.farm32, 5678), 39 | ]) 40 | def test_chucksum_round_trip(typ, value): 41 | buff = checksum_rw.write((typ, value), BytesIO()).getvalue() 42 | assert (typ, value) == checksum_rw.read(BytesIO(buff)) 43 | 44 | 45 | @pytest.mark.parametrize('bs, typ, value', [ 46 | ([0], ChecksumType.none, None), 47 | ([1, 1, 2, 3, 4], ChecksumType.crc32, 16909060), 48 | ([2, 1, 2, 3, 4], ChecksumType.farm32, 16909060), 49 | ]) 50 | def test_checksum_read(bs, typ, value): 51 | assert checksum_rw.read(BytesIO(bytearray(bs))) == (typ, value) 52 | 53 | 54 | def test_tracing_round_trip(): 55 | for i in range(100): 56 | t = Tracing( 57 | random.randint(0, 100000), 58 | random.randint(0, 100000), 59 | random.randint(0, 100000), 60 | random.randint(0, 1), 61 | ) 62 | 63 | buff = tracing_rw.write(t, BytesIO()).getvalue() 64 | assert t == tracing_rw.read(BytesIO(buff)) 65 | 66 | 67 | @pytest.mark.parametrize('tracing, bs', [ 68 | (Tracing(1, 2, 3, 0), [ 69 | 0, 0, 0, 0, 0, 0, 0, 1, # span_id:8 70 | 0, 0, 0, 0, 0, 0, 0, 2, # parent_id:8 71 | 0, 0, 0, 0, 0, 0, 0, 3, # trace_id:8 72 | 0, # traceflags:1 73 | ]) 74 | ]) 75 | def test_tracing_read(tracing, bs): 76 | assert tracing_rw.read(BytesIO(bytearray(bs))) == tracing 77 | -------------------------------------------------------------------------------- /tests/schemes/test_raw.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import ( 22 | absolute_import, division, print_function, unicode_literals 23 | ) 24 | 25 | import pytest 26 | 27 | from tchannel import TChannel, Request, Response, schemes 28 | from tchannel.response import TransportHeaders 29 | 30 | 31 | @pytest.mark.gen_test 32 | @pytest.mark.call 33 | def test_call_should_get_response(): 34 | 35 | # Given this test server: 36 | 37 | server = TChannel(name='server') 38 | 39 | @server.raw.register 40 | def endpoint(request): 41 | 42 | assert isinstance(request, Request) 43 | assert request.headers == b'req headers' 44 | assert request.body == b'req body' 45 | 46 | return Response('resp body', headers='resp headers') 47 | 48 | server.listen() 49 | 50 | # Make a call: 51 | 52 | tchannel = TChannel(name='client') 53 | 54 | resp = yield tchannel.raw( 55 | service='server', 56 | endpoint='endpoint', 57 | headers='req headers', 58 | body='req body', 59 | hostport=server.hostport, 60 | ) 61 | 62 | # verify response 63 | assert isinstance(resp, Response) 64 | assert resp.headers == b'resp headers' 65 | assert resp.body == b'resp body' 66 | 67 | # verify response transport headers 68 | assert isinstance(resp.transport, TransportHeaders) 69 | assert resp.transport.scheme == schemes.RAW 70 | assert resp.transport.failure_domain is None 71 | 72 | 73 | @pytest.mark.gen_test 74 | @pytest.mark.call 75 | def test_register_should_work_with_different_endpoint(): 76 | 77 | # Given this test server: 78 | 79 | server = TChannel(name='server') 80 | 81 | @server.raw.register('foo') 82 | def endpoint(request): 83 | return 'resp body' 84 | 85 | server.listen() 86 | 87 | # Make a call: 88 | 89 | tchannel = TChannel(name='client') 90 | 91 | resp = yield tchannel.raw( 92 | service='server', 93 | endpoint='foo', 94 | hostport=server.hostport, 95 | ) 96 | 97 | assert resp.body == b'resp body' 98 | -------------------------------------------------------------------------------- /tests/serializer/test_serializer_json.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | import pytest 23 | 24 | from tchannel.serializer.json import JsonSerializer 25 | 26 | 27 | @pytest.mark.parametrize('v1', [ 28 | ({}), 29 | ({'a': 'd'}), 30 | ]) 31 | def test_header(v1): 32 | serializer = JsonSerializer() 33 | assert v1 == serializer.deserialize_header( 34 | serializer.serialize_header(v1) 35 | ) 36 | 37 | 38 | @pytest.mark.parametrize('v1, v2', [ 39 | (True, 'true'), 40 | (False, 'false'), 41 | ({}, '{}'), 42 | ({'a': 'd'}, '{"a": "d"}'), 43 | (2, '2'), 44 | (None, 'null'), 45 | ]) 46 | def test_body(v1, v2): 47 | serializer = JsonSerializer() 48 | assert v2 == serializer.serialize_body(v1) 49 | assert v1 == serializer.deserialize_body(v2) 50 | 51 | 52 | def test_exception(): 53 | serializer = JsonSerializer() 54 | 55 | with pytest.raises(ValueError): 56 | serializer.deserialize_header('{sss') 57 | 58 | with pytest.raises(TypeError): 59 | serializer.serialize_body({"sss"}) 60 | 61 | with pytest.raises(ValueError): 62 | serializer.deserialize_body('{sss') 63 | -------------------------------------------------------------------------------- /tests/serializer/test_serializer_raw.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | from tchannel.serializer.raw import RawSerializer 24 | 25 | 26 | def test_header(): 27 | serializer = RawSerializer() 28 | obj = object() 29 | assert obj == serializer.serialize_header(obj) 30 | assert obj == serializer.deserialize_header(obj) 31 | 32 | 33 | def test_body(): 34 | serializer = RawSerializer() 35 | obj = object() 36 | assert obj == serializer.serialize_body(obj) 37 | assert obj == serializer.deserialize_body(obj) 38 | -------------------------------------------------------------------------------- /tests/serializer/test_serializer_thrift.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | import pytest 23 | from tchannel.serializer.thrift import ThriftSerializer 24 | from tests.data.generated.ThriftTest.ThriftTest import ( 25 | testStruct_result, 26 | Xtruct, 27 | ) 28 | 29 | 30 | @pytest.mark.parametrize('v1', [ 31 | ({}), 32 | ({'a': 'd'}), 33 | ]) 34 | def test_header(v1): 35 | serializer = ThriftSerializer(None) 36 | assert v1 == serializer.deserialize_header( 37 | serializer.serialize_header(v1) 38 | ) 39 | 40 | 41 | def test_body(): 42 | result = testStruct_result(Xtruct("s", 0, 1, 2)) 43 | serializer = ThriftSerializer(testStruct_result) 44 | assert result == serializer.deserialize_body( 45 | serializer.serialize_body(result) 46 | ) 47 | -------------------------------------------------------------------------------- /tests/sync/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uber/tchannel-python/248741146cb25f32fc3a1a3fabfb13d1d6b62355/tests/sync/__init__.py -------------------------------------------------------------------------------- /tests/sync/test_singleton.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import ( 22 | absolute_import, division, print_function, unicode_literals 23 | ) 24 | 25 | import pytest 26 | 27 | from tchannel.sync.singleton import TChannel 28 | from tchannel.sync import TChannel as SyncTChannel 29 | from tchannel.singleton import TChannel as AsyncSingleton 30 | from tchannel.errors import SingletonNotPreparedError 31 | 32 | 33 | def test_stored_seperately_from_async_singleton(): 34 | 35 | TChannel.reset() 36 | AsyncSingleton.reset() 37 | 38 | AsyncSingleton.prepare('async-app') 39 | 40 | with pytest.raises(SingletonNotPreparedError): 41 | TChannel.get_instance() 42 | 43 | TChannel.prepare('sync-app') 44 | 45 | instance = TChannel.get_instance() 46 | 47 | assert isinstance(instance, SyncTChannel) 48 | assert AsyncSingleton.get_instance() is not instance 49 | -------------------------------------------------------------------------------- /tests/sync/test_thrift.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | import pytest 24 | 25 | from tchannel import thrift_request_builder 26 | from tchannel.sync import TChannel 27 | 28 | 29 | @pytest.mark.integration 30 | def test_call(mock_server, thrift_service): 31 | 32 | expected = thrift_service.Item( 33 | key='foo', value=thrift_service.Value(integerValue=42) 34 | ) 35 | 36 | mock_server.expect_call( 37 | thrift_service, 38 | 'thrift', 39 | method='getItem', 40 | ).and_result(expected) 41 | 42 | thrift_service = thrift_request_builder( 43 | service='thrift-service', 44 | thrift_module=thrift_service, 45 | hostport=mock_server.hostport, 46 | ) 47 | 48 | tchannel = TChannel('test-client') 49 | 50 | future = tchannel.thrift( 51 | thrift_service.getItem('foo') 52 | ) 53 | result = future.result() 54 | 55 | assert expected == result.body 56 | -------------------------------------------------------------------------------- /tests/test_checksum.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | import mock 23 | 24 | import pytest 25 | 26 | from tchannel import messages 27 | from tchannel import TChannel 28 | from tchannel import thrift 29 | from tchannel.errors import FatalProtocolError 30 | from tchannel.io import BytesIO 31 | from tchannel.messages import CallRequestMessage 32 | from tchannel.messages import ChecksumType 33 | from tchannel.messages.common import generate_checksum 34 | from tchannel.messages.common import verify_checksum 35 | 36 | 37 | @pytest.mark.parametrize('checksum_type', [ 38 | (ChecksumType.none), 39 | (ChecksumType.crc32), 40 | (ChecksumType.crc32c), 41 | ]) 42 | def test_checksum(checksum_type): 43 | message = CallRequestMessage() 44 | message.checksum = (checksum_type, None) 45 | generate_checksum(message) 46 | payload = messages.RW[message.message_type].write( 47 | message, BytesIO() 48 | ).getvalue() 49 | 50 | msg = messages.RW[message.message_type].read(BytesIO(payload)) 51 | assert verify_checksum(msg) 52 | 53 | 54 | @pytest.mark.gen_test 55 | def test_default_checksum_type(): 56 | server = TChannel("server") 57 | server.listen() 58 | with mock.patch( 59 | 'tchannel.messages.common.compute_checksum', autospec=True, 60 | ) as mock_compute_checksum: 61 | client = TChannel("client") 62 | service = thrift.load( 63 | path='tchannel/health/meta.thrift', 64 | service='health_test_server', 65 | hostport=server.hostport, 66 | ) 67 | with pytest.raises(FatalProtocolError): 68 | yield client.thrift(service.Meta.health()) 69 | 70 | mock_compute_checksum.assert_called_with( 71 | ChecksumType.crc32c, mock.ANY, mock.ANY, 72 | ) 73 | -------------------------------------------------------------------------------- /tests/test_context.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | import pytest 24 | from tchannel import context 25 | from tornado import gen 26 | 27 | 28 | @pytest.mark.gen_test 29 | def test_deprecated_context_provider(): 30 | """ 31 | Test that the deprecated RequestContextProvider() can still propagate 32 | the request context across coroutines. 33 | """ 34 | provider = context.RequestContextProvider() 35 | 36 | @gen.coroutine 37 | def _get_context(): 38 | res = provider.get_current_context().parent_tracing 39 | raise gen.Return(res) 40 | 41 | with provider.request_context(parent_tracing='Farnsworth'): 42 | res = _get_context() 43 | 44 | res = yield res 45 | assert res == 'Farnsworth' 46 | -------------------------------------------------------------------------------- /tests/test_frame.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | import pytest 24 | 25 | from tchannel import messages 26 | from tchannel.frame import Frame 27 | from tchannel.frame import FrameHeader 28 | from tchannel.frame import frame_rw 29 | from tchannel.io import BytesIO 30 | from tchannel.messages import PingRequestMessage 31 | from tchannel.messages.types import Types 32 | 33 | 34 | class _FakeMessage(object): 35 | message_type = 0x30 36 | 37 | def serialize(self, out): 38 | """Serialize 0-bytes to ``out``.""" 39 | return 40 | 41 | 42 | @pytest.fixture 43 | def dummy_frame(): 44 | return bytearray([ 45 | 0, 16, # Size 46 | 0, # type 47 | 0, # reserved 48 | 0, 0, 0, 1, # ID 49 | 0, 0, 0, 0, 0, 0, 0, 0 # reserved padding 50 | ]) 51 | 52 | 53 | def test_frame_header_width(): 54 | assert frame_rw.width() == 16 55 | 56 | 57 | def test_empty_payload(connection): 58 | """Verify size is set properly for an empty message.""" 59 | 60 | message_id = 42 61 | 62 | frame = Frame( 63 | header=FrameHeader( 64 | message_id=message_id, 65 | message_type=0x30 66 | ), 67 | payload="" 68 | ) 69 | 70 | frame_rw.write(frame, connection) 71 | assert connection.getvalue() == bytearray([ 72 | 0, 16, # size:2 73 | 0x30, # type:1 74 | 0, # reserved:1 75 | 0, 0, 0, 42, # id:4 76 | 0, 0, 0, 0, 0, 0, 0, 0 # padding:8 77 | ]) 78 | 79 | 80 | def test_decode_empty_buffer(): 81 | """Verify we can parse zero size frame.""" 82 | assert frame_rw.read(BytesIO(b'\x00\x00\x00\x00')) is None 83 | 84 | 85 | def test_decode_with_message_length(dummy_frame): 86 | """Verify we can pre-flight a message size.""" 87 | dummy_frame[2] = Types.PING_REQ 88 | f = frame_rw.read( 89 | BytesIO(dummy_frame[2:]), size=len(dummy_frame) 90 | ) 91 | message_rw = messages.RW[f.header.message_type] 92 | message_rw.read(BytesIO(f.payload)) == PingRequestMessage() 93 | -------------------------------------------------------------------------------- /tests/test_future.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import ( 22 | absolute_import, unicode_literals, division, print_function 23 | ) 24 | 25 | from tornado import gen 26 | 27 | import pytest 28 | 29 | from tchannel._future import fail_to 30 | 31 | 32 | def test_fail_to_no_failure(): 33 | answer = gen.Future() 34 | 35 | @fail_to(answer) 36 | def f(): 37 | return 42 38 | 39 | assert f() == 42 40 | assert answer.running() 41 | 42 | 43 | @pytest.mark.gen_test 44 | def test_fail_to_failure(): 45 | answer = gen.Future() 46 | 47 | @fail_to(answer) 48 | def f(): 49 | raise GreatSadness 50 | 51 | assert f() is None 52 | with pytest.raises(GreatSadness): 53 | yield answer 54 | 55 | 56 | @pytest.mark.gen_test 57 | @pytest.mark.gen_test 58 | def test_fail_to_failure_in_coroutine(): 59 | answer = gen.Future() 60 | 61 | @fail_to(answer) 62 | @gen.coroutine 63 | def f(): 64 | raise GreatSadness 65 | 66 | with pytest.raises(GreatSadness): 67 | yield f() 68 | assert answer.running() 69 | 70 | 71 | class GreatSadness(Exception): 72 | pass 73 | -------------------------------------------------------------------------------- /tests/test_health.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | import pytest 24 | 25 | from tchannel import TChannel, thrift 26 | from tchannel.health import Meta 27 | from tchannel.health import HealthStatus 28 | 29 | 30 | @pytest.mark.gen_test 31 | def test_default_health(): 32 | server = TChannel("health_test_server") 33 | server.listen() 34 | 35 | client = TChannel("health_test_client") 36 | 37 | service = thrift.load( 38 | path='tchannel/health/meta.thrift', 39 | service='health_test_server', 40 | hostport=server.hostport, 41 | ) 42 | 43 | resp = yield client.thrift(service.Meta.health()) 44 | assert resp.body.ok is True 45 | assert resp.body.message is None 46 | 47 | 48 | @pytest.mark.gen_test 49 | def test_user_health(): 50 | server = TChannel("health_test_server") 51 | 52 | @server.thrift.register(Meta, method="health") 53 | def user_health(request): 54 | return HealthStatus(ok=False, message="from me") 55 | 56 | server.listen() 57 | 58 | client = TChannel("health_test_client") 59 | service = thrift.load( 60 | path='tchannel/health/meta.thrift', 61 | service='health_test_server', 62 | hostport=server.hostport, 63 | ) 64 | 65 | resp = yield client.thrift(service.Meta.health()) 66 | assert resp.body.ok is False 67 | assert resp.body.message == "from me" 68 | -------------------------------------------------------------------------------- /tests/test_peer_strategy.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | import sys 24 | 25 | import pytest 26 | from tchannel import TChannel 27 | from tchannel.peer_strategy import PreferIncomingCalculator 28 | from tchannel.tornado.connection import TornadoConnection 29 | from tchannel.tornado.connection import INCOMING 30 | from tchannel.tornado.peer import Peer 31 | 32 | 33 | @pytest.mark.gen_test 34 | def test_get_rank_no_connection(): 35 | server = TChannel('server') 36 | server.listen() 37 | peer = Peer(TChannel('test'), '10.10.101.21:230') 38 | calculator = PreferIncomingCalculator() 39 | assert sys.maxsize == calculator.get_rank(peer) 40 | 41 | 42 | @pytest.mark.gen_test 43 | def test_get_rank_with_outgoing(): 44 | server = TChannel('server') 45 | server.listen() 46 | connection = yield TornadoConnection.outgoing(server.hostport) 47 | 48 | peer = Peer(TChannel('test'), '10.10.101.21:230') 49 | calculator = PreferIncomingCalculator() 50 | peer.register_outgoing_conn(connection) 51 | assert PreferIncomingCalculator.TIERS[1] == calculator.get_rank(peer) 52 | 53 | 54 | @pytest.mark.gen_test 55 | def test_get_rank_with_imcoming(): 56 | server = TChannel('server') 57 | server.listen() 58 | connection = yield TornadoConnection.outgoing(server.hostport) 59 | connection.direction = INCOMING 60 | peer = Peer(TChannel('test'), '10.10.101.21:230') 61 | calculator = PreferIncomingCalculator() 62 | peer.register_incoming_conn(connection) 63 | assert sys.maxsize != calculator.get_rank(peer) 64 | -------------------------------------------------------------------------------- /tests/test_singleton.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import ( 22 | absolute_import, division, print_function, unicode_literals 23 | ) 24 | 25 | import pytest 26 | 27 | from tchannel import TChannel as AsyncTChannel 28 | from tchannel.singleton import TChannel 29 | from tchannel.errors import SingletonNotPreparedError 30 | 31 | 32 | def test_get_instance_is_singleton(): 33 | 34 | TChannel.reset() 35 | TChannel.prepare('my-app') 36 | 37 | assert TChannel.get_instance() is TChannel.get_instance() 38 | 39 | 40 | def test_must_call_prepare_before_get_instance(): 41 | 42 | TChannel.reset() 43 | 44 | with pytest.raises(SingletonNotPreparedError): 45 | TChannel.get_instance() 46 | 47 | 48 | def test_get_instance_returns_configured_tchannel(): 49 | 50 | TChannel.reset() 51 | TChannel.prepare('my-app') 52 | 53 | tchannel = TChannel.get_instance() 54 | 55 | assert isinstance(tchannel, AsyncTChannel) 56 | assert tchannel.name == 'my-app' 57 | -------------------------------------------------------------------------------- /tests/test_stream.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | import os 24 | 25 | import pytest 26 | 27 | from tchannel.errors import UnexpectedError 28 | from tchannel.errors import TChannelError 29 | from tchannel.tornado import Response 30 | from tchannel.tornado.stream import InMemStream 31 | from tchannel.tornado.stream import PipeStream 32 | 33 | 34 | @pytest.mark.gen_test 35 | def test_InMemStream(): 36 | stream = InMemStream() 37 | yield stream.write("1") 38 | yield stream.write("2") 39 | buf = yield stream.read() 40 | assert buf == b"12" 41 | 42 | yield stream.write("3") 43 | buf = yield stream.read() 44 | assert buf == b"3" 45 | 46 | # check internal stream buffer. 47 | assert len(stream._stream) == 0 48 | 49 | stream.close() 50 | with pytest.raises(UnexpectedError): 51 | yield stream.write("4") 52 | 53 | 54 | @pytest.mark.gen_test 55 | def test_PipeStream(): 56 | r, w = os.pipe() 57 | stream = PipeStream(r, w, auto_close=True) 58 | yield stream.write(b"1") 59 | yield stream.write(b"2") 60 | buf = yield stream.read() 61 | assert buf == b"12" 62 | 63 | yield stream.write(b"3") 64 | buf = yield stream.read() 65 | assert buf == b"3" 66 | 67 | stream.close() 68 | with pytest.raises(UnexpectedError): 69 | yield stream.write(b"4") 70 | 71 | 72 | @pytest.mark.gen_test 73 | def test_response_exception(): 74 | resp = Response() 75 | yield resp.write_body("aaa") 76 | 77 | with pytest.raises(UnexpectedError): 78 | yield resp.write_header("aaa") 79 | 80 | resp.flush() 81 | with pytest.raises(TChannelError): 82 | yield resp.write_body("aaaa") 83 | 84 | 85 | @pytest.mark.gen_test 86 | def test_error_during_stream(io_loop): 87 | stream = InMemStream() 88 | try: 89 | 1 / 0 90 | except Exception as e: 91 | stream.set_exception(e) 92 | 93 | with pytest.raises(ZeroDivisionError): 94 | yield stream.read() 95 | 96 | with pytest.raises(ZeroDivisionError): 97 | yield stream.write("a") 98 | -------------------------------------------------------------------------------- /tests/test_types.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | from tchannel.messages.types import Types 24 | 25 | 26 | def test_type_sanity(): 27 | """Simple check to make sure types are importable.""" 28 | assert Types.INIT_REQ == 1 29 | -------------------------------------------------------------------------------- /tests/testing/vcr/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uber/tchannel-python/248741146cb25f32fc3a1a3fabfb13d1d6b62355/tests/testing/vcr/__init__.py -------------------------------------------------------------------------------- /tests/testing/vcr/integration/data/old_with_tracing.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | argScheme: 1 4 | body: '"world"' 5 | endpoint: !!python/unicode 'hello' 6 | headers: '{"$tracing$uber-trace-id": "ca18ff55ca2d44ac:ca18ff55ca2d44ac:0:1"}' 7 | hostPort: localhost:54324 8 | knownPeers: [] 9 | serviceName: !!python/unicode 'hello_service' 10 | transportHeaders: 11 | - key: as 12 | value: json 13 | - key: cn 14 | value: client 15 | response: 16 | body: '"world"' 17 | code: 0 18 | headers: '{}' 19 | version: 1 20 | -------------------------------------------------------------------------------- /tests/testing/vcr/integration/data/old_without_tracing.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | argScheme: 1 4 | body: '"world"' 5 | endpoint: !!python/unicode 'hello' 6 | headers: '{}' 7 | hostPort: localhost:58493 8 | knownPeers: [] 9 | serviceName: !!python/unicode 'hello_service' 10 | transportHeaders: 11 | - key: as 12 | value: json 13 | - key: cn 14 | value: client 15 | response: 16 | body: '"world"' 17 | code: 0 18 | headers: '{}' 19 | version: 1 20 | -------------------------------------------------------------------------------- /tests/testing/vcr/strategies.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | from hypothesis.strategies import ( 24 | binary, 25 | builds, 26 | lists, 27 | sampled_from, 28 | text, 29 | ) 30 | 31 | from tchannel.testing.vcr import proxy 32 | 33 | arg_schemes = sampled_from(proxy.ArgScheme.values) 34 | 35 | transport_headers = builds( 36 | proxy.TransportHeader, 37 | key=binary(), 38 | value=binary(), 39 | ) 40 | 41 | 42 | requests = builds( 43 | proxy.Request, 44 | serviceName=text(), 45 | hostPort=sampled_from(('localhost', '')), 46 | endpoint=text(min_size=1), 47 | headers=binary(), 48 | body=binary(), 49 | argScheme=arg_schemes, 50 | transportHeaders=lists(transport_headers), 51 | ) 52 | 53 | 54 | responses = builds( 55 | proxy.Response, 56 | code=sampled_from(proxy.StatusCode.values), 57 | headers=binary(), 58 | body=binary(), 59 | ) 60 | -------------------------------------------------------------------------------- /tests/testing/vcr/test_patcher.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | from tchannel.tornado import TChannel 24 | from tchannel.testing.vcr.patch import Patcher 25 | from tchannel.testing.vcr.patch import PatchedClientOperation 26 | 27 | 28 | def test_patching_as_context_manager(): 29 | chan = TChannel('client') 30 | with Patcher('localhost:4040'): 31 | ops = chan.request(service='foo') 32 | assert isinstance(ops, PatchedClientOperation) 33 | assert ops.vcr_hostport == 'localhost:4040' 34 | 35 | 36 | def test_patching_as_decorator(): 37 | chan = TChannel('client') 38 | 39 | @Patcher('localhost:4040') 40 | def f(): 41 | ops = chan.request(service='foo') 42 | assert isinstance(ops, PatchedClientOperation) 43 | assert ops.vcr_hostport == 'localhost:4040' 44 | 45 | f() 46 | -------------------------------------------------------------------------------- /tests/thrift/test_module.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | from __future__ import division 23 | from __future__ import print_function 24 | from __future__ import unicode_literals 25 | 26 | import inspect 27 | 28 | import pytest 29 | import six 30 | 31 | from tchannel import thrift_request_builder 32 | from tchannel.thrift.module import ThriftRequest 33 | from tchannel.thrift.module import ThriftRequestMaker 34 | from tests.data.generated.ThriftTest import ThriftTest 35 | 36 | 37 | @pytest.mark.skipif(six.PY3, reason='Deprecated') 38 | @pytest.mark.call 39 | def test_from_thrift_class_should_return_request_maker(): 40 | 41 | maker = thrift_request_builder('thrift_test', ThriftTest) 42 | 43 | assert isinstance(maker, ThriftRequestMaker) 44 | 45 | 46 | @pytest.mark.skipif(six.PY3, reason='Deprecated') 47 | @pytest.mark.call 48 | def test_maker_should_have_thrift_iface_methods(): 49 | 50 | maker = thrift_request_builder('thrift_test', ThriftTest) 51 | 52 | # extract list of maker methods 53 | maker_methods = [ 54 | m[0] for m in 55 | inspect.getmembers(maker, predicate=inspect.ismethod) 56 | ] 57 | 58 | # extract list of iface methods 59 | iface_methods = [ 60 | m[0] for m in 61 | inspect.getmembers(ThriftTest.Iface, predicate=inspect.ismethod) 62 | ] 63 | 64 | # verify all of iface_methods exist in maker_methods 65 | assert set(iface_methods) < set(maker_methods) 66 | 67 | 68 | @pytest.mark.skipif(six.PY3, reason='Deprecated') 69 | @pytest.mark.call 70 | def test_request_maker_should_return_request(): 71 | 72 | maker = thrift_request_builder('thrift_test', ThriftTest) 73 | 74 | request = maker.testString('hi') 75 | 76 | assert isinstance(request, ThriftRequest) 77 | assert request.service == 'thrift_test' 78 | assert request.endpoint == 'ThriftTest::testString' 79 | assert request.result_type == ThriftTest.testString_result 80 | assert request.call_args == ThriftTest.testString_args(thing='hi') 81 | -------------------------------------------------------------------------------- /tests/thrift/test_multiple_services.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import ( 22 | absolute_import, print_function, unicode_literals, division 23 | ) 24 | 25 | import pytest 26 | 27 | from tchannel import TChannel, thrift 28 | 29 | 30 | @pytest.mark.gen_test 31 | def test_inherited_method_names(tmpdir): 32 | thrift_file = tmpdir.join('service.thrift') 33 | thrift_file.write(''' 34 | service Base { string hello() } 35 | service Foo extends Base {} 36 | service Bar extends Base {} 37 | ''') 38 | 39 | service = thrift.load(str(thrift_file), 'myservice') 40 | 41 | server = TChannel('server') 42 | 43 | @server.thrift.register(service.Foo, method='hello') 44 | def foo_hello(request): 45 | return 'foo' 46 | 47 | @server.thrift.register(service.Bar, method='hello') 48 | def bar_hello(request): 49 | return 'bar' 50 | 51 | server.listen() 52 | 53 | client = TChannel('client') 54 | 55 | res = yield client.thrift(service.Foo.hello(), hostport=server.hostport) 56 | assert res.body == 'foo' 57 | 58 | res = yield client.thrift(service.Bar.hello(), hostport=server.hostport) 59 | assert res.body == 'bar' 60 | -------------------------------------------------------------------------------- /tests/thrift/test_reflection.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | from tchannel.thrift.reflection import get_service_methods 24 | 25 | 26 | def test_get_service_methods(): 27 | 28 | class Iface(object): 29 | 30 | def __init__(self): 31 | pass 32 | 33 | def hello(self): 34 | pass 35 | 36 | def world(self, foo): 37 | pass 38 | 39 | assert set(['hello', 'world']) == get_service_methods(Iface) 40 | -------------------------------------------------------------------------------- /tests/tornado/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uber/tchannel-python/248741146cb25f32fc3a1a3fabfb13d1d6b62355/tests/tornado/__init__.py -------------------------------------------------------------------------------- /tests/tornado/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | import socket 24 | 25 | import pytest 26 | import tornado.iostream 27 | 28 | from tchannel.tornado.connection import StreamConnection 29 | 30 | 31 | @pytest.yield_fixture 32 | def tornado_pair(io_loop): 33 | server, client = socket.socketpair() 34 | 35 | server_stream = tornado.iostream.IOStream(server) 36 | client_stream = tornado.iostream.IOStream(client) 37 | 38 | server_conn = StreamConnection(server_stream) 39 | client_conn = StreamConnection(client_stream) 40 | 41 | try: 42 | yield server_conn, client_conn 43 | finally: 44 | server_stream.close() 45 | client_stream.close() 46 | -------------------------------------------------------------------------------- /tests/tornado/test_request.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | from tchannel.messages.common import FlagsType 24 | from tchannel.tornado.request import Request 25 | from tchannel.tornado.request import TransportMetadata 26 | 27 | 28 | def test_transport_metadata_creation(): 29 | request = Request( 30 | id=42, 31 | flags=FlagsType.fragment, 32 | ttl=100, 33 | service='some_service', 34 | headers={'cn': 'another_service', 'as': 'thrift'} 35 | ) 36 | 37 | meta = TransportMetadata.from_request(request) 38 | assert 42 == meta.id 39 | assert FlagsType.fragment == meta.flags 40 | assert 100 == meta.ttl 41 | assert 'some_service' == meta.service 42 | assert {'cn': 'another_service', 'as': 'thrift'} == meta.headers 43 | -------------------------------------------------------------------------------- /tests/tornado/test_tchannel.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import absolute_import 22 | 23 | import pytest 24 | import socket 25 | 26 | from tchannel.errors import AlreadyListeningError 27 | from tchannel.tornado import TChannel 28 | from six.moves import range 29 | 30 | 31 | @pytest.fixture 32 | def tchannel(): 33 | return TChannel(name='test') 34 | 35 | 36 | @pytest.mark.gen_test 37 | def test_peer_caching(tchannel): 38 | "Connections are long-lived and should not be recreated.""" 39 | peer = tchannel.peers.get("localhost:4040") 40 | assert tchannel.peers.get("localhost:4040") is peer 41 | 42 | 43 | def test_known_peers(): 44 | peers = ["localhost:%d" % port for port in range(4040, 4101)] 45 | tchannel = TChannel('test', known_peers=peers) 46 | 47 | for peer in peers: 48 | assert tchannel.peers.lookup(peer) 49 | 50 | 51 | def test_is_listening_should_return_false_when_listen_not_called(tchannel): 52 | 53 | assert tchannel.is_listening() is False 54 | 55 | 56 | def test_is_listening_should_return_true_when_listen_called(tchannel): 57 | 58 | tchannel.listen() 59 | 60 | assert tchannel.is_listening() is True 61 | 62 | 63 | def test_should_error_if_call_listen_twice(tchannel): 64 | 65 | tchannel.listen() 66 | 67 | with pytest.raises(AlreadyListeningError): 68 | tchannel.listen() 69 | 70 | 71 | def test_close_stops_listening(): 72 | server = TChannel(name='server') 73 | server.listen() 74 | 75 | host = server.host 76 | port = server.port 77 | 78 | # Can connect 79 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 80 | sock.connect((host, port)) 81 | sock.close() 82 | 83 | server.close() 84 | 85 | # Can't connect 86 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 87 | 88 | with pytest.raises(socket.error): 89 | sock.connect((host, port)) 90 | -------------------------------------------------------------------------------- /tests/tornado/test_tombstone.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from __future__ import ( 22 | absolute_import, unicode_literals, print_function, division 23 | ) 24 | 25 | import pytest 26 | from tornado import gen 27 | 28 | from tchannel.tornado.tombstone import Cemetery 29 | 30 | 31 | @pytest.mark.gen_test 32 | def test_add_and_forget(): 33 | cem = Cemetery(ttl_offset_secs=0.01) 34 | cem.add(1, 0.01) 35 | cem.add(2, 0.05) 36 | 37 | assert 1 in cem 38 | assert 2 in cem 39 | 40 | yield gen.sleep(0.020) 41 | 42 | assert 1 not in cem 43 | assert 2 in cem 44 | 45 | 46 | @pytest.mark.gen_test 47 | def test_add_and_explicit_forget(): 48 | cem = Cemetery(ttl_offset_secs=0.01) 49 | cem.add(1, 0.05) 50 | 51 | yield gen.sleep(0.04) 52 | 53 | assert 1 in cem 54 | cem.forget(1) 55 | assert 1 not in cem 56 | 57 | 58 | @pytest.mark.gen_test 59 | def test_max_ttl(): 60 | cem = Cemetery(max_ttl_secs=0.05) 61 | cem.add(1, 0.2) 62 | 63 | assert 1 in cem 64 | yield gen.sleep(0.05) 65 | assert 1 not in cem 66 | 67 | 68 | @pytest.mark.gen_test 69 | def test_clear(): 70 | cem = Cemetery() 71 | cem.add(1, 0.1) 72 | cem.add(2, 0.2) 73 | 74 | assert 1 in cem 75 | assert 2 in cem 76 | 77 | cem.clear() 78 | 79 | assert 1 not in cem 80 | assert 2 not in cem 81 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py34,py35,py36,py37,cover,flake8,docs 3 | 4 | 5 | [testenv] 6 | deps = 7 | -rrequirements-test.txt 8 | whitelist_externals = /usr/bin/make 9 | commands = 10 | pytest --cov-report=term-missing --cov tchannel --cov-report=xml -v {posargs} 11 | basepython = 12 | py27: python2.7 13 | py34: python3.4 14 | py35: python3.5 15 | py36: python3.6 16 | py37: python3.7 17 | pypy: pypy 18 | 19 | 20 | [testenv:flake8] 21 | basepython = python 22 | commands = make lint 23 | 24 | 25 | [testenv:cover] 26 | basepython = python 27 | commands = 28 | py.test --cov tchannel --cov-report=xml --cov-report=term-missing {posargs} 29 | 30 | [testenv:benchmark] 31 | basepython = python 32 | commands = make benchmark 33 | 34 | [testenv:docs] 35 | basepython = python 36 | deps = 37 | futures 38 | -rrequirements-docs.txt 39 | tchannel[vcr] 40 | commands = make docs 41 | --------------------------------------------------------------------------------