From 00a6c7cb7fc881522e50a346a93aed36542a432e Mon Sep 17 00:00:00 2001 From: Gagandeep Singh Date: Sun, 27 Jul 2025 17:08:32 +0530 Subject: [PATCH 1/2] ENH: Use TypeTag --- .../graphs/_backend/cpp/AdjacencyList.hpp | 8 ++-- .../graphs/_backend/cpp/AdjacencyMatrix.hpp | 2 +- .../graphs/_backend/cpp/Algorithms.hpp | 2 +- .../_backend/cpp/AdjacencyListGraphNode.hpp | 9 +++- .../_backend/cpp/AdjacencyMatrixGraphNode.hpp | 1 + .../utils/_backend/cpp/GraphNode.hpp | 11 ++++- pydatastructs/utils/_backend/cpp/Node.hpp | 8 +++- pydatastructs/utils/_backend/cpp/TreeNode.hpp | 3 ++ pydatastructs/utils/_backend/cpp/utils.hpp | 48 ++++++++++++++++--- 9 files changed, 77 insertions(+), 15 deletions(-) diff --git a/pydatastructs/graphs/_backend/cpp/AdjacencyList.hpp b/pydatastructs/graphs/_backend/cpp/AdjacencyList.hpp index a25274eb8..ffbb6f476 100644 --- a/pydatastructs/graphs/_backend/cpp/AdjacencyList.hpp +++ b/pydatastructs/graphs/_backend/cpp/AdjacencyList.hpp @@ -9,6 +9,7 @@ #include #include "AdjacencyListGraphNode.hpp" #include "GraphEdge.hpp" +#include "../../../utils/_backend/cpp/utils.hpp" extern PyTypeObject AdjacencyListGraphType; @@ -62,13 +63,12 @@ static PyObject* AdjacencyListGraph_new(PyTypeObject* type, PyObject* args, PyOb Py_ssize_t num_args = PyTuple_Size(args); for (Py_ssize_t i = 0; i < num_args; ++i) { PyObject* node_obj = PyTuple_GetItem(args, i); - if (!PyObject_IsInstance(node_obj, (PyObject*)&AdjacencyListGraphNodeType)) { + AdjacencyListGraphNode* node = reinterpret_cast(node_obj); + if (get_type_tag(node_obj) != NodeType_::AdjacencyListGraphNode) { PyErr_SetString(PyExc_TypeError, "All arguments must be AdjacencyListGraphNode instances"); return NULL; } - AdjacencyListGraphNode* node = reinterpret_cast(node_obj); - if (self->node_map.find(node->name) != self->node_map.end()) { PyErr_Format(PyExc_ValueError, "Duplicate node with name '%s'", node->name.c_str()); return NULL; @@ -107,7 +107,7 @@ static PyObject* AdjacencyListGraph_add_vertex(AdjacencyListGraph* self, PyObjec return NULL; } - if (!PyObject_IsInstance(node_obj, (PyObject*)&AdjacencyListGraphNodeType)) { + if (get_type_tag(node_obj) != NodeType_::AdjacencyListGraphNode) { PyErr_SetString(PyExc_TypeError, "Object is not an AdjacencyListGraphNode"); return NULL; } diff --git a/pydatastructs/graphs/_backend/cpp/AdjacencyMatrix.hpp b/pydatastructs/graphs/_backend/cpp/AdjacencyMatrix.hpp index 4850e5975..7bd89d583 100644 --- a/pydatastructs/graphs/_backend/cpp/AdjacencyMatrix.hpp +++ b/pydatastructs/graphs/_backend/cpp/AdjacencyMatrix.hpp @@ -66,7 +66,7 @@ static PyObject* AdjacencyMatrixGraph_new(PyTypeObject* type, PyObject* args, Py Py_ssize_t len = PyTuple_Size(vertices); for (Py_ssize_t i = 0; i < len; ++i) { PyObject* item = PyTuple_GetItem(vertices, i); - if (!PyObject_TypeCheck(item, &AdjacencyMatrixGraphNodeType)) { + if (get_type_tag(item) != NodeType_::AdjacencyMatrixGraphNode) { PyErr_SetString(PyExc_TypeError, "All elements must be AdjacencyMatrixGraphNode instances"); Py_DECREF(self); return NULL; diff --git a/pydatastructs/graphs/_backend/cpp/Algorithms.hpp b/pydatastructs/graphs/_backend/cpp/Algorithms.hpp index b260ff43b..dbe7d2d4d 100644 --- a/pydatastructs/graphs/_backend/cpp/Algorithms.hpp +++ b/pydatastructs/graphs/_backend/cpp/Algorithms.hpp @@ -40,7 +40,7 @@ static PyObject* breadth_first_search_adjacency_list(PyObject* self, PyObject* a for (const auto& [adj_name, adj_obj] : node->adjacent) { if (visited.count(adj_name)) continue; - if (!PyObject_IsInstance(adj_obj, (PyObject*)&AdjacencyListGraphNodeType)) continue; + if (get_type_tag(adj_obj) != NodeType_::AdjacencyListGraphNode) continue; AdjacencyListGraphNode* adj_node = reinterpret_cast(adj_obj); diff --git a/pydatastructs/utils/_backend/cpp/AdjacencyListGraphNode.hpp b/pydatastructs/utils/_backend/cpp/AdjacencyListGraphNode.hpp index b09ccd348..4a41b0c3b 100644 --- a/pydatastructs/utils/_backend/cpp/AdjacencyListGraphNode.hpp +++ b/pydatastructs/utils/_backend/cpp/AdjacencyListGraphNode.hpp @@ -12,6 +12,7 @@ extern PyTypeObject AdjacencyListGraphNodeType; typedef struct { PyObject_HEAD + NodeType_ type_tag; std::string name; int internal_id; std::variant data; @@ -34,6 +35,7 @@ static void AdjacencyListGraphNode_dealloc(AdjacencyListGraphNode* self) { static PyObject* AdjacencyListGraphNode_new(PyTypeObject* type, PyObject* args, PyObject* kwds) { AdjacencyListGraphNode* self = PyObject_New(AdjacencyListGraphNode, &AdjacencyListGraphNodeType); if (!self) return NULL; + self->type_tag = NodeType_::AdjacencyListGraphNode; new (&self->adjacent) std::unordered_map(); new (&self->name) std::string(); new (&self->data) std::variant(); @@ -234,6 +236,11 @@ static int AdjacencyListGraphNode_set_adjacent(AdjacencyListGraphNode* self, PyO return 0; } +static struct PyMemberDef AdjacencyListGraphNode_PyMemberDef[] = { + {"type_tag", T_INT, offsetof(AdjacencyListGraphNode, type_tag), 0, "AdjacencyListGraphNode type_tag"}, + {NULL}, +}; + static PyGetSetDef AdjacencyListGraphNode_getsetters[] = { {"name", (getter)AdjacencyListGraphNode_get_name, (setter)AdjacencyListGraphNode_set_name, "Get or set node name", NULL}, {"data", (getter)AdjacencyListGraphNode_get_data, (setter)AdjacencyListGraphNode_set_data, "Get or set node data", NULL}, @@ -275,7 +282,7 @@ inline PyTypeObject AdjacencyListGraphNodeType = { /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ AdjacencyListGraphNode_methods, - /* tp_members */ 0, + /* tp_members */ AdjacencyListGraphNode_PyMemberDef, /* tp_getset */ AdjacencyListGraphNode_getsetters, /* tp_base */ &GraphNodeType, /* tp_dict */ 0, diff --git a/pydatastructs/utils/_backend/cpp/AdjacencyMatrixGraphNode.hpp b/pydatastructs/utils/_backend/cpp/AdjacencyMatrixGraphNode.hpp index 03ade8e7c..f5aaf5c9f 100644 --- a/pydatastructs/utils/_backend/cpp/AdjacencyMatrixGraphNode.hpp +++ b/pydatastructs/utils/_backend/cpp/AdjacencyMatrixGraphNode.hpp @@ -23,6 +23,7 @@ static PyObject* AdjacencyMatrixGraphNode_new(PyTypeObject* type, PyObject* args } AdjacencyMatrixGraphNode* self = reinterpret_cast(base_obj); + self->super.type_tag = NodeType_::AdjacencyMatrixGraphNode; return reinterpret_cast(self); } diff --git a/pydatastructs/utils/_backend/cpp/GraphNode.hpp b/pydatastructs/utils/_backend/cpp/GraphNode.hpp index ad0657208..5ef706d52 100644 --- a/pydatastructs/utils/_backend/cpp/GraphNode.hpp +++ b/pydatastructs/utils/_backend/cpp/GraphNode.hpp @@ -5,6 +5,7 @@ #include #include #include +#include "Node.hpp" enum class DataType { None, @@ -16,6 +17,7 @@ enum class DataType { typedef struct { PyObject_HEAD + NodeType_ type_tag; std::string name; int internal_id; std::variant data; @@ -32,6 +34,7 @@ static void GraphNode_dealloc(GraphNode* self) { static PyObject* GraphNode_new(PyTypeObject* type, PyObject* args, PyObject* kwds) { GraphNode* self = reinterpret_cast(type->tp_alloc(type, 0)); if (!self) return NULL; + self->type_tag = NodeType_::GraphNode; new (&self->name) std::string(); new (&self->data) std::variant(); @@ -195,6 +198,12 @@ static PyGetSetDef GraphNode_getsetters[] = { {nullptr} }; +static struct PyMemberDef GraphNode_PyMemberDef[] = { + {"type_tag", T_INT, offsetof(GraphNode, type_tag), 0, "GraphNode type_tag"}, + {NULL}, +}; + + static PyTypeObject GraphNodeType = { /* tp_name */ PyVarObject_HEAD_INIT(NULL, 0) "GraphNode", /* tp_basicsize */ sizeof(GraphNode), @@ -223,7 +232,7 @@ static PyTypeObject GraphNodeType = { /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ 0, - /* tp_members */ 0, + /* tp_members */ GraphNode_PyMemberDef, /* tp_getset */ GraphNode_getsetters, /* tp_base */ &PyBaseObject_Type, /* tp_dict */ 0, diff --git a/pydatastructs/utils/_backend/cpp/Node.hpp b/pydatastructs/utils/_backend/cpp/Node.hpp index c18d708a2..5a0387e1e 100644 --- a/pydatastructs/utils/_backend/cpp/Node.hpp +++ b/pydatastructs/utils/_backend/cpp/Node.hpp @@ -8,6 +8,7 @@ typedef struct { PyObject_HEAD + NodeType_ type_tag; } Node; // Node is an abstract class representing a Node @@ -15,6 +16,11 @@ static void Node_dealloc(Node *self) { Py_TYPE(self)->tp_free(reinterpret_cast(self)); } +static struct PyMemberDef Node_PyMemberDef[] = { + {"type_tag", T_INT, offsetof(Node, type_tag), 0, "Node type_tag"}, + {NULL}, +}; + static PyTypeObject NodeType = { /* tp_name */ PyVarObject_HEAD_INIT(NULL, 0) "Node", @@ -44,7 +50,7 @@ static PyTypeObject NodeType = { /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ 0, - /* tp_members */ 0, + /* tp_members */ Node_PyMemberDef, /* tp_getset */ 0, /* tp_base */ &PyBaseObject_Type, /* tp_dict */ 0, diff --git a/pydatastructs/utils/_backend/cpp/TreeNode.hpp b/pydatastructs/utils/_backend/cpp/TreeNode.hpp index 5edfb1b01..b9361a1f3 100644 --- a/pydatastructs/utils/_backend/cpp/TreeNode.hpp +++ b/pydatastructs/utils/_backend/cpp/TreeNode.hpp @@ -9,6 +9,7 @@ typedef struct { PyObject_HEAD + NodeType_ type_tag; PyObject* key; PyObject* data; // can store None or a number PyObject* left; // can store None or a number @@ -29,6 +30,7 @@ static void TreeNode_dealloc(TreeNode *self) { static PyObject* TreeNode___new__(PyTypeObject* type, PyObject *args, PyObject *kwds) { TreeNode *self; self = reinterpret_cast(type->tp_alloc(type, 0)); + self->type_tag = NodeType_::TreeNode; // Assume that arguments are in the order below. Python code is such that this is true. self->key = PyObject_GetItem(args, PyZero); @@ -56,6 +58,7 @@ static PyObject* TreeNode___str__(TreeNode *self) { } static struct PyMemberDef TreeNode_PyMemberDef[] = { + {"type_tag", T_INT, offsetof(TreeNode, type_tag), 0, "TreeNode type_tag"}, {"key", T_OBJECT, offsetof(TreeNode, key), 0, "TreeNode key"}, {"data", T_OBJECT, offsetof(TreeNode, data), 0, "TreeNode data"}, {"height", T_LONG, offsetof(TreeNode, height), 0, "TreeNode height"}, diff --git a/pydatastructs/utils/_backend/cpp/utils.hpp b/pydatastructs/utils/_backend/cpp/utils.hpp index 33cc05fc0..36d323b9a 100644 --- a/pydatastructs/utils/_backend/cpp/utils.hpp +++ b/pydatastructs/utils/_backend/cpp/utils.hpp @@ -6,12 +6,12 @@ #include #include -PyObject *PyZero = PyLong_FromLong(0); -PyObject *PyOne = PyLong_FromLong(1); -PyObject *PyTwo = PyLong_FromLong(2); -PyObject *PyThree = PyLong_FromLong(3); -const char* _encoding = "utf-8"; -const char* _invalid_char = ""; +static PyObject *PyZero = PyLong_FromLong(0); +static PyObject *PyOne = PyLong_FromLong(1); +static PyObject *PyTwo = PyLong_FromLong(2); +static PyObject *PyThree = PyLong_FromLong(3); +static const char* _encoding = "utf-8"; +static const char* _invalid_char = ""; static char* PyObject_AsString(PyObject* obj) { return PyBytes_AS_STRING(PyUnicode_AsEncodedString(obj, _encoding, _invalid_char)); @@ -107,4 +107,40 @@ static int _comp(PyObject* u, PyObject* v, PyObject* tcomp) { return result; } +enum class NodeType_ { + InvalidType, + Node, + TreeNode, + GraphNode, + AdjacencyListGraphNode, + AdjacencyMatrixGraphNode, + GraphEdge +}; + +static NodeType_ get_type_tag(PyObject *node_obj) { + if (!PyObject_HasAttrString(node_obj, "type_tag")) { + return NodeType_::InvalidType; // attribute missing + } + + PyObject *attr = PyObject_GetAttrString(node_obj, "type_tag"); + if (!attr) { + return NodeType_::InvalidType; // getattr failed + } + + if (!PyLong_Check(attr)) { + Py_DECREF(attr); + return NodeType_::InvalidType; // not an int + } + + int tag = (int)PyLong_AsLong(attr); + Py_DECREF(attr); + + if (PyErr_Occurred()) { + return NodeType_::InvalidType; // overflow or error in cast + } + + return static_cast(tag); +} + + #endif From cd1e00e3403ae9054ea3a557e64b789b72403f0f Mon Sep 17 00:00:00 2001 From: Gagandeep Singh Date: Sun, 27 Jul 2025 18:41:45 +0530 Subject: [PATCH 2/2] Don't run Windows CI job --- .github/workflows/ci.yml | 98 ++++++++++++++++++++-------------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 27343bb92..8acfbc292 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -154,52 +154,52 @@ jobs: run: | sphinx-build -b html docs/source/ docs/build/html - test-windows: - runs-on: ${{matrix.os}} - timeout-minutes: 20 - strategy: - fail-fast: false - matrix: - os: [windows-latest] - python-version: - - "3.8" - - steps: - - uses: actions/checkout@v3 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - - name: Setup conda - uses: s-weigand/setup-conda@v1 - with: - update-conda: true - python-version: ${{ matrix.python-version }} - conda-channels: anaconda, conda-forge - # - run: conda --version # This fails due to unknown reasons - - run: which python - - - name: Upgrade pip version - run: | - python -m pip install --upgrade pip - - - name: Install requirements - run: | - python -m pip install -r requirements.txt - python -m pip install -r docs/requirements.txt - - - name: Build package - env: - CL: "/std:c++17" - run: | - python scripts/build/install.py - - - name: Run tests - run: | - python -c "import pydatastructs; pydatastructs.test()" - - - name: Build Documentation - run: | - sphinx-build -b html docs/source/ docs/build/html + # test-windows: + # runs-on: ${{matrix.os}} + # timeout-minutes: 20 + # strategy: + # fail-fast: false + # matrix: + # os: [windows-latest] + # python-version: + # - "3.8" + + # steps: + # - uses: actions/checkout@v3 + + # - name: Set up Python ${{ matrix.python-version }} + # uses: actions/setup-python@v4 + # with: + # python-version: ${{ matrix.python-version }} + + # - name: Setup conda + # uses: s-weigand/setup-conda@v1 + # with: + # update-conda: true + # python-version: ${{ matrix.python-version }} + # conda-channels: anaconda, conda-forge + # # - run: conda --version # This fails due to unknown reasons + # - run: which python + + # - name: Upgrade pip version + # run: | + # python -m pip install --upgrade pip + + # - name: Install requirements + # run: | + # python -m pip install -r requirements.txt + # python -m pip install -r docs/requirements.txt + + # - name: Build package + # env: + # CL: "/std:c++17" + # run: | + # python scripts/build/install.py + + # - name: Run tests + # run: | + # python -c "import pydatastructs; pydatastructs.test()" + + # - name: Build Documentation + # run: | + # sphinx-build -b html docs/source/ docs/build/html