Qubx 0.0.1__cp311-cp311-manylinux_2_35_x86_64.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.
Potentially problematic release.
This version of Qubx might be problematic. Click here for more details.
- qubx/__init__.py +164 -0
- qubx/_nb_magic.py +69 -0
- qubx/core/__init__.py +0 -0
- qubx/core/basics.py +224 -0
- qubx/core/lookups.py +152 -0
- qubx/core/series.cpython-311-x86_64-linux-gnu.so +0 -0
- qubx/core/series.pxd +94 -0
- qubx/core/series.pyx +763 -0
- qubx/core/strategy.py +89 -0
- qubx/core/utils.cpython-311-x86_64-linux-gnu.so +0 -0
- qubx/core/utils.pyx +54 -0
- qubx/data/readers.py +387 -0
- qubx/math/__init__.py +1 -0
- qubx/math/stats.py +42 -0
- qubx/ta/__init__.py +0 -0
- qubx/ta/indicators.cpython-311-x86_64-linux-gnu.so +0 -0
- qubx/ta/indicators.pyx +258 -0
- qubx/utils/__init__.py +3 -0
- qubx/utils/_pyxreloader.py +271 -0
- qubx/utils/charting/mpl_helpers.py +182 -0
- qubx/utils/marketdata/binance.py +212 -0
- qubx/utils/misc.py +234 -0
- qubx/utils/pandas.py +206 -0
- qubx/utils/time.py +145 -0
- qubx-0.0.1.dist-info/METADATA +39 -0
- qubx-0.0.1.dist-info/RECORD +27 -0
- qubx-0.0.1.dist-info/WHEEL +4 -0
qubx/ta/indicators.pyx
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
cimport numpy as np
|
|
3
|
+
from collections import deque
|
|
4
|
+
|
|
5
|
+
from qubx.core.series cimport TimeSeries, Indicator, RollingSum, nans
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
cdef extern from "math.h":
|
|
9
|
+
float INFINITY
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
cdef class Sma(Indicator):
|
|
13
|
+
"""
|
|
14
|
+
Simple Moving Average indicator
|
|
15
|
+
"""
|
|
16
|
+
cdef unsigned int period
|
|
17
|
+
cdef RollingSum summator
|
|
18
|
+
|
|
19
|
+
"""
|
|
20
|
+
Simple moving average
|
|
21
|
+
"""
|
|
22
|
+
def __init__(self, str name, TimeSeries series, int period):
|
|
23
|
+
self.period = period
|
|
24
|
+
self.summator = RollingSum(period)
|
|
25
|
+
super().__init__(name, series)
|
|
26
|
+
|
|
27
|
+
cpdef double calculate(self, long long time, double value, short new_item_started):
|
|
28
|
+
cdef double r = self.summator.update(value, new_item_started)
|
|
29
|
+
return np.nan if self.summator.is_init_stage else r / self.period
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def sma(series:TimeSeries, period: int):
|
|
33
|
+
return Sma.wrap(series, period)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
cdef class Ema(Indicator):
|
|
37
|
+
"""
|
|
38
|
+
Exponential moving average
|
|
39
|
+
"""
|
|
40
|
+
cdef int period
|
|
41
|
+
cdef np.ndarray __s
|
|
42
|
+
cdef int __i
|
|
43
|
+
cdef double alpha
|
|
44
|
+
cdef double alpha_1
|
|
45
|
+
cdef unsigned short init_mean
|
|
46
|
+
cdef unsigned short _init_stage
|
|
47
|
+
|
|
48
|
+
def __init__(self, str name, TimeSeries series, int period, init_mean=True):
|
|
49
|
+
self.period = period
|
|
50
|
+
|
|
51
|
+
# when it's required to initialize this ema by mean on first period
|
|
52
|
+
self.init_mean = init_mean
|
|
53
|
+
if init_mean:
|
|
54
|
+
self.__s = nans(period)
|
|
55
|
+
self.__i = 0
|
|
56
|
+
|
|
57
|
+
self._init_stage = 1
|
|
58
|
+
self.alpha = 2.0 / (1.0 + period)
|
|
59
|
+
self.alpha_1 = (1 - self.alpha)
|
|
60
|
+
super().__init__(name, series)
|
|
61
|
+
|
|
62
|
+
cpdef double calculate(self, long long time, double value, short new_item_started):
|
|
63
|
+
cdef int prev_bar_idx = 0 if new_item_started else 1
|
|
64
|
+
|
|
65
|
+
if self._init_stage:
|
|
66
|
+
if np.isnan(value): return np.nan
|
|
67
|
+
|
|
68
|
+
if new_item_started:
|
|
69
|
+
self.__i += 1
|
|
70
|
+
if self.__i > self.period - 1:
|
|
71
|
+
self._init_stage = False
|
|
72
|
+
return self.alpha * value + self.alpha_1 * self[prev_bar_idx]
|
|
73
|
+
|
|
74
|
+
if self.__i == self.period - 1:
|
|
75
|
+
self.__s[self.__i] = value
|
|
76
|
+
return np.nansum(self.__s) / self.period
|
|
77
|
+
|
|
78
|
+
self.__s[self.__i] = value
|
|
79
|
+
return np.nan
|
|
80
|
+
|
|
81
|
+
if len(self) == 0:
|
|
82
|
+
return value
|
|
83
|
+
|
|
84
|
+
return self.alpha * value + self.alpha_1 * self[prev_bar_idx]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def ema(series:TimeSeries, period: int, init_mean: bool = True):
|
|
88
|
+
return Ema.wrap(series, period, init_mean=init_mean)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
cdef class Tema(Indicator):
|
|
92
|
+
cdef int period
|
|
93
|
+
cdef unsigned short init_mean
|
|
94
|
+
cdef TimeSeries ser0
|
|
95
|
+
cdef Ema ema1
|
|
96
|
+
cdef Ema ema2
|
|
97
|
+
cdef Ema ema3
|
|
98
|
+
|
|
99
|
+
def __init__(self, str name, TimeSeries series, int period, init_mean=True):
|
|
100
|
+
self.period = period
|
|
101
|
+
self.init_mean = init_mean
|
|
102
|
+
self.ser0 = TimeSeries('ser0', series.timeframe, series.max_series_length)
|
|
103
|
+
self.ema1 = ema(self.ser0, period, init_mean)
|
|
104
|
+
self.ema2 = ema(self.ema1, period, init_mean)
|
|
105
|
+
self.ema3 = ema(self.ema2, period, init_mean)
|
|
106
|
+
super().__init__(name, series)
|
|
107
|
+
|
|
108
|
+
cpdef double calculate(self, long long time, double value, short new_item_started):
|
|
109
|
+
self.ser0.update(time, value)
|
|
110
|
+
return 3 * self.ema1[0] - 3 * self.ema2[0] + self.ema3[0]
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def tema(series:TimeSeries, period: int, init_mean: bool = True):
|
|
114
|
+
return Tema.wrap(series, period, init_mean=init_mean)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
cdef class Dema(Indicator):
|
|
118
|
+
cdef int period
|
|
119
|
+
cdef unsigned short init_mean
|
|
120
|
+
cdef TimeSeries ser0
|
|
121
|
+
cdef Ema ema1
|
|
122
|
+
cdef Ema ema2
|
|
123
|
+
|
|
124
|
+
def __init__(self, str name, TimeSeries series, int period, init_mean=True):
|
|
125
|
+
self.period = period
|
|
126
|
+
self.init_mean = init_mean
|
|
127
|
+
self.ser0 = TimeSeries('ser0', series.timeframe, series.max_series_length)
|
|
128
|
+
self.ema1 = ema(self.ser0, period, init_mean)
|
|
129
|
+
self.ema2 = ema(self.ema1, period, init_mean)
|
|
130
|
+
super().__init__(name, series)
|
|
131
|
+
|
|
132
|
+
cpdef double calculate(self, long long time, double value, short new_item_started):
|
|
133
|
+
self.ser0.update(time, value)
|
|
134
|
+
return 2 * self.ema1[0] - self.ema2[0]
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def dema(series:TimeSeries, period: int, init_mean: bool = True):
|
|
138
|
+
return Dema.wrap(series, period, init_mean=init_mean)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
cdef class Kama(Indicator):
|
|
142
|
+
cdef int period
|
|
143
|
+
cdef int fast_span
|
|
144
|
+
cdef int slow_span
|
|
145
|
+
cdef double _S1
|
|
146
|
+
cdef double _K1
|
|
147
|
+
cdef _x_past
|
|
148
|
+
cdef RollingSum summator
|
|
149
|
+
|
|
150
|
+
def __init__(self, str name, TimeSeries series, int period, int fast_span=2, int slow_span=30):
|
|
151
|
+
self.period = period
|
|
152
|
+
self.fast_span = fast_span
|
|
153
|
+
self.slow_span = slow_span
|
|
154
|
+
self._S1 = 2.0 / (slow_span + 1)
|
|
155
|
+
self._K1 = 2.0 / (fast_span + 1) - self._S1
|
|
156
|
+
self._x_past = deque(nans(period+1), period+1)
|
|
157
|
+
self.summator = RollingSum(period)
|
|
158
|
+
super().__init__(name, series)
|
|
159
|
+
|
|
160
|
+
cpdef double calculate(self, long long time, double value, short new_item_started):
|
|
161
|
+
if new_item_started:
|
|
162
|
+
self._x_past.append(value)
|
|
163
|
+
else:
|
|
164
|
+
self._x_past[-1] = value
|
|
165
|
+
|
|
166
|
+
cdef double rs = self.summator.update(abs(value - self._x_past[-2]), new_item_started)
|
|
167
|
+
cdef double er = abs(value - self._x_past[0]) / rs
|
|
168
|
+
cdef double sc = (er * self._K1 + self._S1) ** 2
|
|
169
|
+
|
|
170
|
+
if self.summator.is_init_stage:
|
|
171
|
+
if not np.isnan(self._x_past[1]):
|
|
172
|
+
return value
|
|
173
|
+
return np.nan
|
|
174
|
+
|
|
175
|
+
return sc * value + (1 - sc) * self[0 if new_item_started else 1]
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def kama(series:TimeSeries, period: int, fast_span:int=2, slow_span:int=30):
|
|
179
|
+
return Kama.wrap(series, period, fast_span, slow_span)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
cdef class Highest(Indicator):
|
|
183
|
+
cdef int period
|
|
184
|
+
cdef queue
|
|
185
|
+
|
|
186
|
+
def __init__(self, str name, TimeSeries series, int period):
|
|
187
|
+
self.period = period
|
|
188
|
+
self.queue = deque([np.nan] * period, maxlen=period)
|
|
189
|
+
super().__init__(name, series)
|
|
190
|
+
|
|
191
|
+
cpdef double calculate(self, long long time, double value, short new_item_started):
|
|
192
|
+
"""
|
|
193
|
+
Not a most effictive algo but simplest and can handle updated last value
|
|
194
|
+
"""
|
|
195
|
+
cdef float r = np.nan
|
|
196
|
+
|
|
197
|
+
if not np.isnan(value):
|
|
198
|
+
if new_item_started:
|
|
199
|
+
self.queue.append(value)
|
|
200
|
+
else:
|
|
201
|
+
self.queue[-1] = value
|
|
202
|
+
|
|
203
|
+
if not np.isnan(self.queue[0]):
|
|
204
|
+
r = max(self.queue)
|
|
205
|
+
|
|
206
|
+
return r
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def highest(series:TimeSeries, period:int):
|
|
210
|
+
return Highest.wrap(series, period)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
cdef class Lowest(Indicator):
|
|
214
|
+
cdef int period
|
|
215
|
+
cdef queue
|
|
216
|
+
|
|
217
|
+
def __init__(self, str name, TimeSeries series, int period):
|
|
218
|
+
self.period = period
|
|
219
|
+
self.queue = deque([np.nan] * period, maxlen=period)
|
|
220
|
+
super().__init__(name, series)
|
|
221
|
+
|
|
222
|
+
cpdef double calculate(self, long long time, double value, short new_item_started):
|
|
223
|
+
"""
|
|
224
|
+
Not a most effictive algo but simplest and can handle updated last value
|
|
225
|
+
"""
|
|
226
|
+
cdef float r = np.nan
|
|
227
|
+
|
|
228
|
+
if not np.isnan(value):
|
|
229
|
+
if new_item_started:
|
|
230
|
+
self.queue.append(value)
|
|
231
|
+
else:
|
|
232
|
+
self.queue[-1] = value
|
|
233
|
+
|
|
234
|
+
if not np.isnan(self.queue[0]):
|
|
235
|
+
r = min(self.queue)
|
|
236
|
+
|
|
237
|
+
return r
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def lowest(series:TimeSeries, period:int):
|
|
241
|
+
return Lowest.wrap(series, period)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
# - - - - TODO !!!!!!!
|
|
245
|
+
cdef class Std(Indicator):
|
|
246
|
+
cdef int period
|
|
247
|
+
|
|
248
|
+
def __init__(self, str name, TimeSeries series, int period):
|
|
249
|
+
self.period = period
|
|
250
|
+
super().__init__(name, series)
|
|
251
|
+
|
|
252
|
+
cpdef double calculate(self, long long time, double value, short new_item_started):
|
|
253
|
+
pass
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def std(series:TimeSeries, period:int, mean=0):
|
|
257
|
+
return Std.wrap(series, period)
|
|
258
|
+
# - - - - TODO !!!!!!!
|
qubx/utils/__init__.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
from .time import convert_seconds_to_str, convert_tf_str_td64, floor_t64, time_to_str, infer_series_frequency
|
|
2
|
+
from .misc import install_pyx_recompiler_for_dev, runtime_env, version, Struct
|
|
3
|
+
from .charting.mpl_helpers import set_mpl_theme, fig, sbp, vline, hline, ellips, set_mpl_theme
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import importlib, glob, imp, os, sys
|
|
2
|
+
from importlib.abc import MetaPathFinder
|
|
3
|
+
from importlib.util import spec_from_file_location
|
|
4
|
+
from importlib.machinery import ExtensionFileLoader, SourceFileLoader
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
PYX_EXT = ".pyx"
|
|
8
|
+
PYXDEP_EXT = ".pyxdep"
|
|
9
|
+
PYXBLD_EXT = ".pyxbld"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def handle_dependencies(pyxfilename):
|
|
13
|
+
testing = '_test_files' in globals()
|
|
14
|
+
dependfile = os.path.splitext(pyxfilename)[0] + PYXDEP_EXT
|
|
15
|
+
|
|
16
|
+
# by default let distutils decide whether to rebuild on its own
|
|
17
|
+
# (it has a better idea of what the output file will be)
|
|
18
|
+
|
|
19
|
+
# but we know more about dependencies so force a rebuild if
|
|
20
|
+
# some of the dependencies are newer than the pyxfile.
|
|
21
|
+
if os.path.exists(dependfile):
|
|
22
|
+
with open(dependfile) as fid:
|
|
23
|
+
depends = fid.readlines()
|
|
24
|
+
depends = [depend.strip() for depend in depends]
|
|
25
|
+
|
|
26
|
+
# gather dependencies in the "files" variable
|
|
27
|
+
# the dependency file is itself a dependency
|
|
28
|
+
files = [dependfile]
|
|
29
|
+
for depend in depends:
|
|
30
|
+
fullpath = os.path.join(os.path.dirname(dependfile),
|
|
31
|
+
depend)
|
|
32
|
+
files.extend(glob.glob(fullpath))
|
|
33
|
+
|
|
34
|
+
# if any file that the pyxfile depends upon is newer than
|
|
35
|
+
# the pyx file, 'touch' the pyx file so that distutils will
|
|
36
|
+
# be tricked into rebuilding it.
|
|
37
|
+
for file in files:
|
|
38
|
+
from distutils.dep_util import newer
|
|
39
|
+
if newer(file, pyxfilename):
|
|
40
|
+
print("Rebuilding %s because of %s", pyxfilename, file)
|
|
41
|
+
filetime = os.path.getmtime(file)
|
|
42
|
+
os.utime(pyxfilename, (filetime, filetime))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def handle_special_build(modname, pyxfilename):
|
|
46
|
+
special_build = os.path.splitext(pyxfilename)[0] + PYXBLD_EXT
|
|
47
|
+
ext = None
|
|
48
|
+
setup_args={}
|
|
49
|
+
if os.path.exists(special_build):
|
|
50
|
+
# globls = {}
|
|
51
|
+
# locs = {}
|
|
52
|
+
# execfile(special_build, globls, locs)
|
|
53
|
+
# ext = locs["make_ext"](modname, pyxfilename)
|
|
54
|
+
with open(special_build) as fid:
|
|
55
|
+
mod = imp.load_source("XXXX", special_build, fid)
|
|
56
|
+
make_ext = getattr(mod,'make_ext',None)
|
|
57
|
+
if make_ext:
|
|
58
|
+
ext = make_ext(modname, pyxfilename)
|
|
59
|
+
assert ext and ext.sources, "make_ext in %s did not return Extension" % special_build
|
|
60
|
+
make_setup_args = getattr(mod, 'make_setup_args',None)
|
|
61
|
+
if make_setup_args:
|
|
62
|
+
setup_args = make_setup_args()
|
|
63
|
+
assert isinstance(setup_args,dict), ("make_setup_args in %s did not return a dict"
|
|
64
|
+
% special_build)
|
|
65
|
+
assert set or setup_args, ("neither make_ext nor make_setup_args %s" % special_build)
|
|
66
|
+
ext.sources = [os.path.join(os.path.dirname(special_build), source) for source in ext.sources]
|
|
67
|
+
return ext, setup_args
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def get_distutils_extension(modname, pyxfilename, language_level=None):
|
|
71
|
+
extension_mod, setup_args = handle_special_build(modname, pyxfilename)
|
|
72
|
+
if not extension_mod:
|
|
73
|
+
if not isinstance(pyxfilename, str):
|
|
74
|
+
# distutils is stupid in Py2 and requires exactly 'str'
|
|
75
|
+
# => encode accidentally coerced unicode strings back to str
|
|
76
|
+
pyxfilename = pyxfilename.encode(sys.getfilesystemencoding())
|
|
77
|
+
from distutils.extension import Extension
|
|
78
|
+
extension_mod = Extension(name = modname, sources=[pyxfilename])
|
|
79
|
+
if language_level is not None:
|
|
80
|
+
extension_mod.cython_directives = {'language_level': language_level}
|
|
81
|
+
return extension_mod, setup_args
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def build_module(name, pyxfilename, user_setup_args, pyxbuild_dir=None, inplace=False, language_level=None,
|
|
85
|
+
build_in_temp=False, reload_support=True):
|
|
86
|
+
assert os.path.exists(pyxfilename), "Path does not exist: %s" % pyxfilename
|
|
87
|
+
handle_dependencies(pyxfilename)
|
|
88
|
+
|
|
89
|
+
extension_mod, setup_args = get_distutils_extension(name, pyxfilename, language_level)
|
|
90
|
+
build_in_temp = True
|
|
91
|
+
sargs = user_setup_args.copy() if user_setup_args else dict()
|
|
92
|
+
sargs.update(setup_args)
|
|
93
|
+
build_in_temp = sargs.pop('build_in_temp',build_in_temp)
|
|
94
|
+
|
|
95
|
+
from pyximport import pyxbuild
|
|
96
|
+
olddir = os.getcwd()
|
|
97
|
+
common = ''
|
|
98
|
+
if pyxbuild_dir:
|
|
99
|
+
# Windows concatenates the pyxbuild_dir to the pyxfilename when
|
|
100
|
+
# compiling, and then complains that the filename is too long
|
|
101
|
+
common = os.path.commonprefix([pyxbuild_dir, pyxfilename])
|
|
102
|
+
if len(common) > 30:
|
|
103
|
+
pyxfilename = os.path.relpath(pyxfilename)
|
|
104
|
+
pyxbuild_dir = os.path.relpath(pyxbuild_dir)
|
|
105
|
+
os.chdir(common)
|
|
106
|
+
try:
|
|
107
|
+
so_path = pyxbuild.pyx_to_dll(pyxfilename, extension_mod,
|
|
108
|
+
force_rebuild=1,
|
|
109
|
+
build_in_temp=build_in_temp,
|
|
110
|
+
pyxbuild_dir=pyxbuild_dir,
|
|
111
|
+
setup_args=sargs,
|
|
112
|
+
inplace=inplace,
|
|
113
|
+
reload_support=reload_support)
|
|
114
|
+
finally:
|
|
115
|
+
os.chdir(olddir)
|
|
116
|
+
so_path = os.path.join(common, so_path)
|
|
117
|
+
assert os.path.exists(so_path), "Cannot find: %s" % so_path
|
|
118
|
+
|
|
119
|
+
junkpath = os.path.join(os.path.dirname(so_path), name+"_*") #very dangerous with --inplace ? yes, indeed, trying to eat my files ;)
|
|
120
|
+
junkstuff = glob.glob(junkpath)
|
|
121
|
+
for path in junkstuff:
|
|
122
|
+
if path != so_path:
|
|
123
|
+
try:
|
|
124
|
+
os.remove(path)
|
|
125
|
+
except IOError:
|
|
126
|
+
print("Couldn't remove %s", path)
|
|
127
|
+
|
|
128
|
+
return so_path
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def load_module(name, pyxfilename, pyxbuild_dir=None, is_package=False, build_inplace=False, language_level=None, so_path=None):
|
|
132
|
+
try:
|
|
133
|
+
if so_path is None:
|
|
134
|
+
if is_package:
|
|
135
|
+
module_name = name + '.__init__'
|
|
136
|
+
else:
|
|
137
|
+
module_name = name
|
|
138
|
+
so_path = build_module(module_name, pyxfilename, pyxbuild_dir, inplace=build_inplace, language_level=language_level)
|
|
139
|
+
mod = imp.load_dynamic(name, so_path)
|
|
140
|
+
if is_package and not hasattr(mod, '__path__'):
|
|
141
|
+
mod.__path__ = [os.path.dirname(so_path)]
|
|
142
|
+
assert mod.__file__ == so_path, (mod.__file__, so_path)
|
|
143
|
+
except Exception as failure_exc:
|
|
144
|
+
print("Failed to load extension module: %r" % failure_exc)
|
|
145
|
+
# if pyxargs.load_py_module_on_import_failure and pyxfilename.endswith('.py'):
|
|
146
|
+
if False and pyxfilename.endswith('.py'):
|
|
147
|
+
# try to fall back to normal import
|
|
148
|
+
mod = imp.load_source(name, pyxfilename)
|
|
149
|
+
assert mod.__file__ in (pyxfilename, pyxfilename+'c', pyxfilename+'o'), (mod.__file__, pyxfilename)
|
|
150
|
+
else:
|
|
151
|
+
tb = sys.exc_info()[2]
|
|
152
|
+
import traceback
|
|
153
|
+
exc = ImportError("Building module %s failed: %s" % (name, traceback.format_exception_only(*sys.exc_info()[:2])))
|
|
154
|
+
if sys.version_info[0] >= 3:
|
|
155
|
+
raise exc.with_traceback(tb)
|
|
156
|
+
else:
|
|
157
|
+
exec("raise exc, None, tb", {'exc': exc, 'tb': tb})
|
|
158
|
+
return mod
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class PyxImportLoader(ExtensionFileLoader):
|
|
162
|
+
|
|
163
|
+
def __init__(self, filename, setup_args, pyxbuild_dir, inplace, language_level, reload_support):
|
|
164
|
+
module_name = os.path.splitext(os.path.basename(filename))[0]
|
|
165
|
+
super().__init__(module_name, filename)
|
|
166
|
+
self._pyxbuild_dir = pyxbuild_dir
|
|
167
|
+
self._inplace = inplace
|
|
168
|
+
self._language_level = language_level
|
|
169
|
+
self._setup_args = setup_args
|
|
170
|
+
self._reload_support = reload_support
|
|
171
|
+
|
|
172
|
+
def create_module(self, spec):
|
|
173
|
+
try:
|
|
174
|
+
# print(f"CREATING MODULE: {spec.name} -> {spec.origin}")
|
|
175
|
+
so_path = build_module(spec.name, pyxfilename=spec.origin, user_setup_args=self._setup_args, pyxbuild_dir=self._pyxbuild_dir,
|
|
176
|
+
inplace=self._inplace, language_level=self._language_level, reload_support=self._reload_support)
|
|
177
|
+
self.path = so_path
|
|
178
|
+
spec.origin = so_path
|
|
179
|
+
return super().create_module(spec)
|
|
180
|
+
except Exception as failure_exc:
|
|
181
|
+
# print("LOADING on FAILURE MODULE")
|
|
182
|
+
# if pyxargs.load_py_module_on_import_failure and spec.origin.endswith('.pyx'):
|
|
183
|
+
if False and spec.origin.endswith(PYX_EXT):
|
|
184
|
+
spec = importlib.util.spec_from_file_location(spec.name, spec.origin,
|
|
185
|
+
loader=SourceFileLoader(spec.name, spec.origin))
|
|
186
|
+
mod = importlib.util.module_from_spec(spec)
|
|
187
|
+
assert mod.__file__ in (spec.origin, spec.origin + 'c', spec.origin + 'o'), (mod.__file__, spec.origin)
|
|
188
|
+
return mod
|
|
189
|
+
else:
|
|
190
|
+
tb = sys.exc_info()[2]
|
|
191
|
+
import traceback
|
|
192
|
+
exc = ImportError("Building module %s failed: %s" % (
|
|
193
|
+
spec.name, traceback.format_exception_only(*sys.exc_info()[:2])))
|
|
194
|
+
raise exc.with_traceback(tb)
|
|
195
|
+
|
|
196
|
+
def exec_module(self, module):
|
|
197
|
+
try:
|
|
198
|
+
# print(f"EXEC MODULE: {module}")
|
|
199
|
+
return super().exec_module(module)
|
|
200
|
+
except Exception as failure_exc:
|
|
201
|
+
import traceback
|
|
202
|
+
print("Failed to load extension module: %r" % failure_exc)
|
|
203
|
+
raise ImportError("Executing module %s failed %s" % (
|
|
204
|
+
module.__file__, traceback.format_exception_only(*sys.exc_info()[:2])))
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class CustomPyxImportMetaFinder(MetaPathFinder):
|
|
208
|
+
|
|
209
|
+
def __init__(self, modules_to_check: List[str], extension=PYX_EXT, setup_args=None, pyxbuild_dir=None, inplace=False, language_level=None, reload_support=True):
|
|
210
|
+
self.valid_modules = modules_to_check
|
|
211
|
+
self.pyxbuild_dir = pyxbuild_dir
|
|
212
|
+
self.inplace = inplace
|
|
213
|
+
self.language_level = language_level
|
|
214
|
+
self.extension = extension
|
|
215
|
+
self.setup_args = setup_args if setup_args else dict()
|
|
216
|
+
self.reload_support = reload_support
|
|
217
|
+
|
|
218
|
+
def find_spec(self, fullname, path, target=None):
|
|
219
|
+
def _is_valid(module):
|
|
220
|
+
if not self.valid_modules:
|
|
221
|
+
return True
|
|
222
|
+
for m in self.valid_modules:
|
|
223
|
+
if module.startswith(m):
|
|
224
|
+
return True
|
|
225
|
+
return False
|
|
226
|
+
|
|
227
|
+
if not path:
|
|
228
|
+
path = [os.getcwd()] # top level import --
|
|
229
|
+
if "." in fullname:
|
|
230
|
+
*parents, name = fullname.split(".")
|
|
231
|
+
else:
|
|
232
|
+
name = fullname
|
|
233
|
+
for entry in path:
|
|
234
|
+
if os.path.isdir(os.path.join(entry, name)):
|
|
235
|
+
# this module has child modules
|
|
236
|
+
filename = os.path.join(entry, name, "__init__" + self.extension)
|
|
237
|
+
submodule_locations = [os.path.join(entry, name)]
|
|
238
|
+
else:
|
|
239
|
+
filename = os.path.join(entry, name + self.extension)
|
|
240
|
+
submodule_locations = None
|
|
241
|
+
if not os.path.exists(filename):
|
|
242
|
+
continue
|
|
243
|
+
|
|
244
|
+
if not _is_valid(fullname):
|
|
245
|
+
continue
|
|
246
|
+
|
|
247
|
+
return spec_from_file_location(
|
|
248
|
+
fullname, filename,
|
|
249
|
+
loader=PyxImportLoader(filename, self.setup_args, self.pyxbuild_dir, self.inplace, self.language_level, self.reload_support),
|
|
250
|
+
submodule_search_locations=submodule_locations)
|
|
251
|
+
|
|
252
|
+
return None # we don't know how to import this
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
__pyx_finder_installed = False
|
|
256
|
+
|
|
257
|
+
def pyx_install_loader(modules_to_check: List[str]):
|
|
258
|
+
import numpy as np
|
|
259
|
+
import pyximport
|
|
260
|
+
global __pyx_finder_installed
|
|
261
|
+
|
|
262
|
+
if not __pyx_finder_installed:
|
|
263
|
+
build_dir = os.path.expanduser("~/.pyxbld")
|
|
264
|
+
setup_args = {'include_dirs': np.get_include()}
|
|
265
|
+
sys.meta_path.insert(0, CustomPyxImportMetaFinder(
|
|
266
|
+
modules_to_check,
|
|
267
|
+
PYX_EXT, setup_args=setup_args, pyxbuild_dir=build_dir,
|
|
268
|
+
language_level=3, reload_support=True
|
|
269
|
+
))
|
|
270
|
+
pyximport.install(setup_args=setup_args, build_dir=build_dir, reload_support=True, language_level=3)
|
|
271
|
+
|