{{!

  Copyright (c) Meta Platforms, Inc. and affiliates.

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.

}}{{!

Included from CythonServices.pyx, this file defines the core adapter between
the C++ service and the Python handler. The C++ classes defined in
ServicesWrapper.h and ServicesWrapper.cpp call into this file using a generated
header. It calls into the call_cy_.... functions, passing it a promise and
the arguments. That function wraps the promise and arguments in Python types
appropriately, then passes them into a coroutine defined in this same file.

That coroutine then looks up the handler coroutine, which does the appropriate
work and then returns a Python value. The coroutine accesses the C++ promise
from the Python wrapper of that promise that was passed into the coroutine and
sets the converted value on that promise.

}}

{{#program:services}}
{{#service:supportedFunctions}}
cdef api void call_cy_{{service:name}}_{{function:name}}(
    object self,
    Cpp2RequestContext* ctx,
    cFollyPromise[{{#function:return_type}}{{!
            }}{{> services/cython_return_type_cpp_type}}{{!
        }}{{/function:return_type}}]{{!
    }} cPromise{{#function:args}},
    {{#field:type}}{{!
            }}{{^function:stack_arguments?}}{{> services/cython_field_type}}{{/function:stack_arguments?}}{{!
            }}{{#function:stack_arguments?}}{{> types/cython_cpp_type}}{{/function:stack_arguments?}}{{!
        }}{{/field:type}} {{field:py_name}}{{/function:args}}
) noexcept:{{! Gets called from a python Executor }}
    {{#function:return_type}}
    cdef {{> services/promise_name}} __promise = {{> services/promise_name}}._fbthrift_create(cmove(cPromise))
    {{/function:return_type}}
    {{#function:args}}
    arg_{{field:py_name}} = {{#field:type}}{{!
            }}{{^function:stack_arguments?}}{{> services/cython_cpp_to_python}}{{/function:stack_arguments?}}{{!
            }}{{#function:stack_arguments?}}{{> types/cython_cpp_value_to_python}}{{/function:stack_arguments?}}{{!
        }}{{/field:type}}
    {{/function:args}}
    __context = RequestContext._fbthrift_create(ctx)
    __context_token = __THRIFT_REQUEST_CONTEXT.set(__context)
    asyncio.get_event_loop().create_task(
        {{service:name}}_{{function:name}}_coro(
            self,
            __promise{{#function:args}},
            arg_{{field:py_name}}{{/function:args}}
        )
    )
    __THRIFT_REQUEST_CONTEXT.reset(__context_token)
{{/service:supportedFunctions}}
{{#service:lifecycleFunctions}}
cdef api void call_cy_{{service:name}}_{{function:name}}(
    object self,
    cFollyPromise[{{#function:return_type}}{{!
            }}{{> services/cython_return_type_cpp_type}}{{!
        }}{{/function:return_type}}]{{!
    }} cPromise
) noexcept:
    {{#function:return_type}}
    cdef {{> services/promise_name}} __promise = {{> services/promise_name}}._fbthrift_create(cmove(cPromise))
    {{/function:return_type}}
    asyncio.get_event_loop().create_task(
        {{service:name}}_{{function:name}}_coro(
            self,
            __promise
        )
    )
{{/service:lifecycleFunctions}}
{{#service:supportedFunctionsWithLifecycle}}
async def {{service:name}}_{{function:name}}_coro(
    object self,
    {{#function:return_type}}{{> services/promise_name}}{{/function:return_type}}{{!
    }} promise{{#function:args}},
    {{field:py_name}}{{/function:args}}
):
    try:
        result = {{#function:return_type}}{{^function:stream?}}await {{/function:stream?}}{{/function:return_type}}self.{{function:name}}({{#function:args}}
                    {{field:py_name}}{{^last?}},{{/last?}}{{/function:args}})
        {{#function:return_type}}
        {{#type:container?}}
        result = {{> types/python_type}}(result)
        {{/type:container?}}
        {{#function:stream?}}
        {{#function:stream_has_first_response?}}
        result = await result
        item, result = result
        {{/function:stream_has_first_response?}}
        if not isinstance(result, (ServerStream, AsyncIterator)):
            result = await result
        if isinstance(result, AsyncIterator):
            {{#function:stream_elem_type}}
            result = ServerStream_{{> types/cython_cpp_type_ident}}._fbthrift_create(cmove(createAsyncIteratorFromPyIterator[{{ >types/cython_cpp_type}}](result, get_executor(), &getNextGenerator_{{service:name}}_{{function:name}})))
            {{/function:stream_elem_type}}
        {{/function:stream?}}
        {{/function:return_type}}
    {{#function:exceptions}}
    {{#field:type}}
    except {{> types/cython_python_type}} as ex:
        promise.cPromise.setException(deref((<{{> types/cython_python_type}}> ex).{{> types/cpp_obj}}))
    {{/field:type}}
    {{/function:exceptions}}
    except __ApplicationError as ex:
        # If the handler raised an ApplicationError convert it to a C++ one
        promise.cPromise.setException(cTApplicationException(
            ex.type.value, ex.message.encode('UTF-8')
        ))
    except Exception as ex:
        print(
            "Unexpected error in service handler {{service:name}}.{{function:name}}:",
            file=sys.stderr)
        traceback.print_exc()
        promise.cPromise.setException(cTApplicationException(
            cTApplicationExceptionType__UNKNOWN, repr(ex).encode('UTF-8')
        ))
    except asyncio.CancelledError as ex:
        print("Coroutine was cancelled in service handler {{service:name}}.{{function:name}}:", file=sys.stderr)
        traceback.print_exc()
        promise.cPromise.setException(cTApplicationException(
            cTApplicationExceptionType__UNKNOWN, (f'Application was cancelled on the server with message: {str(ex)}').encode('UTF-8')
        ))
    else:
        promise.cPromise.setValue({{#function:return_type}}{{> services/cython_return_value}}{{/function:return_type}})

{{/service:supportedFunctionsWithLifecycle}}
{{/program:services}}
