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.
Files changed (152) hide show
  1. locator_parser/__init__.py +0 -0
  2. locator_parser/acl.py +73 -0
  3. locator_parser/actions.py +262 -0
  4. locator_parser/common.py +368 -0
  5. locator_parser/io.py +43 -0
  6. locator_parser/locator.py +150 -0
  7. onetick/__init__.py +101 -0
  8. onetick/doc_utilities/__init__.py +3 -0
  9. onetick/doc_utilities/napoleon.py +40 -0
  10. onetick/doc_utilities/ot_doctest.py +140 -0
  11. onetick/doc_utilities/snippets.py +279 -0
  12. onetick/lib/__init__.py +4 -0
  13. onetick/lib/instance.py +141 -0
  14. onetick/py/__init__.py +293 -0
  15. onetick/py/_stack_info.py +89 -0
  16. onetick/py/_version.py +2 -0
  17. onetick/py/aggregations/__init__.py +11 -0
  18. onetick/py/aggregations/_base.py +648 -0
  19. onetick/py/aggregations/_docs.py +948 -0
  20. onetick/py/aggregations/compute.py +286 -0
  21. onetick/py/aggregations/functions.py +2216 -0
  22. onetick/py/aggregations/generic.py +104 -0
  23. onetick/py/aggregations/high_low.py +80 -0
  24. onetick/py/aggregations/num_distinct.py +83 -0
  25. onetick/py/aggregations/order_book.py +501 -0
  26. onetick/py/aggregations/other.py +1014 -0
  27. onetick/py/backports.py +26 -0
  28. onetick/py/cache.py +374 -0
  29. onetick/py/callback/__init__.py +5 -0
  30. onetick/py/callback/callback.py +276 -0
  31. onetick/py/callback/callbacks.py +131 -0
  32. onetick/py/compatibility.py +798 -0
  33. onetick/py/configuration.py +771 -0
  34. onetick/py/core/__init__.py +0 -0
  35. onetick/py/core/_csv_inspector.py +93 -0
  36. onetick/py/core/_internal/__init__.py +0 -0
  37. onetick/py/core/_internal/_manually_bound_value.py +6 -0
  38. onetick/py/core/_internal/_nodes_history.py +250 -0
  39. onetick/py/core/_internal/_op_utils/__init__.py +0 -0
  40. onetick/py/core/_internal/_op_utils/every_operand.py +9 -0
  41. onetick/py/core/_internal/_op_utils/is_const.py +10 -0
  42. onetick/py/core/_internal/_per_tick_scripts/tick_list_sort_template.script +121 -0
  43. onetick/py/core/_internal/_proxy_node.py +140 -0
  44. onetick/py/core/_internal/_state_objects.py +2312 -0
  45. onetick/py/core/_internal/_state_vars.py +93 -0
  46. onetick/py/core/_source/__init__.py +0 -0
  47. onetick/py/core/_source/_symbol_param.py +95 -0
  48. onetick/py/core/_source/schema.py +97 -0
  49. onetick/py/core/_source/source_methods/__init__.py +0 -0
  50. onetick/py/core/_source/source_methods/aggregations.py +809 -0
  51. onetick/py/core/_source/source_methods/applyers.py +296 -0
  52. onetick/py/core/_source/source_methods/columns.py +141 -0
  53. onetick/py/core/_source/source_methods/data_quality.py +301 -0
  54. onetick/py/core/_source/source_methods/debugs.py +272 -0
  55. onetick/py/core/_source/source_methods/drops.py +120 -0
  56. onetick/py/core/_source/source_methods/fields.py +619 -0
  57. onetick/py/core/_source/source_methods/filters.py +1002 -0
  58. onetick/py/core/_source/source_methods/joins.py +1413 -0
  59. onetick/py/core/_source/source_methods/merges.py +605 -0
  60. onetick/py/core/_source/source_methods/misc.py +1455 -0
  61. onetick/py/core/_source/source_methods/pandases.py +155 -0
  62. onetick/py/core/_source/source_methods/renames.py +356 -0
  63. onetick/py/core/_source/source_methods/sorts.py +183 -0
  64. onetick/py/core/_source/source_methods/switches.py +142 -0
  65. onetick/py/core/_source/source_methods/symbols.py +117 -0
  66. onetick/py/core/_source/source_methods/times.py +627 -0
  67. onetick/py/core/_source/source_methods/writes.py +986 -0
  68. onetick/py/core/_source/symbol.py +205 -0
  69. onetick/py/core/_source/tmp_otq.py +222 -0
  70. onetick/py/core/column.py +209 -0
  71. onetick/py/core/column_operations/__init__.py +0 -0
  72. onetick/py/core/column_operations/_methods/__init__.py +4 -0
  73. onetick/py/core/column_operations/_methods/_internal.py +28 -0
  74. onetick/py/core/column_operations/_methods/conversions.py +216 -0
  75. onetick/py/core/column_operations/_methods/methods.py +292 -0
  76. onetick/py/core/column_operations/_methods/op_types.py +160 -0
  77. onetick/py/core/column_operations/accessors/__init__.py +0 -0
  78. onetick/py/core/column_operations/accessors/_accessor.py +28 -0
  79. onetick/py/core/column_operations/accessors/decimal_accessor.py +104 -0
  80. onetick/py/core/column_operations/accessors/dt_accessor.py +537 -0
  81. onetick/py/core/column_operations/accessors/float_accessor.py +184 -0
  82. onetick/py/core/column_operations/accessors/str_accessor.py +1367 -0
  83. onetick/py/core/column_operations/base.py +1121 -0
  84. onetick/py/core/cut_builder.py +150 -0
  85. onetick/py/core/db_constants.py +20 -0
  86. onetick/py/core/eval_query.py +245 -0
  87. onetick/py/core/lambda_object.py +441 -0
  88. onetick/py/core/multi_output_source.py +232 -0
  89. onetick/py/core/per_tick_script.py +2256 -0
  90. onetick/py/core/query_inspector.py +464 -0
  91. onetick/py/core/source.py +1744 -0
  92. onetick/py/db/__init__.py +2 -0
  93. onetick/py/db/_inspection.py +1128 -0
  94. onetick/py/db/db.py +1327 -0
  95. onetick/py/db/utils.py +64 -0
  96. onetick/py/docs/__init__.py +0 -0
  97. onetick/py/docs/docstring_parser.py +112 -0
  98. onetick/py/docs/utils.py +81 -0
  99. onetick/py/functions.py +2398 -0
  100. onetick/py/license.py +190 -0
  101. onetick/py/log.py +88 -0
  102. onetick/py/math.py +935 -0
  103. onetick/py/misc.py +470 -0
  104. onetick/py/oqd/__init__.py +22 -0
  105. onetick/py/oqd/eps.py +1195 -0
  106. onetick/py/oqd/sources.py +325 -0
  107. onetick/py/otq.py +216 -0
  108. onetick/py/pyomd_mock.py +47 -0
  109. onetick/py/run.py +916 -0
  110. onetick/py/servers.py +173 -0
  111. onetick/py/session.py +1347 -0
  112. onetick/py/sources/__init__.py +19 -0
  113. onetick/py/sources/cache.py +167 -0
  114. onetick/py/sources/common.py +128 -0
  115. onetick/py/sources/csv.py +642 -0
  116. onetick/py/sources/custom.py +85 -0
  117. onetick/py/sources/data_file.py +305 -0
  118. onetick/py/sources/data_source.py +1045 -0
  119. onetick/py/sources/empty.py +94 -0
  120. onetick/py/sources/odbc.py +337 -0
  121. onetick/py/sources/order_book.py +271 -0
  122. onetick/py/sources/parquet.py +168 -0
  123. onetick/py/sources/pit.py +191 -0
  124. onetick/py/sources/query.py +495 -0
  125. onetick/py/sources/snapshots.py +419 -0
  126. onetick/py/sources/split_query_output_by_symbol.py +198 -0
  127. onetick/py/sources/symbology_mapping.py +123 -0
  128. onetick/py/sources/symbols.py +374 -0
  129. onetick/py/sources/ticks.py +825 -0
  130. onetick/py/sql.py +70 -0
  131. onetick/py/state.py +251 -0
  132. onetick/py/types.py +2131 -0
  133. onetick/py/utils/__init__.py +70 -0
  134. onetick/py/utils/acl.py +93 -0
  135. onetick/py/utils/config.py +186 -0
  136. onetick/py/utils/default.py +49 -0
  137. onetick/py/utils/file.py +38 -0
  138. onetick/py/utils/helpers.py +76 -0
  139. onetick/py/utils/locator.py +94 -0
  140. onetick/py/utils/perf.py +498 -0
  141. onetick/py/utils/query.py +49 -0
  142. onetick/py/utils/render.py +1374 -0
  143. onetick/py/utils/script.py +244 -0
  144. onetick/py/utils/temp.py +471 -0
  145. onetick/py/utils/types.py +120 -0
  146. onetick/py/utils/tz.py +84 -0
  147. onetick_py-1.177.0.dist-info/METADATA +137 -0
  148. onetick_py-1.177.0.dist-info/RECORD +152 -0
  149. onetick_py-1.177.0.dist-info/WHEEL +5 -0
  150. onetick_py-1.177.0.dist-info/entry_points.txt +2 -0
  151. onetick_py-1.177.0.dist-info/licenses/LICENSE +21 -0
  152. 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)