Skip to content

[Python API] Fix: Create constant for strings and fix segfault #33574

Open
KarSri7694 wants to merge 25 commits intoopenvinotoolkit:masterfrom
KarSri7694:openvino-string-constant-fix
Open

[Python API] Fix: Create constant for strings and fix segfault #33574
KarSri7694 wants to merge 25 commits intoopenvinotoolkit:masterfrom
KarSri7694:openvino-string-constant-fix

Conversation

@KarSri7694
Copy link
Copy Markdown

This PR fixes #23611

  • The numpy based construtor now handles numpy-array with string as elements.
  • converts None to empty string ''

Related tests have also been added to test_constant.py.

@KarSri7694 KarSri7694 requested a review from a team as a code owner January 13, 2026 10:27
@github-actions github-actions bot added the category: Python API OpenVINO Python bindings label Jan 13, 2026
@sys-openvino-ci sys-openvino-ci added the ExternalPR External contributor label Jan 13, 2026
@KarSri7694
Copy link
Copy Markdown
Author

KarSri7694 commented Jan 13, 2026

Hello, I would request maintainers to review this code and please tell me if any more changes are required.
Also, is None handling done right?
currently it just converts None to ""

The code currently handles shared_memory = True, by giving a warning using std::cerr, is this correct way?

Copy link
Copy Markdown
Contributor

@p-wysocki p-wysocki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall LGTM, thank you for your contribution! I added a few comments before approval.

Comment on lines +70 to +72
std::cerr << "Warning: Creating a String Constant with shared memory is not supported. "
"Data will be copied."
<< std::endl;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<iostream> is a big header which we don't really need included in our bindings. Instead of std::cerr please use OPENVINO_WARN macro. Example usage: https://github.com/openvinotoolkit/openvino/blob/master/src/bindings/python/src/pyopenvino/graph/node_factory.cpp#L92

}
// convert NumPy array to flattened list of strings
std::string* tensor_data = tensor.data<std::string>();
// std::vector<std::string> strings;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// std::vector<std::string> strings;

@KarSri7694
Copy link
Copy Markdown
Author

Hey @p-wysocki , I will resolve the issue by today

@almilosz almilosz requested a review from Copilot January 13, 2026 12:30
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a segmentation fault when creating constants from NumPy arrays containing strings. It adds support for string-based NumPy arrays in the Python API constructor and converts None values to empty strings.

Changes:

  • Added string array handling logic in the C++ constant constructor to detect and process string/object dtype arrays
  • Implemented conversion of None values to empty strings and type conversion for mixed-type object arrays
  • Added comprehensive test coverage for string constants including edge cases (empty arrays, None values, Unicode, mixed types)

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
src/bindings/python/src/pyopenvino/graph/ops/constant.cpp Implements string array detection and conversion logic in the constant constructor
src/bindings/python/tests/test_graph/test_constant.py Adds 8 test cases covering various string constant scenarios

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

auto flat_array = array.reshape({-1});

