diff --git a/.gitignore b/.gitignore index 839282b..09e0bdb 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ MANIFEST _build _generate build +dist/ +.pytest_cache/ +tmp/ diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..f12856f --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,17 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/**", + "~/.virtualenvs/python_example-XRpznMVn/include/**" + ], + "defines": [], + "compilerPath": "/usr/bin/gcc", + "cStandard": "c11", + "cppStandard": "c++17", + "intelliSenseMode": "clang-x64" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9cc0c90 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,33 @@ +{ + "python.pythonPath": "/home/daniel/.virtualenvs/python_example-XRpznMVn/bin/python", + "python.unitTest.pyTestArgs": [ + "tests" + ], + "python.unitTest.unittestEnabled": false, + "python.unitTest.nosetestsEnabled": false, + "python.unitTest.pyTestEnabled": true, + "python.formatting.provider": "black", + "python.linting.pylamaEnabled": true, + "python.linting.pylintEnabled": false, + "files.associations": { + ".flaskenv": "dotenv", + "array": "cpp", + "deque": "cpp", + "forward_list": "cpp", + "list": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "vector": "cpp", + "initializer_list": "cpp", + "string_view": "cpp", + "valarray": "cpp", + "stdexcept": "cpp", + "optional": "cpp", + "istream": "cpp", + "ostream": "cpp", + "system_error": "cpp", + "type_traits": "cpp", + "variant": "cpp", + "utility": "cpp" + } +} \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9548186 --- /dev/null +++ b/Makefile @@ -0,0 +1,41 @@ +SHELL=/bin/sh + +.SILENT: +.IGNORE: + +.PHONY: all +all: clean dev test + +.PHONY: dev +dev: build + pipenv install -e . + +.PHONY: help +help: + echo + echo 'Utility Makefile + echo '================ + echo + echo 'Targets supported are:' + echo + echo ' * clean: remove all build artefacts' + echo ' * build: builds the Python package' + echo ' * install: builds and installs into pipenv environment' + echo ' * test: runs unit tests' + echo ' * help: display help message' + +.PHONY: build +build: + pipenv run python setup.py bdist bdist_wheel + rm -rf tmp/ + +.PHONY: clean +clean: + echo Cleaning ... + rm -rf build/ + rm -rf tmp/ + echo ... done + +.PHONY: test +test: + pytest ./tests diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..e7eabf7 --- /dev/null +++ b/Pipfile @@ -0,0 +1,19 @@ +[pipenv] +allow_prereleases = false + +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +pybind11 = "*" +"e1839a8" = {path = ".", editable = true} + +[dev-packages] +pylama = "*" +pytest = "*" +numpy = "*" + +[requires] +python_version = "3.6" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..7ef90bd --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,167 @@ +{ + "_meta": { + "hash": { + "sha256": "e241b6d03fa64340227a3f40663a85865b61e16e6f3ba1fa3f3d38b9ef468e55" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.6" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "e1839a8": { + "editable": true, + "path": "." + }, + "pybind11": { + "hashes": [ + "sha256:642abbbd2948ed5af28e69adfae1535347c7aa9eb0cdab130e20e1f198f8e1cf", + "sha256:bd68159013d20c79bf79893b174a6ee7f74af740bf60ae731565f5d8d4094403" + ], + "index": "pypi", + "version": "==2.2.4" + } + }, + "develop": { + "atomicwrites": { + "hashes": [ + "sha256:0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0", + "sha256:ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee" + ], + "markers": "python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.3.*' and python_version != '3.2.*' and python_version != '3.1.*'", + "version": "==1.2.1" + }, + "attrs": { + "hashes": [ + "sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69", + "sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb" + ], + "version": "==18.2.0" + }, + "mccabe": { + "hashes": [ + "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", + "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + ], + "version": "==0.6.1" + }, + "more-itertools": { + "hashes": [ + "sha256:c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092", + "sha256:c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e", + "sha256:fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d" + ], + "version": "==4.3.0" + }, + "numpy": { + "hashes": [ + "sha256:1c362ad12dd09a43b348bb28dd2295dd9cdf77f41f0f45965e04ba97f525b864", + "sha256:2156a06bd407918df4ac0122df6497a9c137432118f585e5b17d543e593d1587", + "sha256:24e4149c38489b51fc774b1e1faa9103e82f73344d7a00ba66f6845ab4769f3f", + "sha256:340ec1697d9bb3a9c464028af7a54245298502e91178bddb4c37626d36e197b7", + "sha256:35db8d419345caa4eeaa65cd63f34a15208acd87530a30f0bc25fc84f55c8c80", + "sha256:361370e9b7f5e44c41eee29f2bb5cb3b755abb4b038bce6d6cbe08db7ff9cb74", + "sha256:36e8dcd1813ca92ce7e4299120cee6c03adad33d89b54862c1b1a100443ac399", + "sha256:378378973546ecc1dfaf9e24c160d683dd04df871ecd2dcc86ce658ca20f92c0", + "sha256:419e6faee16097124ee627ed31572c7e80a1070efa25260b78097cca240e219a", + "sha256:4287104c24e6a09b9b418761a1e7b1bbde65105f110690ca46a23600a3c606b8", + "sha256:549f3e9778b148a47f4fb4682955ed88057eb627c9fe5467f33507c536deda9d", + "sha256:5e359e9c531075220785603e5966eef20ccae9b3b6b8a06fdfb66c084361ce92", + "sha256:5ee7f3dbbdba0da75dec7e94bd7a2b10fe57a83e1b38e678200a6ad8e7b14fdc", + "sha256:62d55e96ec7b117d3d5e618c15efcf769e70a6effaee5842857b64fb4883887a", + "sha256:719b6789acb2bc86ea9b33a701d7c43dc2fc56d95107fd3c5b0a8230164d4dfb", + "sha256:7a70f2b60d48828cba94a54a8776b61a9c2657a803d47f5785f8062e3a9c7c55", + "sha256:7b9e37f194f8bcdca8e9e6af92e2cbad79e360542effc2dd6b98d63955d8d8a3", + "sha256:83b8fc18261b70f45bece2d392537c93dc81eb6c539a16c9ac994c47fc79f09a", + "sha256:9473ad28375710ab18378e72b59422399b27e957e9339c413bf00793b4b12df0", + "sha256:95b085b253080e5d09f7826f5e27dce067bae813a132023a77b739614a29de6e", + "sha256:98b86c62c08c2e5dc98a9c856d4a95329d11b1c6058cb9b5191d5ea6891acd09", + "sha256:a3bd01d6d3ed3d7c06d7f9979ba5d68281f15383fafd53b81aa44b9191047cf8", + "sha256:c81a6afc1d2531a9ada50b58f8c36197f8418ef3d0611d4c1d7af93fdcda764f", + "sha256:ce75ed495a746e3e78cfa22a77096b3bff2eda995616cb7a542047f233091268", + "sha256:dae8618c0bcbfcf6cf91350f8abcdd84158323711566a8c5892b5c7f832af76f", + "sha256:df0b02c6705c5d1c25cc35c7b5d6b6f9b3b30833f9d178843397ae55ecc2eebb", + "sha256:e3660744cda0d94b90141cdd0db9308b958a372cfeee8d7188fdf5ad9108ea82", + "sha256:f2362d0ca3e16c37782c1054d7972b8ad2729169567e3f0f4e5dd3cdf85f188e" + ], + "index": "pypi", + "version": "==1.15.1" + }, + "pluggy": { + "hashes": [ + "sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1", + "sha256:95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1" + ], + "markers": "python_version != '3.0.*' and python_version != '3.2.*' and python_version != '3.3.*' and python_version >= '2.7' and python_version != '3.1.*'", + "version": "==0.7.1" + }, + "py": { + "hashes": [ + "sha256:06a30435d058473046be836d3fc4f27167fd84c45b99704f2fb5509ef61f9af1", + "sha256:50402e9d1c9005d759426988a492e0edaadb7f4e68bcddfea586bc7432d009c6" + ], + "markers": "python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.3.*' and python_version != '3.2.*' and python_version != '3.1.*'", + "version": "==1.6.0" + }, + "pycodestyle": { + "hashes": [ + "sha256:cbc619d09254895b0d12c2c691e237b2e91e9b2ecf5e84c26b35400f93dcfb83", + "sha256:cbfca99bd594a10f674d0cd97a3d802a1fdef635d4361e1a2658de47ed261e3a" + ], + "version": "==2.4.0" + }, + "pydocstyle": { + "hashes": [ + "sha256:08a870edc94508264ed90510db466c6357c7192e0e866561d740624a8fc7d90c", + "sha256:4d5bcde961107873bae621f3d580c3e35a426d3687ffc6f8fb356f6628da5a97", + "sha256:af9fcccb303899b83bec82dc9a1d56c60fc369973223a5e80c3dfa9bdf984405" + ], + "version": "==2.1.1" + }, + "pyflakes": { + "hashes": [ + "sha256:9a7662ec724d0120012f6e29d6248ae3727d821bba522a0e6b356eff19126a49", + "sha256:f661252913bc1dbe7fcfcbf0af0db3f42ab65aabd1a6ca68fe5d466bace94dae" + ], + "markers": "python_version != '3.3.*' and python_version != '3.1.*' and python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.2.*'", + "version": "==2.0.0" + }, + "pylama": { + "hashes": [ + "sha256:390c1dab1daebdf3d6acc923e551b035c3faa77d8b96b98530c230493f9ec712", + "sha256:a3670459e7855529e2ccd3959e0bdebd694ac62bcdc7c58a877f3456c0a2058b" + ], + "index": "pypi", + "version": "==7.4.3" + }, + "pytest": { + "hashes": [ + "sha256:453cbbbe5ce6db38717d282b758b917de84802af4288910c12442984bde7b823", + "sha256:a8a07f84e680482eb51e244370aaf2caa6301ef265f37c2bdefb3dd3b663f99d" + ], + "index": "pypi", + "version": "==3.8.0" + }, + "six": { + "hashes": [ + "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", + "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + ], + "version": "==1.11.0" + }, + "snowballstemmer": { + "hashes": [ + "sha256:919f26a68b2c17a7634da993d91339e288964f93c274f1343e3bbbe2096e1128", + "sha256:9f3bcd3c401c3e862ec0ebe6d2c069ebc012ce142cce209c098ccb5b09136e89" + ], + "version": "==1.2.1" + } + } +} diff --git a/README.md b/README.md index dbad46e..de0d8d9 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,9 @@ python_example An example project built with [pybind11](https://github.com/pybind/pybind11). +This fork has been extended with additional examples to explore specific data types +that are needed in another project. + Installation ------------ diff --git a/pylama.ini b/pylama.ini new file mode 100644 index 0000000..12ded65 --- /dev/null +++ b/pylama.ini @@ -0,0 +1,6 @@ +[pylama] +linters=pycodestyle,pyflakes +format=pylint + +[pylama:pycodestyle] +max_line_length=120 \ No newline at end of file diff --git a/setup.py b/setup.py index a0db45c..56263dc 100644 --- a/setup.py +++ b/setup.py @@ -24,8 +24,9 @@ def __str__(self): ext_modules = [ Extension( 'python_example', - ['src/main.cpp'], + ['./src/main.cpp'], include_dirs=[ + './src', # Path to pybind11 headers get_pybind_include(), get_pybind_include(user=True) @@ -68,8 +69,19 @@ def cpp_flag(compiler): class BuildExt(build_ext): """A custom build extension for adding compiler-specific options.""" c_opts = { - 'msvc': ['/EHsc'], - 'unix': [], + 'msvc': [ + '/EHsc', + '/W3', + '/GL', + '/Od', + '/Oi', + '/Gy', + '/Zi', + '-DWINDOWS', + ], + 'unix': [ + '-DUNIX', + ], } if sys.platform == 'darwin': diff --git a/src/main.cpp b/src/main.cpp index 94d89a6..2d4105b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,12 +1,67 @@ +#include +#include +#include +#include #include +#include -int add(int i, int j) { +namespace py = pybind11; + +void runtime_error() +{ + throw std::runtime_error("C++ failure"); +} + +void range_error() +{ + throw std::range_error("C++ range error"); +} + +int add(int i, int j) +{ return i + j; } -namespace py = pybind11; +double ndarray_sum(py::array_t ndarray) +{ + // double ndarray_sum(std::vector &ndarray) { + // return std::accumulate(ndarray.begin(), ndarray.end(), 0.0); + auto buf1 = ndarray.request(); + double sum = 0; + int n = buf1.size; + for (int i = 0; i < n; i++) + { + sum += ((double *)buf1.ptr)[i]; + } + return sum; +} + +py::array_t get_ndarray(int n, double value) +{ + auto result = std::vector(n, value); + return py::array(result.size(), result.data()); +} -PYBIND11_MODULE(python_example, m) { +struct VectorResults +{ + VectorResults(const std::vector& temperature, + const std::vector& humidity) + : temperature(py::array(temperature.size(), temperature.data())), + humidity(py::array(humidity.size(), humidity.data())){}; + + const py::array_t temperature; + const py::array_t humidity; +}; + +VectorResults get_vector_results(int len, double temp_value, double hum_value) +{ + auto tmp = std::vector(len, temp_value); + auto hum = std::vector(len, hum_value); + return VectorResults(tmp, hum); +} + +PYBIND11_MODULE(python_example, m) +{ m.doc() = R"pbdoc( Pybind11 example plugin ----------------------- @@ -32,6 +87,23 @@ PYBIND11_MODULE(python_example, m) { Some other explanation about the subtract function. )pbdoc"); + m.def("ndarray_sum", &ndarray_sum, R"pbdoc( + Return the sum of a numpy ndarray. + + Tests functions that accept a numpy array and + autoconvert to std::vector. + )pbdoc"); + + m.def("runtime_error", &runtime_error); + m.def("range_error", &range_error); + m.def("get_ndarray", &get_ndarray); + + py::class_(m, "VectorResults") + .def_readonly("temperature", &VectorResults::temperature) + .def_readonly("humidity", &VectorResults::humidity); + + m.def("get_vector_results", &get_vector_results); + #ifdef VERSION_INFO m.attr("__version__") = VERSION_INFO; #else diff --git a/tests/test.py b/tests/test.py deleted file mode 100644 index f877b62..0000000 --- a/tests/test.py +++ /dev/null @@ -1,5 +0,0 @@ -import python_example as m - -assert m.__version__ == '0.0.1' -assert m.add(1, 2) == 3 -assert m.subtract(1, 2) == -1 diff --git a/tests/test_python_example.py b/tests/test_python_example.py new file mode 100644 index 0000000..7420f01 --- /dev/null +++ b/tests/test_python_example.py @@ -0,0 +1,48 @@ +import numpy as np +import python_example as m +import pytest + + +def test_version(): + assert m.__version__ == "0.0.1" + + +def test_add_1_2(): + assert m.add(1, 2) == 3 + + +def test_subtract_1_2(): + assert m.subtract(1, 2) == -1 + + +def test_ndarray_args(): + data = np.array([range(10)]) + assert m.ndarray_sum(data) == sum(range(10)) + + +def test_runtime_error(): + with pytest.raises(RuntimeError): + m.runtime_error() + + +def test_range_error(): + with pytest.raises(ValueError): + m.range_error() + + +def test_return_ndarray(): + a = m.get_ndarray(10, 5.0) + assert isinstance(a, np.ndarray) + np.testing.assert_allclose(a, 5.0) + assert len(a) == 10 + + +def test_get_class_with_vector_results(): + n = 10 + t = 4.3 + h = 88.3 + results = m.get_vector_results(n, t, h) + assert len(results.temperature) == n + np.testing.assert_allclose(results.temperature, t) + assert len(results.humidity) == n + np.testing.assert_allclose(results.humidity, h)