onetick-py 1.177.0__py3-none-any.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.
- locator_parser/__init__.py +0 -0
- locator_parser/acl.py +73 -0
- locator_parser/actions.py +262 -0
- locator_parser/common.py +368 -0
- locator_parser/io.py +43 -0
- locator_parser/locator.py +150 -0
- onetick/__init__.py +101 -0
- onetick/doc_utilities/__init__.py +3 -0
- onetick/doc_utilities/napoleon.py +40 -0
- onetick/doc_utilities/ot_doctest.py +140 -0
- onetick/doc_utilities/snippets.py +279 -0
- onetick/lib/__init__.py +4 -0
- onetick/lib/instance.py +141 -0
- onetick/py/__init__.py +293 -0
- onetick/py/_stack_info.py +89 -0
- onetick/py/_version.py +2 -0
- onetick/py/aggregations/__init__.py +11 -0
- onetick/py/aggregations/_base.py +648 -0
- onetick/py/aggregations/_docs.py +948 -0
- onetick/py/aggregations/compute.py +286 -0
- onetick/py/aggregations/functions.py +2216 -0
- onetick/py/aggregations/generic.py +104 -0
- onetick/py/aggregations/high_low.py +80 -0
- onetick/py/aggregations/num_distinct.py +83 -0
- onetick/py/aggregations/order_book.py +501 -0
- onetick/py/aggregations/other.py +1014 -0
- onetick/py/backports.py +26 -0
- onetick/py/cache.py +374 -0
- onetick/py/callback/__init__.py +5 -0
- onetick/py/callback/callback.py +276 -0
- onetick/py/callback/callbacks.py +131 -0
- onetick/py/compatibility.py +798 -0
- onetick/py/configuration.py +771 -0
- onetick/py/core/__init__.py +0 -0
- onetick/py/core/_csv_inspector.py +93 -0
- onetick/py/core/_internal/__init__.py +0 -0
- onetick/py/core/_internal/_manually_bound_value.py +6 -0
- onetick/py/core/_internal/_nodes_history.py +250 -0
- onetick/py/core/_internal/_op_utils/__init__.py +0 -0
- onetick/py/core/_internal/_op_utils/every_operand.py +9 -0
- onetick/py/core/_internal/_op_utils/is_const.py +10 -0
- onetick/py/core/_internal/_per_tick_scripts/tick_list_sort_template.script +121 -0
- onetick/py/core/_internal/_proxy_node.py +140 -0
- onetick/py/core/_internal/_state_objects.py +2312 -0
- onetick/py/core/_internal/_state_vars.py +93 -0
- onetick/py/core/_source/__init__.py +0 -0
- onetick/py/core/_source/_symbol_param.py +95 -0
- onetick/py/core/_source/schema.py +97 -0
- onetick/py/core/_source/source_methods/__init__.py +0 -0
- onetick/py/core/_source/source_methods/aggregations.py +809 -0
- onetick/py/core/_source/source_methods/applyers.py +296 -0
- onetick/py/core/_source/source_methods/columns.py +141 -0
- onetick/py/core/_source/source_methods/data_quality.py +301 -0
- onetick/py/core/_source/source_methods/debugs.py +272 -0
- onetick/py/core/_source/source_methods/drops.py +120 -0
- onetick/py/core/_source/source_methods/fields.py +619 -0
- onetick/py/core/_source/source_methods/filters.py +1002 -0
- onetick/py/core/_source/source_methods/joins.py +1413 -0
- onetick/py/core/_source/source_methods/merges.py +605 -0
- onetick/py/core/_source/source_methods/misc.py +1455 -0
- onetick/py/core/_source/source_methods/pandases.py +155 -0
- onetick/py/core/_source/source_methods/renames.py +356 -0
- onetick/py/core/_source/source_methods/sorts.py +183 -0
- onetick/py/core/_source/source_methods/switches.py +142 -0
- onetick/py/core/_source/source_methods/symbols.py +117 -0
- onetick/py/core/_source/source_methods/times.py +627 -0
- onetick/py/core/_source/source_methods/writes.py +986 -0
- onetick/py/core/_source/symbol.py +205 -0
- onetick/py/core/_source/tmp_otq.py +222 -0
- onetick/py/core/column.py +209 -0
- onetick/py/core/column_operations/__init__.py +0 -0
- onetick/py/core/column_operations/_methods/__init__.py +4 -0
- onetick/py/core/column_operations/_methods/_internal.py +28 -0
- onetick/py/core/column_operations/_methods/conversions.py +216 -0
- onetick/py/core/column_operations/_methods/methods.py +292 -0
- onetick/py/core/column_operations/_methods/op_types.py +160 -0
- onetick/py/core/column_operations/accessors/__init__.py +0 -0
- onetick/py/core/column_operations/accessors/_accessor.py +28 -0
- onetick/py/core/column_operations/accessors/decimal_accessor.py +104 -0
- onetick/py/core/column_operations/accessors/dt_accessor.py +537 -0
- onetick/py/core/column_operations/accessors/float_accessor.py +184 -0
- onetick/py/core/column_operations/accessors/str_accessor.py +1367 -0
- onetick/py/core/column_operations/base.py +1121 -0
- onetick/py/core/cut_builder.py +150 -0
- onetick/py/core/db_constants.py +20 -0
- onetick/py/core/eval_query.py +245 -0
- onetick/py/core/lambda_object.py +441 -0
- onetick/py/core/multi_output_source.py +232 -0
- onetick/py/core/per_tick_script.py +2256 -0
- onetick/py/core/query_inspector.py +464 -0
- onetick/py/core/source.py +1744 -0
- onetick/py/db/__init__.py +2 -0
- onetick/py/db/_inspection.py +1128 -0
- onetick/py/db/db.py +1327 -0
- onetick/py/db/utils.py +64 -0
- onetick/py/docs/__init__.py +0 -0
- onetick/py/docs/docstring_parser.py +112 -0
- onetick/py/docs/utils.py +81 -0
- onetick/py/functions.py +2398 -0
- onetick/py/license.py +190 -0
- onetick/py/log.py +88 -0
- onetick/py/math.py +935 -0
- onetick/py/misc.py +470 -0
- onetick/py/oqd/__init__.py +22 -0
- onetick/py/oqd/eps.py +1195 -0
- onetick/py/oqd/sources.py +325 -0
- onetick/py/otq.py +216 -0
- onetick/py/pyomd_mock.py +47 -0
- onetick/py/run.py +916 -0
- onetick/py/servers.py +173 -0
- onetick/py/session.py +1347 -0
- onetick/py/sources/__init__.py +19 -0
- onetick/py/sources/cache.py +167 -0
- onetick/py/sources/common.py +128 -0
- onetick/py/sources/csv.py +642 -0
- onetick/py/sources/custom.py +85 -0
- onetick/py/sources/data_file.py +305 -0
- onetick/py/sources/data_source.py +1045 -0
- onetick/py/sources/empty.py +94 -0
- onetick/py/sources/odbc.py +337 -0
- onetick/py/sources/order_book.py +271 -0
- onetick/py/sources/parquet.py +168 -0
- onetick/py/sources/pit.py +191 -0
- onetick/py/sources/query.py +495 -0
- onetick/py/sources/snapshots.py +419 -0
- onetick/py/sources/split_query_output_by_symbol.py +198 -0
- onetick/py/sources/symbology_mapping.py +123 -0
- onetick/py/sources/symbols.py +374 -0
- onetick/py/sources/ticks.py +825 -0
- onetick/py/sql.py +70 -0
- onetick/py/state.py +251 -0
- onetick/py/types.py +2131 -0
- onetick/py/utils/__init__.py +70 -0
- onetick/py/utils/acl.py +93 -0
- onetick/py/utils/config.py +186 -0
- onetick/py/utils/default.py +49 -0
- onetick/py/utils/file.py +38 -0
- onetick/py/utils/helpers.py +76 -0
- onetick/py/utils/locator.py +94 -0
- onetick/py/utils/perf.py +498 -0
- onetick/py/utils/query.py +49 -0
- onetick/py/utils/render.py +1374 -0
- onetick/py/utils/script.py +244 -0
- onetick/py/utils/temp.py +471 -0
- onetick/py/utils/types.py +120 -0
- onetick/py/utils/tz.py +84 -0
- onetick_py-1.177.0.dist-info/METADATA +137 -0
- onetick_py-1.177.0.dist-info/RECORD +152 -0
- onetick_py-1.177.0.dist-info/WHEEL +5 -0
- onetick_py-1.177.0.dist-info/entry_points.txt +2 -0
- onetick_py-1.177.0.dist-info/licenses/LICENSE +21 -0
- onetick_py-1.177.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
import operator
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
import onetick.py as otp
|
|
5
|
+
from onetick.py.otq import otq
|
|
6
|
+
|
|
7
|
+
from onetick.py.core.source import Source
|
|
8
|
+
|
|
9
|
+
from .. import types as ott
|
|
10
|
+
from .. import utils, configuration
|
|
11
|
+
from ..core import query_inspector
|
|
12
|
+
from ..core.column_operations.base import _Operation
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
_QUERY_PARAM_SPECIAL_CHARACTERS = "=,"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Query(Source):
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
query_object=None,
|
|
22
|
+
out_pin=utils.adaptive,
|
|
23
|
+
symbol=utils.adaptive,
|
|
24
|
+
start=utils.adaptive,
|
|
25
|
+
end=utils.adaptive,
|
|
26
|
+
params=None,
|
|
27
|
+
schema=None,
|
|
28
|
+
**kwargs,
|
|
29
|
+
):
|
|
30
|
+
"""
|
|
31
|
+
Create data source object from .otq file or query object
|
|
32
|
+
|
|
33
|
+
Parameters
|
|
34
|
+
----------
|
|
35
|
+
query_object: path or :class:`query`
|
|
36
|
+
query to use as a data source
|
|
37
|
+
out_pin: str
|
|
38
|
+
query output pin name
|
|
39
|
+
symbol: None, :py:class:`onetick.py.adaptive`
|
|
40
|
+
Symbol(s) from which data should be taken.
|
|
41
|
+
start, end : :py:class:`datetime.datetime`, :py:class:`otp.datetime <onetick.py.datetime>` or utils.adaptive
|
|
42
|
+
Time interval from which the data should be taken.
|
|
43
|
+
params: dict
|
|
44
|
+
params to pass to query.
|
|
45
|
+
Only applicable to string ``query_object``
|
|
46
|
+
"""
|
|
47
|
+
if self._try_default_constructor(schema=schema, **kwargs):
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
if params is None:
|
|
51
|
+
params = {}
|
|
52
|
+
|
|
53
|
+
# Ignore because of the "Only @runtime_checkable protocols can be used with instance and class checks"
|
|
54
|
+
if isinstance(query_object, (str, os.PathLike)): # type: ignore
|
|
55
|
+
query_object = query(str(query_object), **params)
|
|
56
|
+
elif isinstance(query_object, query):
|
|
57
|
+
if len(params) > 0:
|
|
58
|
+
raise ValueError("Cannot pass both params and a query() (not str) query_object parameter")
|
|
59
|
+
else:
|
|
60
|
+
raise ValueError("query_object parameter has to be either a str (path to the query) or a query object")
|
|
61
|
+
|
|
62
|
+
if symbol == utils.adaptive:
|
|
63
|
+
if query_object.graph_info is None or not query_object.graph_info.has_unbound_sources:
|
|
64
|
+
symbol = None
|
|
65
|
+
elif symbol is not None:
|
|
66
|
+
raise ValueError("symbol parameter should be either None or otp.adaptive")
|
|
67
|
+
|
|
68
|
+
super().__init__(
|
|
69
|
+
_symbols=symbol, _start=start, _end=end, _base_ep_func=lambda: self.base_ep(query_object, out_pin),
|
|
70
|
+
schema=schema, **kwargs,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def base_ep(self, query_object, out_pin):
|
|
74
|
+
nested = otq.NestedOtq(query_object.path, query_object.str_params)
|
|
75
|
+
graph = query_object.graph_info
|
|
76
|
+
|
|
77
|
+
if out_pin is utils.adaptive:
|
|
78
|
+
if graph is not None:
|
|
79
|
+
if len(graph.nested_outputs) == 1:
|
|
80
|
+
return Source(nested[graph.nested_outputs[0].NESTED_OUTPUT])
|
|
81
|
+
if len(graph.nested_outputs) > 1:
|
|
82
|
+
raise ValueError(
|
|
83
|
+
f'Query "{query_object.query_name}" has multiple outputs, but you have not '
|
|
84
|
+
"specified which one should be used. You could specify it"
|
|
85
|
+
' using "out_pin" parameter of the Query constructor.'
|
|
86
|
+
)
|
|
87
|
+
# no output
|
|
88
|
+
return Source(nested, _has_output=False)
|
|
89
|
+
|
|
90
|
+
if graph is not None:
|
|
91
|
+
existed_out_pins = set(map(operator.attrgetter("NESTED_OUTPUT"), graph.nested_outputs))
|
|
92
|
+
if out_pin not in existed_out_pins:
|
|
93
|
+
raise ValueError(
|
|
94
|
+
f'Query "{query_object.query_name}" does not have the "{out_pin}" output, there are only following '
|
|
95
|
+
f"output pins exist: {','.join(existed_out_pins)}"
|
|
96
|
+
)
|
|
97
|
+
return Source(nested[out_pin])
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class query:
|
|
101
|
+
"""
|
|
102
|
+
Constructs a query object with a certain path.
|
|
103
|
+
Keyword arguments specify query parameters.
|
|
104
|
+
|
|
105
|
+
You also can pass an instance of ``otp.query.config`` class as the second positional argument to
|
|
106
|
+
specify a query.
|
|
107
|
+
|
|
108
|
+
Parameters
|
|
109
|
+
----------
|
|
110
|
+
|
|
111
|
+
path : str
|
|
112
|
+
path to an .otq file.
|
|
113
|
+
If path is relative, then it's assumed that file is located in one of the directories
|
|
114
|
+
specified in OneTick ``OTQ_FILE_PATH`` configuration variable.
|
|
115
|
+
If there are more than one query in the file, then its name should be specified
|
|
116
|
+
in the format ``<path>::<query-name>``.
|
|
117
|
+
|
|
118
|
+
Also prefix ``remote://<database-name>::`` can be used to specify if query is located
|
|
119
|
+
on the remote server.
|
|
120
|
+
If such path exists locally too, then this file is inspected to get info about query and its pins
|
|
121
|
+
as we can't get this info from remote server.
|
|
122
|
+
config:
|
|
123
|
+
optional ``otp.query.config`` object.
|
|
124
|
+
This object can be used to specify different query options, e.g. output columns.
|
|
125
|
+
params:
|
|
126
|
+
parameters for the query.
|
|
127
|
+
Dictionary if parameters' names and their values.
|
|
128
|
+
|
|
129
|
+
Raises
|
|
130
|
+
------
|
|
131
|
+
ValueError, TypeError
|
|
132
|
+
|
|
133
|
+
Examples
|
|
134
|
+
--------
|
|
135
|
+
|
|
136
|
+
Adding local query and applying it to the source:
|
|
137
|
+
|
|
138
|
+
>>> q = otp.query('/otqs/some.otq::some_query', PARAM1='val1', PARAM2=3.14) # doctest: +SKIP
|
|
139
|
+
>>> t = otp.Tick(A=1)
|
|
140
|
+
>>> t = t.apply(q) # doctest: +SKIP
|
|
141
|
+
|
|
142
|
+
Adding remote query:
|
|
143
|
+
|
|
144
|
+
>>> otp.query('remote://DATABASE::/otqs/some.otq::some_query', PARAM1='val1', PARAM2=3.14) # doctest: +ELLIPSIS
|
|
145
|
+
<onetick.py.sources.query.query object at ...>
|
|
146
|
+
|
|
147
|
+
Creating python wrapper function around query from ``.otq`` file:
|
|
148
|
+
|
|
149
|
+
>>> def add_w(src):
|
|
150
|
+
... schema = dict(src.schema)
|
|
151
|
+
... if 'W' in schema:
|
|
152
|
+
... raise ValueError("column 'W' already exists")
|
|
153
|
+
... else:
|
|
154
|
+
... schema['W'] = str
|
|
155
|
+
... q = otp.query('add_w.otq::w', otp.query.config(output_columns=list(schema.items())))
|
|
156
|
+
... return src.apply(q)
|
|
157
|
+
>>> t = otp.Tick(A=1)
|
|
158
|
+
>>> t = add_w(t)
|
|
159
|
+
>>> t.schema
|
|
160
|
+
{'A': <class 'int'>, 'W': <class 'str'>}
|
|
161
|
+
>>> t['X'] = t['W'].str.upper()
|
|
162
|
+
>>> otp.run(t)
|
|
163
|
+
Time A W X
|
|
164
|
+
0 2003-12-01 1 hello HELLO
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
class config:
|
|
168
|
+
"""
|
|
169
|
+
The config allows to specify different query options.
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
_special_values = {"input"}
|
|
173
|
+
|
|
174
|
+
def __init__(self, output_columns=None):
|
|
175
|
+
"""
|
|
176
|
+
Parameters
|
|
177
|
+
----------
|
|
178
|
+
|
|
179
|
+
output_columns : str, list, dict, optional
|
|
180
|
+
The parameter defines what the outputs columns are.
|
|
181
|
+
Default value is ``None`` that means no output fields after applying query
|
|
182
|
+
for every output pin.
|
|
183
|
+
|
|
184
|
+
The ``input`` string value means that output columns are the same as inputs for
|
|
185
|
+
every output pin.
|
|
186
|
+
|
|
187
|
+
A list of tuples allows to define output columns with their types;
|
|
188
|
+
for example ``[('x', int), ('y', float), ...]``. Applicable for every output
|
|
189
|
+
pin.
|
|
190
|
+
|
|
191
|
+
A dict allows to specify output columns for every output pin.
|
|
192
|
+
|
|
193
|
+
Raises
|
|
194
|
+
------
|
|
195
|
+
TypeError, ValueError
|
|
196
|
+
|
|
197
|
+
Examples
|
|
198
|
+
--------
|
|
199
|
+
>>> otp.query('/otqs/some.otq::some_query', otp.query.config(output_columns=[('X': int)])) # doctest: +SKIP
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
if output_columns is not None:
|
|
203
|
+
if isinstance(output_columns, list):
|
|
204
|
+
self._validate_columns(output_columns)
|
|
205
|
+
elif isinstance(output_columns, dict):
|
|
206
|
+
for pin, columns in output_columns.items():
|
|
207
|
+
if not isinstance(pin, str):
|
|
208
|
+
raise TypeError(f"Name of pin '{type(pin)}' is of non-str type '%s'")
|
|
209
|
+
else:
|
|
210
|
+
self._validate_columns(columns)
|
|
211
|
+
|
|
212
|
+
elif not isinstance(output_columns, str):
|
|
213
|
+
raise TypeError(f'"output_columns" does not support value of the "{type(output_columns)}" type')
|
|
214
|
+
|
|
215
|
+
if isinstance(output_columns, str):
|
|
216
|
+
if output_columns not in self._special_values:
|
|
217
|
+
raise ValueError(f'Config does not support "{output_columns}" value')
|
|
218
|
+
|
|
219
|
+
self.output_columns = output_columns
|
|
220
|
+
|
|
221
|
+
def _validate_list_item(self, item):
|
|
222
|
+
if isinstance(item, str):
|
|
223
|
+
if item not in self._special_values:
|
|
224
|
+
raise ValueError(f"Value {item} is not supported.")
|
|
225
|
+
|
|
226
|
+
else:
|
|
227
|
+
if not isinstance(item, (tuple, list)) or (len(item) != 2) or not isinstance(item[0], str):
|
|
228
|
+
raise TypeError("Value %s is not a name-type tuple.")
|
|
229
|
+
|
|
230
|
+
def _validate_columns(self, columns):
|
|
231
|
+
if isinstance(columns, str):
|
|
232
|
+
if columns not in self._special_values:
|
|
233
|
+
raise ValueError(f"A pin has invalid output columns definition: '{columns}'")
|
|
234
|
+
|
|
235
|
+
elif isinstance(columns, list):
|
|
236
|
+
if columns.count("input") > 1:
|
|
237
|
+
raise ValueError(f"More than one 'input' value in {columns}")
|
|
238
|
+
|
|
239
|
+
for item in columns:
|
|
240
|
+
self._validate_list_item(item)
|
|
241
|
+
|
|
242
|
+
else:
|
|
243
|
+
raise TypeError(f"A pin's columns definition is of unsupported type '{type(columns)}'")
|
|
244
|
+
|
|
245
|
+
def _get_output_columns_for_pin(self, out_pin_name):
|
|
246
|
+
if isinstance(self.output_columns, dict):
|
|
247
|
+
if out_pin_name not in self.output_columns:
|
|
248
|
+
raise ValueError(f"Pin {out_pin_name} wasn't declared in the config")
|
|
249
|
+
else:
|
|
250
|
+
return self.output_columns[out_pin_name]
|
|
251
|
+
|
|
252
|
+
else:
|
|
253
|
+
return self.output_columns
|
|
254
|
+
|
|
255
|
+
def _apply(self, out_pin_name, src):
|
|
256
|
+
"""
|
|
257
|
+
Applying specified logic on a certain object. Used internally in the functions.apply_query
|
|
258
|
+
"""
|
|
259
|
+
columns_descriptor = self._get_output_columns_for_pin(out_pin_name)
|
|
260
|
+
if columns_descriptor is None:
|
|
261
|
+
# drop columns by default, because we don't know
|
|
262
|
+
# how an external query changes data schema
|
|
263
|
+
src.drop_columns()
|
|
264
|
+
elif columns_descriptor != "input":
|
|
265
|
+
if "input" not in columns_descriptor:
|
|
266
|
+
src.drop_columns()
|
|
267
|
+
|
|
268
|
+
for item in columns_descriptor:
|
|
269
|
+
if item != "input":
|
|
270
|
+
name, dtype = item
|
|
271
|
+
src.schema[name] = dtype
|
|
272
|
+
|
|
273
|
+
def __init__(self, path, *config, **params):
|
|
274
|
+
|
|
275
|
+
# prepare parameters
|
|
276
|
+
self._str_params = None
|
|
277
|
+
self.params = params
|
|
278
|
+
self.update_params()
|
|
279
|
+
|
|
280
|
+
# prepare configs
|
|
281
|
+
if len(config) > 1:
|
|
282
|
+
raise ValueError(f"It is allowed to specify only one config object, but passed {len(config)}")
|
|
283
|
+
elif len(config) == 1:
|
|
284
|
+
if not isinstance(config[0], self.config):
|
|
285
|
+
raise TypeError(
|
|
286
|
+
f'It is expected to see config of the "query.config" type, but got "{type(config[0])}"'
|
|
287
|
+
)
|
|
288
|
+
self.config = config[0]
|
|
289
|
+
else:
|
|
290
|
+
self.config = self.config()
|
|
291
|
+
|
|
292
|
+
# prepare path and query name
|
|
293
|
+
|
|
294
|
+
path = str(path)
|
|
295
|
+
|
|
296
|
+
remote = path.startswith('remote://')
|
|
297
|
+
if remote:
|
|
298
|
+
self.path = path
|
|
299
|
+
_, path = path.split('::', maxsplit=1)
|
|
300
|
+
else:
|
|
301
|
+
if otp.__webapi__:
|
|
302
|
+
# remote:// used only for remote queries, not for locals, like we do without webapi
|
|
303
|
+
self.path = path
|
|
304
|
+
else:
|
|
305
|
+
self.path = f"remote://{configuration.config.get('default_db', 'LOCAL')}::" + path
|
|
306
|
+
|
|
307
|
+
self.query_path, self.query_name = utils.query_to_path_and_name(path)
|
|
308
|
+
|
|
309
|
+
# if query_path does not exist, then we try
|
|
310
|
+
# to resolve it with OTQ_FILE_PATH assuming that
|
|
311
|
+
# a relative path is passed
|
|
312
|
+
if not os.path.exists(self.query_path):
|
|
313
|
+
otq_path = utils.get_config_param(os.environ["ONE_TICK_CONFIG"], "OTQ_FILE_PATH", "")
|
|
314
|
+
try:
|
|
315
|
+
self.query_path = utils.abspath_to_query_by_otq_path(otq_path, self.query_path)
|
|
316
|
+
except FileNotFoundError:
|
|
317
|
+
if remote:
|
|
318
|
+
# TODO: we want to get self.graph_info from remote query somehow, probably will have to download it
|
|
319
|
+
self.graph_info = None
|
|
320
|
+
return
|
|
321
|
+
raise
|
|
322
|
+
|
|
323
|
+
if self.query_name is None:
|
|
324
|
+
# it seems that query name was not passed, then try to find it
|
|
325
|
+
queries = query_inspector.get_queries(self.query_path)
|
|
326
|
+
if len(queries) > 1:
|
|
327
|
+
raise ValueError(f"{self.query_path} has more than one query, "
|
|
328
|
+
f"but you have not specified which one to use.")
|
|
329
|
+
self.query_name = queries[0]
|
|
330
|
+
|
|
331
|
+
self.graph_info = query_inspector.get_query_info(self.query_path, self.query_name)
|
|
332
|
+
|
|
333
|
+
def __call__(self, *ticks, **pins):
|
|
334
|
+
"""
|
|
335
|
+
Return object representing outputs of the query.
|
|
336
|
+
This object can be used to get a specified output pin of the query as a new :py:class:`onetick.py.Source`.
|
|
337
|
+
|
|
338
|
+
Examples
|
|
339
|
+
--------
|
|
340
|
+
>>> query = otp.query('/otqs/some.otq::some_query', PARAM1='val1') # doctest: +SKIP
|
|
341
|
+
>>> query()['OUT'] # doctest: +SKIP,+ELLIPSIS
|
|
342
|
+
<onetick.py.core.source.Source at ...>
|
|
343
|
+
"""
|
|
344
|
+
for key, value in pins.items():
|
|
345
|
+
if not isinstance(value, Source):
|
|
346
|
+
raise ValueError(f'Input "{key}" pin does not support "{type(value)}" type')
|
|
347
|
+
|
|
348
|
+
if self.graph_info is not None and len(pins) == 0 and len(ticks) == 1:
|
|
349
|
+
if len(self.graph_info.nested_inputs) != 1:
|
|
350
|
+
raise ValueError(
|
|
351
|
+
f'It is expected the query "{self.query_path}" to have one input, but it'
|
|
352
|
+
f" has {len(self.graph_info.nested_inputs)}"
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
pins[self.graph_info.nested_inputs[0].NESTED_INPUT] = ticks[0]
|
|
356
|
+
elif len(ticks) == 0:
|
|
357
|
+
# it is the valid case, when query has no input pins
|
|
358
|
+
pass
|
|
359
|
+
else:
|
|
360
|
+
raise ValueError("It is allowed to pass only one non-specified input")
|
|
361
|
+
|
|
362
|
+
outputs = self._outputs()
|
|
363
|
+
outputs.query = self
|
|
364
|
+
outputs.in_sources = pins
|
|
365
|
+
|
|
366
|
+
return outputs
|
|
367
|
+
|
|
368
|
+
class _outputs:
|
|
369
|
+
def __getitem__(self, key):
|
|
370
|
+
output_pins = []
|
|
371
|
+
|
|
372
|
+
if isinstance(key, tuple):
|
|
373
|
+
output_pins = list(key)
|
|
374
|
+
elif isinstance(key, str):
|
|
375
|
+
output_pins = [key]
|
|
376
|
+
elif key is None:
|
|
377
|
+
# No output
|
|
378
|
+
pass
|
|
379
|
+
else:
|
|
380
|
+
raise ValueError(f'Output pins can not be of "{type(key)}" type')
|
|
381
|
+
|
|
382
|
+
return otp.apply_query(
|
|
383
|
+
self.query, in_sources=self.in_sources, output_pins=output_pins, **self.query.params
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
def to_eval_string(self):
|
|
387
|
+
"""
|
|
388
|
+
Converts query object to OneTick's `eval` string.
|
|
389
|
+
"""
|
|
390
|
+
res = '"' + self.path + '"'
|
|
391
|
+
if self.params:
|
|
392
|
+
res += f', "{self._params_to_str(self.params, with_expr=True)}"'
|
|
393
|
+
return "eval(" + res + ")"
|
|
394
|
+
|
|
395
|
+
def update_params(self, **new_params):
|
|
396
|
+
"""
|
|
397
|
+
Update dictionary of parameters of the query.
|
|
398
|
+
"""
|
|
399
|
+
if new_params:
|
|
400
|
+
self.params.update(new_params)
|
|
401
|
+
|
|
402
|
+
@property
|
|
403
|
+
def str_params(self):
|
|
404
|
+
"""
|
|
405
|
+
Query parameters converted to OneTick string representation.
|
|
406
|
+
"""
|
|
407
|
+
if self._str_params is None:
|
|
408
|
+
self._str_params = self._params_to_str(self.params)
|
|
409
|
+
return self._str_params
|
|
410
|
+
|
|
411
|
+
@staticmethod
|
|
412
|
+
def _params_to_str(params, *, with_expr=False):
|
|
413
|
+
""" converts param to str
|
|
414
|
+
|
|
415
|
+
Parameters
|
|
416
|
+
----------
|
|
417
|
+
params: dict
|
|
418
|
+
Parameters as dict(name=value)
|
|
419
|
+
with_expr:
|
|
420
|
+
If true return all expression in expr() function
|
|
421
|
+
|
|
422
|
+
Returns
|
|
423
|
+
-------
|
|
424
|
+
result: str
|
|
425
|
+
string representation of parameters ready for query evaluation
|
|
426
|
+
"""
|
|
427
|
+
|
|
428
|
+
def to_str(v):
|
|
429
|
+
if isinstance(v, list):
|
|
430
|
+
return "\\,".join(map(to_str, v))
|
|
431
|
+
else:
|
|
432
|
+
if with_expr:
|
|
433
|
+
is_dt = ott.is_time_type(v)
|
|
434
|
+
if is_dt:
|
|
435
|
+
v = ott.value2str(v)
|
|
436
|
+
result = query._escape_quotes_in_eval(v)
|
|
437
|
+
if isinstance(v, _Operation) and getattr(v, "name", None) != "_SYMBOL_NAME" or is_dt:
|
|
438
|
+
result = f"expr({result})"
|
|
439
|
+
else:
|
|
440
|
+
result = query._escape_characters_in_query_param(str(v))
|
|
441
|
+
return result
|
|
442
|
+
|
|
443
|
+
return ",".join(key + "=" + to_str(value) for key, value in params.items())
|
|
444
|
+
|
|
445
|
+
@staticmethod
|
|
446
|
+
def _escape_quotes_in_eval(v):
|
|
447
|
+
return str(v).translate(str.maketrans({"'": r"\'", '"': r'\"'}))
|
|
448
|
+
|
|
449
|
+
@staticmethod
|
|
450
|
+
def _escape_characters_in_query_param(result):
|
|
451
|
+
# 0 - no need to add backslash, 1 - need to add
|
|
452
|
+
char_map = [0] * len(result)
|
|
453
|
+
|
|
454
|
+
# put 1 between two quotes symbols
|
|
455
|
+
open_char = None
|
|
456
|
+
last_inx = 0
|
|
457
|
+
for inx, c in enumerate(result):
|
|
458
|
+
if open_char == c:
|
|
459
|
+
open_char = None
|
|
460
|
+
continue
|
|
461
|
+
|
|
462
|
+
if not open_char and c in ("'", '"'):
|
|
463
|
+
open_char = c
|
|
464
|
+
last_inx = inx + 1
|
|
465
|
+
continue
|
|
466
|
+
|
|
467
|
+
if open_char:
|
|
468
|
+
char_map[inx] = 1
|
|
469
|
+
|
|
470
|
+
# clean open tail if necessary
|
|
471
|
+
if open_char:
|
|
472
|
+
char_map[last_inx:] = [0] * (len(result) - last_inx)
|
|
473
|
+
|
|
474
|
+
# apply mapping
|
|
475
|
+
res = []
|
|
476
|
+
last_esc = False # do not add esc if the previous one is already esc
|
|
477
|
+
n_brackets_in_expr_block = 0 # do not escape in expr(...)
|
|
478
|
+
for inx, c in enumerate(result):
|
|
479
|
+
if c == "(":
|
|
480
|
+
if n_brackets_in_expr_block:
|
|
481
|
+
n_brackets_in_expr_block += 1
|
|
482
|
+
elif result[inx - 4:inx] == "expr":
|
|
483
|
+
n_brackets_in_expr_block = 1
|
|
484
|
+
if c == ")" and n_brackets_in_expr_block:
|
|
485
|
+
n_brackets_in_expr_block -= 1
|
|
486
|
+
|
|
487
|
+
if c in _QUERY_PARAM_SPECIAL_CHARACTERS and char_map[inx] == 0:
|
|
488
|
+
if not last_esc and not n_brackets_in_expr_block:
|
|
489
|
+
c = "\\" + c
|
|
490
|
+
|
|
491
|
+
last_esc = c == "\\"
|
|
492
|
+
|
|
493
|
+
res.append(c)
|
|
494
|
+
|
|
495
|
+
return "".join(res)
|