pykx 4.2.0__cp315-cp315-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pykx/__init__.py +586 -0
- pykx/_ipc.c +13916 -0
- pykx/_ipc.cp315-win_amd64.pyd +0 -0
- pykx/_ipc.pxd +10 -0
- pykx/_ipc.pyx +103 -0
- pykx/_numpy.c +171 -0
- pykx/_numpy.cp315-win_amd64.pyd +0 -0
- pykx/_pyarrow.py +68 -0
- pykx/_pykx_helpers.c +35 -0
- pykx/_pykx_helpers.cp315-win_amd64.pyd +0 -0
- pykx/_streamlit_conn.py +238 -0
- pykx/_tcore.c +622 -0
- pykx/_version.py +21 -0
- pykx/_wrappers.c +51599 -0
- pykx/_wrappers.cp315-win_amd64.pyd +0 -0
- pykx/_wrappers.pxd +15 -0
- pykx/_wrappers.pyx +649 -0
- pykx/cast.py +137 -0
- pykx/compress_encrypt.py +193 -0
- pykx/config.py +651 -0
- pykx/console.py +68 -0
- pykx/constants.py +11 -0
- pykx/core.c +25650 -0
- pykx/core.cp315-win_amd64.pyd +0 -0
- pykx/core.pxd +114 -0
- pykx/core.pyx +518 -0
- pykx/ctx.py +265 -0
- pykx/db.py +1348 -0
- pykx/docs/api/pykx-execution/q.md +2839 -0
- pykx/embedded_q.py +268 -0
- pykx/exceptions.py +97 -0
- pykx/extensions/chained_tick.q +39 -0
- pykx/extensions/dashboards.q +129 -0
- pykx/extensions/gateway.q +44 -0
- pykx/extensions/hdb.q +18 -0
- pykx/extensions/plant.q +77 -0
- pykx/extensions/rdb.q +49 -0
- pykx/help.py +215 -0
- pykx/include/foreign.h +55 -0
- pykx/include/k.h +196 -0
- pykx/include/py.h +197 -0
- pykx/include/unlicensed_getitem.h +34 -0
- pykx/include/vector_conversion.h +30 -0
- pykx/ipc.py +3141 -0
- pykx/lib/bq.q_ +0 -0
- pykx/lib/csvutil.q +137 -0
- pykx/lib/dbmaint.q +158 -0
- pykx/lib/html.q +94 -0
- pykx/lib/kurl.q_ +0 -0
- pykx/lib/kurl.sidecar.q_ +0 -0
- pykx/lib/kxic.k +23 -0
- pykx/lib/l64/.gitkeep +0 -0
- pykx/lib/l64/kxreaper +0 -0
- pykx/lib/l64/libe.so +0 -0
- pykx/lib/l64/libkurl.so +0 -0
- pykx/lib/l64/libobjstor.so +0 -0
- pykx/lib/l64/libq.so +0 -0
- pykx/lib/l64/pg +0 -0
- pykx/lib/l64arm/.gitkeep +0 -0
- pykx/lib/l64arm/libe.so +0 -0
- pykx/lib/l64arm/libq.so +0 -0
- pykx/lib/licmet.q_ +0 -0
- pykx/lib/m64/.gitkeep +0 -0
- pykx/lib/m64/libe.so +0 -0
- pykx/lib/m64/libq.dylib +0 -0
- pykx/lib/m64arm/.gitkeep +0 -0
- pykx/lib/m64arm/libe.so +0 -0
- pykx/lib/m64arm/libq.dylib +0 -0
- pykx/lib/objstor.q_ +5 -0
- pykx/lib/pykx.q +2033 -0
- pykx/lib/pykx_init.q_ +0 -0
- pykx/lib/qlog.q_ +0 -0
- pykx/lib/read.q +46 -0
- pykx/lib/rest.q_ +0 -0
- pykx/lib/sql.k_ +0 -0
- pykx/lib/w64/.gitkeep +0 -0
- pykx/lib/w64/e.dll +0 -0
- pykx/lib/w64/e.lib +0 -0
- pykx/lib/w64/q.dll +0 -0
- pykx/lib/w64/qdll.lib +0 -0
- pykx/lib/write.q +44 -0
- pykx/license.py +231 -0
- pykx/module.py +503 -0
- pykx/nbextension.py +207 -0
- pykx/numpy_conversions.c +62 -0
- pykx/numpy_conversions.cp315-win_amd64.pyd +0 -0
- pykx/p.k +1 -0
- pykx/pandas_api/__init__.py +114 -0
- pykx/pandas_api/pandas_apply.py +79 -0
- pykx/pandas_api/pandas_conversions.py +412 -0
- pykx/pandas_api/pandas_indexing.py +883 -0
- pykx/pandas_api/pandas_map.py +94 -0
- pykx/pandas_api/pandas_merge.py +592 -0
- pykx/pandas_api/pandas_meta.py +1119 -0
- pykx/pandas_api/pandas_native_agg.py +192 -0
- pykx/pandas_api/pandas_replace.py +47 -0
- pykx/pandas_api/pandas_reset_index.py +84 -0
- pykx/pandas_api/pandas_set_index.py +71 -0
- pykx/pandas_api/pandas_sorting.py +161 -0
- pykx/pykx.c +830 -0
- pykx/pykx.dll +0 -0
- pykx/pykx.exp +0 -0
- pykx/pykx.lib +0 -0
- pykx/pykx.q +2033 -0
- pykx/pykx_init.q_ +0 -0
- pykx/pykxq.c +830 -0
- pykx/pykxq.dll +0 -0
- pykx/pykxq.exp +0 -0
- pykx/pykxq.lib +0 -0
- pykx/python.c +23 -0
- pykx/q.so/libs/kdbx/l64/kxreaper +0 -0
- pykx/q.so/libs/kdbx/l64/libkurl.so +0 -0
- pykx/q.so/libs/kdbx/l64/libobjstor.so +0 -0
- pykx/q.so/libs/kdbx/l64/libq.so +0 -0
- pykx/q.so/libs/kdbx/l64/pg +0 -0
- pykx/q.so/libs/kdbx/l64/symbols.txt +50 -0
- pykx/q.so/libs/kdbx/l64arm/libq.so +0 -0
- pykx/q.so/libs/kdbx/m64/libq.dylib +0 -0
- pykx/q.so/libs/kdbx/m64arm/libq.dylib +0 -0
- pykx/q.so/libs/kdbx/w64/q.dll +0 -0
- pykx/q.so/libs/kdbx/w64/qdll.lib +0 -0
- pykx/q.so/qk/bq.q_ +0 -0
- pykx/q.so/qk/kurl.q_ +0 -0
- pykx/q.so/qk/kurl.sidecar.q_ +0 -0
- pykx/q.so/qk/licmet.q_ +0 -0
- pykx/q.so/qk/objstor.q_ +5 -0
- pykx/q.so/qk/pykx_init.q_ +0 -0
- pykx/q.so/qk/qlog.q_ +0 -0
- pykx/q.so/qk/rest.q_ +0 -0
- pykx/q.so/qk/sql.k_ +0 -0
- pykx/query.py +1034 -0
- pykx/random.py +129 -0
- pykx/read.py +341 -0
- pykx/register.py +166 -0
- pykx/reimporter.py +73 -0
- pykx/remote.py +236 -0
- pykx/schema.py +170 -0
- pykx/serialize.py +150 -0
- pykx/streamlit.py +24 -0
- pykx/system.py +524 -0
- pykx/tick.py +1811 -0
- pykx/toq.c +64075 -0
- pykx/toq.cp315-win_amd64.pyd +0 -0
- pykx/toq.pyx +3218 -0
- pykx/util.py +844 -0
- pykx/wrappers.py +11922 -0
- pykx/write.py +196 -0
- pykx-4.2.0.dist-info/LICENSE.txt +265 -0
- pykx-4.2.0.dist-info/METADATA +647 -0
- pykx-4.2.0.dist-info/RECORD +152 -0
- pykx-4.2.0.dist-info/WHEEL +5 -0
- pykx-4.2.0.dist-info/top_level.txt +1 -0
pykx/__init__.py
ADDED
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
"""
|
|
2
|
+
An interface between Python and q.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import base64
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
if os.getenv('PYKX_LOADING_Q', '') == '':
|
|
11
|
+
os.environ['PYKX_LOADING_PYTHON'] = 'true'
|
|
12
|
+
|
|
13
|
+
if os.getenv('PYKX_OLD_QHOME', '') != '' and os.getenv('PYKX_LOADING_PYTHON', '') != '':
|
|
14
|
+
os.environ['QHOME'] = os.getenv('PYKX_OLD_QHOME', "")
|
|
15
|
+
if os.getenv('PYKX_OLD_QPATH', '') != '' and os.getenv('PYKX_LOADING_PYTHON', '') != '':
|
|
16
|
+
os.environ['QPATH'] = os.getenv('PYKX_OLD_QPATH', "")
|
|
17
|
+
|
|
18
|
+
if os.getenv('PYKX_LOADED_UNDER_Q') == 'True':
|
|
19
|
+
sys.exit(0)
|
|
20
|
+
|
|
21
|
+
# Attempt to import PyArrow early to get ahead of others (e.g. Pandas) who would try to import it
|
|
22
|
+
# without guarding against segfaults. Skip this if we're just doing a qinit check.
|
|
23
|
+
if os.getenv('PYKX_QINIT_CHECK', '') == '':
|
|
24
|
+
try:
|
|
25
|
+
from ._pyarrow import pyarrow
|
|
26
|
+
except ImportError: # nocov
|
|
27
|
+
pass
|
|
28
|
+
else: # nocov
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# List of beta features available in the current KDB-X Python version
|
|
33
|
+
beta_features = []
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
from . import reimporter
|
|
37
|
+
# Importing core initializes q if in licensed mode, and loads the q C API symbols. This should
|
|
38
|
+
# happen early on so that if the qinit check is currently happening then no time is wasted.
|
|
39
|
+
# The current process will exit while the core module is loading if the qinit check is happening.
|
|
40
|
+
from . import core
|
|
41
|
+
|
|
42
|
+
from abc import ABCMeta, abstractmethod
|
|
43
|
+
import itertools as it
|
|
44
|
+
from pathlib import Path
|
|
45
|
+
import platform
|
|
46
|
+
import shutil
|
|
47
|
+
import signal
|
|
48
|
+
from typing import Any, List, Optional, Union
|
|
49
|
+
from warnings import warn
|
|
50
|
+
from weakref import proxy
|
|
51
|
+
|
|
52
|
+
from .config import k_allocator, licensed, no_pykx_signal, pykx_lib_dir, pykx_platlib_dir, under_q
|
|
53
|
+
from . import util
|
|
54
|
+
|
|
55
|
+
if platform.system() == 'Windows': # nocov
|
|
56
|
+
os.environ['PATH'] += f';{pykx_platlib_dir}'
|
|
57
|
+
if platform.python_version_tuple()[:2] >= ('3', '8'):
|
|
58
|
+
os.add_dll_directory(pykx_platlib_dir)
|
|
59
|
+
|
|
60
|
+
# Cache initialised signal values prior to KDB-X Python loading
|
|
61
|
+
_signal_list = [
|
|
62
|
+
'signal.SIGINT',
|
|
63
|
+
'signal.SIGTERM',
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
_signal_dict = {}
|
|
67
|
+
|
|
68
|
+
for i in _signal_list:
|
|
69
|
+
_signal_dict[i] = signal.getsignal(eval(i))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _first_resolved_path(possible_paths: List[Union[str, Path]]) -> Path:
|
|
73
|
+
"""Returns the resolved version of the first path that exists."""
|
|
74
|
+
for path in possible_paths:
|
|
75
|
+
try:
|
|
76
|
+
return Path(path).resolve(strict=True)
|
|
77
|
+
except FileNotFoundError:
|
|
78
|
+
pass
|
|
79
|
+
unfound_paths = '\n'.join(str(path) for path in possible_paths)
|
|
80
|
+
raise FileNotFoundError(f'Could not find any of the following files:\n{unfound_paths}')
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class Q(metaclass=ABCMeta):
|
|
84
|
+
"""Abstract base class for all interfaces between Python and q.
|
|
85
|
+
|
|
86
|
+
See Also:
|
|
87
|
+
- [`pykx.EmbeddedQ`][pykx.EmbeddedQ]
|
|
88
|
+
- [`pykx.QConnection`][pykx.QConnection]
|
|
89
|
+
"""
|
|
90
|
+
reserved_words = {
|
|
91
|
+
'abs', 'acos', 'aj', 'aj0', 'ajf', 'ajf0', 'all', 'and', 'any', 'asc',
|
|
92
|
+
'asin', 'asof', 'atan', 'attr', 'avg', 'avgs', 'bin',
|
|
93
|
+
'binr', 'ceiling', 'cols', 'cor', 'cos', 'count', 'cov', 'cross',
|
|
94
|
+
'csv', 'cut', 'delete', 'deltas', 'desc', 'dev', 'differ', 'distinct',
|
|
95
|
+
'div', 'do', 'dsave', 'each', 'ej', 'ema', 'enlist', 'eval',
|
|
96
|
+
'except', 'exec', 'exit', 'exp', 'fby', 'fills', 'first', 'fkeys',
|
|
97
|
+
'flip', 'floor', 'get', 'getenv', 'group', 'gtime', 'hclose', 'hcount', 'hdel',
|
|
98
|
+
'hopen', 'hsym', 'iasc', 'idesc', 'if', 'ij', 'ijf', 'in',
|
|
99
|
+
'inter', 'inv', 'key', 'keys', 'last', 'like', 'lj', 'ljf',
|
|
100
|
+
'load', 'log', 'lower', 'lsq', 'ltime', 'ltrim', 'mavg', 'max', 'maxs',
|
|
101
|
+
'mcount', 'md5', 'mdev', 'med', 'meta', 'min', 'mins', 'mmax', 'mmin', 'mmu', 'mod',
|
|
102
|
+
'msum', 'neg', 'next', 'not', 'null', 'or', 'over', 'parse', 'peach', 'pj', 'prd',
|
|
103
|
+
'prds', 'prev', 'prior', 'rand', 'rank', 'ratios', 'raze', 'read0', 'read1', 'reciprocal',
|
|
104
|
+
'reval', 'reverse', 'rload', 'rotate', 'rsave', 'rtrim', 'save', 'scan', 'scov', 'sdev',
|
|
105
|
+
'select', 'set', 'setenv', 'show', 'signum', 'sin',
|
|
106
|
+
'sqrt', 'ss', 'ssr', 'string', 'sublist', 'sum', 'sums', 'sv', 'svar',
|
|
107
|
+
'system', 'tables', 'tan', 'til', 'trim', 'type', 'uj', 'ujf', 'ungroup',
|
|
108
|
+
'union', 'update', 'upper', 'value', 'var', 'view', 'views',
|
|
109
|
+
'vs', 'wavg', 'where', 'while', 'within', 'wj', 'wj1',
|
|
110
|
+
'wsum', 'xasc', 'xbar', 'xcol', 'xcols', 'xdesc', 'xexp', 'xgroup', 'xkey', 'xlog',
|
|
111
|
+
'xprev', 'xrank'
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
operators = {
|
|
115
|
+
'drop': '_',
|
|
116
|
+
'coalesce': '^', 'fill': '^',
|
|
117
|
+
'take': '#', 'set_attribute': '#',
|
|
118
|
+
'join': ',',
|
|
119
|
+
'find': '?', 'enum_extend': '?', 'roll': '?', 'deal': '?',
|
|
120
|
+
'dict': '!', 'enkey': '!', 'unkey': '!', 'enumeration': '!',
|
|
121
|
+
'enumerate': '$', 'pad': '$', 'cast': '$', 'tok': '$',
|
|
122
|
+
'compose': '\''
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
def __init__(self):
|
|
126
|
+
self.paths = default_paths
|
|
127
|
+
# Start with an empty set to avoid errors during initialization.
|
|
128
|
+
object.__setattr__(self, '_q_ctx_keys', set())
|
|
129
|
+
# HACK: `'_connection_info' in self.__dict__` is used as a proxy for a type-check to avoid a
|
|
130
|
+
# cyclic import.
|
|
131
|
+
if licensed or '_connection_info' in self.__dict__:
|
|
132
|
+
if '_connection_info' in self.__dict__ and self._connection_info['no_ctx']:
|
|
133
|
+
object.__setattr__(self, 'ctx', QContext(proxy(self), '', None, no_ctx=True))
|
|
134
|
+
else:
|
|
135
|
+
object.__setattr__(self, 'ctx', QContext(proxy(self), '', None))
|
|
136
|
+
object.__setattr__(self, '_q_ctx_keys', {
|
|
137
|
+
*self.ctx.q._context_keys,
|
|
138
|
+
*self.reserved_words,
|
|
139
|
+
})
|
|
140
|
+
object.__setattr__(self, 'console', QConsole(proxy(self)))
|
|
141
|
+
object.__setattr__(self, 'insert', Insert(proxy(self)))
|
|
142
|
+
object.__setattr__(self, 'upsert', Upsert(proxy(self)))
|
|
143
|
+
object.__setattr__(self, 'query', None) # placeholder
|
|
144
|
+
object.__setattr__(self, 'qsql', QSQL(proxy(self)))
|
|
145
|
+
object.__setattr__(self, 'sql', SQL(proxy(self)))
|
|
146
|
+
object.__setattr__(self, 'read', QReader(proxy(self)))
|
|
147
|
+
object.__setattr__(self, 'write', QWriter(proxy(self)))
|
|
148
|
+
object.__setattr__(self, 'system', SystemCommands(proxy(self)))
|
|
149
|
+
|
|
150
|
+
@abstractmethod
|
|
151
|
+
def __call__(self, query: str, *args, wait: Optional[bool] = None):
|
|
152
|
+
pass # nocov
|
|
153
|
+
|
|
154
|
+
def __getattr__(self, key):
|
|
155
|
+
if key == "__objclass__":
|
|
156
|
+
raise AttributeError
|
|
157
|
+
# Elevate the q context to the global context, as is done in q normally
|
|
158
|
+
try:
|
|
159
|
+
ctx = self.__getattribute__('ctx')
|
|
160
|
+
except BaseException:
|
|
161
|
+
raise exceptions.QError('Cannot load requested context object in unlicensed mode')
|
|
162
|
+
if key in self.__getattribute__('_q_ctx_keys'):
|
|
163
|
+
# if-statement used instead of try-block for performance
|
|
164
|
+
return ctx.q.__getattr__(key)
|
|
165
|
+
try:
|
|
166
|
+
return ctx.__getattr__(key)
|
|
167
|
+
except AttributeError as attribute_error:
|
|
168
|
+
try:
|
|
169
|
+
self.__getattribute__('_register')(name=key)
|
|
170
|
+
ctx._invalidate_cache()
|
|
171
|
+
return ctx.__getattr__(key)
|
|
172
|
+
except Exception as inner_error:
|
|
173
|
+
raise attribute_error from inner_error
|
|
174
|
+
|
|
175
|
+
# __setattr__ takes precedence over data descriptors, so we implement a custom one that uses
|
|
176
|
+
# the default behavior for select keys
|
|
177
|
+
def __setattr__(self, key, value):
|
|
178
|
+
if key in self.__dict__ or key in Q.__dict__:
|
|
179
|
+
object.__setattr__(self, key, value)
|
|
180
|
+
else:
|
|
181
|
+
if key in self.reserved_words or key in self.q:
|
|
182
|
+
raise exceptions.PyKXException(
|
|
183
|
+
'Cannot assign to reserved word or overwrite q namespace.'
|
|
184
|
+
)
|
|
185
|
+
self._call('set', f'.{key}', value, wait=True)
|
|
186
|
+
|
|
187
|
+
def __delattr__(self, key):
|
|
188
|
+
self.ctx.__delattr__(key)
|
|
189
|
+
|
|
190
|
+
def __getitem__(self, key):
|
|
191
|
+
return self._call('get', key, wait=True)
|
|
192
|
+
|
|
193
|
+
def __setitem__(self, key, value):
|
|
194
|
+
if key in self.reserved_words or key in self.q:
|
|
195
|
+
raise exceptions.PyKXException(
|
|
196
|
+
'Cannot assign to reserved word or overwrite q namespace.'
|
|
197
|
+
)
|
|
198
|
+
self._call('set', key, value, wait=True)
|
|
199
|
+
|
|
200
|
+
def __delitem__(self, key):
|
|
201
|
+
key = str(key)
|
|
202
|
+
if key.startswith('.'):
|
|
203
|
+
raise exceptions.PyKXException('Cannot delete from the global context.')
|
|
204
|
+
elif key not in self._call('key `.', wait=True).py():
|
|
205
|
+
raise KeyError(key)
|
|
206
|
+
self._call('{![`.;();0b;enlist x]}', key, wait=True) # delete key from `.
|
|
207
|
+
|
|
208
|
+
def __dir__(self):
|
|
209
|
+
if hasattr(self, 'ctx'): # Temporary workaround to disable the context interface over IPC.
|
|
210
|
+
return sorted({
|
|
211
|
+
*super().__dir__(),
|
|
212
|
+
*dir(self.ctx),
|
|
213
|
+
*dir(self.ctx.__getattr__('q')),
|
|
214
|
+
})
|
|
215
|
+
else:
|
|
216
|
+
return super().__dir__()
|
|
217
|
+
|
|
218
|
+
def _register(self,
|
|
219
|
+
name: Optional[str] = None,
|
|
220
|
+
path: Optional[Union[Path, str]] = None,
|
|
221
|
+
) -> str:
|
|
222
|
+
"""Switch to a named q context (read [Q for Mortals
|
|
223
|
+
Chapter 12](https://code.kx.com/q4m3/12_Workspace_Organization/#122-contexts))
|
|
224
|
+
and load variable definitions into that context from a q/k script. Once the script is
|
|
225
|
+
loaded this function switches back to the previous q context.
|
|
226
|
+
|
|
227
|
+
Parameters:
|
|
228
|
+
name: Name to assign the context being loaded. If no argument is provided the
|
|
229
|
+
assigned name of the context is set to the name of the file without filetype
|
|
230
|
+
extension.
|
|
231
|
+
path: Path to the script to load. If no argument is provided this function
|
|
232
|
+
searches for a file matching the given name, loading it if found.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
The attribute name for the newly loaded module.
|
|
236
|
+
"""
|
|
237
|
+
if name is None and path is None:
|
|
238
|
+
raise ValueError('Module name or path must be provided.')
|
|
239
|
+
if path is not None:
|
|
240
|
+
path = Path(path).resolve(strict=True)
|
|
241
|
+
else:
|
|
242
|
+
path = _first_resolved_path([''.join(x) for x in it.product(
|
|
243
|
+
# `str(Path/@)[:-1]` gets the path with a trailing path separator
|
|
244
|
+
(str(x/Path('@'))[:-1] for x in self.paths),
|
|
245
|
+
('.', ''),
|
|
246
|
+
(name,),
|
|
247
|
+
('.q', '.k'),
|
|
248
|
+
('', '_')
|
|
249
|
+
)])
|
|
250
|
+
if " " in str(path) and not suppress_warnings:
|
|
251
|
+
warn("""Space detected in path being loaded.
|
|
252
|
+
KDB-X Python will change directory to the path parent and then load directly before returning to current working directory.
|
|
253
|
+
To turn off this warning set PYKX_SUPPRESS_WARNINGS to True.""", exceptions.PyKXWarning) # noqa: E501
|
|
254
|
+
if name is None: # defaults to filename at end of path sans extension
|
|
255
|
+
name = path.stem
|
|
256
|
+
prev_ctx = self._call('string system"d"', wait=True)
|
|
257
|
+
try:
|
|
258
|
+
self._call('''{[name;folder;file]
|
|
259
|
+
name set (enlist`)!enlist(::);
|
|
260
|
+
system "d ",string name;
|
|
261
|
+
$[@[{get x;1b};`.pykx.util.loadfile;{0b}];
|
|
262
|
+
.pykx.util.loadfile[folder;file];
|
|
263
|
+
system"l ",$[.z.o like "w*";"\\\\";"/"] sv ((),folder;(),file)]}
|
|
264
|
+
''', "" if name[0] == "." else "."+name, str(path.parent).encode(),
|
|
265
|
+
path.name.encode(), wait=True,
|
|
266
|
+
)
|
|
267
|
+
return name[1:] if name[0] == '.' else name
|
|
268
|
+
finally:
|
|
269
|
+
self._call('{system"d ",x}', prev_ctx, wait=True)
|
|
270
|
+
|
|
271
|
+
@property
|
|
272
|
+
def paths(self):
|
|
273
|
+
"""List of locations for the context interface to find q scripts in.
|
|
274
|
+
|
|
275
|
+
Defaults to the current working directory and `#!bash $QHOME`.
|
|
276
|
+
|
|
277
|
+
If you change directories, the current working directory stored in this list
|
|
278
|
+
automatically reflects that change.
|
|
279
|
+
"""
|
|
280
|
+
return object.__getattribute__(self, '_paths')
|
|
281
|
+
|
|
282
|
+
@paths.setter
|
|
283
|
+
def paths(self, paths: List[Union[str, Path]]):
|
|
284
|
+
resolved = []
|
|
285
|
+
for path in paths:
|
|
286
|
+
if not isinstance(path, Path):
|
|
287
|
+
path = Path(path)
|
|
288
|
+
try:
|
|
289
|
+
resolved.append(path.resolve(strict=True))
|
|
290
|
+
except FileNotFoundError:
|
|
291
|
+
warn(f"Module path '{path!r}' not found", RuntimeWarning)
|
|
292
|
+
object.__setattr__(self, '_paths', resolved)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
# Import order matters here, so the imports are not ordered conventionally.
|
|
296
|
+
from .serialize import deserialize, serialize
|
|
297
|
+
from .console import QConsole
|
|
298
|
+
from .ctx import default_paths, QContext
|
|
299
|
+
from .query import Insert, QSQL, SQL, Upsert
|
|
300
|
+
from .read import QReader
|
|
301
|
+
from .system import SystemCommands
|
|
302
|
+
from .write import QWriter
|
|
303
|
+
from .reimporter import PyKXReimport
|
|
304
|
+
from .module import QModule, QNamespaceProxy, use
|
|
305
|
+
|
|
306
|
+
from . import config
|
|
307
|
+
from . import console
|
|
308
|
+
from . import exceptions
|
|
309
|
+
from . import wrappers
|
|
310
|
+
from . import schema
|
|
311
|
+
from . import streamlit
|
|
312
|
+
from . import random
|
|
313
|
+
from . import help
|
|
314
|
+
|
|
315
|
+
from ._wrappers import _init as _wrappers_init
|
|
316
|
+
_wrappers_init(wrappers)
|
|
317
|
+
|
|
318
|
+
from .embedded_q import EmbeddedQ, EmbeddedQFuture, q
|
|
319
|
+
from ._version import version as __version__
|
|
320
|
+
from .exceptions import *
|
|
321
|
+
|
|
322
|
+
from .util import _init as _util_init
|
|
323
|
+
_util_init(q)
|
|
324
|
+
|
|
325
|
+
from ._ipc import _init as _ipc_init
|
|
326
|
+
_ipc_init(q)
|
|
327
|
+
|
|
328
|
+
from .compress_encrypt import Compress, CompressionAlgorithm, Encrypt
|
|
329
|
+
from .db import DB
|
|
330
|
+
from .tick import TICK
|
|
331
|
+
from .ipc import AsyncQConnection, QConnection, QFuture, RawQConnection, SecureQConnection, SyncQConnection # noqa
|
|
332
|
+
from .config import qargs, qhome, qlic, suppress_warnings
|
|
333
|
+
from .wrappers import *
|
|
334
|
+
from .wrappers import CharVector, K
|
|
335
|
+
|
|
336
|
+
from ._ipc import ssl_info
|
|
337
|
+
|
|
338
|
+
from .schema import _init as _schema_init
|
|
339
|
+
_schema_init(q)
|
|
340
|
+
|
|
341
|
+
from .register import _init as _register_init
|
|
342
|
+
_register_init(q)
|
|
343
|
+
|
|
344
|
+
from .license import _init as _license_init
|
|
345
|
+
_license_init(q)
|
|
346
|
+
|
|
347
|
+
from .random import _init as _random_init
|
|
348
|
+
_random_init(q)
|
|
349
|
+
|
|
350
|
+
from .db import _init as _db_init
|
|
351
|
+
_db_init(q)
|
|
352
|
+
|
|
353
|
+
from .tick import _init as _tick_init
|
|
354
|
+
_tick_init(q)
|
|
355
|
+
|
|
356
|
+
from .remote import _init as _remote_init
|
|
357
|
+
_remote_init(q)
|
|
358
|
+
|
|
359
|
+
from .compress_encrypt import _init as _compress_init
|
|
360
|
+
_compress_init(q)
|
|
361
|
+
|
|
362
|
+
from .help import _init as _help_init
|
|
363
|
+
_help_init(q)
|
|
364
|
+
qhelp = help.qhelp
|
|
365
|
+
|
|
366
|
+
from .module import _init as _module_init
|
|
367
|
+
_module_init(q)
|
|
368
|
+
|
|
369
|
+
if k_allocator:
|
|
370
|
+
from . import _numpy as _pykx_numpy_cext
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def merge_asof(left, *args, **kwargs):
|
|
374
|
+
return left.merge_asof(*args, **kwargs)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
if sys.version_info[1] < 9:
|
|
378
|
+
warn(
|
|
379
|
+
'Python 3.9 is the minimum support version. Please upgrade.',
|
|
380
|
+
exceptions.PyKXWarning
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def install_into_QHOME(overwrite_embedpy=False,
|
|
385
|
+
to_local_folder=False,
|
|
386
|
+
cloud_libraries=False) -> None:
|
|
387
|
+
"""Copies the embedded Python functionality of KDB-X Python into `$QHOME`.
|
|
388
|
+
|
|
389
|
+
Parameters:
|
|
390
|
+
overwrite_embedpy: If embedPy had previously been installed replace it otherwise
|
|
391
|
+
save functionality as pykx.q.
|
|
392
|
+
to_local_folder: Copy the files to your local folder using True, or specify '/some/path'
|
|
393
|
+
rather than `#!bash QHOME`.
|
|
394
|
+
cloud_libraries: Copy cloud libraries to `#!bash QHOME`.
|
|
395
|
+
|
|
396
|
+
Returns:
|
|
397
|
+
None
|
|
398
|
+
"""
|
|
399
|
+
if isinstance(to_local_folder, (str, Path)):
|
|
400
|
+
dest = Path(to_local_folder)
|
|
401
|
+
elif to_local_folder:
|
|
402
|
+
dest = Path('.')
|
|
403
|
+
else:
|
|
404
|
+
dest = qhome
|
|
405
|
+
if dest == pykx_lib_dir:
|
|
406
|
+
warn(f"""
|
|
407
|
+
The destination {dest} matches pykx.config.pykx_lib_dir which already contains pykx.q,
|
|
408
|
+
no files will be copied.
|
|
409
|
+
If your environment does not have QHOME set or you wish to control where pykx.q
|
|
410
|
+
is installed use to_local_folder:
|
|
411
|
+
to_local_folder = True will install pykx.q to your current working directory
|
|
412
|
+
to_local_folder = 'path/to/folder' will install pykx.q to the specified path
|
|
413
|
+
""")
|
|
414
|
+
return
|
|
415
|
+
|
|
416
|
+
p = Path(dest)/'p.k'
|
|
417
|
+
c_files = ['kurl.q_', 'kurl.sidecar.q_', 'objstor.q_', 'qlog.q_', 'rest.q_', 'bq.q_']
|
|
418
|
+
|
|
419
|
+
if not p.exists() or overwrite_embedpy:
|
|
420
|
+
shutil.copy(Path(__file__).parent/'p.k', p)
|
|
421
|
+
if overwrite_embedpy:
|
|
422
|
+
shutil.copy(Path(__file__).parent/'p.k', Path(dest)/'p.q')
|
|
423
|
+
if cloud_libraries:
|
|
424
|
+
for i in c_files:
|
|
425
|
+
shutil.copy(Path(__file__).parent/'lib'/i, Path(dest)/i)
|
|
426
|
+
shutil.copy(Path(__file__).parent/'lib'/'sql.k_', Path(dest)/'s.k_')
|
|
427
|
+
shutil.copy(Path(__file__).parent/'pykx.q', dest)
|
|
428
|
+
shutil.copy(Path(__file__).parent/'pykx_init.q_', dest)
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def activate_numpy_allocator() -> None:
|
|
432
|
+
"""Sets the allocator used for Numpy array data to one optimized for use with KDB-X Python.
|
|
433
|
+
|
|
434
|
+
This will only change the default allocator if the environment variable `PYKX_NO_ALLOCATOR`
|
|
435
|
+
is not set to 1 or if the flag `--pykxnoalloc` is not present in the QARGS environment variable.
|
|
436
|
+
|
|
437
|
+
The name of the allocator set by this function is `'pykx_allocator'`.
|
|
438
|
+
|
|
439
|
+
The name of the active Numpy allocator can be retrieved by running
|
|
440
|
+
`numpy.core.multiarray.get_handler_name()`.
|
|
441
|
+
|
|
442
|
+
Refer to NEP 49 for details about custom Numpy allocators: https://numpy.org/neps/nep-0049.html
|
|
443
|
+
|
|
444
|
+
The custom allocator set by KDB-X Python allocates Numpy array data into the embedded q memory
|
|
445
|
+
space.
|
|
446
|
+
Numpy arrays created with this allocator can be converted into a q vector without copying the
|
|
447
|
+
data.
|
|
448
|
+
|
|
449
|
+
Because q objects must have their metadata immediately preceding the data, only a single
|
|
450
|
+
q vector can be created using this approach. Repeated conversions of the Numpy array into a q
|
|
451
|
+
vector will yield the same q vector with its reference count incremented by 1 each time.
|
|
452
|
+
|
|
453
|
+
Since Numpy stores its metadata separately from its data, multiple views can be taken over the
|
|
454
|
+
data without incurring a copy. The same is not true for q vectors. If a Numpy array is
|
|
455
|
+
converted into a q vector, then a view is taken of the Numpy array, this results in the view
|
|
456
|
+
being converted into a q vector and a copy of the data being incurred. Regardless of whether
|
|
457
|
+
this custom Numpy allocator is activated.
|
|
458
|
+
|
|
459
|
+
The Numpy array data object holds a reference to the q vector. When the Numpy array data object
|
|
460
|
+
is deallocated, it decrements the refcount of the q vector.
|
|
461
|
+
|
|
462
|
+
If the installed version of Numpy support custom allocators (i.e. is greater than or equal to
|
|
463
|
+
version `1.22.0`), and the active Numpy allocator when KDB-X Python is imported is the default
|
|
464
|
+
allocator, KDB-X Python will automatically call this function.
|
|
465
|
+
|
|
466
|
+
Note that Numpy arrays created with an allocator other than `'pykx_allocator'` (e.g. before
|
|
467
|
+
KDB-X Python has been imported) will not gain the benefits enabled by this allocator.
|
|
468
|
+
The name of the allocator used by a given array `x` can be retrieved by running
|
|
469
|
+
`numpy.core.multiarray.get_handler_name(x)`.
|
|
470
|
+
"""
|
|
471
|
+
if k_allocator:
|
|
472
|
+
_pykx_numpy_cext.activate_pykx_allocators()
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
import numpy as np
|
|
476
|
+
if k_allocator:
|
|
477
|
+
_pykx_numpy_cext.init_numpy_ctx(core._r0_ptr, core._k_ptr, core._ktn_ptr)
|
|
478
|
+
if np.__version__[0] == '1':
|
|
479
|
+
handler = np.core.multiarray.get_handler_name()
|
|
480
|
+
else:
|
|
481
|
+
handler = np._core.multiarray.get_handler_name()
|
|
482
|
+
if handler == 'default_allocator':
|
|
483
|
+
activate_numpy_allocator()
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def deactivate_numpy_allocator():
|
|
487
|
+
"""Deactivate the KDB-X Python Numpy allocator.
|
|
488
|
+
|
|
489
|
+
If the KDB-X Python Numpy allocator has not been activated then this function has no effect.
|
|
490
|
+
Otherwise, it replaces the KDB-X Python Numpy allocator with the allocator that was in use
|
|
491
|
+
when it was first activated.
|
|
492
|
+
"""
|
|
493
|
+
if k_allocator:
|
|
494
|
+
_pykx_numpy_cext.deactivate_pykx_allocators()
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
try:
|
|
498
|
+
# If we are running under IPython/Jupyter...
|
|
499
|
+
ipython = get_ipython() # noqa
|
|
500
|
+
# Load the KDB-X Python extension for Jupyter Notebook.
|
|
501
|
+
ipython.extension_manager.load_extension('pykx.nbextension')
|
|
502
|
+
|
|
503
|
+
if config.jupyterq:
|
|
504
|
+
util.jupyter_qfirst_enable()
|
|
505
|
+
except NameError:
|
|
506
|
+
# Not running under IPython/Jupyter...
|
|
507
|
+
pass
|
|
508
|
+
|
|
509
|
+
shutdown_thread = core.shutdown_thread
|
|
510
|
+
|
|
511
|
+
if licensed:
|
|
512
|
+
days_to_expiry = q('"D"$', q.z.l[1]) - q.z.D
|
|
513
|
+
if days_to_expiry < 10:
|
|
514
|
+
logging.warning(f'KDB-X Python license set to expire in {int(days_to_expiry)} days, '
|
|
515
|
+
'please consider installing an updated license')
|
|
516
|
+
|
|
517
|
+
__all__ = sorted([
|
|
518
|
+
'AsyncQConnection',
|
|
519
|
+
'deserialize',
|
|
520
|
+
'EmbeddedQ',
|
|
521
|
+
'EmbeddedQFuture',
|
|
522
|
+
'Q',
|
|
523
|
+
'qargs',
|
|
524
|
+
'QConnection',
|
|
525
|
+
'QConsole',
|
|
526
|
+
'QContext',
|
|
527
|
+
'QFuture',
|
|
528
|
+
'qhome',
|
|
529
|
+
'QReader',
|
|
530
|
+
'random',
|
|
531
|
+
'remote',
|
|
532
|
+
'QWriter',
|
|
533
|
+
'qlic',
|
|
534
|
+
'serialize',
|
|
535
|
+
'SyncQConnection',
|
|
536
|
+
'RawQConnection',
|
|
537
|
+
'activate_numpy_allocator',
|
|
538
|
+
'console',
|
|
539
|
+
'ctx',
|
|
540
|
+
'deactivate_numpy_allocator',
|
|
541
|
+
'exceptions',
|
|
542
|
+
'install_into_QHOME',
|
|
543
|
+
'licensed',
|
|
544
|
+
'toq',
|
|
545
|
+
'schema',
|
|
546
|
+
'config',
|
|
547
|
+
'util',
|
|
548
|
+
'q',
|
|
549
|
+
'QModule',
|
|
550
|
+
'QNamespaceProxy',
|
|
551
|
+
'use',
|
|
552
|
+
'shutdown_thread',
|
|
553
|
+
'PyKXReimport',
|
|
554
|
+
'help',
|
|
555
|
+
'qhelp',
|
|
556
|
+
*exceptions.__all__,
|
|
557
|
+
*wrappers.__all__,
|
|
558
|
+
])
|
|
559
|
+
|
|
560
|
+
if not no_pykx_signal:
|
|
561
|
+
for k, v in _signal_dict.items():
|
|
562
|
+
try:
|
|
563
|
+
signal.signal(eval(k), v)
|
|
564
|
+
except Exception:
|
|
565
|
+
pass
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
def __dir__():
|
|
569
|
+
return __all__
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
envlist = (
|
|
573
|
+
'PYKX_UNDER_Q',
|
|
574
|
+
'PYKX_UNDER_PYTHON',
|
|
575
|
+
'PYKX_Q_LOADED_MARKER',
|
|
576
|
+
'PYKX_LOADED_UNDER_Q',
|
|
577
|
+
'PYKX_LOADING_PYTHON',
|
|
578
|
+
'PYKX_LOADING_Q',
|
|
579
|
+
'PYKX_QINIT_CHECK',
|
|
580
|
+
)
|
|
581
|
+
if os.getenv('PYKX_LOADING_PYTHON', '') != '':
|
|
582
|
+
for x in envlist:
|
|
583
|
+
os.environ[x] = ''
|
|
584
|
+
del os.environ[x]
|
|
585
|
+
if licensed:
|
|
586
|
+
q(f'setenv[`{x}; ""]')
|