/* * 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. */ #pragma once #include #include #include #include #include #include #include #include namespace apache { namespace thrift { namespace python { namespace capi { // Using an ExtractorResult lets us accommodate two different error handling // strategies: // 1. If an error has occurred, avoid making further capi calls // and throw C++ exception at C++ entrypoint // 2. If an error has occurred, avoid making further capi calls and let // cython raise python error at cython-binding return to python. // The unexpected std::string_view arms lets us capture the C++ file and line // to augment the python error raised by capi. template using ExtractorResult = folly::Expected, std::string_view>; template ExtractorResult extractorError(std::string_view msg) { return folly::makeUnexpected(msg); } #define EXTRACTOR_ERROR(TYPE, MESSAGE) \ extractorError(CAPI_LOCATED_ERROR(MESSAGE)) template ExtractorResult extractorSuccess(T&& val) { static_assert(std::is_same_v>); return folly::makeExpected(std::forward(val)); } template struct Extractor; /* * Base class provides a conditional extract(PyObject*, bool&). The bool * represents whether any python error has occurred. The base extractor * calls the derived Extractor operator (PyObject*) if no error has * occurred; otherwise it returns Unexpected variant to avoid cpython api * calls that are undefined behavior. */ template struct BaseExtractor { ExtractorResult extract(PyObject* obj, bool& extractError) { if (extractError) { return EXTRACTOR_ERROR(T, "extractError already set"); } auto res = (*static_cast*>(this))(obj); extractError = res.hasError(); return res; } template std::enable_if_t< !::apache::thrift::detail::is_shared_or_unique_ptr_v> extractInto( FieldRef ref, PyObject* obj, std::optional& error) { if (error) { return; } if constexpr ( is_optional_maybe_boxed_field_ref_v || is_union_field_ref{}) { if (obj == Py_None) { return; } } auto extractResult = (*static_cast*>(this))(obj); if (extractResult.hasError()) { error = extractResult.error(); } else { ref.emplace(std::move(*extractResult)); } } template void extractInto( std::unique_ptr& ref, /* RefType.Unique */ PyObject* obj, std::optional& error) { // Ref may be optional so always check if None if (error || obj == Py_None) { return; } auto extractResult = (*static_cast*>(this))(obj); if (extractResult.hasError()) { error = extractResult.error(); } else { ref = std::make_unique(std::move(*extractResult)); } } template void extractInto( std::shared_ptr& ref, /* RefType.Shared, RefType.SharedMutable */ PyObject* obj, std::optional& error) { // Ref may be optional so always check if None if (error || obj == Py_None) { return; } auto extractResult = (*static_cast*>(this))(obj); if (extractResult.hasError()) { error = extractResult.error(); } else { ref = std::make_shared(std::move(*extractResult)); } } }; /** * Series of functions to convert a Python object into a native type T. * * Some important notes: * 1. The Extractor borrows a reference to `obj`: It's intended to be used as a * temporary. * 2: The extractor will leave the python indicator set if a python * exception is encountered during the extraction. Be sure to check the error * indicator before calling back into python! */ template struct Extractor : public BaseExtractor { template struct Unspecialized : std::false_type {}; static_assert(Unspecialized{}, "No extractor defined"); ExtractorResult operator()(/* Source */ PyObject* obj); int typeCheck(PyObject* obj); /* returns 0/1 bool or -1 on error */ }; // Concrete type specializations #define SPECIALIZE_SCALAR(type) \ template <> \ struct Extractor : public BaseExtractor { \ ExtractorResult operator()(PyObject* obj); \ int typeCheck(PyObject* obj); \ } SPECIALIZE_SCALAR(signed char); SPECIALIZE_SCALAR(signed short); SPECIALIZE_SCALAR(signed int); SPECIALIZE_SCALAR(signed long); SPECIALIZE_SCALAR(signed long long); SPECIALIZE_SCALAR(unsigned char); SPECIALIZE_SCALAR(unsigned short); SPECIALIZE_SCALAR(unsigned int); SPECIALIZE_SCALAR(unsigned long); SPECIALIZE_SCALAR(unsigned long long); SPECIALIZE_SCALAR(bool); SPECIALIZE_SCALAR(float); SPECIALIZE_SCALAR(double); SPECIALIZE_SCALAR(Bytes); SPECIALIZE_SCALAR(String); SPECIALIZE_SCALAR(BytesView); SPECIALIZE_SCALAR(StringView); SPECIALIZE_SCALAR(std::unique_ptr); SPECIALIZE_SCALAR(folly::IOBuf); #undef SPECIALIZE_SCALAR template struct Extractor> : public BaseExtractor> { ExtractorResult operator()(PyObject* obj) { auto result = Extractor{}(obj); if (result.hasError()) { return extractorError(result.error()); } return Adapter::fromThrift(std::move(*result)); } }; template struct Extractor> : public BaseExtractor> { ExtractorResult operator()(PyObject* obj) { auto fbthrift_value = Extractor{}(obj); if (fbthrift_value.hasError()) { return extractorError(fbthrift_value.error()); } return static_cast(*fbthrift_value); } int typeCheck(PyObject* obj) { return Extractor::typeCheck(obj); } }; template using reserve_method_t = decltype(std::declval().reserve(std::declval())); template constexpr bool has_reserve_v = folly::is_detected_v>; template struct Extractor> : public BaseExtractor> { using list_t = list; ExtractorResult operator()(PyObject* obj) { if constexpr (folly::kIsDebug) { if (PyTuple_Check(obj) != 1) { PyErr_SetString(PyExc_TypeError, CAPI_LOCATED_ERROR("Not a tuple")); return EXTRACTOR_ERROR(list_t, "Not a tuple"); } } CppT ret; Py_ssize_t size = PyTuple_GET_SIZE(obj); if (size == 0) { return ret; } if constexpr (has_reserve_v) { ret.reserve(size); } Extractor extractor; for (Py_ssize_t i = 0; i < size; ++i) { // Note PyTuple_GET_ITEM returns a borrowed reference auto extracted = extractor(PyTuple_GET_ITEM(obj, i)); if (!extracted.hasValue()) { // Extraction failed. Abort return extractorError(extracted.error()); } ret.push_back(std::move(*extracted)); } return ret; } int typeCheck(PyObject* obj) { if (PyTuple_Check(obj) != 1) { return 0; } const Py_ssize_t size = PyTuple_GET_SIZE(obj); // Just check the first element given type checking inherent in // thrift-python struct construction; thrift consts if (size == 0) { return 1; } PyObject* front_obj = PyTuple_GET_ITEM(obj, 0); if (!front_obj) { return 0; } return Extractor{}.typeCheck(front_obj); } }; template struct Extractor> : public BaseExtractor> { using set_t = set; ExtractorResult operator()(PyObject* obj) { StrongRef iter(PyObject_GetIter(obj)); if (!iter) { return EXTRACTOR_ERROR(set_t, "GetIter failed"); } CppT ret; if constexpr (has_reserve_v) { Py_ssize_t len = PySet_Size(obj); if (len == -1) { return EXTRACTOR_ERROR(set_t, "len() failed"); } else if (len == 0) { return ret; } ret.reserve(static_cast(len)); } Extractor extractor; while (auto item = StrongRef(PyIter_Next(*iter))) { auto extracted = extractor(*item); if (!extracted.hasValue()) { return EXTRACTOR_ERROR(set_t, "extractor error while iterating"); } ret.insert(std::move(*extracted)); } return ret; } int typeCheck(PyObject* obj) { if (PySet_Check(obj) != 1) { return 0; } const Py_ssize_t size = PySet_Size(obj); // Just check the first element given type checking inherent in // thrift-python struct construction; thrift consts if (size == 0) { return 1; } StrongRef iter(PyObject_GetIter(obj)); if (!iter) { PyErr_Clear(); return 0; } StrongRef front_obj(PyIter_Next(*iter)); if (!front_obj) { PyErr_Clear(); return 0; } return Extractor{}.typeCheck(*front_obj); } }; template using emplace_method_t = decltype(std::declval().emplace(std::declval(), std::declval())); template constexpr bool has_emplace_v = folly::is_detected_v, K, V>; template struct Extractor> : public BaseExtractor> { using map_t = map; ExtractorResult operator()(PyObject* obj) { if constexpr (folly::kIsDebug) { if (PyTuple_Check(obj) != 1) { PyErr_SetString(PyExc_TypeError, CAPI_LOCATED_ERROR("Not a tuple")); return EXTRACTOR_ERROR(map_t, "Not a tuple"); } } CppT ret; Py_ssize_t size = PyTuple_GET_SIZE(obj); if (size == 0) { return ret; } if constexpr (has_reserve_v) { ret.reserve(size); } Extractor key_extractor; Extractor val_extractor; for (Py_ssize_t i = 0; i < size; ++i) { // Note PyTuple_GET_ITEM returns a borrowed reference auto kv_tuple = PyTuple_GET_ITEM(obj, i); auto key = key_extractor(PyTuple_GET_ITEM(kv_tuple, 0)); if (!key.hasValue()) { // Extraction failed. Abort return extractorError(key.error()); } auto val = val_extractor(PyTuple_GET_ITEM(kv_tuple, 1)); if (!val.hasValue()) { // Extraction failed. Abort return extractorError(val.error()); } if constexpr (has_emplace_v) { ret.emplace(std::move(*key), std::move(*val)); } else { // DO NOT CODEMOD to try_emplace ret.insert({std::move(*key), std::move(*val)}); } } return ret; } int typeCheck(PyObject* obj) { if (PyTuple_Check(obj) != 1) { return 0; } const Py_ssize_t size = PyTuple_GET_SIZE(obj); // Just check the first element given type checking inherent in // thrift-python struct construction; thrift consts if (size == 0) { return 1; } PyObject* front_kv = PyTuple_GET_ITEM(obj, 0); if (!front_kv) { return 0; } return Extractor{}.typeCheck(PyTuple_GET_ITEM(front_kv, 0)) && Extractor{}.typeCheck(PyTuple_GET_ITEM(front_kv, 1)); } }; } // namespace capi } // namespace python } // namespace thrift } // namespace apache