From 4ef214c6a9b3a0c7aa2fb9ebfa10b443243e95de Mon Sep 17 00:00:00 2001 From: Dobatymo Date: Fri, 19 Jul 2024 22:30:18 +0800 Subject: [PATCH] improved build system improve readme don't support python 3.6 anymore auto format use avx instead of avx2 --- .github/workflows/pythonpackage.yml | 63 ++++++++-------- .gitignore | 1 + .pre-commit-config.yaml | 75 +++++++++++++++---- README.md | 53 +++++++++++++- include/smartptr.h | 108 +++++++++++++--------------- pyproject.toml | 48 ++++++++++++- requirements-build.txt | 1 - requirements-dev.txt | 1 - setup.py | 19 +---- tests/test_cppcontainers.py | 1 - tox.ini | 2 +- 11 files changed, 242 insertions(+), 130 deletions(-) delete mode 100644 requirements-build.txt delete mode 100644 requirements-dev.txt diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index fec213b..604a5c7 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -3,39 +3,33 @@ name: Python package on: push: branches: - - '**' + - '**' tags-ignore: - - '**' + - '**' paths-ignore: - - '.pre-commit-config.yaml' + - .pre-commit-config.yaml pull_request: release: types: - - published + - published jobs: test: strategy: matrix: - os: ["windows-latest", "ubuntu-20.04", "macos-latest"] - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] + os: [windows-latest, ubuntu-20.04, macos-13] + python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install -r requirements-build.txt - python -m pip install -r requirements-dev.txt - - name: Build - run: | - python setup.py build_ext --inplace - name: Run tests run: | - python -m unittest discover -v tests "*.py" + python -m pip install .[test] + cd tests && python -m unittest build-wheels: if: github.event_name == 'release' && github.event.action == 'published' @@ -43,42 +37,41 @@ jobs: strategy: matrix: - os: ["ubuntu-20.04", "macos-latest", "windows-latest"] + os: [ubuntu-20.04, macos-13, windows-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: 3.8 - name: Install dependencies run: | - python -m pip install -U pip setuptools wheel cibuildwheel + python -m pip install cibuildwheel==2.11.2 - name: Build run: | python -m cibuildwheel - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: path: wheelhouse/*.whl build-sdist: if: github.event_name == 'release' && github.event.action == 'published' needs: test - runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: 3.8 - name: Install dependencies run: | - python -m pip install -U pip setuptools Cython==3.0.0a11 + python -m pip install build - name: Build dists run: | - python setup.py sdist - - uses: actions/upload-artifact@v2 + python -m build --sdist + - uses: actions/upload-artifact@v3 with: path: dist/*.tar.gz @@ -89,12 +82,12 @@ jobs: runs-on: ubuntu-20.04 steps: - - uses: actions/download-artifact@v2 - with: - name: artifact - path: dist + - uses: actions/download-artifact@v3 + with: + name: artifact + path: dist - - uses: pypa/gh-action-pypi-publish@master - with: - user: __token__ - password: ${{ secrets.pypi_password }} + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.pypi_password }} diff --git a/.gitignore b/.gitignore index 60cece5..c932f8f 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ dist/ # cython cython_debug/ +*.cpp diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c947837..5dd7a62 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,13 +1,64 @@ repos: - - repo: https://github.com/psf/black - rev: 22.10.0 - hooks: - - id: black - - repo: https://github.com/PyCQA/isort - rev: 5.10.1 - hooks: - - id: isort - - repo: https://github.com/pycqa/flake8 - rev: 6.0.0 - hooks: - - id: flake8 +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: check-added-large-files + - id: check-case-conflict + - id: check-merge-conflict + - id: check-symlinks + - id: check-json + - id: check-toml + - id: check-yaml + - id: debug-statements + - id: detect-private-key + - id: end-of-file-fixer + - id: mixed-line-ending + args: [--fix=no] + - id: requirements-txt-fixer + - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] +- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks + rev: v2.14.0 + hooks: + - id: pretty-format-yaml + args: [--autofix] +- repo: https://github.com/tox-dev/pyproject-fmt + rev: 2.1.4 + hooks: + - id: pyproject-fmt +- repo: https://github.com/pre-commit/mirrors-clang-format + rev: v18.1.8 + hooks: + - id: clang-format +- repo: https://github.com/asottile/pyupgrade + rev: v3.16.0 + hooks: + - id: pyupgrade + args: [--py37-plus] +- repo: https://github.com/MarcoGorelli/cython-lint + rev: v0.16.2 + hooks: + - id: cython-lint + - id: double-quote-cython-strings +- repo: https://github.com/psf/black-pre-commit-mirror + rev: 24.4.2 + hooks: + - id: black +- repo: https://github.com/PyCQA/isort + rev: 5.13.2 + hooks: + - id: isort +- repo: https://github.com/pycqa/flake8 + rev: 7.1.0 + hooks: + - id: flake8 + additional_dependencies: + - flake8-annotations + - flake8-bugbear + - flake8-eradicate + - flake8-mutable + - flake8-simplify +- repo: https://github.com/Yelp/detect-secrets + rev: v1.5.0 + hooks: + - id: detect-secrets diff --git a/README.md b/README.md index b11f718..42c3006 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,59 @@ # cpp-containers-python -Python wrapper for the C++ Standard Library containers. The behaviour of the containers might not be as expected, since for sorting and hashing the items only the pointer to the Python object is used, and not the actual content. That means items in ordered collections will be ordered according to memory layout and to object content. Furhermore, equality checks will work as pairwise `is` tests, because only the pointers are compared internally. Also certain operations will lead to *undefined behavior*, such as accessing out of bounds indices in `Vector`. +Python wrapper for the C++ Standard Library containers. The behavior of the containers might not be as expected, since for sorting and hashing the items, only the pointer to the Python object is used, and not the actual content. That means items in ordered collections will be ordered according to memory layout and not object content. Furthermore, equality checks will work as pairwise `is` tests, because only the pointers are compared internally. Also certain operations will lead to *undefined behavior*, such as accessing out of bounds indices in `Vector`. This design decision was made for performance reasons and to keep the code as simple and close to C++ as possible. Real equality checks can still be implemented by pairwise comparison of the objects at Python level. This won't be any faster than doing so on normal Python collections. For more documentation about the C++ containers see . +## Requirements +Python 3.7+. + ## Install -`pip install cpp-containers` (not working yet) +`pip install cpp-containers` + +## Examples +``` +from cppcontainers import MultiSet +s = MultiSet() +s.insert(1) +s.insert(2) +s.insert(2) +assert list(s) == [1, 2, 2] +assert s.size() == 3 +assert s.count(2) == 2 +assert s.begin().deref() == 1 +``` + +The containers store references to Python objects, not copies. + +``` +from cppcontainers import Vector +from sys import getrefcount +v = Vector() +a = "asd" +b = [1, 2, 3] +v.push_back(a) +v.push_back(b) +assert id(a) == id(v[0]) +assert list(v) == [a, b] +b.append(4) +assert b[-1] == v[1][-1] +assert getrefcount(a) == 3 +del v +assert getrefcount(a) == 2 +``` + +The following crashes, just like in C++. + +``` +from cppcontainers import Vector +Vector()[0] +``` + +And this throws an exception like in C++. + +``` +from cppcontainers import Vector +Vector().at(0) +``` diff --git a/include/smartptr.h b/include/smartptr.h index fdb730d..1e57531 100644 --- a/include/smartptr.h +++ b/include/smartptr.h @@ -2,84 +2,74 @@ #define PY_SSIZE_T_CLEAN #include "Python.h" -// use https://docs.python.org/3/c-api/init.html#c.PyGILState_Ensure around refcount actions? +// use https://docs.python.org/3/c-api/init.html#c.PyGILState_Ensure around +// refcount actions? class PyObjectSmartPtr { protected: - PyObject *ptr; + PyObject *ptr; public: - PyObjectSmartPtr(): ptr(nullptr) { - } - - PyObjectSmartPtr(const PyObjectSmartPtr &other) : ptr(other.ptr) { - Py_XINCREF(ptr); - } + PyObjectSmartPtr() : ptr(nullptr) {} - PyObjectSmartPtr &operator=(const PyObjectSmartPtr &other) noexcept { - if (this != &other) - { - Py_XDECREF(ptr); - ptr = other.ptr; - Py_XINCREF(ptr); - } + PyObjectSmartPtr(const PyObjectSmartPtr &other) : ptr(other.ptr) { + Py_XINCREF(ptr); + } - return *this; + PyObjectSmartPtr &operator=(const PyObjectSmartPtr &other) noexcept { + if (this != &other) { + Py_XDECREF(ptr); + ptr = other.ptr; + Py_XINCREF(ptr); } - ~PyObjectSmartPtr() noexcept { - Py_XDECREF(ptr); - } + return *this; + } - PyObjectSmartPtr(PyObject *ptr): ptr(ptr) { - if (ptr == nullptr) { - throw std::invalid_argument("nullptr"); - } - Py_INCREF(ptr); - } + ~PyObjectSmartPtr() noexcept { Py_XDECREF(ptr); } - PyObject& operator*() const { - if (ptr == nullptr) { - throw std::invalid_argument("nullptr"); - } - return *ptr; + PyObjectSmartPtr(PyObject *ptr) : ptr(ptr) { + if (ptr == nullptr) { + throw std::invalid_argument("nullptr"); } + Py_INCREF(ptr); + } - PyObject* operator->() const noexcept { - return ptr; + PyObject &operator*() const { + if (ptr == nullptr) { + throw std::invalid_argument("nullptr"); } + return *ptr; + } - PyObject *get() const noexcept { - return ptr; - } + PyObject *operator->() const noexcept { return ptr; } - bool operator<(const PyObjectSmartPtr &other) const noexcept { - return get() < other.get(); - } + PyObject *get() const noexcept { return ptr; } - bool operator==(const PyObjectSmartPtr &other) const noexcept { - return get() == other.get(); - } + bool operator<(const PyObjectSmartPtr &other) const noexcept { + return get() < other.get(); + } - bool operator!=(const PyObjectSmartPtr &other) const noexcept { - return get() != other.get(); - } + bool operator==(const PyObjectSmartPtr &other) const noexcept { + return get() == other.get(); + } - Py_ssize_t refcount() const { - if (ptr == nullptr) { - throw std::invalid_argument("nullptr"); - } - return Py_REFCNT(ptr); + bool operator!=(const PyObjectSmartPtr &other) const noexcept { + return get() != other.get(); + } + + Py_ssize_t refcount() const { + if (ptr == nullptr) { + throw std::invalid_argument("nullptr"); } + return Py_REFCNT(ptr); + } }; -namespace std -{ - template<> struct hash - { - std::size_t operator()(PyObjectSmartPtr const& s) const noexcept - { - return std::hash{}(s.get()); - } - }; -} +namespace std { +template <> struct hash { + std::size_t operator()(PyObjectSmartPtr const &s) const noexcept { + return std::hash{}(s.get()); + } +}; +} // namespace std diff --git a/pyproject.toml b/pyproject.toml index 770fd79..9ad30cb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,54 @@ [build-system] -requires = ["setuptools", "wheel", "Cython==3.0.0a11"] +build-backend = "setuptools.build_meta" +requires = [ + "cython==3.0.10", + "setuptools", +] + +[project] +name = "cpp-containers" +version = "0.1" +description = "Python wrapper for CPP stdlib containers" +readme = "README.md" +license = { file = "LICENSE" } +authors = [ { name = "Dobatymo" } ] +requires-python = ">=3.7" +classifiers = [ + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] +optional-dependencies.test = [ + "genutility[test]", +] + +urls.Home = "https://github.com/Dobatymo/cpp-containers-python" + +[tool.setuptools] +packages = [ "cppcontainers" ] [tool.black] line-length = 120 +[tool.ruff] +line-length = 120 + [tool.isort] profile = "black" -src_paths = ["cppcontainers"] line_length = 120 + +[tool.mypy] +allow_redefinition = true +warn_unused_configs = true +warn_unused_ignores = true + +[tool.bandit] +skips = [ "B101" ] + +[tool.cython-lint] +max-line-length = 120 +ignore = [ "W191" ] diff --git a/requirements-build.txt b/requirements-build.txt deleted file mode 100644 index c0d7461..0000000 --- a/requirements-build.txt +++ /dev/null @@ -1 +0,0 @@ -Cython==3.0.0a11 diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index 3b03c09..0000000 --- a/requirements-dev.txt +++ /dev/null @@ -1 +0,0 @@ -genutility diff --git a/setup.py b/setup.py index 18e9623..c0f577f 100644 --- a/setup.py +++ b/setup.py @@ -9,17 +9,17 @@ if sys.platform.startswith("linux"): if machine in x86: - cflags = ["-std=c++14", "-O2", "-mavx2"] + cflags = ["-std=c++14", "-O2", "-mavx"] else: cflags = ["-std=c++14", "-O2"] elif sys.platform == "win32": if machine in x86: - cflags = ["/std:c++14", "/O2", "/arch:AVX2"] + cflags = ["/std:c++14", "/O2", "/arch:AVX"] else: cflags = ["/std:c++14", "/O2"] elif sys.platform == "darwin": if machine in x86: - cflags = ["-std=c++14", "-O2", "-mavx2"] + cflags = ["-std=c++14", "-O2", "-mavx"] else: cflags = ["-std=c++14", "-O2"] else: @@ -35,9 +35,6 @@ ), ] -with open("README.md", "r", encoding="utf-8") as fr: - long_description = fr.read() - compiler_directives = { "boundscheck": False, "wraparound": False, @@ -50,15 +47,5 @@ } setup( - author="Dobatymo", - name="cpp-containers", - version="0.1", - url="https://github.com/Dobatymo/cpp-containers-python", - description="Python wrapper for CPP stdlib containers", - long_description=long_description, - long_description_content_type="text/markdown", - packages=["cppcontainers"], ext_modules=cythonize(cy_extensions, language_level=3, compiler_directives=compiler_directives), - python_requires=">=3.6", - zip_safe=False, ) diff --git a/tests/test_cppcontainers.py b/tests/test_cppcontainers.py index fa88e3e..f5b16ff 100644 --- a/tests/test_cppcontainers.py +++ b/tests/test_cppcontainers.py @@ -22,7 +22,6 @@ class CppContainersTest(MyTestCase): - # Sequence def test_vector(self): diff --git a/tox.ini b/tox.ini index a4c1477..3b1f3de 100644 --- a/tox.ini +++ b/tox.ini @@ -1,4 +1,4 @@ [flake8] max-line-length = 120 -select = E7, E9, W2, W6, F +select = B, E7, E9, W2, W3, W6, F exclude = .git,.mypy_cache,__pycache__,build,dist