aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/python/grpcio
diff options
context:
space:
mode:
authorGravatar Craig Tiller <craig.tiller@gmail.com>2015-08-01 16:21:18 -0700
committerGravatar Craig Tiller <craig.tiller@gmail.com>2015-08-01 16:21:18 -0700
commit42ce225d50534f2bd6e24c47963896f096e1cb5f (patch)
tree62e1611cd5f6d50e7b2924f0bd88ece373870e1a /src/python/grpcio
parent85c2a08ca6e008589f660113de5d8d3b7df74b85 (diff)
parent7716c53a217292f1982d986e4681c1e2b25f4367 (diff)
Merge github.com:grpc/grpc into plucking-hell
Conflicts: Makefile
Diffstat (limited to 'src/python/grpcio')
-rw-r--r--src/python/grpcio/.gitignore8
-rw-r--r--src/python/grpcio/MANIFEST.in3
-rw-r--r--src/python/grpcio/README.rst23
-rw-r--r--src/python/grpcio/commands.py76
-rw-r--r--src/python/grpcio/grpc/__init__.py30
-rw-r--r--src/python/grpcio/grpc/_adapter/.gitignore5
-rw-r--r--src/python/grpcio/grpc/_adapter/__init__.py30
-rw-r--r--src/python/grpcio/grpc/_adapter/_c/module.c61
-rw-r--r--src/python/grpcio/grpc/_adapter/_c/types.c60
-rw-r--r--src/python/grpcio/grpc/_adapter/_c/types.h267
-rw-r--r--src/python/grpcio/grpc/_adapter/_c/types/call.c163
-rw-r--r--src/python/grpcio/grpc/_adapter/_c/types/channel.c134
-rw-r--r--src/python/grpcio/grpc/_adapter/_c/types/client_credentials.c270
-rw-r--r--src/python/grpcio/grpc/_adapter/_c/types/completion_queue.c124
-rw-r--r--src/python/grpcio/grpc/_adapter/_c/types/server.c180
-rw-r--r--src/python/grpcio/grpc/_adapter/_c/types/server_credentials.c138
-rw-r--r--src/python/grpcio/grpc/_adapter/_c/utility.c506
-rw-r--r--src/python/grpcio/grpc/_adapter/_common.py76
-rw-r--r--src/python/grpcio/grpc/_adapter/_intermediary_low.py259
-rw-r--r--src/python/grpcio/grpc/_adapter/_low.py108
-rw-r--r--src/python/grpcio/grpc/_adapter/_types.py368
-rw-r--r--src/python/grpcio/grpc/_adapter/fore.py363
-rw-r--r--src/python/grpcio/grpc/_adapter/rear.py395
-rw-r--r--src/python/grpcio/grpc/_cython/.gitignore7
-rw-r--r--src/python/grpcio/grpc/_cython/README.rst52
-rw-r--r--src/python/grpcio/grpc/_cython/__init__.py28
-rw-r--r--src/python/grpcio/grpc/_cython/_cygrpc/__init__.py28
-rw-r--r--src/python/grpcio/grpc/_cython/_cygrpc/call.pxd37
-rw-r--r--src/python/grpcio/grpc/_cython/_cygrpc/call.pyx82
-rw-r--r--src/python/grpcio/grpc/_cython/_cygrpc/channel.pxd36
-rw-r--r--src/python/grpcio/grpc/_cython/_cygrpc/channel.pyx84
-rw-r--r--src/python/grpcio/grpc/_cython/_cygrpc/completion_queue.pxd39
-rw-r--r--src/python/grpcio/grpc/_cython/_cygrpc/completion_queue.pyx117
-rw-r--r--src/python/grpcio/grpc/_cython/_cygrpc/credentials.pxd45
-rw-r--r--src/python/grpcio/grpc/_cython/_cygrpc/credentials.pyx207
-rw-r--r--src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxd342
-rw-r--r--src/python/grpcio/grpc/_cython/_cygrpc/records.pxd129
-rw-r--r--src/python/grpcio/grpc/_cython/_cygrpc/records.pyx575
-rw-r--r--src/python/grpcio/grpc/_cython/_cygrpc/server.pxd45
-rw-r--r--src/python/grpcio/grpc/_cython/_cygrpc/server.pyx158
-rw-r--r--src/python/grpcio/grpc/_cython/adapter_low.py106
-rw-r--r--src/python/grpcio/grpc/_cython/cygrpc.pyx107
-rw-r--r--src/python/grpcio/grpc/_links/__init__.py30
-rw-r--r--src/python/grpcio/grpc/_links/invocation.py363
-rw-r--r--src/python/grpcio/grpc/_links/service.py402
-rw-r--r--src/python/grpcio/grpc/early_adopter/__init__.py30
-rw-r--r--src/python/grpcio/grpc/early_adopter/implementations.py250
-rw-r--r--src/python/grpcio/grpc/framework/__init__.py30
-rw-r--r--src/python/grpcio/grpc/framework/alpha/__init__.py28
-rw-r--r--src/python/grpcio/grpc/framework/alpha/_face_utilities.py183
-rw-r--r--src/python/grpcio/grpc/framework/alpha/_reexport.py203
-rw-r--r--src/python/grpcio/grpc/framework/alpha/exceptions.py48
-rw-r--r--src/python/grpcio/grpc/framework/alpha/interfaces.py388
-rw-r--r--src/python/grpcio/grpc/framework/alpha/utilities.py269
-rw-r--r--src/python/grpcio/grpc/framework/base/__init__.py30
-rw-r--r--src/python/grpcio/grpc/framework/base/_cancellation.py64
-rw-r--r--src/python/grpcio/grpc/framework/base/_constants.py32
-rw-r--r--src/python/grpcio/grpc/framework/base/_context.py99
-rw-r--r--src/python/grpcio/grpc/framework/base/_emission.py125
-rw-r--r--src/python/grpcio/grpc/framework/base/_ends.py399
-rw-r--r--src/python/grpcio/grpc/framework/base/_expiration.py158
-rw-r--r--src/python/grpcio/grpc/framework/base/_ingestion.py442
-rw-r--r--src/python/grpcio/grpc/framework/base/_interfaces.py271
-rw-r--r--src/python/grpcio/grpc/framework/base/_reception.py399
-rw-r--r--src/python/grpcio/grpc/framework/base/_termination.py204
-rw-r--r--src/python/grpcio/grpc/framework/base/_transmission.py429
-rw-r--r--src/python/grpcio/grpc/framework/base/exceptions.py34
-rw-r--r--src/python/grpcio/grpc/framework/base/implementations.py77
-rw-r--r--src/python/grpcio/grpc/framework/base/in_memory.py108
-rw-r--r--src/python/grpcio/grpc/framework/base/interfaces.py363
-rw-r--r--src/python/grpcio/grpc/framework/base/null.py56
-rw-r--r--src/python/grpcio/grpc/framework/base/util.py94
-rw-r--r--src/python/grpcio/grpc/framework/common/__init__.py30
-rw-r--r--src/python/grpcio/grpc/framework/common/cardinality.py42
-rw-r--r--src/python/grpcio/grpc/framework/common/style.py40
-rw-r--r--src/python/grpcio/grpc/framework/face/__init__.py30
-rw-r--r--src/python/grpcio/grpc/framework/face/_calls.py422
-rw-r--r--src/python/grpcio/grpc/framework/face/_control.py198
-rw-r--r--src/python/grpcio/grpc/framework/face/_service.py187
-rw-r--r--src/python/grpcio/grpc/framework/face/demonstration.py118
-rw-r--r--src/python/grpcio/grpc/framework/face/exceptions.py77
-rw-r--r--src/python/grpcio/grpc/framework/face/implementations.py318
-rw-r--r--src/python/grpcio/grpc/framework/face/interfaces.py640
-rw-r--r--src/python/grpcio/grpc/framework/face/utilities.py177
-rw-r--r--src/python/grpcio/grpc/framework/foundation/__init__.py30
-rw-r--r--src/python/grpcio/grpc/framework/foundation/_timer_future.py228
-rw-r--r--src/python/grpcio/grpc/framework/foundation/abandonment.py38
-rw-r--r--src/python/grpcio/grpc/framework/foundation/activated.py65
-rw-r--r--src/python/grpcio/grpc/framework/foundation/callable_util.py107
-rw-r--r--src/python/grpcio/grpc/framework/foundation/future.py236
-rw-r--r--src/python/grpcio/grpc/framework/foundation/later.py51
-rw-r--r--src/python/grpcio/grpc/framework/foundation/logging_pool.py83
-rw-r--r--src/python/grpcio/grpc/framework/foundation/relay.py175
-rw-r--r--src/python/grpcio/grpc/framework/foundation/stream.py60
-rw-r--r--src/python/grpcio/grpc/framework/foundation/stream_util.py160
-rw-r--r--src/python/grpcio/grpc/framework/interfaces/__init__.py30
-rw-r--r--src/python/grpcio/grpc/framework/interfaces/links/__init__.py30
-rw-r--r--src/python/grpcio/grpc/framework/interfaces/links/links.py124
-rw-r--r--src/python/grpcio/grpc/framework/interfaces/links/utilities.py44
-rw-r--r--src/python/grpcio/requirements.txt3
-rw-r--r--src/python/grpcio/setup.cfg2
-rw-r--r--src/python/grpcio/setup.py112
102 files changed, 15336 insertions, 0 deletions
diff --git a/src/python/grpcio/.gitignore b/src/python/grpcio/.gitignore
new file mode 100644
index 0000000000..efbe1737ba
--- /dev/null
+++ b/src/python/grpcio/.gitignore
@@ -0,0 +1,8 @@
+MANIFEST
+*.egg-info/
+build/
+dist/
+*.egg
+*.egg/
+*.eggs/
+doc/
diff --git a/src/python/grpcio/MANIFEST.in b/src/python/grpcio/MANIFEST.in
new file mode 100644
index 0000000000..9583dc7768
--- /dev/null
+++ b/src/python/grpcio/MANIFEST.in
@@ -0,0 +1,3 @@
+graft grpc
+include commands.py
+include requirements.txt
diff --git a/src/python/grpcio/README.rst b/src/python/grpcio/README.rst
new file mode 100644
index 0000000000..00bdecf56f
--- /dev/null
+++ b/src/python/grpcio/README.rst
@@ -0,0 +1,23 @@
+gRPC Python
+===========
+
+Package for GRPC Python.
+
+Dependencies
+------------
+
+Ensure you have installed the gRPC core. On Mac OS X, install homebrew_. On Linux, install linuxbrew_.
+Run the following command to install gRPC Python.
+
+::
+
+ $ curl -fsSL https://goo.gl/getgrpc | bash -s python
+
+This will download and run the [gRPC install script][] to install grpc core. The script then uses pip to install this package. It also installs the Protocol Buffers compiler (_protoc_) and the gRPC _protoc_ plugin for python.
+
+Otherwise, `install from source`_
+
+.. _`install from source`: https://github.com/grpc/grpc/blob/master/src/python/README.md#building-from-source
+.. _homebrew: http://brew.sh
+.. _linuxbrew: https://github.com/Homebrew/linuxbrew#installation
+.. _`gRPC install script`: https://raw.githubusercontent.com/grpc/homebrew-grpc/master/scripts/install
diff --git a/src/python/grpcio/commands.py b/src/python/grpcio/commands.py
new file mode 100644
index 0000000000..605d9d5612
--- /dev/null
+++ b/src/python/grpcio/commands.py
@@ -0,0 +1,76 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Provides distutils command classes for the GRPC Python setup process."""
+
+import os
+import os.path
+import sys
+
+import setuptools
+
+_CONF_PY_ADDENDUM = """
+extensions.append('sphinx.ext.napoleon')
+napoleon_google_docstring = True
+napoleon_numpy_docstring = True
+
+html_theme = 'sphinx_rtd_theme'
+"""
+
+
+class SphinxDocumentation(setuptools.Command):
+ """Command to generate documentation via sphinx."""
+
+ description = ''
+ user_options = []
+
+ def initialize_options(self):
+ pass
+
+ def finalize_options(self):
+ pass
+
+ def run(self):
+ # We import here to ensure that setup.py has had a chance to install the
+ # relevant package eggs first.
+ import sphinx
+ import sphinx.apidoc
+ metadata = self.distribution.metadata
+ src_dir = os.path.join(
+ os.getcwd(), self.distribution.package_dir['grpc'])
+ sys.path.append(src_dir)
+ sphinx.apidoc.main([
+ '', '--force', '--full', '-H', metadata.name, '-A', metadata.author,
+ '-V', metadata.version, '-R', metadata.version,
+ '-o', os.path.join('doc', 'src'), src_dir])
+ conf_filepath = os.path.join('doc', 'src', 'conf.py')
+ with open(conf_filepath, 'a') as conf_file:
+ conf_file.write(_CONF_PY_ADDENDUM)
+ sphinx.main(['', os.path.join('doc', 'src'), os.path.join('doc', 'build')])
+
diff --git a/src/python/grpcio/grpc/__init__.py b/src/python/grpcio/grpc/__init__.py
new file mode 100644
index 0000000000..7086519106
--- /dev/null
+++ b/src/python/grpcio/grpc/__init__.py
@@ -0,0 +1,30 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
diff --git a/src/python/grpcio/grpc/_adapter/.gitignore b/src/python/grpcio/grpc/_adapter/.gitignore
new file mode 100644
index 0000000000..a6f96cd6db
--- /dev/null
+++ b/src/python/grpcio/grpc/_adapter/.gitignore
@@ -0,0 +1,5 @@
+*.a
+*.so
+*.dll
+*.pyc
+*.pyd
diff --git a/src/python/grpcio/grpc/_adapter/__init__.py b/src/python/grpcio/grpc/_adapter/__init__.py
new file mode 100644
index 0000000000..7086519106
--- /dev/null
+++ b/src/python/grpcio/grpc/_adapter/__init__.py
@@ -0,0 +1,30 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
diff --git a/src/python/grpcio/grpc/_adapter/_c/module.c b/src/python/grpcio/grpc/_adapter/_c/module.c
new file mode 100644
index 0000000000..1f3aedd9d8
--- /dev/null
+++ b/src/python/grpcio/grpc/_adapter/_c/module.c
@@ -0,0 +1,61 @@
+/*
+ *
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include <stdlib.h>
+
+#define PY_SSIZE_T_CLEAN
+#include <Python.h>
+#include <grpc/grpc.h>
+
+#include "grpc/_adapter/_c/types.h"
+
+static PyMethodDef c_methods[] = {
+ {NULL}
+};
+
+PyMODINIT_FUNC init_c(void) {
+ PyObject *module;
+
+ module = Py_InitModule3("_c", c_methods,
+ "Wrappings of C structures and functions.");
+
+ if (pygrpc_module_add_types(module) < 0) {
+ return;
+ }
+
+ /* GRPC maintains an internal counter of how many times it has been
+ initialized and handles multiple pairs of grpc_init()/grpc_shutdown()
+ invocations accordingly. */
+ grpc_init();
+ atexit(&grpc_shutdown);
+}
diff --git a/src/python/grpcio/grpc/_adapter/_c/types.c b/src/python/grpcio/grpc/_adapter/_c/types.c
new file mode 100644
index 0000000000..8855c32ca6
--- /dev/null
+++ b/src/python/grpcio/grpc/_adapter/_c/types.c
@@ -0,0 +1,60 @@
+/*
+ *
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "grpc/_adapter/_c/types.h"
+
+#define PY_SSIZE_T_CLEAN
+#include <Python.h>
+#include <grpc/grpc.h>
+
+int pygrpc_module_add_types(PyObject *module) {
+ int i;
+ PyTypeObject *types[] = {
+ &pygrpc_ClientCredentials_type,
+ &pygrpc_ServerCredentials_type,
+ &pygrpc_CompletionQueue_type,
+ &pygrpc_Call_type,
+ &pygrpc_Channel_type,
+ &pygrpc_Server_type
+ };
+ for (i = 0; i < sizeof(types)/sizeof(PyTypeObject *); ++i) {
+ if (PyType_Ready(types[i]) < 0) {
+ return -1;
+ }
+ }
+ for (i = 0; i < sizeof(types)/sizeof(PyTypeObject *); ++i) {
+ Py_INCREF(types[i]);
+ PyModule_AddObject(module, types[i]->tp_name, (PyObject *)types[i]);
+ }
+ return 0;
+}
diff --git a/src/python/grpcio/grpc/_adapter/_c/types.h b/src/python/grpcio/grpc/_adapter/_c/types.h
new file mode 100644
index 0000000000..4e0da4a28a
--- /dev/null
+++ b/src/python/grpcio/grpc/_adapter/_c/types.h
@@ -0,0 +1,267 @@
+/*
+ *
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef GRPC__ADAPTER__C_TYPES_H_
+#define GRPC__ADAPTER__C_TYPES_H_
+
+#define PY_SSIZE_T_CLEAN
+#include <Python.h>
+#include <grpc/grpc.h>
+#include <grpc/grpc_security.h>
+
+
+/*=========================*/
+/* Client-side credentials */
+/*=========================*/
+
+typedef struct ClientCredentials {
+ PyObject_HEAD
+ grpc_credentials *c_creds;
+} ClientCredentials;
+void pygrpc_ClientCredentials_dealloc(ClientCredentials *self);
+ClientCredentials *pygrpc_ClientCredentials_google_default(
+ PyTypeObject *type, PyObject *ignored);
+ClientCredentials *pygrpc_ClientCredentials_ssl(
+ PyTypeObject *type, PyObject *args, PyObject *kwargs);
+ClientCredentials *pygrpc_ClientCredentials_composite(
+ PyTypeObject *type, PyObject *args, PyObject *kwargs);
+ClientCredentials *pygrpc_ClientCredentials_compute_engine(
+ PyTypeObject *type, PyObject *ignored);
+ClientCredentials *pygrpc_ClientCredentials_service_account(
+ PyTypeObject *type, PyObject *args, PyObject *kwargs);
+ClientCredentials *pygrpc_ClientCredentials_jwt(
+ PyTypeObject *type, PyObject *args, PyObject *kwargs);
+ClientCredentials *pygrpc_ClientCredentials_refresh_token(
+ PyTypeObject *type, PyObject *args, PyObject *kwargs);
+ClientCredentials *pygrpc_ClientCredentials_iam(
+ PyTypeObject *type, PyObject *args, PyObject *kwargs);
+extern PyTypeObject pygrpc_ClientCredentials_type;
+
+
+/*=========================*/
+/* Server-side credentials */
+/*=========================*/
+
+typedef struct ServerCredentials {
+ PyObject_HEAD
+ grpc_server_credentials *c_creds;
+} ServerCredentials;
+void pygrpc_ServerCredentials_dealloc(ServerCredentials *self);
+ServerCredentials *pygrpc_ServerCredentials_ssl(
+ PyTypeObject *type, PyObject *args, PyObject *kwargs);
+extern PyTypeObject pygrpc_ServerCredentials_type;
+
+
+/*==================*/
+/* Completion queue */
+/*==================*/
+
+typedef struct CompletionQueue {
+ PyObject_HEAD
+ grpc_completion_queue *c_cq;
+} CompletionQueue;
+CompletionQueue *pygrpc_CompletionQueue_new(
+ PyTypeObject *type, PyObject *args, PyObject *kwargs);
+void pygrpc_CompletionQueue_dealloc(CompletionQueue *self);
+PyObject *pygrpc_CompletionQueue_next(
+ CompletionQueue *self, PyObject *args, PyObject *kwargs);
+PyObject *pygrpc_CompletionQueue_shutdown(
+ CompletionQueue *self, PyObject *ignored);
+extern PyTypeObject pygrpc_CompletionQueue_type;
+
+
+/*======*/
+/* Call */
+/*======*/
+
+typedef struct Call {
+ PyObject_HEAD
+ grpc_call *c_call;
+ CompletionQueue *cq;
+} Call;
+Call *pygrpc_Call_new_empty(CompletionQueue *cq);
+void pygrpc_Call_dealloc(Call *self);
+PyObject *pygrpc_Call_start_batch(Call *self, PyObject *args, PyObject *kwargs);
+PyObject *pygrpc_Call_cancel(Call *self, PyObject *args, PyObject *kwargs);
+extern PyTypeObject pygrpc_Call_type;
+
+
+/*=========*/
+/* Channel */
+/*=========*/
+
+typedef struct Channel {
+ PyObject_HEAD
+ grpc_channel *c_chan;
+} Channel;
+Channel *pygrpc_Channel_new(
+ PyTypeObject *type, PyObject *args, PyObject *kwargs);
+void pygrpc_Channel_dealloc(Channel *self);
+Call *pygrpc_Channel_create_call(
+ Channel *self, PyObject *args, PyObject *kwargs);
+extern PyTypeObject pygrpc_Channel_type;
+
+
+/*========*/
+/* Server */
+/*========*/
+
+typedef struct Server {
+ PyObject_HEAD
+ grpc_server *c_serv;
+ CompletionQueue *cq;
+} Server;
+Server *pygrpc_Server_new(PyTypeObject *type, PyObject *args, PyObject *kwargs);
+void pygrpc_Server_dealloc(Server *self);
+PyObject *pygrpc_Server_request_call(
+ Server *self, PyObject *args, PyObject *kwargs);
+PyObject *pygrpc_Server_add_http2_port(
+ Server *self, PyObject *args, PyObject *kwargs);
+PyObject *pygrpc_Server_start(Server *self, PyObject *ignored);
+PyObject *pygrpc_Server_shutdown(
+ Server *self, PyObject *args, PyObject *kwargs);
+extern PyTypeObject pygrpc_Server_type;
+
+/*=========*/
+/* Utility */
+/*=========*/
+
+/* Every tag that passes from Python GRPC to GRPC core is of this type. */
+typedef struct pygrpc_tag {
+ PyObject *user_tag;
+ Call *call;
+ grpc_call_details request_call_details;
+ grpc_metadata_array request_metadata;
+ grpc_op *ops;
+ size_t nops;
+ int is_new_call;
+} pygrpc_tag;
+
+/* Construct a tag associated with a batch call. Does not take ownership of the
+ resources in the elements of ops. */
+pygrpc_tag *pygrpc_produce_batch_tag(PyObject *user_tag, Call *call,
+ grpc_op *ops, size_t nops);
+
+
+/* Construct a tag associated with a server request. The calling code should
+ use the appropriate fields of the produced tag in the invocation of
+ grpc_server_request_call. */
+pygrpc_tag *pygrpc_produce_request_tag(PyObject *user_tag, Call *empty_call);
+
+/* Construct a tag associated with a server shutdown. */
+pygrpc_tag *pygrpc_produce_server_shutdown_tag(PyObject *user_tag);
+
+/* Frees all resources owned by the tag and the tag itself. */
+void pygrpc_discard_tag(pygrpc_tag *tag);
+
+/* Consumes an event and its associated tag, providing a Python tuple of the
+ form `(type, tag, call, call_details, results)` (where type is an integer
+ corresponding to a grpc_completion_type, tag is an arbitrary PyObject, call
+ is the call object associated with the event [if any], call_details is a
+ tuple of form `(method, host, deadline)` [if such details are available],
+ and resultd is a list of tuples of form `(type, metadata, message, status,
+ cancelled)` [where type corresponds to a grpc_op_type, metadata is a
+ sequence of 2-sequences of strings, message is a byte string, and status is
+ a 2-tuple of an integer corresponding to grpc_status_code and a string of
+ status details]).
+
+ Frees all resources associated with the event tag. */
+PyObject *pygrpc_consume_event(grpc_event event);
+
+/* Transliterate the Python tuple of form `(type, metadata, message,
+ status)` (where type is an integer corresponding to a grpc_op_type, metadata
+ is a sequence of 2-sequences of strings, message is a byte string, and
+ status is 2-tuple of an integer corresponding to grpc_status_code and a
+ string of status details) to a grpc_op suitable for use in a
+ grpc_call_start_batch invocation. The grpc_op is a 'directory' of resources
+ that must be freed after GRPC core is done with them.
+
+ Calls gpr_malloc (or the appropriate type-specific grpc_*_create function)
+ to populate the appropriate union-discriminated members of the op.
+
+ Returns true on success, false on failure. */
+int pygrpc_produce_op(PyObject *op, grpc_op *result);
+
+/* Discards all resources associated with the passed in op that was produced by
+ pygrpc_produce_op. */
+void pygrpc_discard_op(grpc_op op);
+
+/* Transliterate the grpc_ops (which have been sent through a
+ grpc_call_start_batch invocation and whose corresponding event has appeared
+ on a completion queue) to a Python tuple of form `(type, metadata, message,
+ status, cancelled)` (where type is an integer corresponding to a
+ grpc_op_type, metadata is a sequence of 2-sequences of strings, message is a
+ byte string, and status is 2-tuple of an integer corresponding to
+ grpc_status_code and a string of status details).
+
+ Calls gpr_free (or the appropriate type-specific grpc_*_destroy function) on
+ the appropriate union-discriminated populated members of the ops. */
+PyObject *pygrpc_consume_ops(grpc_op *op, size_t nops);
+
+/* Transliterate from a gpr_timespec to a double (in units of seconds, either
+ from the epoch if interpreted absolutely or as a delta otherwise). */
+double pygrpc_cast_gpr_timespec_to_double(gpr_timespec timespec);
+
+/* Transliterate from a double (in units of seconds from the epoch if
+ interpreted absolutely or as a delta otherwise) to a gpr_timespec. */
+gpr_timespec pygrpc_cast_double_to_gpr_timespec(double seconds);
+
+/* Returns true on success, false on failure. */
+int pygrpc_cast_pyseq_to_send_metadata(
+ PyObject *pyseq, grpc_metadata **metadata, size_t *count);
+/* Returns a metadata array as a Python object on success, else NULL. */
+PyObject *pygrpc_cast_metadata_array_to_pyseq(grpc_metadata_array metadata);
+
+/* Transliterate from a list of python channel arguments (2-tuples of string
+ and string|integer|None) to a grpc_channel_args object. The strings placed
+ in the grpc_channel_args object's grpc_arg elements are views of the Python
+ object. The Python object must live long enough for the grpc_channel_args
+ to be used. Arguments set to None are silently ignored. Returns true on
+ success, false on failure. */
+int pygrpc_produce_channel_args(PyObject *py_args, grpc_channel_args *c_args);
+void pygrpc_discard_channel_args(grpc_channel_args args);
+
+/* Read the bytes from grpc_byte_buffer to a gpr_malloc'd array of bytes;
+ output to result and result_size. */
+void pygrpc_byte_buffer_to_bytes(
+ grpc_byte_buffer *buffer, char **result, size_t *result_size);
+
+
+/*========*/
+/* Module */
+/*========*/
+
+/* Returns 0 on success, -1 on failure. */
+int pygrpc_module_add_types(PyObject *module);
+
+#endif /* GRPC__ADAPTER__C_TYPES_H_ */
diff --git a/src/python/grpcio/grpc/_adapter/_c/types/call.c b/src/python/grpcio/grpc/_adapter/_c/types/call.c
new file mode 100644
index 0000000000..0739070044
--- /dev/null
+++ b/src/python/grpcio/grpc/_adapter/_c/types/call.c
@@ -0,0 +1,163 @@
+/*
+ *
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "grpc/_adapter/_c/types.h"
+
+#define PY_SSIZE_T_CLEAN
+#include <Python.h>
+#include <grpc/grpc.h>
+#include <grpc/support/alloc.h>
+
+
+PyMethodDef pygrpc_Call_methods[] = {
+ {"start_batch", (PyCFunction)pygrpc_Call_start_batch, METH_KEYWORDS, ""},
+ {"cancel", (PyCFunction)pygrpc_Call_cancel, METH_KEYWORDS, ""},
+ {NULL}
+};
+const char pygrpc_Call_doc[] = "See grpc._adapter._types.Call.";
+PyTypeObject pygrpc_Call_type = {
+ PyObject_HEAD_INIT(NULL)
+ 0, /* ob_size */
+ "Call", /* tp_name */
+ sizeof(Call), /* tp_basicsize */
+ 0, /* tp_itemsize */
+ (destructor)pygrpc_Call_dealloc, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ pygrpc_Call_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ pygrpc_Call_methods, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ 0 /* tp_new */
+};
+
+Call *pygrpc_Call_new_empty(CompletionQueue *cq) {
+ Call *call = (Call *)pygrpc_Call_type.tp_alloc(&pygrpc_Call_type, 0);
+ call->c_call = NULL;
+ call->cq = cq;
+ Py_XINCREF(call->cq);
+ return call;
+}
+void pygrpc_Call_dealloc(Call *self) {
+ if (self->c_call) {
+ grpc_call_destroy(self->c_call);
+ }
+ Py_XDECREF(self->cq);
+ self->ob_type->tp_free((PyObject *)self);
+}
+PyObject *pygrpc_Call_start_batch(Call *self, PyObject *args, PyObject *kwargs) {
+ PyObject *op_list;
+ PyObject *user_tag;
+ grpc_op *ops;
+ size_t nops;
+ size_t i;
+ size_t j;
+ pygrpc_tag *tag;
+ grpc_call_error errcode;
+ static char *keywords[] = {"ops", "tag", NULL};
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO:start_batch", keywords,
+ &op_list, &user_tag)) {
+ return NULL;
+ }
+ if (!PyList_Check(op_list)) {
+ PyErr_SetString(PyExc_TypeError, "expected a list of OpArgs");
+ return NULL;
+ }
+ nops = PyList_Size(op_list);
+ ops = gpr_malloc(sizeof(grpc_op) * nops);
+ for (i = 0; i < nops; ++i) {
+ PyObject *item = PyList_GET_ITEM(op_list, i);
+ if (!pygrpc_produce_op(item, &ops[i])) {
+ for (j = 0; j < i; ++j) {
+ pygrpc_discard_op(ops[j]);
+ }
+ return NULL;
+ }
+ }
+ tag = pygrpc_produce_batch_tag(user_tag, self, ops, nops);
+ errcode = grpc_call_start_batch(self->c_call, tag->ops, tag->nops, tag);
+ gpr_free(ops);
+ return PyInt_FromLong(errcode);
+}
+PyObject *pygrpc_Call_cancel(Call *self, PyObject *args, PyObject *kwargs) {
+ PyObject *py_code = NULL;
+ grpc_call_error errcode;
+ int code;
+ char *details = NULL;
+ static char *keywords[] = {"code", "details", NULL};
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|Os:start_batch", keywords,
+ &py_code, &details)) {
+ return NULL;
+ }
+ if (py_code != NULL && details != NULL) {
+ if (!PyInt_Check(py_code)) {
+ PyErr_SetString(PyExc_TypeError, "expected integer code");
+ return NULL;
+ }
+ code = PyInt_AsLong(py_code);
+ errcode = grpc_call_cancel_with_status(self->c_call, code, details);
+ } else if (py_code != NULL || details != NULL) {
+ PyErr_SetString(PyExc_ValueError,
+ "if `code` is specified, so must `details`");
+ return NULL;
+ } else {
+ errcode = grpc_call_cancel(self->c_call);
+ }
+ return PyInt_FromLong(errcode);
+}
diff --git a/src/python/grpcio/grpc/_adapter/_c/types/channel.c b/src/python/grpcio/grpc/_adapter/_c/types/channel.c
new file mode 100644
index 0000000000..feb256cf00
--- /dev/null
+++ b/src/python/grpcio/grpc/_adapter/_c/types/channel.c
@@ -0,0 +1,134 @@
+/*
+ *
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "grpc/_adapter/_c/types.h"
+
+#define PY_SSIZE_T_CLEAN
+#include <Python.h>
+#include <grpc/grpc.h>
+
+
+PyMethodDef pygrpc_Channel_methods[] = {
+ {"create_call", (PyCFunction)pygrpc_Channel_create_call, METH_KEYWORDS, ""},
+ {NULL}
+};
+const char pygrpc_Channel_doc[] = "See grpc._adapter._types.Channel.";
+PyTypeObject pygrpc_Channel_type = {
+ PyObject_HEAD_INIT(NULL)
+ 0, /* ob_size */
+ "Channel", /* tp_name */
+ sizeof(Channel), /* tp_basicsize */
+ 0, /* tp_itemsize */
+ (destructor)pygrpc_Channel_dealloc, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ pygrpc_Channel_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ pygrpc_Channel_methods, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ (newfunc)pygrpc_Channel_new /* tp_new */
+};
+
+Channel *pygrpc_Channel_new(
+ PyTypeObject *type, PyObject *args, PyObject *kwargs) {
+ Channel *self;
+ const char *target;
+ PyObject *py_args;
+ ClientCredentials *creds = NULL;
+ grpc_channel_args c_args;
+ char *keywords[] = {"target", "args", "creds", NULL};
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO|O!:Channel", keywords,
+ &target, &py_args, &pygrpc_ClientCredentials_type, &creds)) {
+ return NULL;
+ }
+ if (!pygrpc_produce_channel_args(py_args, &c_args)) {
+ return NULL;
+ }
+ self = (Channel *)type->tp_alloc(type, 0);
+ if (creds) {
+ self->c_chan = grpc_secure_channel_create(creds->c_creds, target, &c_args);
+ } else {
+ self->c_chan = grpc_insecure_channel_create(target, &c_args);
+ }
+ pygrpc_discard_channel_args(c_args);
+ return self;
+}
+void pygrpc_Channel_dealloc(Channel *self) {
+ grpc_channel_destroy(self->c_chan);
+ self->ob_type->tp_free((PyObject *)self);
+}
+
+Call *pygrpc_Channel_create_call(
+ Channel *self, PyObject *args, PyObject *kwargs) {
+ Call *call;
+ CompletionQueue *cq;
+ const char *method;
+ const char *host;
+ double deadline;
+ char *keywords[] = {"cq", "method", "host", "deadline", NULL};
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!ssd:create_call", keywords,
+ &pygrpc_CompletionQueue_type, &cq, &method, &host, &deadline)) {
+ return NULL;
+ }
+ call = pygrpc_Call_new_empty(cq);
+ call->c_call = grpc_channel_create_call(
+ self->c_chan, cq->c_cq, method, host,
+ pygrpc_cast_double_to_gpr_timespec(deadline));
+ return call;
+}
diff --git a/src/python/grpcio/grpc/_adapter/_c/types/client_credentials.c b/src/python/grpcio/grpc/_adapter/_c/types/client_credentials.c
new file mode 100644
index 0000000000..e314c15324
--- /dev/null
+++ b/src/python/grpcio/grpc/_adapter/_c/types/client_credentials.c
@@ -0,0 +1,270 @@
+/*
+ *
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "grpc/_adapter/_c/types.h"
+
+#define PY_SSIZE_T_CLEAN
+#include <Python.h>
+#include <grpc/grpc.h>
+#include <grpc/grpc_security.h>
+
+
+PyMethodDef pygrpc_ClientCredentials_methods[] = {
+ {"google_default", (PyCFunction)pygrpc_ClientCredentials_google_default,
+ METH_CLASS|METH_NOARGS, ""},
+ {"ssl", (PyCFunction)pygrpc_ClientCredentials_ssl,
+ METH_CLASS|METH_KEYWORDS, ""},
+ {"composite", (PyCFunction)pygrpc_ClientCredentials_composite,
+ METH_CLASS|METH_KEYWORDS, ""},
+ {"compute_engine", (PyCFunction)pygrpc_ClientCredentials_compute_engine,
+ METH_CLASS|METH_NOARGS, ""},
+ {"service_account", (PyCFunction)pygrpc_ClientCredentials_service_account,
+ METH_CLASS|METH_KEYWORDS, ""},
+ {"jwt", (PyCFunction)pygrpc_ClientCredentials_jwt,
+ METH_CLASS|METH_KEYWORDS, ""},
+ {"refresh_token", (PyCFunction)pygrpc_ClientCredentials_refresh_token,
+ METH_CLASS|METH_KEYWORDS, ""},
+ {"iam", (PyCFunction)pygrpc_ClientCredentials_iam,
+ METH_CLASS|METH_KEYWORDS, ""},
+ {NULL}
+};
+const char pygrpc_ClientCredentials_doc[] = "";
+PyTypeObject pygrpc_ClientCredentials_type = {
+ PyObject_HEAD_INIT(NULL)
+ 0, /* ob_size */
+ "ClientCredentials", /* tp_name */
+ sizeof(ClientCredentials), /* tp_basicsize */
+ 0, /* tp_itemsize */
+ (destructor)pygrpc_ClientCredentials_dealloc, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ pygrpc_ClientCredentials_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ pygrpc_ClientCredentials_methods, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ 0 /* tp_new */
+};
+
+void pygrpc_ClientCredentials_dealloc(ClientCredentials *self) {
+ grpc_credentials_release(self->c_creds);
+ self->ob_type->tp_free((PyObject *)self);
+}
+
+ClientCredentials *pygrpc_ClientCredentials_google_default(
+ PyTypeObject *type, PyObject *ignored) {
+ ClientCredentials *self = (ClientCredentials *)type->tp_alloc(type, 0);
+ self->c_creds = grpc_google_default_credentials_create();
+ if (!self->c_creds) {
+ Py_DECREF(self);
+ PyErr_SetString(PyExc_RuntimeError,
+ "couldn't create Google default credentials");
+ return NULL;
+ }
+ return self;
+}
+
+ClientCredentials *pygrpc_ClientCredentials_ssl(
+ PyTypeObject *type, PyObject *args, PyObject *kwargs) {
+ ClientCredentials *self;
+ const char *root_certs;
+ const char *private_key = NULL;
+ const char *cert_chain = NULL;
+ grpc_ssl_pem_key_cert_pair key_cert_pair;
+ static char *keywords[] = {"root_certs", "private_key", "cert_chain", NULL};
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "z|zz:ssl", keywords,
+ &root_certs, &private_key, &cert_chain)) {
+ return NULL;
+ }
+ self = (ClientCredentials *)type->tp_alloc(type, 0);
+ if (private_key && cert_chain) {
+ key_cert_pair.private_key = private_key;
+ key_cert_pair.cert_chain = cert_chain;
+ self->c_creds = grpc_ssl_credentials_create(root_certs, &key_cert_pair);
+ } else {
+ self->c_creds = grpc_ssl_credentials_create(root_certs, NULL);
+ }
+ if (!self->c_creds) {
+ Py_DECREF(self);
+ PyErr_SetString(PyExc_RuntimeError, "couldn't create ssl credentials");
+ return NULL;
+ }
+ return self;
+}
+
+ClientCredentials *pygrpc_ClientCredentials_composite(
+ PyTypeObject *type, PyObject *args, PyObject *kwargs) {
+ ClientCredentials *self;
+ ClientCredentials *creds1;
+ ClientCredentials *creds2;
+ static char *keywords[] = {"creds1", "creds2", NULL};
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!O!:composite", keywords,
+ &pygrpc_ClientCredentials_type, &creds1,
+ &pygrpc_ClientCredentials_type, &creds2)) {
+ return NULL;
+ }
+ self = (ClientCredentials *)type->tp_alloc(type, 0);
+ self->c_creds = grpc_composite_credentials_create(
+ creds1->c_creds, creds2->c_creds);
+ if (!self->c_creds) {
+ Py_DECREF(self);
+ PyErr_SetString(PyExc_RuntimeError, "couldn't create composite credentials");
+ return NULL;
+ }
+ return self;
+}
+
+ClientCredentials *pygrpc_ClientCredentials_compute_engine(
+ PyTypeObject *type, PyObject *ignored) {
+ ClientCredentials *self = (ClientCredentials *)type->tp_alloc(type, 0);
+ self->c_creds = grpc_compute_engine_credentials_create();
+ if (!self->c_creds) {
+ Py_DECREF(self);
+ PyErr_SetString(PyExc_RuntimeError,
+ "couldn't create compute engine credentials");
+ return NULL;
+ }
+ return self;
+}
+
+ClientCredentials *pygrpc_ClientCredentials_service_account(
+ PyTypeObject *type, PyObject *args, PyObject *kwargs) {
+ ClientCredentials *self;
+ const char *json_key;
+ const char *scope;
+ double lifetime;
+ static char *keywords[] = {"json_key", "scope", "token_lifetime", NULL};
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ssd:service_account", keywords,
+ &json_key, &scope, &lifetime)) {
+ return NULL;
+ }
+ self = (ClientCredentials *)type->tp_alloc(type, 0);
+ self->c_creds = grpc_service_account_credentials_create(
+ json_key, scope, pygrpc_cast_double_to_gpr_timespec(lifetime));
+ if (!self->c_creds) {
+ Py_DECREF(self);
+ PyErr_SetString(PyExc_RuntimeError,
+ "couldn't create service account credentials");
+ return NULL;
+ }
+ return self;
+}
+
+/* TODO: Rename this credentials to something like service_account_jwt_access */
+ClientCredentials *pygrpc_ClientCredentials_jwt(
+ PyTypeObject *type, PyObject *args, PyObject *kwargs) {
+ ClientCredentials *self;
+ const char *json_key;
+ double lifetime;
+ static char *keywords[] = {"json_key", "token_lifetime", NULL};
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sd:jwt", keywords,
+ &json_key, &lifetime)) {
+ return NULL;
+ }
+ self = (ClientCredentials *)type->tp_alloc(type, 0);
+ self->c_creds = grpc_service_account_jwt_access_credentials_create(
+ json_key, pygrpc_cast_double_to_gpr_timespec(lifetime));
+ if (!self->c_creds) {
+ Py_DECREF(self);
+ PyErr_SetString(PyExc_RuntimeError, "couldn't create JWT credentials");
+ return NULL;
+ }
+ return self;
+}
+
+ClientCredentials *pygrpc_ClientCredentials_refresh_token(
+ PyTypeObject *type, PyObject *args, PyObject *kwargs) {
+ ClientCredentials *self;
+ const char *json_refresh_token;
+ static char *keywords[] = {"json_refresh_token", NULL};
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s:refresh_token", keywords,
+ &json_refresh_token)) {
+ return NULL;
+ }
+ self = (ClientCredentials *)type->tp_alloc(type, 0);
+ self->c_creds = grpc_refresh_token_credentials_create(json_refresh_token);
+ if (!self->c_creds) {
+ Py_DECREF(self);
+ PyErr_SetString(PyExc_RuntimeError,
+ "couldn't create credentials from refresh token");
+ return NULL;
+ }
+ return self;
+}
+
+ClientCredentials *pygrpc_ClientCredentials_iam(
+ PyTypeObject *type, PyObject *args, PyObject *kwargs) {
+ ClientCredentials *self;
+ const char *authorization_token;
+ const char *authority_selector;
+ static char *keywords[] = {"authorization_token", "authority_selector", NULL};
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ss:iam", keywords,
+ &authorization_token, &authority_selector)) {
+ return NULL;
+ }
+ self = (ClientCredentials *)type->tp_alloc(type, 0);
+ self->c_creds = grpc_iam_credentials_create(authorization_token,
+ authority_selector);
+ if (!self->c_creds) {
+ Py_DECREF(self);
+ PyErr_SetString(PyExc_RuntimeError, "couldn't create IAM credentials");
+ return NULL;
+ }
+ return self;
+}
+
diff --git a/src/python/grpcio/grpc/_adapter/_c/types/completion_queue.c b/src/python/grpcio/grpc/_adapter/_c/types/completion_queue.c
new file mode 100644
index 0000000000..2dd44b6ddd
--- /dev/null
+++ b/src/python/grpcio/grpc/_adapter/_c/types/completion_queue.c
@@ -0,0 +1,124 @@
+/*
+ *
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "grpc/_adapter/_c/types.h"
+
+#define PY_SSIZE_T_CLEAN
+#include <Python.h>
+#include <grpc/grpc.h>
+
+
+PyMethodDef pygrpc_CompletionQueue_methods[] = {
+ {"next", (PyCFunction)pygrpc_CompletionQueue_next, METH_KEYWORDS, ""},
+ {"shutdown", (PyCFunction)pygrpc_CompletionQueue_shutdown, METH_NOARGS, ""},
+ {NULL}
+};
+const char pygrpc_CompletionQueue_doc[] =
+ "See grpc._adapter._types.CompletionQueue.";
+PyTypeObject pygrpc_CompletionQueue_type = {
+ PyObject_HEAD_INIT(NULL)
+ 0, /* ob_size */
+ "CompletionQueue", /* tp_name */
+ sizeof(CompletionQueue), /* tp_basicsize */
+ 0, /* tp_itemsize */
+ (destructor)pygrpc_CompletionQueue_dealloc, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ pygrpc_CompletionQueue_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ pygrpc_CompletionQueue_methods, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ (newfunc)pygrpc_CompletionQueue_new /* tp_new */
+};
+
+CompletionQueue *pygrpc_CompletionQueue_new(
+ PyTypeObject *type, PyObject *args, PyObject *kwargs) {
+ CompletionQueue *self = (CompletionQueue *)type->tp_alloc(type, 0);
+ self->c_cq = grpc_completion_queue_create();
+ return self;
+}
+
+void pygrpc_CompletionQueue_dealloc(CompletionQueue *self) {
+ grpc_completion_queue_destroy(self->c_cq);
+ self->ob_type->tp_free((PyObject *)self);
+}
+
+PyObject *pygrpc_CompletionQueue_next(
+ CompletionQueue *self, PyObject *args, PyObject *kwargs) {
+ double deadline;
+ grpc_event event;
+ PyObject *transliterated_event;
+ static char *keywords[] = {"deadline", NULL};
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "d:next", keywords,
+ &deadline)) {
+ return NULL;
+ }
+ Py_BEGIN_ALLOW_THREADS;
+ event = grpc_completion_queue_next(
+ self->c_cq, pygrpc_cast_double_to_gpr_timespec(deadline));
+ Py_END_ALLOW_THREADS;
+ transliterated_event = pygrpc_consume_event(event);
+ return transliterated_event;
+}
+
+PyObject *pygrpc_CompletionQueue_shutdown(
+ CompletionQueue *self, PyObject *ignored) {
+ grpc_completion_queue_shutdown(self->c_cq);
+ Py_RETURN_NONE;
+}
diff --git a/src/python/grpcio/grpc/_adapter/_c/types/server.c b/src/python/grpcio/grpc/_adapter/_c/types/server.c
new file mode 100644
index 0000000000..2a00f34039
--- /dev/null
+++ b/src/python/grpcio/grpc/_adapter/_c/types/server.c
@@ -0,0 +1,180 @@
+/*
+ *
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "grpc/_adapter/_c/types.h"
+
+#define PY_SSIZE_T_CLEAN
+#include <Python.h>
+#include <grpc/grpc.h>
+
+
+PyMethodDef pygrpc_Server_methods[] = {
+ {"request_call", (PyCFunction)pygrpc_Server_request_call,
+ METH_KEYWORDS, ""},
+ {"add_http2_port", (PyCFunction)pygrpc_Server_add_http2_port,
+ METH_KEYWORDS, ""},
+ {"start", (PyCFunction)pygrpc_Server_start, METH_NOARGS, ""},
+ {"shutdown", (PyCFunction)pygrpc_Server_shutdown, METH_KEYWORDS, ""},
+ {NULL}
+};
+const char pygrpc_Server_doc[] = "See grpc._adapter._types.Server.";
+PyTypeObject pygrpc_Server_type = {
+ PyObject_HEAD_INIT(NULL)
+ 0, /* ob_size */
+ "Server", /* tp_name */
+ sizeof(Server), /* tp_basicsize */
+ 0, /* tp_itemsize */
+ (destructor)pygrpc_Server_dealloc, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ pygrpc_Server_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ pygrpc_Server_methods, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ (newfunc)pygrpc_Server_new /* tp_new */
+};
+
+Server *pygrpc_Server_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) {
+ Server *self;
+ CompletionQueue *cq;
+ PyObject *py_args;
+ grpc_channel_args c_args;
+ char *keywords[] = {"cq", "args", NULL};
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!O:Channel", keywords,
+ &pygrpc_CompletionQueue_type, &cq, &py_args)) {
+ return NULL;
+ }
+ if (!pygrpc_produce_channel_args(py_args, &c_args)) {
+ return NULL;
+ }
+ self = (Server *)type->tp_alloc(type, 0);
+ self->c_serv = grpc_server_create(&c_args);
+ grpc_server_register_completion_queue(self->c_serv, cq->c_cq);
+ pygrpc_discard_channel_args(c_args);
+ self->cq = cq;
+ Py_INCREF(self->cq);
+ return self;
+}
+
+void pygrpc_Server_dealloc(Server *self) {
+ grpc_server_destroy(self->c_serv);
+ Py_XDECREF(self->cq);
+ self->ob_type->tp_free((PyObject *)self);
+}
+
+PyObject *pygrpc_Server_request_call(
+ Server *self, PyObject *args, PyObject *kwargs) {
+ CompletionQueue *cq;
+ PyObject *user_tag;
+ pygrpc_tag *tag;
+ Call *empty_call;
+ grpc_call_error errcode;
+ static char *keywords[] = {"cq", "tag", NULL};
+ if (!PyArg_ParseTupleAndKeywords(
+ args, kwargs, "O!O", keywords,
+ &pygrpc_CompletionQueue_type, &cq, &user_tag)) {
+ return NULL;
+ }
+ empty_call = pygrpc_Call_new_empty(cq);
+ tag = pygrpc_produce_request_tag(user_tag, empty_call);
+ errcode = grpc_server_request_call(
+ self->c_serv, &tag->call->c_call, &tag->request_call_details,
+ &tag->request_metadata, tag->call->cq->c_cq, self->cq->c_cq, tag);
+ Py_DECREF(empty_call);
+ return PyInt_FromLong(errcode);
+}
+
+PyObject *pygrpc_Server_add_http2_port(
+ Server *self, PyObject *args, PyObject *kwargs) {
+ const char *addr;
+ ServerCredentials *creds = NULL;
+ int port;
+ static char *keywords[] = {"addr", "creds", NULL};
+ if (!PyArg_ParseTupleAndKeywords(
+ args, kwargs, "s|O!:add_http2_port", keywords,
+ &addr, &pygrpc_ServerCredentials_type, &creds)) {
+ return NULL;
+ }
+ if (creds) {
+ port = grpc_server_add_secure_http2_port(
+ self->c_serv, addr, creds->c_creds);
+ } else {
+ port = grpc_server_add_http2_port(self->c_serv, addr);
+ }
+ return PyInt_FromLong(port);
+
+}
+
+PyObject *pygrpc_Server_start(Server *self, PyObject *ignored) {
+ grpc_server_start(self->c_serv);
+ Py_RETURN_NONE;
+}
+
+PyObject *pygrpc_Server_shutdown(
+ Server *self, PyObject *args, PyObject *kwargs) {
+ PyObject *user_tag;
+ pygrpc_tag *tag;
+ static char *keywords[] = {"tag", NULL};
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", keywords, &user_tag)) {
+ return NULL;
+ }
+ tag = pygrpc_produce_server_shutdown_tag(user_tag);
+ grpc_server_shutdown_and_notify(self->c_serv, self->cq->c_cq, tag);
+ Py_RETURN_NONE;
+}
diff --git a/src/python/grpcio/grpc/_adapter/_c/types/server_credentials.c b/src/python/grpcio/grpc/_adapter/_c/types/server_credentials.c
new file mode 100644
index 0000000000..f6859b79d7
--- /dev/null
+++ b/src/python/grpcio/grpc/_adapter/_c/types/server_credentials.c
@@ -0,0 +1,138 @@
+/*
+ *
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "grpc/_adapter/_c/types.h"
+
+#define PY_SSIZE_T_CLEAN
+#include <Python.h>
+#include <grpc/grpc.h>
+#include <grpc/grpc_security.h>
+#include <grpc/support/alloc.h>
+
+
+PyMethodDef pygrpc_ServerCredentials_methods[] = {
+ {"ssl", (PyCFunction)pygrpc_ServerCredentials_ssl,
+ METH_CLASS|METH_KEYWORDS, ""},
+ {NULL}
+};
+const char pygrpc_ServerCredentials_doc[] = "";
+PyTypeObject pygrpc_ServerCredentials_type = {
+ PyObject_HEAD_INIT(NULL)
+ 0, /* ob_size */
+ "ServerCredentials", /* tp_name */
+ sizeof(ServerCredentials), /* tp_basicsize */
+ 0, /* tp_itemsize */
+ (destructor)pygrpc_ServerCredentials_dealloc, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ pygrpc_ServerCredentials_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ pygrpc_ServerCredentials_methods, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ 0 /* tp_new */
+};
+
+void pygrpc_ServerCredentials_dealloc(ServerCredentials *self) {
+ grpc_server_credentials_release(self->c_creds);
+ self->ob_type->tp_free((PyObject *)self);
+}
+
+ServerCredentials *pygrpc_ServerCredentials_ssl(
+ PyTypeObject *type, PyObject *args, PyObject *kwargs) {
+ ServerCredentials *self;
+ const char *root_certs;
+ PyObject *py_key_cert_pairs;
+ grpc_ssl_pem_key_cert_pair *key_cert_pairs;
+ size_t num_key_cert_pairs;
+ size_t i;
+ static char *keywords[] = {"root_certs", "key_cert_pairs", NULL};
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "zO:ssl", keywords,
+ &root_certs, &py_key_cert_pairs)) {
+ return NULL;
+ }
+ if (!PyList_Check(py_key_cert_pairs)) {
+ PyErr_SetString(PyExc_TypeError, "expected a list of 2-tuples of strings");
+ return NULL;
+ }
+ num_key_cert_pairs = PyList_Size(py_key_cert_pairs);
+ key_cert_pairs =
+ gpr_malloc(sizeof(grpc_ssl_pem_key_cert_pair) * num_key_cert_pairs);
+ for (i = 0; i < num_key_cert_pairs; ++i) {
+ PyObject *item = PyList_GET_ITEM(py_key_cert_pairs, i);
+ const char *key;
+ const char *cert;
+ if (!PyArg_ParseTuple(item, "zz", &key, &cert)) {
+ gpr_free(key_cert_pairs);
+ PyErr_SetString(PyExc_TypeError,
+ "expected a list of 2-tuples of strings");
+ return NULL;
+ }
+ key_cert_pairs[i].private_key = key;
+ key_cert_pairs[i].cert_chain = cert;
+ }
+
+ self = (ServerCredentials *)type->tp_alloc(type, 0);
+ /* TODO: Add a force_client_auth parameter in the python object and pass it
+ here as the last arg. */
+ self->c_creds = grpc_ssl_server_credentials_create(
+ root_certs, key_cert_pairs, num_key_cert_pairs, 0);
+ gpr_free(key_cert_pairs);
+ return self;
+}
+
diff --git a/src/python/grpcio/grpc/_adapter/_c/utility.c b/src/python/grpcio/grpc/_adapter/_c/utility.c
new file mode 100644
index 0000000000..51f3c9be01
--- /dev/null
+++ b/src/python/grpcio/grpc/_adapter/_c/utility.c
@@ -0,0 +1,506 @@
+/*
+ *
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include <math.h>
+#include <string.h>
+
+#define PY_SSIZE_T_CLEAN
+#include <Python.h>
+#include <grpc/grpc.h>
+#include <grpc/byte_buffer_reader.h>
+#include <grpc/support/alloc.h>
+#include <grpc/support/slice.h>
+#include <grpc/support/time.h>
+#include <grpc/support/string_util.h>
+
+#include "grpc/_adapter/_c/types.h"
+
+pygrpc_tag *pygrpc_produce_batch_tag(
+ PyObject *user_tag, Call *call, grpc_op *ops, size_t nops) {
+ pygrpc_tag *tag = gpr_malloc(sizeof(pygrpc_tag));
+ tag->user_tag = user_tag;
+ Py_XINCREF(tag->user_tag);
+ tag->call = call;
+ Py_XINCREF(tag->call);
+ tag->ops = gpr_malloc(sizeof(grpc_op)*nops);
+ memcpy(tag->ops, ops, sizeof(grpc_op)*nops);
+ tag->nops = nops;
+ grpc_call_details_init(&tag->request_call_details);
+ grpc_metadata_array_init(&tag->request_metadata);
+ tag->is_new_call = 0;
+ return tag;
+}
+
+pygrpc_tag *pygrpc_produce_request_tag(PyObject *user_tag, Call *empty_call) {
+ pygrpc_tag *tag = gpr_malloc(sizeof(pygrpc_tag));
+ tag->user_tag = user_tag;
+ Py_XINCREF(tag->user_tag);
+ tag->call = empty_call;
+ Py_XINCREF(tag->call);
+ tag->ops = NULL;
+ tag->nops = 0;
+ grpc_call_details_init(&tag->request_call_details);
+ grpc_metadata_array_init(&tag->request_metadata);
+ tag->is_new_call = 1;
+ return tag;
+}
+
+pygrpc_tag *pygrpc_produce_server_shutdown_tag(PyObject *user_tag) {
+ pygrpc_tag *tag = gpr_malloc(sizeof(pygrpc_tag));
+ tag->user_tag = user_tag;
+ Py_XINCREF(tag->user_tag);
+ tag->call = NULL;
+ tag->ops = NULL;
+ tag->nops = 0;
+ grpc_call_details_init(&tag->request_call_details);
+ grpc_metadata_array_init(&tag->request_metadata);
+ tag->is_new_call = 0;
+ return tag;
+}
+
+void pygrpc_discard_tag(pygrpc_tag *tag) {
+ if (!tag) {
+ return;
+ }
+ Py_XDECREF(tag->user_tag);
+ Py_XDECREF(tag->call);
+ gpr_free(tag->ops);
+ grpc_call_details_destroy(&tag->request_call_details);
+ grpc_metadata_array_destroy(&tag->request_metadata);
+ gpr_free(tag);
+}
+
+PyObject *pygrpc_consume_event(grpc_event event) {
+ pygrpc_tag *tag;
+ PyObject *result;
+ if (event.type == GRPC_QUEUE_TIMEOUT) {
+ Py_RETURN_NONE;
+ }
+ tag = event.tag;
+ switch (event.type) {
+ case GRPC_QUEUE_SHUTDOWN:
+ result = Py_BuildValue("iOOOOO", GRPC_QUEUE_SHUTDOWN,
+ Py_None, Py_None, Py_None, Py_None, Py_True);
+ break;
+ case GRPC_OP_COMPLETE:
+ if (tag->is_new_call) {
+ result = Py_BuildValue(
+ "iOO(ssd)[(iNOOOO)]O", GRPC_OP_COMPLETE, tag->user_tag, tag->call,
+ tag->request_call_details.method, tag->request_call_details.host,
+ pygrpc_cast_gpr_timespec_to_double(tag->request_call_details.deadline),
+ GRPC_OP_RECV_INITIAL_METADATA,
+ pygrpc_cast_metadata_array_to_pyseq(tag->request_metadata), Py_None,
+ Py_None, Py_None, Py_None,
+ event.success ? Py_True : Py_False);
+ } else {
+ result = Py_BuildValue("iOOONO", GRPC_OP_COMPLETE, tag->user_tag,
+ tag->call ? (PyObject*)tag->call : Py_None, Py_None,
+ pygrpc_consume_ops(tag->ops, tag->nops),
+ event.success ? Py_True : Py_False);
+ }
+ break;
+ default:
+ PyErr_SetString(PyExc_ValueError,
+ "unknown completion type; could not translate event");
+ return NULL;
+ }
+ pygrpc_discard_tag(tag);
+ return result;
+}
+
+int pygrpc_produce_op(PyObject *op, grpc_op *result) {
+ static const int OP_TUPLE_SIZE = 5;
+ static const int STATUS_TUPLE_SIZE = 2;
+ static const int TYPE_INDEX = 0;
+ static const int INITIAL_METADATA_INDEX = 1;
+ static const int TRAILING_METADATA_INDEX = 2;
+ static const int MESSAGE_INDEX = 3;
+ static const int STATUS_INDEX = 4;
+ static const int STATUS_CODE_INDEX = 0;
+ static const int STATUS_DETAILS_INDEX = 1;
+ int type;
+ Py_ssize_t message_size;
+ char *message;
+ char *status_details;
+ gpr_slice message_slice;
+ grpc_op c_op;
+ if (!PyTuple_Check(op)) {
+ PyErr_SetString(PyExc_TypeError, "expected tuple op");
+ return 0;
+ }
+ if (PyTuple_Size(op) != OP_TUPLE_SIZE) {
+ char *buf;
+ gpr_asprintf(&buf, "expected tuple op of length %d", OP_TUPLE_SIZE);
+ PyErr_SetString(PyExc_ValueError, buf);
+ gpr_free(buf);
+ return 0;
+ }
+ type = PyInt_AsLong(PyTuple_GET_ITEM(op, TYPE_INDEX));
+ if (PyErr_Occurred()) {
+ return 0;
+ }
+ c_op.op = type;
+ c_op.flags = 0;
+ switch (type) {
+ case GRPC_OP_SEND_INITIAL_METADATA:
+ if (!pygrpc_cast_pyseq_to_send_metadata(
+ PyTuple_GetItem(op, INITIAL_METADATA_INDEX),
+ &c_op.data.send_initial_metadata.metadata,
+ &c_op.data.send_initial_metadata.count)) {
+ return 0;
+ }
+ break;
+ case GRPC_OP_SEND_MESSAGE:
+ PyString_AsStringAndSize(
+ PyTuple_GET_ITEM(op, MESSAGE_INDEX), &message, &message_size);
+ message_slice = gpr_slice_from_copied_buffer(message, message_size);
+ c_op.data.send_message = grpc_raw_byte_buffer_create(&message_slice, 1);
+ gpr_slice_unref(message_slice);
+ break;
+ case GRPC_OP_SEND_CLOSE_FROM_CLIENT:
+ /* Don't need to fill in any other fields. */
+ break;
+ case GRPC_OP_SEND_STATUS_FROM_SERVER:
+ if (!pygrpc_cast_pyseq_to_send_metadata(
+ PyTuple_GetItem(op, TRAILING_METADATA_INDEX),
+ &c_op.data.send_status_from_server.trailing_metadata,
+ &c_op.data.send_status_from_server.trailing_metadata_count)) {
+ return 0;
+ }
+ if (!PyTuple_Check(PyTuple_GET_ITEM(op, STATUS_INDEX))) {
+ char *buf;
+ gpr_asprintf(&buf, "expected tuple status in op of length %d",
+ STATUS_TUPLE_SIZE);
+ PyErr_SetString(PyExc_ValueError, buf);
+ gpr_free(buf);
+ return 0;
+ }
+ c_op.data.send_status_from_server.status = PyInt_AsLong(
+ PyTuple_GET_ITEM(PyTuple_GET_ITEM(op, STATUS_INDEX), STATUS_CODE_INDEX));
+ status_details = PyString_AsString(
+ PyTuple_GET_ITEM(PyTuple_GET_ITEM(op, STATUS_INDEX), STATUS_DETAILS_INDEX));
+ if (PyErr_Occurred()) {
+ return 0;
+ }
+ c_op.data.send_status_from_server.status_details =
+ gpr_malloc(strlen(status_details) + 1);
+ strcpy((char *)c_op.data.send_status_from_server.status_details,
+ status_details);
+ break;
+ case GRPC_OP_RECV_INITIAL_METADATA:
+ c_op.data.recv_initial_metadata = gpr_malloc(sizeof(grpc_metadata_array));
+ grpc_metadata_array_init(c_op.data.recv_initial_metadata);
+ break;
+ case GRPC_OP_RECV_MESSAGE:
+ c_op.data.recv_message = gpr_malloc(sizeof(grpc_byte_buffer *));
+ break;
+ case GRPC_OP_RECV_STATUS_ON_CLIENT:
+ c_op.data.recv_status_on_client.trailing_metadata =
+ gpr_malloc(sizeof(grpc_metadata_array));
+ grpc_metadata_array_init(c_op.data.recv_status_on_client.trailing_metadata);
+ c_op.data.recv_status_on_client.status =
+ gpr_malloc(sizeof(grpc_status_code *));
+ c_op.data.recv_status_on_client.status_details =
+ gpr_malloc(sizeof(char *));
+ *c_op.data.recv_status_on_client.status_details = NULL;
+ c_op.data.recv_status_on_client.status_details_capacity =
+ gpr_malloc(sizeof(size_t));
+ *c_op.data.recv_status_on_client.status_details_capacity = 0;
+ break;
+ case GRPC_OP_RECV_CLOSE_ON_SERVER:
+ c_op.data.recv_close_on_server.cancelled = gpr_malloc(sizeof(int));
+ break;
+ default:
+ return 0;
+ }
+ *result = c_op;
+ return 1;
+}
+
+void pygrpc_discard_op(grpc_op op) {
+ size_t i;
+ switch(op.op) {
+ case GRPC_OP_SEND_INITIAL_METADATA:
+ /* Whenever we produce send-metadata, we allocate new strings (to handle
+ arbitrary sequence input as opposed to just lists or just tuples). We
+ thus must free those elements. */
+ for (i = 0; i < op.data.send_initial_metadata.count; ++i) {
+ gpr_free((void *)op.data.send_initial_metadata.metadata[i].key);
+ gpr_free((void *)op.data.send_initial_metadata.metadata[i].value);
+ }
+ gpr_free(op.data.send_initial_metadata.metadata);
+ break;
+ case GRPC_OP_SEND_MESSAGE:
+ grpc_byte_buffer_destroy(op.data.send_message);
+ break;
+ case GRPC_OP_SEND_CLOSE_FROM_CLIENT:
+ /* Don't need to free any fields. */
+ break;
+ case GRPC_OP_SEND_STATUS_FROM_SERVER:
+ /* Whenever we produce send-metadata, we allocate new strings (to handle
+ arbitrary sequence input as opposed to just lists or just tuples). We
+ thus must free those elements. */
+ for (i = 0; i < op.data.send_status_from_server.trailing_metadata_count;
+ ++i) {
+ gpr_free(
+ (void *)op.data.send_status_from_server.trailing_metadata[i].key);
+ gpr_free(
+ (void *)op.data.send_status_from_server.trailing_metadata[i].value);
+ }
+ gpr_free(op.data.send_status_from_server.trailing_metadata);
+ gpr_free((char *)op.data.send_status_from_server.status_details);
+ break;
+ case GRPC_OP_RECV_INITIAL_METADATA:
+ grpc_metadata_array_destroy(op.data.recv_initial_metadata);
+ gpr_free(op.data.recv_initial_metadata);
+ break;
+ case GRPC_OP_RECV_MESSAGE:
+ grpc_byte_buffer_destroy(*op.data.recv_message);
+ gpr_free(op.data.recv_message);
+ break;
+ case GRPC_OP_RECV_STATUS_ON_CLIENT:
+ grpc_metadata_array_destroy(op.data.recv_status_on_client.trailing_metadata);
+ gpr_free(op.data.recv_status_on_client.trailing_metadata);
+ gpr_free(op.data.recv_status_on_client.status);
+ gpr_free(*op.data.recv_status_on_client.status_details);
+ gpr_free(op.data.recv_status_on_client.status_details);
+ gpr_free(op.data.recv_status_on_client.status_details_capacity);
+ break;
+ case GRPC_OP_RECV_CLOSE_ON_SERVER:
+ gpr_free(op.data.recv_close_on_server.cancelled);
+ break;
+ }
+}
+
+PyObject *pygrpc_consume_ops(grpc_op *op, size_t nops) {
+ static const int TYPE_INDEX = 0;
+ static const int INITIAL_METADATA_INDEX = 1;
+ static const int TRAILING_METADATA_INDEX = 2;
+ static const int MESSAGE_INDEX = 3;
+ static const int STATUS_INDEX = 4;
+ static const int CANCELLED_INDEX = 5;
+ static const int OPRESULT_LENGTH = 6;
+ PyObject *list;
+ size_t i;
+ size_t j;
+ char *bytes;
+ size_t bytes_size;
+ PyObject *results = PyList_New(nops);
+ if (!results) {
+ return NULL;
+ }
+ for (i = 0; i < nops; ++i) {
+ PyObject *result = PyTuple_Pack(OPRESULT_LENGTH, Py_None, Py_None, Py_None,
+ Py_None, Py_None, Py_None);
+ PyTuple_SetItem(result, TYPE_INDEX, PyInt_FromLong(op[i].op));
+ switch(op[i].op) {
+ case GRPC_OP_RECV_INITIAL_METADATA:
+ PyTuple_SetItem(result, INITIAL_METADATA_INDEX,
+ list=PyList_New(op[i].data.recv_initial_metadata->count));
+ for (j = 0; j < op[i].data.recv_initial_metadata->count; ++j) {
+ grpc_metadata md = op[i].data.recv_initial_metadata->metadata[j];
+ PyList_SetItem(list, j, Py_BuildValue("ss#", md.key, md.value,
+ (Py_ssize_t)md.value_length));
+ }
+ break;
+ case GRPC_OP_RECV_MESSAGE:
+ if (*op[i].data.recv_message) {
+ pygrpc_byte_buffer_to_bytes(
+ *op[i].data.recv_message, &bytes, &bytes_size);
+ PyTuple_SetItem(result, MESSAGE_INDEX,
+ PyString_FromStringAndSize(bytes, bytes_size));
+ gpr_free(bytes);
+ } else {
+ PyTuple_SetItem(result, MESSAGE_INDEX, Py_BuildValue(""));
+ }
+ break;
+ case GRPC_OP_RECV_STATUS_ON_CLIENT:
+ PyTuple_SetItem(
+ result, TRAILING_METADATA_INDEX,
+ list = PyList_New(op[i].data.recv_status_on_client.trailing_metadata->count));
+ for (j = 0; j < op[i].data.recv_status_on_client.trailing_metadata->count; ++j) {
+ grpc_metadata md =
+ op[i].data.recv_status_on_client.trailing_metadata->metadata[j];
+ PyList_SetItem(list, j, Py_BuildValue("ss#", md.key, md.value,
+ (Py_ssize_t)md.value_length));
+ }
+ PyTuple_SetItem(
+ result, STATUS_INDEX, Py_BuildValue(
+ "is", *op[i].data.recv_status_on_client.status,
+ *op[i].data.recv_status_on_client.status_details));
+ break;
+ case GRPC_OP_RECV_CLOSE_ON_SERVER:
+ PyTuple_SetItem(
+ result, CANCELLED_INDEX,
+ PyBool_FromLong(*op[i].data.recv_close_on_server.cancelled));
+ break;
+ default:
+ break;
+ }
+ pygrpc_discard_op(op[i]);
+ PyList_SetItem(results, i, result);
+ }
+ return results;
+}
+
+double pygrpc_cast_gpr_timespec_to_double(gpr_timespec timespec) {
+ timespec = gpr_convert_clock_type(timespec, GPR_CLOCK_REALTIME);
+ return timespec.tv_sec + 1e-9*timespec.tv_nsec;
+}
+
+/* Because C89 doesn't have a way to check for infinity... */
+static int pygrpc_isinf(double x) {
+ return x * 0 != 0;
+}
+
+gpr_timespec pygrpc_cast_double_to_gpr_timespec(double seconds) {
+ gpr_timespec result;
+ if (pygrpc_isinf(seconds)) {
+ result = seconds > 0.0 ? gpr_inf_future(GPR_CLOCK_REALTIME)
+ : gpr_inf_past(GPR_CLOCK_REALTIME);
+ } else {
+ result.tv_sec = (time_t)seconds;
+ result.tv_nsec = ((seconds - result.tv_sec) * 1e9);
+ result.clock_type = GPR_CLOCK_REALTIME;
+ }
+ return result;
+}
+
+int pygrpc_produce_channel_args(PyObject *py_args, grpc_channel_args *c_args) {
+ size_t num_args = PyList_Size(py_args);
+ size_t i;
+ grpc_channel_args args;
+ args.num_args = num_args;
+ args.args = gpr_malloc(sizeof(grpc_arg) * num_args);
+ for (i = 0; i < args.num_args; ++i) {
+ char *key;
+ PyObject *value;
+ if (!PyArg_ParseTuple(PyList_GetItem(py_args, i), "zO", &key, &value)) {
+ gpr_free(args.args);
+ args.num_args = 0;
+ args.args = NULL;
+ PyErr_SetString(PyExc_TypeError,
+ "expected a list of 2-tuple of str and str|int|None");
+ return 0;
+ }
+ args.args[i].key = key;
+ if (PyInt_Check(value)) {
+ args.args[i].type = GRPC_ARG_INTEGER;
+ args.args[i].value.integer = PyInt_AsLong(value);
+ } else if (PyString_Check(value)) {
+ args.args[i].type = GRPC_ARG_STRING;
+ args.args[i].value.string = PyString_AsString(value);
+ } else if (value == Py_None) {
+ --args.num_args;
+ --i;
+ continue;
+ } else {
+ gpr_free(args.args);
+ args.num_args = 0;
+ args.args = NULL;
+ PyErr_SetString(PyExc_TypeError,
+ "expected a list of 2-tuple of str and str|int|None");
+ return 0;
+ }
+ }
+ *c_args = args;
+ return 1;
+}
+
+void pygrpc_discard_channel_args(grpc_channel_args args) {
+ gpr_free(args.args);
+}
+
+int pygrpc_cast_pyseq_to_send_metadata(
+ PyObject *pyseq, grpc_metadata **metadata, size_t *count) {
+ size_t i;
+ Py_ssize_t value_length;
+ char *key;
+ char *value;
+ if (!PySequence_Check(pyseq)) {
+ return 0;
+ }
+ *count = PySequence_Size(pyseq);
+ *metadata = gpr_malloc(sizeof(grpc_metadata) * *count);
+ for (i = 0; i < *count; ++i) {
+ PyObject *item = PySequence_GetItem(pyseq, i);
+ if (!PyArg_ParseTuple(item, "ss#", &key, &value, &value_length)) {
+ Py_DECREF(item);
+ gpr_free(*metadata);
+ *count = 0;
+ *metadata = NULL;
+ return 0;
+ } else {
+ (*metadata)[i].key = gpr_strdup(key);
+ (*metadata)[i].value = gpr_malloc(value_length);
+ memcpy((void *)(*metadata)[i].value, value, value_length);
+ Py_DECREF(item);
+ }
+ (*metadata)[i].value_length = value_length;
+ }
+ return 1;
+}
+
+PyObject *pygrpc_cast_metadata_array_to_pyseq(grpc_metadata_array metadata) {
+ PyObject *result = PyTuple_New(metadata.count);
+ size_t i;
+ for (i = 0; i < metadata.count; ++i) {
+ PyTuple_SetItem(
+ result, i, Py_BuildValue(
+ "ss#", metadata.metadata[i].key, metadata.metadata[i].value,
+ (Py_ssize_t)metadata.metadata[i].value_length));
+ if (PyErr_Occurred()) {
+ Py_DECREF(result);
+ return NULL;
+ }
+ }
+ return result;
+}
+
+void pygrpc_byte_buffer_to_bytes(
+ grpc_byte_buffer *buffer, char **result, size_t *result_size) {
+ grpc_byte_buffer_reader reader;
+ gpr_slice slice;
+ char *read_result = NULL;
+ size_t size = 0;
+ grpc_byte_buffer_reader_init(&reader, buffer);
+ while (grpc_byte_buffer_reader_next(&reader, &slice)) {
+ read_result = gpr_realloc(read_result, size + GPR_SLICE_LENGTH(slice));
+ memcpy(read_result + size, GPR_SLICE_START_PTR(slice),
+ GPR_SLICE_LENGTH(slice));
+ size = size + GPR_SLICE_LENGTH(slice);
+ gpr_slice_unref(slice);
+ }
+ *result_size = size;
+ *result = read_result;
+}
diff --git a/src/python/grpcio/grpc/_adapter/_common.py b/src/python/grpcio/grpc/_adapter/_common.py
new file mode 100644
index 0000000000..492849f4cb
--- /dev/null
+++ b/src/python/grpcio/grpc/_adapter/_common.py
@@ -0,0 +1,76 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""State used by both invocation-side and service-side code."""
+
+import enum
+
+
+@enum.unique
+class HighWrite(enum.Enum):
+ """The possible categories of high-level write state."""
+
+ OPEN = 'OPEN'
+ CLOSED = 'CLOSED'
+
+
+class WriteState(object):
+ """A description of the state of writing to an RPC.
+
+ Attributes:
+ low: A side-specific value describing the low-level state of writing.
+ high: A HighWrite value describing the high-level state of writing.
+ pending: A list of bytestrings for the RPC waiting to be written to the
+ other side of the RPC.
+ """
+
+ def __init__(self, low, high, pending):
+ self.low = low
+ self.high = high
+ self.pending = pending
+
+
+class CommonRPCState(object):
+ """A description of an RPC's state.
+
+ Attributes:
+ write: A WriteState describing the state of writing to the RPC.
+ sequence_number: The lowest-unused sequence number for use in generating
+ tickets locally describing the progress of the RPC.
+ deserializer: The behavior to be used to deserialize payload bytestreams
+ taken off the wire.
+ serializer: The behavior to be used to serialize payloads to be sent on the
+ wire.
+ """
+
+ def __init__(self, write, sequence_number, deserializer, serializer):
+ self.write = write
+ self.sequence_number = sequence_number
+ self.deserializer = deserializer
+ self.serializer = serializer
diff --git a/src/python/grpcio/grpc/_adapter/_intermediary_low.py b/src/python/grpcio/grpc/_adapter/_intermediary_low.py
new file mode 100644
index 0000000000..3c7f0a2619
--- /dev/null
+++ b/src/python/grpcio/grpc/_adapter/_intermediary_low.py
@@ -0,0 +1,259 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Temporary old _low-like layer.
+
+Eases refactoring burden while we overhaul the Python framework.
+
+Plan:
+ The layers used to look like:
+ ... # outside _adapter
+ fore.py + rear.py # visible outside _adapter
+ _low
+ _c
+ The layers currently look like:
+ ... # outside _adapter
+ fore.py + rear.py # visible outside _adapter
+ _low_intermediary # adapter for new '_low' to old '_low'
+ _low # new '_low'
+ _c # new '_c'
+ We will later remove _low_intermediary after refactoring of fore.py and
+ rear.py according to the ticket system refactoring and get:
+ ... # outside _adapter, refactored
+ fore.py + rear.py # visible outside _adapter, refactored
+ _low # new '_low'
+ _c # new '_c'
+"""
+
+import collections
+import enum
+
+from grpc._adapter import _low
+from grpc._adapter import _types
+
+_IGNORE_ME_TAG = object()
+Code = _types.StatusCode
+
+
+class Status(collections.namedtuple('Status', ['code', 'details'])):
+ """Describes an RPC's overall status."""
+
+
+class ServiceAcceptance(
+ collections.namedtuple(
+ 'ServiceAcceptance', ['call', 'method', 'host', 'deadline'])):
+ """Describes an RPC on the service side at the start of service."""
+
+
+class Event(
+ collections.namedtuple(
+ 'Event',
+ ['kind', 'tag', 'write_accepted', 'complete_accepted',
+ 'service_acceptance', 'bytes', 'status', 'metadata'])):
+ """Describes an event emitted from a completion queue."""
+
+ @enum.unique
+ class Kind(enum.Enum):
+ """Describes the kind of an event."""
+
+ STOP = object()
+ WRITE_ACCEPTED = object()
+ COMPLETE_ACCEPTED = object()
+ SERVICE_ACCEPTED = object()
+ READ_ACCEPTED = object()
+ METADATA_ACCEPTED = object()
+ FINISH = object()
+
+
+class _TagAdapter(collections.namedtuple('_TagAdapter', [
+ 'user_tag',
+ 'kind'
+ ])):
+ pass
+
+
+class Call(object):
+ """Adapter from old _low.Call interface to new _low.Call."""
+
+ def __init__(self, channel, completion_queue, method, host, deadline):
+ self._internal = channel._internal.create_call(
+ completion_queue._internal, method, host, deadline)
+ self._metadata = []
+
+ @staticmethod
+ def _from_internal(internal):
+ call = Call.__new__(Call)
+ call._internal = internal
+ call._metadata = []
+ return call
+
+ def invoke(self, completion_queue, metadata_tag, finish_tag):
+ err0 = self._internal.start_batch([
+ _types.OpArgs.send_initial_metadata(self._metadata)
+ ], _IGNORE_ME_TAG)
+ err1 = self._internal.start_batch([
+ _types.OpArgs.recv_initial_metadata()
+ ], _TagAdapter(metadata_tag, Event.Kind.METADATA_ACCEPTED))
+ err2 = self._internal.start_batch([
+ _types.OpArgs.recv_status_on_client()
+ ], _TagAdapter(finish_tag, Event.Kind.FINISH))
+ return err0 if err0 != _types.CallError.OK else err1 if err1 != _types.CallError.OK else err2 if err2 != _types.CallError.OK else _types.CallError.OK
+
+ def write(self, message, tag):
+ return self._internal.start_batch([
+ _types.OpArgs.send_message(message)
+ ], _TagAdapter(tag, Event.Kind.WRITE_ACCEPTED))
+
+ def complete(self, tag):
+ return self._internal.start_batch([
+ _types.OpArgs.send_close_from_client()
+ ], _TagAdapter(tag, Event.Kind.COMPLETE_ACCEPTED))
+
+ def accept(self, completion_queue, tag):
+ return self._internal.start_batch([
+ _types.OpArgs.recv_close_on_server()
+ ], _TagAdapter(tag, Event.Kind.FINISH))
+
+ def add_metadata(self, key, value):
+ self._metadata.append((key, value))
+
+ def premetadata(self):
+ result = self._internal.start_batch([
+ _types.OpArgs.send_initial_metadata(self._metadata)
+ ], _IGNORE_ME_TAG)
+ self._metadata = []
+ return result
+
+ def read(self, tag):
+ return self._internal.start_batch([
+ _types.OpArgs.recv_message()
+ ], _TagAdapter(tag, Event.Kind.READ_ACCEPTED))
+
+ def status(self, status, tag):
+ return self._internal.start_batch([
+ _types.OpArgs.send_status_from_server(self._metadata, status.code, status.details)
+ ], _TagAdapter(tag, Event.Kind.COMPLETE_ACCEPTED))
+
+ def cancel(self):
+ return self._internal.cancel()
+
+
+class Channel(object):
+ """Adapter from old _low.Channel interface to new _low.Channel."""
+
+ def __init__(self, hostport, client_credentials, server_host_override=None):
+ args = []
+ if server_host_override:
+ args.append((_types.GrpcChannelArgumentKeys.SSL_TARGET_NAME_OVERRIDE.value, server_host_override))
+ creds = None
+ if client_credentials:
+ creds = client_credentials._internal
+ self._internal = _low.Channel(hostport, args, creds)
+
+
+class CompletionQueue(object):
+ """Adapter from old _low.CompletionQueue interface to new _low.CompletionQueue."""
+
+ def __init__(self):
+ self._internal = _low.CompletionQueue()
+
+ def get(self, deadline=None):
+ if deadline is None:
+ ev = self._internal.next()
+ else:
+ ev = self._internal.next(deadline)
+ if ev is None:
+ return None
+ elif ev.tag is _IGNORE_ME_TAG:
+ return self.get(deadline)
+ elif ev.type == _types.EventType.QUEUE_SHUTDOWN:
+ kind = Event.Kind.STOP
+ tag = None
+ write_accepted = None
+ complete_accepted = None
+ service_acceptance = None
+ message_bytes = None
+ status = None
+ metadata = None
+ elif ev.type == _types.EventType.OP_COMPLETE:
+ kind = ev.tag.kind
+ tag = ev.tag.user_tag
+ write_accepted = ev.success if kind == Event.Kind.WRITE_ACCEPTED else None
+ complete_accepted = ev.success if kind == Event.Kind.COMPLETE_ACCEPTED else None
+ service_acceptance = ServiceAcceptance(Call._from_internal(ev.call), ev.call_details.method, ev.call_details.host, ev.call_details.deadline) if kind == Event.Kind.SERVICE_ACCEPTED else None
+ message_bytes = ev.results[0].message if kind == Event.Kind.READ_ACCEPTED else None
+ status = Status(ev.results[0].status.code, ev.results[0].status.details) if (kind == Event.Kind.FINISH and ev.results[0].status) else Status(_types.StatusCode.CANCELLED if ev.results[0].cancelled else _types.StatusCode.OK, '') if len(ev.results) > 0 and ev.results[0].cancelled is not None else None
+ metadata = ev.results[0].initial_metadata if (kind in [Event.Kind.SERVICE_ACCEPTED, Event.Kind.METADATA_ACCEPTED]) else (ev.results[0].trailing_metadata if kind == Event.Kind.FINISH else None)
+ else:
+ raise RuntimeError('unknown event')
+ result_ev = Event(kind=kind, tag=tag, write_accepted=write_accepted, complete_accepted=complete_accepted, service_acceptance=service_acceptance, bytes=message_bytes, status=status, metadata=metadata)
+ return result_ev
+
+ def stop(self):
+ self._internal.shutdown()
+
+
+class Server(object):
+ """Adapter from old _low.Server interface to new _low.Server."""
+
+ def __init__(self, completion_queue):
+ self._internal = _low.Server(completion_queue._internal, [])
+ self._internal_cq = completion_queue._internal
+
+ def add_http2_addr(self, addr):
+ return self._internal.add_http2_port(addr)
+
+ def add_secure_http2_addr(self, addr, server_credentials):
+ if server_credentials is None:
+ return self._internal.add_http2_port(addr, None)
+ else:
+ return self._internal.add_http2_port(addr, server_credentials._internal)
+
+ def start(self):
+ return self._internal.start()
+
+ def service(self, tag):
+ return self._internal.request_call(self._internal_cq, _TagAdapter(tag, Event.Kind.SERVICE_ACCEPTED))
+
+ def stop(self):
+ return self._internal.shutdown(_TagAdapter(None, Event.Kind.STOP))
+
+
+class ClientCredentials(object):
+ """Adapter from old _low.ClientCredentials interface to new _low.ClientCredentials."""
+
+ def __init__(self, root_certificates, private_key, certificate_chain):
+ self._internal = _low.ClientCredentials.ssl(root_certificates, private_key, certificate_chain)
+
+
+class ServerCredentials(object):
+ """Adapter from old _low.ServerCredentials interface to new _low.ServerCredentials."""
+
+ def __init__(self, root_credentials, pair_sequence):
+ self._internal = _low.ServerCredentials.ssl(root_credentials, list(pair_sequence))
diff --git a/src/python/grpcio/grpc/_adapter/_low.py b/src/python/grpcio/grpc/_adapter/_low.py
new file mode 100644
index 0000000000..dcf67dbc11
--- /dev/null
+++ b/src/python/grpcio/grpc/_adapter/_low.py
@@ -0,0 +1,108 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from grpc._adapter import _c
+from grpc._adapter import _types
+
+ClientCredentials = _c.ClientCredentials
+ServerCredentials = _c.ServerCredentials
+
+
+class CompletionQueue(_types.CompletionQueue):
+
+ def __init__(self):
+ self.completion_queue = _c.CompletionQueue()
+
+ def next(self, deadline=float('+inf')):
+ raw_event = self.completion_queue.next(deadline)
+ if raw_event is None:
+ return None
+ event = _types.Event(*raw_event)
+ if event.call is not None:
+ event = event._replace(call=Call(event.call))
+ if event.call_details is not None:
+ event = event._replace(call_details=_types.CallDetails(*event.call_details))
+ if event.results is not None:
+ new_results = [_types.OpResult(*r) for r in event.results]
+ new_results = [r if r.status is None else r._replace(status=_types.Status(_types.StatusCode(r.status[0]), r.status[1])) for r in new_results]
+ event = event._replace(results=new_results)
+ return event
+
+ def shutdown(self):
+ self.completion_queue.shutdown()
+
+
+class Call(_types.Call):
+
+ def __init__(self, call):
+ self.call = call
+
+ def start_batch(self, ops, tag):
+ return self.call.start_batch(ops, tag)
+
+ def cancel(self, code=None, details=None):
+ if code is None and details is None:
+ return self.call.cancel()
+ else:
+ return self.call.cancel(code, details)
+
+
+class Channel(_types.Channel):
+
+ def __init__(self, target, args, creds=None):
+ if creds is None:
+ self.channel = _c.Channel(target, args)
+ else:
+ self.channel = _c.Channel(target, args, creds)
+
+ def create_call(self, completion_queue, method, host, deadline=None):
+ return Call(self.channel.create_call(completion_queue.completion_queue, method, host, deadline))
+
+
+_NO_TAG = object()
+
+class Server(_types.Server):
+
+ def __init__(self, completion_queue, args):
+ self.server = _c.Server(completion_queue.completion_queue, args)
+
+ def add_http2_port(self, addr, creds=None):
+ if creds is None:
+ return self.server.add_http2_port(addr)
+ else:
+ return self.server.add_http2_port(addr, creds)
+
+ def start(self):
+ return self.server.start()
+
+ def shutdown(self, tag=None):
+ return self.server.shutdown(tag)
+
+ def request_call(self, completion_queue, tag):
+ return self.server.request_call(completion_queue.completion_queue, tag)
diff --git a/src/python/grpcio/grpc/_adapter/_types.py b/src/python/grpcio/grpc/_adapter/_types.py
new file mode 100644
index 0000000000..5ddb1774ea
--- /dev/null
+++ b/src/python/grpcio/grpc/_adapter/_types.py
@@ -0,0 +1,368 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import abc
+import collections
+import enum
+
+# TODO(atash): decide whether or not to move these enums to the _c module to
+# force build errors with upstream changes.
+
+class GrpcChannelArgumentKeys(enum.Enum):
+ """Mirrors keys used in grpc_channel_args for GRPC-specific arguments."""
+ SSL_TARGET_NAME_OVERRIDE = 'grpc.ssl_target_name_override'
+
+@enum.unique
+class CallError(enum.IntEnum):
+ """Mirrors grpc_call_error in the C core."""
+ OK = 0
+ ERROR = 1
+ ERROR_NOT_ON_SERVER = 2
+ ERROR_NOT_ON_CLIENT = 3
+ ERROR_ALREADY_ACCEPTED = 4
+ ERROR_ALREADY_INVOKED = 5
+ ERROR_NOT_INVOKED = 6
+ ERROR_ALREADY_FINISHED = 7
+ ERROR_TOO_MANY_OPERATIONS = 8
+ ERROR_INVALID_FLAGS = 9
+ ERROR_INVALID_METADATA = 10
+
+@enum.unique
+class StatusCode(enum.IntEnum):
+ """Mirrors grpc_status_code in the C core."""
+ OK = 0
+ CANCELLED = 1
+ UNKNOWN = 2
+ INVALID_ARGUMENT = 3
+ DEADLINE_EXCEEDED = 4
+ NOT_FOUND = 5
+ ALREADY_EXISTS = 6
+ PERMISSION_DENIED = 7
+ RESOURCE_EXHAUSTED = 8
+ FAILED_PRECONDITION = 9
+ ABORTED = 10
+ OUT_OF_RANGE = 11
+ UNIMPLEMENTED = 12
+ INTERNAL = 13
+ UNAVAILABLE = 14
+ DATA_LOSS = 15
+ UNAUTHENTICATED = 16
+
+@enum.unique
+class OpType(enum.IntEnum):
+ """Mirrors grpc_op_type in the C core."""
+ SEND_INITIAL_METADATA = 0
+ SEND_MESSAGE = 1
+ SEND_CLOSE_FROM_CLIENT = 2
+ SEND_STATUS_FROM_SERVER = 3
+ RECV_INITIAL_METADATA = 4
+ RECV_MESSAGE = 5
+ RECV_STATUS_ON_CLIENT = 6
+ RECV_CLOSE_ON_SERVER = 7
+
+@enum.unique
+class EventType(enum.IntEnum):
+ """Mirrors grpc_completion_type in the C core."""
+ QUEUE_SHUTDOWN = 0
+ QUEUE_TIMEOUT = 1 # if seen on the Python side, something went horridly wrong
+ OP_COMPLETE = 2
+
+class Status(collections.namedtuple(
+ 'Status', [
+ 'code',
+ 'details',
+ ])):
+ """The end status of a GRPC call.
+
+ Attributes:
+ code (StatusCode): ...
+ details (str): ...
+ """
+
+class CallDetails(collections.namedtuple(
+ 'CallDetails', [
+ 'method',
+ 'host',
+ 'deadline',
+ ])):
+ """Provides information to the server about the client's call.
+
+ Attributes:
+ method (str): ...
+ host (str): ...
+ deadline (float): ...
+ """
+
+class OpArgs(collections.namedtuple(
+ 'OpArgs', [
+ 'type',
+ 'initial_metadata',
+ 'trailing_metadata',
+ 'message',
+ 'status',
+ ])):
+ """Arguments passed into a GRPC operation.
+
+ Attributes:
+ type (OpType): ...
+ initial_metadata (sequence of 2-sequence of str): Only valid if type ==
+ OpType.SEND_INITIAL_METADATA, else is None.
+ trailing_metadata (sequence of 2-sequence of str): Only valid if type ==
+ OpType.SEND_STATUS_FROM_SERVER, else is None.
+ message (bytes): Only valid if type == OpType.SEND_MESSAGE, else is None.
+ status (Status): Only valid if type == OpType.SEND_STATUS_FROM_SERVER, else
+ is None.
+ """
+
+ @staticmethod
+ def send_initial_metadata(initial_metadata):
+ return OpArgs(OpType.SEND_INITIAL_METADATA, initial_metadata, None, None, None)
+
+ @staticmethod
+ def send_message(message):
+ return OpArgs(OpType.SEND_MESSAGE, None, None, message, None)
+
+ @staticmethod
+ def send_close_from_client():
+ return OpArgs(OpType.SEND_CLOSE_FROM_CLIENT, None, None, None, None)
+
+ @staticmethod
+ def send_status_from_server(trailing_metadata, status_code, status_details):
+ return OpArgs(OpType.SEND_STATUS_FROM_SERVER, None, trailing_metadata, None, Status(status_code, status_details))
+
+ @staticmethod
+ def recv_initial_metadata():
+ return OpArgs(OpType.RECV_INITIAL_METADATA, None, None, None, None);
+
+ @staticmethod
+ def recv_message():
+ return OpArgs(OpType.RECV_MESSAGE, None, None, None, None)
+
+ @staticmethod
+ def recv_status_on_client():
+ return OpArgs(OpType.RECV_STATUS_ON_CLIENT, None, None, None, None)
+
+ @staticmethod
+ def recv_close_on_server():
+ return OpArgs(OpType.RECV_CLOSE_ON_SERVER, None, None, None, None)
+
+
+class OpResult(collections.namedtuple(
+ 'OpResult', [
+ 'type',
+ 'initial_metadata',
+ 'trailing_metadata',
+ 'message',
+ 'status',
+ 'cancelled',
+ ])):
+ """Results received from a GRPC operation.
+
+ Attributes:
+ type (OpType): ...
+ initial_metadata (sequence of 2-sequence of str): Only valid if type ==
+ OpType.RECV_INITIAL_METADATA, else is None.
+ trailing_metadata (sequence of 2-sequence of str): Only valid if type ==
+ OpType.RECV_STATUS_ON_CLIENT, else is None.
+ message (bytes): Only valid if type == OpType.RECV_MESSAGE, else is None.
+ status (Status): Only valid if type == OpType.RECV_STATUS_ON_CLIENT, else
+ is None.
+ cancelled (bool): Only valid if type == OpType.RECV_CLOSE_ON_SERVER, else
+ is None.
+ """
+
+
+class Event(collections.namedtuple(
+ 'Event', [
+ 'type',
+ 'tag',
+ 'call',
+ 'call_details',
+ 'results',
+ 'success',
+ ])):
+ """An event received from a GRPC completion queue.
+
+ Attributes:
+ type (EventType): ...
+ tag (object): ...
+ call (Call): The Call object associated with this event (if there is one,
+ else None).
+ call_details (CallDetails): The call details associated with the
+ server-side call (if there is such information, else None).
+ results (list of OpResult): ...
+ success (bool): ...
+ """
+
+
+class CompletionQueue:
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def __init__(self):
+ pass
+
+ def __iter__(self):
+ """This class may be iterated over.
+
+ This is the equivalent of calling next() repeatedly with an absolute
+ deadline of None (i.e. no deadline).
+ """
+ return self
+
+ @abc.abstractmethod
+ def next(self, deadline=float('+inf')):
+ """Get the next event on this completion queue.
+
+ Args:
+ deadline (float): absolute deadline in seconds from the Python epoch, or
+ None for no deadline.
+
+ Returns:
+ Event: ...
+ """
+ pass
+
+ @abc.abstractmethod
+ def shutdown(self):
+ """Begin the shutdown process of this completion queue.
+
+ Note that this does not immediately destroy the completion queue.
+ Nevertheless, user code should not pass it around after invoking this.
+ """
+ return None
+
+
+class Call:
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def start_batch(self, ops, tag):
+ """Start a batch of operations.
+
+ Args:
+ ops (sequence of OpArgs): ...
+ tag (object): ...
+
+ Returns:
+ CallError: ...
+ """
+ return CallError.ERROR
+
+ @abc.abstractmethod
+ def cancel(self, code=None, details=None):
+ """Cancel the call.
+
+ Args:
+ code (int): Status code to cancel with (on the server side). If
+ specified, so must `details`.
+ details (str): Status details to cancel with (on the server side). If
+ specified, so must `code`.
+
+ Returns:
+ CallError: ...
+ """
+ return CallError.ERROR
+
+
+class Channel:
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def __init__(self, target, args, credentials=None):
+ """Initialize a Channel.
+
+ Args:
+ target (str): ...
+ args (sequence of 2-sequence of str, (str|integer)): ...
+ credentials (ClientCredentials): If None, create an insecure channel,
+ else create a secure channel using the client credentials.
+ """
+
+ @abc.abstractmethod
+ def create_call(self, completion_queue, method, host, deadline=float('+inf')):
+ """Create a call from this channel.
+
+ Args:
+ completion_queue (CompletionQueue): ...
+ method (str): ...
+ host (str): ...
+ deadline (float): absolute deadline in seconds from the Python epoch, or
+ None for no deadline.
+
+ Returns:
+ Call: call object associated with this Channel and passed parameters.
+ """
+ return None
+
+
+class Server:
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def __init__(self, completion_queue, args):
+ """Initialize a server.
+
+ Args:
+ completion_queue (CompletionQueue): ...
+ args (sequence of 2-sequence of str, (str|integer)): ...
+ """
+
+ @abc.abstractmethod
+ def add_http2_port(self, address, credentials=None):
+ """Adds an HTTP/2 address+port to the server.
+
+ Args:
+ address (str): ...
+ credentials (ServerCredentials): If None, create an insecure port, else
+ create a secure port using the server credentials.
+ """
+
+ @abc.abstractmethod
+ def start(self):
+ """Starts the server."""
+
+ @abc.abstractmethod
+ def shutdown(self, tag=None):
+ """Shuts down the server. Does not immediately destroy the server.
+
+ Args:
+ tag (object): if not None, have the server place an event on its
+ completion queue notifying it when this server has completely shut down.
+ """
+
+ @abc.abstractmethod
+ def request_call(self, completion_queue, tag):
+ """Requests a call from the server on the server's completion queue.
+
+ Args:
+ completion_queue (CompletionQueue): Completion queue for the call. May be
+ the same as the server's completion queue.
+ tag (object) ...
+ """
diff --git a/src/python/grpcio/grpc/_adapter/fore.py b/src/python/grpcio/grpc/_adapter/fore.py
new file mode 100644
index 0000000000..7d88bda263
--- /dev/null
+++ b/src/python/grpcio/grpc/_adapter/fore.py
@@ -0,0 +1,363 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""The RPC-service-side bridge between RPC Framework and GRPC-on-the-wire."""
+
+import enum
+import logging
+import threading
+import time
+
+from grpc._adapter import _common
+from grpc._adapter import _intermediary_low as _low
+from grpc.framework.base import interfaces as base_interfaces
+from grpc.framework.base import null
+from grpc.framework.foundation import activated
+from grpc.framework.foundation import logging_pool
+
+_THREAD_POOL_SIZE = 10
+
+
+@enum.unique
+class _LowWrite(enum.Enum):
+ """The possible categories of low-level write state."""
+
+ OPEN = 'OPEN'
+ ACTIVE = 'ACTIVE'
+ CLOSED = 'CLOSED'
+
+
+def _write(call, rpc_state, payload):
+ serialized_payload = rpc_state.serializer(payload)
+ if rpc_state.write.low is _LowWrite.OPEN:
+ call.write(serialized_payload, call)
+ rpc_state.write.low = _LowWrite.ACTIVE
+ else:
+ rpc_state.write.pending.append(serialized_payload)
+
+
+def _status(call, rpc_state):
+ call.status(_low.Status(_low.Code.OK, ''), call)
+ rpc_state.write.low = _LowWrite.CLOSED
+
+
+class ForeLink(base_interfaces.ForeLink, activated.Activated):
+ """A service-side bridge between RPC Framework and the C-ish _low code."""
+
+ def __init__(
+ self, pool, request_deserializers, response_serializers,
+ root_certificates, key_chain_pairs, port=None):
+ """Constructor.
+
+ Args:
+ pool: A thread pool.
+ request_deserializers: A dict from RPC method names to request object
+ deserializer behaviors.
+ response_serializers: A dict from RPC method names to response object
+ serializer behaviors.
+ root_certificates: The PEM-encoded client root certificates as a
+ bytestring or None.
+ key_chain_pairs: A sequence of PEM-encoded private key-certificate chain
+ pairs.
+ port: The port on which to serve, or None to have a port selected
+ automatically.
+ """
+ self._condition = threading.Condition()
+ self._pool = pool
+ self._request_deserializers = request_deserializers
+ self._response_serializers = response_serializers
+ self._root_certificates = root_certificates
+ self._key_chain_pairs = key_chain_pairs
+ self._requested_port = port
+
+ self._rear_link = null.NULL_REAR_LINK
+ self._completion_queue = None
+ self._server = None
+ self._rpc_states = {}
+ self._spinning = False
+ self._port = None
+
+ def _on_stop_event(self):
+ self._spinning = False
+ self._condition.notify_all()
+
+ def _on_service_acceptance_event(self, event, server):
+ """Handle a service invocation event."""
+ service_acceptance = event.service_acceptance
+ if service_acceptance is None:
+ return
+
+ call = service_acceptance.call
+ call.accept(self._completion_queue, call)
+ # TODO(nathaniel): Metadata support.
+ call.premetadata()
+ call.read(call)
+ method = service_acceptance.method
+
+ self._rpc_states[call] = _common.CommonRPCState(
+ _common.WriteState(_LowWrite.OPEN, _common.HighWrite.OPEN, []), 1,
+ self._request_deserializers[method],
+ self._response_serializers[method])
+
+ ticket = base_interfaces.FrontToBackTicket(
+ call, 0, base_interfaces.FrontToBackTicket.Kind.COMMENCEMENT, method,
+ base_interfaces.ServicedSubscription.Kind.FULL, None, None,
+ service_acceptance.deadline - time.time())
+ self._rear_link.accept_front_to_back_ticket(ticket)
+
+ server.service(None)
+
+ def _on_read_event(self, event):
+ """Handle data arriving during an RPC."""
+ call = event.tag
+ rpc_state = self._rpc_states.get(call, None)
+ if rpc_state is None:
+ return
+
+ sequence_number = rpc_state.sequence_number
+ rpc_state.sequence_number += 1
+ if event.bytes is None:
+ ticket = base_interfaces.FrontToBackTicket(
+ call, sequence_number,
+ base_interfaces.FrontToBackTicket.Kind.COMPLETION, None, None, None,
+ None, None)
+ else:
+ call.read(call)
+ ticket = base_interfaces.FrontToBackTicket(
+ call, sequence_number,
+ base_interfaces.FrontToBackTicket.Kind.CONTINUATION, None, None,
+ None, rpc_state.deserializer(event.bytes), None)
+
+ self._rear_link.accept_front_to_back_ticket(ticket)
+
+ def _on_write_event(self, event):
+ call = event.tag
+ rpc_state = self._rpc_states.get(call, None)
+ if rpc_state is None:
+ return
+
+ if rpc_state.write.pending:
+ serialized_payload = rpc_state.write.pending.pop(0)
+ call.write(serialized_payload, call)
+ elif rpc_state.write.high is _common.HighWrite.CLOSED:
+ _status(call, rpc_state)
+ else:
+ rpc_state.write.low = _LowWrite.OPEN
+
+ def _on_complete_event(self, event):
+ if not event.complete_accepted:
+ logging.error('Complete not accepted! %s', (event,))
+ call = event.tag
+ rpc_state = self._rpc_states.pop(call, None)
+ if rpc_state is None:
+ return
+
+ sequence_number = rpc_state.sequence_number
+ rpc_state.sequence_number += 1
+ ticket = base_interfaces.FrontToBackTicket(
+ call, sequence_number,
+ base_interfaces.FrontToBackTicket.Kind.TRANSMISSION_FAILURE, None,
+ None, None, None, None)
+ self._rear_link.accept_front_to_back_ticket(ticket)
+
+ def _on_finish_event(self, event):
+ """Handle termination of an RPC."""
+ call = event.tag
+ rpc_state = self._rpc_states.pop(call, None)
+ if rpc_state is None:
+ return
+
+ code = event.status.code
+ if code is _low.Code.OK:
+ return
+
+ sequence_number = rpc_state.sequence_number
+ rpc_state.sequence_number += 1
+ if code is _low.Code.CANCELLED:
+ ticket = base_interfaces.FrontToBackTicket(
+ call, sequence_number,
+ base_interfaces.FrontToBackTicket.Kind.CANCELLATION, None, None,
+ None, None, None)
+ elif code is _low.Code.DEADLINE_EXCEEDED:
+ ticket = base_interfaces.FrontToBackTicket(
+ call, sequence_number,
+ base_interfaces.FrontToBackTicket.Kind.EXPIRATION, None, None, None,
+ None, None)
+ else:
+ # TODO(nathaniel): Better mapping of codes to ticket-categories
+ ticket = base_interfaces.FrontToBackTicket(
+ call, sequence_number,
+ base_interfaces.FrontToBackTicket.Kind.TRANSMISSION_FAILURE, None,
+ None, None, None, None)
+ self._rear_link.accept_front_to_back_ticket(ticket)
+
+ def _spin(self, completion_queue, server):
+ while True:
+ event = completion_queue.get(None)
+
+ with self._condition:
+ if event.kind is _low.Event.Kind.STOP:
+ self._on_stop_event()
+ return
+ elif self._server is None:
+ continue
+ elif event.kind is _low.Event.Kind.SERVICE_ACCEPTED:
+ self._on_service_acceptance_event(event, server)
+ elif event.kind is _low.Event.Kind.READ_ACCEPTED:
+ self._on_read_event(event)
+ elif event.kind is _low.Event.Kind.WRITE_ACCEPTED:
+ self._on_write_event(event)
+ elif event.kind is _low.Event.Kind.COMPLETE_ACCEPTED:
+ self._on_complete_event(event)
+ elif event.kind is _low.Event.Kind.FINISH:
+ self._on_finish_event(event)
+ else:
+ logging.error('Illegal event! %s', (event,))
+
+ def _continue(self, call, payload):
+ rpc_state = self._rpc_states.get(call, None)
+ if rpc_state is None:
+ return
+
+ _write(call, rpc_state, payload)
+
+ def _complete(self, call, payload):
+ """Handle completion of the writes of an RPC."""
+ rpc_state = self._rpc_states.get(call, None)
+ if rpc_state is None:
+ return
+
+ if rpc_state.write.low is _LowWrite.OPEN:
+ if payload is None:
+ _status(call, rpc_state)
+ else:
+ _write(call, rpc_state, payload)
+ elif rpc_state.write.low is _LowWrite.ACTIVE:
+ if payload is not None:
+ rpc_state.write.pending.append(rpc_state.serializer(payload))
+ else:
+ raise ValueError('Called to complete after having already completed!')
+ rpc_state.write.high = _common.HighWrite.CLOSED
+
+ def _cancel(self, call):
+ call.cancel()
+ self._rpc_states.pop(call, None)
+
+ def join_rear_link(self, rear_link):
+ """See base_interfaces.ForeLink.join_rear_link for specification."""
+ self._rear_link = null.NULL_REAR_LINK if rear_link is None else rear_link
+
+ def _start(self):
+ """Starts this ForeLink.
+
+ This method must be called before attempting to exchange tickets with this
+ object.
+ """
+ with self._condition:
+ address = '[::]:%d' % (
+ 0 if self._requested_port is None else self._requested_port)
+ self._completion_queue = _low.CompletionQueue()
+ if self._root_certificates is None and not self._key_chain_pairs:
+ self._server = _low.Server(self._completion_queue)
+ self._port = self._server.add_http2_addr(address)
+ else:
+ server_credentials = _low.ServerCredentials(
+ self._root_certificates, self._key_chain_pairs)
+ self._server = _low.Server(self._completion_queue)
+ self._port = self._server.add_secure_http2_addr(
+ address, server_credentials)
+ self._server.start()
+
+ self._server.service(None)
+
+ self._pool.submit(self._spin, self._completion_queue, self._server)
+ self._spinning = True
+
+ return self
+
+ # TODO(nathaniel): Expose graceful-shutdown semantics in which this object
+ # enters a state in which it finishes ongoing RPCs but refuses new ones.
+ def _stop(self):
+ """Stops this ForeLink.
+
+ This method must be called for proper termination of this object, and no
+ attempts to exchange tickets with this object may be made after this method
+ has been called.
+ """
+ with self._condition:
+ self._server.stop()
+ # TODO(nathaniel): Yep, this is weird. Deleting a server shouldn't have a
+ # behaviorally significant side-effect.
+ self._server = None
+ self._completion_queue.stop()
+
+ while self._spinning:
+ self._condition.wait()
+
+ self._port = None
+
+ def __enter__(self):
+ """See activated.Activated.__enter__ for specification."""
+ return self._start()
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ """See activated.Activated.__exit__ for specification."""
+ self._stop()
+ return False
+
+ def start(self):
+ """See activated.Activated.start for specification."""
+ return self._start()
+
+ def stop(self):
+ """See activated.Activated.stop for specification."""
+ self._stop()
+
+ def port(self):
+ """Identifies the port on which this ForeLink is servicing RPCs.
+
+ Returns:
+ The number of the port on which this ForeLink is servicing RPCs, or None
+ if this ForeLink is not currently activated and servicing RPCs.
+ """
+ with self._condition:
+ return self._port
+
+ def accept_back_to_front_ticket(self, ticket):
+ """See base_interfaces.ForeLink.accept_back_to_front_ticket for spec."""
+ with self._condition:
+ if self._server is None:
+ return
+
+ if ticket.kind is base_interfaces.BackToFrontTicket.Kind.CONTINUATION:
+ self._continue(ticket.operation_id, ticket.payload)
+ elif ticket.kind is base_interfaces.BackToFrontTicket.Kind.COMPLETION:
+ self._complete(ticket.operation_id, ticket.payload)
+ else:
+ self._cancel(ticket.operation_id)
diff --git a/src/python/grpcio/grpc/_adapter/rear.py b/src/python/grpcio/grpc/_adapter/rear.py
new file mode 100644
index 0000000000..fd6f45f7a7
--- /dev/null
+++ b/src/python/grpcio/grpc/_adapter/rear.py
@@ -0,0 +1,395 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""The RPC-invocation-side bridge between RPC Framework and GRPC-on-the-wire."""
+
+import enum
+import logging
+import threading
+import time
+
+from grpc._adapter import _common
+from grpc._adapter import _intermediary_low as _low
+from grpc.framework.base import interfaces as base_interfaces
+from grpc.framework.base import null
+from grpc.framework.foundation import activated
+from grpc.framework.foundation import logging_pool
+
+_THREAD_POOL_SIZE = 10
+
+_INVOCATION_EVENT_KINDS = (
+ _low.Event.Kind.METADATA_ACCEPTED,
+ _low.Event.Kind.FINISH
+)
+
+
+@enum.unique
+class _LowWrite(enum.Enum):
+ """The possible categories of low-level write state."""
+
+ OPEN = 'OPEN'
+ ACTIVE = 'ACTIVE'
+ CLOSED = 'CLOSED'
+
+
+class _RPCState(object):
+ """The full state of any tracked RPC.
+
+ Attributes:
+ call: The _low.Call object for the RPC.
+ outstanding: The set of Event.Kind values describing expected future events
+ for the RPC.
+ active: A boolean indicating whether or not the RPC is active.
+ common: An _common.RPCState describing additional state for the RPC.
+ """
+
+ def __init__(self, call, outstanding, active, common):
+ self.call = call
+ self.outstanding = outstanding
+ self.active = active
+ self.common = common
+
+
+def _write(operation_id, call, outstanding, write_state, serialized_payload):
+ if write_state.low is _LowWrite.OPEN:
+ call.write(serialized_payload, operation_id)
+ outstanding.add(_low.Event.Kind.WRITE_ACCEPTED)
+ write_state.low = _LowWrite.ACTIVE
+ elif write_state.low is _LowWrite.ACTIVE:
+ write_state.pending.append(serialized_payload)
+ else:
+ raise ValueError('Write attempted after writes completed!')
+
+
+class RearLink(base_interfaces.RearLink, activated.Activated):
+ """An invocation-side bridge between RPC Framework and the C-ish _low code."""
+
+ def __init__(
+ self, host, port, pool, request_serializers, response_deserializers,
+ secure, root_certificates, private_key, certificate_chain,
+ metadata_transformer=None, server_host_override=None):
+ """Constructor.
+
+ Args:
+ host: The host to which to connect for RPC service.
+ port: The port to which to connect for RPC service.
+ pool: A thread pool.
+ request_serializers: A dict from RPC method names to request object
+ serializer behaviors.
+ response_deserializers: A dict from RPC method names to response object
+ deserializer behaviors.
+ secure: A boolean indicating whether or not to use a secure connection.
+ root_certificates: The PEM-encoded root certificates or None to ask for
+ them to be retrieved from a default location.
+ private_key: The PEM-encoded private key to use or None if no private
+ key should be used.
+ certificate_chain: The PEM-encoded certificate chain to use or None if
+ no certificate chain should be used.
+ metadata_transformer: A function that given a metadata object produces
+ another metadata to be used in the underlying communication on the
+ wire.
+ server_host_override: (For testing only) the target name used for SSL
+ host name checking.
+ """
+ self._condition = threading.Condition()
+ self._host = host
+ self._port = port
+ self._pool = pool
+ self._request_serializers = request_serializers
+ self._response_deserializers = response_deserializers
+
+ self._fore_link = null.NULL_FORE_LINK
+ self._completion_queue = None
+ self._channel = None
+ self._rpc_states = {}
+ self._spinning = False
+ if secure:
+ self._client_credentials = _low.ClientCredentials(
+ root_certificates, private_key, certificate_chain)
+ else:
+ self._client_credentials = None
+ self._root_certificates = root_certificates
+ self._private_key = private_key
+ self._certificate_chain = certificate_chain
+ self._metadata_transformer = metadata_transformer
+ self._server_host_override = server_host_override
+
+ def _on_write_event(self, operation_id, event, rpc_state):
+ if event.write_accepted:
+ if rpc_state.common.write.pending:
+ rpc_state.call.write(
+ rpc_state.common.write.pending.pop(0), operation_id)
+ rpc_state.outstanding.add(_low.Event.Kind.WRITE_ACCEPTED)
+ elif rpc_state.common.write.high is _common.HighWrite.CLOSED:
+ rpc_state.call.complete(operation_id)
+ rpc_state.outstanding.add(_low.Event.Kind.COMPLETE_ACCEPTED)
+ rpc_state.common.write.low = _LowWrite.CLOSED
+ else:
+ rpc_state.common.write.low = _LowWrite.OPEN
+ else:
+ logging.error('RPC write not accepted! Event: %s', (event,))
+ rpc_state.active = False
+ ticket = base_interfaces.BackToFrontTicket(
+ operation_id, rpc_state.common.sequence_number,
+ base_interfaces.BackToFrontTicket.Kind.TRANSMISSION_FAILURE, None)
+ rpc_state.common.sequence_number += 1
+ self._fore_link.accept_back_to_front_ticket(ticket)
+
+ def _on_read_event(self, operation_id, event, rpc_state):
+ if event.bytes is not None:
+ rpc_state.call.read(operation_id)
+ rpc_state.outstanding.add(_low.Event.Kind.READ_ACCEPTED)
+
+ ticket = base_interfaces.BackToFrontTicket(
+ operation_id, rpc_state.common.sequence_number,
+ base_interfaces.BackToFrontTicket.Kind.CONTINUATION,
+ rpc_state.common.deserializer(event.bytes))
+ rpc_state.common.sequence_number += 1
+ self._fore_link.accept_back_to_front_ticket(ticket)
+
+ def _on_complete_event(self, operation_id, event, rpc_state):
+ if not event.complete_accepted:
+ logging.error('RPC complete not accepted! Event: %s', (event,))
+ rpc_state.active = False
+ ticket = base_interfaces.BackToFrontTicket(
+ operation_id, rpc_state.common.sequence_number,
+ base_interfaces.BackToFrontTicket.Kind.TRANSMISSION_FAILURE, None)
+ rpc_state.common.sequence_number += 1
+ self._fore_link.accept_back_to_front_ticket(ticket)
+
+ # TODO(nathaniel): Metadata support.
+ def _on_metadata_event(self, operation_id, event, rpc_state): # pylint: disable=unused-argument
+ rpc_state.call.read(operation_id)
+ rpc_state.outstanding.add(_low.Event.Kind.READ_ACCEPTED)
+
+ def _on_finish_event(self, operation_id, event, rpc_state):
+ """Handle termination of an RPC."""
+ # TODO(nathaniel): Cover all statuses.
+ if event.status.code is _low.Code.OK:
+ kind = base_interfaces.BackToFrontTicket.Kind.COMPLETION
+ elif event.status.code is _low.Code.CANCELLED:
+ kind = base_interfaces.BackToFrontTicket.Kind.CANCELLATION
+ elif event.status.code is _low.Code.DEADLINE_EXCEEDED:
+ kind = base_interfaces.BackToFrontTicket.Kind.EXPIRATION
+ else:
+ kind = base_interfaces.BackToFrontTicket.Kind.TRANSMISSION_FAILURE
+ ticket = base_interfaces.BackToFrontTicket(
+ operation_id, rpc_state.common.sequence_number, kind, None)
+ rpc_state.common.sequence_number += 1
+ self._fore_link.accept_back_to_front_ticket(ticket)
+
+ def _spin(self, completion_queue):
+ while True:
+ event = completion_queue.get(None)
+ operation_id = event.tag
+
+ with self._condition:
+ rpc_state = self._rpc_states[operation_id]
+ rpc_state.outstanding.remove(event.kind)
+ if rpc_state.active and self._completion_queue is not None:
+ if event.kind is _low.Event.Kind.WRITE_ACCEPTED:
+ self._on_write_event(operation_id, event, rpc_state)
+ elif event.kind is _low.Event.Kind.METADATA_ACCEPTED:
+ self._on_metadata_event(operation_id, event, rpc_state)
+ elif event.kind is _low.Event.Kind.READ_ACCEPTED:
+ self._on_read_event(operation_id, event, rpc_state)
+ elif event.kind is _low.Event.Kind.COMPLETE_ACCEPTED:
+ self._on_complete_event(operation_id, event, rpc_state)
+ elif event.kind is _low.Event.Kind.FINISH:
+ self._on_finish_event(operation_id, event, rpc_state)
+ else:
+ logging.error('Illegal RPC event! %s', (event,))
+
+ if not rpc_state.outstanding:
+ self._rpc_states.pop(operation_id)
+ if not self._rpc_states:
+ self._spinning = False
+ self._condition.notify_all()
+ return
+
+ def _invoke(self, operation_id, name, high_state, payload, timeout):
+ """Invoke an RPC.
+
+ Args:
+ operation_id: Any object to be used as an operation ID for the RPC.
+ name: The RPC method name.
+ high_state: A _common.HighWrite value representing the "high write state"
+ of the RPC.
+ payload: A payload object for the RPC or None if no payload was given at
+ invocation-time.
+ timeout: A duration of time in seconds to allow for the RPC.
+ """
+ request_serializer = self._request_serializers[name]
+ call = _low.Call(self._channel, self._completion_queue, name, self._host, time.time() + timeout)
+ if self._metadata_transformer is not None:
+ metadata = self._metadata_transformer([])
+ for metadata_key, metadata_value in metadata:
+ call.add_metadata(metadata_key, metadata_value)
+ call.invoke(self._completion_queue, operation_id, operation_id)
+ outstanding = set(_INVOCATION_EVENT_KINDS)
+
+ if payload is None:
+ if high_state is _common.HighWrite.CLOSED:
+ call.complete(operation_id)
+ low_state = _LowWrite.CLOSED
+ outstanding.add(_low.Event.Kind.COMPLETE_ACCEPTED)
+ else:
+ low_state = _LowWrite.OPEN
+ else:
+ serialized_payload = request_serializer(payload)
+ call.write(serialized_payload, operation_id)
+ outstanding.add(_low.Event.Kind.WRITE_ACCEPTED)
+ low_state = _LowWrite.ACTIVE
+
+ write_state = _common.WriteState(low_state, high_state, [])
+ common_state = _common.CommonRPCState(
+ write_state, 0, self._response_deserializers[name], request_serializer)
+ self._rpc_states[operation_id] = _RPCState(
+ call, outstanding, True, common_state)
+
+ if not self._spinning:
+ self._pool.submit(self._spin, self._completion_queue)
+ self._spinning = True
+
+ def _commence(self, operation_id, name, payload, timeout):
+ self._invoke(operation_id, name, _common.HighWrite.OPEN, payload, timeout)
+
+ def _continue(self, operation_id, payload):
+ rpc_state = self._rpc_states.get(operation_id, None)
+ if rpc_state is None or not rpc_state.active:
+ return
+
+ _write(
+ operation_id, rpc_state.call, rpc_state.outstanding,
+ rpc_state.common.write, rpc_state.common.serializer(payload))
+
+ def _complete(self, operation_id, payload):
+ """Close writes associated with an ongoing RPC.
+
+ Args:
+ operation_id: Any object being use as an operation ID for the RPC.
+ payload: A payload object for the RPC (and thus the last payload object
+ for the RPC) or None if no payload was given along with the instruction
+ to indicate the end of writes for the RPC.
+ """
+ rpc_state = self._rpc_states.get(operation_id, None)
+ if rpc_state is None or not rpc_state.active:
+ return
+
+ write_state = rpc_state.common.write
+ if payload is None:
+ if write_state.low is _LowWrite.OPEN:
+ rpc_state.call.complete(operation_id)
+ rpc_state.outstanding.add(_low.Event.Kind.COMPLETE_ACCEPTED)
+ write_state.low = _LowWrite.CLOSED
+ else:
+ _write(
+ operation_id, rpc_state.call, rpc_state.outstanding, write_state,
+ rpc_state.common.serializer(payload))
+ write_state.high = _common.HighWrite.CLOSED
+
+ def _entire(self, operation_id, name, payload, timeout):
+ self._invoke(operation_id, name, _common.HighWrite.CLOSED, payload, timeout)
+
+ def _cancel(self, operation_id):
+ rpc_state = self._rpc_states.get(operation_id, None)
+ if rpc_state is not None and rpc_state.active:
+ rpc_state.call.cancel()
+ rpc_state.active = False
+
+ def join_fore_link(self, fore_link):
+ """See base_interfaces.RearLink.join_fore_link for specification."""
+ with self._condition:
+ self._fore_link = null.NULL_FORE_LINK if fore_link is None else fore_link
+
+ def _start(self):
+ """Starts this RearLink.
+
+ This method must be called before attempting to exchange tickets with this
+ object.
+ """
+ with self._condition:
+ self._completion_queue = _low.CompletionQueue()
+ self._channel = _low.Channel(
+ '%s:%d' % (self._host, self._port), self._client_credentials,
+ server_host_override=self._server_host_override)
+ return self
+
+ def _stop(self):
+ """Stops this RearLink.
+
+ This method must be called for proper termination of this object, and no
+ attempts to exchange tickets with this object may be made after this method
+ has been called.
+ """
+ with self._condition:
+ self._completion_queue.stop()
+ self._completion_queue = None
+
+ while self._spinning:
+ self._condition.wait()
+
+ def __enter__(self):
+ """See activated.Activated.__enter__ for specification."""
+ return self._start()
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ """See activated.Activated.__exit__ for specification."""
+ self._stop()
+ return False
+
+ def start(self):
+ """See activated.Activated.start for specification."""
+ return self._start()
+
+ def stop(self):
+ """See activated.Activated.stop for specification."""
+ self._stop()
+
+ def accept_front_to_back_ticket(self, ticket):
+ """See base_interfaces.RearLink.accept_front_to_back_ticket for spec."""
+ with self._condition:
+ if self._completion_queue is None:
+ return
+
+ if ticket.kind is base_interfaces.FrontToBackTicket.Kind.COMMENCEMENT:
+ self._commence(
+ ticket.operation_id, ticket.name, ticket.payload, ticket.timeout)
+ elif ticket.kind is base_interfaces.FrontToBackTicket.Kind.CONTINUATION:
+ self._continue(ticket.operation_id, ticket.payload)
+ elif ticket.kind is base_interfaces.FrontToBackTicket.Kind.COMPLETION:
+ self._complete(ticket.operation_id, ticket.payload)
+ elif ticket.kind is base_interfaces.FrontToBackTicket.Kind.ENTIRE:
+ self._entire(
+ ticket.operation_id, ticket.name, ticket.payload, ticket.timeout)
+ elif ticket.kind is base_interfaces.FrontToBackTicket.Kind.CANCELLATION:
+ self._cancel(ticket.operation_id)
+ else:
+ # NOTE(nathaniel): All other categories are treated as cancellation.
+ self._cancel(ticket.operation_id)
diff --git a/src/python/grpcio/grpc/_cython/.gitignore b/src/python/grpcio/grpc/_cython/.gitignore
new file mode 100644
index 0000000000..c315029288
--- /dev/null
+++ b/src/python/grpcio/grpc/_cython/.gitignore
@@ -0,0 +1,7 @@
+*.h
+*.c
+*.a
+*.so
+*.dll
+*.pyc
+*.pyd
diff --git a/src/python/grpcio/grpc/_cython/README.rst b/src/python/grpcio/grpc/_cython/README.rst
new file mode 100644
index 0000000000..c0e66734e8
--- /dev/null
+++ b/src/python/grpcio/grpc/_cython/README.rst
@@ -0,0 +1,52 @@
+GRPC Python Cython layer
+========================
+
+Package for the GRPC Python Cython layer.
+
+What is Cython?
+---------------
+
+Cython is both a superset of the Python language with extensions for dealing
+with C types and a tool that transpiles this superset into C code. It provides
+convenient means of statically typing expressions and of converting Python
+strings to pointers (among other niceties), thus dramatically smoothing the
+Python/C interop by allowing fluid use of APIs in both from the same source.
+See the wonderful `Cython website`_.
+
+Why Cython?
+-----------
+
+- **Python 2 and 3 support**
+ Cython generated C code has precompiler macros to target both Python 2 and
+ Python 3 C APIs, even while acting as a superset of just the Python 2
+ language (e.g. using ``basestring``).
+- **Significantly less semantic noise**
+ A lot of CPython code is just glue, especially human-error-prone
+ ``Py_INCREF``-ing and ``Py_DECREF``-ing around error handlers and such.
+ Cython takes care of that automagically.
+- **Possible PyPy support**
+ One of the major developments in Cython over the past few years was the
+ addition of support for PyPy. We might soon be able to provide such support
+ ourselves through our use of Cython.
+- **Less Python glue code**
+ There existed several adapter layers in and around the original CPython code
+ to smooth the surface exposed to Python due to how much trouble it was to
+ make such a smooth surface via the CPython API alone. Cython makes writing
+ such a surface incredibly easy, so these adapter layers may be removed.
+
+Implications for Users
+----------------------
+
+Nothing additional will be required for users. PyPI packages will contain
+Cython generated C code and thus not necessitate a Cython installation.
+
+Implications for GRPC Developers
+--------------------------------
+
+A typical edit-compile-debug cycle now requires Cython. We install Cython in
+the ``virtualenv`` generated for the Python tests in this repository, so
+initial test runs may take an extra 2+ minutes to complete. Subsequent test
+runs won't reinstall ``Cython`` (unless required versions change and the
+``virtualenv`` doesn't have installed versions that satisfy the change).
+
+.. _`Cython website`: http://cython.org/
diff --git a/src/python/grpcio/grpc/_cython/__init__.py b/src/python/grpcio/grpc/_cython/__init__.py
new file mode 100644
index 0000000000..b89398809f
--- /dev/null
+++ b/src/python/grpcio/grpc/_cython/__init__.py
@@ -0,0 +1,28 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/__init__.py b/src/python/grpcio/grpc/_cython/_cygrpc/__init__.py
new file mode 100644
index 0000000000..b89398809f
--- /dev/null
+++ b/src/python/grpcio/grpc/_cython/_cygrpc/__init__.py
@@ -0,0 +1,28 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/call.pxd b/src/python/grpcio/grpc/_cython/_cygrpc/call.pxd
new file mode 100644
index 0000000000..fe9b81e3d3
--- /dev/null
+++ b/src/python/grpcio/grpc/_cython/_cygrpc/call.pxd
@@ -0,0 +1,37 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from grpc._cython._cygrpc cimport grpc
+
+
+cdef class Call:
+
+ cdef grpc.grpc_call *c_call
+ cdef list references
+
diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/call.pyx b/src/python/grpcio/grpc/_cython/_cygrpc/call.pyx
new file mode 100644
index 0000000000..4349786b3a
--- /dev/null
+++ b/src/python/grpcio/grpc/_cython/_cygrpc/call.pyx
@@ -0,0 +1,82 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+cimport cpython
+
+from grpc._cython._cygrpc cimport records
+
+
+cdef class Call:
+
+ def __cinit__(self):
+ # Create an *empty* call
+ self.c_call = NULL
+ self.references = []
+
+ def start_batch(self, operations, tag):
+ if not self.is_valid:
+ raise ValueError("invalid call object cannot be used from Python")
+ cdef records.Operations cy_operations = records.Operations(operations)
+ cdef records.OperationTag operation_tag = records.OperationTag(tag)
+ operation_tag.operation_call = self
+ operation_tag.batch_operations = cy_operations
+ cpython.Py_INCREF(operation_tag)
+ return grpc.grpc_call_start_batch(
+ self.c_call, cy_operations.c_ops, cy_operations.c_nops,
+ <cpython.PyObject *>operation_tag)
+
+ def cancel(self,
+ grpc.grpc_status_code error_code=grpc.GRPC_STATUS__DO_NOT_USE,
+ details=None):
+ if not self.is_valid:
+ raise ValueError("invalid call object cannot be used from Python")
+ if (details is None) != (error_code == grpc.GRPC_STATUS__DO_NOT_USE):
+ raise ValueError("if error_code is specified, so must details "
+ "(and vice-versa)")
+ if isinstance(details, bytes):
+ pass
+ elif isinstance(details, basestring):
+ details = details.encode()
+ else:
+ raise TypeError("expected details to be str or bytes")
+ if error_code != grpc.GRPC_STATUS__DO_NOT_USE:
+ self.references.append(details)
+ return grpc.grpc_call_cancel_with_status(self.c_call, error_code, details)
+ else:
+ return grpc.grpc_call_cancel(self.c_call)
+
+ def __dealloc__(self):
+ if self.c_call != NULL:
+ grpc.grpc_call_destroy(self.c_call)
+
+ # The object *should* always be valid from Python. Used for debugging.
+ @property
+ def is_valid(self):
+ return self.c_call != NULL
+
diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/channel.pxd b/src/python/grpcio/grpc/_cython/_cygrpc/channel.pxd
new file mode 100644
index 0000000000..3e341bf222
--- /dev/null
+++ b/src/python/grpcio/grpc/_cython/_cygrpc/channel.pxd
@@ -0,0 +1,36 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from grpc._cython._cygrpc cimport grpc
+
+
+cdef class Channel:
+
+ cdef grpc.grpc_channel *c_channel
+ cdef list references
diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/channel.pyx b/src/python/grpcio/grpc/_cython/_cygrpc/channel.pyx
new file mode 100644
index 0000000000..b20313818d
--- /dev/null
+++ b/src/python/grpcio/grpc/_cython/_cygrpc/channel.pyx
@@ -0,0 +1,84 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from grpc._cython._cygrpc cimport call
+from grpc._cython._cygrpc cimport completion_queue
+from grpc._cython._cygrpc cimport credentials
+from grpc._cython._cygrpc cimport records
+
+
+cdef class Channel:
+
+ def __cinit__(self, target, records.ChannelArgs arguments=None,
+ credentials.ClientCredentials client_credentials=None):
+ cdef grpc.grpc_channel_args *c_arguments = NULL
+ self.c_channel = NULL
+ self.references = []
+ if arguments is not None:
+ c_arguments = &arguments.c_args
+ if isinstance(target, bytes):
+ pass
+ elif isinstance(target, basestring):
+ target = target.encode()
+ else:
+ raise TypeError("expected target to be str or bytes")
+ if client_credentials is None:
+ self.c_channel = grpc.grpc_channel_create(target, c_arguments)
+ else:
+ self.c_channel = grpc.grpc_secure_channel_create(
+ client_credentials.c_credentials, target, c_arguments)
+ self.references.append(client_credentials)
+ self.references.append(target)
+ self.references.append(arguments)
+
+ def create_call(self, completion_queue.CompletionQueue queue not None,
+ method, host, records.Timespec deadline not None):
+ if queue.is_shutting_down:
+ raise ValueError("queue must not be shutting down or shutdown")
+ if isinstance(method, bytes):
+ pass
+ elif isinstance(method, basestring):
+ method = method.encode()
+ else:
+ raise TypeError("expected method to be str or bytes")
+ if isinstance(host, bytes):
+ pass
+ elif isinstance(host, basestring):
+ host = host.encode()
+ else:
+ raise TypeError("expected host to be str or bytes")
+ cdef call.Call operation_call = call.Call()
+ operation_call.references = [self, method, host, queue]
+ operation_call.c_call = grpc.grpc_channel_create_call(
+ self.c_channel, queue.c_completion_queue, method, host, deadline.c_time)
+ return operation_call
+
+ def __dealloc__(self):
+ if self.c_channel != NULL:
+ grpc.grpc_channel_destroy(self.c_channel)
diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/completion_queue.pxd b/src/python/grpcio/grpc/_cython/_cygrpc/completion_queue.pxd
new file mode 100644
index 0000000000..fd562ad75b
--- /dev/null
+++ b/src/python/grpcio/grpc/_cython/_cygrpc/completion_queue.pxd
@@ -0,0 +1,39 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from grpc._cython._cygrpc cimport grpc
+
+
+cdef class CompletionQueue:
+
+ cdef grpc.grpc_completion_queue *c_completion_queue
+ cdef object poll_condition
+ cdef bint is_polling
+ cdef bint is_shutting_down
+ cdef bint is_shutdown
diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/completion_queue.pyx b/src/python/grpcio/grpc/_cython/_cygrpc/completion_queue.pyx
new file mode 100644
index 0000000000..886d85360a
--- /dev/null
+++ b/src/python/grpcio/grpc/_cython/_cygrpc/completion_queue.pyx
@@ -0,0 +1,117 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+cimport cpython
+
+from grpc._cython._cygrpc cimport call
+from grpc._cython._cygrpc cimport records
+
+import threading
+import time
+
+
+cdef class CompletionQueue:
+
+ def __cinit__(self):
+ self.c_completion_queue = grpc.grpc_completion_queue_create()
+ self.is_shutting_down = False
+ self.is_shutdown = False
+ self.poll_condition = threading.Condition()
+ self.is_polling = False
+
+ def poll(self, records.Timespec deadline=None):
+ # We name this 'poll' to avoid problems with CPython's expectations for
+ # 'special' methods (like next and __next__).
+ cdef grpc.gpr_timespec c_deadline = grpc.gpr_inf_future
+ cdef records.OperationTag tag = None
+ cdef object user_tag = None
+ cdef call.Call operation_call = None
+ cdef records.CallDetails request_call_details = None
+ cdef records.Metadata request_metadata = None
+ cdef records.Operations batch_operations = None
+ if deadline is not None:
+ c_deadline = deadline.c_time
+ cdef grpc.grpc_event event
+
+ # Poll within a critical section
+ with self.poll_condition:
+ while self.is_polling:
+ self.poll_condition.wait(float(deadline) - time.time())
+ self.is_polling = True
+ with nogil:
+ event = grpc.grpc_completion_queue_next(
+ self.c_completion_queue, c_deadline)
+ with self.poll_condition:
+ self.is_polling = False
+ self.poll_condition.notify()
+
+ if event.type == grpc.GRPC_QUEUE_TIMEOUT:
+ return records.Event(event.type, False, None, None, None, None, None)
+ elif event.type == grpc.GRPC_QUEUE_SHUTDOWN:
+ self.is_shutdown = True
+ return records.Event(event.type, True, None, None, None, None, None)
+ else:
+ if event.tag != NULL:
+ tag = <records.OperationTag>event.tag
+ # We receive event tags only after they've been inc-ref'd elsewhere in
+ # the code.
+ cpython.Py_DECREF(tag)
+ if tag.shutting_down_server is not None:
+ tag.shutting_down_server.notify_shutdown_complete()
+ user_tag = tag.user_tag
+ operation_call = tag.operation_call
+ request_call_details = tag.request_call_details
+ request_metadata = tag.request_metadata
+ batch_operations = tag.batch_operations
+ if tag.is_new_request:
+ # Stuff in the tag not explicitly handled by us needs to live through
+ # the life of the call
+ operation_call.references.extend(tag.references)
+ return records.Event(
+ event.type, event.success, user_tag, operation_call,
+ request_call_details, request_metadata, batch_operations)
+
+ def shutdown(self):
+ grpc.grpc_completion_queue_shutdown(self.c_completion_queue)
+ self.is_shutting_down = True
+
+ def clear(self):
+ if not self.is_shutting_down:
+ raise ValueError('queue must be shutting down to be cleared')
+ while self.poll().type != grpc.GRPC_QUEUE_SHUTDOWN:
+ pass
+
+ def __dealloc__(self):
+ if self.c_completion_queue != NULL:
+ # Ensure shutdown, pump the queue
+ if not self.is_shutting_down:
+ self.shutdown()
+ while not self.is_shutdown:
+ self.poll()
+ grpc.grpc_completion_queue_destroy(self.c_completion_queue)
diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/credentials.pxd b/src/python/grpcio/grpc/_cython/_cygrpc/credentials.pxd
new file mode 100644
index 0000000000..6b74a267e0
--- /dev/null
+++ b/src/python/grpcio/grpc/_cython/_cygrpc/credentials.pxd
@@ -0,0 +1,45 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from grpc._cython._cygrpc cimport grpc
+
+
+cdef class ClientCredentials:
+
+ cdef grpc.grpc_credentials *c_credentials
+ cdef grpc.grpc_ssl_pem_key_cert_pair c_ssl_pem_key_cert_pair
+ cdef list references
+
+
+cdef class ServerCredentials:
+
+ cdef grpc.grpc_server_credentials *c_credentials
+ cdef grpc.grpc_ssl_pem_key_cert_pair *c_ssl_pem_key_cert_pairs
+ cdef size_t c_ssl_pem_key_cert_pairs_count
+ cdef list references
diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/credentials.pyx b/src/python/grpcio/grpc/_cython/_cygrpc/credentials.pyx
new file mode 100644
index 0000000000..2d74702fbd
--- /dev/null
+++ b/src/python/grpcio/grpc/_cython/_cygrpc/credentials.pyx
@@ -0,0 +1,207 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from grpc._cython._cygrpc cimport records
+
+
+cdef class ClientCredentials:
+
+ def __cinit__(self):
+ self.c_credentials = NULL
+ self.c_ssl_pem_key_cert_pair.private_key = NULL
+ self.c_ssl_pem_key_cert_pair.certificate_chain = NULL
+ self.references = []
+
+ # The object *can* be invalid in Python if we fail to make the credentials
+ # (and the core thus returns NULL credentials). Used primarily for debugging.
+ @property
+ def is_valid(self):
+ return self.c_credentials != NULL
+
+ def __dealloc__(self):
+ if self.c_credentials != NULL:
+ grpc.grpc_credentials_release(self.c_credentials)
+
+
+cdef class ServerCredentials:
+
+ def __cinit__(self):
+ self.c_credentials = NULL
+
+ def __dealloc__(self):
+ if self.c_credentials != NULL:
+ grpc.grpc_server_credentials_release(self.c_credentials)
+
+
+def client_credentials_google_default():
+ cdef ClientCredentials credentials = ClientCredentials();
+ credentials.c_credentials = grpc.grpc_google_default_credentials_create()
+ return credentials
+
+def client_credentials_ssl(pem_root_certificates,
+ records.SslPemKeyCertPair ssl_pem_key_cert_pair):
+ if pem_root_certificates is None:
+ pass
+ elif isinstance(pem_root_certificates, bytes):
+ pass
+ elif isinstance(pem_root_certificates, basestring):
+ pem_root_certificates = pem_root_certificates.encode()
+ else:
+ raise TypeError("expected str or bytes for pem_root_certificates")
+ cdef ClientCredentials credentials = ClientCredentials()
+ cdef const char *c_pem_root_certificates = NULL
+ if pem_root_certificates is not None:
+ c_pem_root_certificates = pem_root_certificates
+ credentials.references.append(pem_root_certificates)
+ if ssl_pem_key_cert_pair is not None:
+ credentials.c_credentials = grpc.grpc_ssl_credentials_create(
+ c_pem_root_certificates, &ssl_pem_key_cert_pair.c_pair
+ )
+ credentials.references.append(ssl_pem_key_cert_pair)
+ else:
+ credentials.c_credentials = grpc.grpc_ssl_credentials_create(
+ c_pem_root_certificates, NULL
+ )
+
+def client_credentials_composite_credentials(
+ ClientCredentials credentials_1 not None,
+ ClientCredentials credentials_2 not None):
+ if not credentials_1.is_valid or not credentials_2.is_valid:
+ raise ValueError("passed credentials must both be valid")
+ cdef ClientCredentials credentials = ClientCredentials()
+ credentials.c_credentials = grpc.grpc_composite_credentials_create(
+ credentials_1.c_credentials, credentials_2.c_credentials)
+ credentials.references.append(credentials_1)
+ credentials.references.append(credentials_2)
+ return credentials
+
+def client_credentials_compute_engine():
+ cdef ClientCredentials credentials = ClientCredentials()
+ credentials.c_credentials = grpc.grpc_compute_engine_credentials_create()
+ return credentials
+
+def client_credentials_service_account(
+ json_key, scope, records.Timespec token_lifetime not None):
+ if isinstance(json_key, bytes):
+ pass
+ elif isinstance(json_key, basestring):
+ json_key = json_key.encode()
+ else:
+ raise TypeError("expected json_key to be str or bytes")
+ if isinstance(scope, bytes):
+ pass
+ elif isinstance(scope, basestring):
+ scope = scope.encode()
+ else:
+ raise TypeError("expected scope to be str or bytes")
+ cdef ClientCredentials credentials = ClientCredentials()
+ credentials.c_credentials = grpc.grpc_service_account_credentials_create(
+ json_key, scope, token_lifetime.c_time)
+ credentials.references.extend([json_key, scope])
+ return credentials
+
+#TODO rename to something like client_credentials_service_account_jwt_access.
+def client_credentials_jwt(json_key, records.Timespec token_lifetime not None):
+ if isinstance(json_key, bytes):
+ pass
+ elif isinstance(json_key, basestring):
+ json_key = json_key.encode()
+ else:
+ raise TypeError("expected json_key to be str or bytes")
+ cdef ClientCredentials credentials = ClientCredentials()
+ credentials.c_credentials = grpc.grpc_service_account_jwt_access_credentials_create(
+ json_key, token_lifetime.c_time)
+ credentials.references.append(json_key)
+ return credentials
+
+def client_credentials_refresh_token(json_refresh_token):
+ if isinstance(json_refresh_token, bytes):
+ pass
+ elif isinstance(json_refresh_token, basestring):
+ json_refresh_token = json_refresh_token.encode()
+ else:
+ raise TypeError("expected json_refresh_token to be str or bytes")
+ cdef ClientCredentials credentials = ClientCredentials()
+ credentials.c_credentials = grpc.grpc_refresh_token_credentials_create(
+ json_refresh_token)
+ credentials.references.append(json_refresh_token)
+ return credentials
+
+def client_credentials_iam(authorization_token, authority_selector):
+ if isinstance(authorization_token, bytes):
+ pass
+ elif isinstance(authorization_token, basestring):
+ authorization_token = authorization_token.encode()
+ else:
+ raise TypeError("expected authorization_token to be str or bytes")
+ if isinstance(authority_selector, bytes):
+ pass
+ elif isinstance(authority_selector, basestring):
+ authority_selector = authority_selector.encode()
+ else:
+ raise TypeError("expected authority_selector to be str or bytes")
+ cdef ClientCredentials credentials = ClientCredentials()
+ credentials.c_credentials = grpc.grpc_iam_credentials_create(
+ authorization_token, authority_selector)
+ credentials.references.append(authorization_token)
+ credentials.references.append(authority_selector)
+ return credentials
+
+def server_credentials_ssl(pem_root_certs, pem_key_cert_pairs):
+ if pem_root_certs is None:
+ pass
+ elif isinstance(pem_root_certs, bytes):
+ pass
+ elif isinstance(pem_root_certs, basestring):
+ pem_root_certs = pem_root_certs.encode()
+ else:
+ raise TypeError("expected pem_root_certs to be str or bytes")
+ pem_key_cert_pairs = list(pem_key_cert_pairs)
+ for pair in pem_key_cert_pairs:
+ if not isinstance(pair, records.SslPemKeyCertPair):
+ raise TypeError("expected pem_key_cert_pairs to be sequence of "
+ "records.SslPemKeyCertPair")
+ cdef ServerCredentials credentials = ServerCredentials()
+ credentials.references.append(pem_key_cert_pairs)
+ credentials.references.append(pem_root_certs)
+ credentials.c_ssl_pem_key_cert_pairs_count = len(pem_key_cert_pairs)
+ credentials.c_ssl_pem_key_cert_pairs = (
+ <grpc.grpc_ssl_pem_key_cert_pair *>grpc.gpr_malloc(
+ sizeof(grpc.grpc_ssl_pem_key_cert_pair) *
+ credentials.c_ssl_pem_key_cert_pairs_count
+ ))
+ for i in range(credentials.c_ssl_pem_key_cert_pairs_count):
+ credentials.c_ssl_pem_key_cert_pairs[i] = (
+ (<records.SslPemKeyCertPair>pem_key_cert_pairs[i]).c_pair)
+ credentials.c_credentials = grpc.grpc_ssl_server_credentials_create(
+ pem_root_certs, credentials.c_ssl_pem_key_cert_pairs,
+ credentials.c_ssl_pem_key_cert_pairs_count
+ )
+ return credentials
+
diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxd b/src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxd
new file mode 100644
index 0000000000..d065383587
--- /dev/null
+++ b/src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxd
@@ -0,0 +1,342 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+cimport libc.time
+
+
+cdef extern from "grpc/support/alloc.h":
+ void *gpr_malloc(size_t size)
+ void gpr_free(void *ptr)
+ void *gpr_realloc(void *p, size_t size)
+
+cdef extern from "grpc/support/slice.h":
+ ctypedef struct gpr_slice:
+ # don't worry about writing out the members of gpr_slice; we never access
+ # them directly.
+ pass
+
+ gpr_slice gpr_slice_ref(gpr_slice s)
+ void gpr_slice_unref(gpr_slice s)
+ gpr_slice gpr_slice_new(void *p, size_t len, void (*destroy)(void *))
+ gpr_slice gpr_slice_new_with_len(
+ void *p, size_t len, void (*destroy)(void *, size_t))
+ gpr_slice gpr_slice_malloc(size_t length)
+ gpr_slice gpr_slice_from_copied_string(const char *source)
+ gpr_slice gpr_slice_from_copied_buffer(const char *source, size_t len)
+
+ # Declare functions for function-like macros (because Cython)...
+ void *gpr_slice_start_ptr "GPR_SLICE_START_PTR" (gpr_slice s)
+ size_t gpr_slice_length "GPR_SLICE_LENGTH" (gpr_slice s)
+
+
+cdef extern from "grpc/support/port_platform.h":
+ # As long as the header file gets this type right, we don't need to get this
+ # type exactly; just close enough that the operations will be supported in the
+ # underlying C layers.
+ ctypedef unsigned int gpr_uint32
+
+
+cdef extern from "grpc/support/time.h":
+
+ ctypedef struct gpr_timespec:
+ libc.time.time_t seconds "tv_sec"
+ int nanoseconds "tv_nsec"
+
+ cdef gpr_timespec gpr_time_0
+ cdef gpr_timespec gpr_inf_future
+ cdef gpr_timespec gpr_inf_past
+
+ gpr_timespec gpr_now()
+
+
+cdef extern from "grpc/status.h":
+ ctypedef enum grpc_status_code:
+ GRPC_STATUS_OK
+ GRPC_STATUS_CANCELLED
+ GRPC_STATUS_UNKNOWN
+ GRPC_STATUS_INVALID_ARGUMENT
+ GRPC_STATUS_DEADLINE_EXCEEDED
+ GRPC_STATUS_NOT_FOUND
+ GRPC_STATUS_ALREADY_EXISTS
+ GRPC_STATUS_PERMISSION_DENIED
+ GRPC_STATUS_UNAUTHENTICATED
+ GRPC_STATUS_RESOURCE_EXHAUSTED
+ GRPC_STATUS_FAILED_PRECONDITION
+ GRPC_STATUS_ABORTED
+ GRPC_STATUS_OUT_OF_RANGE
+ GRPC_STATUS_UNIMPLEMENTED
+ GRPC_STATUS_INTERNAL
+ GRPC_STATUS_UNAVAILABLE
+ GRPC_STATUS_DATA_LOSS
+ GRPC_STATUS__DO_NOT_USE
+
+
+cdef extern from "grpc/byte_buffer_reader.h":
+ struct grpc_byte_buffer_reader:
+ # We don't care about the internals
+ pass
+
+
+cdef extern from "grpc/byte_buffer.h":
+ ctypedef struct grpc_byte_buffer:
+ # We don't care about the internals.
+ pass
+
+ grpc_byte_buffer *grpc_raw_byte_buffer_create(gpr_slice *slices,
+ size_t nslices)
+ size_t grpc_byte_buffer_length(grpc_byte_buffer *bb)
+ void grpc_byte_buffer_destroy(grpc_byte_buffer *byte_buffer)
+
+ void grpc_byte_buffer_reader_init(grpc_byte_buffer_reader *reader,
+ grpc_byte_buffer *buffer)
+ int grpc_byte_buffer_reader_next(grpc_byte_buffer_reader *reader,
+ gpr_slice *slice)
+ void grpc_byte_buffer_reader_destroy(grpc_byte_buffer_reader *reader)
+
+
+cdef extern from "grpc/grpc.h":
+
+ ctypedef struct grpc_completion_queue:
+ # We don't care about the internals (and in fact don't know them)
+ pass
+
+ ctypedef struct grpc_channel:
+ # We don't care about the internals (and in fact don't know them)
+ pass
+
+ ctypedef struct grpc_server:
+ # We don't care about the internals (and in fact don't know them)
+ pass
+
+ ctypedef struct grpc_call:
+ # We don't care about the internals (and in fact don't know them)
+ pass
+
+ ctypedef enum grpc_arg_type:
+ grpc_arg_string "GRPC_ARG_STRING"
+ grpc_arg_integer "GRPC_ARG_INTEGER"
+ grpc_arg_pointer "GRPC_ARG_POINTER"
+
+ ctypedef struct grpc_arg_value_pointer:
+ void *address "p"
+ void *(*copy)(void *)
+ void (*destroy)(void *)
+
+ union grpc_arg_value:
+ char *string
+ int integer
+ grpc_arg_value_pointer pointer
+
+ ctypedef struct grpc_arg:
+ grpc_arg_type type
+ char *key
+ grpc_arg_value value
+
+ ctypedef struct grpc_channel_args:
+ size_t arguments_length "num_args"
+ grpc_arg *arguments "args"
+
+ ctypedef enum grpc_call_error:
+ GRPC_CALL_OK
+ GRPC_CALL_ERROR
+ GRPC_CALL_ERROR_NOT_ON_SERVER
+ GRPC_CALL_ERROR_NOT_ON_CLIENT
+ GRPC_CALL_ERROR_ALREADY_ACCEPTED
+ GRPC_CALL_ERROR_ALREADY_INVOKED
+ GRPC_CALL_ERROR_NOT_INVOKED
+ GRPC_CALL_ERROR_ALREADY_FINISHED
+ GRPC_CALL_ERROR_TOO_MANY_OPERATIONS
+ GRPC_CALL_ERROR_INVALID_FLAGS
+ GRPC_CALL_ERROR_INVALID_METADATA
+
+ ctypedef struct grpc_metadata:
+ const char *key
+ const char *value
+ size_t value_length
+ # ignore the 'internal_data.obfuscated' fields.
+
+ ctypedef enum grpc_completion_type:
+ GRPC_QUEUE_SHUTDOWN
+ GRPC_QUEUE_TIMEOUT
+ GRPC_OP_COMPLETE
+
+ ctypedef struct grpc_event:
+ grpc_completion_type type
+ int success
+ void *tag
+
+ ctypedef struct grpc_metadata_array:
+ size_t count
+ size_t capacity
+ grpc_metadata *metadata
+
+ void grpc_metadata_array_init(grpc_metadata_array *array)
+ void grpc_metadata_array_destroy(grpc_metadata_array *array)
+
+ ctypedef struct grpc_call_details:
+ char *method
+ size_t method_capacity
+ char *host
+ size_t host_capacity
+ gpr_timespec deadline
+
+ void grpc_call_details_init(grpc_call_details *details)
+ void grpc_call_details_destroy(grpc_call_details *details)
+
+ ctypedef enum grpc_op_type:
+ GRPC_OP_SEND_INITIAL_METADATA
+ GRPC_OP_SEND_MESSAGE
+ GRPC_OP_SEND_CLOSE_FROM_CLIENT
+ GRPC_OP_SEND_STATUS_FROM_SERVER
+ GRPC_OP_RECV_INITIAL_METADATA
+ GRPC_OP_RECV_MESSAGE
+ GRPC_OP_RECV_STATUS_ON_CLIENT
+ GRPC_OP_RECV_CLOSE_ON_SERVER
+
+ ctypedef struct grpc_op_data_send_initial_metadata:
+ size_t count
+ grpc_metadata *metadata
+
+ ctypedef struct grpc_op_data_send_status_from_server:
+ size_t trailing_metadata_count
+ grpc_metadata *trailing_metadata
+ grpc_status_code status
+ const char *status_details
+
+ ctypedef struct grpc_op_data_recv_status_on_client:
+ grpc_metadata_array *trailing_metadata
+ grpc_status_code *status
+ char **status_details
+ size_t *status_details_capacity
+
+ ctypedef struct grpc_op_data_recv_close_on_server:
+ int *cancelled
+
+ union grpc_op_data:
+ grpc_op_data_send_initial_metadata send_initial_metadata
+ grpc_byte_buffer *send_message
+ grpc_op_data_send_status_from_server send_status_from_server
+ grpc_metadata_array *receive_initial_metadata "recv_initial_metadata"
+ grpc_byte_buffer **receive_message "recv_message"
+ grpc_op_data_recv_status_on_client receive_status_on_client "recv_status_on_client"
+ grpc_op_data_recv_close_on_server receive_close_on_server "recv_close_on_server"
+
+ ctypedef struct grpc_op:
+ grpc_op_type type "op"
+ gpr_uint32 flags
+ grpc_op_data data
+
+ void grpc_init()
+ void grpc_shutdown()
+
+ grpc_completion_queue *grpc_completion_queue_create()
+ grpc_event grpc_completion_queue_next(grpc_completion_queue *cq,
+ gpr_timespec deadline) nogil
+ void grpc_completion_queue_shutdown(grpc_completion_queue *cq)
+ void grpc_completion_queue_destroy(grpc_completion_queue *cq)
+
+ grpc_call_error grpc_call_start_batch(grpc_call *call, const grpc_op *ops,
+ size_t nops, void *tag)
+ grpc_call_error grpc_call_cancel(grpc_call *call)
+ grpc_call_error grpc_call_cancel_with_status(grpc_call *call,
+ grpc_status_code status,
+ const char *description)
+ void grpc_call_destroy(grpc_call *call)
+
+
+ grpc_channel *grpc_channel_create(const char *target,
+ const grpc_channel_args *args)
+ grpc_call *grpc_channel_create_call(grpc_channel *channel,
+ grpc_completion_queue *completion_queue,
+ const char *method, const char *host,
+ gpr_timespec deadline)
+ void grpc_channel_destroy(grpc_channel *channel)
+
+ grpc_server *grpc_server_create(const grpc_channel_args *args)
+ grpc_call_error grpc_server_request_call(
+ grpc_server *server, grpc_call **call, grpc_call_details *details,
+ grpc_metadata_array *request_metadata, grpc_completion_queue
+ *cq_bound_to_call, grpc_completion_queue *cq_for_notification, void
+ *tag_new)
+ void grpc_server_register_completion_queue(grpc_server *server,
+ grpc_completion_queue *cq)
+ int grpc_server_add_http2_port(grpc_server *server, const char *addr)
+ void grpc_server_start(grpc_server *server)
+ void grpc_server_shutdown_and_notify(
+ grpc_server *server, grpc_completion_queue *cq, void *tag)
+ void grpc_server_cancel_all_calls(grpc_server *server)
+ void grpc_server_destroy(grpc_server *server)
+
+
+cdef extern from "grpc/grpc_security.h":
+
+ ctypedef struct grpc_ssl_pem_key_cert_pair:
+ const char *private_key
+ const char *certificate_chain "cert_chain"
+
+ ctypedef struct grpc_credentials:
+ # We don't care about the internals (and in fact don't know them)
+ pass
+
+ grpc_credentials *grpc_google_default_credentials_create()
+ grpc_credentials *grpc_ssl_credentials_create(
+ const char *pem_root_certs, grpc_ssl_pem_key_cert_pair *pem_key_cert_pair)
+
+ grpc_credentials *grpc_composite_credentials_create(grpc_credentials *creds1,
+ grpc_credentials *creds2)
+ grpc_credentials *grpc_compute_engine_credentials_create()
+ grpc_credentials *grpc_service_account_credentials_create(
+ const char *json_key, const char *scope, gpr_timespec token_lifetime)
+ grpc_credentials *grpc_service_account_jwt_access_credentials_create(const char *json_key,
+ gpr_timespec token_lifetime)
+ grpc_credentials *grpc_refresh_token_credentials_create(
+ const char *json_refresh_token)
+ grpc_credentials *grpc_iam_credentials_create(const char *authorization_token,
+ const char *authority_selector)
+ void grpc_credentials_release(grpc_credentials *creds)
+
+ grpc_channel *grpc_secure_channel_create(
+ grpc_credentials *creds, const char *target,
+ const grpc_channel_args *args)
+
+ ctypedef struct grpc_server_credentials:
+ # We don't care about the internals (and in fact don't know them)
+ pass
+
+ grpc_server_credentials *grpc_ssl_server_credentials_create(
+ const char *pem_root_certs,
+ grpc_ssl_pem_key_cert_pair *pem_key_cert_pairs,
+ size_t num_key_cert_pairs);
+ void grpc_server_credentials_release(grpc_server_credentials *creds)
+
+ int grpc_server_add_secure_http2_port(grpc_server *server, const char *addr,
+ grpc_server_credentials *creds)
+
+ grpc_call_error grpc_call_set_credentials(grpc_call *call,
+ grpc_credentials *creds)
diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/records.pxd b/src/python/grpcio/grpc/_cython/_cygrpc/records.pxd
new file mode 100644
index 0000000000..9ee487882a
--- /dev/null
+++ b/src/python/grpcio/grpc/_cython/_cygrpc/records.pxd
@@ -0,0 +1,129 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from grpc._cython._cygrpc cimport grpc
+from grpc._cython._cygrpc cimport call
+from grpc._cython._cygrpc cimport server
+
+
+cdef class Timespec:
+
+ cdef grpc.gpr_timespec c_time
+
+
+cdef class CallDetails:
+
+ cdef grpc.grpc_call_details c_details
+
+
+cdef class OperationTag:
+
+ cdef object user_tag
+ cdef list references
+ # This allows CompletionQueue to notify the Python Server object that the
+ # underlying GRPC core server has shutdown
+ cdef server.Server shutting_down_server
+ cdef call.Call operation_call
+ cdef CallDetails request_call_details
+ cdef Metadata request_metadata
+ cdef Operations batch_operations
+ cdef bint is_new_request
+
+
+cdef class Event:
+
+ cdef readonly grpc.grpc_completion_type type
+ cdef readonly bint success
+ cdef readonly object tag
+
+ # For operations with calls
+ cdef readonly call.Call operation_call
+
+ # For Server.request_call
+ cdef readonly CallDetails request_call_details
+ cdef readonly Metadata request_metadata
+
+ # For Call.start_batch
+ cdef readonly Operations batch_operations
+
+
+cdef class ByteBuffer:
+
+ cdef grpc.grpc_byte_buffer *c_byte_buffer
+
+
+cdef class SslPemKeyCertPair:
+
+ cdef grpc.grpc_ssl_pem_key_cert_pair c_pair
+ cdef readonly object private_key, certificate_chain
+
+
+cdef class ChannelArg:
+
+ cdef grpc.grpc_arg c_arg
+ cdef readonly object key, value
+
+
+cdef class ChannelArgs:
+
+ cdef grpc.grpc_channel_args c_args
+ cdef list args
+
+
+cdef class Metadatum:
+
+ cdef grpc.grpc_metadata c_metadata
+ cdef object _key, _value
+
+
+cdef class Metadata:
+
+ cdef grpc.grpc_metadata_array c_metadata_array
+ cdef object metadata
+
+
+cdef class Operation:
+
+ cdef grpc.grpc_op c_op
+ cdef ByteBuffer _received_message
+ cdef Metadata _received_metadata
+ cdef grpc.grpc_status_code _received_status_code
+ cdef char *_received_status_details
+ cdef size_t _received_status_details_capacity
+ cdef int _received_cancelled
+ cdef readonly bint is_valid
+ cdef object references
+
+
+cdef class Operations:
+
+ cdef grpc.grpc_op *c_ops
+ cdef size_t c_nops
+ cdef list operations
+
diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/records.pyx b/src/python/grpcio/grpc/_cython/_cygrpc/records.pyx
new file mode 100644
index 0000000000..4814769fd2
--- /dev/null
+++ b/src/python/grpcio/grpc/_cython/_cygrpc/records.pyx
@@ -0,0 +1,575 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from grpc._cython._cygrpc cimport grpc
+from grpc._cython._cygrpc cimport call
+from grpc._cython._cygrpc cimport server
+
+
+class StatusCode:
+ ok = grpc.GRPC_STATUS_OK
+ cancelled = grpc.GRPC_STATUS_CANCELLED
+ unknown = grpc.GRPC_STATUS_UNKNOWN
+ invalid_argument = grpc.GRPC_STATUS_INVALID_ARGUMENT
+ deadline_exceeded = grpc.GRPC_STATUS_DEADLINE_EXCEEDED
+ not_found = grpc.GRPC_STATUS_NOT_FOUND
+ already_exists = grpc.GRPC_STATUS_ALREADY_EXISTS
+ permission_denied = grpc.GRPC_STATUS_PERMISSION_DENIED
+ unauthenticated = grpc.GRPC_STATUS_UNAUTHENTICATED
+ resource_exhausted = grpc.GRPC_STATUS_RESOURCE_EXHAUSTED
+ failed_precondition = grpc.GRPC_STATUS_FAILED_PRECONDITION
+ aborted = grpc.GRPC_STATUS_ABORTED
+ out_of_range = grpc.GRPC_STATUS_OUT_OF_RANGE
+ unimplemented = grpc.GRPC_STATUS_UNIMPLEMENTED
+ internal = grpc.GRPC_STATUS_INTERNAL
+ unavailable = grpc.GRPC_STATUS_UNAVAILABLE
+ data_loss = grpc.GRPC_STATUS_DATA_LOSS
+
+
+class CallError:
+ ok = grpc.GRPC_CALL_OK
+ error = grpc.GRPC_CALL_ERROR
+ not_on_server = grpc.GRPC_CALL_ERROR_NOT_ON_SERVER
+ not_on_client = grpc.GRPC_CALL_ERROR_NOT_ON_CLIENT
+ already_accepted = grpc.GRPC_CALL_ERROR_ALREADY_ACCEPTED
+ already_invoked = grpc.GRPC_CALL_ERROR_ALREADY_INVOKED
+ not_invoked = grpc.GRPC_CALL_ERROR_NOT_INVOKED
+ already_finished = grpc.GRPC_CALL_ERROR_ALREADY_FINISHED
+ too_many_operations = grpc.GRPC_CALL_ERROR_TOO_MANY_OPERATIONS
+ invalid_flags = grpc.GRPC_CALL_ERROR_INVALID_FLAGS
+ invalid_metadata = grpc.GRPC_CALL_ERROR_INVALID_METADATA
+
+
+class CompletionType:
+ queue_shutdown = grpc.GRPC_QUEUE_SHUTDOWN
+ queue_timeout = grpc.GRPC_QUEUE_TIMEOUT
+ operation_complete = grpc.GRPC_OP_COMPLETE
+
+
+class OperationType:
+ send_initial_metadata = grpc.GRPC_OP_SEND_INITIAL_METADATA
+ send_message = grpc.GRPC_OP_SEND_MESSAGE
+ send_close_from_client = grpc.GRPC_OP_SEND_CLOSE_FROM_CLIENT
+ send_status_from_server = grpc.GRPC_OP_SEND_STATUS_FROM_SERVER
+ receive_initial_metadata = grpc.GRPC_OP_RECV_INITIAL_METADATA
+ receive_message = grpc.GRPC_OP_RECV_MESSAGE
+ receive_status_on_client = grpc.GRPC_OP_RECV_STATUS_ON_CLIENT
+ receive_close_on_server = grpc.GRPC_OP_RECV_CLOSE_ON_SERVER
+
+
+cdef class Timespec:
+
+ def __cinit__(self, time):
+ if time is None:
+ self.c_time = grpc.gpr_now()
+ elif isinstance(time, float):
+ if time == float("+inf"):
+ self.c_time = grpc.gpr_inf_future
+ elif time == float("-inf"):
+ self.c_time = grpc.gpr_inf_past
+ else:
+ self.c_time.seconds = time
+ self.c_time.nanoseconds = (time - float(self.c_time.seconds)) * 1e9
+ else:
+ raise TypeError("expected time to be float")
+
+ @property
+ def seconds(self):
+ return self.c_time.seconds
+
+ @property
+ def nanoseconds(self):
+ return self.c_time.nanoseconds
+
+ def __float__(self):
+ return <double>self.c_time.seconds + <double>self.c_time.nanoseconds / 1e9
+
+ infinite_future = Timespec(float("+inf"))
+ infinite_past = Timespec(float("-inf"))
+
+
+cdef class CallDetails:
+
+ def __cinit__(self):
+ grpc.grpc_call_details_init(&self.c_details)
+
+ def __dealloc__(self):
+ grpc.grpc_call_details_destroy(&self.c_details)
+
+ @property
+ def method(self):
+ if self.c_details.method != NULL:
+ return <bytes>self.c_details.method
+ else:
+ return None
+
+ @property
+ def host(self):
+ if self.c_details.host != NULL:
+ return <bytes>self.c_details.host
+ else:
+ return None
+
+ @property
+ def deadline(self):
+ timespec = Timespec(float("-inf"))
+ timespec.c_time = self.c_details.deadline
+ return timespec
+
+
+cdef class OperationTag:
+
+ def __cinit__(self, user_tag):
+ self.user_tag = user_tag
+ self.references = []
+
+
+cdef class Event:
+
+ def __cinit__(self, grpc.grpc_completion_type type, bint success,
+ object tag, call.Call operation_call,
+ CallDetails request_call_details,
+ Metadata request_metadata,
+ Operations batch_operations):
+ self.type = type
+ self.success = success
+ self.tag = tag
+ self.operation_call = operation_call
+ self.request_call_details = request_call_details
+ self.request_metadata = request_metadata
+ self.batch_operations = batch_operations
+
+
+cdef class ByteBuffer:
+
+ def __cinit__(self, data):
+ if data is None:
+ self.c_byte_buffer = NULL
+ return
+ if isinstance(data, bytes):
+ pass
+ elif isinstance(data, basestring):
+ data = data.encode()
+ else:
+ raise TypeError("expected value to be of type str or bytes")
+
+ cdef char *c_data = data
+ data_slice = grpc.gpr_slice_from_copied_buffer(c_data, len(data))
+ self.c_byte_buffer = grpc.grpc_raw_byte_buffer_create(
+ &data_slice, 1)
+ grpc.gpr_slice_unref(data_slice)
+
+ def bytes(self):
+ cdef grpc.grpc_byte_buffer_reader reader
+ cdef grpc.gpr_slice data_slice
+ cdef size_t data_slice_length
+ cdef void *data_slice_pointer
+ if self.c_byte_buffer != NULL:
+ grpc.grpc_byte_buffer_reader_init(&reader, self.c_byte_buffer)
+ result = b""
+ while grpc.grpc_byte_buffer_reader_next(&reader, &data_slice):
+ data_slice_pointer = grpc.gpr_slice_start_ptr(data_slice)
+ data_slice_length = grpc.gpr_slice_length(data_slice)
+ result += (<char *>data_slice_pointer)[:data_slice_length]
+ grpc.grpc_byte_buffer_reader_destroy(&reader)
+ return result
+ else:
+ return None
+
+ def __len__(self):
+ if self.c_byte_buffer != NULL:
+ return grpc.grpc_byte_buffer_length(self.c_byte_buffer)
+ else:
+ return 0
+
+ def __str__(self):
+ return self.bytes()
+
+ def __dealloc__(self):
+ if self.c_byte_buffer != NULL:
+ grpc.grpc_byte_buffer_destroy(self.c_byte_buffer)
+
+
+cdef class SslPemKeyCertPair:
+
+ def __cinit__(self, private_key, certificate_chain):
+ if isinstance(private_key, bytes):
+ self.private_key = private_key
+ elif isinstance(private_key, basestring):
+ self.private_key = private_key.encode()
+ else:
+ raise TypeError("expected private_key to be of type str or bytes")
+ if isinstance(certificate_chain, bytes):
+ self.certificate_chain = certificate_chain
+ elif isinstance(certificate_chain, basestring):
+ self.certificate_chain = certificate_chain.encode()
+ else:
+ raise TypeError("expected certificate_chain to be of type str or bytes "
+ "or int")
+ self.c_pair.private_key = self.private_key
+ self.c_pair.certificate_chain = self.certificate_chain
+
+
+cdef class ChannelArg:
+
+ def __cinit__(self, key, value):
+ if isinstance(key, bytes):
+ self.key = key
+ elif isinstance(key, basestring):
+ self.key = key.encode()
+ else:
+ raise TypeError("expected key to be of type str or bytes")
+ if isinstance(value, bytes):
+ self.value = value
+ self.c_arg.type = grpc.GRPC_ARG_STRING
+ self.c_arg.value.string = self.value
+ elif isinstance(value, basestring):
+ self.value = value.encode()
+ self.c_arg.type = grpc.GRPC_ARG_STRING
+ self.c_arg.value.string = self.value
+ elif isinstance(value, int):
+ self.value = int(value)
+ self.c_arg.type = grpc.GRPC_ARG_INTEGER
+ self.c_arg.value.integer = self.value
+ else:
+ raise TypeError("expected value to be of type str or bytes or int")
+ self.c_arg.key = self.key
+
+
+cdef class ChannelArgs:
+
+ def __cinit__(self, args):
+ self.args = list(args)
+ for arg in self.args:
+ if not isinstance(arg, ChannelArg):
+ raise TypeError("expected list of ChannelArg")
+ self.c_args.arguments_length = len(self.args)
+ self.c_args.arguments = <grpc.grpc_arg *>grpc.gpr_malloc(
+ self.c_args.arguments_length*sizeof(grpc.grpc_arg)
+ )
+ for i in range(self.c_args.arguments_length):
+ self.c_args.arguments[i] = (<ChannelArg>self.args[i]).c_arg
+
+ def __dealloc__(self):
+ grpc.gpr_free(self.c_args.arguments)
+
+ def __len__(self):
+ # self.args is never stale; it's only updated from this file
+ return len(self.args)
+
+ def __getitem__(self, size_t i):
+ # self.args is never stale; it's only updated from this file
+ return self.args[i]
+
+
+cdef class Metadatum:
+
+ def __cinit__(self, key, value):
+ if isinstance(key, bytes):
+ self._key = key
+ elif isinstance(key, basestring):
+ self._key = key.encode()
+ else:
+ raise TypeError("expected key to be of type str or bytes")
+ if isinstance(value, bytes):
+ self._value = value
+ elif isinstance(value, basestring):
+ self._value = value.encode()
+ else:
+ raise TypeError("expected value to be of type str or bytes")
+ self.c_metadata.key = self._key
+ self.c_metadata.value = self._value
+ self.c_metadata.value_length = len(self._value)
+
+ @property
+ def key(self):
+ return <bytes>self.c_metadata.key
+
+ @property
+ def value(self):
+ return <bytes>self.c_metadata.value[:self.c_metadata.value_length]
+
+ def __len__(self):
+ return 2
+
+ def __getitem__(self, size_t i):
+ if i == 0:
+ return self.key
+ elif i == 1:
+ return self.value
+ else:
+ raise IndexError("index must be 0 (key) or 1 (value)")
+
+ def __iter__(self):
+ return iter((self.key, self.value))
+
+
+cdef class _MetadataIterator:
+
+ cdef size_t i
+ cdef Metadata metadata
+
+ def __cinit__(self, Metadata metadata not None):
+ self.i = 0
+ self.metadata = metadata
+
+ def __next__(self):
+ if self.i < len(self.metadata):
+ result = self.metadata[self.i]
+ self.i = self.i + 1
+ return result
+ else:
+ raise StopIteration()
+
+
+cdef class Metadata:
+
+ def __cinit__(self, metadata):
+ self.metadata = list(metadata)
+ for metadatum in metadata:
+ if not isinstance(metadatum, Metadatum):
+ raise TypeError("expected list of Metadatum")
+ grpc.grpc_metadata_array_init(&self.c_metadata_array)
+ self.c_metadata_array.count = len(self.metadata)
+ self.c_metadata_array.capacity = len(self.metadata)
+ self.c_metadata_array.metadata = <grpc.grpc_metadata *>grpc.gpr_malloc(
+ self.c_metadata_array.count*sizeof(grpc.grpc_metadata)
+ )
+ for i in range(self.c_metadata_array.count):
+ self.c_metadata_array.metadata[i] = (
+ (<Metadatum>self.metadata[i]).c_metadata)
+
+ def __dealloc__(self):
+ # this frees the allocated memory for the grpc_metadata_array (although
+ # it'd be nice if that were documented somewhere...) TODO(atash): document
+ # this in the C core
+ grpc.grpc_metadata_array_destroy(&self.c_metadata_array)
+
+ def __len__(self):
+ return self.c_metadata_array.count
+
+ def __getitem__(self, size_t i):
+ return Metadatum(
+ key=<bytes>self.c_metadata_array.metadata[i].key,
+ value=<bytes>self.c_metadata_array.metadata[i].value[
+ :self.c_metadata_array.metadata[i].value_length])
+
+ def __iter__(self):
+ return _MetadataIterator(self)
+
+
+cdef class Operation:
+
+ def __cinit__(self):
+ self.references = []
+ self._received_status_details = NULL
+ self._received_status_details_capacity = 0
+ self.is_valid = False
+
+ @property
+ def type(self):
+ return self.c_op.type
+
+ @property
+ def received_message(self):
+ if self.c_op.type != grpc.GRPC_OP_RECV_MESSAGE:
+ raise TypeError("self must be an operation receiving a message")
+ return self._received_message
+
+ @property
+ def received_metadata(self):
+ if (self.c_op.type != grpc.GRPC_OP_RECV_INITIAL_METADATA and
+ self.c_op.type != grpc.GRPC_OP_RECV_STATUS_ON_CLIENT):
+ raise TypeError("self must be an operation receiving metadata")
+ return self._received_metadata
+
+ @property
+ def received_status_code(self):
+ if self.c_op.type != grpc.GRPC_OP_RECV_STATUS_ON_CLIENT:
+ raise TypeError("self must be an operation receiving a status code")
+ return self._received_status_code
+
+ @property
+ def received_status_details(self):
+ if self.c_op.type != grpc.GRPC_OP_RECV_STATUS_ON_CLIENT:
+ raise TypeError("self must be an operation receiving status details")
+ if self._received_status_details:
+ return self._received_status_details
+ else:
+ return None
+
+ @property
+ def received_cancelled(self):
+ if self.c_op.type != grpc.GRPC_OP_RECV_CLOSE_ON_SERVER:
+ raise TypeError("self must be an operation receiving cancellation "
+ "information")
+ return False if self._received_cancelled == 0 else True
+
+ def __dealloc__(self):
+ # We *almost* don't need to do anything; most of the objects are handled by
+ # Python. The remaining one(s) are primitive fields filled in by GRPC core.
+ # This means that we need to clean up after receive_status_on_client.
+ if self.c_op.type == grpc.GRPC_OP_RECV_STATUS_ON_CLIENT:
+ grpc.gpr_free(self._received_status_details)
+
+def operation_send_initial_metadata(Metadata metadata):
+ cdef Operation op = Operation()
+ op.c_op.type = grpc.GRPC_OP_SEND_INITIAL_METADATA
+ op.c_op.data.send_initial_metadata.count = metadata.c_metadata_array.count
+ op.c_op.data.send_initial_metadata.metadata = (
+ metadata.c_metadata_array.metadata)
+ op.references.append(metadata)
+ op.is_valid = True
+ return op
+
+def operation_send_message(data):
+ cdef Operation op = Operation()
+ op.c_op.type = grpc.GRPC_OP_SEND_MESSAGE
+ byte_buffer = ByteBuffer(data)
+ op.c_op.data.send_message = byte_buffer.c_byte_buffer
+ op.references.append(byte_buffer)
+ op.is_valid = True
+ return op
+
+def operation_send_close_from_client():
+ cdef Operation op = Operation()
+ op.c_op.type = grpc.GRPC_OP_SEND_CLOSE_FROM_CLIENT
+ op.is_valid = True
+ return op
+
+def operation_send_status_from_server(
+ Metadata metadata, grpc.grpc_status_code code, details):
+ if isinstance(details, bytes):
+ pass
+ elif isinstance(details, basestring):
+ details = details.encode()
+ else:
+ raise TypeError("expected a str or bytes object for details")
+ cdef Operation op = Operation()
+ op.c_op.type = grpc.GRPC_OP_SEND_STATUS_FROM_SERVER
+ op.c_op.data.send_status_from_server.trailing_metadata_count = (
+ metadata.c_metadata_array.count)
+ op.c_op.data.send_status_from_server.trailing_metadata = (
+ metadata.c_metadata_array.metadata)
+ op.c_op.data.send_status_from_server.status = code
+ op.c_op.data.send_status_from_server.status_details = details
+ op.references.append(metadata)
+ op.references.append(details)
+ op.is_valid = True
+ return op
+
+def operation_receive_initial_metadata():
+ cdef Operation op = Operation()
+ op.c_op.type = grpc.GRPC_OP_RECV_INITIAL_METADATA
+ op._received_metadata = Metadata([])
+ op.c_op.data.receive_initial_metadata = (
+ &op._received_metadata.c_metadata_array)
+ op.is_valid = True
+ return op
+
+def operation_receive_message():
+ cdef Operation op = Operation()
+ op.c_op.type = grpc.GRPC_OP_RECV_MESSAGE
+ op._received_message = ByteBuffer(None)
+ # n.b. the c_op.data.receive_message field needs to be deleted by us,
+ # anyway, so we just let that be handled by the ByteBuffer() we allocated
+ # the line before.
+ op.c_op.data.receive_message = &op._received_message.c_byte_buffer
+ op.is_valid = True
+ return op
+
+def operation_receive_status_on_client():
+ cdef Operation op = Operation()
+ op.c_op.type = grpc.GRPC_OP_RECV_STATUS_ON_CLIENT
+ op._received_metadata = Metadata([])
+ op.c_op.data.receive_status_on_client.trailing_metadata = (
+ &op._received_metadata.c_metadata_array)
+ op.c_op.data.receive_status_on_client.status = (
+ &op._received_status_code)
+ op.c_op.data.receive_status_on_client.status_details = (
+ &op._received_status_details)
+ op.c_op.data.receive_status_on_client.status_details_capacity = (
+ &op._received_status_details_capacity)
+ op.is_valid = True
+ return op
+
+def operation_receive_close_on_server():
+ cdef Operation op = Operation()
+ op.c_op.type = grpc.GRPC_OP_RECV_CLOSE_ON_SERVER
+ op.c_op.data.receive_close_on_server.cancelled = &op._received_cancelled
+ op.is_valid = True
+ return op
+
+
+cdef class _OperationsIterator:
+
+ cdef size_t i
+ cdef Operations operations
+
+ def __cinit__(self, Operations operations not None):
+ self.i = 0
+ self.operations = operations
+
+ def __next__(self):
+ if self.i < len(self.operations):
+ result = self.operations[self.i]
+ self.i = self.i + 1
+ return result
+ else:
+ raise StopIteration()
+
+
+cdef class Operations:
+
+ def __cinit__(self, operations):
+ self.operations = list(operations) # normalize iterable
+ self.c_ops = NULL
+ self.c_nops = 0
+ for operation in self.operations:
+ if not isinstance(operation, Operation):
+ raise TypeError("expected operations to be iterable of Operation")
+ self.c_nops = len(self.operations)
+ self.c_ops = <grpc.grpc_op *>grpc.gpr_malloc(
+ sizeof(grpc.grpc_op)*self.c_nops)
+ for i in range(self.c_nops):
+ self.c_ops[i] = (<Operation>(self.operations[i])).c_op
+
+ def __len__(self):
+ return self.c_nops
+
+ def __getitem__(self, size_t i):
+ # self.operations is never stale; it's only updated from this file
+ return self.operations[i]
+
+ def __dealloc__(self):
+ grpc.gpr_free(self.c_ops)
+
+ def __iter__(self):
+ return _OperationsIterator(self)
+
diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/server.pxd b/src/python/grpcio/grpc/_cython/_cygrpc/server.pxd
new file mode 100644
index 0000000000..0257542a03
--- /dev/null
+++ b/src/python/grpcio/grpc/_cython/_cygrpc/server.pxd
@@ -0,0 +1,45 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from grpc._cython._cygrpc cimport grpc
+from grpc._cython._cygrpc cimport completion_queue
+
+
+cdef class Server:
+
+ cdef grpc.grpc_server *c_server
+ cdef bint is_started # start has been called
+ cdef bint is_shutting_down # shutdown has been called
+ cdef bint is_shutdown # notification of complete shutdown received
+ # used at dealloc when user forgets to shutdown
+ cdef completion_queue.CompletionQueue backup_shutdown_queue
+ cdef list references
+ cdef list registered_completion_queues
+
+ cdef notify_shutdown_complete(self)
diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/server.pyx b/src/python/grpcio/grpc/_cython/_cygrpc/server.pyx
new file mode 100644
index 0000000000..dcf9d38337
--- /dev/null
+++ b/src/python/grpcio/grpc/_cython/_cygrpc/server.pyx
@@ -0,0 +1,158 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+cimport cpython
+
+from grpc._cython._cygrpc cimport call
+from grpc._cython._cygrpc cimport completion_queue
+from grpc._cython._cygrpc cimport credentials
+from grpc._cython._cygrpc cimport records
+
+import time
+
+
+cdef class Server:
+
+ def __cinit__(self, records.ChannelArgs arguments=None):
+ cdef grpc.grpc_channel_args *c_arguments = NULL
+ self.references = []
+ self.registered_completion_queues = []
+ if arguments is not None:
+ c_arguments = &arguments.c_args
+ self.references.append(arguments)
+ self.c_server = grpc.grpc_server_create(c_arguments)
+ self.is_started = False
+ self.is_shutting_down = False
+ self.is_shutdown = False
+
+ def request_call(
+ self, completion_queue.CompletionQueue call_queue not None,
+ completion_queue.CompletionQueue server_queue not None, tag):
+ if not self.is_started or self.is_shutting_down:
+ raise ValueError("server must be started and not shutting down")
+ if server_queue not in self.registered_completion_queues:
+ raise ValueError("server_queue must be a registered completion queue")
+ cdef records.OperationTag operation_tag = records.OperationTag(tag)
+ operation_tag.operation_call = call.Call()
+ operation_tag.request_call_details = records.CallDetails()
+ operation_tag.request_metadata = records.Metadata([])
+ operation_tag.references.extend([self, call_queue, server_queue])
+ operation_tag.is_new_request = True
+ operation_tag.batch_operations = records.Operations([])
+ cpython.Py_INCREF(operation_tag)
+ return grpc.grpc_server_request_call(
+ self.c_server, &operation_tag.operation_call.c_call,
+ &operation_tag.request_call_details.c_details,
+ &operation_tag.request_metadata.c_metadata_array,
+ call_queue.c_completion_queue, server_queue.c_completion_queue,
+ <cpython.PyObject *>operation_tag)
+
+ def register_completion_queue(
+ self, completion_queue.CompletionQueue queue not None):
+ if self.is_started:
+ raise ValueError("cannot register completion queues after start")
+ grpc.grpc_server_register_completion_queue(
+ self.c_server, queue.c_completion_queue)
+ self.registered_completion_queues.append(queue)
+
+ def start(self):
+ if self.is_started:
+ raise ValueError("the server has already started")
+ self.backup_shutdown_queue = completion_queue.CompletionQueue()
+ self.register_completion_queue(self.backup_shutdown_queue)
+ self.is_started = True
+ grpc.grpc_server_start(self.c_server)
+
+ def add_http2_port(self, address,
+ credentials.ServerCredentials server_credentials=None):
+ if isinstance(address, bytes):
+ pass
+ elif isinstance(address, basestring):
+ address = address.encode()
+ else:
+ raise TypeError("expected address to be a str or bytes")
+ self.references.append(address)
+ if server_credentials is not None:
+ self.references.append(server_credentials)
+ return grpc.grpc_server_add_secure_http2_port(
+ self.c_server, address, server_credentials.c_credentials)
+ else:
+ return grpc.grpc_server_add_http2_port(self.c_server, address)
+
+ def shutdown(self, completion_queue.CompletionQueue queue not None, tag):
+ cdef records.OperationTag operation_tag
+ if queue.is_shutting_down:
+ raise ValueError("queue must be live")
+ elif not self.is_started:
+ raise ValueError("the server hasn't started yet")
+ elif self.is_shutting_down:
+ return
+ elif queue not in self.registered_completion_queues:
+ raise ValueError("expected registered completion queue")
+ else:
+ self.is_shutting_down = True
+ operation_tag = records.OperationTag(tag)
+ operation_tag.shutting_down_server = self
+ operation_tag.references.extend([self, queue])
+ cpython.Py_INCREF(operation_tag)
+ grpc.grpc_server_shutdown_and_notify(
+ self.c_server, queue.c_completion_queue,
+ <cpython.PyObject *>operation_tag)
+
+ cdef notify_shutdown_complete(self):
+ # called only by a completion queue on receiving our shutdown operation tag
+ self.is_shutdown = True
+
+ def cancel_all_calls(self):
+ if not self.is_shutting_down:
+ raise ValueError("the server must be shutting down to cancel all calls")
+ elif self.is_shutdown:
+ return
+ else:
+ grpc.grpc_server_cancel_all_calls(self.c_server)
+
+ def __dealloc__(self):
+ if self.c_server != NULL:
+ if not self.is_started:
+ pass
+ elif self.is_shutdown:
+ pass
+ elif not self.is_shutting_down:
+ # the user didn't call shutdown - use our backup queue
+ self.shutdown(self.backup_shutdown_queue, None)
+ # and now we wait
+ while not self.is_shutdown:
+ self.backup_shutdown_queue.poll()
+ else:
+ # We're in the process of shutting down, but have not shutdown; can't do
+ # much but repeatedly release the GIL and wait
+ while not self.is_shutdown:
+ time.sleep(0)
+ grpc.grpc_server_destroy(self.c_server)
+
diff --git a/src/python/grpcio/grpc/_cython/adapter_low.py b/src/python/grpcio/grpc/_cython/adapter_low.py
new file mode 100644
index 0000000000..2bb468eece
--- /dev/null
+++ b/src/python/grpcio/grpc/_cython/adapter_low.py
@@ -0,0 +1,106 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+# Adapter from grpc._cython.types to the surface expected by
+# grpc._adapter._intermediary_low.
+#
+# TODO(atash): Once this is plugged into grpc._adapter._intermediary_low, remove
+# both grpc._adapter._intermediary_low and this file. The fore and rear links in
+# grpc._adapter should be able to use grpc._cython.types directly.
+
+from grpc._adapter import _types as type_interfaces
+from grpc._cython import cygrpc
+
+
+class ClientCredentials(object):
+ def __init__(self):
+ raise NotImplementedError()
+
+ @staticmethod
+ def google_default():
+ raise NotImplementedError()
+
+ @staticmethod
+ def ssl():
+ raise NotImplementedError()
+
+ @staticmethod
+ def composite():
+ raise NotImplementedError()
+
+ @staticmethod
+ def compute_engine():
+ raise NotImplementedError()
+
+ @staticmethod
+ def service_account():
+ raise NotImplementedError()
+
+ @staticmethod
+ def jwt():
+ raise NotImplementedError()
+
+ @staticmethod
+ def refresh_token():
+ raise NotImplementedError()
+
+ @staticmethod
+ def iam():
+ raise NotImplementedError()
+
+
+class ServerCredentials(object):
+ def __init__(self):
+ raise NotImplementedError()
+
+ @staticmethod
+ def ssl():
+ raise NotImplementedError()
+
+
+class CompletionQueue(type_interfaces.CompletionQueue):
+ def __init__(self):
+ raise NotImplementedError()
+
+
+class Call(type_interfaces.Call):
+ def __init__(self):
+ raise NotImplementedError()
+
+
+class Channel(type_interfaces.Channel):
+ def __init__(self):
+ raise NotImplementedError()
+
+
+class Server(type_interfaces.Server):
+ def __init__(self):
+ raise NotImplementedError()
+
diff --git a/src/python/grpcio/grpc/_cython/cygrpc.pyx b/src/python/grpcio/grpc/_cython/cygrpc.pyx
new file mode 100644
index 0000000000..f4d9661580
--- /dev/null
+++ b/src/python/grpcio/grpc/_cython/cygrpc.pyx
@@ -0,0 +1,107 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+cimport cpython
+
+from grpc._cython._cygrpc cimport grpc
+from grpc._cython._cygrpc cimport call
+from grpc._cython._cygrpc cimport channel
+from grpc._cython._cygrpc cimport credentials
+from grpc._cython._cygrpc cimport completion_queue
+from grpc._cython._cygrpc cimport records
+from grpc._cython._cygrpc cimport server
+
+from grpc._cython._cygrpc import call
+from grpc._cython._cygrpc import channel
+from grpc._cython._cygrpc import credentials
+from grpc._cython._cygrpc import completion_queue
+from grpc._cython._cygrpc import records
+from grpc._cython._cygrpc import server
+
+StatusCode = records.StatusCode
+CallError = records.CallError
+CompletionType = records.CompletionType
+OperationType = records.OperationType
+Timespec = records.Timespec
+CallDetails = records.CallDetails
+Event = records.Event
+ByteBuffer = records.ByteBuffer
+SslPemKeyCertPair = records.SslPemKeyCertPair
+ChannelArg = records.ChannelArg
+ChannelArgs = records.ChannelArgs
+Metadatum = records.Metadatum
+Metadata = records.Metadata
+Operation = records.Operation
+
+operation_send_initial_metadata = records.operation_send_initial_metadata
+operation_send_message = records.operation_send_message
+operation_send_close_from_client = records.operation_send_close_from_client
+operation_send_status_from_server = records.operation_send_status_from_server
+operation_receive_initial_metadata = records.operation_receive_initial_metadata
+operation_receive_message = records.operation_receive_message
+operation_receive_status_on_client = records.operation_receive_status_on_client
+operation_receive_close_on_server = records.operation_receive_close_on_server
+
+Operations = records.Operations
+
+ClientCredentials = credentials.ClientCredentials
+ServerCredentials = credentials.ServerCredentials
+
+client_credentials_google_default = (
+ credentials.client_credentials_google_default)
+client_credentials_ssl = credentials.client_credentials_ssl
+client_credentials_composite_credentials = (
+ credentials.client_credentials_composite_credentials)
+client_credentials_compute_engine = (
+ credentials.client_credentials_compute_engine)
+client_credentials_jwt = credentials.client_credentials_jwt
+client_credentials_refresh_token = credentials.client_credentials_refresh_token
+client_credentials_iam = credentials.client_credentials_iam
+server_credentials_ssl = credentials.server_credentials_ssl
+
+CompletionQueue = completion_queue.CompletionQueue
+Channel = channel.Channel
+Server = server.Server
+Call = call.Call
+
+
+#
+# Global state
+#
+
+cdef class _ModuleState:
+
+ def __cinit__(self):
+ grpc.grpc_init()
+
+ def __dealloc__(self):
+ grpc.grpc_shutdown()
+
+_module_state = _ModuleState()
+
diff --git a/src/python/grpcio/grpc/_links/__init__.py b/src/python/grpcio/grpc/_links/__init__.py
new file mode 100644
index 0000000000..7086519106
--- /dev/null
+++ b/src/python/grpcio/grpc/_links/__init__.py
@@ -0,0 +1,30 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
diff --git a/src/python/grpcio/grpc/_links/invocation.py b/src/python/grpcio/grpc/_links/invocation.py
new file mode 100644
index 0000000000..0058ae91f8
--- /dev/null
+++ b/src/python/grpcio/grpc/_links/invocation.py
@@ -0,0 +1,363 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""The RPC-invocation-side bridge between RPC Framework and GRPC-on-the-wire."""
+
+import abc
+import enum
+import logging
+import threading
+import time
+
+from grpc._adapter import _intermediary_low
+from grpc.framework.foundation import activated
+from grpc.framework.foundation import logging_pool
+from grpc.framework.foundation import relay
+from grpc.framework.interfaces.links import links
+
+
+@enum.unique
+class _Read(enum.Enum):
+ AWAITING_METADATA = 'awaiting metadata'
+ READING = 'reading'
+ AWAITING_ALLOWANCE = 'awaiting allowance'
+ CLOSED = 'closed'
+
+
+@enum.unique
+class _HighWrite(enum.Enum):
+ OPEN = 'open'
+ CLOSED = 'closed'
+
+
+@enum.unique
+class _LowWrite(enum.Enum):
+ OPEN = 'OPEN'
+ ACTIVE = 'ACTIVE'
+ CLOSED = 'CLOSED'
+
+
+class _RPCState(object):
+
+ def __init__(
+ self, call, request_serializer, response_deserializer, sequence_number,
+ read, allowance, high_write, low_write):
+ self.call = call
+ self.request_serializer = request_serializer
+ self.response_deserializer = response_deserializer
+ self.sequence_number = sequence_number
+ self.read = read
+ self.allowance = allowance
+ self.high_write = high_write
+ self.low_write = low_write
+
+
+class _Kernel(object):
+
+ def __init__(
+ self, channel, host, request_serializers, response_deserializers,
+ ticket_relay):
+ self._lock = threading.Lock()
+ self._channel = channel
+ self._host = host
+ self._request_serializers = request_serializers
+ self._response_deserializers = response_deserializers
+ self._relay = ticket_relay
+
+ self._completion_queue = None
+ self._rpc_states = None
+ self._pool = None
+
+ def _on_write_event(self, operation_id, unused_event, rpc_state):
+ if rpc_state.high_write is _HighWrite.CLOSED:
+ rpc_state.call.complete(operation_id)
+ rpc_state.low_write = _LowWrite.CLOSED
+ else:
+ ticket = links.Ticket(
+ operation_id, rpc_state.sequence_number, None, None, None, None, 1,
+ None, None, None, None, None, None)
+ rpc_state.sequence_number += 1
+ self._relay.add_value(ticket)
+ rpc_state.low_write = _LowWrite.OPEN
+
+ def _on_read_event(self, operation_id, event, rpc_state):
+ if event.bytes is None:
+ rpc_state.read = _Read.CLOSED
+ else:
+ if 0 < rpc_state.allowance:
+ rpc_state.allowance -= 1
+ rpc_state.call.read(operation_id)
+ else:
+ rpc_state.read = _Read.AWAITING_ALLOWANCE
+ ticket = links.Ticket(
+ operation_id, rpc_state.sequence_number, None, None, None, None, None,
+ None, rpc_state.response_deserializer(event.bytes), None, None, None,
+ None)
+ rpc_state.sequence_number += 1
+ self._relay.add_value(ticket)
+
+ def _on_metadata_event(self, operation_id, event, rpc_state):
+ rpc_state.allowance -= 1
+ rpc_state.call.read(operation_id)
+ rpc_state.read = _Read.READING
+ ticket = links.Ticket(
+ operation_id, rpc_state.sequence_number, None, None,
+ links.Ticket.Subscription.FULL, None, None, event.metadata, None, None,
+ None, None, None)
+ rpc_state.sequence_number += 1
+ self._relay.add_value(ticket)
+
+ def _on_finish_event(self, operation_id, event, rpc_state):
+ self._rpc_states.pop(operation_id, None)
+ if event.status.code is _intermediary_low.Code.OK:
+ termination = links.Ticket.Termination.COMPLETION
+ elif event.status.code is _intermediary_low.Code.CANCELLED:
+ termination = links.Ticket.Termination.CANCELLATION
+ elif event.status.code is _intermediary_low.Code.DEADLINE_EXCEEDED:
+ termination = links.Ticket.Termination.EXPIRATION
+ else:
+ termination = links.Ticket.Termination.TRANSMISSION_FAILURE
+ ticket = links.Ticket(
+ operation_id, rpc_state.sequence_number, None, None, None, None, None,
+ None, None, event.metadata, event.status.code, event.status.details,
+ termination)
+ rpc_state.sequence_number += 1
+ self._relay.add_value(ticket)
+
+ def _spin(self, completion_queue):
+ while True:
+ event = completion_queue.get(None)
+ if event.kind is _intermediary_low.Event.Kind.STOP:
+ return
+ operation_id = event.tag
+ with self._lock:
+ if self._completion_queue is None:
+ continue
+ rpc_state = self._rpc_states.get(operation_id)
+ if rpc_state is not None:
+ if event.kind is _intermediary_low.Event.Kind.WRITE_ACCEPTED:
+ self._on_write_event(operation_id, event, rpc_state)
+ elif event.kind is _intermediary_low.Event.Kind.METADATA_ACCEPTED:
+ self._on_metadata_event(operation_id, event, rpc_state)
+ elif event.kind is _intermediary_low.Event.Kind.READ_ACCEPTED:
+ self._on_read_event(operation_id, event, rpc_state)
+ elif event.kind is _intermediary_low.Event.Kind.FINISH:
+ self._on_finish_event(operation_id, event, rpc_state)
+ elif event.kind is _intermediary_low.Event.Kind.COMPLETE_ACCEPTED:
+ pass
+ else:
+ logging.error('Illegal RPC event! %s', (event,))
+
+ def _invoke(
+ self, operation_id, group, method, initial_metadata, payload, termination,
+ timeout, allowance):
+ """Invoke an RPC.
+
+ Args:
+ operation_id: Any object to be used as an operation ID for the RPC.
+ group: The group to which the RPC method belongs.
+ method: The RPC method name.
+ initial_metadata: The initial metadata object for the RPC.
+ payload: A payload object for the RPC or None if no payload was given at
+ invocation-time.
+ termination: A links.Ticket.Termination value or None indicated whether or
+ not more writes will follow from this side of the RPC.
+ timeout: A duration of time in seconds to allow for the RPC.
+ allowance: The number of payloads (beyond the free first one) that the
+ local ticket exchange mate has granted permission to be read.
+ """
+ if termination is links.Ticket.Termination.COMPLETION:
+ high_write = _HighWrite.CLOSED
+ elif termination is None:
+ high_write = _HighWrite.OPEN
+ else:
+ return
+
+ request_serializer = self._request_serializers.get((group, method))
+ response_deserializer = self._response_deserializers.get((group, method))
+ if request_serializer is None or response_deserializer is None:
+ cancellation_ticket = links.Ticket(
+ operation_id, 0, None, None, None, None, None, None, None, None, None,
+ None, links.Ticket.Termination.CANCELLATION)
+ self._relay.add_value(cancellation_ticket)
+ return
+
+ call = _intermediary_low.Call(
+ self._channel, self._completion_queue, '/%s/%s' % (group, method),
+ self._host, time.time() + timeout)
+ if initial_metadata is not None:
+ for metadata_key, metadata_value in initial_metadata:
+ call.add_metadata(metadata_key, metadata_value)
+ call.invoke(self._completion_queue, operation_id, operation_id)
+ if payload is None:
+ if high_write is _HighWrite.CLOSED:
+ call.complete(operation_id)
+ low_write = _LowWrite.CLOSED
+ else:
+ low_write = _LowWrite.OPEN
+ else:
+ call.write(request_serializer(payload), operation_id)
+ low_write = _LowWrite.ACTIVE
+ self._rpc_states[operation_id] = _RPCState(
+ call, request_serializer, response_deserializer, 0,
+ _Read.AWAITING_METADATA, 1 if allowance is None else (1 + allowance),
+ high_write, low_write)
+
+ def _advance(self, operation_id, rpc_state, payload, termination, allowance):
+ if payload is not None:
+ rpc_state.call.write(rpc_state.request_serializer(payload), operation_id)
+ rpc_state.low_write = _LowWrite.ACTIVE
+
+ if allowance is not None:
+ if rpc_state.read is _Read.AWAITING_ALLOWANCE:
+ rpc_state.allowance += allowance - 1
+ rpc_state.call.read(operation_id)
+ rpc_state.read = _Read.READING
+ else:
+ rpc_state.allowance += allowance
+
+ if termination is links.Ticket.Termination.COMPLETION:
+ rpc_state.high_write = _HighWrite.CLOSED
+ if rpc_state.low_write is _LowWrite.OPEN:
+ rpc_state.call.complete(operation_id)
+ rpc_state.low_write = _LowWrite.CLOSED
+ elif termination is not None:
+ rpc_state.call.cancel()
+
+ def add_ticket(self, ticket):
+ with self._lock:
+ if self._completion_queue is None:
+ return
+ if ticket.sequence_number == 0:
+ self._invoke(
+ ticket.operation_id, ticket.group, ticket.method,
+ ticket.initial_metadata, ticket.payload, ticket.termination,
+ ticket.timeout, ticket.allowance)
+ else:
+ rpc_state = self._rpc_states.get(ticket.operation_id)
+ if rpc_state is not None:
+ self._advance(
+ ticket.operation_id, rpc_state, ticket.payload,
+ ticket.termination, ticket.allowance)
+
+ def start(self):
+ """Starts this object.
+
+ This method must be called before attempting to exchange tickets with this
+ object.
+ """
+ with self._lock:
+ self._completion_queue = _intermediary_low.CompletionQueue()
+ self._rpc_states = {}
+ self._pool = logging_pool.pool(1)
+ self._pool.submit(self._spin, self._completion_queue)
+
+ def stop(self):
+ """Stops this object.
+
+ This method must be called for proper termination of this object, and no
+ attempts to exchange tickets with this object may be made after this method
+ has been called.
+ """
+ with self._lock:
+ self._completion_queue.stop()
+ self._completion_queue = None
+ pool = self._pool
+ self._pool = None
+ self._rpc_states = None
+ pool.shutdown(wait=True)
+
+
+class InvocationLink(links.Link, activated.Activated):
+ """A links.Link for use on the invocation-side of a gRPC connection.
+
+ Implementations of this interface are only valid for use when activated.
+ """
+ __metaclass__ = abc.ABCMeta
+
+
+class _InvocationLink(InvocationLink):
+
+ def __init__(
+ self, channel, host, request_serializers, response_deserializers):
+ self._relay = relay.relay(None)
+ self._kernel = _Kernel(
+ channel, host, request_serializers, response_deserializers, self._relay)
+
+ def _start(self):
+ self._relay.start()
+ self._kernel.start()
+ return self
+
+ def _stop(self):
+ self._kernel.stop()
+ self._relay.stop()
+
+ def accept_ticket(self, ticket):
+ """See links.Link.accept_ticket for specification."""
+ self._kernel.add_ticket(ticket)
+
+ def join_link(self, link):
+ """See links.Link.join_link for specification."""
+ self._relay.set_behavior(link.accept_ticket)
+
+ def __enter__(self):
+ """See activated.Activated.__enter__ for specification."""
+ return self._start()
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ """See activated.Activated.__exit__ for specification."""
+ self._stop()
+ return False
+
+ def start(self):
+ """See activated.Activated.start for specification."""
+ return self._start()
+
+ def stop(self):
+ """See activated.Activated.stop for specification."""
+ self._stop()
+
+
+def invocation_link(channel, host, request_serializers, response_deserializers):
+ """Creates an InvocationLink.
+
+ Args:
+ channel: A channel for use by the link.
+ host: The host to specify when invoking RPCs.
+ request_serializers: A dict from group-method pair to request object
+ serialization behavior.
+ response_deserializers: A dict from group-method pair to response object
+ deserialization behavior.
+
+ Returns:
+ An InvocationLink.
+ """
+ return _InvocationLink(
+ channel, host, request_serializers, response_deserializers)
diff --git a/src/python/grpcio/grpc/_links/service.py b/src/python/grpcio/grpc/_links/service.py
new file mode 100644
index 0000000000..7783e91824
--- /dev/null
+++ b/src/python/grpcio/grpc/_links/service.py
@@ -0,0 +1,402 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""The RPC-service-side bridge between RPC Framework and GRPC-on-the-wire."""
+
+import abc
+import enum
+import logging
+import threading
+import time
+
+from grpc._adapter import _intermediary_low
+from grpc.framework.foundation import logging_pool
+from grpc.framework.foundation import relay
+from grpc.framework.interfaces.links import links
+
+
+@enum.unique
+class _Read(enum.Enum):
+ READING = 'reading'
+ AWAITING_ALLOWANCE = 'awaiting allowance'
+ CLOSED = 'closed'
+
+
+@enum.unique
+class _HighWrite(enum.Enum):
+ OPEN = 'open'
+ CLOSED = 'closed'
+
+
+@enum.unique
+class _LowWrite(enum.Enum):
+ """The possible categories of low-level write state."""
+
+ OPEN = 'OPEN'
+ ACTIVE = 'ACTIVE'
+ CLOSED = 'CLOSED'
+
+
+class _RPCState(object):
+
+ def __init__(
+ self, request_deserializer, response_serializer, sequence_number, read,
+ allowance, high_write, low_write, premetadataed, terminal_metadata, code,
+ message):
+ self.request_deserializer = request_deserializer
+ self.response_serializer = response_serializer
+ self.sequence_number = sequence_number
+ self.read = read
+ self.allowance = allowance
+ self.high_write = high_write
+ self.low_write = low_write
+ self.premetadataed = premetadataed
+ self.terminal_metadata = terminal_metadata
+ self.code = code
+ self.message = message
+
+
+def _metadatafy(call, metadata):
+ for metadata_key, metadata_value in metadata:
+ call.add_metadata(metadata_key, metadata_value)
+
+
+class _Kernel(object):
+
+ def __init__(self, request_deserializers, response_serializers, ticket_relay):
+ self._lock = threading.Lock()
+ self._request_deserializers = request_deserializers
+ self._response_serializers = response_serializers
+ self._relay = ticket_relay
+
+ self._completion_queue = None
+ self._server = None
+ self._rpc_states = {}
+ self._pool = None
+
+ def _on_service_acceptance_event(self, event, server):
+ server.service(None)
+
+ service_acceptance = event.service_acceptance
+ call = service_acceptance.call
+ call.accept(self._completion_queue, call)
+ try:
+ group, method = service_acceptance.method.split('/')[1:3]
+ except ValueError:
+ logging.info('Illegal path "%s"!', service_acceptance.method)
+ return
+ request_deserializer = self._request_deserializers.get((group, method))
+ response_serializer = self._response_serializers.get((group, method))
+ if request_deserializer is None or response_serializer is None:
+ # TODO(nathaniel): Terminate the RPC with code NOT_FOUND.
+ call.cancel()
+ return
+
+ call.read(call)
+ self._rpc_states[call] = _RPCState(
+ request_deserializer, response_serializer, 1, _Read.READING, 0,
+ _HighWrite.OPEN, _LowWrite.OPEN, False, None, None, None)
+ ticket = links.Ticket(
+ call, 0, group, method, links.Ticket.Subscription.FULL,
+ service_acceptance.deadline - time.time(), None, event.metadata, None,
+ None, None, None, None)
+ self._relay.add_value(ticket)
+
+ def _on_read_event(self, event):
+ call = event.tag
+ rpc_state = self._rpc_states.get(call, None)
+ if rpc_state is None:
+ return
+
+ if event.bytes is None:
+ rpc_state.read = _Read.CLOSED
+ payload = None
+ termination = links.Ticket.Termination.COMPLETION
+ else:
+ if 0 < rpc_state.allowance:
+ rpc_state.allowance -= 1
+ call.read(call)
+ else:
+ rpc_state.read = _Read.AWAITING_ALLOWANCE
+ payload = rpc_state.request_deserializer(event.bytes)
+ termination = None
+ ticket = links.Ticket(
+ call, rpc_state.sequence_number, None, None, None, None, None, None,
+ payload, None, None, None, termination)
+ rpc_state.sequence_number += 1
+ self._relay.add_value(ticket)
+
+ def _on_write_event(self, event):
+ call = event.tag
+ rpc_state = self._rpc_states.get(call, None)
+ if rpc_state is None:
+ return
+
+ if rpc_state.high_write is _HighWrite.CLOSED:
+ if rpc_state.terminal_metadata is not None:
+ _metadatafy(call, rpc_state.terminal_metadata)
+ call.status(
+ _intermediary_low.Status(rpc_state.code, rpc_state.message), call)
+ rpc_state.low_write = _LowWrite.CLOSED
+ else:
+ ticket = links.Ticket(
+ call, rpc_state.sequence_number, None, None, None, None, 1, None,
+ None, None, None, None, None)
+ rpc_state.sequence_number += 1
+ self._relay.add_value(ticket)
+ rpc_state.low_write = _LowWrite.OPEN
+
+ def _on_finish_event(self, event):
+ call = event.tag
+ rpc_state = self._rpc_states.pop(call, None)
+ if rpc_state is None:
+ return
+ code = event.status.code
+ if code is _intermediary_low.Code.OK:
+ return
+
+ if code is _intermediary_low.Code.CANCELLED:
+ termination = links.Ticket.Termination.CANCELLATION
+ elif code is _intermediary_low.Code.DEADLINE_EXCEEDED:
+ termination = links.Ticket.Termination.EXPIRATION
+ else:
+ termination = links.Ticket.Termination.TRANSMISSION_FAILURE
+ ticket = links.Ticket(
+ call, rpc_state.sequence_number, None, None, None, None, None, None,
+ None, None, None, None, termination)
+ rpc_state.sequence_number += 1
+ self._relay.add_value(ticket)
+
+ def _spin(self, completion_queue, server):
+ while True:
+ event = completion_queue.get(None)
+ if event.kind is _intermediary_low.Event.Kind.STOP:
+ return
+ with self._lock:
+ if self._server is None:
+ continue
+ elif event.kind is _intermediary_low.Event.Kind.SERVICE_ACCEPTED:
+ self._on_service_acceptance_event(event, server)
+ elif event.kind is _intermediary_low.Event.Kind.READ_ACCEPTED:
+ self._on_read_event(event)
+ elif event.kind is _intermediary_low.Event.Kind.WRITE_ACCEPTED:
+ self._on_write_event(event)
+ elif event.kind is _intermediary_low.Event.Kind.COMPLETE_ACCEPTED:
+ pass
+ elif event.kind is _intermediary_low.Event.Kind.FINISH:
+ self._on_finish_event(event)
+ else:
+ logging.error('Illegal event! %s', (event,))
+
+ def add_ticket(self, ticket):
+ with self._lock:
+ if self._server is None:
+ return
+ call = ticket.operation_id
+ rpc_state = self._rpc_states.get(call)
+ if rpc_state is None:
+ return
+
+ if ticket.initial_metadata is not None:
+ _metadatafy(call, ticket.initial_metadata)
+ call.premetadata()
+ rpc_state.premetadataed = True
+ elif not rpc_state.premetadataed:
+ if (ticket.terminal_metadata is not None or
+ ticket.payload is not None or
+ ticket.termination is links.Ticket.Termination.COMPLETION or
+ ticket.code is not None or
+ ticket.message is not None):
+ call.premetadata()
+ rpc_state.premetadataed = True
+
+ if ticket.allowance is not None:
+ if rpc_state.read is _Read.AWAITING_ALLOWANCE:
+ rpc_state.allowance += ticket.allowance - 1
+ call.read(call)
+ rpc_state.read = _Read.READING
+ else:
+ rpc_state.allowance += ticket.allowance
+
+ if ticket.payload is not None:
+ call.write(rpc_state.response_serializer(ticket.payload), call)
+ rpc_state.low_write = _LowWrite.ACTIVE
+
+ if ticket.terminal_metadata is not None:
+ rpc_state.terminal_metadata = ticket.terminal_metadata
+ if ticket.code is not None:
+ rpc_state.code = ticket.code
+ if ticket.message is not None:
+ rpc_state.message = ticket.message
+
+ if ticket.termination is links.Ticket.Termination.COMPLETION:
+ rpc_state.high_write = _HighWrite.CLOSED
+ if rpc_state.low_write is _LowWrite.OPEN:
+ if rpc_state.terminal_metadata is not None:
+ _metadatafy(call, rpc_state.terminal_metadata)
+ status = _intermediary_low.Status(
+ _intermediary_low.Code.OK
+ if rpc_state.code is None else rpc_state.code,
+ '' if rpc_state.message is None else rpc_state.message)
+ call.status(status, call)
+ rpc_state.low_write = _LowWrite.CLOSED
+ elif ticket.termination is not None:
+ call.cancel()
+ self._rpc_states.pop(call, None)
+
+ def add_port(self, port, server_credentials):
+ with self._lock:
+ address = '[::]:%d' % port
+ if self._server is None:
+ self._completion_queue = _intermediary_low.CompletionQueue()
+ self._server = _intermediary_low.Server(self._completion_queue)
+ if server_credentials is None:
+ return self._server.add_http2_addr(address)
+ else:
+ return self._server.add_secure_http2_addr(address, server_credentials)
+
+ def start(self):
+ with self._lock:
+ if self._server is None:
+ self._completion_queue = _intermediary_low.CompletionQueue()
+ self._server = _intermediary_low.Server(self._completion_queue)
+ self._pool = logging_pool.pool(1)
+ self._pool.submit(self._spin, self._completion_queue, self._server)
+ self._server.start()
+ self._server.service(None)
+
+ def graceful_stop(self):
+ with self._lock:
+ self._server.stop()
+ self._server = None
+ self._completion_queue.stop()
+ self._completion_queue = None
+ pool = self._pool
+ self._pool = None
+ self._rpc_states = None
+ pool.shutdown(wait=True)
+
+ def immediate_stop(self):
+ # TODO(nathaniel): Implementation.
+ raise NotImplementedError(
+ 'TODO(nathaniel): after merge of rewritten lower layers')
+
+
+class ServiceLink(links.Link):
+ """A links.Link for use on the service-side of a gRPC connection.
+
+ Implementations of this interface are only valid for use between calls to
+ their start method and one of their stop methods.
+ """
+
+ @abc.abstractmethod
+ def add_port(self, port, server_credentials):
+ """Adds a port on which to service RPCs after this link has been started.
+
+ Args:
+ port: The port on which to service RPCs, or zero to request that a port be
+ automatically selected and used.
+ server_credentials: A ServerCredentials object, or None for insecure
+ service.
+
+ Returns:
+ A port on which RPCs will be serviced after this link has been started.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def start(self):
+ """Starts this object.
+
+ This method must be called before attempting to use this Link in ticket
+ exchange.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def stop_gracefully(self):
+ """Stops this link.
+
+ New RPCs will be rejected as soon as this method is called, but ongoing RPCs
+ will be allowed to continue until they terminate. This method blocks until
+ all RPCs have terminated.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def stop_immediately(self):
+ """Stops this link.
+
+ All in-progress RPCs will be terminated immediately.
+ """
+ raise NotImplementedError()
+
+
+class _ServiceLink(ServiceLink):
+
+ def __init__(self, request_deserializers, response_serializers):
+ self._relay = relay.relay(None)
+ self._kernel = _Kernel(
+ request_deserializers, response_serializers, self._relay)
+
+ def accept_ticket(self, ticket):
+ self._kernel.add_ticket(ticket)
+
+ def join_link(self, link):
+ self._relay.set_behavior(link.accept_ticket)
+
+ def add_port(self, port, server_credentials):
+ return self._kernel.add_port(port, server_credentials)
+
+ def start(self):
+ self._relay.start()
+ return self._kernel.start()
+
+ def stop_gracefully(self):
+ self._kernel.graceful_stop()
+ self._relay.stop()
+
+ def stop_immediately(self):
+ self._kernel.immediate_stop()
+ self._relay.stop()
+
+
+def service_link(request_deserializers, response_serializers):
+ """Creates a ServiceLink.
+
+ Args:
+ request_deserializers: A dict from group-method pair to request object
+ deserialization behavior.
+ response_serializers: A dict from group-method pair to response ojbect
+ serialization behavior.
+
+ Returns:
+ A ServiceLink.
+ """
+ return _ServiceLink(request_deserializers, response_serializers)
diff --git a/src/python/grpcio/grpc/early_adopter/__init__.py b/src/python/grpcio/grpc/early_adopter/__init__.py
new file mode 100644
index 0000000000..7086519106
--- /dev/null
+++ b/src/python/grpcio/grpc/early_adopter/__init__.py
@@ -0,0 +1,30 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
diff --git a/src/python/grpcio/grpc/early_adopter/implementations.py b/src/python/grpcio/grpc/early_adopter/implementations.py
new file mode 100644
index 0000000000..10919fae69
--- /dev/null
+++ b/src/python/grpcio/grpc/early_adopter/implementations.py
@@ -0,0 +1,250 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Entry points into GRPC."""
+
+import threading
+
+from grpc._adapter import fore as _fore
+from grpc._adapter import rear as _rear
+from grpc.framework.alpha import _face_utilities
+from grpc.framework.alpha import _reexport
+from grpc.framework.alpha import interfaces
+from grpc.framework.base import implementations as _base_implementations
+from grpc.framework.base import util as _base_utilities
+from grpc.framework.face import implementations as _face_implementations
+from grpc.framework.foundation import logging_pool
+
+_THREAD_POOL_SIZE = 8
+_ONE_DAY_IN_SECONDS = 24 * 60 * 60
+
+
+class _Server(interfaces.Server):
+
+ def __init__(self, breakdown, port, private_key, certificate_chain):
+ self._lock = threading.Lock()
+ self._breakdown = breakdown
+ self._port = port
+ if private_key is None or certificate_chain is None:
+ self._key_chain_pairs = ()
+ else:
+ self._key_chain_pairs = ((private_key, certificate_chain),)
+
+ self._pool = None
+ self._back = None
+ self._fore_link = None
+
+ def _start(self):
+ with self._lock:
+ if self._pool is None:
+ self._pool = logging_pool.pool(_THREAD_POOL_SIZE)
+ servicer = _face_implementations.servicer(
+ self._pool, self._breakdown.implementations, None)
+ self._back = _base_implementations.back_link(
+ servicer, self._pool, self._pool, self._pool, _ONE_DAY_IN_SECONDS,
+ _ONE_DAY_IN_SECONDS)
+ self._fore_link = _fore.ForeLink(
+ self._pool, self._breakdown.request_deserializers,
+ self._breakdown.response_serializers, None, self._key_chain_pairs,
+ port=self._port)
+ self._back.join_fore_link(self._fore_link)
+ self._fore_link.join_rear_link(self._back)
+ self._fore_link.start()
+ else:
+ raise ValueError('Server currently running!')
+
+ def _stop(self):
+ with self._lock:
+ if self._pool is None:
+ raise ValueError('Server not running!')
+ else:
+ self._fore_link.stop()
+ _base_utilities.wait_for_idle(self._back)
+ self._pool.shutdown(wait=True)
+ self._fore_link = None
+ self._back = None
+ self._pool = None
+
+ def __enter__(self):
+ self._start()
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ self._stop()
+ return False
+
+ def start(self):
+ self._start()
+
+ def stop(self):
+ self._stop()
+
+ def port(self):
+ with self._lock:
+ return self._fore_link.port()
+
+
+class _Stub(interfaces.Stub):
+
+ def __init__(
+ self, breakdown, host, port, secure, root_certificates, private_key,
+ certificate_chain, metadata_transformer=None, server_host_override=None):
+ self._lock = threading.Lock()
+ self._breakdown = breakdown
+ self._host = host
+ self._port = port
+ self._secure = secure
+ self._root_certificates = root_certificates
+ self._private_key = private_key
+ self._certificate_chain = certificate_chain
+ self._metadata_transformer = metadata_transformer
+ self._server_host_override = server_host_override
+
+ self._pool = None
+ self._front = None
+ self._rear_link = None
+ self._understub = None
+
+ def __enter__(self):
+ with self._lock:
+ if self._pool is None:
+ self._pool = logging_pool.pool(_THREAD_POOL_SIZE)
+ self._front = _base_implementations.front_link(
+ self._pool, self._pool, self._pool)
+ self._rear_link = _rear.RearLink(
+ self._host, self._port, self._pool,
+ self._breakdown.request_serializers,
+ self._breakdown.response_deserializers, self._secure,
+ self._root_certificates, self._private_key, self._certificate_chain,
+ metadata_transformer=self._metadata_transformer,
+ server_host_override=self._server_host_override)
+ self._front.join_rear_link(self._rear_link)
+ self._rear_link.join_fore_link(self._front)
+ self._rear_link.start()
+ self._understub = _face_implementations.dynamic_stub(
+ self._breakdown.face_cardinalities, self._front, self._pool, '')
+ else:
+ raise ValueError('Tried to __enter__ already-__enter__ed Stub!')
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ with self._lock:
+ if self._pool is None:
+ raise ValueError('Tried to __exit__ non-__enter__ed Stub!')
+ else:
+ self._rear_link.stop()
+ _base_utilities.wait_for_idle(self._front)
+ self._pool.shutdown(wait=True)
+ self._rear_link = None
+ self._front = None
+ self._pool = None
+ self._understub = None
+ return False
+
+ def __getattr__(self, attr):
+ with self._lock:
+ if self._pool is None:
+ raise ValueError('Tried to __getattr__ non-__enter__ed Stub!')
+ else:
+ method_cardinality = self._breakdown.cardinalities.get(attr)
+ underlying_attr = getattr(
+ self._understub, self._breakdown.qualified_names.get(attr), None)
+ if method_cardinality is interfaces.Cardinality.UNARY_UNARY:
+ return _reexport.unary_unary_sync_async(underlying_attr)
+ elif method_cardinality is interfaces.Cardinality.UNARY_STREAM:
+ return lambda request, timeout: _reexport.cancellable_iterator(
+ underlying_attr(request, timeout))
+ elif method_cardinality is interfaces.Cardinality.STREAM_UNARY:
+ return _reexport.stream_unary_sync_async(underlying_attr)
+ elif method_cardinality is interfaces.Cardinality.STREAM_STREAM:
+ return lambda request_iterator, timeout: (
+ _reexport.cancellable_iterator(underlying_attr(
+ request_iterator, timeout)))
+ else:
+ raise AttributeError(attr)
+
+
+def stub(
+ service_name, methods, host, port, metadata_transformer=None, secure=False,
+ root_certificates=None, private_key=None, certificate_chain=None,
+ server_host_override=None):
+ """Constructs an interfaces.Stub.
+
+ Args:
+ service_name: The package-qualified full name of the service.
+ methods: A dictionary from RPC method name to
+ interfaces.RpcMethodInvocationDescription describing the RPCs to be
+ supported by the created stub. The RPC method names in the dictionary are
+ not qualified by the service name or decorated in any other way.
+ host: The host to which to connect for RPC service.
+ port: The port to which to connect for RPC service.
+ metadata_transformer: A callable that given a metadata object produces
+ another metadata object to be used in the underlying communication on the
+ wire.
+ secure: Whether or not to construct the stub with a secure connection.
+ root_certificates: The PEM-encoded root certificates or None to ask for
+ them to be retrieved from a default location.
+ private_key: The PEM-encoded private key to use or None if no private key
+ should be used.
+ certificate_chain: The PEM-encoded certificate chain to use or None if no
+ certificate chain should be used.
+ server_host_override: (For testing only) the target name used for SSL
+ host name checking.
+
+ Returns:
+ An interfaces.Stub affording RPC invocation.
+ """
+ breakdown = _face_utilities.break_down_invocation(service_name, methods)
+ return _Stub(
+ breakdown, host, port, secure, root_certificates, private_key,
+ certificate_chain, server_host_override=server_host_override,
+ metadata_transformer=metadata_transformer)
+
+
+def server(
+ service_name, methods, port, private_key=None, certificate_chain=None):
+ """Constructs an interfaces.Server.
+
+ Args:
+ service_name: The package-qualified full name of the service.
+ methods: A dictionary from RPC method name to
+ interfaces.RpcMethodServiceDescription describing the RPCs to
+ be serviced by the created server. The RPC method names in the dictionary
+ are not qualified by the service name or decorated in any other way.
+ port: The port on which to serve or zero to ask for a port to be
+ automatically selected.
+ private_key: A pem-encoded private key, or None for an insecure server.
+ certificate_chain: A pem-encoded certificate chain, or None for an insecure
+ server.
+
+ Returns:
+ An interfaces.Server that will serve secure traffic.
+ """
+ breakdown = _face_utilities.break_down_service(service_name, methods)
+ return _Server(breakdown, port, private_key, certificate_chain)
diff --git a/src/python/grpcio/grpc/framework/__init__.py b/src/python/grpcio/grpc/framework/__init__.py
new file mode 100644
index 0000000000..7086519106
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/__init__.py
@@ -0,0 +1,30 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
diff --git a/src/python/grpcio/grpc/framework/alpha/__init__.py b/src/python/grpcio/grpc/framework/alpha/__init__.py
new file mode 100644
index 0000000000..b89398809f
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/alpha/__init__.py
@@ -0,0 +1,28 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/src/python/grpcio/grpc/framework/alpha/_face_utilities.py b/src/python/grpcio/grpc/framework/alpha/_face_utilities.py
new file mode 100644
index 0000000000..fb0cfe426d
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/alpha/_face_utilities.py
@@ -0,0 +1,183 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import abc
+import collections
+
+# face_interfaces is referenced from specification in this module.
+from grpc.framework.common import cardinality
+from grpc.framework.face import interfaces as face_interfaces # pylint: disable=unused-import
+from grpc.framework.face import utilities as face_utilities
+from grpc.framework.alpha import _reexport
+from grpc.framework.alpha import interfaces
+
+
+def _qualified_name(service_name, method_name):
+ return '/%s/%s' % (service_name, method_name)
+
+
+# TODO(nathaniel): This structure is getting bloated; it could be shrunk if
+# implementations._Stub used a generic rather than a dynamic underlying
+# face-layer stub.
+class InvocationBreakdown(object):
+ """An intermediate representation of invocation-side views of RPC methods.
+
+ Attributes:
+ cardinalities: A dictionary from RPC method name to interfaces.Cardinality
+ value.
+ qualified_names: A dictionary from unqualified RPC method name to
+ service-qualified RPC method name.
+ face_cardinalities: A dictionary from service-qualified RPC method name to
+ to cardinality.Cardinality value.
+ request_serializers: A dictionary from service-qualified RPC method name to
+ callable behavior to be used serializing request values for the RPC.
+ response_deserializers: A dictionary from service-qualified RPC method name
+ to callable behavior to be used deserializing response values for the
+ RPC.
+ """
+ __metaclass__ = abc.ABCMeta
+
+
+class _EasyInvocationBreakdown(
+ InvocationBreakdown,
+ collections.namedtuple(
+ '_EasyInvocationBreakdown',
+ ('cardinalities', 'qualified_names', 'face_cardinalities',
+ 'request_serializers', 'response_deserializers'))):
+ pass
+
+
+class ServiceBreakdown(object):
+ """An intermediate representation of service-side views of RPC methods.
+
+ Attributes:
+ implementations: A dictionary from service-qualified RPC method name to
+ face_interfaces.MethodImplementation implementing the RPC method.
+ request_deserializers: A dictionary from service-qualified RPC method name
+ to callable behavior to be used deserializing request values for the RPC.
+ response_serializers: A dictionary from service-qualified RPC method name
+ to callable behavior to be used serializing response values for the RPC.
+ """
+ __metaclass__ = abc.ABCMeta
+
+
+class _EasyServiceBreakdown(
+ ServiceBreakdown,
+ collections.namedtuple(
+ '_EasyServiceBreakdown',
+ ('implementations', 'request_deserializers', 'response_serializers'))):
+ pass
+
+
+def break_down_invocation(service_name, method_descriptions):
+ """Derives an InvocationBreakdown from several RPC method descriptions.
+
+ Args:
+ service_name: The package-qualified full name of the service.
+ method_descriptions: A dictionary from RPC method name to
+ interfaces.RpcMethodInvocationDescription describing the RPCs.
+
+ Returns:
+ An InvocationBreakdown corresponding to the given method descriptions.
+ """
+ cardinalities = {}
+ qualified_names = {}
+ face_cardinalities = {}
+ request_serializers = {}
+ response_deserializers = {}
+ for name, method_description in method_descriptions.iteritems():
+ qualified_name = _qualified_name(service_name, name)
+ method_cardinality = method_description.cardinality()
+ cardinalities[name] = method_description.cardinality()
+ qualified_names[name] = qualified_name
+ face_cardinalities[qualified_name] = _reexport.common_cardinality(
+ method_cardinality)
+ request_serializers[qualified_name] = method_description.serialize_request
+ response_deserializers[qualified_name] = (
+ method_description.deserialize_response)
+ return _EasyInvocationBreakdown(
+ cardinalities, qualified_names, face_cardinalities, request_serializers,
+ response_deserializers)
+
+
+def break_down_service(service_name, method_descriptions):
+ """Derives a ServiceBreakdown from several RPC method descriptions.
+
+ Args:
+ method_descriptions: A dictionary from RPC method name to
+ interfaces.RpcMethodServiceDescription describing the RPCs.
+
+ Returns:
+ A ServiceBreakdown corresponding to the given method descriptions.
+ """
+ implementations = {}
+ request_deserializers = {}
+ response_serializers = {}
+ for name, method_description in method_descriptions.iteritems():
+ qualified_name = _qualified_name(service_name, name)
+ method_cardinality = method_description.cardinality()
+ if method_cardinality is interfaces.Cardinality.UNARY_UNARY:
+ def service(
+ request, face_rpc_context,
+ service_behavior=method_description.service_unary_unary):
+ return service_behavior(
+ request, _reexport.rpc_context(face_rpc_context))
+ implementations[qualified_name] = face_utilities.unary_unary_inline(
+ service)
+ elif method_cardinality is interfaces.Cardinality.UNARY_STREAM:
+ def service(
+ request, face_rpc_context,
+ service_behavior=method_description.service_unary_stream):
+ return service_behavior(
+ request, _reexport.rpc_context(face_rpc_context))
+ implementations[qualified_name] = face_utilities.unary_stream_inline(
+ service)
+ elif method_cardinality is interfaces.Cardinality.STREAM_UNARY:
+ def service(
+ request_iterator, face_rpc_context,
+ service_behavior=method_description.service_stream_unary):
+ return service_behavior(
+ request_iterator, _reexport.rpc_context(face_rpc_context))
+ implementations[qualified_name] = face_utilities.stream_unary_inline(
+ service)
+ elif method_cardinality is interfaces.Cardinality.STREAM_STREAM:
+ def service(
+ request_iterator, face_rpc_context,
+ service_behavior=method_description.service_stream_stream):
+ return service_behavior(
+ request_iterator, _reexport.rpc_context(face_rpc_context))
+ implementations[qualified_name] = face_utilities.stream_stream_inline(
+ service)
+ request_deserializers[qualified_name] = (
+ method_description.deserialize_request)
+ response_serializers[qualified_name] = (
+ method_description.serialize_response)
+
+ return _EasyServiceBreakdown(
+ implementations, request_deserializers, response_serializers)
diff --git a/src/python/grpcio/grpc/framework/alpha/_reexport.py b/src/python/grpcio/grpc/framework/alpha/_reexport.py
new file mode 100644
index 0000000000..198cb95ad5
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/alpha/_reexport.py
@@ -0,0 +1,203 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from grpc.framework.common import cardinality
+from grpc.framework.face import exceptions as face_exceptions
+from grpc.framework.face import interfaces as face_interfaces
+from grpc.framework.foundation import future
+from grpc.framework.alpha import exceptions
+from grpc.framework.alpha import interfaces
+
+_EARLY_ADOPTER_CARDINALITY_TO_COMMON_CARDINALITY = {
+ interfaces.Cardinality.UNARY_UNARY: cardinality.Cardinality.UNARY_UNARY,
+ interfaces.Cardinality.UNARY_STREAM: cardinality.Cardinality.UNARY_STREAM,
+ interfaces.Cardinality.STREAM_UNARY: cardinality.Cardinality.STREAM_UNARY,
+ interfaces.Cardinality.STREAM_STREAM: cardinality.Cardinality.STREAM_STREAM,
+}
+
+_ABORTION_REEXPORT = {
+ face_interfaces.Abortion.CANCELLED: interfaces.Abortion.CANCELLED,
+ face_interfaces.Abortion.EXPIRED: interfaces.Abortion.EXPIRED,
+ face_interfaces.Abortion.NETWORK_FAILURE:
+ interfaces.Abortion.NETWORK_FAILURE,
+ face_interfaces.Abortion.SERVICED_FAILURE:
+ interfaces.Abortion.SERVICED_FAILURE,
+ face_interfaces.Abortion.SERVICER_FAILURE:
+ interfaces.Abortion.SERVICER_FAILURE,
+}
+
+
+class _RpcError(exceptions.RpcError):
+ pass
+
+
+def _reexport_error(face_rpc_error):
+ if isinstance(face_rpc_error, face_exceptions.CancellationError):
+ return exceptions.CancellationError()
+ elif isinstance(face_rpc_error, face_exceptions.ExpirationError):
+ return exceptions.ExpirationError()
+ else:
+ return _RpcError()
+
+
+def _as_face_abortion_callback(abortion_callback):
+ def face_abortion_callback(face_abortion):
+ abortion_callback(_ABORTION_REEXPORT[face_abortion])
+ return face_abortion_callback
+
+
+class _ReexportedFuture(future.Future):
+
+ def __init__(self, face_future):
+ self._face_future = face_future
+
+ def cancel(self):
+ return self._face_future.cancel()
+
+ def cancelled(self):
+ return self._face_future.cancelled()
+
+ def running(self):
+ return self._face_future.running()
+
+ def done(self):
+ return self._face_future.done()
+
+ def result(self, timeout=None):
+ try:
+ return self._face_future.result(timeout=timeout)
+ except face_exceptions.RpcError as e:
+ raise _reexport_error(e)
+
+ def exception(self, timeout=None):
+ face_error = self._face_future.exception(timeout=timeout)
+ return None if face_error is None else _reexport_error(face_error)
+
+ def traceback(self, timeout=None):
+ return self._face_future.traceback(timeout=timeout)
+
+ def add_done_callback(self, fn):
+ self._face_future.add_done_callback(lambda unused_face_future: fn(self))
+
+
+def _call_reexporting_errors(behavior, *args, **kwargs):
+ try:
+ return behavior(*args, **kwargs)
+ except face_exceptions.RpcError as e:
+ raise _reexport_error(e)
+
+
+def _reexported_future(face_future):
+ return _ReexportedFuture(face_future)
+
+
+class _CancellableIterator(interfaces.CancellableIterator):
+
+ def __init__(self, face_cancellable_iterator):
+ self._face_cancellable_iterator = face_cancellable_iterator
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ return _call_reexporting_errors(self._face_cancellable_iterator.next)
+
+ def cancel(self):
+ self._face_cancellable_iterator.cancel()
+
+
+class _RpcContext(interfaces.RpcContext):
+
+ def __init__(self, face_rpc_context):
+ self._face_rpc_context = face_rpc_context
+
+ def is_active(self):
+ return self._face_rpc_context.is_active()
+
+ def time_remaining(self):
+ return self._face_rpc_context.time_remaining()
+
+ def add_abortion_callback(self, abortion_callback):
+ self._face_rpc_context.add_abortion_callback(
+ _as_face_abortion_callback(abortion_callback))
+
+
+class _UnaryUnarySyncAsync(interfaces.UnaryUnarySyncAsync):
+
+ def __init__(self, face_unary_unary_multi_callable):
+ self._underlying = face_unary_unary_multi_callable
+
+ def __call__(self, request, timeout):
+ return _call_reexporting_errors(
+ self._underlying, request, timeout)
+
+ def async(self, request, timeout):
+ return _ReexportedFuture(self._underlying.future(request, timeout))
+
+
+class _StreamUnarySyncAsync(interfaces.StreamUnarySyncAsync):
+
+ def __init__(self, face_stream_unary_multi_callable):
+ self._underlying = face_stream_unary_multi_callable
+
+ def __call__(self, request_iterator, timeout):
+ return _call_reexporting_errors(
+ self._underlying, request_iterator, timeout)
+
+ def async(self, request_iterator, timeout):
+ return _ReexportedFuture(self._underlying.future(request_iterator, timeout))
+
+
+def common_cardinality(early_adopter_cardinality):
+ return _EARLY_ADOPTER_CARDINALITY_TO_COMMON_CARDINALITY[
+ early_adopter_cardinality]
+
+
+def common_cardinalities(early_adopter_cardinalities):
+ common_cardinalities = {}
+ for name, early_adopter_cardinality in early_adopter_cardinalities.iteritems():
+ common_cardinalities[name] = _EARLY_ADOPTER_CARDINALITY_TO_COMMON_CARDINALITY[
+ early_adopter_cardinality]
+ return common_cardinalities
+
+
+def rpc_context(face_rpc_context):
+ return _RpcContext(face_rpc_context)
+
+
+def cancellable_iterator(face_cancellable_iterator):
+ return _CancellableIterator(face_cancellable_iterator)
+
+
+def unary_unary_sync_async(face_unary_unary_multi_callable):
+ return _UnaryUnarySyncAsync(face_unary_unary_multi_callable)
+
+
+def stream_unary_sync_async(face_stream_unary_multi_callable):
+ return _StreamUnarySyncAsync(face_stream_unary_multi_callable)
diff --git a/src/python/grpcio/grpc/framework/alpha/exceptions.py b/src/python/grpcio/grpc/framework/alpha/exceptions.py
new file mode 100644
index 0000000000..5234d3b91c
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/alpha/exceptions.py
@@ -0,0 +1,48 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Exceptions raised by GRPC.
+
+Only GRPC should instantiate and raise these exceptions.
+"""
+
+import abc
+
+
+class RpcError(Exception):
+ """Common super type for all exceptions raised by GRPC."""
+ __metaclass__ = abc.ABCMeta
+
+
+class CancellationError(RpcError):
+ """Indicates that an RPC has been cancelled."""
+
+
+class ExpirationError(RpcError):
+ """Indicates that an RPC has expired ("timed out")."""
diff --git a/src/python/grpcio/grpc/framework/alpha/interfaces.py b/src/python/grpcio/grpc/framework/alpha/interfaces.py
new file mode 100644
index 0000000000..8380567c97
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/alpha/interfaces.py
@@ -0,0 +1,388 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Interfaces of GRPC."""
+
+import abc
+import enum
+
+# exceptions is referenced from specification in this module.
+from grpc.framework.alpha import exceptions # pylint: disable=unused-import
+from grpc.framework.foundation import activated
+from grpc.framework.foundation import future
+
+
+@enum.unique
+class Cardinality(enum.Enum):
+ """Constants for the four cardinalities of RPC."""
+
+ UNARY_UNARY = 'request-unary/response-unary'
+ UNARY_STREAM = 'request-unary/response-streaming'
+ STREAM_UNARY = 'request-streaming/response-unary'
+ STREAM_STREAM = 'request-streaming/response-streaming'
+
+
+@enum.unique
+class Abortion(enum.Enum):
+ """Categories of RPC abortion."""
+
+ CANCELLED = 'cancelled'
+ EXPIRED = 'expired'
+ NETWORK_FAILURE = 'network failure'
+ SERVICED_FAILURE = 'serviced failure'
+ SERVICER_FAILURE = 'servicer failure'
+
+
+class CancellableIterator(object):
+ """Implements the Iterator protocol and affords a cancel method."""
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def __iter__(self):
+ """Returns the self object in accordance with the Iterator protocol."""
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def next(self):
+ """Returns a value or raises StopIteration per the Iterator protocol."""
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def cancel(self):
+ """Requests cancellation of whatever computation underlies this iterator."""
+ raise NotImplementedError()
+
+
+class RpcContext(object):
+ """Provides RPC-related information and action."""
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def is_active(self):
+ """Describes whether the RPC is active or has terminated."""
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def time_remaining(self):
+ """Describes the length of allowed time remaining for the RPC.
+ Returns:
+ A nonnegative float indicating the length of allowed time in seconds
+ remaining for the RPC to complete before it is considered to have timed
+ out.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def add_abortion_callback(self, abortion_callback):
+ """Registers a callback to be called if the RPC is aborted.
+ Args:
+ abortion_callback: A callable to be called and passed an Abortion value
+ in the event of RPC abortion.
+ """
+ raise NotImplementedError()
+
+
+class UnaryUnarySyncAsync(object):
+ """Affords invoking a unary-unary RPC synchronously or asynchronously.
+ Values implementing this interface are directly callable and present an
+ "async" method. Both calls take a request value and a numeric timeout.
+ Direct invocation of a value of this type invokes its associated RPC and
+ blocks until the RPC's response is available. Calling the "async" method
+ of a value of this type invokes its associated RPC and immediately returns a
+ future.Future bound to the asynchronous execution of the RPC.
+ """
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def __call__(self, request, timeout):
+ """Synchronously invokes the underlying RPC.
+ Args:
+ request: The request value for the RPC.
+ timeout: A duration of time in seconds to allow for the RPC.
+ Returns:
+ The response value for the RPC.
+ Raises:
+ exceptions.RpcError: Indicating that the RPC was aborted.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def async(self, request, timeout):
+ """Asynchronously invokes the underlying RPC.
+ Args:
+ request: The request value for the RPC.
+ timeout: A duration of time in seconds to allow for the RPC.
+ Returns:
+ A future.Future representing the RPC. In the event of RPC completion, the
+ returned Future's result value will be the response value of the RPC.
+ In the event of RPC abortion, the returned Future's exception value
+ will be an exceptions.RpcError.
+ """
+ raise NotImplementedError()
+
+
+class StreamUnarySyncAsync(object):
+ """Affords invoking a stream-unary RPC synchronously or asynchronously.
+ Values implementing this interface are directly callable and present an
+ "async" method. Both calls take an iterator of request values and a numeric
+ timeout. Direct invocation of a value of this type invokes its associated RPC
+ and blocks until the RPC's response is available. Calling the "async" method
+ of a value of this type invokes its associated RPC and immediately returns a
+ future.Future bound to the asynchronous execution of the RPC.
+ """
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def __call__(self, request_iterator, timeout):
+ """Synchronously invokes the underlying RPC.
+
+ Args:
+ request_iterator: An iterator that yields request values for the RPC.
+ timeout: A duration of time in seconds to allow for the RPC.
+
+ Returns:
+ The response value for the RPC.
+
+ Raises:
+ exceptions.RpcError: Indicating that the RPC was aborted.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def async(self, request_iterator, timeout):
+ """Asynchronously invokes the underlying RPC.
+
+ Args:
+ request_iterator: An iterator that yields request values for the RPC.
+ timeout: A duration of time in seconds to allow for the RPC.
+
+ Returns:
+ A future.Future representing the RPC. In the event of RPC completion, the
+ returned Future's result value will be the response value of the RPC.
+ In the event of RPC abortion, the returned Future's exception value
+ will be an exceptions.RpcError.
+ """
+ raise NotImplementedError()
+
+
+class RpcMethodDescription(object):
+ """A type for the common aspects of RPC method descriptions."""
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def cardinality(self):
+ """Identifies the cardinality of this RpcMethodDescription.
+
+ Returns:
+ A Cardinality value identifying whether or not this
+ RpcMethodDescription is request-unary or request-streaming and
+ whether or not it is response-unary or response-streaming.
+ """
+ raise NotImplementedError()
+
+
+class RpcMethodInvocationDescription(RpcMethodDescription):
+ """Invocation-side description of an RPC method."""
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def serialize_request(self, request):
+ """Serializes a request value.
+
+ Args:
+ request: A request value appropriate for the RPC method described by this
+ RpcMethodInvocationDescription.
+
+ Returns:
+ The serialization of the given request value as a
+ bytestring.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def deserialize_response(self, serialized_response):
+ """Deserializes a response value.
+
+ Args:
+ serialized_response: A bytestring that is the serialization of a response
+ value appropriate for the RPC method described by this
+ RpcMethodInvocationDescription.
+
+ Returns:
+ A response value corresponding to the given bytestring.
+ """
+ raise NotImplementedError()
+
+
+class RpcMethodServiceDescription(RpcMethodDescription):
+ """Service-side description of an RPC method."""
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def deserialize_request(self, serialized_request):
+ """Deserializes a request value.
+
+ Args:
+ serialized_request: A bytestring that is the serialization of a request
+ value appropriate for the RPC method described by this
+ RpcMethodServiceDescription.
+
+ Returns:
+ A request value corresponding to the given bytestring.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def serialize_response(self, response):
+ """Serializes a response value.
+
+ Args:
+ response: A response value appropriate for the RPC method described by
+ this RpcMethodServiceDescription.
+
+ Returns:
+ The serialization of the given response value as a
+ bytestring.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def service_unary_unary(self, request, context):
+ """Carries out this RPC.
+
+ This method may only be called if the cardinality of this
+ RpcMethodServiceDescription is Cardinality.UNARY_UNARY.
+
+ Args:
+ request: A request value appropriate for the RPC method described by this
+ RpcMethodServiceDescription.
+ context: An RpcContext object for the RPC.
+
+ Returns:
+ A response value appropriate for the RPC method described by this
+ RpcMethodServiceDescription.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def service_unary_stream(self, request, context):
+ """Carries out this RPC.
+
+ This method may only be called if the cardinality of this
+ RpcMethodServiceDescription is Cardinality.UNARY_STREAM.
+
+ Args:
+ request: A request value appropriate for the RPC method described by this
+ RpcMethodServiceDescription.
+ context: An RpcContext object for the RPC.
+
+ Yields:
+ Zero or more response values appropriate for the RPC method described by
+ this RpcMethodServiceDescription.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def service_stream_unary(self, request_iterator, context):
+ """Carries out this RPC.
+
+ This method may only be called if the cardinality of this
+ RpcMethodServiceDescription is Cardinality.STREAM_UNARY.
+
+ Args:
+ request_iterator: An iterator of request values appropriate for the RPC
+ method described by this RpcMethodServiceDescription.
+ context: An RpcContext object for the RPC.
+
+ Returns:
+ A response value appropriate for the RPC method described by this
+ RpcMethodServiceDescription.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def service_stream_stream(self, request_iterator, context):
+ """Carries out this RPC.
+
+ This method may only be called if the cardinality of this
+ RpcMethodServiceDescription is Cardinality.STREAM_STREAM.
+
+ Args:
+ request_iterator: An iterator of request values appropriate for the RPC
+ method described by this RpcMethodServiceDescription.
+ context: An RpcContext object for the RPC.
+
+ Yields:
+ Zero or more response values appropriate for the RPC method described by
+ this RpcMethodServiceDescription.
+ """
+ raise NotImplementedError()
+
+
+class Stub(object):
+ """A stub with callable RPC method names for attributes.
+
+ Instances of this type are context managers and only afford RPC invocation
+ when used in context.
+
+ Instances of this type, when used in context, respond to attribute access
+ as follows: if the requested attribute is the name of a unary-unary RPC
+ method, the value of the attribute will be a UnaryUnarySyncAsync with which
+ to invoke the RPC method. If the requested attribute is the name of a
+ unary-stream RPC method, the value of the attribute will be a callable taking
+ a request object and a timeout parameter and returning a CancellableIterator
+ that yields the response values of the RPC. If the requested attribute is the
+ name of a stream-unary RPC method, the value of the attribute will be a
+ StreamUnarySyncAsync with which to invoke the RPC method. If the requested
+ attribute is the name of a stream-stream RPC method, the value of the
+ attribute will be a callable taking an iterator of request objects and a
+ timeout and returning a CancellableIterator that yields the response values
+ of the RPC.
+
+ In all cases indication of abortion is indicated by raising of
+ exceptions.RpcError, exceptions.CancellationError,
+ and exceptions.ExpirationError.
+ """
+ __metaclass__ = abc.ABCMeta
+
+
+class Server(activated.Activated):
+ """A GRPC Server."""
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def port(self):
+ """Reports the port on which the server is serving.
+
+ This method may only be called while the server is activated.
+
+ Returns:
+ The port on which the server is serving.
+ """
+ raise NotImplementedError()
diff --git a/src/python/grpcio/grpc/framework/alpha/utilities.py b/src/python/grpcio/grpc/framework/alpha/utilities.py
new file mode 100644
index 0000000000..7d7f78f5e4
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/alpha/utilities.py
@@ -0,0 +1,269 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Utilities for use with GRPC."""
+
+from grpc.framework.alpha import interfaces
+
+
+class _RpcMethodDescription(
+ interfaces.RpcMethodInvocationDescription,
+ interfaces.RpcMethodServiceDescription):
+
+ def __init__(
+ self, cardinality, unary_unary, unary_stream, stream_unary,
+ stream_stream, request_serializer, request_deserializer,
+ response_serializer, response_deserializer):
+ self._cardinality = cardinality
+ self._unary_unary = unary_unary
+ self._unary_stream = unary_stream
+ self._stream_unary = stream_unary
+ self._stream_stream = stream_stream
+ self._request_serializer = request_serializer
+ self._request_deserializer = request_deserializer
+ self._response_serializer = response_serializer
+ self._response_deserializer = response_deserializer
+
+ def cardinality(self):
+ """See interfaces.RpcMethodDescription.cardinality for specification."""
+ return self._cardinality
+
+ def serialize_request(self, request):
+ """See interfaces.RpcMethodInvocationDescription.serialize_request."""
+ return self._request_serializer(request)
+
+ def deserialize_request(self, serialized_request):
+ """See interfaces.RpcMethodServiceDescription.deserialize_request."""
+ return self._request_deserializer(serialized_request)
+
+ def serialize_response(self, response):
+ """See interfaces.RpcMethodServiceDescription.serialize_response."""
+ return self._response_serializer(response)
+
+ def deserialize_response(self, serialized_response):
+ """See interfaces.RpcMethodInvocationDescription.deserialize_response."""
+ return self._response_deserializer(serialized_response)
+
+ def service_unary_unary(self, request, context):
+ """See interfaces.RpcMethodServiceDescription.service_unary_unary."""
+ return self._unary_unary(request, context)
+
+ def service_unary_stream(self, request, context):
+ """See interfaces.RpcMethodServiceDescription.service_unary_stream."""
+ return self._unary_stream(request, context)
+
+ def service_stream_unary(self, request_iterator, context):
+ """See interfaces.RpcMethodServiceDescription.service_stream_unary."""
+ return self._stream_unary(request_iterator, context)
+
+ def service_stream_stream(self, request_iterator, context):
+ """See interfaces.RpcMethodServiceDescription.service_stream_stream."""
+ return self._stream_stream(request_iterator, context)
+
+
+def unary_unary_invocation_description(
+ request_serializer, response_deserializer):
+ """Creates an interfaces.RpcMethodInvocationDescription for an RPC method.
+
+ Args:
+ request_serializer: A callable that when called on a request
+ value returns a bytestring corresponding to that value.
+ response_deserializer: A callable that when called on a
+ bytestring returns the response value corresponding to
+ that bytestring.
+
+ Returns:
+ An interfaces.RpcMethodInvocationDescription constructed from the given
+ arguments representing a unary-request/unary-response RPC method.
+ """
+ return _RpcMethodDescription(
+ interfaces.Cardinality.UNARY_UNARY, None, None, None, None,
+ request_serializer, None, None, response_deserializer)
+
+
+def unary_stream_invocation_description(
+ request_serializer, response_deserializer):
+ """Creates an interfaces.RpcMethodInvocationDescription for an RPC method.
+
+ Args:
+ request_serializer: A callable that when called on a request
+ value returns a bytestring corresponding to that value.
+ response_deserializer: A callable that when called on a
+ bytestring returns the response value corresponding to
+ that bytestring.
+
+ Returns:
+ An interfaces.RpcMethodInvocationDescription constructed from the given
+ arguments representing a unary-request/streaming-response RPC method.
+ """
+ return _RpcMethodDescription(
+ interfaces.Cardinality.UNARY_STREAM, None, None, None, None,
+ request_serializer, None, None, response_deserializer)
+
+
+def stream_unary_invocation_description(
+ request_serializer, response_deserializer):
+ """Creates an interfaces.RpcMethodInvocationDescription for an RPC method.
+
+ Args:
+ request_serializer: A callable that when called on a request
+ value returns a bytestring corresponding to that value.
+ response_deserializer: A callable that when called on a
+ bytestring returns the response value corresponding to
+ that bytestring.
+
+ Returns:
+ An interfaces.RpcMethodInvocationDescription constructed from the given
+ arguments representing a streaming-request/unary-response RPC method.
+ """
+ return _RpcMethodDescription(
+ interfaces.Cardinality.STREAM_UNARY, None, None, None, None,
+ request_serializer, None, None, response_deserializer)
+
+
+def stream_stream_invocation_description(
+ request_serializer, response_deserializer):
+ """Creates an interfaces.RpcMethodInvocationDescription for an RPC method.
+
+ Args:
+ request_serializer: A callable that when called on a request
+ value returns a bytestring corresponding to that value.
+ response_deserializer: A callable that when called on a
+ bytestring returns the response value corresponding to
+ that bytestring.
+
+ Returns:
+ An interfaces.RpcMethodInvocationDescription constructed from the given
+ arguments representing a streaming-request/streaming-response RPC
+ method.
+ """
+ return _RpcMethodDescription(
+ interfaces.Cardinality.STREAM_STREAM, None, None, None, None,
+ request_serializer, None, None, response_deserializer)
+
+
+def unary_unary_service_description(
+ behavior, request_deserializer, response_serializer):
+ """Creates an interfaces.RpcMethodServiceDescription for the given behavior.
+
+ Args:
+ behavior: A callable that implements a unary-unary RPC
+ method that accepts a single request and an interfaces.RpcContext and
+ returns a single response.
+ request_deserializer: A callable that when called on a
+ bytestring returns the request value corresponding to that
+ bytestring.
+ response_serializer: A callable that when called on a
+ response value returns the bytestring corresponding to
+ that value.
+
+ Returns:
+ An interfaces.RpcMethodServiceDescription constructed from the given
+ arguments representing a unary-request/unary-response RPC
+ method.
+ """
+ return _RpcMethodDescription(
+ interfaces.Cardinality.UNARY_UNARY, behavior, None, None, None,
+ None, request_deserializer, response_serializer, None)
+
+
+def unary_stream_service_description(
+ behavior, request_deserializer, response_serializer):
+ """Creates an interfaces.RpcMethodServiceDescription for the given behavior.
+
+ Args:
+ behavior: A callable that implements a unary-stream RPC
+ method that accepts a single request and an interfaces.RpcContext
+ and returns an iterator of zero or more responses.
+ request_deserializer: A callable that when called on a
+ bytestring returns the request value corresponding to that
+ bytestring.
+ response_serializer: A callable that when called on a
+ response value returns the bytestring corresponding to
+ that value.
+
+ Returns:
+ An interfaces.RpcMethodServiceDescription constructed from the given
+ arguments representing a unary-request/streaming-response
+ RPC method.
+ """
+ return _RpcMethodDescription(
+ interfaces.Cardinality.UNARY_STREAM, None, behavior, None, None,
+ None, request_deserializer, response_serializer, None)
+
+
+def stream_unary_service_description(
+ behavior, request_deserializer, response_serializer):
+ """Creates an interfaces.RpcMethodServiceDescription for the given behavior.
+
+ Args:
+ behavior: A callable that implements a stream-unary RPC
+ method that accepts an iterator of zero or more requests
+ and an interfaces.RpcContext and returns a single response.
+ request_deserializer: A callable that when called on a
+ bytestring returns the request value corresponding to that
+ bytestring.
+ response_serializer: A callable that when called on a
+ response value returns the bytestring corresponding to
+ that value.
+
+ Returns:
+ An interfaces.RpcMethodServiceDescription constructed from the given
+ arguments representing a streaming-request/unary-response
+ RPC method.
+ """
+ return _RpcMethodDescription(
+ interfaces.Cardinality.STREAM_UNARY, None, None, behavior, None,
+ None, request_deserializer, response_serializer, None)
+
+
+def stream_stream_service_description(
+ behavior, request_deserializer, response_serializer):
+ """Creates an interfaces.RpcMethodServiceDescription for the given behavior.
+
+ Args:
+ behavior: A callable that implements a stream-stream RPC
+ method that accepts an iterator of zero or more requests
+ and an interfaces.RpcContext and returns an iterator of
+ zero or more responses.
+ request_deserializer: A callable that when called on a
+ bytestring returns the request value corresponding to that
+ bytestring.
+ response_serializer: A callable that when called on a
+ response value returns the bytestring corresponding to
+ that value.
+
+ Returns:
+ An interfaces.RpcMethodServiceDescription constructed from the given
+ arguments representing a
+ streaming-request/streaming-response RPC method.
+ """
+ return _RpcMethodDescription(
+ interfaces.Cardinality.STREAM_STREAM, None, None, None, behavior,
+ None, request_deserializer, response_serializer, None)
diff --git a/src/python/grpcio/grpc/framework/base/__init__.py b/src/python/grpcio/grpc/framework/base/__init__.py
new file mode 100644
index 0000000000..7086519106
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/base/__init__.py
@@ -0,0 +1,30 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
diff --git a/src/python/grpcio/grpc/framework/base/_cancellation.py b/src/python/grpcio/grpc/framework/base/_cancellation.py
new file mode 100644
index 0000000000..ffbc90668f
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/base/_cancellation.py
@@ -0,0 +1,64 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""State and behavior for operation cancellation."""
+
+from grpc.framework.base import _interfaces
+from grpc.framework.base import interfaces
+
+
+class CancellationManager(_interfaces.CancellationManager):
+ """An implementation of _interfaces.CancellationManager."""
+
+ def __init__(
+ self, lock, termination_manager, transmission_manager, ingestion_manager,
+ expiration_manager):
+ """Constructor.
+
+ Args:
+ lock: The operation-wide lock.
+ termination_manager: The _interfaces.TerminationManager for the operation.
+ transmission_manager: The _interfaces.TransmissionManager for the
+ operation.
+ ingestion_manager: The _interfaces.IngestionManager for the operation.
+ expiration_manager: The _interfaces.ExpirationManager for the operation.
+ """
+ self._lock = lock
+ self._termination_manager = termination_manager
+ self._transmission_manager = transmission_manager
+ self._ingestion_manager = ingestion_manager
+ self._expiration_manager = expiration_manager
+
+ def cancel(self):
+ """See _interfaces.CancellationManager.cancel for specification."""
+ with self._lock:
+ self._termination_manager.abort(interfaces.Outcome.CANCELLED)
+ self._transmission_manager.abort(interfaces.Outcome.CANCELLED)
+ self._ingestion_manager.abort()
+ self._expiration_manager.abort()
diff --git a/src/python/grpcio/grpc/framework/base/_constants.py b/src/python/grpcio/grpc/framework/base/_constants.py
new file mode 100644
index 0000000000..8fbdc82782
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/base/_constants.py
@@ -0,0 +1,32 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Private constants for the package."""
+
+INTERNAL_ERROR_LOG_MESSAGE = ':-( RPC Framework (Base) internal error! :-('
diff --git a/src/python/grpcio/grpc/framework/base/_context.py b/src/python/grpcio/grpc/framework/base/_context.py
new file mode 100644
index 0000000000..d84871d639
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/base/_context.py
@@ -0,0 +1,99 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""State and behavior for operation context."""
+
+import time
+
+# _interfaces is referenced from specification in this module.
+from grpc.framework.base import interfaces
+from grpc.framework.base import _interfaces # pylint: disable=unused-import
+
+
+class OperationContext(interfaces.OperationContext):
+ """An implementation of interfaces.OperationContext."""
+
+ def __init__(
+ self, lock, operation_id, local_failure, termination_manager,
+ transmission_manager):
+ """Constructor.
+
+ Args:
+ lock: The operation-wide lock.
+ operation_id: An object identifying the operation.
+ local_failure: Whichever one of interfaces.Outcome.SERVICED_FAILURE or
+ interfaces.Outcome.SERVICER_FAILURE describes local failure of
+ customer code.
+ termination_manager: The _interfaces.TerminationManager for the operation.
+ transmission_manager: The _interfaces.TransmissionManager for the
+ operation.
+ """
+ self._lock = lock
+ self._local_failure = local_failure
+ self._termination_manager = termination_manager
+ self._transmission_manager = transmission_manager
+ self._ingestion_manager = None
+ self._expiration_manager = None
+
+ self.operation_id = operation_id
+
+ def set_ingestion_and_expiration_managers(
+ self, ingestion_manager, expiration_manager):
+ """Sets managers with which this OperationContext cooperates.
+
+ Args:
+ ingestion_manager: The _interfaces.IngestionManager for the operation.
+ expiration_manager: The _interfaces.ExpirationManager for the operation.
+ """
+ self._ingestion_manager = ingestion_manager
+ self._expiration_manager = expiration_manager
+
+ def is_active(self):
+ """See interfaces.OperationContext.is_active for specification."""
+ with self._lock:
+ return self._termination_manager.is_active()
+
+ def add_termination_callback(self, callback):
+ """See interfaces.OperationContext.add_termination_callback."""
+ with self._lock:
+ self._termination_manager.add_callback(callback)
+
+ def time_remaining(self):
+ """See interfaces.OperationContext.time_remaining for specification."""
+ with self._lock:
+ deadline = self._expiration_manager.deadline()
+ return max(0.0, deadline - time.time())
+
+ def fail(self, exception):
+ """See interfaces.OperationContext.fail for specification."""
+ with self._lock:
+ self._termination_manager.abort(self._local_failure)
+ self._transmission_manager.abort(self._local_failure)
+ self._ingestion_manager.abort()
+ self._expiration_manager.abort()
diff --git a/src/python/grpcio/grpc/framework/base/_emission.py b/src/python/grpcio/grpc/framework/base/_emission.py
new file mode 100644
index 0000000000..1829669a72
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/base/_emission.py
@@ -0,0 +1,125 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""State and behavior for handling emitted values."""
+
+from grpc.framework.base import interfaces
+from grpc.framework.base import _interfaces
+
+
+class _EmissionManager(_interfaces.EmissionManager):
+ """An implementation of _interfaces.EmissionManager."""
+
+ def __init__(
+ self, lock, failure_outcome, termination_manager, transmission_manager):
+ """Constructor.
+
+ Args:
+ lock: The operation-wide lock.
+ failure_outcome: Whichever one of interfaces.Outcome.SERVICED_FAILURE or
+ interfaces.Outcome.SERVICER_FAILURE describes this object's methods
+ being called inappropriately by customer code.
+ termination_manager: The _interfaces.TerminationManager for the operation.
+ transmission_manager: The _interfaces.TransmissionManager for the
+ operation.
+ """
+ self._lock = lock
+ self._failure_outcome = failure_outcome
+ self._termination_manager = termination_manager
+ self._transmission_manager = transmission_manager
+ self._ingestion_manager = None
+ self._expiration_manager = None
+
+ self._emission_complete = False
+
+ def set_ingestion_manager_and_expiration_manager(
+ self, ingestion_manager, expiration_manager):
+ self._ingestion_manager = ingestion_manager
+ self._expiration_manager = expiration_manager
+
+ def _abort(self):
+ self._termination_manager.abort(self._failure_outcome)
+ self._transmission_manager.abort(self._failure_outcome)
+ self._ingestion_manager.abort()
+ self._expiration_manager.abort()
+
+ def consume(self, value):
+ with self._lock:
+ if self._emission_complete:
+ self._abort()
+ else:
+ self._transmission_manager.inmit(value, False)
+
+ def terminate(self):
+ with self._lock:
+ if not self._emission_complete:
+ self._termination_manager.emission_complete()
+ self._transmission_manager.inmit(None, True)
+ self._emission_complete = True
+
+ def consume_and_terminate(self, value):
+ with self._lock:
+ if self._emission_complete:
+ self._abort()
+ else:
+ self._termination_manager.emission_complete()
+ self._transmission_manager.inmit(value, True)
+ self._emission_complete = True
+
+
+def front_emission_manager(lock, termination_manager, transmission_manager):
+ """Creates an _interfaces.EmissionManager appropriate for front-side use.
+
+ Args:
+ lock: The operation-wide lock.
+ termination_manager: The _interfaces.TerminationManager for the operation.
+ transmission_manager: The _interfaces.TransmissionManager for the operation.
+
+ Returns:
+ An _interfaces.EmissionManager appropriate for front-side use.
+ """
+ return _EmissionManager(
+ lock, interfaces.Outcome.SERVICED_FAILURE, termination_manager,
+ transmission_manager)
+
+
+def back_emission_manager(lock, termination_manager, transmission_manager):
+ """Creates an _interfaces.EmissionManager appropriate for back-side use.
+
+ Args:
+ lock: The operation-wide lock.
+ termination_manager: The _interfaces.TerminationManager for the operation.
+ transmission_manager: The _interfaces.TransmissionManager for the operation.
+
+ Returns:
+ An _interfaces.EmissionManager appropriate for back-side use.
+ """
+ return _EmissionManager(
+ lock, interfaces.Outcome.SERVICER_FAILURE, termination_manager,
+ transmission_manager)
diff --git a/src/python/grpcio/grpc/framework/base/_ends.py b/src/python/grpcio/grpc/framework/base/_ends.py
new file mode 100644
index 0000000000..176f3ac06e
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/base/_ends.py
@@ -0,0 +1,399 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Implementations of FrontLinks and BackLinks."""
+
+import collections
+import threading
+import uuid
+
+# _interfaces is referenced from specification in this module.
+from grpc.framework.base import _cancellation
+from grpc.framework.base import _context
+from grpc.framework.base import _emission
+from grpc.framework.base import _expiration
+from grpc.framework.base import _ingestion
+from grpc.framework.base import _interfaces # pylint: disable=unused-import
+from grpc.framework.base import _reception
+from grpc.framework.base import _termination
+from grpc.framework.base import _transmission
+from grpc.framework.base import interfaces
+from grpc.framework.foundation import callable_util
+
+_IDLE_ACTION_EXCEPTION_LOG_MESSAGE = 'Exception calling idle action!'
+
+
+class _EasyOperation(interfaces.Operation):
+ """A trivial implementation of interfaces.Operation."""
+
+ def __init__(self, emission_manager, context, cancellation_manager):
+ """Constructor.
+
+ Args:
+ emission_manager: The _interfaces.EmissionManager for the operation that
+ will accept values emitted by customer code.
+ context: The interfaces.OperationContext for use by the customer
+ during the operation.
+ cancellation_manager: The _interfaces.CancellationManager for the
+ operation.
+ """
+ self.consumer = emission_manager
+ self.context = context
+ self._cancellation_manager = cancellation_manager
+
+ def cancel(self):
+ self._cancellation_manager.cancel()
+
+
+class _Endlette(object):
+ """Utility for stateful behavior common to Fronts and Backs."""
+
+ def __init__(self, pool):
+ """Constructor.
+
+ Args:
+ pool: A thread pool to use when calling registered idle actions.
+ """
+ self._lock = threading.Lock()
+ self._pool = pool
+ # Dictionary from operation IDs to ReceptionManager-or-None. A None value
+ # indicates an in-progress fire-and-forget operation for which the customer
+ # has chosen to ignore results.
+ self._operations = {}
+ self._stats = {outcome: 0 for outcome in interfaces.Outcome}
+ self._idle_actions = []
+
+ def terminal_action(self, operation_id):
+ """Constructs the termination action for a single operation.
+
+ Args:
+ operation_id: An operation ID.
+
+ Returns:
+ A callable that takes an operation outcome for an argument to be used as
+ the termination action for the operation associated with the given
+ operation ID.
+ """
+ def termination_action(outcome):
+ with self._lock:
+ self._stats[outcome] += 1
+ self._operations.pop(operation_id, None)
+ if not self._operations:
+ for action in self._idle_actions:
+ self._pool.submit(callable_util.with_exceptions_logged(
+ action, _IDLE_ACTION_EXCEPTION_LOG_MESSAGE))
+ self._idle_actions = []
+ return termination_action
+
+ def __enter__(self):
+ self._lock.acquire()
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ self._lock.release()
+
+ def get_operation(self, operation_id):
+ return self._operations.get(operation_id, None)
+
+ def add_operation(self, operation_id, operation_reception_manager):
+ self._operations[operation_id] = operation_reception_manager
+
+ def operation_stats(self):
+ with self._lock:
+ return dict(self._stats)
+
+ def add_idle_action(self, action):
+ with self._lock:
+ if self._operations:
+ self._idle_actions.append(action)
+ else:
+ self._pool.submit(callable_util.with_exceptions_logged(
+ action, _IDLE_ACTION_EXCEPTION_LOG_MESSAGE))
+
+
+class _FrontManagement(
+ collections.namedtuple(
+ '_FrontManagement',
+ ('reception', 'emission', 'operation', 'cancellation'))):
+ """Just a trivial helper class to bundle four fellow-traveling objects."""
+
+
+def _front_operate(
+ callback, work_pool, transmission_pool, utility_pool,
+ termination_action, operation_id, name, payload, complete, timeout,
+ subscription, trace_id):
+ """Constructs objects necessary for front-side operation management.
+
+ Args:
+ callback: A callable that accepts interfaces.FrontToBackTickets and
+ delivers them to the other side of the operation. Execution of this
+ callable may take any arbitrary length of time.
+ work_pool: A thread pool in which to execute customer code.
+ transmission_pool: A thread pool to use for transmitting to the other side
+ of the operation.
+ utility_pool: A thread pool for utility tasks.
+ termination_action: A no-arg behavior to be called upon operation
+ completion.
+ operation_id: An object identifying the operation.
+ name: The name of the method being called during the operation.
+ payload: The first customer-significant value to be transmitted to the other
+ side. May be None if there is no such value or if the customer chose not
+ to pass it at operation invocation.
+ complete: A boolean indicating whether or not additional payloads will be
+ supplied by the customer.
+ timeout: A length of time in seconds to allow for the operation.
+ subscription: A interfaces.ServicedSubscription describing the
+ customer's interest in the results of the operation.
+ trace_id: A uuid.UUID identifying a set of related operations to which this
+ operation belongs. May be None.
+
+ Returns:
+ A _FrontManagement object bundling together the
+ _interfaces.ReceptionManager, _interfaces.EmissionManager,
+ _context.OperationContext, and _interfaces.CancellationManager for the
+ operation.
+ """
+ lock = threading.Lock()
+ with lock:
+ termination_manager = _termination.front_termination_manager(
+ work_pool, utility_pool, termination_action, subscription.kind)
+ transmission_manager = _transmission.front_transmission_manager(
+ lock, transmission_pool, callback, operation_id, name,
+ subscription.kind, trace_id, timeout, termination_manager)
+ operation_context = _context.OperationContext(
+ lock, operation_id, interfaces.Outcome.SERVICED_FAILURE,
+ termination_manager, transmission_manager)
+ emission_manager = _emission.front_emission_manager(
+ lock, termination_manager, transmission_manager)
+ ingestion_manager = _ingestion.front_ingestion_manager(
+ lock, work_pool, subscription, termination_manager,
+ transmission_manager, operation_context)
+ expiration_manager = _expiration.front_expiration_manager(
+ lock, termination_manager, transmission_manager, ingestion_manager,
+ timeout)
+ reception_manager = _reception.front_reception_manager(
+ lock, termination_manager, transmission_manager, ingestion_manager,
+ expiration_manager)
+ cancellation_manager = _cancellation.CancellationManager(
+ lock, termination_manager, transmission_manager, ingestion_manager,
+ expiration_manager)
+
+ termination_manager.set_expiration_manager(expiration_manager)
+ transmission_manager.set_ingestion_and_expiration_managers(
+ ingestion_manager, expiration_manager)
+ operation_context.set_ingestion_and_expiration_managers(
+ ingestion_manager, expiration_manager)
+ emission_manager.set_ingestion_manager_and_expiration_manager(
+ ingestion_manager, expiration_manager)
+ ingestion_manager.set_expiration_manager(expiration_manager)
+
+ transmission_manager.inmit(payload, complete)
+
+ if subscription.kind is interfaces.ServicedSubscription.Kind.NONE:
+ returned_reception_manager = None
+ else:
+ returned_reception_manager = reception_manager
+
+ return _FrontManagement(
+ returned_reception_manager, emission_manager, operation_context,
+ cancellation_manager)
+
+
+class FrontLink(interfaces.FrontLink):
+ """An implementation of interfaces.FrontLink."""
+
+ def __init__(self, work_pool, transmission_pool, utility_pool):
+ """Constructor.
+
+ Args:
+ work_pool: A thread pool to be used for executing customer code.
+ transmission_pool: A thread pool to be used for transmitting values to
+ the other side of the operation.
+ utility_pool: A thread pool to be used for utility tasks.
+ """
+ self._endlette = _Endlette(utility_pool)
+ self._work_pool = work_pool
+ self._transmission_pool = transmission_pool
+ self._utility_pool = utility_pool
+ self._callback = None
+
+ self._operations = {}
+
+ def join_rear_link(self, rear_link):
+ """See interfaces.ForeLink.join_rear_link for specification."""
+ with self._endlette:
+ self._callback = rear_link.accept_front_to_back_ticket
+
+ def operation_stats(self):
+ """See interfaces.End.operation_stats for specification."""
+ return self._endlette.operation_stats()
+
+ def add_idle_action(self, action):
+ """See interfaces.End.add_idle_action for specification."""
+ self._endlette.add_idle_action(action)
+
+ def operate(
+ self, name, payload, complete, timeout, subscription, trace_id):
+ """See interfaces.Front.operate for specification."""
+ operation_id = uuid.uuid4()
+ with self._endlette:
+ management = _front_operate(
+ self._callback, self._work_pool, self._transmission_pool,
+ self._utility_pool, self._endlette.terminal_action(operation_id),
+ operation_id, name, payload, complete, timeout, subscription,
+ trace_id)
+ self._endlette.add_operation(operation_id, management.reception)
+ return _EasyOperation(
+ management.emission, management.operation, management.cancellation)
+
+ def accept_back_to_front_ticket(self, ticket):
+ """See interfaces.End.act for specification."""
+ with self._endlette:
+ reception_manager = self._endlette.get_operation(ticket.operation_id)
+ if reception_manager:
+ reception_manager.receive_ticket(ticket)
+
+
+def _back_operate(
+ servicer, callback, work_pool, transmission_pool, utility_pool,
+ termination_action, ticket, default_timeout, maximum_timeout):
+ """Constructs objects necessary for back-side operation management.
+
+ Also begins back-side operation by feeding the first received ticket into the
+ constructed _interfaces.ReceptionManager.
+
+ Args:
+ servicer: An interfaces.Servicer for servicing operations.
+ callback: A callable that accepts interfaces.BackToFrontTickets and
+ delivers them to the other side of the operation. Execution of this
+ callable may take any arbitrary length of time.
+ work_pool: A thread pool in which to execute customer code.
+ transmission_pool: A thread pool to use for transmitting to the other side
+ of the operation.
+ utility_pool: A thread pool for utility tasks.
+ termination_action: A no-arg behavior to be called upon operation
+ completion.
+ ticket: The first interfaces.FrontToBackTicket received for the operation.
+ default_timeout: A length of time in seconds to be used as the default
+ time alloted for a single operation.
+ maximum_timeout: A length of time in seconds to be used as the maximum
+ time alloted for a single operation.
+
+ Returns:
+ The _interfaces.ReceptionManager to be used for the operation.
+ """
+ lock = threading.Lock()
+ with lock:
+ termination_manager = _termination.back_termination_manager(
+ work_pool, utility_pool, termination_action, ticket.subscription)
+ transmission_manager = _transmission.back_transmission_manager(
+ lock, transmission_pool, callback, ticket.operation_id,
+ termination_manager, ticket.subscription)
+ operation_context = _context.OperationContext(
+ lock, ticket.operation_id, interfaces.Outcome.SERVICER_FAILURE,
+ termination_manager, transmission_manager)
+ emission_manager = _emission.back_emission_manager(
+ lock, termination_manager, transmission_manager)
+ ingestion_manager = _ingestion.back_ingestion_manager(
+ lock, work_pool, servicer, termination_manager,
+ transmission_manager, operation_context, emission_manager)
+ expiration_manager = _expiration.back_expiration_manager(
+ lock, termination_manager, transmission_manager, ingestion_manager,
+ ticket.timeout, default_timeout, maximum_timeout)
+ reception_manager = _reception.back_reception_manager(
+ lock, termination_manager, transmission_manager, ingestion_manager,
+ expiration_manager)
+
+ termination_manager.set_expiration_manager(expiration_manager)
+ transmission_manager.set_ingestion_and_expiration_managers(
+ ingestion_manager, expiration_manager)
+ operation_context.set_ingestion_and_expiration_managers(
+ ingestion_manager, expiration_manager)
+ emission_manager.set_ingestion_manager_and_expiration_manager(
+ ingestion_manager, expiration_manager)
+ ingestion_manager.set_expiration_manager(expiration_manager)
+
+ reception_manager.receive_ticket(ticket)
+
+ return reception_manager
+
+
+class BackLink(interfaces.BackLink):
+ """An implementation of interfaces.BackLink."""
+
+ def __init__(
+ self, servicer, work_pool, transmission_pool, utility_pool,
+ default_timeout, maximum_timeout):
+ """Constructor.
+
+ Args:
+ servicer: An interfaces.Servicer for servicing operations.
+ work_pool: A thread pool in which to execute customer code.
+ transmission_pool: A thread pool to use for transmitting to the other side
+ of the operation.
+ utility_pool: A thread pool for utility tasks.
+ default_timeout: A length of time in seconds to be used as the default
+ time alloted for a single operation.
+ maximum_timeout: A length of time in seconds to be used as the maximum
+ time alloted for a single operation.
+ """
+ self._endlette = _Endlette(utility_pool)
+ self._servicer = servicer
+ self._work_pool = work_pool
+ self._transmission_pool = transmission_pool
+ self._utility_pool = utility_pool
+ self._default_timeout = default_timeout
+ self._maximum_timeout = maximum_timeout
+ self._callback = None
+
+ def join_fore_link(self, fore_link):
+ """See interfaces.RearLink.join_fore_link for specification."""
+ with self._endlette:
+ self._callback = fore_link.accept_back_to_front_ticket
+
+ def accept_front_to_back_ticket(self, ticket):
+ """See interfaces.RearLink.accept_front_to_back_ticket for specification."""
+ with self._endlette:
+ reception_manager = self._endlette.get_operation(ticket.operation_id)
+ if reception_manager is None:
+ reception_manager = _back_operate(
+ self._servicer, self._callback, self._work_pool,
+ self._transmission_pool, self._utility_pool,
+ self._endlette.terminal_action(ticket.operation_id), ticket,
+ self._default_timeout, self._maximum_timeout)
+ self._endlette.add_operation(ticket.operation_id, reception_manager)
+ else:
+ reception_manager.receive_ticket(ticket)
+
+ def operation_stats(self):
+ """See interfaces.End.operation_stats for specification."""
+ return self._endlette.operation_stats()
+
+ def add_idle_action(self, action):
+ """See interfaces.End.add_idle_action for specification."""
+ self._endlette.add_idle_action(action)
diff --git a/src/python/grpcio/grpc/framework/base/_expiration.py b/src/python/grpcio/grpc/framework/base/_expiration.py
new file mode 100644
index 0000000000..17acbef4c1
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/base/_expiration.py
@@ -0,0 +1,158 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""State and behavior for operation expiration."""
+
+import time
+
+from grpc.framework.base import _interfaces
+from grpc.framework.base import interfaces
+from grpc.framework.foundation import later
+
+
+class _ExpirationManager(_interfaces.ExpirationManager):
+ """An implementation of _interfaces.ExpirationManager."""
+
+ def __init__(
+ self, lock, termination_manager, transmission_manager, ingestion_manager,
+ commencement, timeout, maximum_timeout):
+ """Constructor.
+
+ Args:
+ lock: The operation-wide lock.
+ termination_manager: The _interfaces.TerminationManager for the operation.
+ transmission_manager: The _interfaces.TransmissionManager for the
+ operation.
+ ingestion_manager: The _interfaces.IngestionManager for the operation.
+ commencement: The time in seconds since the epoch at which the operation
+ began.
+ timeout: A length of time in seconds to allow for the operation to run.
+ maximum_timeout: The maximum length of time in seconds to allow for the
+ operation to run despite what is requested via this object's
+ change_timout method.
+ """
+ self._lock = lock
+ self._termination_manager = termination_manager
+ self._transmission_manager = transmission_manager
+ self._ingestion_manager = ingestion_manager
+ self._commencement = commencement
+ self._maximum_timeout = maximum_timeout
+
+ self._timeout = timeout
+ self._deadline = commencement + timeout
+ self._index = None
+ self._future = None
+
+ def _expire(self, index):
+ with self._lock:
+ if self._future is not None and index == self._index:
+ self._future = None
+ self._termination_manager.abort(interfaces.Outcome.EXPIRED)
+ self._transmission_manager.abort(interfaces.Outcome.EXPIRED)
+ self._ingestion_manager.abort()
+
+ def start(self):
+ self._index = 0
+ self._future = later.later(self._timeout, lambda: self._expire(0))
+
+ def change_timeout(self, timeout):
+ if self._future is not None and timeout != self._timeout:
+ self._future.cancel()
+ new_timeout = min(timeout, self._maximum_timeout)
+ new_index = self._index + 1
+ self._timeout = new_timeout
+ self._deadline = self._commencement + new_timeout
+ self._index = new_index
+ delay = self._deadline - time.time()
+ self._future = later.later(
+ delay, lambda: self._expire(new_index))
+
+ def deadline(self):
+ return self._deadline
+
+ def abort(self):
+ if self._future:
+ self._future.cancel()
+ self._future = None
+ self._deadline_index = None
+
+
+def front_expiration_manager(
+ lock, termination_manager, transmission_manager, ingestion_manager,
+ timeout):
+ """Creates an _interfaces.ExpirationManager appropriate for front-side use.
+
+ Args:
+ lock: The operation-wide lock.
+ termination_manager: The _interfaces.TerminationManager for the operation.
+ transmission_manager: The _interfaces.TransmissionManager for the
+ operation.
+ ingestion_manager: The _interfaces.IngestionManager for the operation.
+ timeout: A length of time in seconds to allow for the operation to run.
+
+ Returns:
+ An _interfaces.ExpirationManager appropriate for front-side use.
+ """
+ commencement = time.time()
+ expiration_manager = _ExpirationManager(
+ lock, termination_manager, transmission_manager, ingestion_manager,
+ commencement, timeout, timeout)
+ expiration_manager.start()
+ return expiration_manager
+
+
+def back_expiration_manager(
+ lock, termination_manager, transmission_manager, ingestion_manager,
+ timeout, default_timeout, maximum_timeout):
+ """Creates an _interfaces.ExpirationManager appropriate for back-side use.
+
+ Args:
+ lock: The operation-wide lock.
+ termination_manager: The _interfaces.TerminationManager for the operation.
+ transmission_manager: The _interfaces.TransmissionManager for the
+ operation.
+ ingestion_manager: The _interfaces.IngestionManager for the operation.
+ timeout: A length of time in seconds to allow for the operation to run. May
+ be None in which case default_timeout will be used.
+ default_timeout: The default length of time in seconds to allow for the
+ operation to run if the front-side customer has not specified such a value
+ (or if the value they specified is not yet known).
+ maximum_timeout: The maximum length of time in seconds to allow for the
+ operation to run.
+
+ Returns:
+ An _interfaces.ExpirationManager appropriate for back-side use.
+ """
+ commencement = time.time()
+ expiration_manager = _ExpirationManager(
+ lock, termination_manager, transmission_manager, ingestion_manager,
+ commencement, default_timeout if timeout is None else timeout,
+ maximum_timeout)
+ expiration_manager.start()
+ return expiration_manager
diff --git a/src/python/grpcio/grpc/framework/base/_ingestion.py b/src/python/grpcio/grpc/framework/base/_ingestion.py
new file mode 100644
index 0000000000..06d5b92f0b
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/base/_ingestion.py
@@ -0,0 +1,442 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""State and behavior for ingestion during an operation."""
+
+import abc
+import collections
+
+from grpc.framework.base import _constants
+from grpc.framework.base import _interfaces
+from grpc.framework.base import exceptions
+from grpc.framework.base import interfaces
+from grpc.framework.foundation import abandonment
+from grpc.framework.foundation import callable_util
+from grpc.framework.foundation import stream
+
+_CREATE_CONSUMER_EXCEPTION_LOG_MESSAGE = 'Exception initializing ingestion!'
+_CONSUME_EXCEPTION_LOG_MESSAGE = 'Exception during ingestion!'
+
+
+class _ConsumerCreation(collections.namedtuple(
+ '_ConsumerCreation', ('consumer', 'remote_error', 'abandoned'))):
+ """A sum type for the outcome of ingestion initialization.
+
+ Either consumer will be non-None, remote_error will be True, or abandoned will
+ be True.
+
+ Attributes:
+ consumer: A stream.Consumer for ingesting payloads.
+ remote_error: A boolean indicating that the consumer could not be created
+ due to an error on the remote side of the operation.
+ abandoned: A boolean indicating that the consumer creation was abandoned.
+ """
+
+
+class _EmptyConsumer(stream.Consumer):
+ """A no-operative stream.Consumer that ignores all inputs and calls."""
+
+ def consume(self, value):
+ """See stream.Consumer.consume for specification."""
+
+ def terminate(self):
+ """See stream.Consumer.terminate for specification."""
+
+ def consume_and_terminate(self, value):
+ """See stream.Consumer.consume_and_terminate for specification."""
+
+
+class _ConsumerCreator(object):
+ """Common specification of different consumer-creating behavior."""
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def create_consumer(self, requirement):
+ """Creates the stream.Consumer to which customer payloads will be delivered.
+
+ Any exceptions raised by this method should be attributed to and treated as
+ defects in the serviced or servicer code called by this method.
+
+ Args:
+ requirement: A value required by this _ConsumerCreator for consumer
+ creation.
+
+ Returns:
+ A _ConsumerCreation describing the result of consumer creation.
+ """
+ raise NotImplementedError()
+
+
+class _FrontConsumerCreator(_ConsumerCreator):
+ """A _ConsumerCreator appropriate for front-side use."""
+
+ def __init__(self, subscription, operation_context):
+ """Constructor.
+
+ Args:
+ subscription: The serviced's interfaces.ServicedSubscription for the
+ operation.
+ operation_context: The interfaces.OperationContext object for the
+ operation.
+ """
+ self._subscription = subscription
+ self._operation_context = operation_context
+
+ def create_consumer(self, requirement):
+ """See _ConsumerCreator.create_consumer for specification."""
+ if self._subscription.kind is interfaces.ServicedSubscription.Kind.FULL:
+ try:
+ return _ConsumerCreation(
+ self._subscription.ingestor.consumer(self._operation_context),
+ False, False)
+ except abandonment.Abandoned:
+ return _ConsumerCreation(None, False, True)
+ else:
+ return _ConsumerCreation(_EmptyConsumer(), False, False)
+
+
+class _BackConsumerCreator(_ConsumerCreator):
+ """A _ConsumerCreator appropriate for back-side use."""
+
+ def __init__(self, servicer, operation_context, emission_consumer):
+ """Constructor.
+
+ Args:
+ servicer: The interfaces.Servicer that will service the operation.
+ operation_context: The interfaces.OperationContext object for the
+ operation.
+ emission_consumer: The stream.Consumer object to which payloads emitted
+ from the operation will be passed.
+ """
+ self._servicer = servicer
+ self._operation_context = operation_context
+ self._emission_consumer = emission_consumer
+
+ def create_consumer(self, requirement):
+ """See _ConsumerCreator.create_consumer for full specification.
+
+ Args:
+ requirement: The name of the Servicer method to be called during this
+ operation.
+
+ Returns:
+ A _ConsumerCreation describing the result of consumer creation.
+ """
+ try:
+ return _ConsumerCreation(
+ self._servicer.service(
+ requirement, self._operation_context, self._emission_consumer),
+ False, False)
+ except exceptions.NoSuchMethodError:
+ return _ConsumerCreation(None, True, False)
+ except abandonment.Abandoned:
+ return _ConsumerCreation(None, False, True)
+
+
+class _WrappedConsumer(object):
+ """Wraps a consumer to catch the exceptions that it is allowed to throw."""
+
+ def __init__(self, consumer):
+ """Constructor.
+
+ Args:
+ consumer: A stream.Consumer that may raise abandonment.Abandoned from any
+ of its methods.
+ """
+ self._consumer = consumer
+
+ def moar(self, payload, complete):
+ """Makes progress with the wrapped consumer.
+
+ This method catches all exceptions allowed to be thrown by the wrapped
+ consumer. Any exceptions raised by this method should be blamed on the
+ customer-supplied consumer.
+
+ Args:
+ payload: A customer-significant payload object. May be None only if
+ complete is True.
+ complete: Whether or not the end of the payload sequence has been reached.
+ Must be True if payload is None.
+
+ Returns:
+ True if the wrapped consumer made progress or False if the wrapped
+ consumer raised abandonment.Abandoned to indicate its abandonment of
+ progress.
+ """
+ try:
+ if payload is None:
+ self._consumer.terminate()
+ elif complete:
+ self._consumer.consume_and_terminate(payload)
+ else:
+ self._consumer.consume(payload)
+ return True
+ except abandonment.Abandoned:
+ return False
+
+
+class _IngestionManager(_interfaces.IngestionManager):
+ """An implementation of _interfaces.IngestionManager."""
+
+ def __init__(
+ self, lock, pool, consumer_creator, failure_outcome, termination_manager,
+ transmission_manager):
+ """Constructor.
+
+ Args:
+ lock: The operation-wide lock.
+ pool: A thread pool in which to execute customer code.
+ consumer_creator: A _ConsumerCreator wrapping the portion of customer code
+ that when called returns the stream.Consumer with which the customer
+ code will ingest payload values.
+ failure_outcome: Whichever one of
+ interfaces.Outcome.SERVICED_FAILURE or
+ interfaces.Outcome.SERVICER_FAILURE describes local failure of
+ customer code.
+ termination_manager: The _interfaces.TerminationManager for the operation.
+ transmission_manager: The _interfaces.TransmissionManager for the
+ operation.
+ """
+ self._lock = lock
+ self._pool = pool
+ self._consumer_creator = consumer_creator
+ self._failure_outcome = failure_outcome
+ self._termination_manager = termination_manager
+ self._transmission_manager = transmission_manager
+ self._expiration_manager = None
+
+ self._wrapped_ingestion_consumer = None
+ self._pending_ingestion = []
+ self._ingestion_complete = False
+ self._processing = False
+
+ def set_expiration_manager(self, expiration_manager):
+ self._expiration_manager = expiration_manager
+
+ def _abort_internal_only(self):
+ self._wrapped_ingestion_consumer = None
+ self._pending_ingestion = None
+
+ def _abort_and_notify(self, outcome):
+ self._abort_internal_only()
+ self._termination_manager.abort(outcome)
+ self._transmission_manager.abort(outcome)
+ self._expiration_manager.abort()
+
+ def _next(self):
+ """Computes the next step for ingestion.
+
+ Returns:
+ A payload, complete, continue triplet indicating what payload (if any) is
+ available to feed into customer code, whether or not the sequence of
+ payloads has terminated, and whether or not there is anything
+ immediately actionable to call customer code to do.
+ """
+ if self._pending_ingestion is None:
+ return None, False, False
+ elif self._pending_ingestion:
+ payload = self._pending_ingestion.pop(0)
+ complete = self._ingestion_complete and not self._pending_ingestion
+ return payload, complete, True
+ elif self._ingestion_complete:
+ return None, True, True
+ else:
+ return None, False, False
+
+ def _process(self, wrapped_ingestion_consumer, payload, complete):
+ """A method to call to execute customer code.
+
+ This object's lock must *not* be held when calling this method.
+
+ Args:
+ wrapped_ingestion_consumer: The _WrappedConsumer with which to pass
+ payloads to customer code.
+ payload: A customer payload. May be None only if complete is True.
+ complete: Whether or not the sequence of payloads to pass to the customer
+ has concluded.
+ """
+ while True:
+ consumption_outcome = callable_util.call_logging_exceptions(
+ wrapped_ingestion_consumer.moar, _CONSUME_EXCEPTION_LOG_MESSAGE,
+ payload, complete)
+ if consumption_outcome.exception is None:
+ if consumption_outcome.return_value:
+ with self._lock:
+ if complete:
+ self._pending_ingestion = None
+ self._termination_manager.ingestion_complete()
+ return
+ else:
+ payload, complete, moar = self._next()
+ if not moar:
+ self._processing = False
+ return
+ else:
+ with self._lock:
+ if self._pending_ingestion is not None:
+ self._abort_and_notify(self._failure_outcome)
+ self._processing = False
+ return
+ else:
+ with self._lock:
+ self._abort_and_notify(self._failure_outcome)
+ self._processing = False
+ return
+
+ def start(self, requirement):
+ if self._pending_ingestion is not None:
+ def initialize():
+ consumer_creation_outcome = callable_util.call_logging_exceptions(
+ self._consumer_creator.create_consumer,
+ _CREATE_CONSUMER_EXCEPTION_LOG_MESSAGE, requirement)
+ if consumer_creation_outcome.return_value is None:
+ with self._lock:
+ self._abort_and_notify(self._failure_outcome)
+ self._processing = False
+ elif consumer_creation_outcome.return_value.remote_error:
+ with self._lock:
+ self._abort_and_notify(interfaces.Outcome.RECEPTION_FAILURE)
+ self._processing = False
+ elif consumer_creation_outcome.return_value.abandoned:
+ with self._lock:
+ if self._pending_ingestion is not None:
+ self._abort_and_notify(self._failure_outcome)
+ self._processing = False
+ else:
+ wrapped_ingestion_consumer = _WrappedConsumer(
+ consumer_creation_outcome.return_value.consumer)
+ with self._lock:
+ self._wrapped_ingestion_consumer = wrapped_ingestion_consumer
+ payload, complete, moar = self._next()
+ if not moar:
+ self._processing = False
+ return
+
+ self._process(wrapped_ingestion_consumer, payload, complete)
+
+ self._pool.submit(
+ callable_util.with_exceptions_logged(
+ initialize, _constants.INTERNAL_ERROR_LOG_MESSAGE))
+ self._processing = True
+
+ def consume(self, payload):
+ if self._ingestion_complete:
+ self._abort_and_notify(self._failure_outcome)
+ elif self._pending_ingestion is not None:
+ if self._processing:
+ self._pending_ingestion.append(payload)
+ else:
+ self._pool.submit(
+ callable_util.with_exceptions_logged(
+ self._process, _constants.INTERNAL_ERROR_LOG_MESSAGE),
+ self._wrapped_ingestion_consumer, payload, False)
+ self._processing = True
+
+ def terminate(self):
+ if self._ingestion_complete:
+ self._abort_and_notify(self._failure_outcome)
+ else:
+ self._ingestion_complete = True
+ if self._pending_ingestion is not None and not self._processing:
+ self._pool.submit(
+ callable_util.with_exceptions_logged(
+ self._process, _constants.INTERNAL_ERROR_LOG_MESSAGE),
+ self._wrapped_ingestion_consumer, None, True)
+ self._processing = True
+
+ def consume_and_terminate(self, payload):
+ if self._ingestion_complete:
+ self._abort_and_notify(self._failure_outcome)
+ else:
+ self._ingestion_complete = True
+ if self._pending_ingestion is not None:
+ if self._processing:
+ self._pending_ingestion.append(payload)
+ else:
+ self._pool.submit(
+ callable_util.with_exceptions_logged(
+ self._process, _constants.INTERNAL_ERROR_LOG_MESSAGE),
+ self._wrapped_ingestion_consumer, payload, True)
+ self._processing = True
+
+ def abort(self):
+ """See _interfaces.IngestionManager.abort for specification."""
+ self._abort_internal_only()
+
+
+def front_ingestion_manager(
+ lock, pool, subscription, termination_manager, transmission_manager,
+ operation_context):
+ """Creates an IngestionManager appropriate for front-side use.
+
+ Args:
+ lock: The operation-wide lock.
+ pool: A thread pool in which to execute customer code.
+ subscription: A interfaces.ServicedSubscription indicating the
+ customer's interest in the results of the operation.
+ termination_manager: The _interfaces.TerminationManager for the operation.
+ transmission_manager: The _interfaces.TransmissionManager for the
+ operation.
+ operation_context: A interfaces.OperationContext for the operation.
+
+ Returns:
+ An IngestionManager appropriate for front-side use.
+ """
+ ingestion_manager = _IngestionManager(
+ lock, pool, _FrontConsumerCreator(subscription, operation_context),
+ interfaces.Outcome.SERVICED_FAILURE, termination_manager,
+ transmission_manager)
+ ingestion_manager.start(None)
+ return ingestion_manager
+
+
+def back_ingestion_manager(
+ lock, pool, servicer, termination_manager, transmission_manager,
+ operation_context, emission_consumer):
+ """Creates an IngestionManager appropriate for back-side use.
+
+ Args:
+ lock: The operation-wide lock.
+ pool: A thread pool in which to execute customer code.
+ servicer: A interfaces.Servicer for servicing the operation.
+ termination_manager: The _interfaces.TerminationManager for the operation.
+ transmission_manager: The _interfaces.TransmissionManager for the
+ operation.
+ operation_context: A interfaces.OperationContext for the operation.
+ emission_consumer: The _interfaces.EmissionConsumer for the operation.
+
+ Returns:
+ An IngestionManager appropriate for back-side use.
+ """
+ ingestion_manager = _IngestionManager(
+ lock, pool, _BackConsumerCreator(
+ servicer, operation_context, emission_consumer),
+ interfaces.Outcome.SERVICER_FAILURE, termination_manager,
+ transmission_manager)
+ return ingestion_manager
diff --git a/src/python/grpcio/grpc/framework/base/_interfaces.py b/src/python/grpcio/grpc/framework/base/_interfaces.py
new file mode 100644
index 0000000000..d88cf76590
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/base/_interfaces.py
@@ -0,0 +1,271 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Package-internal interfaces."""
+
+import abc
+
+# interfaces is referenced from specification in this module.
+from grpc.framework.base import interfaces # pylint: disable=unused-import
+from grpc.framework.foundation import stream
+
+
+class TerminationManager(object):
+ """An object responsible for handling the termination of an operation."""
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def set_expiration_manager(self, expiration_manager):
+ """Sets the ExpirationManager with which this object will cooperate."""
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def is_active(self):
+ """Reports whether or not the operation is active.
+
+ Returns:
+ True if the operation is active or False if the operation has terminated.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def add_callback(self, callback):
+ """Registers a callback to be called on operation termination.
+
+ If the operation has already terminated, the callback will be called
+ immediately.
+
+ Args:
+ callback: A callable that will be passed an interfaces.Outcome value.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def emission_complete(self):
+ """Indicates that emissions from customer code have completed."""
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def transmission_complete(self):
+ """Indicates that transmissions to the remote end are complete."""
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def ingestion_complete(self):
+ """Indicates that customer code ingestion of received values is complete."""
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def abort(self, outcome):
+ """Indicates that the operation must abort for the indicated reason.
+
+ Args:
+ outcome: An interfaces.Outcome indicating operation abortion.
+ """
+ raise NotImplementedError()
+
+
+class TransmissionManager(object):
+ """A manager responsible for transmitting to the other end of an operation."""
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def inmit(self, emission, complete):
+ """Accepts a value for transmission to the other end of the operation.
+
+ Args:
+ emission: A value of some significance to the customer to be transmitted
+ to the other end of the operation. May be None only if complete is True.
+ complete: A boolean that if True indicates that customer code has emitted
+ all values it intends to emit.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def abort(self, outcome):
+ """Indicates that the operation has aborted for the indicated reason.
+
+ Args:
+ outcome: An interfaces.Outcome indicating operation abortion.
+ """
+ raise NotImplementedError()
+
+
+class EmissionManager(stream.Consumer):
+ """A manager of values emitted by customer code."""
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def set_ingestion_manager_and_expiration_manager(
+ self, ingestion_manager, expiration_manager):
+ """Sets two other objects with which this EmissionManager will cooperate.
+
+ Args:
+ ingestion_manager: The IngestionManager for the operation.
+ expiration_manager: The ExpirationManager for the operation.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def consume(self, value):
+ """Accepts a value emitted by customer code.
+
+ This method should only be called by customer code.
+
+ Args:
+ value: Any value of significance to the customer.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def terminate(self):
+ """Indicates that no more values will be emitted by customer code.
+
+ This method should only be called by customer code.
+
+ Implementations of this method may be idempotent and forgive customer code
+ calling this method more than once.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def consume_and_terminate(self, value):
+ """Accepts the last value emitted by customer code.
+
+ This method should only be called by customer code.
+
+ Args:
+ value: Any value of significance to the customer.
+ """
+ raise NotImplementedError()
+
+
+class IngestionManager(stream.Consumer):
+ """A manager responsible for executing customer code."""
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def set_expiration_manager(self, expiration_manager):
+ """Sets the ExpirationManager with which this object will cooperate."""
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def start(self, requirement):
+ """Commences execution of customer code.
+
+ Args:
+ requirement: Some value unavailable at the time of this object's
+ construction that is required to begin executing customer code.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def consume(self, payload):
+ """Accepts a customer-significant value to be supplied to customer code.
+
+ Args:
+ payload: Some customer-significant value.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def terminate(self):
+ """Indicates the end of values to be supplied to customer code."""
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def consume_and_terminate(self, payload):
+ """Accepts the last value to be supplied to customer code.
+
+ Args:
+ payload: Some customer-significant value (and the last such value).
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def abort(self):
+ """Indicates to this manager that the operation has aborted."""
+ raise NotImplementedError()
+
+
+class ExpirationManager(object):
+ """A manager responsible for aborting the operation if it runs out of time."""
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def change_timeout(self, timeout):
+ """Changes the timeout allotted for the operation.
+
+ Operation duration is always measure from the beginning of the operation;
+ calling this method changes the operation's allotted time to timeout total
+ seconds, not timeout seconds from the time of this method call.
+
+ Args:
+ timeout: A length of time in seconds to allow for the operation.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def deadline(self):
+ """Returns the time until which the operation is allowed to run.
+
+ Returns:
+ The time (seconds since the epoch) at which the operation will expire.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def abort(self):
+ """Indicates to this manager that the operation has aborted."""
+ raise NotImplementedError()
+
+
+class ReceptionManager(object):
+ """A manager responsible for receiving tickets from the other end."""
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def receive_ticket(self, ticket):
+ """Handle a ticket from the other side of the operation.
+
+ Args:
+ ticket: An interfaces.BackToFrontTicket or interfaces.FrontToBackTicket
+ appropriate to this end of the operation and this object.
+ """
+ raise NotImplementedError()
+
+
+class CancellationManager(object):
+ """A manager of operation cancellation."""
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def cancel(self):
+ """Cancels the operation."""
+ raise NotImplementedError()
diff --git a/src/python/grpcio/grpc/framework/base/_reception.py b/src/python/grpcio/grpc/framework/base/_reception.py
new file mode 100644
index 0000000000..dd428964f1
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/base/_reception.py
@@ -0,0 +1,399 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""State and behavior for ticket reception."""
+
+import abc
+
+from grpc.framework.base import interfaces
+from grpc.framework.base import _interfaces
+
+_INITIAL_FRONT_TO_BACK_TICKET_KINDS = (
+ interfaces.FrontToBackTicket.Kind.COMMENCEMENT,
+ interfaces.FrontToBackTicket.Kind.ENTIRE,
+)
+
+
+class _Receiver(object):
+ """Common specification of different ticket-handling behavior."""
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def abort_if_abortive(self, ticket):
+ """Aborts the operation if the ticket is abortive.
+
+ Args:
+ ticket: A just-arrived ticket.
+
+ Returns:
+ A boolean indicating whether or not this Receiver aborted the operation
+ based on the ticket.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def receive(self, ticket):
+ """Handles a just-arrived ticket.
+
+ Args:
+ ticket: A just-arrived ticket.
+
+ Returns:
+ A boolean indicating whether or not the ticket was terminal (i.e. whether
+ or not non-abortive tickets are legal after this one).
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def reception_failure(self):
+ """Aborts the operation with an indication of reception failure."""
+ raise NotImplementedError()
+
+
+def _abort(
+ outcome, termination_manager, transmission_manager, ingestion_manager,
+ expiration_manager):
+ """Indicates abortion with the given outcome to the given managers."""
+ termination_manager.abort(outcome)
+ transmission_manager.abort(outcome)
+ ingestion_manager.abort()
+ expiration_manager.abort()
+
+
+def _abort_if_abortive(
+ ticket, abortive, termination_manager, transmission_manager,
+ ingestion_manager, expiration_manager):
+ """Determines a ticket's being abortive and if so aborts the operation.
+
+ Args:
+ ticket: A just-arrived ticket.
+ abortive: A callable that takes a ticket and returns an interfaces.Outcome
+ indicating that the operation should be aborted or None indicating that
+ the operation should not be aborted.
+ termination_manager: The operation's _interfaces.TerminationManager.
+ transmission_manager: The operation's _interfaces.TransmissionManager.
+ ingestion_manager: The operation's _interfaces.IngestionManager.
+ expiration_manager: The operation's _interfaces.ExpirationManager.
+
+ Returns:
+ True if the operation was aborted; False otherwise.
+ """
+ abortion_outcome = abortive(ticket)
+ if abortion_outcome is None:
+ return False
+ else:
+ _abort(
+ abortion_outcome, termination_manager, transmission_manager,
+ ingestion_manager, expiration_manager)
+ return True
+
+
+def _reception_failure(
+ termination_manager, transmission_manager, ingestion_manager,
+ expiration_manager):
+ """Aborts the operation with an indication of reception failure."""
+ _abort(
+ interfaces.Outcome.RECEPTION_FAILURE, termination_manager,
+ transmission_manager, ingestion_manager, expiration_manager)
+
+
+class _BackReceiver(_Receiver):
+ """Ticket-handling specific to the back side of an operation."""
+
+ def __init__(
+ self, termination_manager, transmission_manager, ingestion_manager,
+ expiration_manager):
+ """Constructor.
+
+ Args:
+ termination_manager: The operation's _interfaces.TerminationManager.
+ transmission_manager: The operation's _interfaces.TransmissionManager.
+ ingestion_manager: The operation's _interfaces.IngestionManager.
+ expiration_manager: The operation's _interfaces.ExpirationManager.
+ """
+ self._termination_manager = termination_manager
+ self._transmission_manager = transmission_manager
+ self._ingestion_manager = ingestion_manager
+ self._expiration_manager = expiration_manager
+
+ self._first_ticket_seen = False
+ self._last_ticket_seen = False
+
+ def _abortive(self, ticket):
+ """Determines whether or not (and if so, how) a ticket is abortive.
+
+ Args:
+ ticket: A just-arrived ticket.
+
+ Returns:
+ An interfaces.Outcome value describing operation abortion if the
+ ticket is abortive or None if the ticket is not abortive.
+ """
+ if ticket.kind is interfaces.FrontToBackTicket.Kind.CANCELLATION:
+ return interfaces.Outcome.CANCELLED
+ elif ticket.kind is interfaces.FrontToBackTicket.Kind.EXPIRATION:
+ return interfaces.Outcome.EXPIRED
+ elif ticket.kind is interfaces.FrontToBackTicket.Kind.SERVICED_FAILURE:
+ return interfaces.Outcome.SERVICED_FAILURE
+ elif ticket.kind is interfaces.FrontToBackTicket.Kind.RECEPTION_FAILURE:
+ return interfaces.Outcome.SERVICED_FAILURE
+ elif (ticket.kind in _INITIAL_FRONT_TO_BACK_TICKET_KINDS and
+ self._first_ticket_seen):
+ return interfaces.Outcome.RECEPTION_FAILURE
+ elif self._last_ticket_seen:
+ return interfaces.Outcome.RECEPTION_FAILURE
+ else:
+ return None
+
+ def abort_if_abortive(self, ticket):
+ """See _Receiver.abort_if_abortive for specification."""
+ return _abort_if_abortive(
+ ticket, self._abortive, self._termination_manager,
+ self._transmission_manager, self._ingestion_manager,
+ self._expiration_manager)
+
+ def receive(self, ticket):
+ """See _Receiver.receive for specification."""
+ if ticket.timeout is not None:
+ self._expiration_manager.change_timeout(ticket.timeout)
+
+ if ticket.kind is interfaces.FrontToBackTicket.Kind.COMMENCEMENT:
+ self._first_ticket_seen = True
+ self._ingestion_manager.start(ticket.name)
+ if ticket.payload is not None:
+ self._ingestion_manager.consume(ticket.payload)
+ elif ticket.kind is interfaces.FrontToBackTicket.Kind.CONTINUATION:
+ self._ingestion_manager.consume(ticket.payload)
+ elif ticket.kind is interfaces.FrontToBackTicket.Kind.COMPLETION:
+ self._last_ticket_seen = True
+ if ticket.payload is None:
+ self._ingestion_manager.terminate()
+ else:
+ self._ingestion_manager.consume_and_terminate(ticket.payload)
+ else:
+ self._first_ticket_seen = True
+ self._last_ticket_seen = True
+ self._ingestion_manager.start(ticket.name)
+ if ticket.payload is None:
+ self._ingestion_manager.terminate()
+ else:
+ self._ingestion_manager.consume_and_terminate(ticket.payload)
+
+ def reception_failure(self):
+ """See _Receiver.reception_failure for specification."""
+ _reception_failure(
+ self._termination_manager, self._transmission_manager,
+ self._ingestion_manager, self._expiration_manager)
+
+
+class _FrontReceiver(_Receiver):
+ """Ticket-handling specific to the front side of an operation."""
+
+ def __init__(
+ self, termination_manager, transmission_manager, ingestion_manager,
+ expiration_manager):
+ """Constructor.
+
+ Args:
+ termination_manager: The operation's _interfaces.TerminationManager.
+ transmission_manager: The operation's _interfaces.TransmissionManager.
+ ingestion_manager: The operation's _interfaces.IngestionManager.
+ expiration_manager: The operation's _interfaces.ExpirationManager.
+ """
+ self._termination_manager = termination_manager
+ self._transmission_manager = transmission_manager
+ self._ingestion_manager = ingestion_manager
+ self._expiration_manager = expiration_manager
+
+ self._last_ticket_seen = False
+
+ def _abortive(self, ticket):
+ """Determines whether or not (and if so, how) a ticket is abortive.
+
+ Args:
+ ticket: A just-arrived ticket.
+
+ Returns:
+ An interfaces.Outcome value describing operation abortion if the ticket
+ is abortive or None if the ticket is not abortive.
+ """
+ if ticket.kind is interfaces.BackToFrontTicket.Kind.CANCELLATION:
+ return interfaces.Outcome.CANCELLED
+ elif ticket.kind is interfaces.BackToFrontTicket.Kind.EXPIRATION:
+ return interfaces.Outcome.EXPIRED
+ elif ticket.kind is interfaces.BackToFrontTicket.Kind.SERVICER_FAILURE:
+ return interfaces.Outcome.SERVICER_FAILURE
+ elif ticket.kind is interfaces.BackToFrontTicket.Kind.RECEPTION_FAILURE:
+ return interfaces.Outcome.SERVICER_FAILURE
+ elif self._last_ticket_seen:
+ return interfaces.Outcome.RECEPTION_FAILURE
+ else:
+ return None
+
+ def abort_if_abortive(self, ticket):
+ """See _Receiver.abort_if_abortive for specification."""
+ return _abort_if_abortive(
+ ticket, self._abortive, self._termination_manager,
+ self._transmission_manager, self._ingestion_manager,
+ self._expiration_manager)
+
+ def receive(self, ticket):
+ """See _Receiver.receive for specification."""
+ if ticket.kind is interfaces.BackToFrontTicket.Kind.CONTINUATION:
+ self._ingestion_manager.consume(ticket.payload)
+ elif ticket.kind is interfaces.BackToFrontTicket.Kind.COMPLETION:
+ self._last_ticket_seen = True
+ if ticket.payload is None:
+ self._ingestion_manager.terminate()
+ else:
+ self._ingestion_manager.consume_and_terminate(ticket.payload)
+
+ def reception_failure(self):
+ """See _Receiver.reception_failure for specification."""
+ _reception_failure(
+ self._termination_manager, self._transmission_manager,
+ self._ingestion_manager, self._expiration_manager)
+
+
+class _ReceptionManager(_interfaces.ReceptionManager):
+ """A ReceptionManager based around a _Receiver passed to it."""
+
+ def __init__(self, lock, receiver):
+ """Constructor.
+
+ Args:
+ lock: The operation-servicing-wide lock object.
+ receiver: A _Receiver responsible for handling received tickets.
+ """
+ self._lock = lock
+ self._receiver = receiver
+
+ self._lowest_unseen_sequence_number = 0
+ self._out_of_sequence_tickets = {}
+ self._completed_sequence_number = None
+ self._aborted = False
+
+ def _sequence_failure(self, ticket):
+ """Determines a just-arrived ticket's sequential legitimacy.
+
+ Args:
+ ticket: A just-arrived ticket.
+
+ Returns:
+ True if the ticket is sequentially legitimate; False otherwise.
+ """
+ if ticket.sequence_number < self._lowest_unseen_sequence_number:
+ return True
+ elif ticket.sequence_number in self._out_of_sequence_tickets:
+ return True
+ elif (self._completed_sequence_number is not None and
+ self._completed_sequence_number <= ticket.sequence_number):
+ return True
+ else:
+ return False
+
+ def _process(self, ticket):
+ """Process those tickets ready to be processed.
+
+ Args:
+ ticket: A just-arrived ticket the sequence number of which matches this
+ _ReceptionManager's _lowest_unseen_sequence_number field.
+ """
+ while True:
+ completed = self._receiver.receive(ticket)
+ if completed:
+ self._out_of_sequence_tickets.clear()
+ self._completed_sequence_number = ticket.sequence_number
+ self._lowest_unseen_sequence_number = ticket.sequence_number + 1
+ return
+ else:
+ next_ticket = self._out_of_sequence_tickets.pop(
+ ticket.sequence_number + 1, None)
+ if next_ticket is None:
+ self._lowest_unseen_sequence_number = ticket.sequence_number + 1
+ return
+ else:
+ ticket = next_ticket
+
+ def receive_ticket(self, ticket):
+ """See _interfaces.ReceptionManager.receive_ticket for specification."""
+ with self._lock:
+ if self._aborted:
+ return
+ elif self._sequence_failure(ticket):
+ self._receiver.reception_failure()
+ self._aborted = True
+ elif self._receiver.abort_if_abortive(ticket):
+ self._aborted = True
+ elif ticket.sequence_number == self._lowest_unseen_sequence_number:
+ self._process(ticket)
+ else:
+ self._out_of_sequence_tickets[ticket.sequence_number] = ticket
+
+
+def front_reception_manager(
+ lock, termination_manager, transmission_manager, ingestion_manager,
+ expiration_manager):
+ """Creates a _interfaces.ReceptionManager for front-side use.
+
+ Args:
+ lock: The operation-servicing-wide lock object.
+ termination_manager: The operation's _interfaces.TerminationManager.
+ transmission_manager: The operation's _interfaces.TransmissionManager.
+ ingestion_manager: The operation's _interfaces.IngestionManager.
+ expiration_manager: The operation's _interfaces.ExpirationManager.
+
+ Returns:
+ A _interfaces.ReceptionManager appropriate for front-side use.
+ """
+ return _ReceptionManager(
+ lock, _FrontReceiver(
+ termination_manager, transmission_manager, ingestion_manager,
+ expiration_manager))
+
+
+def back_reception_manager(
+ lock, termination_manager, transmission_manager, ingestion_manager,
+ expiration_manager):
+ """Creates a _interfaces.ReceptionManager for back-side use.
+
+ Args:
+ lock: The operation-servicing-wide lock object.
+ termination_manager: The operation's _interfaces.TerminationManager.
+ transmission_manager: The operation's _interfaces.TransmissionManager.
+ ingestion_manager: The operation's _interfaces.IngestionManager.
+ expiration_manager: The operation's _interfaces.ExpirationManager.
+
+ Returns:
+ A _interfaces.ReceptionManager appropriate for back-side use.
+ """
+ return _ReceptionManager(
+ lock, _BackReceiver(
+ termination_manager, transmission_manager, ingestion_manager,
+ expiration_manager))
diff --git a/src/python/grpcio/grpc/framework/base/_termination.py b/src/python/grpcio/grpc/framework/base/_termination.py
new file mode 100644
index 0000000000..ddcbc60293
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/base/_termination.py
@@ -0,0 +1,204 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""State and behavior for operation termination."""
+
+import enum
+
+from grpc.framework.base import _constants
+from grpc.framework.base import _interfaces
+from grpc.framework.base import interfaces
+from grpc.framework.foundation import callable_util
+
+_CALLBACK_EXCEPTION_LOG_MESSAGE = 'Exception calling termination callback!'
+
+
+@enum.unique
+class _Requirement(enum.Enum):
+ """Symbols indicating events required for termination."""
+
+ EMISSION = 'emission'
+ TRANSMISSION = 'transmission'
+ INGESTION = 'ingestion'
+
+_FRONT_NOT_LISTENING_REQUIREMENTS = (_Requirement.TRANSMISSION,)
+_BACK_NOT_LISTENING_REQUIREMENTS = (
+ _Requirement.EMISSION, _Requirement.INGESTION,)
+_LISTENING_REQUIREMENTS = (
+ _Requirement.TRANSMISSION, _Requirement.INGESTION,)
+
+
+class _TerminationManager(_interfaces.TerminationManager):
+ """An implementation of _interfaces.TerminationManager."""
+
+ def __init__(
+ self, work_pool, utility_pool, action, requirements, local_failure):
+ """Constructor.
+
+ Args:
+ work_pool: A thread pool in which customer work will be done.
+ utility_pool: A thread pool in which work utility work will be done.
+ action: An action to call on operation termination.
+ requirements: A combination of _Requirement values identifying what
+ must finish for the operation to be considered completed.
+ local_failure: An interfaces.Outcome specifying what constitutes local
+ failure of customer work.
+ """
+ self._work_pool = work_pool
+ self._utility_pool = utility_pool
+ self._action = action
+ self._local_failure = local_failure
+ self._has_locally_failed = False
+ self._expiration_manager = None
+
+ self._outstanding_requirements = set(requirements)
+ self._outcome = None
+ self._callbacks = []
+
+ def set_expiration_manager(self, expiration_manager):
+ self._expiration_manager = expiration_manager
+
+ def _terminate(self, outcome):
+ """Terminates the operation.
+
+ Args:
+ outcome: An interfaces.Outcome describing the outcome of the operation.
+ """
+ self._expiration_manager.abort()
+ self._outstanding_requirements = None
+ callbacks = list(self._callbacks)
+ self._callbacks = None
+ self._outcome = outcome
+
+ act = callable_util.with_exceptions_logged(
+ self._action, _constants.INTERNAL_ERROR_LOG_MESSAGE)
+
+ if self._has_locally_failed:
+ self._utility_pool.submit(act, outcome)
+ else:
+ def call_callbacks_and_act(callbacks, outcome):
+ for callback in callbacks:
+ callback_outcome = callable_util.call_logging_exceptions(
+ callback, _CALLBACK_EXCEPTION_LOG_MESSAGE, outcome)
+ if callback_outcome.exception is not None:
+ outcome = self._local_failure
+ break
+ self._utility_pool.submit(act, outcome)
+
+ self._work_pool.submit(callable_util.with_exceptions_logged(
+ call_callbacks_and_act,
+ _constants.INTERNAL_ERROR_LOG_MESSAGE),
+ callbacks, outcome)
+
+ def is_active(self):
+ """See _interfaces.TerminationManager.is_active for specification."""
+ return self._outstanding_requirements is not None
+
+ def add_callback(self, callback):
+ """See _interfaces.TerminationManager.add_callback for specification."""
+ if not self._has_locally_failed:
+ if self._outstanding_requirements is None:
+ self._work_pool.submit(
+ callable_util.with_exceptions_logged(
+ callback, _CALLBACK_EXCEPTION_LOG_MESSAGE), self._outcome)
+ else:
+ self._callbacks.append(callback)
+
+ def emission_complete(self):
+ """See superclass method for specification."""
+ if self._outstanding_requirements is not None:
+ self._outstanding_requirements.discard(_Requirement.EMISSION)
+ if not self._outstanding_requirements:
+ self._terminate(interfaces.Outcome.COMPLETED)
+
+ def transmission_complete(self):
+ """See superclass method for specification."""
+ if self._outstanding_requirements is not None:
+ self._outstanding_requirements.discard(_Requirement.TRANSMISSION)
+ if not self._outstanding_requirements:
+ self._terminate(interfaces.Outcome.COMPLETED)
+
+ def ingestion_complete(self):
+ """See superclass method for specification."""
+ if self._outstanding_requirements is not None:
+ self._outstanding_requirements.discard(_Requirement.INGESTION)
+ if not self._outstanding_requirements:
+ self._terminate(interfaces.Outcome.COMPLETED)
+
+ def abort(self, outcome):
+ """See _interfaces.TerminationManager.abort for specification."""
+ if outcome is self._local_failure:
+ self._has_failed_locally = True
+ if self._outstanding_requirements is not None:
+ self._terminate(outcome)
+
+
+def front_termination_manager(
+ work_pool, utility_pool, action, subscription_kind):
+ """Creates a TerminationManager appropriate for front-side use.
+
+ Args:
+ work_pool: A thread pool in which customer work will be done.
+ utility_pool: A thread pool in which work utility work will be done.
+ action: An action to call on operation termination.
+ subscription_kind: An interfaces.ServicedSubscription.Kind value.
+
+ Returns:
+ A TerminationManager appropriate for front-side use.
+ """
+ if subscription_kind is interfaces.ServicedSubscription.Kind.NONE:
+ requirements = _FRONT_NOT_LISTENING_REQUIREMENTS
+ else:
+ requirements = _LISTENING_REQUIREMENTS
+
+ return _TerminationManager(
+ work_pool, utility_pool, action, requirements,
+ interfaces.Outcome.SERVICED_FAILURE)
+
+
+def back_termination_manager(work_pool, utility_pool, action, subscription_kind):
+ """Creates a TerminationManager appropriate for back-side use.
+
+ Args:
+ work_pool: A thread pool in which customer work will be done.
+ utility_pool: A thread pool in which work utility work will be done.
+ action: An action to call on operation termination.
+ subscription_kind: An interfaces.ServicedSubscription.Kind value.
+
+ Returns:
+ A TerminationManager appropriate for back-side use.
+ """
+ if subscription_kind is interfaces.ServicedSubscription.Kind.NONE:
+ requirements = _BACK_NOT_LISTENING_REQUIREMENTS
+ else:
+ requirements = _LISTENING_REQUIREMENTS
+
+ return _TerminationManager(
+ work_pool, utility_pool, action, requirements,
+ interfaces.Outcome.SERVICER_FAILURE)
diff --git a/src/python/grpcio/grpc/framework/base/_transmission.py b/src/python/grpcio/grpc/framework/base/_transmission.py
new file mode 100644
index 0000000000..6845129234
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/base/_transmission.py
@@ -0,0 +1,429 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""State and behavior for ticket transmission during an operation."""
+
+import abc
+
+from grpc.framework.base import _constants
+from grpc.framework.base import _interfaces
+from grpc.framework.base import interfaces
+from grpc.framework.foundation import callable_util
+
+_TRANSMISSION_EXCEPTION_LOG_MESSAGE = 'Exception during transmission!'
+
+_FRONT_TO_BACK_NO_TRANSMISSION_OUTCOMES = (
+ interfaces.Outcome.SERVICER_FAILURE,
+ )
+_BACK_TO_FRONT_NO_TRANSMISSION_OUTCOMES = (
+ interfaces.Outcome.CANCELLED,
+ interfaces.Outcome.SERVICED_FAILURE,
+ )
+
+_ABORTION_OUTCOME_TO_FRONT_TO_BACK_TICKET_KIND = {
+ interfaces.Outcome.CANCELLED:
+ interfaces.FrontToBackTicket.Kind.CANCELLATION,
+ interfaces.Outcome.EXPIRED:
+ interfaces.FrontToBackTicket.Kind.EXPIRATION,
+ interfaces.Outcome.RECEPTION_FAILURE:
+ interfaces.FrontToBackTicket.Kind.RECEPTION_FAILURE,
+ interfaces.Outcome.TRANSMISSION_FAILURE:
+ interfaces.FrontToBackTicket.Kind.TRANSMISSION_FAILURE,
+ interfaces.Outcome.SERVICED_FAILURE:
+ interfaces.FrontToBackTicket.Kind.SERVICED_FAILURE,
+ interfaces.Outcome.SERVICER_FAILURE:
+ interfaces.FrontToBackTicket.Kind.SERVICER_FAILURE,
+}
+
+_ABORTION_OUTCOME_TO_BACK_TO_FRONT_TICKET_KIND = {
+ interfaces.Outcome.CANCELLED:
+ interfaces.BackToFrontTicket.Kind.CANCELLATION,
+ interfaces.Outcome.EXPIRED:
+ interfaces.BackToFrontTicket.Kind.EXPIRATION,
+ interfaces.Outcome.RECEPTION_FAILURE:
+ interfaces.BackToFrontTicket.Kind.RECEPTION_FAILURE,
+ interfaces.Outcome.TRANSMISSION_FAILURE:
+ interfaces.BackToFrontTicket.Kind.TRANSMISSION_FAILURE,
+ interfaces.Outcome.SERVICED_FAILURE:
+ interfaces.BackToFrontTicket.Kind.SERVICED_FAILURE,
+ interfaces.Outcome.SERVICER_FAILURE:
+ interfaces.BackToFrontTicket.Kind.SERVICER_FAILURE,
+}
+
+
+class _Ticketizer(object):
+ """Common specification of different ticket-creating behavior."""
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def ticketize(self, operation_id, sequence_number, payload, complete):
+ """Creates a ticket indicating ordinary operation progress.
+
+ Args:
+ operation_id: The operation ID for the current operation.
+ sequence_number: A sequence number for the ticket.
+ payload: A customer payload object. May be None if sequence_number is
+ zero or complete is true.
+ complete: A boolean indicating whether or not the ticket should describe
+ itself as (but for a later indication of operation abortion) the last
+ ticket to be sent.
+
+ Returns:
+ An object of an appropriate type suitable for transmission to the other
+ side of the operation.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def ticketize_abortion(self, operation_id, sequence_number, outcome):
+ """Creates a ticket indicating that the operation is aborted.
+
+ Args:
+ operation_id: The operation ID for the current operation.
+ sequence_number: A sequence number for the ticket.
+ outcome: An interfaces.Outcome value describing the operation abortion.
+
+ Returns:
+ An object of an appropriate type suitable for transmission to the other
+ side of the operation, or None if transmission is not appropriate for
+ the given outcome.
+ """
+ raise NotImplementedError()
+
+
+class _FrontTicketizer(_Ticketizer):
+ """Front-side ticket-creating behavior."""
+
+ def __init__(self, name, subscription_kind, trace_id, timeout):
+ """Constructor.
+
+ Args:
+ name: The name of the operation.
+ subscription_kind: An interfaces.ServicedSubscription.Kind value
+ describing the interest the front has in tickets sent from the back.
+ trace_id: A uuid.UUID identifying a set of related operations to which
+ this operation belongs.
+ timeout: A length of time in seconds to allow for the entire operation.
+ """
+ self._name = name
+ self._subscription_kind = subscription_kind
+ self._trace_id = trace_id
+ self._timeout = timeout
+
+ def ticketize(self, operation_id, sequence_number, payload, complete):
+ """See _Ticketizer.ticketize for specification."""
+ if sequence_number:
+ if complete:
+ kind = interfaces.FrontToBackTicket.Kind.COMPLETION
+ else:
+ kind = interfaces.FrontToBackTicket.Kind.CONTINUATION
+ return interfaces.FrontToBackTicket(
+ operation_id, sequence_number, kind, self._name,
+ self._subscription_kind, self._trace_id, payload, self._timeout)
+ else:
+ if complete:
+ kind = interfaces.FrontToBackTicket.Kind.ENTIRE
+ else:
+ kind = interfaces.FrontToBackTicket.Kind.COMMENCEMENT
+ return interfaces.FrontToBackTicket(
+ operation_id, 0, kind, self._name, self._subscription_kind,
+ self._trace_id, payload, self._timeout)
+
+ def ticketize_abortion(self, operation_id, sequence_number, outcome):
+ """See _Ticketizer.ticketize_abortion for specification."""
+ if outcome in _FRONT_TO_BACK_NO_TRANSMISSION_OUTCOMES:
+ return None
+ else:
+ kind = _ABORTION_OUTCOME_TO_FRONT_TO_BACK_TICKET_KIND[outcome]
+ return interfaces.FrontToBackTicket(
+ operation_id, sequence_number, kind, None, None, None, None, None)
+
+
+class _BackTicketizer(_Ticketizer):
+ """Back-side ticket-creating behavior."""
+
+ def ticketize(self, operation_id, sequence_number, payload, complete):
+ """See _Ticketizer.ticketize for specification."""
+ if complete:
+ kind = interfaces.BackToFrontTicket.Kind.COMPLETION
+ else:
+ kind = interfaces.BackToFrontTicket.Kind.CONTINUATION
+ return interfaces.BackToFrontTicket(
+ operation_id, sequence_number, kind, payload)
+
+ def ticketize_abortion(self, operation_id, sequence_number, outcome):
+ """See _Ticketizer.ticketize_abortion for specification."""
+ if outcome in _BACK_TO_FRONT_NO_TRANSMISSION_OUTCOMES:
+ return None
+ else:
+ kind = _ABORTION_OUTCOME_TO_BACK_TO_FRONT_TICKET_KIND[outcome]
+ return interfaces.BackToFrontTicket(
+ operation_id, sequence_number, kind, None)
+
+
+class TransmissionManager(_interfaces.TransmissionManager):
+ """A _interfaces.TransmissionManager on which other managers may be set."""
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def set_ingestion_and_expiration_managers(
+ self, ingestion_manager, expiration_manager):
+ """Sets two of the other managers with which this manager may interact.
+
+ Args:
+ ingestion_manager: The _interfaces.IngestionManager associated with the
+ current operation.
+ expiration_manager: The _interfaces.ExpirationManager associated with the
+ current operation.
+ """
+ raise NotImplementedError()
+
+
+class _EmptyTransmissionManager(TransmissionManager):
+ """A completely no-operative _interfaces.TransmissionManager."""
+
+ def set_ingestion_and_expiration_managers(
+ self, ingestion_manager, expiration_manager):
+ """See overriden method for specification."""
+
+ def inmit(self, emission, complete):
+ """See _interfaces.TransmissionManager.inmit for specification."""
+
+ def abort(self, outcome):
+ """See _interfaces.TransmissionManager.abort for specification."""
+
+
+class _TransmittingTransmissionManager(TransmissionManager):
+ """A TransmissionManager implementation that sends tickets."""
+
+ def __init__(
+ self, lock, pool, callback, operation_id, ticketizer,
+ termination_manager):
+ """Constructor.
+
+ Args:
+ lock: The operation-servicing-wide lock object.
+ pool: A thread pool in which the work of transmitting tickets will be
+ performed.
+ callback: A callable that accepts tickets and sends them to the other side
+ of the operation.
+ operation_id: The operation's ID.
+ ticketizer: A _Ticketizer for ticket creation.
+ termination_manager: The _interfaces.TerminationManager associated with
+ this operation.
+ """
+ self._lock = lock
+ self._pool = pool
+ self._callback = callback
+ self._operation_id = operation_id
+ self._ticketizer = ticketizer
+ self._termination_manager = termination_manager
+ self._ingestion_manager = None
+ self._expiration_manager = None
+
+ self._emissions = []
+ self._emission_complete = False
+ self._outcome = None
+ self._lowest_unused_sequence_number = 0
+ self._transmitting = False
+
+ def set_ingestion_and_expiration_managers(
+ self, ingestion_manager, expiration_manager):
+ """See overridden method for specification."""
+ self._ingestion_manager = ingestion_manager
+ self._expiration_manager = expiration_manager
+
+ def _lead_ticket(self, emission, complete):
+ """Creates a ticket suitable for leading off the transmission loop.
+
+ Args:
+ emission: A customer payload object to be sent to the other side of the
+ operation.
+ complete: Whether or not the sequence of customer payloads ends with
+ the passed object.
+
+ Returns:
+ A ticket with which to lead off the transmission loop.
+ """
+ sequence_number = self._lowest_unused_sequence_number
+ self._lowest_unused_sequence_number += 1
+ return self._ticketizer.ticketize(
+ self._operation_id, sequence_number, emission, complete)
+
+ def _abortive_response_ticket(self, outcome):
+ """Creates a ticket indicating operation abortion.
+
+ Args:
+ outcome: An interfaces.Outcome value describing operation abortion.
+
+ Returns:
+ A ticket indicating operation abortion.
+ """
+ ticket = self._ticketizer.ticketize_abortion(
+ self._operation_id, self._lowest_unused_sequence_number, outcome)
+ if ticket is None:
+ return None
+ else:
+ self._lowest_unused_sequence_number += 1
+ return ticket
+
+ def _next_ticket(self):
+ """Creates the next ticket to be sent to the other side of the operation.
+
+ Returns:
+ A (completed, ticket) tuple comprised of a boolean indicating whether or
+ not the sequence of tickets has completed normally and a ticket to send
+ to the other side if the sequence of tickets hasn't completed. The tuple
+ will never have both a True first element and a non-None second element.
+ """
+ if self._emissions is None:
+ return False, None
+ elif self._outcome is None:
+ if self._emissions:
+ payload = self._emissions.pop(0)
+ complete = self._emission_complete and not self._emissions
+ sequence_number = self._lowest_unused_sequence_number
+ self._lowest_unused_sequence_number += 1
+ return complete, self._ticketizer.ticketize(
+ self._operation_id, sequence_number, payload, complete)
+ else:
+ return self._emission_complete, None
+ else:
+ ticket = self._abortive_response_ticket(self._outcome)
+ self._emissions = None
+ return False, None if ticket is None else ticket
+
+ def _transmit(self, ticket):
+ """Commences the transmission loop sending tickets.
+
+ Args:
+ ticket: A ticket to be sent to the other side of the operation.
+ """
+ def transmit(ticket):
+ while True:
+ transmission_outcome = callable_util.call_logging_exceptions(
+ self._callback, _TRANSMISSION_EXCEPTION_LOG_MESSAGE, ticket)
+ if transmission_outcome.exception is None:
+ with self._lock:
+ complete, ticket = self._next_ticket()
+ if ticket is None:
+ if complete:
+ self._termination_manager.transmission_complete()
+ self._transmitting = False
+ return
+ else:
+ with self._lock:
+ self._emissions = None
+ self._termination_manager.abort(
+ interfaces.Outcome.TRANSMISSION_FAILURE)
+ self._ingestion_manager.abort()
+ self._expiration_manager.abort()
+ self._transmitting = False
+ return
+
+ self._pool.submit(callable_util.with_exceptions_logged(
+ transmit, _constants.INTERNAL_ERROR_LOG_MESSAGE), ticket)
+ self._transmitting = True
+
+ def inmit(self, emission, complete):
+ """See _interfaces.TransmissionManager.inmit for specification."""
+ if self._emissions is not None and self._outcome is None:
+ self._emission_complete = complete
+ if self._transmitting:
+ self._emissions.append(emission)
+ else:
+ self._transmit(self._lead_ticket(emission, complete))
+
+ def abort(self, outcome):
+ """See _interfaces.TransmissionManager.abort for specification."""
+ if self._emissions is not None and self._outcome is None:
+ self._outcome = outcome
+ if not self._transmitting:
+ ticket = self._abortive_response_ticket(outcome)
+ self._emissions = None
+ if ticket is not None:
+ self._transmit(ticket)
+
+
+def front_transmission_manager(
+ lock, pool, callback, operation_id, name, subscription_kind, trace_id,
+ timeout, termination_manager):
+ """Creates a TransmissionManager appropriate for front-side use.
+
+ Args:
+ lock: The operation-servicing-wide lock object.
+ pool: A thread pool in which the work of transmitting tickets will be
+ performed.
+ callback: A callable that accepts tickets and sends them to the other side
+ of the operation.
+ operation_id: The operation's ID.
+ name: The name of the operation.
+ subscription_kind: An interfaces.ServicedSubscription.Kind value
+ describing the interest the front has in tickets sent from the back.
+ trace_id: A uuid.UUID identifying a set of related operations to which
+ this operation belongs.
+ timeout: A length of time in seconds to allow for the entire operation.
+ termination_manager: The _interfaces.TerminationManager associated with
+ this operation.
+
+ Returns:
+ A TransmissionManager appropriate for front-side use.
+ """
+ return _TransmittingTransmissionManager(
+ lock, pool, callback, operation_id, _FrontTicketizer(
+ name, subscription_kind, trace_id, timeout),
+ termination_manager)
+
+
+def back_transmission_manager(
+ lock, pool, callback, operation_id, termination_manager,
+ subscription_kind):
+ """Creates a TransmissionManager appropriate for back-side use.
+
+ Args:
+ lock: The operation-servicing-wide lock object.
+ pool: A thread pool in which the work of transmitting tickets will be
+ performed.
+ callback: A callable that accepts tickets and sends them to the other side
+ of the operation.
+ operation_id: The operation's ID.
+ termination_manager: The _interfaces.TerminationManager associated with
+ this operation.
+ subscription_kind: An interfaces.ServicedSubscription.Kind value
+ describing the interest the front has in tickets sent from the back.
+
+ Returns:
+ A TransmissionManager appropriate for back-side use.
+ """
+ if subscription_kind is interfaces.ServicedSubscription.Kind.NONE:
+ return _EmptyTransmissionManager()
+ else:
+ return _TransmittingTransmissionManager(
+ lock, pool, callback, operation_id, _BackTicketizer(),
+ termination_manager)
diff --git a/src/python/grpcio/grpc/framework/base/exceptions.py b/src/python/grpcio/grpc/framework/base/exceptions.py
new file mode 100644
index 0000000000..b8f4752184
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/base/exceptions.py
@@ -0,0 +1,34 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Exceptions defined and used by the base layer of RPC Framework."""
+
+
+class NoSuchMethodError(Exception):
+ """Indicates that an operation with an unrecognized name has been called."""
diff --git a/src/python/grpcio/grpc/framework/base/implementations.py b/src/python/grpcio/grpc/framework/base/implementations.py
new file mode 100644
index 0000000000..5656f9f981
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/base/implementations.py
@@ -0,0 +1,77 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Entry points into the ticket-exchange-based base layer implementation."""
+
+# interfaces is referenced from specification in this module.
+from grpc.framework.base import _ends
+from grpc.framework.base import interfaces # pylint: disable=unused-import
+
+
+def front_link(work_pool, transmission_pool, utility_pool):
+ """Factory function for creating interfaces.FrontLinks.
+
+ Args:
+ work_pool: A thread pool to be used for doing work within the created
+ FrontLink object.
+ transmission_pool: A thread pool to be used within the created FrontLink
+ object for transmitting values to a joined RearLink object.
+ utility_pool: A thread pool to be used within the created FrontLink object
+ for utility tasks.
+
+ Returns:
+ An interfaces.FrontLink.
+ """
+ return _ends.FrontLink(work_pool, transmission_pool, utility_pool)
+
+
+def back_link(
+ servicer, work_pool, transmission_pool, utility_pool, default_timeout,
+ maximum_timeout):
+ """Factory function for creating interfaces.BackLinks.
+
+ Args:
+ servicer: An interfaces.Servicer for servicing operations.
+ work_pool: A thread pool to be used for doing work within the created
+ BackLink object.
+ transmission_pool: A thread pool to be used within the created BackLink
+ object for transmitting values to a joined ForeLink object.
+ utility_pool: A thread pool to be used within the created BackLink object
+ for utility tasks.
+ default_timeout: A length of time in seconds to be used as the default
+ time alloted for a single operation.
+ maximum_timeout: A length of time in seconds to be used as the maximum
+ time alloted for a single operation.
+
+ Returns:
+ An interfaces.BackLink.
+ """
+ return _ends.BackLink(
+ servicer, work_pool, transmission_pool, utility_pool, default_timeout,
+ maximum_timeout)
diff --git a/src/python/grpcio/grpc/framework/base/in_memory.py b/src/python/grpcio/grpc/framework/base/in_memory.py
new file mode 100644
index 0000000000..c92d0bc663
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/base/in_memory.py
@@ -0,0 +1,108 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""In-memory implementations of base layer interfaces."""
+
+import threading
+
+from grpc.framework.base import _constants
+from grpc.framework.base import interfaces
+from grpc.framework.foundation import callable_util
+
+
+class _Serializer(object):
+ """A utility for serializing values that may arrive concurrently."""
+
+ def __init__(self, pool):
+ self._lock = threading.Lock()
+ self._pool = pool
+ self._sink = None
+ self._spinning = False
+ self._values = []
+
+ def _spin(self, sink, value):
+ while True:
+ sink(value)
+ with self._lock:
+ if self._sink is None or not self._values:
+ self._spinning = False
+ return
+ else:
+ sink, value = self._sink, self._values.pop(0)
+
+ def set_sink(self, sink):
+ with self._lock:
+ self._sink = sink
+ if sink is not None and self._values and not self._spinning:
+ self._spinning = True
+ self._pool.submit(
+ callable_util.with_exceptions_logged(
+ self._spin, _constants.INTERNAL_ERROR_LOG_MESSAGE),
+ sink, self._values.pop(0))
+
+ def add_value(self, value):
+ with self._lock:
+ if self._sink and not self._spinning:
+ self._spinning = True
+ self._pool.submit(
+ callable_util.with_exceptions_logged(
+ self._spin, _constants.INTERNAL_ERROR_LOG_MESSAGE),
+ self._sink, value)
+ else:
+ self._values.append(value)
+
+
+class Link(interfaces.ForeLink, interfaces.RearLink):
+ """A trivial implementation of interfaces.ForeLink and interfaces.RearLink."""
+
+ def __init__(self, pool):
+ """Constructor.
+
+ Args:
+ pool: A thread pool to be used for serializing ticket exchange in each
+ direction.
+ """
+ self._front_to_back = _Serializer(pool)
+ self._back_to_front = _Serializer(pool)
+
+ def join_fore_link(self, fore_link):
+ """See interfaces.RearLink.join_fore_link for specification."""
+ self._back_to_front.set_sink(fore_link.accept_back_to_front_ticket)
+
+ def join_rear_link(self, rear_link):
+ """See interfaces.ForeLink.join_rear_link for specification."""
+ self._front_to_back.set_sink(rear_link.accept_front_to_back_ticket)
+
+ def accept_front_to_back_ticket(self, ticket):
+ """See interfaces.ForeLink.accept_front_to_back_ticket for specification."""
+ self._front_to_back.add_value(ticket)
+
+ def accept_back_to_front_ticket(self, ticket):
+ """See interfaces.RearLink.accept_back_to_front_ticket for specification."""
+ self._back_to_front.add_value(ticket)
diff --git a/src/python/grpcio/grpc/framework/base/interfaces.py b/src/python/grpcio/grpc/framework/base/interfaces.py
new file mode 100644
index 0000000000..e22c10d975
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/base/interfaces.py
@@ -0,0 +1,363 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Interfaces defined and used by the base layer of RPC Framework."""
+
+import abc
+import collections
+import enum
+
+# stream is referenced from specification in this module.
+from grpc.framework.foundation import stream # pylint: disable=unused-import
+
+
+@enum.unique
+class Outcome(enum.Enum):
+ """Operation outcomes."""
+
+ COMPLETED = 'completed'
+ CANCELLED = 'cancelled'
+ EXPIRED = 'expired'
+ RECEPTION_FAILURE = 'reception failure'
+ TRANSMISSION_FAILURE = 'transmission failure'
+ SERVICER_FAILURE = 'servicer failure'
+ SERVICED_FAILURE = 'serviced failure'
+
+
+class OperationContext(object):
+ """Provides operation-related information and action.
+
+ Attributes:
+ trace_id: A uuid.UUID identifying a particular set of related operations.
+ """
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def is_active(self):
+ """Describes whether the operation is active or has terminated."""
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def add_termination_callback(self, callback):
+ """Adds a function to be called upon operation termination.
+
+ Args:
+ callback: A callable that will be passed an Outcome value.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def time_remaining(self):
+ """Describes the length of allowed time remaining for the operation.
+
+ Returns:
+ A nonnegative float indicating the length of allowed time in seconds
+ remaining for the operation to complete before it is considered to have
+ timed out.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def fail(self, exception):
+ """Indicates that the operation has failed.
+
+ Args:
+ exception: An exception germane to the operation failure. May be None.
+ """
+ raise NotImplementedError()
+
+
+class Servicer(object):
+ """Interface for service implementations."""
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def service(self, name, context, output_consumer):
+ """Services an operation.
+
+ Args:
+ name: The name of the operation.
+ context: A ServicerContext object affording contextual information and
+ actions.
+ output_consumer: A stream.Consumer that will accept output values of
+ the operation.
+
+ Returns:
+ A stream.Consumer that will accept input values for the operation.
+
+ Raises:
+ exceptions.NoSuchMethodError: If this Servicer affords no method with the
+ given name.
+ abandonment.Abandoned: If the operation has been aborted and there no
+ longer is any reason to service the operation.
+ """
+ raise NotImplementedError()
+
+
+class Operation(object):
+ """Representation of an in-progress operation.
+
+ Attributes:
+ consumer: A stream.Consumer into which payloads constituting the operation's
+ input may be passed.
+ context: An OperationContext affording information and action about the
+ operation.
+ """
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def cancel(self):
+ """Cancels this operation."""
+ raise NotImplementedError()
+
+
+class ServicedIngestor(object):
+ """Responsible for accepting the result of an operation."""
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def consumer(self, operation_context):
+ """Affords a consumer to which operation results will be passed.
+
+ Args:
+ operation_context: An OperationContext object for the current operation.
+
+ Returns:
+ A stream.Consumer to which the results of the current operation will be
+ passed.
+
+ Raises:
+ abandonment.Abandoned: If the operation has been aborted and there no
+ longer is any reason to service the operation.
+ """
+ raise NotImplementedError()
+
+
+class ServicedSubscription(object):
+ """A sum type representing a serviced's interest in an operation.
+
+ Attributes:
+ kind: A Kind value.
+ ingestor: A ServicedIngestor. Must be present if kind is Kind.FULL. Must
+ be None if kind is Kind.TERMINATION_ONLY or Kind.NONE.
+ """
+ __metaclass__ = abc.ABCMeta
+
+ @enum.unique
+ class Kind(enum.Enum):
+ """Kinds of subscription."""
+
+ FULL = 'full'
+ TERMINATION_ONLY = 'termination only'
+ NONE = 'none'
+
+
+class End(object):
+ """Common type for entry-point objects on both sides of an operation."""
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def operation_stats(self):
+ """Reports the number of terminated operations broken down by outcome.
+
+ Returns:
+ A dictionary from Outcome value to an integer identifying the number
+ of operations that terminated with that outcome.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def add_idle_action(self, action):
+ """Adds an action to be called when this End has no ongoing operations.
+
+ Args:
+ action: A callable that accepts no arguments.
+ """
+ raise NotImplementedError()
+
+
+class Front(End):
+ """Clientish objects that afford the invocation of operations."""
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def operate(
+ self, name, payload, complete, timeout, subscription, trace_id):
+ """Commences an operation.
+
+ Args:
+ name: The name of the method invoked for the operation.
+ payload: An initial payload for the operation. May be None.
+ complete: A boolean indicating whether or not additional payloads to be
+ sent to the servicer may be supplied after this call.
+ timeout: A length of time in seconds to allow for the operation.
+ subscription: A ServicedSubscription for the operation.
+ trace_id: A uuid.UUID identifying a set of related operations to which
+ this operation belongs.
+
+ Returns:
+ An Operation object affording information and action about the operation
+ in progress.
+ """
+ raise NotImplementedError()
+
+
+class Back(End):
+ """Serverish objects that perform the work of operations."""
+ __metaclass__ = abc.ABCMeta
+
+
+class FrontToBackTicket(
+ collections.namedtuple(
+ 'FrontToBackTicket',
+ ['operation_id', 'sequence_number', 'kind', 'name', 'subscription',
+ 'trace_id', 'payload', 'timeout'])):
+ """A sum type for all values sent from a front to a back.
+
+ Attributes:
+ operation_id: A unique-with-respect-to-equality hashable object identifying
+ a particular operation.
+ sequence_number: A zero-indexed integer sequence number identifying the
+ ticket's place among all the tickets sent from front to back for this
+ particular operation. Must be zero if kind is Kind.COMMENCEMENT or
+ Kind.ENTIRE. Must be positive for any other kind.
+ kind: A Kind value describing the overall kind of ticket.
+ name: The name of an operation. Must be present if kind is Kind.COMMENCEMENT
+ or Kind.ENTIRE. Must be None for any other kind.
+ subscription: An ServicedSubscription.Kind value describing the interest
+ the front has in tickets sent from the back. Must be present if
+ kind is Kind.COMMENCEMENT or Kind.ENTIRE. Must be None for any other kind.
+ trace_id: A uuid.UUID identifying a set of related operations to which this
+ operation belongs. May be None.
+ payload: A customer payload object. Must be present if kind is
+ Kind.CONTINUATION. Must be None if kind is Kind.CANCELLATION. May be None
+ for any other kind.
+ timeout: An optional length of time (measured from the beginning of the
+ operation) to allow for the entire operation. If None, a default value on
+ the back will be used. If present and excessively large, the back may
+ limit the operation to a smaller duration of its choice. May be present
+ for any ticket kind; setting a value on a later ticket allows fronts
+ to request time extensions (or even time reductions!) on in-progress
+ operations.
+ """
+
+ @enum.unique
+ class Kind(enum.Enum):
+ """Identifies the overall kind of a FrontToBackTicket."""
+
+ COMMENCEMENT = 'commencement'
+ CONTINUATION = 'continuation'
+ COMPLETION = 'completion'
+ ENTIRE = 'entire'
+ CANCELLATION = 'cancellation'
+ EXPIRATION = 'expiration'
+ SERVICER_FAILURE = 'servicer failure'
+ SERVICED_FAILURE = 'serviced failure'
+ RECEPTION_FAILURE = 'reception failure'
+ TRANSMISSION_FAILURE = 'transmission failure'
+
+
+class BackToFrontTicket(
+ collections.namedtuple(
+ 'BackToFrontTicket',
+ ['operation_id', 'sequence_number', 'kind', 'payload'])):
+ """A sum type for all values sent from a back to a front.
+
+ Attributes:
+ operation_id: A unique-with-respect-to-equality hashable object identifying
+ a particular operation.
+ sequence_number: A zero-indexed integer sequence number identifying the
+ ticket's place among all the tickets sent from back to front for this
+ particular operation.
+ kind: A Kind value describing the overall kind of ticket.
+ payload: A customer payload object. Must be present if kind is
+ Kind.CONTINUATION. May be None if kind is Kind.COMPLETION. Must be None
+ otherwise.
+ """
+
+ @enum.unique
+ class Kind(enum.Enum):
+ """Identifies the overall kind of a BackToFrontTicket."""
+
+ CONTINUATION = 'continuation'
+ COMPLETION = 'completion'
+ CANCELLATION = 'cancellation'
+ EXPIRATION = 'expiration'
+ SERVICER_FAILURE = 'servicer failure'
+ SERVICED_FAILURE = 'serviced failure'
+ RECEPTION_FAILURE = 'reception failure'
+ TRANSMISSION_FAILURE = 'transmission failure'
+
+
+class ForeLink(object):
+ """Accepts back-to-front tickets and emits front-to-back tickets."""
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def accept_back_to_front_ticket(self, ticket):
+ """Accept a BackToFrontTicket.
+
+ Args:
+ ticket: Any BackToFrontTicket.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def join_rear_link(self, rear_link):
+ """Mates this object with a peer with which it will exchange tickets."""
+ raise NotImplementedError()
+
+
+class RearLink(object):
+ """Accepts front-to-back tickets and emits back-to-front tickets."""
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def accept_front_to_back_ticket(self, ticket):
+ """Accepts a FrontToBackTicket.
+
+ Args:
+ ticket: Any FrontToBackTicket.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def join_fore_link(self, fore_link):
+ """Mates this object with a peer with which it will exchange tickets."""
+ raise NotImplementedError()
+
+
+class FrontLink(Front, ForeLink):
+ """Clientish objects that operate by sending and receiving tickets."""
+ __metaclass__ = abc.ABCMeta
+
+
+class BackLink(Back, RearLink):
+ """Serverish objects that operate by sending and receiving tickets."""
+ __metaclass__ = abc.ABCMeta
diff --git a/src/python/grpcio/grpc/framework/base/null.py b/src/python/grpcio/grpc/framework/base/null.py
new file mode 100644
index 0000000000..1e30d4557b
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/base/null.py
@@ -0,0 +1,56 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Null links that ignore tickets passed to them."""
+
+from grpc.framework.base import interfaces
+
+
+class _NullForeLink(interfaces.ForeLink):
+ """A do-nothing ForeLink."""
+
+ def accept_back_to_front_ticket(self, ticket):
+ pass
+
+ def join_rear_link(self, rear_link):
+ raise NotImplementedError()
+
+
+class _NullRearLink(interfaces.RearLink):
+ """A do-nothing RearLink."""
+
+ def accept_front_to_back_ticket(self, ticket):
+ pass
+
+ def join_fore_link(self, fore_link):
+ raise NotImplementedError()
+
+
+NULL_FORE_LINK = _NullForeLink()
+NULL_REAR_LINK = _NullRearLink()
diff --git a/src/python/grpcio/grpc/framework/base/util.py b/src/python/grpcio/grpc/framework/base/util.py
new file mode 100644
index 0000000000..c832c826cf
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/base/util.py
@@ -0,0 +1,94 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Utilities helpful for working with the base layer of RPC Framework."""
+
+import collections
+import threading
+
+from grpc.framework.base import interfaces
+
+
+class _ServicedSubscription(
+ collections.namedtuple('_ServicedSubscription', ['kind', 'ingestor']),
+ interfaces.ServicedSubscription):
+ """See interfaces.ServicedSubscription for specification."""
+
+_NONE_SUBSCRIPTION = _ServicedSubscription(
+ interfaces.ServicedSubscription.Kind.NONE, None)
+_TERMINATION_ONLY_SUBSCRIPTION = _ServicedSubscription(
+ interfaces.ServicedSubscription.Kind.TERMINATION_ONLY, None)
+
+
+def none_serviced_subscription():
+ """Creates a "none" interfaces.ServicedSubscription object.
+
+ Returns:
+ An interfaces.ServicedSubscription indicating no subscription to an
+ operation's results (such as would be the case for a fire-and-forget
+ operation invocation).
+ """
+ return _NONE_SUBSCRIPTION
+
+
+def termination_only_serviced_subscription():
+ """Creates a "termination only" interfaces.ServicedSubscription object.
+
+ Returns:
+ An interfaces.ServicedSubscription indicating that the front-side customer
+ is interested only in the overall termination outcome of the operation
+ (such as completion or expiration) and would ignore the actual results of
+ the operation.
+ """
+ return _TERMINATION_ONLY_SUBSCRIPTION
+
+
+def full_serviced_subscription(ingestor):
+ """Creates a "full" interfaces.ServicedSubscription object.
+
+ Args:
+ ingestor: An interfaces.ServicedIngestor.
+
+ Returns:
+ An interfaces.ServicedSubscription object indicating a full
+ subscription.
+ """
+ return _ServicedSubscription(
+ interfaces.ServicedSubscription.Kind.FULL, ingestor)
+
+
+def wait_for_idle(end):
+ """Waits for an interfaces.End to complete all operations.
+
+ Args:
+ end: Any interfaces.End.
+ """
+ event = threading.Event()
+ end.add_idle_action(event.set)
+ event.wait()
diff --git a/src/python/grpcio/grpc/framework/common/__init__.py b/src/python/grpcio/grpc/framework/common/__init__.py
new file mode 100644
index 0000000000..7086519106
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/common/__init__.py
@@ -0,0 +1,30 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
diff --git a/src/python/grpcio/grpc/framework/common/cardinality.py b/src/python/grpcio/grpc/framework/common/cardinality.py
new file mode 100644
index 0000000000..610425e803
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/common/cardinality.py
@@ -0,0 +1,42 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Defines an enum for classifying RPC methods by streaming semantics."""
+
+import enum
+
+
+@enum.unique
+class Cardinality(enum.Enum):
+ """Describes the streaming semantics of an RPC method."""
+
+ UNARY_UNARY = 'request-unary/response-unary'
+ UNARY_STREAM = 'request-unary/response-streaming'
+ STREAM_UNARY = 'request-streaming/response-unary'
+ STREAM_STREAM = 'request-streaming/response-streaming'
diff --git a/src/python/grpcio/grpc/framework/common/style.py b/src/python/grpcio/grpc/framework/common/style.py
new file mode 100644
index 0000000000..6ae694bdcb
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/common/style.py
@@ -0,0 +1,40 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Defines an enum for classifying RPC methods by control flow semantics."""
+
+import enum
+
+
+@enum.unique
+class Service(enum.Enum):
+ """Describes the control flow style of RPC method implementation."""
+
+ INLINE = 'inline'
+ EVENT = 'event'
diff --git a/src/python/grpcio/grpc/framework/face/__init__.py b/src/python/grpcio/grpc/framework/face/__init__.py
new file mode 100644
index 0000000000..7086519106
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/face/__init__.py
@@ -0,0 +1,30 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
diff --git a/src/python/grpcio/grpc/framework/face/_calls.py b/src/python/grpcio/grpc/framework/face/_calls.py
new file mode 100644
index 0000000000..87edeb0f0e
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/face/_calls.py
@@ -0,0 +1,422 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Utility functions for invoking RPCs."""
+
+import sys
+import threading
+
+from grpc.framework.base import interfaces as base_interfaces
+from grpc.framework.base import util as base_util
+from grpc.framework.face import _control
+from grpc.framework.face import interfaces
+from grpc.framework.foundation import callable_util
+from grpc.framework.foundation import future
+
+_ITERATOR_EXCEPTION_LOG_MESSAGE = 'Exception iterating over requests!'
+_DONE_CALLBACK_LOG_MESSAGE = 'Exception calling Future "done" callback!'
+
+
+class _RendezvousServicedIngestor(base_interfaces.ServicedIngestor):
+
+ def __init__(self, rendezvous):
+ self._rendezvous = rendezvous
+
+ def consumer(self, operation_context):
+ return self._rendezvous
+
+
+class _EventServicedIngestor(base_interfaces.ServicedIngestor):
+
+ def __init__(self, result_consumer, abortion_callback):
+ self._result_consumer = result_consumer
+ self._abortion_callback = abortion_callback
+
+ def consumer(self, operation_context):
+ operation_context.add_termination_callback(
+ _control.as_operation_termination_callback(self._abortion_callback))
+ return self._result_consumer
+
+
+def _rendezvous_subscription(rendezvous):
+ return base_util.full_serviced_subscription(
+ _RendezvousServicedIngestor(rendezvous))
+
+
+def _unary_event_subscription(completion_callback, abortion_callback):
+ return base_util.full_serviced_subscription(
+ _EventServicedIngestor(
+ _control.UnaryConsumer(completion_callback), abortion_callback))
+
+
+def _stream_event_subscription(result_consumer, abortion_callback):
+ return base_util.full_serviced_subscription(
+ _EventServicedIngestor(result_consumer, abortion_callback))
+
+
+# NOTE(nathaniel): This class has some extremely special semantics around
+# cancellation that allow it to be used by both "blocking" APIs and "futures"
+# APIs.
+#
+# Since futures.Future defines its own exception for cancellation, we want these
+# objects, when returned by methods of a returning-Futures-from-other-methods
+# object, to raise the same exception for cancellation. But that's weird in a
+# blocking API - why should this object, also returned by methods of blocking
+# APIs, raise exceptions from the "future" module? Should we do something like
+# have this class be parameterized by the type of exception that it raises in
+# cancellation circumstances?
+#
+# We don't have to take such a dramatic step: since blocking APIs define no
+# cancellation semantics whatsoever, there is no supported way for
+# blocking-API-users of these objects to cancel RPCs, and thus no supported way
+# for them to see an exception the type of which would be weird to them.
+#
+# Bonus: in both blocking and futures APIs, this object still properly raises
+# exceptions.CancellationError for any *server-side cancellation* of an RPC.
+class _OperationCancellableIterator(interfaces.CancellableIterator):
+ """An interfaces.CancellableIterator for response-streaming operations."""
+
+ def __init__(self, rendezvous, operation):
+ self._lock = threading.Lock()
+ self._rendezvous = rendezvous
+ self._operation = operation
+ self._cancelled = False
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ with self._lock:
+ if self._cancelled:
+ raise future.CancelledError()
+ return next(self._rendezvous)
+
+ def cancel(self):
+ with self._lock:
+ self._cancelled = True
+ self._operation.cancel()
+ self._rendezvous.set_outcome(base_interfaces.Outcome.CANCELLED)
+
+
+class _OperationFuture(future.Future):
+ """A future.Future interface to an operation."""
+
+ def __init__(self, rendezvous, operation):
+ self._condition = threading.Condition()
+ self._rendezvous = rendezvous
+ self._operation = operation
+
+ self._cancelled = False
+ self._computed = False
+ self._payload = None
+ self._exception = None
+ self._traceback = None
+ self._callbacks = []
+
+ def cancel(self):
+ """See future.Future.cancel for specification."""
+ with self._condition:
+ if not self._cancelled and not self._computed:
+ self._operation.cancel()
+ self._cancelled = True
+ self._condition.notify_all()
+ return False
+
+ def cancelled(self):
+ """See future.Future.cancelled for specification."""
+ with self._condition:
+ return self._cancelled
+
+ def running(self):
+ """See future.Future.running for specification."""
+ with self._condition:
+ return not self._cancelled and not self._computed
+
+ def done(self):
+ """See future.Future.done for specification."""
+ with self._condition:
+ return self._cancelled or self._computed
+
+ def result(self, timeout=None):
+ """See future.Future.result for specification."""
+ with self._condition:
+ if self._cancelled:
+ raise future.CancelledError()
+ if self._computed:
+ if self._payload is None:
+ raise self._exception # pylint: disable=raising-bad-type
+ else:
+ return self._payload
+
+ condition = threading.Condition()
+ def notify_condition(unused_future):
+ with condition:
+ condition.notify()
+ self._callbacks.append(notify_condition)
+
+ with condition:
+ condition.wait(timeout=timeout)
+
+ with self._condition:
+ if self._cancelled:
+ raise future.CancelledError()
+ elif self._computed:
+ if self._payload is None:
+ raise self._exception # pylint: disable=raising-bad-type
+ else:
+ return self._payload
+ else:
+ raise future.TimeoutError()
+
+ def exception(self, timeout=None):
+ """See future.Future.exception for specification."""
+ with self._condition:
+ if self._cancelled:
+ raise future.CancelledError()
+ if self._computed:
+ return self._exception
+
+ condition = threading.Condition()
+ def notify_condition(unused_future):
+ with condition:
+ condition.notify()
+ self._callbacks.append(notify_condition)
+
+ with condition:
+ condition.wait(timeout=timeout)
+
+ with self._condition:
+ if self._cancelled:
+ raise future.CancelledError()
+ elif self._computed:
+ return self._exception
+ else:
+ raise future.TimeoutError()
+
+ def traceback(self, timeout=None):
+ """See future.Future.traceback for specification."""
+ with self._condition:
+ if self._cancelled:
+ raise future.CancelledError()
+ if self._computed:
+ return self._traceback
+
+ condition = threading.Condition()
+ def notify_condition(unused_future):
+ with condition:
+ condition.notify()
+ self._callbacks.append(notify_condition)
+
+ with condition:
+ condition.wait(timeout=timeout)
+
+ with self._condition:
+ if self._cancelled:
+ raise future.CancelledError()
+ elif self._computed:
+ return self._traceback
+ else:
+ raise future.TimeoutError()
+
+ def add_done_callback(self, fn):
+ """See future.Future.add_done_callback for specification."""
+ with self._condition:
+ if self._callbacks is not None:
+ self._callbacks.append(fn)
+ return
+
+ callable_util.call_logging_exceptions(fn, _DONE_CALLBACK_LOG_MESSAGE, self)
+
+ def on_operation_termination(self, operation_outcome):
+ """Indicates to this object that the operation has terminated.
+
+ Args:
+ operation_outcome: A base_interfaces.Outcome value indicating the
+ outcome of the operation.
+ """
+ with self._condition:
+ cancelled = self._cancelled
+ if cancelled:
+ callbacks = list(self._callbacks)
+ self._callbacks = None
+ else:
+ rendezvous = self._rendezvous
+
+ if not cancelled:
+ payload = None
+ exception = None
+ traceback = None
+ if operation_outcome == base_interfaces.Outcome.COMPLETED:
+ try:
+ payload = next(rendezvous)
+ except Exception as e: # pylint: disable=broad-except
+ exception = e
+ traceback = sys.exc_info()[2]
+ else:
+ try:
+ # We raise and then immediately catch in order to create a traceback.
+ raise _control.abortion_outcome_to_exception(operation_outcome)
+ except Exception as e: # pylint: disable=broad-except
+ exception = e
+ traceback = sys.exc_info()[2]
+ with self._condition:
+ if not self._cancelled:
+ self._computed = True
+ self._payload = payload
+ self._exception = exception
+ self._traceback = traceback
+ callbacks = list(self._callbacks)
+ self._callbacks = None
+
+ for callback in callbacks:
+ callable_util.call_logging_exceptions(
+ callback, _DONE_CALLBACK_LOG_MESSAGE, self)
+
+
+class _Call(interfaces.Call):
+
+ def __init__(self, operation):
+ self._operation = operation
+ self.context = _control.RpcContext(operation.context)
+
+ def cancel(self):
+ self._operation.cancel()
+
+
+def blocking_value_in_value_out(front, name, payload, timeout, trace_id):
+ """Services in a blocking fashion a value-in value-out servicer method."""
+ rendezvous = _control.Rendezvous()
+ subscription = _rendezvous_subscription(rendezvous)
+ operation = front.operate(
+ name, payload, True, timeout, subscription, trace_id)
+ operation.context.add_termination_callback(rendezvous.set_outcome)
+ return next(rendezvous)
+
+
+def future_value_in_value_out(front, name, payload, timeout, trace_id):
+ """Services a value-in value-out servicer method by returning a Future."""
+ rendezvous = _control.Rendezvous()
+ subscription = _rendezvous_subscription(rendezvous)
+ operation = front.operate(
+ name, payload, True, timeout, subscription, trace_id)
+ operation.context.add_termination_callback(rendezvous.set_outcome)
+ operation_future = _OperationFuture(rendezvous, operation)
+ operation.context.add_termination_callback(
+ operation_future.on_operation_termination)
+ return operation_future
+
+
+def inline_value_in_stream_out(front, name, payload, timeout, trace_id):
+ """Services a value-in stream-out servicer method."""
+ rendezvous = _control.Rendezvous()
+ subscription = _rendezvous_subscription(rendezvous)
+ operation = front.operate(
+ name, payload, True, timeout, subscription, trace_id)
+ operation.context.add_termination_callback(rendezvous.set_outcome)
+ return _OperationCancellableIterator(rendezvous, operation)
+
+
+def blocking_stream_in_value_out(
+ front, name, payload_iterator, timeout, trace_id):
+ """Services in a blocking fashion a stream-in value-out servicer method."""
+ rendezvous = _control.Rendezvous()
+ subscription = _rendezvous_subscription(rendezvous)
+ operation = front.operate(name, None, False, timeout, subscription, trace_id)
+ operation.context.add_termination_callback(rendezvous.set_outcome)
+ for payload in payload_iterator:
+ operation.consumer.consume(payload)
+ operation.consumer.terminate()
+ return next(rendezvous)
+
+
+def future_stream_in_value_out(
+ front, name, payload_iterator, timeout, trace_id, pool):
+ """Services a stream-in value-out servicer method by returning a Future."""
+ rendezvous = _control.Rendezvous()
+ subscription = _rendezvous_subscription(rendezvous)
+ operation = front.operate(name, None, False, timeout, subscription, trace_id)
+ operation.context.add_termination_callback(rendezvous.set_outcome)
+ pool.submit(
+ callable_util.with_exceptions_logged(
+ _control.pipe_iterator_to_consumer, _ITERATOR_EXCEPTION_LOG_MESSAGE),
+ payload_iterator, operation.consumer, lambda: True, True)
+ operation_future = _OperationFuture(rendezvous, operation)
+ operation.context.add_termination_callback(
+ operation_future.on_operation_termination)
+ return operation_future
+
+
+def inline_stream_in_stream_out(
+ front, name, payload_iterator, timeout, trace_id, pool):
+ """Services a stream-in stream-out servicer method."""
+ rendezvous = _control.Rendezvous()
+ subscription = _rendezvous_subscription(rendezvous)
+ operation = front.operate(name, None, False, timeout, subscription, trace_id)
+ operation.context.add_termination_callback(rendezvous.set_outcome)
+ pool.submit(
+ callable_util.with_exceptions_logged(
+ _control.pipe_iterator_to_consumer, _ITERATOR_EXCEPTION_LOG_MESSAGE),
+ payload_iterator, operation.consumer, lambda: True, True)
+ return _OperationCancellableIterator(rendezvous, operation)
+
+
+def event_value_in_value_out(
+ front, name, payload, completion_callback, abortion_callback, timeout,
+ trace_id):
+ subscription = _unary_event_subscription(
+ completion_callback, abortion_callback)
+ operation = front.operate(
+ name, payload, True, timeout, subscription, trace_id)
+ return _Call(operation)
+
+
+def event_value_in_stream_out(
+ front, name, payload, result_payload_consumer, abortion_callback, timeout,
+ trace_id):
+ subscription = _stream_event_subscription(
+ result_payload_consumer, abortion_callback)
+ operation = front.operate(
+ name, payload, True, timeout, subscription, trace_id)
+ return _Call(operation)
+
+
+def event_stream_in_value_out(
+ front, name, completion_callback, abortion_callback, timeout, trace_id):
+ subscription = _unary_event_subscription(
+ completion_callback, abortion_callback)
+ operation = front.operate(name, None, False, timeout, subscription, trace_id)
+ return _Call(operation), operation.consumer
+
+
+def event_stream_in_stream_out(
+ front, name, result_payload_consumer, abortion_callback, timeout, trace_id):
+ subscription = _stream_event_subscription(
+ result_payload_consumer, abortion_callback)
+ operation = front.operate(name, None, False, timeout, subscription, trace_id)
+ return _Call(operation), operation.consumer
diff --git a/src/python/grpcio/grpc/framework/face/_control.py b/src/python/grpcio/grpc/framework/face/_control.py
new file mode 100644
index 0000000000..e918907b74
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/face/_control.py
@@ -0,0 +1,198 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""State and behavior for translating between sync and async control flow."""
+
+import threading
+
+from grpc.framework.base import interfaces as base_interfaces
+from grpc.framework.face import exceptions
+from grpc.framework.face import interfaces
+from grpc.framework.foundation import abandonment
+from grpc.framework.foundation import stream
+
+INTERNAL_ERROR_LOG_MESSAGE = ':-( RPC Framework (Face) Internal Error! :-('
+
+_OPERATION_OUTCOME_TO_RPC_ABORTION = {
+ base_interfaces.Outcome.CANCELLED: interfaces.Abortion.CANCELLED,
+ base_interfaces.Outcome.EXPIRED: interfaces.Abortion.EXPIRED,
+ base_interfaces.Outcome.RECEPTION_FAILURE:
+ interfaces.Abortion.NETWORK_FAILURE,
+ base_interfaces.Outcome.TRANSMISSION_FAILURE:
+ interfaces.Abortion.NETWORK_FAILURE,
+ base_interfaces.Outcome.SERVICED_FAILURE:
+ interfaces.Abortion.SERVICED_FAILURE,
+ base_interfaces.Outcome.SERVICER_FAILURE:
+ interfaces.Abortion.SERVICER_FAILURE,
+}
+
+
+def _as_operation_termination_callback(rpc_abortion_callback):
+ def operation_termination_callback(operation_outcome):
+ rpc_abortion = _OPERATION_OUTCOME_TO_RPC_ABORTION.get(
+ operation_outcome, None)
+ if rpc_abortion is not None:
+ rpc_abortion_callback(rpc_abortion)
+ return operation_termination_callback
+
+
+def _abortion_outcome_to_exception(abortion_outcome):
+ if abortion_outcome == base_interfaces.Outcome.CANCELLED:
+ return exceptions.CancellationError()
+ elif abortion_outcome == base_interfaces.Outcome.EXPIRED:
+ return exceptions.ExpirationError()
+ elif abortion_outcome == base_interfaces.Outcome.SERVICER_FAILURE:
+ return exceptions.ServicerError()
+ elif abortion_outcome == base_interfaces.Outcome.SERVICED_FAILURE:
+ return exceptions.ServicedError()
+ else:
+ return exceptions.NetworkError()
+
+
+class UnaryConsumer(stream.Consumer):
+ """A stream.Consumer that should only ever be passed one value."""
+
+ def __init__(self, on_termination):
+ self._on_termination = on_termination
+ self._value = None
+
+ def consume(self, value):
+ self._value = value
+
+ def terminate(self):
+ self._on_termination(self._value)
+
+ def consume_and_terminate(self, value):
+ self._on_termination(value)
+
+
+class Rendezvous(stream.Consumer):
+ """A rendez-vous with stream.Consumer and iterator interfaces."""
+
+ def __init__(self):
+ self._condition = threading.Condition()
+ self._values = []
+ self._values_completed = False
+ self._abortion = None
+
+ def consume(self, value):
+ with self._condition:
+ self._values.append(value)
+ self._condition.notify()
+
+ def terminate(self):
+ with self._condition:
+ self._values_completed = True
+ self._condition.notify()
+
+ def consume_and_terminate(self, value):
+ with self._condition:
+ self._values.append(value)
+ self._values_completed = True
+ self._condition.notify()
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ with self._condition:
+ while ((self._abortion is None) and
+ (not self._values) and
+ (not self._values_completed)):
+ self._condition.wait()
+ if self._abortion is not None:
+ raise _abortion_outcome_to_exception(self._abortion)
+ elif self._values:
+ return self._values.pop(0)
+ elif self._values_completed:
+ raise StopIteration()
+ else:
+ raise AssertionError('Unreachable code reached!')
+
+ def set_outcome(self, outcome):
+ with self._condition:
+ if outcome is not base_interfaces.Outcome.COMPLETED:
+ self._abortion = outcome
+ self._condition.notify()
+
+
+class RpcContext(interfaces.RpcContext):
+ """A wrapped base_interfaces.OperationContext."""
+
+ def __init__(self, operation_context):
+ self._operation_context = operation_context
+
+ def is_active(self):
+ return self._operation_context.is_active()
+
+ def time_remaining(self):
+ return self._operation_context.time_remaining()
+
+ def add_abortion_callback(self, abortion_callback):
+ self._operation_context.add_termination_callback(
+ _as_operation_termination_callback(abortion_callback))
+
+
+def pipe_iterator_to_consumer(iterator, consumer, active, terminate):
+ """Pipes values emitted from an iterator to a stream.Consumer.
+
+ Args:
+ iterator: An iterator from which values will be emitted.
+ consumer: A stream.Consumer to which values will be passed.
+ active: A no-argument callable that returns True if the work being done by
+ this function is still valid and should not be abandoned and False if the
+ work being done by this function should be abandoned.
+ terminate: A boolean indicating whether or not this function should
+ terminate the given consumer after passing to it all values emitted by the
+ given iterator.
+
+ Raises:
+ abandonment.Abandoned: If this function quits early after seeing False
+ returned by the active function passed to it.
+ Exception: This function raises whatever exceptions are raised by iterating
+ over the given iterator.
+ """
+ for element in iterator:
+ if not active():
+ raise abandonment.Abandoned()
+
+ consumer.consume(element)
+
+ if not active():
+ raise abandonment.Abandoned()
+ if terminate:
+ consumer.terminate()
+
+
+def abortion_outcome_to_exception(abortion_outcome):
+ return _abortion_outcome_to_exception(abortion_outcome)
+
+
+def as_operation_termination_callback(rpc_abortion_callback):
+ return _as_operation_termination_callback(rpc_abortion_callback)
diff --git a/src/python/grpcio/grpc/framework/face/_service.py b/src/python/grpcio/grpc/framework/face/_service.py
new file mode 100644
index 0000000000..cdf413356a
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/face/_service.py
@@ -0,0 +1,187 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Behaviors for servicing RPCs."""
+
+# base_interfaces and interfaces are referenced from specification in this
+# module.
+from grpc.framework.base import interfaces as base_interfaces # pylint: disable=unused-import
+from grpc.framework.face import _control
+from grpc.framework.face import exceptions
+from grpc.framework.face import interfaces # pylint: disable=unused-import
+from grpc.framework.foundation import abandonment
+from grpc.framework.foundation import callable_util
+from grpc.framework.foundation import stream
+from grpc.framework.foundation import stream_util
+
+
+class _ValueInStreamOutConsumer(stream.Consumer):
+ """A stream.Consumer that maps inputs one-to-many onto outputs."""
+
+ def __init__(self, behavior, context, downstream):
+ """Constructor.
+
+ Args:
+ behavior: A callable that takes a single value and an
+ interfaces.RpcContext and returns a generator of arbitrarily many
+ values.
+ context: An interfaces.RpcContext.
+ downstream: A stream.Consumer to which to pass the values generated by the
+ given behavior.
+ """
+ self._behavior = behavior
+ self._context = context
+ self._downstream = downstream
+
+ def consume(self, value):
+ _control.pipe_iterator_to_consumer(
+ self._behavior(value, self._context), self._downstream,
+ self._context.is_active, False)
+
+ def terminate(self):
+ self._downstream.terminate()
+
+ def consume_and_terminate(self, value):
+ _control.pipe_iterator_to_consumer(
+ self._behavior(value, self._context), self._downstream,
+ self._context.is_active, True)
+
+
+def _pool_wrap(behavior, operation_context):
+ """Wraps an operation-related behavior so that it may be called in a pool.
+
+ Args:
+ behavior: A callable related to carrying out an operation.
+ operation_context: A base_interfaces.OperationContext for the operation.
+
+ Returns:
+ A callable that when called carries out the behavior of the given callable
+ and handles whatever exceptions it raises appropriately.
+ """
+ def translation(*args):
+ try:
+ behavior(*args)
+ except (
+ abandonment.Abandoned,
+ exceptions.ExpirationError,
+ exceptions.CancellationError,
+ exceptions.ServicedError,
+ exceptions.NetworkError) as e:
+ if operation_context.is_active():
+ operation_context.fail(e)
+ except Exception as e:
+ operation_context.fail(e)
+ return callable_util.with_exceptions_logged(
+ translation, _control.INTERNAL_ERROR_LOG_MESSAGE)
+
+
+def adapt_inline_value_in_value_out(method):
+ def adaptation(response_consumer, operation_context):
+ rpc_context = _control.RpcContext(operation_context)
+ return stream_util.TransformingConsumer(
+ lambda request: method(request, rpc_context), response_consumer)
+ return adaptation
+
+
+def adapt_inline_value_in_stream_out(method):
+ def adaptation(response_consumer, operation_context):
+ rpc_context = _control.RpcContext(operation_context)
+ return _ValueInStreamOutConsumer(method, rpc_context, response_consumer)
+ return adaptation
+
+
+def adapt_inline_stream_in_value_out(method, pool):
+ def adaptation(response_consumer, operation_context):
+ rendezvous = _control.Rendezvous()
+ operation_context.add_termination_callback(rendezvous.set_outcome)
+ def in_pool_thread():
+ response_consumer.consume_and_terminate(
+ method(rendezvous, _control.RpcContext(operation_context)))
+ pool.submit(_pool_wrap(in_pool_thread, operation_context))
+ return rendezvous
+ return adaptation
+
+
+def adapt_inline_stream_in_stream_out(method, pool):
+ """Adapts an interfaces.InlineStreamInStreamOutMethod for use with Consumers.
+
+ RPCs may be serviced by calling the return value of this function, passing
+ request values to the stream.Consumer returned from that call, and receiving
+ response values from the stream.Consumer passed to that call.
+
+ Args:
+ method: An interfaces.InlineStreamInStreamOutMethod.
+ pool: A thread pool.
+
+ Returns:
+ A callable that takes a stream.Consumer and a
+ base_interfaces.OperationContext and returns a stream.Consumer.
+ """
+ def adaptation(response_consumer, operation_context):
+ rendezvous = _control.Rendezvous()
+ operation_context.add_termination_callback(rendezvous.set_outcome)
+ def in_pool_thread():
+ _control.pipe_iterator_to_consumer(
+ method(rendezvous, _control.RpcContext(operation_context)),
+ response_consumer, operation_context.is_active, True)
+ pool.submit(_pool_wrap(in_pool_thread, operation_context))
+ return rendezvous
+ return adaptation
+
+
+def adapt_event_value_in_value_out(method):
+ def adaptation(response_consumer, operation_context):
+ def on_payload(payload):
+ method(
+ payload, response_consumer.consume_and_terminate,
+ _control.RpcContext(operation_context))
+ return _control.UnaryConsumer(on_payload)
+ return adaptation
+
+
+def adapt_event_value_in_stream_out(method):
+ def adaptation(response_consumer, operation_context):
+ def on_payload(payload):
+ method(
+ payload, response_consumer, _control.RpcContext(operation_context))
+ return _control.UnaryConsumer(on_payload)
+ return adaptation
+
+
+def adapt_event_stream_in_value_out(method):
+ def adaptation(response_consumer, operation_context):
+ rpc_context = _control.RpcContext(operation_context)
+ return method(response_consumer.consume_and_terminate, rpc_context)
+ return adaptation
+
+
+def adapt_event_stream_in_stream_out(method):
+ def adaptation(response_consumer, operation_context):
+ return method(response_consumer, _control.RpcContext(operation_context))
+ return adaptation
diff --git a/src/python/grpcio/grpc/framework/face/demonstration.py b/src/python/grpcio/grpc/framework/face/demonstration.py
new file mode 100644
index 0000000000..f6b4b609ff
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/face/demonstration.py
@@ -0,0 +1,118 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Demonstration-suitable implementation of the face layer of RPC Framework."""
+
+from grpc.framework.base import util as _base_util
+from grpc.framework.base import implementations as _base_implementations
+from grpc.framework.face import implementations
+from grpc.framework.foundation import logging_pool
+
+_POOL_SIZE_LIMIT = 5
+
+_MAXIMUM_TIMEOUT = 90
+
+
+class LinkedPair(object):
+ """A Server and Stub that are linked to one another.
+
+ Attributes:
+ server: A Server.
+ stub: A Stub.
+ """
+
+ def shut_down(self):
+ """Shuts down this object and releases its resources."""
+ raise NotImplementedError()
+
+
+class _LinkedPair(LinkedPair):
+
+ def __init__(self, server, stub, front, back, pools):
+ self.server = server
+ self.stub = stub
+ self._front = front
+ self._back = back
+ self._pools = pools
+
+ def shut_down(self):
+ _base_util.wait_for_idle(self._front)
+ _base_util.wait_for_idle(self._back)
+
+ for pool in self._pools:
+ pool.shutdown(wait=True)
+
+
+def server_and_stub(
+ default_timeout,
+ inline_value_in_value_out_methods=None,
+ inline_value_in_stream_out_methods=None,
+ inline_stream_in_value_out_methods=None,
+ inline_stream_in_stream_out_methods=None,
+ event_value_in_value_out_methods=None,
+ event_value_in_stream_out_methods=None,
+ event_stream_in_value_out_methods=None,
+ event_stream_in_stream_out_methods=None,
+ multi_method=None):
+ """Creates a Server and Stub linked together for use."""
+ front_work_pool = logging_pool.pool(_POOL_SIZE_LIMIT)
+ front_transmission_pool = logging_pool.pool(_POOL_SIZE_LIMIT)
+ front_utility_pool = logging_pool.pool(_POOL_SIZE_LIMIT)
+ back_work_pool = logging_pool.pool(_POOL_SIZE_LIMIT)
+ back_transmission_pool = logging_pool.pool(_POOL_SIZE_LIMIT)
+ back_utility_pool = logging_pool.pool(_POOL_SIZE_LIMIT)
+ stub_pool = logging_pool.pool(_POOL_SIZE_LIMIT)
+ pools = (
+ front_work_pool, front_transmission_pool, front_utility_pool,
+ back_work_pool, back_transmission_pool, back_utility_pool,
+ stub_pool)
+
+ servicer = implementations.servicer(
+ back_work_pool,
+ inline_value_in_value_out_methods=inline_value_in_value_out_methods,
+ inline_value_in_stream_out_methods=inline_value_in_stream_out_methods,
+ inline_stream_in_value_out_methods=inline_stream_in_value_out_methods,
+ inline_stream_in_stream_out_methods=inline_stream_in_stream_out_methods,
+ event_value_in_value_out_methods=event_value_in_value_out_methods,
+ event_value_in_stream_out_methods=event_value_in_stream_out_methods,
+ event_stream_in_value_out_methods=event_stream_in_value_out_methods,
+ event_stream_in_stream_out_methods=event_stream_in_stream_out_methods,
+ multi_method=multi_method)
+
+ front = _base_implementations.front_link(
+ front_work_pool, front_transmission_pool, front_utility_pool)
+ back = _base_implementations.back_link(
+ servicer, back_work_pool, back_transmission_pool, back_utility_pool,
+ default_timeout, _MAXIMUM_TIMEOUT)
+ front.join_rear_link(back)
+ back.join_fore_link(front)
+
+ stub = implementations.stub(front, stub_pool)
+
+ return _LinkedPair(implementations.server(), stub, front, back, pools)
diff --git a/src/python/grpcio/grpc/framework/face/exceptions.py b/src/python/grpcio/grpc/framework/face/exceptions.py
new file mode 100644
index 0000000000..f112df70bc
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/face/exceptions.py
@@ -0,0 +1,77 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Exceptions used in the Face layer of RPC Framework."""
+
+import abc
+
+
+class NoSuchMethodError(Exception):
+ """Raised by customer code to indicate an unrecognized RPC method name.
+
+ Attributes:
+ name: The unrecognized name.
+ """
+
+ def __init__(self, name):
+ """Constructor.
+
+ Args:
+ name: The unrecognized RPC method name.
+ """
+ super(NoSuchMethodError, self).__init__()
+ self.name = name
+
+
+class RpcError(Exception):
+ """Common super type for all exceptions raised by the Face layer.
+
+ Only RPC Framework should instantiate and raise these exceptions.
+ """
+ __metaclass__ = abc.ABCMeta
+
+
+class CancellationError(RpcError):
+ """Indicates that an RPC has been cancelled."""
+
+
+class ExpirationError(RpcError):
+ """Indicates that an RPC has expired ("timed out")."""
+
+
+class NetworkError(RpcError):
+ """Indicates that some error occurred on the network."""
+
+
+class ServicedError(RpcError):
+ """Indicates that the Serviced failed in the course of an RPC."""
+
+
+class ServicerError(RpcError):
+ """Indicates that the Servicer failed in the course of servicing an RPC."""
diff --git a/src/python/grpcio/grpc/framework/face/implementations.py b/src/python/grpcio/grpc/framework/face/implementations.py
new file mode 100644
index 0000000000..4a6de52974
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/face/implementations.py
@@ -0,0 +1,318 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Entry points into the Face layer of RPC Framework."""
+
+from grpc.framework.common import cardinality
+from grpc.framework.common import style
+from grpc.framework.base import exceptions as _base_exceptions
+from grpc.framework.base import interfaces as base_interfaces
+from grpc.framework.face import _calls
+from grpc.framework.face import _service
+from grpc.framework.face import exceptions
+from grpc.framework.face import interfaces
+
+
+class _BaseServicer(base_interfaces.Servicer):
+
+ def __init__(self, methods, multi_method):
+ self._methods = methods
+ self._multi_method = multi_method
+
+ def service(self, name, context, output_consumer):
+ method = self._methods.get(name, None)
+ if method is not None:
+ return method(output_consumer, context)
+ elif self._multi_method is not None:
+ try:
+ return self._multi_method.service(name, output_consumer, context)
+ except exceptions.NoSuchMethodError:
+ raise _base_exceptions.NoSuchMethodError()
+ else:
+ raise _base_exceptions.NoSuchMethodError()
+
+
+class _UnaryUnaryMultiCallable(interfaces.UnaryUnaryMultiCallable):
+
+ def __init__(self, front, name):
+ self._front = front
+ self._name = name
+
+ def __call__(self, request, timeout):
+ return _calls.blocking_value_in_value_out(
+ self._front, self._name, request, timeout, 'unused trace ID')
+
+ def future(self, request, timeout):
+ return _calls.future_value_in_value_out(
+ self._front, self._name, request, timeout, 'unused trace ID')
+
+ def event(self, request, response_callback, abortion_callback, timeout):
+ return _calls.event_value_in_value_out(
+ self._front, self._name, request, response_callback, abortion_callback,
+ timeout, 'unused trace ID')
+
+
+class _UnaryStreamMultiCallable(interfaces.UnaryStreamMultiCallable):
+
+ def __init__(self, front, name):
+ self._front = front
+ self._name = name
+
+ def __call__(self, request, timeout):
+ return _calls.inline_value_in_stream_out(
+ self._front, self._name, request, timeout, 'unused trace ID')
+
+ def event(self, request, response_consumer, abortion_callback, timeout):
+ return _calls.event_value_in_stream_out(
+ self._front, self._name, request, response_consumer, abortion_callback,
+ timeout, 'unused trace ID')
+
+
+class _StreamUnaryMultiCallable(interfaces.StreamUnaryMultiCallable):
+
+ def __init__(self, front, name, pool):
+ self._front = front
+ self._name = name
+ self._pool = pool
+
+ def __call__(self, request_iterator, timeout):
+ return _calls.blocking_stream_in_value_out(
+ self._front, self._name, request_iterator, timeout, 'unused trace ID')
+
+ def future(self, request_iterator, timeout):
+ return _calls.future_stream_in_value_out(
+ self._front, self._name, request_iterator, timeout, 'unused trace ID',
+ self._pool)
+
+ def event(self, response_callback, abortion_callback, timeout):
+ return _calls.event_stream_in_value_out(
+ self._front, self._name, response_callback, abortion_callback, timeout,
+ 'unused trace ID')
+
+
+class _StreamStreamMultiCallable(interfaces.StreamStreamMultiCallable):
+
+ def __init__(self, front, name, pool):
+ self._front = front
+ self._name = name
+ self._pool = pool
+
+ def __call__(self, request_iterator, timeout):
+ return _calls.inline_stream_in_stream_out(
+ self._front, self._name, request_iterator, timeout, 'unused trace ID',
+ self._pool)
+
+ def event(self, response_consumer, abortion_callback, timeout):
+ return _calls.event_stream_in_stream_out(
+ self._front, self._name, response_consumer, abortion_callback, timeout,
+ 'unused trace ID')
+
+
+class _GenericStub(interfaces.GenericStub):
+ """An interfaces.GenericStub implementation."""
+
+ def __init__(self, front, pool):
+ self._front = front
+ self._pool = pool
+
+ def blocking_value_in_value_out(self, name, request, timeout):
+ return _calls.blocking_value_in_value_out(
+ self._front, name, request, timeout, 'unused trace ID')
+
+ def future_value_in_value_out(self, name, request, timeout):
+ return _calls.future_value_in_value_out(
+ self._front, name, request, timeout, 'unused trace ID')
+
+ def inline_value_in_stream_out(self, name, request, timeout):
+ return _calls.inline_value_in_stream_out(
+ self._front, name, request, timeout, 'unused trace ID')
+
+ def blocking_stream_in_value_out(self, name, request_iterator, timeout):
+ return _calls.blocking_stream_in_value_out(
+ self._front, name, request_iterator, timeout, 'unused trace ID')
+
+ def future_stream_in_value_out(self, name, request_iterator, timeout):
+ return _calls.future_stream_in_value_out(
+ self._front, name, request_iterator, timeout, 'unused trace ID',
+ self._pool)
+
+ def inline_stream_in_stream_out(self, name, request_iterator, timeout):
+ return _calls.inline_stream_in_stream_out(
+ self._front, name, request_iterator, timeout, 'unused trace ID',
+ self._pool)
+
+ def event_value_in_value_out(
+ self, name, request, response_callback, abortion_callback, timeout):
+ return _calls.event_value_in_value_out(
+ self._front, name, request, response_callback, abortion_callback,
+ timeout, 'unused trace ID')
+
+ def event_value_in_stream_out(
+ self, name, request, response_consumer, abortion_callback, timeout):
+ return _calls.event_value_in_stream_out(
+ self._front, name, request, response_consumer, abortion_callback,
+ timeout, 'unused trace ID')
+
+ def event_stream_in_value_out(
+ self, name, response_callback, abortion_callback, timeout):
+ return _calls.event_stream_in_value_out(
+ self._front, name, response_callback, abortion_callback, timeout,
+ 'unused trace ID')
+
+ def event_stream_in_stream_out(
+ self, name, response_consumer, abortion_callback, timeout):
+ return _calls.event_stream_in_stream_out(
+ self._front, name, response_consumer, abortion_callback, timeout,
+ 'unused trace ID')
+
+ def unary_unary_multi_callable(self, name):
+ return _UnaryUnaryMultiCallable(self._front, name)
+
+ def unary_stream_multi_callable(self, name):
+ return _UnaryStreamMultiCallable(self._front, name)
+
+ def stream_unary_multi_callable(self, name):
+ return _StreamUnaryMultiCallable(self._front, name, self._pool)
+
+ def stream_stream_multi_callable(self, name):
+ return _StreamStreamMultiCallable(self._front, name, self._pool)
+
+
+class _DynamicStub(interfaces.DynamicStub):
+ """An interfaces.DynamicStub implementation."""
+
+ def __init__(self, cardinalities, front, pool):
+ self._cardinalities = cardinalities
+ self._front = front
+ self._pool = pool
+
+ def __getattr__(self, attr):
+ method_cardinality = self._cardinalities.get(attr)
+ if method_cardinality is cardinality.Cardinality.UNARY_UNARY:
+ return _UnaryUnaryMultiCallable(self._front, attr)
+ elif method_cardinality is cardinality.Cardinality.UNARY_STREAM:
+ return _UnaryStreamMultiCallable(self._front, attr)
+ elif method_cardinality is cardinality.Cardinality.STREAM_UNARY:
+ return _StreamUnaryMultiCallable(self._front, attr, self._pool)
+ elif method_cardinality is cardinality.Cardinality.STREAM_STREAM:
+ return _StreamStreamMultiCallable(self._front, attr, self._pool)
+ else:
+ raise AttributeError('_DynamicStub object has no attribute "%s"!' % attr)
+
+
+def _adapt_method_implementations(method_implementations, pool):
+ adapted_implementations = {}
+ for name, method_implementation in method_implementations.iteritems():
+ if method_implementation.style is style.Service.INLINE:
+ if method_implementation.cardinality is cardinality.Cardinality.UNARY_UNARY:
+ adapted_implementations[name] = _service.adapt_inline_value_in_value_out(
+ method_implementation.unary_unary_inline)
+ elif method_implementation.cardinality is cardinality.Cardinality.UNARY_STREAM:
+ adapted_implementations[name] = _service.adapt_inline_value_in_stream_out(
+ method_implementation.unary_stream_inline)
+ elif method_implementation.cardinality is cardinality.Cardinality.STREAM_UNARY:
+ adapted_implementations[name] = _service.adapt_inline_stream_in_value_out(
+ method_implementation.stream_unary_inline, pool)
+ elif method_implementation.cardinality is cardinality.Cardinality.STREAM_STREAM:
+ adapted_implementations[name] = _service.adapt_inline_stream_in_stream_out(
+ method_implementation.stream_stream_inline, pool)
+ elif method_implementation.style is style.Service.EVENT:
+ if method_implementation.cardinality is cardinality.Cardinality.UNARY_UNARY:
+ adapted_implementations[name] = _service.adapt_event_value_in_value_out(
+ method_implementation.unary_unary_event)
+ elif method_implementation.cardinality is cardinality.Cardinality.UNARY_STREAM:
+ adapted_implementations[name] = _service.adapt_event_value_in_stream_out(
+ method_implementation.unary_stream_event)
+ elif method_implementation.cardinality is cardinality.Cardinality.STREAM_UNARY:
+ adapted_implementations[name] = _service.adapt_event_stream_in_value_out(
+ method_implementation.stream_unary_event)
+ elif method_implementation.cardinality is cardinality.Cardinality.STREAM_STREAM:
+ adapted_implementations[name] = _service.adapt_event_stream_in_stream_out(
+ method_implementation.stream_stream_event)
+ return adapted_implementations
+
+
+def servicer(pool, method_implementations, multi_method_implementation):
+ """Creates a base_interfaces.Servicer.
+
+ It is guaranteed that any passed interfaces.MultiMethodImplementation will
+ only be called to service an RPC if there is no
+ interfaces.MethodImplementation for the RPC method in the passed
+ method_implementations dictionary.
+
+ Args:
+ pool: A thread pool.
+ method_implementations: A dictionary from RPC method name to
+ interfaces.MethodImplementation object to be used to service the named
+ RPC method.
+ multi_method_implementation: An interfaces.MultiMethodImplementation to be
+ used to service any RPCs not serviced by the
+ interfaces.MethodImplementations given in the method_implementations
+ dictionary, or None.
+
+ Returns:
+ A base_interfaces.Servicer that services RPCs via the given implementations.
+ """
+ adapted_implementations = _adapt_method_implementations(
+ method_implementations, pool)
+ return _BaseServicer(adapted_implementations, multi_method_implementation)
+
+
+def generic_stub(front, pool):
+ """Creates an interfaces.GenericStub.
+
+ Args:
+ front: A base_interfaces.Front.
+ pool: A futures.ThreadPoolExecutor.
+
+ Returns:
+ An interfaces.GenericStub that performs RPCs via the given
+ base_interfaces.Front.
+ """
+ return _GenericStub(front, pool)
+
+
+def dynamic_stub(cardinalities, front, pool, prefix):
+ """Creates an interfaces.DynamicStub.
+
+ Args:
+ cardinalities: A dict from RPC method name to cardinality.Cardinality
+ value identifying the cardinality of every RPC method to be supported by
+ the created interfaces.DynamicStub.
+ front: A base_interfaces.Front.
+ pool: A futures.ThreadPoolExecutor.
+ prefix: A string to prepend when mapping requested attribute name to RPC
+ method name during attribute access on the created
+ interfaces.DynamicStub.
+
+ Returns:
+ An interfaces.DynamicStub that performs RPCs via the given
+ base_interfaces.Front.
+ """
+ return _DynamicStub(cardinalities, front, pool)
diff --git a/src/python/grpcio/grpc/framework/face/interfaces.py b/src/python/grpcio/grpc/framework/face/interfaces.py
new file mode 100644
index 0000000000..b7cc4c1169
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/face/interfaces.py
@@ -0,0 +1,640 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Interfaces for the face layer of RPC Framework."""
+
+import abc
+import enum
+
+# cardinality, style, exceptions, abandonment, future, and stream are
+# referenced from specification in this module.
+from grpc.framework.common import cardinality # pylint: disable=unused-import
+from grpc.framework.common import style # pylint: disable=unused-import
+from grpc.framework.face import exceptions # pylint: disable=unused-import
+from grpc.framework.foundation import abandonment # pylint: disable=unused-import
+from grpc.framework.foundation import future # pylint: disable=unused-import
+from grpc.framework.foundation import stream # pylint: disable=unused-import
+
+
+@enum.unique
+class Abortion(enum.Enum):
+ """Categories of RPC abortion."""
+ CANCELLED = 'cancelled'
+ EXPIRED = 'expired'
+ NETWORK_FAILURE = 'network failure'
+ SERVICED_FAILURE = 'serviced failure'
+ SERVICER_FAILURE = 'servicer failure'
+
+
+class CancellableIterator(object):
+ """Implements the Iterator protocol and affords a cancel method."""
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def __iter__(self):
+ """Returns the self object in accordance with the Iterator protocol."""
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def next(self):
+ """Returns a value or raises StopIteration per the Iterator protocol."""
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def cancel(self):
+ """Requests cancellation of whatever computation underlies this iterator."""
+ raise NotImplementedError()
+
+
+class RpcContext(object):
+ """Provides RPC-related information and action."""
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def is_active(self):
+ """Describes whether the RPC is active or has terminated."""
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def time_remaining(self):
+ """Describes the length of allowed time remaining for the RPC.
+
+ Returns:
+ A nonnegative float indicating the length of allowed time in seconds
+ remaining for the RPC to complete before it is considered to have timed
+ out.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def add_abortion_callback(self, abortion_callback):
+ """Registers a callback to be called if the RPC is aborted.
+
+ Args:
+ abortion_callback: A callable to be called and passed an Abortion value
+ in the event of RPC abortion.
+ """
+ raise NotImplementedError()
+
+
+class Call(object):
+ """Invocation-side representation of an RPC.
+
+ Attributes:
+ context: An RpcContext affording information about the RPC.
+ """
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def cancel(self):
+ """Requests cancellation of the RPC."""
+ raise NotImplementedError()
+
+
+class UnaryUnaryMultiCallable(object):
+ """Affords invoking a unary-unary RPC in any call style."""
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def __call__(self, request, timeout):
+ """Synchronously invokes the underlying RPC.
+
+ Args:
+ request: The request value for the RPC.
+ timeout: A duration of time in seconds to allow for the RPC.
+
+ Returns:
+ The response value for the RPC.
+
+ Raises:
+ exceptions.RpcError: Indicating that the RPC was aborted.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def future(self, request, timeout):
+ """Asynchronously invokes the underlying RPC.
+
+ Args:
+ request: The request value for the RPC.
+ timeout: A duration of time in seconds to allow for the RPC.
+
+ Returns:
+ A future.Future representing the RPC. In the event of RPC completion, the
+ returned Future's result value will be the response value of the RPC.
+ In the event of RPC abortion, the returned Future's exception value
+ will be an exceptions.RpcError.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def event(self, request, response_callback, abortion_callback, timeout):
+ """Asynchronously invokes the underlying RPC.
+
+ Args:
+ request: The request value for the RPC.
+ response_callback: A callback to be called to accept the restponse value
+ of the RPC.
+ abortion_callback: A callback to be called and passed an Abortion value
+ in the event of RPC abortion.
+ timeout: A duration of time in seconds to allow for the RPC.
+
+ Returns:
+ A Call object for the RPC.
+ """
+ raise NotImplementedError()
+
+
+class UnaryStreamMultiCallable(object):
+ """Affords invoking a unary-stream RPC in any call style."""
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def __call__(self, request, timeout):
+ """Synchronously invokes the underlying RPC.
+
+ Args:
+ request: The request value for the RPC.
+ timeout: A duration of time in seconds to allow for the RPC.
+
+ Returns:
+ A CancellableIterator that yields the response values of the RPC and
+ affords RPC cancellation. Drawing response values from the returned
+ CancellableIterator may raise exceptions.RpcError indicating abortion
+ of the RPC.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def event(self, request, response_consumer, abortion_callback, timeout):
+ """Asynchronously invokes the underlying RPC.
+
+ Args:
+ request: The request value for the RPC.
+ response_consumer: A stream.Consumer to be called to accept the restponse
+ values of the RPC.
+ abortion_callback: A callback to be called and passed an Abortion value
+ in the event of RPC abortion.
+ timeout: A duration of time in seconds to allow for the RPC.
+
+ Returns:
+ A Call object for the RPC.
+ """
+ raise NotImplementedError()
+
+
+class StreamUnaryMultiCallable(object):
+ """Affords invoking a stream-unary RPC in any call style."""
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def __call__(self, request_iterator, timeout):
+ """Synchronously invokes the underlying RPC.
+
+ Args:
+ request_iterator: An iterator that yields request values for the RPC.
+ timeout: A duration of time in seconds to allow for the RPC.
+
+ Returns:
+ The response value for the RPC.
+
+ Raises:
+ exceptions.RpcError: Indicating that the RPC was aborted.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def future(self, request_iterator, timeout):
+ """Asynchronously invokes the underlying RPC.
+
+ Args:
+ request_iterator: An iterator that yields request values for the RPC.
+ timeout: A duration of time in seconds to allow for the RPC.
+
+ Returns:
+ A future.Future representing the RPC. In the event of RPC completion, the
+ returned Future's result value will be the response value of the RPC.
+ In the event of RPC abortion, the returned Future's exception value
+ will be an exceptions.RpcError.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def event(self, response_callback, abortion_callback, timeout):
+ """Asynchronously invokes the underlying RPC.
+
+ Args:
+ request: The request value for the RPC.
+ response_callback: A callback to be called to accept the restponse value
+ of the RPC.
+ abortion_callback: A callback to be called and passed an Abortion value
+ in the event of RPC abortion.
+ timeout: A duration of time in seconds to allow for the RPC.
+
+ Returns:
+ A pair of a Call object for the RPC and a stream.Consumer to which the
+ request values of the RPC should be passed.
+ """
+ raise NotImplementedError()
+
+
+class StreamStreamMultiCallable(object):
+ """Affords invoking a stream-stream RPC in any call style."""
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def __call__(self, request_iterator, timeout):
+ """Synchronously invokes the underlying RPC.
+
+ Args:
+ request_iterator: An iterator that yields request values for the RPC.
+ timeout: A duration of time in seconds to allow for the RPC.
+
+ Returns:
+ A CancellableIterator that yields the response values of the RPC and
+ affords RPC cancellation. Drawing response values from the returned
+ CancellableIterator may raise exceptions.RpcError indicating abortion
+ of the RPC.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def event(self, response_consumer, abortion_callback, timeout):
+ """Asynchronously invokes the underlying RPC.
+
+l Args:
+ response_consumer: A stream.Consumer to be called to accept the restponse
+ values of the RPC.
+ abortion_callback: A callback to be called and passed an Abortion value
+ in the event of RPC abortion.
+ timeout: A duration of time in seconds to allow for the RPC.
+
+ Returns:
+ A pair of a Call object for the RPC and a stream.Consumer to which the
+ request values of the RPC should be passed.
+ """
+ raise NotImplementedError()
+
+
+class MethodImplementation(object):
+ """A sum type that describes an RPC method implementation.
+
+ Attributes:
+ cardinality: A cardinality.Cardinality value.
+ style: A style.Service value.
+ unary_unary_inline: The implementation of the RPC method as a callable
+ value that takes a request value and an RpcContext object and returns a
+ response value. Only non-None if cardinality is
+ cardinality.Cardinality.UNARY_UNARY and style is style.Service.INLINE.
+ unary_stream_inline: The implementation of the RPC method as a callable
+ value that takes a request value and an RpcContext object and returns an
+ iterator of response values. Only non-None if cardinality is
+ cardinality.Cardinality.UNARY_STREAM and style is style.Service.INLINE.
+ stream_unary_inline: The implementation of the RPC method as a callable
+ value that takes an iterator of request values and an RpcContext object
+ and returns a response value. Only non-None if cardinality is
+ cardinality.Cardinality.STREAM_UNARY and style is style.Service.INLINE.
+ stream_stream_inline: The implementation of the RPC method as a callable
+ value that takes an iterator of request values and an RpcContext object
+ and returns an iterator of response values. Only non-None if cardinality
+ is cardinality.Cardinality.STREAM_STREAM and style is
+ style.Service.INLINE.
+ unary_unary_event: The implementation of the RPC method as a callable value
+ that takes a request value, a response callback to which to pass the
+ response value of the RPC, and an RpcContext. Only non-None if
+ cardinality is cardinality.Cardinality.UNARY_UNARY and style is
+ style.Service.EVENT.
+ unary_stream_event: The implementation of the RPC method as a callable
+ value that takes a request value, a stream.Consumer to which to pass the
+ the response values of the RPC, and an RpcContext. Only non-None if
+ cardinality is cardinality.Cardinality.UNARY_STREAM and style is
+ style.Service.EVENT.
+ stream_unary_event: The implementation of the RPC method as a callable
+ value that takes a response callback to which to pass the response value
+ of the RPC and an RpcContext and returns a stream.Consumer to which the
+ request values of the RPC should be passed. Only non-None if cardinality
+ is cardinality.Cardinality.STREAM_UNARY and style is style.Service.EVENT.
+ stream_stream_event: The implementation of the RPC method as a callable
+ value that takes a stream.Consumer to which to pass the response values
+ of the RPC and an RpcContext and returns a stream.Consumer to which the
+ request values of the RPC should be passed. Only non-None if cardinality
+ is cardinality.Cardinality.STREAM_STREAM and style is
+ style.Service.EVENT.
+ """
+ __metaclass__ = abc.ABCMeta
+
+
+class MultiMethodImplementation(object):
+ """A general type able to service many RPC methods."""
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def service(self, name, response_consumer, context):
+ """Services an RPC.
+
+ Args:
+ name: The RPC method name.
+ response_consumer: A stream.Consumer to be called to accept the response
+ values of the RPC.
+ context: An RpcContext object.
+
+ Returns:
+ A stream.Consumer with which to accept the request values of the RPC. The
+ consumer returned from this method may or may not be invoked to
+ completion: in the case of RPC abortion, RPC Framework will simply stop
+ passing values to this object. Implementations must not assume that this
+ object will be called to completion of the request stream or even called
+ at all.
+
+ Raises:
+ abandonment.Abandoned: May or may not be raised when the RPC has been
+ aborted.
+ exceptions.NoSuchMethodError: If this MultiMethod does not recognize the
+ given RPC method name and is not able to service the RPC.
+ """
+ raise NotImplementedError()
+
+
+class GenericStub(object):
+ """Affords RPC methods to callers."""
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def blocking_value_in_value_out(self, name, request, timeout):
+ """Invokes a unary-request-unary-response RPC method.
+
+ This method blocks until either returning the response value of the RPC
+ (in the event of RPC completion) or raising an exception (in the event of
+ RPC abortion).
+
+ Args:
+ name: The RPC method name.
+ request: The request value for the RPC.
+ timeout: A duration of time in seconds to allow for the RPC.
+
+ Returns:
+ The response value for the RPC.
+
+ Raises:
+ exceptions.RpcError: Indicating that the RPC was aborted.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def future_value_in_value_out(self, name, request, timeout):
+ """Invokes a unary-request-unary-response RPC method.
+
+ Args:
+ name: The RPC method name.
+ request: The request value for the RPC.
+ timeout: A duration of time in seconds to allow for the RPC.
+
+ Returns:
+ A future.Future representing the RPC. In the event of RPC completion, the
+ returned Future will return an outcome indicating that the RPC returned
+ the response value of the RPC. In the event of RPC abortion, the
+ returned Future will return an outcome indicating that the RPC raised
+ an exceptions.RpcError.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def inline_value_in_stream_out(self, name, request, timeout):
+ """Invokes a unary-request-stream-response RPC method.
+
+ Args:
+ name: The RPC method name.
+ request: The request value for the RPC.
+ timeout: A duration of time in seconds to allow for the RPC.
+
+ Returns:
+ A CancellableIterator that yields the response values of the RPC and
+ affords RPC cancellation. Drawing response values from the returned
+ CancellableIterator may raise exceptions.RpcError indicating abortion of
+ the RPC.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def blocking_stream_in_value_out(self, name, request_iterator, timeout):
+ """Invokes a stream-request-unary-response RPC method.
+
+ This method blocks until either returning the response value of the RPC
+ (in the event of RPC completion) or raising an exception (in the event of
+ RPC abortion).
+
+ Args:
+ name: The RPC method name.
+ request_iterator: An iterator that yields the request values of the RPC.
+ timeout: A duration of time in seconds to allow for the RPC.
+
+ Returns:
+ The response value for the RPC.
+
+ Raises:
+ exceptions.RpcError: Indicating that the RPC was aborted.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def future_stream_in_value_out(self, name, request_iterator, timeout):
+ """Invokes a stream-request-unary-response RPC method.
+
+ Args:
+ name: The RPC method name.
+ request_iterator: An iterator that yields the request values of the RPC.
+ timeout: A duration of time in seconds to allow for the RPC.
+
+ Returns:
+ A future.Future representing the RPC. In the event of RPC completion, the
+ returned Future will return an outcome indicating that the RPC returned
+ the response value of the RPC. In the event of RPC abortion, the
+ returned Future will return an outcome indicating that the RPC raised
+ an exceptions.RpcError.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def inline_stream_in_stream_out(self, name, request_iterator, timeout):
+ """Invokes a stream-request-stream-response RPC method.
+
+ Args:
+ name: The RPC method name.
+ request_iterator: An iterator that yields the request values of the RPC.
+ timeout: A duration of time in seconds to allow for the RPC.
+
+ Returns:
+ A CancellableIterator that yields the response values of the RPC and
+ affords RPC cancellation. Drawing response values from the returned
+ CancellableIterator may raise exceptions.RpcError indicating abortion of
+ the RPC.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def event_value_in_value_out(
+ self, name, request, response_callback, abortion_callback, timeout):
+ """Event-driven invocation of a unary-request-unary-response RPC method.
+
+ Args:
+ name: The RPC method name.
+ request: The request value for the RPC.
+ response_callback: A callback to be called to accept the response value
+ of the RPC.
+ abortion_callback: A callback to be called and passed an Abortion value
+ in the event of RPC abortion.
+ timeout: A duration of time in seconds to allow for the RPC.
+
+ Returns:
+ A Call object for the RPC.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def event_value_in_stream_out(
+ self, name, request, response_consumer, abortion_callback, timeout):
+ """Event-driven invocation of a unary-request-stream-response RPC method.
+
+ Args:
+ name: The RPC method name.
+ request: The request value for the RPC.
+ response_consumer: A stream.Consumer to be called to accept the response
+ values of the RPC.
+ abortion_callback: A callback to be called and passed an Abortion value
+ in the event of RPC abortion.
+ timeout: A duration of time in seconds to allow for the RPC.
+
+ Returns:
+ A Call object for the RPC.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def event_stream_in_value_out(
+ self, name, response_callback, abortion_callback, timeout):
+ """Event-driven invocation of a unary-request-unary-response RPC method.
+
+ Args:
+ name: The RPC method name.
+ response_callback: A callback to be called to accept the response value
+ of the RPC.
+ abortion_callback: A callback to be called and passed an Abortion value
+ in the event of RPC abortion.
+ timeout: A duration of time in seconds to allow for the RPC.
+
+ Returns:
+ A pair of a Call object for the RPC and a stream.Consumer to which the
+ request values of the RPC should be passed.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def event_stream_in_stream_out(
+ self, name, response_consumer, abortion_callback, timeout):
+ """Event-driven invocation of a unary-request-stream-response RPC method.
+
+ Args:
+ name: The RPC method name.
+ response_consumer: A stream.Consumer to be called to accept the response
+ values of the RPC.
+ abortion_callback: A callback to be called and passed an Abortion value
+ in the event of RPC abortion.
+ timeout: A duration of time in seconds to allow for the RPC.
+
+ Returns:
+ A pair of a Call object for the RPC and a stream.Consumer to which the
+ request values of the RPC should be passed.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def unary_unary_multi_callable(self, name):
+ """Creates a UnaryUnaryMultiCallable for a unary-unary RPC method.
+
+ Args:
+ name: The RPC method name.
+
+ Returns:
+ A UnaryUnaryMultiCallable value for the named unary-unary RPC method.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def unary_stream_multi_callable(self, name):
+ """Creates a UnaryStreamMultiCallable for a unary-stream RPC method.
+
+ Args:
+ name: The RPC method name.
+
+ Returns:
+ A UnaryStreamMultiCallable value for the name unary-stream RPC method.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def stream_unary_multi_callable(self, name):
+ """Creates a StreamUnaryMultiCallable for a stream-unary RPC method.
+
+ Args:
+ name: The RPC method name.
+
+ Returns:
+ A StreamUnaryMultiCallable value for the named stream-unary RPC method.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def stream_stream_multi_callable(self, name):
+ """Creates a StreamStreamMultiCallable for a stream-stream RPC method.
+
+ Args:
+ name: The RPC method name.
+
+ Returns:
+ A StreamStreamMultiCallable value for the named stream-stream RPC method.
+ """
+ raise NotImplementedError()
+
+
+class DynamicStub(object):
+ """A stub with RPC-method-bound multi-callable attributes.
+
+ Instances of this type responsd to attribute access as follows: if the
+ requested attribute is the name of a unary-unary RPC method, the value of the
+ attribute will be a UnaryUnaryMultiCallable with which to invoke the RPC
+ method; if the requested attribute is the name of a unary-stream RPC method,
+ the value of the attribute will be a UnaryStreamMultiCallable with which to
+ invoke the RPC method; if the requested attribute is the name of a
+ stream-unary RPC method, the value of the attribute will be a
+ StreamUnaryMultiCallable with which to invoke the RPC method; and if the
+ requested attribute is the name of a stream-stream RPC method, the value of
+ the attribute will be a StreamStreamMultiCallable with which to invoke the
+ RPC method.
+ """
+ __metaclass__ = abc.ABCMeta
diff --git a/src/python/grpcio/grpc/framework/face/utilities.py b/src/python/grpcio/grpc/framework/face/utilities.py
new file mode 100644
index 0000000000..a63fe8c60d
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/face/utilities.py
@@ -0,0 +1,177 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Utilities for RPC framework's face layer."""
+
+import collections
+
+from grpc.framework.common import cardinality
+from grpc.framework.common import style
+from grpc.framework.face import interfaces
+from grpc.framework.foundation import stream
+
+
+class _MethodImplementation(
+ interfaces.MethodImplementation,
+ collections.namedtuple(
+ '_MethodImplementation',
+ ['cardinality', 'style', 'unary_unary_inline', 'unary_stream_inline',
+ 'stream_unary_inline', 'stream_stream_inline', 'unary_unary_event',
+ 'unary_stream_event', 'stream_unary_event', 'stream_stream_event',])):
+ pass
+
+
+def unary_unary_inline(behavior):
+ """Creates an interfaces.MethodImplementation for the given behavior.
+
+ Args:
+ behavior: The implementation of a unary-unary RPC method as a callable value
+ that takes a request value and an interfaces.RpcContext object and
+ returns a response value.
+
+ Returns:
+ An interfaces.MethodImplementation derived from the given behavior.
+ """
+ return _MethodImplementation(
+ cardinality.Cardinality.UNARY_UNARY, style.Service.INLINE, behavior,
+ None, None, None, None, None, None, None)
+
+
+def unary_stream_inline(behavior):
+ """Creates an interfaces.MethodImplementation for the given behavior.
+
+ Args:
+ behavior: The implementation of a unary-stream RPC method as a callable
+ value that takes a request value and an interfaces.RpcContext object and
+ returns an iterator of response values.
+
+ Returns:
+ An interfaces.MethodImplementation derived from the given behavior.
+ """
+ return _MethodImplementation(
+ cardinality.Cardinality.UNARY_STREAM, style.Service.INLINE, None,
+ behavior, None, None, None, None, None, None)
+
+
+def stream_unary_inline(behavior):
+ """Creates an interfaces.MethodImplementation for the given behavior.
+
+ Args:
+ behavior: The implementation of a stream-unary RPC method as a callable
+ value that takes an iterator of request values and an
+ interfaces.RpcContext object and returns a response value.
+
+ Returns:
+ An interfaces.MethodImplementation derived from the given behavior.
+ """
+ return _MethodImplementation(
+ cardinality.Cardinality.STREAM_UNARY, style.Service.INLINE, None, None,
+ behavior, None, None, None, None, None)
+
+
+def stream_stream_inline(behavior):
+ """Creates an interfaces.MethodImplementation for the given behavior.
+
+ Args:
+ behavior: The implementation of a stream-stream RPC method as a callable
+ value that takes an iterator of request values and an
+ interfaces.RpcContext object and returns an iterator of response values.
+
+ Returns:
+ An interfaces.MethodImplementation derived from the given behavior.
+ """
+ return _MethodImplementation(
+ cardinality.Cardinality.STREAM_STREAM, style.Service.INLINE, None, None,
+ None, behavior, None, None, None, None)
+
+
+def unary_unary_event(behavior):
+ """Creates an interfaces.MethodImplementation for the given behavior.
+
+ Args:
+ behavior: The implementation of a unary-unary RPC method as a callable
+ value that takes a request value, a response callback to which to pass
+ the response value of the RPC, and an interfaces.RpcContext.
+
+ Returns:
+ An interfaces.MethodImplementation derived from the given behavior.
+ """
+ return _MethodImplementation(
+ cardinality.Cardinality.UNARY_UNARY, style.Service.EVENT, None, None,
+ None, None, behavior, None, None, None)
+
+
+def unary_stream_event(behavior):
+ """Creates an interfaces.MethodImplementation for the given behavior.
+
+ Args:
+ behavior: The implementation of a unary-stream RPC method as a callable
+ value that takes a request value, a stream.Consumer to which to pass the
+ the response values of the RPC, and an interfaces.RpcContext.
+
+ Returns:
+ An interfaces.MethodImplementation derived from the given behavior.
+ """
+ return _MethodImplementation(
+ cardinality.Cardinality.UNARY_STREAM, style.Service.EVENT, None, None,
+ None, None, None, behavior, None, None)
+
+
+def stream_unary_event(behavior):
+ """Creates an interfaces.MethodImplementation for the given behavior.
+
+ Args:
+ behavior: The implementation of a stream-unary RPC method as a callable
+ value that takes a response callback to which to pass the response value
+ of the RPC and an interfaces.RpcContext and returns a stream.Consumer to
+ which the request values of the RPC should be passed.
+
+ Returns:
+ An interfaces.MethodImplementation derived from the given behavior.
+ """
+ return _MethodImplementation(
+ cardinality.Cardinality.STREAM_UNARY, style.Service.EVENT, None, None,
+ None, None, None, None, behavior, None)
+
+
+def stream_stream_event(behavior):
+ """Creates an interfaces.MethodImplementation for the given behavior.
+
+ Args:
+ behavior: The implementation of a stream-stream RPC method as a callable
+ value that takes a stream.Consumer to which to pass the response values
+ of the RPC and an interfaces.RpcContext and returns a stream.Consumer to
+ which the request values of the RPC should be passed.
+
+ Returns:
+ An interfaces.MethodImplementation derived from the given behavior.
+ """
+ return _MethodImplementation(
+ cardinality.Cardinality.STREAM_STREAM, style.Service.EVENT, None, None,
+ None, None, None, None, None, behavior)
diff --git a/src/python/grpcio/grpc/framework/foundation/__init__.py b/src/python/grpcio/grpc/framework/foundation/__init__.py
new file mode 100644
index 0000000000..7086519106
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/foundation/__init__.py
@@ -0,0 +1,30 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
diff --git a/src/python/grpcio/grpc/framework/foundation/_timer_future.py b/src/python/grpcio/grpc/framework/foundation/_timer_future.py
new file mode 100644
index 0000000000..2c9996aa9d
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/foundation/_timer_future.py
@@ -0,0 +1,228 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Affords a Future implementation based on Python's threading.Timer."""
+
+import sys
+import threading
+import time
+
+from grpc.framework.foundation import future
+
+
+class TimerFuture(future.Future):
+ """A Future implementation based around Timer objects."""
+
+ def __init__(self, compute_time, computation):
+ """Constructor.
+
+ Args:
+ compute_time: The time after which to begin this future's computation.
+ computation: The computation to be performed within this Future.
+ """
+ self._lock = threading.Lock()
+ self._compute_time = compute_time
+ self._computation = computation
+ self._timer = None
+ self._computing = False
+ self._computed = False
+ self._cancelled = False
+ self._result = None
+ self._exception = None
+ self._traceback = None
+ self._waiting = []
+
+ def _compute(self):
+ """Performs the computation embedded in this Future.
+
+ Or doesn't, if the time to perform it has not yet arrived.
+ """
+ with self._lock:
+ time_remaining = self._compute_time - time.time()
+ if 0 < time_remaining:
+ self._timer = threading.Timer(time_remaining, self._compute)
+ self._timer.start()
+ return
+ else:
+ self._computing = True
+
+ try:
+ return_value = self._computation()
+ exception = None
+ traceback = None
+ except Exception as e: # pylint: disable=broad-except
+ return_value = None
+ exception = e
+ traceback = sys.exc_info()[2]
+
+ with self._lock:
+ self._computing = False
+ self._computed = True
+ self._return_value = return_value
+ self._exception = exception
+ self._traceback = traceback
+ waiting = self._waiting
+
+ for callback in waiting:
+ callback(self)
+
+ def start(self):
+ """Starts this Future.
+
+ This must be called exactly once, immediately after construction.
+ """
+ with self._lock:
+ self._timer = threading.Timer(
+ self._compute_time - time.time(), self._compute)
+ self._timer.start()
+
+ def cancel(self):
+ """See future.Future.cancel for specification."""
+ with self._lock:
+ if self._computing or self._computed:
+ return False
+ elif self._cancelled:
+ return True
+ else:
+ self._timer.cancel()
+ self._cancelled = True
+ waiting = self._waiting
+
+ for callback in waiting:
+ try:
+ callback(self)
+ except Exception: # pylint: disable=broad-except
+ pass
+
+ return True
+
+ def cancelled(self):
+ """See future.Future.cancelled for specification."""
+ with self._lock:
+ return self._cancelled
+
+ def running(self):
+ """See future.Future.running for specification."""
+ with self._lock:
+ return not self._computed and not self._cancelled
+
+ def done(self):
+ """See future.Future.done for specification."""
+ with self._lock:
+ return self._computed or self._cancelled
+
+ def result(self, timeout=None):
+ """See future.Future.result for specification."""
+ with self._lock:
+ if self._cancelled:
+ raise future.CancelledError()
+ elif self._computed:
+ if self._exception is None:
+ return self._return_value
+ else:
+ raise self._exception # pylint: disable=raising-bad-type
+
+ condition = threading.Condition()
+ def notify_condition(unused_future):
+ with condition:
+ condition.notify()
+ self._waiting.append(notify_condition)
+
+ with condition:
+ condition.wait(timeout=timeout)
+
+ with self._lock:
+ if self._cancelled:
+ raise future.CancelledError()
+ elif self._computed:
+ if self._exception is None:
+ return self._return_value
+ else:
+ raise self._exception # pylint: disable=raising-bad-type
+ else:
+ raise future.TimeoutError()
+
+ def exception(self, timeout=None):
+ """See future.Future.exception for specification."""
+ with self._lock:
+ if self._cancelled:
+ raise future.CancelledError()
+ elif self._computed:
+ return self._exception
+
+ condition = threading.Condition()
+ def notify_condition(unused_future):
+ with condition:
+ condition.notify()
+ self._waiting.append(notify_condition)
+
+ with condition:
+ condition.wait(timeout=timeout)
+
+ with self._lock:
+ if self._cancelled:
+ raise future.CancelledError()
+ elif self._computed:
+ return self._exception
+ else:
+ raise future.TimeoutError()
+
+ def traceback(self, timeout=None):
+ """See future.Future.traceback for specification."""
+ with self._lock:
+ if self._cancelled:
+ raise future.CancelledError()
+ elif self._computed:
+ return self._traceback
+
+ condition = threading.Condition()
+ def notify_condition(unused_future):
+ with condition:
+ condition.notify()
+ self._waiting.append(notify_condition)
+
+ with condition:
+ condition.wait(timeout=timeout)
+
+ with self._lock:
+ if self._cancelled:
+ raise future.CancelledError()
+ elif self._computed:
+ return self._traceback
+ else:
+ raise future.TimeoutError()
+
+ def add_done_callback(self, fn):
+ """See future.Future.add_done_callback for specification."""
+ with self._lock:
+ if not self._computed and not self._cancelled:
+ self._waiting.append(fn)
+ return
+
+ fn(self)
diff --git a/src/python/grpcio/grpc/framework/foundation/abandonment.py b/src/python/grpcio/grpc/framework/foundation/abandonment.py
new file mode 100644
index 0000000000..960b4d06b4
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/foundation/abandonment.py
@@ -0,0 +1,38 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Utilities for indicating abandonment of computation."""
+
+
+class Abandoned(Exception):
+ """Indicates that some computation is being abandoned.
+
+ Abandoning a computation is different than returning a value or raising
+ an exception indicating some operational or programming defect.
+ """
diff --git a/src/python/grpcio/grpc/framework/foundation/activated.py b/src/python/grpcio/grpc/framework/foundation/activated.py
new file mode 100644
index 0000000000..426a71c705
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/foundation/activated.py
@@ -0,0 +1,65 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Interfaces related to streams of values or objects."""
+
+import abc
+
+
+class Activated(object):
+ """Interface for objects that may be started and stopped.
+
+ Values implementing this type must also implement the context manager
+ protocol.
+ """
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def __enter__(self):
+ """See the context manager protocol for specification."""
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ """See the context manager protocol for specification."""
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def start(self):
+ """Activates this object.
+
+ Returns:
+ A value equal to the value returned by this object's __enter__ method.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def stop(self):
+ """Deactivates this object."""
+ raise NotImplementedError()
diff --git a/src/python/grpcio/grpc/framework/foundation/callable_util.py b/src/python/grpcio/grpc/framework/foundation/callable_util.py
new file mode 100644
index 0000000000..32b0751a01
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/foundation/callable_util.py
@@ -0,0 +1,107 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Utilities for working with callables."""
+
+import abc
+import collections
+import enum
+import functools
+import logging
+
+
+class Outcome(object):
+ """A sum type describing the outcome of some call.
+
+ Attributes:
+ kind: One of Kind.RETURNED or Kind.RAISED respectively indicating that the
+ call returned a value or raised an exception.
+ return_value: The value returned by the call. Must be present if kind is
+ Kind.RETURNED.
+ exception: The exception raised by the call. Must be present if kind is
+ Kind.RAISED.
+ """
+ __metaclass__ = abc.ABCMeta
+
+ @enum.unique
+ class Kind(enum.Enum):
+ """Identifies the general kind of the outcome of some call."""
+
+ RETURNED = object()
+ RAISED = object()
+
+
+class _EasyOutcome(
+ collections.namedtuple(
+ '_EasyOutcome', ['kind', 'return_value', 'exception']),
+ Outcome):
+ """A trivial implementation of Outcome."""
+
+
+def _call_logging_exceptions(behavior, message, *args, **kwargs):
+ try:
+ return _EasyOutcome(Outcome.Kind.RETURNED, behavior(*args, **kwargs), None)
+ except Exception as e: # pylint: disable=broad-except
+ logging.exception(message)
+ return _EasyOutcome(Outcome.Kind.RAISED, None, e)
+
+
+def with_exceptions_logged(behavior, message):
+ """Wraps a callable in a try-except that logs any exceptions it raises.
+
+ Args:
+ behavior: Any callable.
+ message: A string to log if the behavior raises an exception.
+
+ Returns:
+ A callable that when executed invokes the given behavior. The returned
+ callable takes the same arguments as the given behavior but returns a
+ future.Outcome describing whether the given behavior returned a value or
+ raised an exception.
+ """
+ @functools.wraps(behavior)
+ def wrapped_behavior(*args, **kwargs):
+ return _call_logging_exceptions(behavior, message, *args, **kwargs)
+ return wrapped_behavior
+
+
+def call_logging_exceptions(behavior, message, *args, **kwargs):
+ """Calls a behavior in a try-except that logs any exceptions it raises.
+
+ Args:
+ behavior: Any callable.
+ message: A string to log if the behavior raises an exception.
+ *args: Positional arguments to pass to the given behavior.
+ **kwargs: Keyword arguments to pass to the given behavior.
+
+ Returns:
+ An Outcome describing whether the given behavior returned a value or raised
+ an exception.
+ """
+ return _call_logging_exceptions(behavior, message, *args, **kwargs)
diff --git a/src/python/grpcio/grpc/framework/foundation/future.py b/src/python/grpcio/grpc/framework/foundation/future.py
new file mode 100644
index 0000000000..bfc16fc1ea
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/foundation/future.py
@@ -0,0 +1,236 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""A Future interface.
+
+Python doesn't have a Future interface in its standard library. In the absence
+of such a standard, three separate, incompatible implementations
+(concurrent.futures.Future, ndb.Future, and asyncio.Future) have appeared. This
+interface attempts to be as compatible as possible with
+concurrent.futures.Future. From ndb.Future it adopts a traceback-object accessor
+method.
+
+Unlike the concrete and implemented Future classes listed above, the Future
+class defined in this module is an entirely abstract interface that anyone may
+implement and use.
+
+The one known incompatibility between this interface and the interface of
+concurrent.futures.Future is that this interface defines its own CancelledError
+and TimeoutError exceptions rather than raising the implementation-private
+concurrent.futures._base.CancelledError and the
+built-in-but-only-in-3.3-and-later TimeoutError.
+"""
+
+import abc
+
+
+class TimeoutError(Exception):
+ """Indicates that a particular call timed out."""
+
+
+class CancelledError(Exception):
+ """Indicates that the computation underlying a Future was cancelled."""
+
+
+class Future(object):
+ """A representation of a computation in another control flow.
+
+ Computations represented by a Future may be yet to be begun, may be ongoing,
+ or may have already completed.
+ """
+ __metaclass__ = abc.ABCMeta
+
+ # NOTE(nathaniel): This isn't the return type that I would want to have if it
+ # were up to me. Were this interface being written from scratch, the return
+ # type of this method would probably be a sum type like:
+ #
+ # NOT_COMMENCED
+ # COMMENCED_AND_NOT_COMPLETED
+ # PARTIAL_RESULT<Partial_Result_Type>
+ # COMPLETED<Result_Type>
+ # UNCANCELLABLE
+ # NOT_IMMEDIATELY_DETERMINABLE
+ @abc.abstractmethod
+ def cancel(self):
+ """Attempts to cancel the computation.
+
+ This method does not block.
+
+ Returns:
+ True if the computation has not yet begun, will not be allowed to take
+ place, and determination of both was possible without blocking. False
+ under all other circumstances including but not limited to the
+ computation's already having begun, the computation's already having
+ finished, and the computation's having been scheduled for execution on a
+ remote system for which a determination of whether or not it commenced
+ before being cancelled cannot be made without blocking.
+ """
+ raise NotImplementedError()
+
+ # NOTE(nathaniel): Here too this isn't the return type that I'd want this
+ # method to have if it were up to me. I think I'd go with another sum type
+ # like:
+ #
+ # NOT_CANCELLED (this object's cancel method hasn't been called)
+ # NOT_COMMENCED
+ # COMMENCED_AND_NOT_COMPLETED
+ # PARTIAL_RESULT<Partial_Result_Type>
+ # COMPLETED<Result_Type>
+ # UNCANCELLABLE
+ # NOT_IMMEDIATELY_DETERMINABLE
+ #
+ # Notice how giving the cancel method the right semantics obviates most
+ # reasons for this method to exist.
+ @abc.abstractmethod
+ def cancelled(self):
+ """Describes whether the computation was cancelled.
+
+ This method does not block.
+
+ Returns:
+ True if the computation was cancelled any time before its result became
+ immediately available. False under all other circumstances including but
+ not limited to this object's cancel method not having been called and
+ the computation's result having become immediately available.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def running(self):
+ """Describes whether the computation is taking place.
+
+ This method does not block.
+
+ Returns:
+ True if the computation is scheduled to take place in the future or is
+ taking place now, or False if the computation took place in the past or
+ was cancelled.
+ """
+ raise NotImplementedError()
+
+ # NOTE(nathaniel): These aren't quite the semantics I'd like here either. I
+ # would rather this only returned True in cases in which the underlying
+ # computation completed successfully. A computation's having been cancelled
+ # conflicts with considering that computation "done".
+ @abc.abstractmethod
+ def done(self):
+ """Describes whether the computation has taken place.
+
+ This method does not block.
+
+ Returns:
+ True if the computation is known to have either completed or have been
+ unscheduled or interrupted. False if the computation may possibly be
+ executing or scheduled to execute later.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def result(self, timeout=None):
+ """Accesses the outcome of the computation or raises its exception.
+
+ This method may return immediately or may block.
+
+ Args:
+ timeout: The length of time in seconds to wait for the computation to
+ finish or be cancelled, or None if this method should block until the
+ computation has finished or is cancelled no matter how long that takes.
+
+ Returns:
+ The return value of the computation.
+
+ Raises:
+ TimeoutError: If a timeout value is passed and the computation does not
+ terminate within the allotted time.
+ CancelledError: If the computation was cancelled.
+ Exception: If the computation raised an exception, this call will raise
+ the same exception.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def exception(self, timeout=None):
+ """Return the exception raised by the computation.
+
+ This method may return immediately or may block.
+
+ Args:
+ timeout: The length of time in seconds to wait for the computation to
+ terminate or be cancelled, or None if this method should block until
+ the computation is terminated or is cancelled no matter how long that
+ takes.
+
+ Returns:
+ The exception raised by the computation, or None if the computation did
+ not raise an exception.
+
+ Raises:
+ TimeoutError: If a timeout value is passed and the computation does not
+ terminate within the allotted time.
+ CancelledError: If the computation was cancelled.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def traceback(self, timeout=None):
+ """Access the traceback of the exception raised by the computation.
+
+ This method may return immediately or may block.
+
+ Args:
+ timeout: The length of time in seconds to wait for the computation to
+ terminate or be cancelled, or None if this method should block until
+ the computation is terminated or is cancelled no matter how long that
+ takes.
+
+ Returns:
+ The traceback of the exception raised by the computation, or None if the
+ computation did not raise an exception.
+
+ Raises:
+ TimeoutError: If a timeout value is passed and the computation does not
+ terminate within the allotted time.
+ CancelledError: If the computation was cancelled.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def add_done_callback(self, fn):
+ """Adds a function to be called at completion of the computation.
+
+ The callback will be passed this Future object describing the outcome of
+ the computation.
+
+ If the computation has already completed, the callback will be called
+ immediately.
+
+ Args:
+ fn: A callable taking a this Future object as its single parameter.
+ """
+ raise NotImplementedError()
diff --git a/src/python/grpcio/grpc/framework/foundation/later.py b/src/python/grpcio/grpc/framework/foundation/later.py
new file mode 100644
index 0000000000..1d1e065041
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/foundation/later.py
@@ -0,0 +1,51 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Enables scheduling execution at a later time."""
+
+import time
+
+from grpc.framework.foundation import _timer_future
+
+
+def later(delay, computation):
+ """Schedules later execution of a callable.
+
+ Args:
+ delay: Any numeric value. Represents the minimum length of time in seconds
+ to allow to pass before beginning the computation. No guarantees are made
+ about the maximum length of time that will pass.
+ computation: A callable that accepts no arguments.
+
+ Returns:
+ A Future representing the scheduled computation.
+ """
+ timer_future = _timer_future.TimerFuture(time.time() + delay, computation)
+ timer_future.start()
+ return timer_future
diff --git a/src/python/grpcio/grpc/framework/foundation/logging_pool.py b/src/python/grpcio/grpc/framework/foundation/logging_pool.py
new file mode 100644
index 0000000000..7c7a6eebfc
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/foundation/logging_pool.py
@@ -0,0 +1,83 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""A thread pool that logs exceptions raised by tasks executed within it."""
+
+import functools
+import logging
+
+from concurrent import futures
+
+
+def _wrap(behavior):
+ """Wraps an arbitrary callable behavior in exception-logging."""
+ @functools.wraps(behavior)
+ def _wrapping(*args, **kwargs):
+ try:
+ return behavior(*args, **kwargs)
+ except Exception as e:
+ logging.exception('Unexpected exception from task run in logging pool!')
+ raise
+ return _wrapping
+
+
+class _LoggingPool(object):
+ """An exception-logging futures.ThreadPoolExecutor-compatible thread pool."""
+
+ def __init__(self, backing_pool):
+ self._backing_pool = backing_pool
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ self._backing_pool.shutdown(wait=True)
+
+ def submit(self, fn, *args, **kwargs):
+ return self._backing_pool.submit(_wrap(fn), *args, **kwargs)
+
+ def map(self, func, *iterables, **kwargs):
+ return self._backing_pool.map(
+ _wrap(func), *iterables, timeout=kwargs.get('timeout', None))
+
+ def shutdown(self, wait=True):
+ self._backing_pool.shutdown(wait=wait)
+
+
+def pool(max_workers):
+ """Creates a thread pool that logs exceptions raised by the tasks within it.
+
+ Args:
+ max_workers: The maximum number of worker threads to allow the pool.
+
+ Returns:
+ A futures.ThreadPoolExecutor-compatible thread pool that logs exceptions
+ raised by the tasks executed within it.
+ """
+ return _LoggingPool(futures.ThreadPoolExecutor(max_workers))
diff --git a/src/python/grpcio/grpc/framework/foundation/relay.py b/src/python/grpcio/grpc/framework/foundation/relay.py
new file mode 100644
index 0000000000..9c23946552
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/foundation/relay.py
@@ -0,0 +1,175 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Implementations of in-order work deference."""
+
+import abc
+import enum
+import threading
+
+from grpc.framework.foundation import activated
+from grpc.framework.foundation import logging_pool
+
+_NULL_BEHAVIOR = lambda unused_value: None
+
+
+class Relay(object):
+ """Performs work submitted to it in another thread.
+
+ Performs work in the order in which work was submitted to it; otherwise there
+ would be no reason to use an implementation of this interface instead of a
+ thread pool.
+ """
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def add_value(self, value):
+ """Adds a value to be passed to the behavior registered with this Relay.
+
+ Args:
+ value: A value that will be passed to a call made in another thread to the
+ behavior registered with this Relay.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def set_behavior(self, behavior):
+ """Sets the behavior that this Relay should call when passed values.
+
+ Args:
+ behavior: The behavior that this Relay should call in another thread when
+ passed a value, or None to have passed values ignored.
+ """
+ raise NotImplementedError()
+
+
+class _PoolRelay(activated.Activated, Relay):
+
+ @enum.unique
+ class _State(enum.Enum):
+ INACTIVE = 'inactive'
+ IDLE = 'idle'
+ SPINNING = 'spinning'
+
+ def __init__(self, pool, behavior):
+ self._condition = threading.Condition()
+ self._pool = pool
+ self._own_pool = pool is None
+ self._state = _PoolRelay._State.INACTIVE
+ self._activated = False
+ self._spinning = False
+ self._values = []
+ self._behavior = _NULL_BEHAVIOR if behavior is None else behavior
+
+ def _spin(self, behavior, value):
+ while True:
+ behavior(value)
+ with self._condition:
+ if self._values:
+ value = self._values.pop(0)
+ behavior = self._behavior
+ else:
+ self._state = _PoolRelay._State.IDLE
+ self._condition.notify_all()
+ break
+
+ def add_value(self, value):
+ with self._condition:
+ if self._state is _PoolRelay._State.INACTIVE:
+ raise ValueError('add_value not valid on inactive Relay!')
+ elif self._state is _PoolRelay._State.IDLE:
+ self._pool.submit(self._spin, self._behavior, value)
+ self._state = _PoolRelay._State.SPINNING
+ else:
+ self._values.append(value)
+
+ def set_behavior(self, behavior):
+ with self._condition:
+ self._behavior = _NULL_BEHAVIOR if behavior is None else behavior
+
+ def _start(self):
+ with self._condition:
+ self._state = _PoolRelay._State.IDLE
+ if self._own_pool:
+ self._pool = logging_pool.pool(1)
+ return self
+
+ def _stop(self):
+ with self._condition:
+ while self._state is _PoolRelay._State.SPINNING:
+ self._condition.wait()
+ if self._own_pool:
+ self._pool.shutdown(wait=True)
+ self._state = _PoolRelay._State.INACTIVE
+
+ def __enter__(self):
+ return self._start()
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ self._stop()
+ return False
+
+ def start(self):
+ return self._start()
+
+ def stop(self):
+ self._stop()
+
+
+def relay(behavior):
+ """Creates a Relay.
+
+ Args:
+ behavior: The behavior to be called by the created Relay, or None to have
+ passed values dropped until a different behavior is given to the returned
+ Relay later.
+
+ Returns:
+ An object that is both an activated.Activated and a Relay. The object is
+ only valid for use as a Relay when activated.
+ """
+ return _PoolRelay(None, behavior)
+
+
+def pool_relay(pool, behavior):
+ """Creates a Relay that uses a given thread pool.
+
+ This object will make use of at most one thread in the given pool.
+
+ Args:
+ pool: A futures.ThreadPoolExecutor for use by the created Relay.
+ behavior: The behavior to be called by the created Relay, or None to have
+ passed values dropped until a different behavior is given to the returned
+ Relay later.
+
+ Returns:
+ An object that is both an activated.Activated and a Relay. The object is
+ only valid for use as a Relay when activated.
+ """
+ return _PoolRelay(pool, behavior)
diff --git a/src/python/grpcio/grpc/framework/foundation/stream.py b/src/python/grpcio/grpc/framework/foundation/stream.py
new file mode 100644
index 0000000000..75c0cf145b
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/foundation/stream.py
@@ -0,0 +1,60 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Interfaces related to streams of values or objects."""
+
+import abc
+
+
+class Consumer(object):
+ """Interface for consumers of finite streams of values or objects."""
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def consume(self, value):
+ """Accepts a value.
+
+ Args:
+ value: Any value accepted by this Consumer.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def terminate(self):
+ """Indicates to this Consumer that no more values will be supplied."""
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def consume_and_terminate(self, value):
+ """Supplies a value and signals that no more values will be supplied.
+
+ Args:
+ value: Any value accepted by this Consumer.
+ """
+ raise NotImplementedError()
diff --git a/src/python/grpcio/grpc/framework/foundation/stream_util.py b/src/python/grpcio/grpc/framework/foundation/stream_util.py
new file mode 100644
index 0000000000..2210e4efcf
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/foundation/stream_util.py
@@ -0,0 +1,160 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Helpful utilities related to the stream module."""
+
+import logging
+import threading
+
+from grpc.framework.foundation import stream
+
+_NO_VALUE = object()
+
+
+class TransformingConsumer(stream.Consumer):
+ """A stream.Consumer that passes a transformation of its input to another."""
+
+ def __init__(self, transformation, downstream):
+ self._transformation = transformation
+ self._downstream = downstream
+
+ def consume(self, value):
+ self._downstream.consume(self._transformation(value))
+
+ def terminate(self):
+ self._downstream.terminate()
+
+ def consume_and_terminate(self, value):
+ self._downstream.consume_and_terminate(self._transformation(value))
+
+
+class IterableConsumer(stream.Consumer):
+ """A Consumer that when iterated over emits the values it has consumed."""
+
+ def __init__(self):
+ self._condition = threading.Condition()
+ self._values = []
+ self._active = True
+
+ def consume(self, stock_reply):
+ with self._condition:
+ if self._active:
+ self._values.append(stock_reply)
+ self._condition.notify()
+
+ def terminate(self):
+ with self._condition:
+ self._active = False
+ self._condition.notify()
+
+ def consume_and_terminate(self, stock_reply):
+ with self._condition:
+ if self._active:
+ self._values.append(stock_reply)
+ self._active = False
+ self._condition.notify()
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ with self._condition:
+ while self._active and not self._values:
+ self._condition.wait()
+ if self._values:
+ return self._values.pop(0)
+ else:
+ raise StopIteration()
+
+
+class ThreadSwitchingConsumer(stream.Consumer):
+ """A Consumer decorator that affords serialization and asynchrony."""
+
+ def __init__(self, sink, pool):
+ self._lock = threading.Lock()
+ self._sink = sink
+ self._pool = pool
+ # True if self._spin has been submitted to the pool to be called once and
+ # that call has not yet returned, False otherwise.
+ self._spinning = False
+ self._values = []
+ self._active = True
+
+ def _spin(self, sink, value, terminate):
+ while True:
+ try:
+ if value is _NO_VALUE:
+ sink.terminate()
+ elif terminate:
+ sink.consume_and_terminate(value)
+ else:
+ sink.consume(value)
+ except Exception as e: # pylint:disable=broad-except
+ logging.exception(e)
+
+ with self._lock:
+ if terminate:
+ self._spinning = False
+ return
+ elif self._values:
+ value = self._values.pop(0)
+ terminate = not self._values and not self._active
+ elif not self._active:
+ value = _NO_VALUE
+ terminate = True
+ else:
+ self._spinning = False
+ return
+
+ def consume(self, value):
+ with self._lock:
+ if self._active:
+ if self._spinning:
+ self._values.append(value)
+ else:
+ self._pool.submit(self._spin, self._sink, value, False)
+ self._spinning = True
+
+ def terminate(self):
+ with self._lock:
+ if self._active:
+ self._active = False
+ if not self._spinning:
+ self._pool.submit(self._spin, self._sink, _NO_VALUE, True)
+ self._spinning = True
+
+ def consume_and_terminate(self, value):
+ with self._lock:
+ if self._active:
+ self._active = False
+ if self._spinning:
+ self._values.append(value)
+ else:
+ self._pool.submit(self._spin, self._sink, value, True)
+ self._spinning = True
diff --git a/src/python/grpcio/grpc/framework/interfaces/__init__.py b/src/python/grpcio/grpc/framework/interfaces/__init__.py
new file mode 100644
index 0000000000..7086519106
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/interfaces/__init__.py
@@ -0,0 +1,30 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
diff --git a/src/python/grpcio/grpc/framework/interfaces/links/__init__.py b/src/python/grpcio/grpc/framework/interfaces/links/__init__.py
new file mode 100644
index 0000000000..7086519106
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/interfaces/links/__init__.py
@@ -0,0 +1,30 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
diff --git a/src/python/grpcio/grpc/framework/interfaces/links/links.py b/src/python/grpcio/grpc/framework/interfaces/links/links.py
new file mode 100644
index 0000000000..5ebbac8a6f
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/interfaces/links/links.py
@@ -0,0 +1,124 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""The low-level ticket-exchanging-links interface of RPC Framework."""
+
+import abc
+import collections
+import enum
+
+
+class Ticket(
+ collections.namedtuple(
+ 'Ticket',
+ ['operation_id', 'sequence_number', 'group', 'method', 'subscription',
+ 'timeout', 'allowance', 'initial_metadata', 'payload',
+ 'terminal_metadata', 'code', 'message', 'termination'])):
+ """A sum type for all values sent from a front to a back.
+
+ Attributes:
+ operation_id: A unique-with-respect-to-equality hashable object identifying
+ a particular operation.
+ sequence_number: A zero-indexed integer sequence number identifying the
+ ticket's place in the stream of tickets sent in one direction for the
+ particular operation.
+ group: The group to which the method of the operation belongs. Must be
+ present in the first ticket from invocation side to service side. Ignored
+ for all other tickets exchanged during the operation.
+ method: The name of an operation. Must be present in the first ticket from
+ invocation side to service side. Ignored for all other tickets exchanged
+ during the operation.
+ subscription: A Subscription value describing the interest one side has in
+ receiving information from the other side. Must be present in the first
+ ticket from either side. Ignored for all other tickets exchanged during
+ the operation.
+ timeout: A nonzero length of time (measured from the beginning of the
+ operation) to allow for the entire operation. Must be present in the first
+ ticket from invocation side to service side. Optional for all other
+ tickets exchanged during the operation. Receipt of a value from the other
+ side of the operation indicates the value in use by that side. Setting a
+ value on a later ticket allows either side to request time extensions (or
+ even time reductions!) on in-progress operations.
+ allowance: A positive integer granting permission for a number of payloads
+ to be transmitted to the communicating side of the operation, or None if
+ no additional allowance is being granted with this ticket.
+ initial_metadata: An optional metadata value communicated from one side to
+ the other at the beginning of the operation. May be non-None in at most
+ one ticket from each side. Any non-None value must appear no later than
+ the first payload value.
+ payload: A customer payload object. May be None.
+ terminal_metadata: A metadata value comminicated from one side to the other
+ at the end of the operation. May be non-None in the same ticket as
+ the code and message, but must be None for all earlier tickets.
+ code: A value communicated at operation completion. May be None.
+ message: A value communicated at operation completion. May be None.
+ termination: A Termination value describing the end of the operation, or
+ None if the operation has not yet terminated. If set, no further tickets
+ may be sent in the same direction.
+ """
+
+ @enum.unique
+ class Subscription(enum.Enum):
+ """Identifies the level of subscription of a side of an operation."""
+
+ NONE = 'none'
+ TERMINATION = 'termination'
+ FULL = 'full'
+
+ @enum.unique
+ class Termination(enum.Enum):
+ """Identifies the termination of an operation."""
+
+ COMPLETION = 'completion'
+ CANCELLATION = 'cancellation'
+ EXPIRATION = 'expiration'
+ LOCAL_SHUTDOWN = 'local shutdown'
+ RECEPTION_FAILURE = 'reception failure'
+ TRANSMISSION_FAILURE = 'transmission failure'
+ LOCAL_FAILURE = 'local failure'
+ REMOTE_FAILURE = 'remote failure'
+
+
+class Link(object):
+ """Accepts and emits tickets."""
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def accept_ticket(self, ticket):
+ """Accept a Ticket.
+
+ Args:
+ ticket: Any Ticket.
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def join_link(self, link):
+ """Mates this object with a peer with which it will exchange tickets."""
+ raise NotImplementedError()
diff --git a/src/python/grpcio/grpc/framework/interfaces/links/utilities.py b/src/python/grpcio/grpc/framework/interfaces/links/utilities.py
new file mode 100644
index 0000000000..6e4fd76d93
--- /dev/null
+++ b/src/python/grpcio/grpc/framework/interfaces/links/utilities.py
@@ -0,0 +1,44 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Utilities provided as part of the links interface."""
+
+from grpc.framework.interfaces.links import links
+
+
+class _NullLink(links.Link):
+ """A do-nothing links.Link."""
+
+ def accept_ticket(self, ticket):
+ pass
+
+ def join_link(self, link):
+ pass
+
+NULL_LINK = _NullLink()
diff --git a/src/python/grpcio/requirements.txt b/src/python/grpcio/requirements.txt
new file mode 100644
index 0000000000..43395df03b
--- /dev/null
+++ b/src/python/grpcio/requirements.txt
@@ -0,0 +1,3 @@
+enum34==1.0.4
+futures==2.2.0
+protobuf==3.0.0a3
diff --git a/src/python/grpcio/setup.cfg b/src/python/grpcio/setup.cfg
new file mode 100644
index 0000000000..8f69613632
--- /dev/null
+++ b/src/python/grpcio/setup.cfg
@@ -0,0 +1,2 @@
+[build_ext]
+inplace=1
diff --git a/src/python/grpcio/setup.py b/src/python/grpcio/setup.py
new file mode 100644
index 0000000000..e408f2ace9
--- /dev/null
+++ b/src/python/grpcio/setup.py
@@ -0,0 +1,112 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""A setup module for the GRPC Python package."""
+
+import os
+import os.path
+import sys
+
+from distutils import core as _core
+import setuptools
+
+# Ensure we're in the proper directory whether or not we're being used by pip.
+os.chdir(os.path.dirname(os.path.abspath(__file__)))
+
+# Break import-style to ensure we can actually find our commands module.
+import commands
+
+# Use environment variables to determine whether or not the Cython extension
+# should *use* Cython or use the generated C files. Note that this requires the
+# C files to have been generated by building first *with* Cython support.
+_BUILD_WITH_CYTHON = os.environ.get('GRPC_PYTHON_BUILD_WITH_CYTHON', False)
+
+_C_EXTENSION_SOURCES = (
+ 'grpc/_adapter/_c/module.c',
+ 'grpc/_adapter/_c/types.c',
+ 'grpc/_adapter/_c/utility.c',
+ 'grpc/_adapter/_c/types/client_credentials.c',
+ 'grpc/_adapter/_c/types/server_credentials.c',
+ 'grpc/_adapter/_c/types/completion_queue.c',
+ 'grpc/_adapter/_c/types/call.c',
+ 'grpc/_adapter/_c/types/channel.c',
+ 'grpc/_adapter/_c/types/server.c',
+)
+
+_EXTENSION_INCLUDE_DIRECTORIES = (
+ '.',
+)
+
+_EXTENSION_LIBRARIES = (
+ 'grpc',
+ 'gpr',
+)
+if not "darwin" in sys.platform:
+ _EXTENSION_LIBRARIES += ('rt',)
+
+
+_C_EXTENSION_MODULE = _core.Extension(
+ 'grpc._adapter._c', sources=list(_C_EXTENSION_SOURCES),
+ include_dirs=list(_EXTENSION_INCLUDE_DIRECTORIES),
+ libraries=list(_EXTENSION_LIBRARIES),
+)
+_EXTENSION_MODULES = [_C_EXTENSION_MODULE]
+
+_PACKAGES = (
+ setuptools.find_packages('.', exclude=['*._cython', '*._cython.*'])
+)
+
+_PACKAGE_DIRECTORIES = {
+ '': '.',
+}
+
+_INSTALL_REQUIRES = (
+ 'enum34==1.0.4',
+ 'futures==2.2.0',
+ 'protobuf==3.0.0a3',
+)
+
+_SETUP_REQUIRES = (
+ 'sphinx>=1.3',
+) + _INSTALL_REQUIRES
+
+_COMMAND_CLASS = {
+ 'doc': commands.SphinxDocumentation,
+}
+
+setuptools.setup(
+ name='grpcio',
+ version='0.10.0a0',
+ ext_modules=_EXTENSION_MODULES,
+ packages=list(_PACKAGES),
+ package_dir=_PACKAGE_DIRECTORIES,
+ install_requires=_INSTALL_REQUIRES,
+ setup_requires=_SETUP_REQUIRES,
+ cmdclass=_COMMAND_CLASS
+)