diff --git a/docs/release-notes/changelog.md b/docs/release-notes/changelog.md index 4964b33..74757c2 100644 --- a/docs/release-notes/changelog.md +++ b/docs/release-notes/changelog.md @@ -4,6 +4,17 @@ The changelog presented here outlines changes to PyKX when operating within a Python environment specifically, if you require changelogs associated with PyKX operating under a q environment see [here](./underq-changelog.md). +## PyKX 3.1.4 + +#### Release Date + +2025-07-17 + +### Fixes and Improvements + +- Resolved error on import when `PYKX_THREADING` was set. +- Fixed an issue where data could be corrupted in keyed columns of PyArrow backed Pandas dataframes. + ## PyKX 3.1.3 #### Release Date diff --git a/docs/release-notes/underq-changelog.md b/docs/release-notes/underq-changelog.md index 4d788c2..69ddac5 100644 --- a/docs/release-notes/underq-changelog.md +++ b/docs/release-notes/underq-changelog.md @@ -6,6 +6,37 @@ This changelog provides updates from PyKX 2.0.0 and above, for information relat The changelog presented here outlines changes to PyKX when operating within a q environment specifically, if you require changelogs associated with PyKX operating within a Python environment see [here](./changelog.md). +## PyKX 3.1.4 + +#### Release Date + +2025-07-17 + +### Fixes and Improvements + +- Resolved `object has no attribute 't'` error for certain conversions + + === "Behaviour prior to change" + + ```python + q).pykx.setdefault (),"k"; + q).pykx.import[`numpy.random][`:choice][01b; 10] + 'AttributeError("'numpy.int64' object has no attribute 't'") + [2] /home/user/q/pykx.q:241: .pykx.util.pykx: + ]; + wrap pyfunc[f] . x]; + ^ + ":"~first a0:string x0; + ``` + + === "Behaviour post change" + + ```python + q).pykx.setdefault (),"k"; + q).pykx.import[`numpy.random][`:choice][01b; 10] + 1011111110b + ``` + ## PyKX 3.1.3 #### Release Date diff --git a/docs/user-guide/advanced/ipc.md b/docs/user-guide/advanced/ipc.md index 7b450fa..1086a29 100644 --- a/docs/user-guide/advanced/ipc.md +++ b/docs/user-guide/advanced/ipc.md @@ -92,7 +92,7 @@ In the below examples you can connect to a process on port 5050 and run a query. ```python >>> conn = await kx.AsyncQConnection('localhost', 5050) - >>> print(await conn('til 10').py()) + >>> print((await conn('til 10')).py()) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> conn.close() ``` diff --git a/docs/user-guide/advanced/numpy.md b/docs/user-guide/advanced/numpy.md index cf49338..c534d1e 100644 --- a/docs/user-guide/advanced/numpy.md +++ b/docs/user-guide/advanced/numpy.md @@ -11,7 +11,7 @@ _This page explains how to integrate PyKX with NumPy._ PyKX is designed for advanced integration with NumPy. This integration is built on three pillars: -- [NEP-49](https://numpy.org/neps/nep-0049.html) +- [NEP-49](https://numpy.org/neps/nep-0049-data-allocation-strategies.html) - the NumPy [array interface](https://numpy.org/doc/stable/reference/arrays.interface.html) - [universal functions](https://numpy.org/doc/stable/reference/ufuncs.html) diff --git a/pyproject.toml b/pyproject.toml index 28d33f7..ee5dc89 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -126,6 +126,7 @@ test = [ "pytest-randomly==3.11.0", "pytest-xdist==2.5.0", "pytest-order==1.1.0", + "pytest-rerunfailures==10.3", "psutil>=5.0.0", "pytest-timeout>=2.0.0", "IPython", diff --git a/src/pykx/core.pyx b/src/pykx/core.pyx index 6068818..a35d287 100644 --- a/src/pykx/core.pyx +++ b/src/pykx/core.pyx @@ -241,7 +241,7 @@ def _link_qhome(): warn('Unable to connect user QHOME to PyKX QHOME via symlinks.\n' # nocov 'To permanently disable attempts to create symlinks you can\n' # nocov '\t1. Set the environment variable "PYKX_IGNORE_QHOME" = True.\n' # nocov - '\t2. Update the file ".pykx.config" using kx.util.add_to_config({\'PYKX_IGNORE_QHOME\': \'True\'})\n' # nocov + '\t2. Update the file ".pykx-config" using kx.util.add_to_config({\'PYKX_IGNORE_QHOME\': \'True\'})\n' # nocov f'Error: {ex}\n', # nocov PyKXWarning) # nocov return # nocov diff --git a/src/pykx/embedded_q.py b/src/pykx/embedded_q.py index 3bfa259..0379adf 100644 --- a/src/pykx/embedded_q.py +++ b/src/pykx/embedded_q.py @@ -214,7 +214,7 @@ def __call__(self, query: The code to run in the q instance. *args: Arguments to the q query. Each argument is converted into a `#!python pykx.K` object. This parameter supports up to 8 args (the maximum amount - supported by q functions), providing more causesw an error. + supported by q functions), providing more causes an error. wait: A keyword to allow users to call any `#!python pykx.EmbeddedQ` or `#!python pykx.QConnection` instance the same way. All queries executed by this function are synchronous on the embedded q instance. Using a `#!python False` diff --git a/src/pykx/nbextension.py b/src/pykx/nbextension.py index 250a7e0..0955a43 100644 --- a/src/pykx/nbextension.py +++ b/src/pykx/nbextension.py @@ -188,7 +188,7 @@ def write_to_q_file(_q, locked, path, code): if locked: output_file = Path(path[:-1]) _q('0:', output_file, [kx.CharVector(code)]) - _q('\_ ' + path[:-1]) + _q('\\_ ' + path[:-1]) _q('hdel', output_file) else: output_file = Path(path) diff --git a/src/pykx/pykx.c b/src/pykx/pykx.c index adafe65..0a9b75e 100644 --- a/src/pykx/pykx.c +++ b/src/pykx/pykx.c @@ -217,7 +217,7 @@ static PyObject* k_to_py_list(K x) { EXPORT K k_to_py_foreign(K x, K typenum, K israw) { K k; if (pykx_threading) - return raise_k_error("pykx.q is not supported when using PYKX_THREADING"); + return raise_k_error("pyForeign is not supported when using PYKX_THREADING"); PyGILState_STATE gstate; gstate = PyGILState_Ensure(); PyObject* p = k_to_py_cast(x, typenum, israw); @@ -291,7 +291,7 @@ void construct_args_kwargs(PyObject* params, PyObject** args, PyObject** kwargs, EXPORT K k_pyrun(K k_ret, K k_eval_or_exec, K as_foreign, K k_code_string) { if (pykx_threading) - return raise_k_error("pykx.q is not supported when using PYKX_THREADING"); + return raise_k_error("pyrun is not supported when using PYKX_THREADING"); PyGILState_STATE gstate; gstate = PyGILState_Ensure(); K k; @@ -435,7 +435,7 @@ EXPORT K k_modpow(K k_base, K k_exp, K k_mod_arg) { EXPORT K foreign_to_q(K f, K b) { if (pykx_threading) - return raise_k_error("pykx.q is not supported when using PYKX_THREADING"); + return raise_k_error("foreignToq is not supported when using PYKX_THREADING"); if (f->t != 112) return raise_k_error("Expected foreign object for call to .pykx.toq"); if (!check_py_foreign(f)) @@ -483,7 +483,7 @@ EXPORT K foreign_to_q(K f, K b) { EXPORT K repr(K as_repr, K f) { if (pykx_threading) - return raise_k_error("pykx.q is not supported when using PYKX_THREADING"); + return raise_k_error("repr is not supported when using PYKX_THREADING"); K k; if (f->t != 112) { if (as_repr->g){ @@ -526,7 +526,7 @@ EXPORT K repr(K as_repr, K f) { EXPORT K get_attr(K f, K attr) { if (pykx_threading) - return raise_k_error("pykx.q is not supported when using PYKX_THREADING"); + return raise_k_error("getattr is not supported when using PYKX_THREADING"); K k; if (f->t != 112) { if (f->t == 105) { @@ -555,7 +555,7 @@ EXPORT K get_attr(K f, K attr) { EXPORT K get_global(K attr) { if (pykx_threading) - return raise_k_error("pykx.q is not supported when using PYKX_THREADING"); + return raise_k_error("getGlobal is not supported when using PYKX_THREADING"); K k; if (attr->t != -11) { return raise_k_error("Expected a SymbolAtom for the attribute to get in .pykx.get"); @@ -582,7 +582,7 @@ EXPORT K get_global(K attr) { EXPORT K set_global(K attr, K val) { if (pykx_threading) - return raise_k_error("pykx.q is not supported when using PYKX_THREADING"); + return raise_k_error("setGlobal is not supported when using PYKX_THREADING"); K k; int gstate = PyGILState_Ensure(); @@ -608,7 +608,7 @@ EXPORT K set_global(K attr, K val) { EXPORT K set_attr(K f, K attr, K val) { if (pykx_threading) - return raise_k_error("pykx.q is not supported when using PYKX_THREADING"); + return raise_k_error("setattr is not supported when using PYKX_THREADING"); if (f->t != 112) { if (f->t == 105) { return raise_k_error("Expected foreign object for call to .pykx.setattr, try unwrapping the foreign object with `."); @@ -638,7 +638,7 @@ EXPORT K set_attr(K f, K attr, K val) { EXPORT K import(K module) { if (pykx_threading) - return raise_k_error("pykx.q is not supported when using PYKX_THREADING"); + return raise_k_error("import is not supported when using PYKX_THREADING"); K k; K res; if (module->t != -11) @@ -660,7 +660,7 @@ EXPORT K import(K module) { EXPORT K call_func(K f, K has_no_args, K args, K kwargs) { if (pykx_threading) - return raise_k_error("pykx.q is not supported when using PYKX_THREADING"); + return raise_k_error("import is not supported when using PYKX_THREADING"); K k; PyObject* pyf = NULL; diff --git a/src/pykx/pykx_init.q_ b/src/pykx/pykx_init.q_ deleted file mode 100644 index e81c0a5..0000000 Binary files a/src/pykx/pykx_init.q_ and /dev/null differ diff --git a/src/pykx/q.so/qk/pykx_init.q_ b/src/pykx/q.so/qk/pykx_init.q_ index e81c0a5..c312fca 100644 Binary files a/src/pykx/q.so/qk/pykx_init.q_ and b/src/pykx/q.so/qk/pykx_init.q_ differ diff --git a/src/pykx/toq.pyx b/src/pykx/toq.pyx index c2e1096..6e45dbd 100644 --- a/src/pykx/toq.pyx +++ b/src/pykx/toq.pyx @@ -1782,7 +1782,7 @@ def from_pandas_index(x: pd.Index, index_dict = from_dict(d) return factory(core.xT(core.r1(_k(index_dict))), False) elif isinstance(x, _supported_pandas_index_types_via_numpy): - return from_numpy_ndarray(x.to_numpy(), cast=cast, handle_nulls=handle_nulls) + return from_numpy_ndarray(x.to_numpy().copy(), cast=cast, handle_nulls=handle_nulls) else: raise _conversion_TypeError(x, 'Pandas index', ktype) diff --git a/src/pykx/wrappers.py b/src/pykx/wrappers.py index 1160942..9766a92 100644 --- a/src/pykx/wrappers.py +++ b/src/pykx/wrappers.py @@ -594,7 +594,7 @@ def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): **kwargs ) - if res.t >= 0: + if isinstance(res, K) and res.t >= 0: res = res._unlicensed_getitem(0) return res diff --git a/tests/__init__.py b/tests/__init__.py index 6d31cca..b9b542e 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -12,16 +12,6 @@ os.environ['PYTHONWARNINGS'] = 'ignore:No data was collected,ignore:Module pykx was never imported' - -# Addition of configuration toml used in testing -# The configuration values set here are the default values for the PyKX so should not -# overwrite test behavior -config_file = open(Path.home()/".pykx.config", "w") -config_content = {"default": {"PYKX_KEEP_LOCAL_TIMES", 0}} -toml.dump(config_content, config_file) -config_file.close() - - if system() != 'Windows': if threading.current_thread() == threading.main_thread(): signal.signal(signal.SIGUSR1, lambda *_: None) diff --git a/tests/qcumber_tests/import.quke b/tests/qcumber_tests/import.quke index 8101657..be97a89 100644 --- a/tests/qcumber_tests/import.quke +++ b/tests/qcumber_tests/import.quke @@ -17,3 +17,10 @@ feature .pykx.import @[{.pykx.import x; 0b}; "a"; {x like "Module to be imported must be a symbol"}] + + should allow generation of random objects + expect generation without errors + def:.pykx.setdefault (),"k"; + imp:.pykx.import[`numpy.random][`:choice][01b; 10]`; + 10 = count imp + \ No newline at end of file diff --git a/tests/test_cloud_edition.py b/tests/test_cloud_edition.py index 3d819c6..bbdc363 100644 --- a/tests/test_cloud_edition.py +++ b/tests/test_cloud_edition.py @@ -262,6 +262,10 @@ def test_objstor_aws_read1(q, kx): assert isinstance(res, kx.ByteVector) +@pytest.mark.skipif( + os.getenv('PYKX_THREADING') is not None, + reason='ToDo investigate in KXI-63220' +) def test_qlog_fd_stdout_endpoint(q): """Test STDOUT endpoint creation.""" q('.com_kx_log.fd.i.write:{.fd.cache,:enlist(x;y)}') @@ -274,6 +278,10 @@ def test_qlog_fd_stdout_endpoint(q): q('.com_kx_log.lcloseAll[]') +@pytest.mark.skipif( + os.getenv('PYKX_THREADING') is not None, + reason='ToDo investigate in KXI-63220' +) def test_qlog_fd_stderr_setup(q): """Tests creation of stderr endpoint.""" q('id:.com_kx_log.lopen[`:fd://stderr]') @@ -282,6 +290,10 @@ def test_qlog_fd_stderr_setup(q): q('.com_kx_log.lcloseAll[]') +@pytest.mark.skipif( + os.getenv('PYKX_THREADING') is not None, + reason='ToDo investigate in KXI-63220' +) def test_qlog_fd_stdout_log_string(q): """Test default string logging.""" q('.com_kx_log.fd.i.write:{.fd.cache,:enlist(x;y)}') @@ -300,6 +312,10 @@ def test_qlog_fd_stdout_log_string(q): q('.com_kx_log.lcloseAll[]') +@pytest.mark.skipif( + os.getenv('PYKX_THREADING') is not None, + reason='ToDo investigate in KXI-63220' +) def test_qlog_fd_stdout_custom_log(q): """Tests custom string logging.""" q('.com_kx_log.fd.i.write:{.fd.cache,:enlist(x;y)}') @@ -316,6 +332,10 @@ def test_qlog_fd_stdout_custom_log(q): q('.com_kx_log.lcloseAll[]') +@pytest.mark.skipif( + os.getenv('PYKX_THREADING') is not None, + reason='ToDo investigate in KXI-63220' +) def test_qlog_fd_stdout_dict_log(q): """Tests dictionary message logging.""" q('.com_kx_log.fd.i.write:{.fd.cache,:enlist(x;y)}') @@ -330,6 +350,10 @@ def test_qlog_fd_stdout_dict_log(q): q('.com_kx_log.lcloseAll[]') +@pytest.mark.skipif( + os.getenv('PYKX_THREADING') is not None, + reason='ToDo investigate in KXI-63220' +) def test_qlog_fd_file_publish(q): """Tests logging message to file.""" q('.com_kx_log.fd.i.write:{.fd.cache,:enlist(x;y)}') diff --git a/tests/test_sql.py b/tests/test_sql.py index 01da07e..336c9aa 100644 --- a/tests/test_sql.py +++ b/tests/test_sql.py @@ -1,4 +1,5 @@ # Do not import pykx here - use the `kx` fixture instead! +import os from abc import ABCMeta import pytest @@ -163,6 +164,10 @@ def test_sql_get_input_values(q, kx): assert q.sql.get_input_types(p2) == ['SymbolAtom/SymbolVector', 'FloatAtom/FloatVector'] +@pytest.mark.skipif( + os.getenv('PYKX_THREADING') is not None, + reason='ToDo investigate in KXI-63220' +) @pytest.mark.embedded def test_sql_string_col(q): q('t:([] optid:1 2 3;Market:`a`b`CBOE;date:3#2023.11.14;Symbol:("a";"b";"odMP=20"))') diff --git a/tests/win_tests.bat b/tests/win_tests.bat index 0cbfff6..5435a21 100644 --- a/tests/win_tests.bat +++ b/tests/win_tests.bat @@ -2,21 +2,21 @@ python .\parse_tests.py cd .. -python -m pytest -vvv -n 0 --no-cov --junitxml=licensed_report.xml .\tests\win_tests\lic\licensed_tests.py +python -m pytest -vvv -n 0 --no-cov --color=yes --reruns=5 --junitxml=licensed_report.xml .\tests\win_tests\lic\licensed_tests.py SET /A licensed = %ERRORLEVEL% -python -m pytest -vvv -n 0 --no-cov --junitxml=unlicensed_report.xml .\tests\win_tests\unlic\unlicensed_tests.py +python -m pytest -vvv -n 0 --no-cov --color=yes --reruns=5 --junitxml=unlicensed_report.xml .\tests\win_tests\unlic\unlicensed_tests.py SET /A unlicensed = %ERRORLEVEL% -python -m pytest -vvv -n 0 --no-cov --junitxml=ipc_licensed_report.xml .\tests\win_tests\ipc_lic\ipc_licensed_tests.py +python -m pytest -vvv -n 0 --no-cov --color=yes --reruns=5 --junitxml=ipc_licensed_report.xml .\tests\win_tests\ipc_lic\ipc_licensed_tests.py SET /A ipc_licensed = %ERRORLEVEL% -python -m pytest -vvv -n 0 --no-cov --junitxml=ipc_unlicensed_report.xml .\tests\win_tests\ipc_unlic\ipc_unlicensed_tests.py +python -m pytest -vvv -n 0 --no-cov --color=yes --reruns=5 --junitxml=ipc_unlicensed_report.xml .\tests\win_tests\ipc_unlic\ipc_unlicensed_tests.py SET /A ipc_unlicensed = %ERRORLEVEL% -python -m pytest -vvv -n 0 --no-cov --junitxml=embedded_report.xml .\tests\win_tests\embedded\embedded_tests.py +python -m pytest -vvv -n 0 --no-cov --color=yes --reruns=5 --junitxml=embedded_report.xml .\tests\win_tests\embedded\embedded_tests.py SET /A embedded = %ERRORLEVEL% -python -m pytest -vvv -n 0 --no-cov --junitxml=nep_licensed_report.xml .\tests\win_tests\nep_lic\nep_licensed_tests.py +python -m pytest -vvv -n 0 --no-cov --color=yes --reruns=5 --junitxml=nep_licensed_report.xml .\tests\win_tests\nep_lic\nep_licensed_tests.py SET /A nep_licensed = %ERRORLEVEL% -python -m pytest -vvv -n 0 --no-cov --junitxml=nep_unlicensed_report.xml .\tests\win_tests\nep_unlic\nep_unlicensed_tests.py +python -m pytest -vvv -n 0 --no-cov --color=yes --reruns=5 --junitxml=nep_unlicensed_report.xml .\tests\win_tests\nep_unlic\nep_unlicensed_tests.py SET /A nep_unlicensed = %ERRORLEVEL% -python -m pytest -vvv -n 0 --no-cov --junitxml=pandas_licensed_report.xml .\tests\win_tests\pandas_lic\pandas_licensed_tests.py +python -m pytest -vvv -n 0 --no-cov --color=yes --reruns=5 --junitxml=pandas_licensed_report.xml .\tests\win_tests\pandas_lic\pandas_licensed_tests.py SET /A pandas_licensed = %ERRORLEVEL% IF %licensed% NEQ 0 ( exit %licensed%