diff --git a/api/CMakeLists.txt b/api/CMakeLists.txt index 8d61d1cd1..4777e7cd7 100644 --- a/api/CMakeLists.txt +++ b/api/CMakeLists.txt @@ -21,6 +21,10 @@ c4_log("found swig ${SWIG_VERSION}: ${SWIG_EXECUTABLE}") # https://cmake.org/cmake/help/v3.13/module/UseSWIG.html include(UseSWIG) +if(NOT RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS) + message(FATAL_ERROR "API requires exceptions") +endif() + set(RYML_API_DIR ${CMAKE_CURRENT_LIST_DIR}) set(RYML_SWIG_SRC ${RYML_API_DIR}/ryml.i) @@ -99,7 +103,8 @@ if(RYML_BUILD_API_PYTHON) if(WIN32) target_compile_definitions(${t} PUBLIC __WIN32__) endif() - target_compile_definitions(${t} PUBLIC ${RYML_SWIG_ARCH_DEFINES}) + c4_get_transitive_property(ryml COMPILE_DEFINITIONS ryml_defs) + target_compile_definitions(${t} PUBLIC ${RYML_SWIG_ARCH_DEFINES} ${ryml_defs}) # Install the SWIG .so/.dll file install( diff --git a/api/python/Makefile b/api/python/Makefile index bdb5a6ffd..4f79f02fd 100644 --- a/api/python/Makefile +++ b/api/python/Makefile @@ -13,7 +13,7 @@ endif # How to invoke python PYTHON := python # How to invoke pytest -PYTEST := $(PYTHON) -m pytest -vvv +PYTEST := $(PYTHON) -m pytest -vvv -s ACTIVATE=[[ -e $(ACTIVATE_SCRIPT) ]] && source $(ACTIVATE_SCRIPT); @@ -40,10 +40,11 @@ venv: # Setup requirements. ${ACTIVATE} pip install -v -r requirements.txt ${ACTIVATE} pip install -v -e ../.. - @${ACTIVATE} $(PYTHON) -c "from ryml.version import version as v; print('Installed version:', v)" + ${ACTIVATE} $(PYTHON) -c "from ryml.version import version as v; print('Installed version:', v)" .PHONY: build-sdist build-sdist: | $(ACTIVATE_SCRIPT) + ${ACTIVATE} (cd ../..; pip show build) ${ACTIVATE} (cd ../..; $(PYTHON) -m build --sdist --outdir $(PWD)/dist) @@ -55,30 +56,27 @@ build-wheel: | $(ACTIVATE_SCRIPT) ${ACTIVATE} pip wheel -v dist/*.tar.gz --wheel-dir $(PWD)/dist .PHONY: build -build: - rm -rf build dist - $(MAKE) build-sdist - $(MAKE) build-wheel +build: build-sdist build-wheel # PYPI_TEST = --repository-url https://test.pypi.org/legacy/ PYPI_TEST = --repository testpypi .PHONY: upload-test upload-test: | $(ACTIVATE_SCRIPT) - make clean - make build-sdist + $(MAKE) clean + $(MAKE) build-sdist ${ACTIVATE} twine upload ${PYPI_TEST} dist/* .PHONY: upload upload: | $(ACTIVATE_SCRIPT) - make clean - make build-sdist + $(MAKE) clean + $(MAKE) build-sdist ${ACTIVATE} twine upload --verbose dist/* .PHONY: check check: | $(ACTIVATE_SCRIPT) - make clean - make build-wheel + $(MAKE) clean + $(MAKE) build-wheel ${ACTIVATE} twine check dist/*.whl .PHONY: install @@ -87,6 +85,7 @@ install: | $(ACTIVATE_SCRIPT) .PHONY: test test: | $(ACTIVATE_SCRIPT) + ${ACTIVATE} pip install -v -e ../.. ${ACTIVATE} $(PYTEST) tests .PHONY: version diff --git a/api/python/requirements.txt b/api/python/requirements.txt index 86c126420..4a902dfd8 100644 --- a/api/python/requirements.txt +++ b/api/python/requirements.txt @@ -3,3 +3,5 @@ ninja pyyaml prettytable pytest +build +twine diff --git a/api/python/tests/test_parse.py b/api/python/tests/test_parse.py index bb7973cb3..4f3ae98fa 100644 --- a/api/python/tests/test_parse.py +++ b/api/python/tests/test_parse.py @@ -481,6 +481,30 @@ def test44_emit_json_short_buf(self): +# ----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- + +class TestParseFailure(unittest.TestCase): + + yaml = "[:HELLO: b}" + + def setUp(self): + self.src_as_str = str(__class__.yaml) + self.src_as_bytearray = bytearray(__class__.yaml, "utf8") + + def test_in_arena(self): + self.assertNotEqual(self.src_as_str, "") + with self.assertRaises(RuntimeError): + tree = ryml.parse_in_arena(self.src_as_str) + + def test_in_place(self): + self.assertNotEqual(self.src_as_bytearray, "") + with self.assertRaises(RuntimeError): + tree = ryml.parse_in_place(self.src_as_bytearray) + + + # ----------------------------------------------------------------------------- # ----------------------------------------------------------------------------- # ----------------------------------------------------------------------------- diff --git a/api/ryml.i b/api/ryml.i index ac226142a..c4f832fd0 100644 --- a/api/ryml.i +++ b/api/ryml.i @@ -1,7 +1,6 @@ %module ryml - //----------------------------------------------------------------------------- // this block will be pasted verbatim in the generated C++ source file @@ -23,8 +22,8 @@ using csubstr = c4::csubstr; %} -//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- %apply (const char *STRING, size_t LENGTH) { (const char *str, size_t len) }; %apply (char *STRING, size_t LENGTH) { (char *str, size_t len) }; @@ -91,6 +90,8 @@ using csubstr = c4::csubstr; %typemap(typecheck) c4::csubstr = const char *; +//----------------------------------------------------------------------------- + %typemap(out) c4::csubstr { #if defined(SWIGPYTHON) if($1.str == nullptr) { @@ -111,6 +112,26 @@ using csubstr = c4::csubstr; }; +//----------------------------------------------------------------------------- + +// Language independent exception handler. +// KEEP THIS BEFORE THE FOLLOWING FUNCTIONS! +// see https://stackoverflow.com/a/61621747 +%include +%include +%exception { + try { + $action + } catch(std::exception &e) { + SWIG_exception(SWIG_RuntimeError, e.what()); + } catch(...) { + SWIG_exception(SWIG_UnknownError, "unknown error"); + } +} + + +//----------------------------------------------------------------------------- + %inline %{ void parse_csubstr(c4::csubstr s, c4::yml::Tree *t) diff --git a/doc/sphinx_other_languages.rst b/doc/sphinx_other_languages.rst index 3c0cefdf9..f9dc9c909 100644 --- a/doc/sphinx_other_languages.rst +++ b/doc/sphinx_other_languages.rst @@ -25,6 +25,7 @@ using `emscripten`: -D RYML_BUILD_TESTS=ON \ -D RYML_BUILD_BENCHMARKS=OFF \ -D RYML_TEST_SUITE=OFF \ + -D RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS=ON \ -D CMAKE_BUILD_TYPE=Release \ -D CMAKE_CXX_FLAGS='-s DISABLE_EXCEPTION_CATCHING=0' cmake --build build/emscripten --target ryml-test-run -j diff --git a/setup.py b/setup.py index c604758f4..3f56a87f0 100644 --- a/setup.py +++ b/setup.py @@ -56,6 +56,8 @@ def get_environment_cmake_flags(): cmake_component='python', cmake_configure_options=get_environment_cmake_flags() + [ "-DRYML_BUILD_API:BOOL=ON", + "-DRYML_DEFAULT_CALLBACKS:BOOL=ON", + "-DRYML_DEFAULT_CALLBACK_USES_EXCEPTIONS:BOOL=ON", # Force cmake to use the Python interpreter we are currently # using to run setup.py "-DPython3_EXECUTABLE:FILEPATH=" + sys.executable, diff --git a/src/c4/yml/common.cpp b/src/c4/yml/common.cpp index 56758e3eb..cdfdb81b8 100644 --- a/src/c4/yml/common.cpp +++ b/src/c4/yml/common.cpp @@ -8,6 +8,7 @@ # endif #endif // RYML_NO_DEFAULT_CALLBACKS + namespace c4 { namespace yml {