// copy data
for (int i = 0; i < flat_array.size(); ++i) {
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use size_t or py::ssize_t instead of int for the loop counter. The flat_array.size() returns a size type, and using int could cause issues with large arrays or sign comparison warnings.

Suggested change
for (int i = 0; i < flat_array.size(); ++i) {
for (py::ssize_t i = 0; i < flat_array.size(); ++i) {

Copilot uses AI. Check for mistakes.
# Note: Warning goes to stderr, check if it appears
captured = capfd.readouterr()
# The warning should be printed to stderr
assert "Warning" in captured.err or len(captured.err) == 0 # May not always capture in test
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assertion doesn't reliably verify the warning behavior. The condition or len(captured.err) == 0 makes the test pass even when no warning is produced, defeating the purpose of testing the warning. Consider using pytest's warns context manager or making the assertion more strict.

Copilot uses AI. Check for mistakes.
}
// convert NumPy array to flattened list of strings
std::string* tensor_data = tensor.data<std::string>();
// std::vector<std::string> strings;
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove commented-out code. This appears to be leftover from development and should be cleaned up.

Suggested change
// std::vector<std::string> strings;

Copilot uses AI. Check for mistakes.
- OPENVINO_WARN outputs to stderr and is not visible when calling from python, so this method is now used. It is still kept in code and can be used while debugging when ENABLE_OPENVINO_DEBUG flag is set to ON
@KarSri7694
Copy link
Copy Markdown
Author

Hey @p-wysocki, I have made the necessary changes, please review.

I have added both OPENVINO_WARN and PyErr_WarnEx.
OPENVINO_WARN can be useful in debugging scenarios and PyErr_WarnEx is used to show RuntimeWarning in python.

I have also updated the test_constant.py to detect updated RuntimeWarning using pytest.warns()

Comment on lines +70 to +76
if (shared_memory) {
OPENVINO_WARN(
"Creating a String Constant with shared memory is not supported. Data will be copied.");
PyErr_WarnEx(PyExc_RuntimeWarning,
"Creating a String Constant with shared memory is not supported. Data will be copied.",
1);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (shared_memory) {
OPENVINO_WARN(
"Creating a String Constant with shared memory is not supported. Data will be copied.");
PyErr_WarnEx(PyExc_RuntimeWarning,
"Creating a String Constant with shared memory is not supported. Data will be copied.",
1);
}
if (shared_memory) {
OPENVINO_WARN(
"Creating a String Constant with shared memory is not supported. Data will be copied.");
}

-The test currently does not assert the statements caught by capfd, this is because the logs are getting supressed in windows build and i cannot find a way to fix it
@KarSri7694
Copy link
Copy Markdown
Author

KarSri7694 commented Jan 14, 2026

Hello,
I have removed the PyErr_WarnEx code as asked, I have also omitted the assert code in test_constant as I am unable to produce logs even after setting OPENVINO_LOG_LEVEL=2 in the environment in my windows build. I think windows might be supressing the logs. Any help is appreciated

@p-wysocki
Copy link
Copy Markdown
Contributor

@KarSri7694 I see you attempted it with captured.err, but that's not the recommended way. Could you please try using with pytest.warns? There are numerous examples in the Python tests.

@p-wysocki
Copy link
Copy Markdown
Contributor

Linters failed:

tests/test_graph/test_constant.py:419:121: E501 line too long (154 > 120 characters)
        [4.75, 4.5, -5.25, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, -0.0, -0.1, -0.2, -0.3, -0.4, -0.5, -0.6, -0.7, -0.8, -0.9, -1, 448, 512],
                                                                                                                        ^
tests/test_graph/test_constant.py:538:121: E501 line too long (153 > 120 characters)
        [4.75, 4.5, 5.25, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, -0.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0, 448, 512, np.nan],
                                                                                                                        ^
tests/test_graph/test_constant.py:552:121: E501 line too long (160 > 120 characters)
    target = [4.0, 4.0, 4.0, 0.0, 0.125, 0.25, 0.25, 0.5, 0.5, 0.5, 0.5, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 512, 512, np.nan]
                                                                                                                        ^
tests/test_graph/test_constant.py:661:121: E501 line too long (154 > 120 characters)
        [4.75, 4.5, -5.25, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, -0.0, -0.1, -0.2, -0.3, -0.4, -0.5, -0.6, -0.7, -0.8, -0.9, -1, 448, 512],
                                                                                                                        ^
tests/test_graph/test_constant.py:722:121: E501 line too long (153 > 120 characters)
        [4.75, 4.5, 5.25, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, -0.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0, 448, 512, np.nan],
                                                                                                                        ^
tests/test_graph/test_constant.py:737:121: E501 line too long (160 > 120 characters)
    target = [4.0, 4.0, 4.0, 0.0, 0.125, 0.25, 0.25, 0.5, 0.5, 0.5, 0.5, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 512, 512, np.nan]
                                                                                                                        ^
tests/test_graph/test_constant.py:1029:5: F841 local variable 'captured' is assigned to but never used
    captured = capfd.readouterr()
    ^

You can run linters by going to the Python directory and running python -m flake8 tests/ --config=setup.cfg.

@KarSri7694
Copy link
Copy Markdown
Author

@KarSri7694 I see you attempted it with captured.err, but that's not the recommended way. Could you please try using with pytest.warns? There are numerous examples in the Python tests.

I looked into using pytest.warns, but there is a compatibility issue:

  1. pytest.warns only captures Python Warning objects (generated by warnings.warn or the C-API PyErr_WarnEx).

  2. OPENVINO_WARN logs directly to the C++ standard output stream. It does not raise a Python exception or warning object, so pytest.warns ignores it completely.

To use pytest.warns as requested, I would need to revert the implementation to use PyErr_WarnEx. To keep OPENVINO_WARN, I must use capfd to verify the log output.

Please let me know which implementation you prefer.

@KarSri7694
Copy link
Copy Markdown
Author

Linters failed:

tests/test_graph/test_constant.py:419:121: E501 line too long (154 > 120 characters)
        [4.75, 4.5, -5.25, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, -0.0, -0.1, -0.2, -0.3, -0.4, -0.5, -0.6, -0.7, -0.8, -0.9, -1, 448, 512],
                                                                                                                        ^
tests/test_graph/test_constant.py:538:121: E501 line too long (153 > 120 characters)
        [4.75, 4.5, 5.25, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, -0.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0, 448, 512, np.nan],
                                                                                                                        ^
tests/test_graph/test_constant.py:552:121: E501 line too long (160 > 120 characters)
    target = [4.0, 4.0, 4.0, 0.0, 0.125, 0.25, 0.25, 0.5, 0.5, 0.5, 0.5, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 512, 512, np.nan]
                                                                                                                        ^
tests/test_graph/test_constant.py:661:121: E501 line too long (154 > 120 characters)
        [4.75, 4.5, -5.25, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, -0.0, -0.1, -0.2, -0.3, -0.4, -0.5, -0.6, -0.7, -0.8, -0.9, -1, 448, 512],
                                                                                                                        ^
tests/test_graph/test_constant.py:722:121: E501 line too long (153 > 120 characters)
        [4.75, 4.5, 5.25, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, -0.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0, 448, 512, np.nan],
                                                                                                                        ^
tests/test_graph/test_constant.py:737:121: E501 line too long (160 > 120 characters)
    target = [4.0, 4.0, 4.0, 0.0, 0.125, 0.25, 0.25, 0.5, 0.5, 0.5, 0.5, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 512, 512, np.nan]
                                                                                                                        ^
tests/test_graph/test_constant.py:1029:5: F841 local variable 'captured' is assigned to but never used
    captured = capfd.readouterr()
    ^

You can run linters by going to the Python directory and running python -m flake8 tests/ --config=setup.cfg.

Will fix it

@KarSri7694
Copy link
Copy Markdown
Author

Hey @p-wysocki, I have implemented the pytest.warns function in test_constant.py file
Linting errors are also fixed.
Please review.

Copy link
Copy Markdown
Contributor

@p-wysocki p-wysocki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, please take a look at my last nit. Thank you for your contribution!


#include "openvino/core/shape.hpp"
#include "openvino/runtime/tensor.hpp"
#include "openvino/util/log.hpp"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which utility is this required for? PyErr_WarnEx is used, but it's a pybind util. If it's not necessary, please remove it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously I was using OPENVINO_WARN, then switched to PyErr_WarnEx, but forgot to remove the header, Will remove its no longer required.

@KarSri7694
Copy link
Copy Markdown
Author

I have made the required changes and removed log header.

@KarSri7694
Copy link
Copy Markdown
Author

Hey @p-wysocki ,
Just a gentle follow-up on this PR.
Please let me know if any changes or updates are required from my side.
Thanks!

@KarSri7694 KarSri7694 requested a review from p-wysocki February 21, 2026 05:34
@mlukasze mlukasze requested a review from Copilot February 25, 2026 08:31
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comment on lines +99 to +100
return std::make_shared<ov::op::v0::Constant>(
Common::object_from_data<ov::op::v0::Constant>(array, shared_memory));
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return statement creates a Constant from another Constant object returned by object_from_data. Since object_from_data already returns an ov::op::v0::Constant, wrapping it in another std::make_shared<ov::op::v0::Constant>() is redundant and may cause issues. Return the result directly instead.

Suggested change
return std::make_shared<ov::op::v0::Constant>(
Common::object_from_data<ov::op::v0::Constant>(array, shared_memory));
return Common::object_from_data<ov::op::v0::Constant>(array, shared_memory);

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

category: Python API OpenVINO Python bindings ExternalPR External contributor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Good First Issue][Python API]: Create constant for string tensor and fix segfault

5 participants