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,471 @@
1
+ import abc
2
+ import datetime
3
+ import errno
4
+ import getpass
5
+ import os
6
+ import shutil
7
+ import tempfile
8
+ import weakref
9
+ from collections import defaultdict
10
+ from typing import Dict, List
11
+ from .types import default
12
+
13
+ import coolname
14
+
15
+
16
+ WEBAPI_TEST_MODE_SHARED_CONFIG = os.getenv('WEBAPI_TEST_MODE_SHARED_CONFIG')
17
+ if os.getenv('OTP_WEBAPI_TEST_MODE') and not WEBAPI_TEST_MODE_SHARED_CONFIG:
18
+ raise ValueError("WEBAPI_TEST_MODE_SHARED_CONFIG is not set, but it is required with OTP_WEBAPI_TEST_MODE.")
19
+
20
+
21
+ def default_clean_up(clean_up):
22
+ from ..configuration import config
23
+ if clean_up is default:
24
+ return config.clean_up_tmp_files
25
+ return clean_up
26
+
27
+
28
+ def get_logger(*args):
29
+ from .. import log
30
+ return log.get_logger(__name__, *args)
31
+
32
+
33
+ class CleanUpFinalizer:
34
+ """
35
+ Class that manages destruction of the object,
36
+ setting up proper finalizer callback and clean_up boolean flag.
37
+ """
38
+
39
+ def __init__(self, clean_up=default, *args):
40
+ clean_up = default_clean_up(clean_up)
41
+ # we need to use reference type here so finalizer can get the latest clean_up value,
42
+ # even if this value is changed after finalizer is created
43
+ # (because weakref.finalize can't use object's fields)
44
+ self._clean_up_ref = [clean_up]
45
+ self._finalizer = weakref.finalize(self, self._cleanup, self._clean_up_ref, *args)
46
+
47
+ @classmethod
48
+ def _cleanup(cls, clean_up_ref, *args):
49
+ raise NotImplementedError()
50
+
51
+ @property
52
+ def need_to_cleanup(self):
53
+ return self._clean_up_ref[0]
54
+
55
+ @need_to_cleanup.setter
56
+ def need_to_cleanup(self, value):
57
+ self._clean_up_ref[0] = value
58
+
59
+
60
+ class File(abc.ABC):
61
+ def __init__(self, path):
62
+ self._path = path
63
+
64
+ @property
65
+ def path(self):
66
+ return self._path
67
+
68
+ def __str__(self):
69
+ return self._path
70
+
71
+ def __fspath__(self):
72
+ """
73
+ If children inherit os.PathLike, then this method is required and
74
+ makes files behave compatible with 'os' module
75
+ """
76
+ return self._path
77
+
78
+
79
+ def __name_generator():
80
+ """
81
+ Returns tuple with initialized name generator and
82
+ the number of unique names this generator will produce
83
+ before it starts to repeat itself.
84
+ """
85
+ def cool_gen():
86
+ while True:
87
+ yield coolname.generate_slug(2)
88
+ return cool_gen(), coolname.get_combinations_count(2)
89
+
90
+
91
+ def mktemp(fun, dir, prefix='', suffix='', **kwargs):
92
+ names, max_unique_values = __name_generator()
93
+ for _ in range(max_unique_values):
94
+ name = next(names)
95
+ filename = os.path.join(dir, prefix + name + suffix)
96
+ try:
97
+ return fun(filename, **kwargs), filename
98
+ except FileExistsError:
99
+ continue
100
+ except PermissionError:
101
+ # This exception is thrown when a directory with the chosen name already exists on Windows
102
+ if os.name == 'nt' and os.path.isdir(dir) and os.access(dir, os.W_OK):
103
+ continue
104
+ else:
105
+ raise
106
+ raise FileExistsError(errno.EEXIST, 'No usable temporary file name found')
107
+
108
+
109
+ def mkstemp(dir, prefix='', suffix=''):
110
+ flags = os.O_RDWR | os.O_CREAT | os.O_EXCL
111
+ if hasattr(os, 'O_NOFOLLOW'):
112
+ flags |= os.O_NOFOLLOW
113
+ return mktemp(os.open, dir, prefix, suffix, flags=flags, mode=0o600)
114
+
115
+
116
+ def mkdtemp(dir, prefix='', suffix=''):
117
+ _, filename = mktemp(os.mkdir, dir, prefix, suffix, mode=0o700)
118
+ return filename
119
+
120
+
121
+ _TMP_CONFIGS_DIR_BASE = os.environ.get('OTP_BASE_FOLDER_FOR_GENERATED_RESOURCE',
122
+ os.path.join(tempfile.gettempdir(), "test_" + getpass.getuser()))
123
+
124
+
125
+ class TmpFile(File, os.PathLike, CleanUpFinalizer):
126
+
127
+ ALL: Dict[str, List['TmpFile']] = defaultdict(list)
128
+ keep_everything_generated = False
129
+
130
+ def __init__(self, suffix="", name="", clean_up=default, force=False, base_dir=None):
131
+ """
132
+ Class to create a temporary file.
133
+ By default, this file will be deleted automatically after all references to it are gone.
134
+ Base path where temporary files are created could be set using the ``ONE_TICK_TMP_DIR``.
135
+ By default they are created under the ``tempfile.gettempdir()`` folder.
136
+
137
+
138
+ Parameters
139
+ ----------
140
+ name: str
141
+ name of the temporary file without suffix.
142
+ By default some random name will be generated.
143
+ suffix: str
144
+ suffix of the name of the temporary file.
145
+ clean_up: bool
146
+ Controls whether this temporary file will be deleted automatically
147
+ after all references to it are gone.
148
+
149
+ By default,
150
+ :py:attr:`otp.config.clean_up_tmp_files<onetick.py.configuration.Config.clean_up_tmp_files>` is used.
151
+ force: bool
152
+ Rewrite temporary file if it exists and parameter ``name`` is set.
153
+ base_dir: str
154
+ Absolute path of the directory where temporary file will be created.
155
+
156
+ See also
157
+ --------
158
+
159
+ The testing framework has a ``--keep-generated`` flag that controls clean up for all related instances
160
+ :ref:`onetick py test features`
161
+ """
162
+ clean_up = default_clean_up(clean_up)
163
+ clean_up = clean_up and not TmpFile.keep_everything_generated
164
+ fd, self._path = self._create(clean_up, suffix=suffix, name=name, force=force, base_dir=base_dir)
165
+ # we only needed to create file, so closing opened file descriptor
166
+ os.close(fd)
167
+ self._logger = get_logger(self.__class__.__name__)
168
+ CleanUpFinalizer.__init__(self, clean_up, self.path, self._logger)
169
+ self._logger.debug(f'created {self._path}, clean_up={self.need_to_cleanup}')
170
+
171
+ def __repr__(self):
172
+ return f"TmpFile({self.path})"
173
+
174
+ @classmethod
175
+ def _cleanup(cls, clean_up_ref, path, logger):
176
+ if clean_up_ref[0] and os.path.exists(path):
177
+ logger.debug(f'removing {path}')
178
+ try:
179
+ os.remove(path)
180
+ except Exception:
181
+ # TODO: remove try-except block when BDS-116 will be fixed
182
+ pass
183
+
184
+ def _create(self, clean_up, suffix="", name="", force=False, base_dir=None):
185
+
186
+ self._parent_dir = None
187
+
188
+ if ONE_TICK_TMP_DIR() and not base_dir:
189
+ # allows to set a test name suffix for when we use it in
190
+ # tests with goal to distinguish tests content (configs, databases, etc)
191
+ # from each other
192
+ dir_path = ONE_TICK_TMP_DIR()
193
+ # save to access them from the teardown
194
+ TmpFile.ALL[dir_path].append(self)
195
+
196
+ if not os.path.exists(dir_path):
197
+ # If it does not exist, then lets create it. Note that it goes up
198
+ # recursively until finds an existing folder.
199
+ # We store it as a field of the instance to guarantee that it will
200
+ # be destroyed later than a temporary file.
201
+ self._parent_dir = GeneratedDir(dir_path, clean_up=clean_up)
202
+ elif dir_path in GeneratedDir.ALL:
203
+ self._parent_dir = GeneratedDir.ALL[dir_path]
204
+ else:
205
+ if base_dir and not os.path.exists(base_dir):
206
+ # If it does not exist, then lets create it. Note that it goes up
207
+ # recursively until finds an existing folder.
208
+ # We store it as a field of the instance to guarantee that it will
209
+ # be destroyed later than a temporary file.
210
+ self._parent_dir = GeneratedDir(base_dir, clean_up=clean_up)
211
+ dir_path = base_dir if base_dir else TMP_CONFIGS_DIR()
212
+ TmpFile.ALL[dir_path].append(self)
213
+ if dir_path in GeneratedDir.ALL:
214
+ self._parent_dir = GeneratedDir.ALL[dir_path]
215
+
216
+ # let know the parent generated folder to not destroy itself
217
+ if self._parent_dir is not None and not clean_up:
218
+ self._parent_dir.need_to_cleanup = False
219
+
220
+ if name:
221
+ path = os.path.normpath(os.path.join(dir_path, name)) + suffix
222
+ flags = os.O_RDWR | os.O_CREAT
223
+ if not force:
224
+ flags = flags | os.O_EXCL
225
+ if hasattr(os, 'O_NOFOLLOW'):
226
+ flags |= os.O_NOFOLLOW
227
+ mode = 0o600
228
+ if os.getenv('OTP_WEBAPI_TEST_MODE'):
229
+ mode = 0o644
230
+ return os.open(path, flags=flags, mode=mode), path
231
+ else:
232
+ return mkstemp(dir=dir_path, suffix=suffix, prefix="")
233
+
234
+
235
+ class TmpDir(os.PathLike, CleanUpFinalizer):
236
+
237
+ ALL: Dict[str, List['TmpDir']] = defaultdict(list)
238
+ keep_everything_generated = False
239
+
240
+ def __init__(self, rel_path="", *, suffix="", clean_up=default, base_dir=""):
241
+ """
242
+ Class to create a temporary directory.
243
+ By default, this directory will be deleted automatically after all references to it are gone.
244
+ All files and directories under this one will be deleted too.
245
+
246
+ Base path where directories are created could be set using the ``ONE_TICK_TMP_DIR``.
247
+ By default they are created under the ``tempfile.gettempdir()`` folder.
248
+
249
+ Parameters
250
+ ----------
251
+ rel_path: str
252
+ relative path to the temporary directory.
253
+ If empty, then the name will be auto-generated.
254
+ suffix: str
255
+ suffix of the name of the temporary directory.
256
+ base_dir: str
257
+ relative path of the directory where temporary directory will be created.
258
+ clean_up: bool
259
+ Controls whether this temporary directory will be deleted automatically
260
+ after all references to it are gone.
261
+
262
+ By default,
263
+ :py:attr:`otp.config.clean_up_tmp_files<onetick.py.configuration.Config.clean_up_tmp_files>` is used.
264
+
265
+ See also
266
+ --------
267
+
268
+ The testing framework has a ``--keep-generated`` flag that controls clean up for all related instances
269
+ :ref:`onetick py test features`
270
+ """
271
+ clean_up = default_clean_up(clean_up)
272
+ clean_up = clean_up and not TmpDir.keep_everything_generated
273
+ self._parent_dir = None
274
+
275
+ if os.path.isabs(rel_path):
276
+ raise ValueError("Absolute paths are not supported in 'rel_path' parameter.")
277
+
278
+ if ONE_TICK_TMP_DIR():
279
+ # allows to set a test name suffix for when we use it in
280
+ # tests with goal to distinguish tests content (configs, databases, etc)
281
+ # from each other
282
+ dir_path = os.path.normpath(os.path.join(ONE_TICK_TMP_DIR(), base_dir))
283
+
284
+ if not os.path.exists(dir_path):
285
+ # If it does not exist, then lets create it. Note that it goes up
286
+ # recursively until finds an existing folder.
287
+ # We store it as a field of the instance to guarantee that it will
288
+ # be destroyed later than a temporary file.
289
+ self._parent_dir = GeneratedDir(dir_path, clean_up=clean_up)
290
+ elif not clean_up:
291
+ if dir_path in GeneratedDir.ALL: # NOSONAR
292
+ # let know the parent generated folder to not destroy itself
293
+ GeneratedDir.ALL[dir_path].need_to_cleanup = False
294
+
295
+ self._parent_dir = GeneratedDir.ALL[dir_path]
296
+
297
+ else:
298
+ dir_path = os.path.normpath(os.path.join(TMP_CONFIGS_DIR(), base_dir))
299
+
300
+ if rel_path:
301
+ path = os.path.normpath(os.path.join(dir_path, rel_path)) + suffix
302
+ # dir_path should be the parent directory of path
303
+ dir_path = os.path.dirname(path)
304
+ for tmp_dir in TmpDir.ALL[dir_path]:
305
+ # check if path already exists
306
+ if tmp_dir.path == path:
307
+ self.path = tmp_dir.path
308
+ if not clean_up:
309
+ tmp_dir.need_to_cleanup = clean_up
310
+ return
311
+
312
+ os.mkdir(path, mode=0o700)
313
+ self.path = path
314
+ else:
315
+ self.path = mkdtemp(dir=dir_path, suffix=suffix, prefix="")
316
+
317
+ # save to access them in the teardown
318
+ TmpDir.ALL[dir_path].append(self)
319
+
320
+ self._logger = get_logger(self.__class__.__name__)
321
+ CleanUpFinalizer.__init__(self, clean_up, self.path, self._logger)
322
+ self._logger.debug(f'created {self.path}, clean_up={self.need_to_cleanup}')
323
+
324
+ @CleanUpFinalizer.need_to_cleanup.setter # type: ignore
325
+ def need_to_cleanup(self, value):
326
+ super(TmpDir, type(self)).need_to_cleanup.fset(self, value)
327
+ children = self.ALL[self.path]
328
+ for child in children:
329
+ child.need_to_cleanup = value
330
+
331
+ @classmethod
332
+ def _cleanup(cls, clean_up_ref, path, logger):
333
+ if clean_up_ref[0]:
334
+ logger.debug(f'removing {path}')
335
+ shutil.rmtree(path, ignore_errors=True)
336
+
337
+ def __str__(self):
338
+ return self.path
339
+
340
+ def __repr__(self):
341
+ return f"TmpDir({self.path})"
342
+
343
+ def __fspath__(self):
344
+ return self.path
345
+
346
+
347
+ class GeneratedDir(os.PathLike, CleanUpFinalizer):
348
+
349
+ ALL: Dict[str, 'GeneratedDir'] = {} # store all created dir with goal to set cleanup=False if it requires
350
+ keep_everything_generated = False
351
+
352
+ def __init__(self, dir_path, clean_up=default):
353
+ """
354
+ Class to create generated temporary directory.
355
+ By default, this directory will be deleted automatically after all references to it are gone.
356
+ All files and directories under this one will be deleted too.
357
+
358
+ The main difference from TmpDir class is that also this directory's parent directory will be deleted!
359
+
360
+ The exact path to this directory will be: ``dir_path``.
361
+
362
+ Parameters
363
+ ----------
364
+ dir_path: str
365
+ path to the temporary directory.
366
+ clean_up: bool
367
+ Controls whether this temporary directory will be deleted automatically
368
+ after all references to it are gone.
369
+
370
+ By default,
371
+ :py:attr:`otp.config.clean_up_tmp_files<onetick.py.configuration.Config.clean_up_tmp_files>` is used.
372
+ """
373
+
374
+ parent = os.path.normpath(os.path.dirname(dir_path))
375
+
376
+ clean_up = default_clean_up(clean_up)
377
+ clean_up = clean_up and not GeneratedDir.keep_everything_generated
378
+
379
+ self._parent_dir = None
380
+ if not os.path.exists(parent):
381
+ # Do the same thing for the parent folder.
382
+ # Save it as a field to guarantee that child folder is destroyed
383
+ # earlier than parent
384
+ self._parent_dir = GeneratedDir(parent, clean_up=clean_up)
385
+ elif not clean_up:
386
+ if parent in GeneratedDir.ALL: # NOSONAR
387
+ GeneratedDir.ALL[parent].need_to_cleanup = False
388
+
389
+ if self._parent_dir is None and parent in GeneratedDir.ALL:
390
+ self._parent_dir = GeneratedDir.ALL[parent]
391
+
392
+ self.path = dir_path
393
+
394
+ if os.path.exists(dir_path):
395
+ # Do not create and even handle it, because it has been created externally.
396
+ # It mgiht happen in the concurrent runs.
397
+ return
398
+
399
+ # We need to set exist_ok=True, because it still can be created externally in the concurrent runs
400
+ os.makedirs(dir_path, exist_ok=True)
401
+
402
+ self._logger = get_logger(self.__class__.__name__)
403
+ CleanUpFinalizer.__init__(self, clean_up, self.path, self._logger)
404
+ self._logger.debug(f'created {self.path}, clean_up={self.need_to_cleanup}')
405
+
406
+ GeneratedDir.ALL[dir_path] = self
407
+
408
+ @CleanUpFinalizer.need_to_cleanup.setter # type: ignore
409
+ def need_to_cleanup(self, value):
410
+ super(GeneratedDir, type(self)).need_to_cleanup.fset(self, value)
411
+
412
+ if self._parent_dir:
413
+ self._parent_dir.need_to_cleanup = value
414
+
415
+ @classmethod
416
+ def _cleanup(cls, clean_up_ref, path, logger):
417
+ if clean_up_ref[0]:
418
+ logger.debug(f'removing {path}')
419
+ shutil.rmtree(path, ignore_errors=True)
420
+
421
+ def __str__(self):
422
+ return self.path
423
+
424
+ def __repr__(self):
425
+ return f"GeneratedDir({self.path})"
426
+
427
+ def __fspath__(self):
428
+ return self.path
429
+
430
+
431
+ # ----------------------------------------------------
432
+ # We set TMP_CONFIGS_DIR to a function, that returns
433
+ # only the first result.
434
+ # We need to support multiprocessing environment, where
435
+ # every process should use it's own base temporary folder
436
+ # but generate it only when something is required, because
437
+ # otherwise parent process could construct it with its pid
438
+ # and other processes would use the same location.
439
+
440
+
441
+ def _gen_root_path_():
442
+ generated = []
443
+
444
+ def _inner_():
445
+ if not generated:
446
+ generated.append(
447
+ GeneratedDir(
448
+ os.path.join(
449
+ _TMP_CONFIGS_DIR_BASE,
450
+ f'run_{datetime.datetime.now().strftime("%Y%m%d_%H%M%S")}_{os.getpid()}',
451
+ )
452
+ ).path
453
+ )
454
+ return generated[0]
455
+
456
+ return _inner_
457
+
458
+
459
+ TMP_CONFIGS_DIR = _gen_root_path_()
460
+ def ONE_TICK_TMP_DIR(): # noqa # NOSONAR
461
+ if os.getenv('OTP_WEBAPI_TEST_MODE'):
462
+ return WEBAPI_TEST_MODE_SHARED_CONFIG
463
+ if 'ONE_TICK_TMP_DIR' not in os.environ:
464
+ return None
465
+ return os.path.normpath(os.path.join(TMP_CONFIGS_DIR(), os.environ['ONE_TICK_TMP_DIR']))
466
+ # ----------------------------------------------------
467
+
468
+
469
+ class PermanentFile(File):
470
+ def copy(self):
471
+ return TmpFile(self._path)
@@ -0,0 +1,120 @@
1
+ def get_type_that_includes(types):
2
+ import onetick.py.types as ott
3
+
4
+ def merge_two(type1, type2):
5
+ type_changed = False
6
+
7
+ b_type1, b_type2 = ott.get_base_type(type1), ott.get_base_type(type2)
8
+ if b_type1 != b_type2:
9
+ if {b_type1, b_type2} == {int, float}:
10
+ dtype = float
11
+ elif {b_type1, b_type2} == {ott.decimal, float} or {b_type1, b_type2} == {ott.decimal, int}:
12
+ dtype = ott.decimal
13
+ elif {b_type1, b_type2} == {ott.nsectime, ott.msectime}:
14
+ dtype = ott.nsectime
15
+ else:
16
+ raise ValueError(f"Incompatible types: {type1}, {type2}")
17
+
18
+ type_changed = True
19
+ elif issubclass(b_type1, str):
20
+ t1_length = ott.string.DEFAULT_LENGTH if type1 is str or type1.length is None else type1.length
21
+ t2_length = ott.string.DEFAULT_LENGTH if type2 is str or type2.length is None else type2.length
22
+
23
+ if t1_length is Ellipsis or t2_length is Ellipsis:
24
+ dtype = ott.varstring
25
+ else:
26
+ dtype = type2 if t1_length < t2_length else type1
27
+
28
+ if t1_length != t2_length:
29
+ type_changed = True # TODO: test
30
+
31
+ else:
32
+ dtype = type1
33
+
34
+ return dtype, type_changed
35
+
36
+ dtype = types[0]
37
+ type_changed = False
38
+
39
+ for other_dtype in types[1:]:
40
+ dtype, new_type_change = merge_two(dtype, other_dtype)
41
+ type_changed |= new_type_change
42
+
43
+ return dtype, type_changed
44
+
45
+
46
+ class adaptive:
47
+ """
48
+ This class is mostly used as the default value for the functions' parameters
49
+ when the value of ``None`` has some other meaning
50
+ or when the meaning of the parameter depends on the other parameter's values,
51
+ :ref:`otp.config <static/configuration/root:configuration>` options or the context.
52
+
53
+ Examples
54
+ --------
55
+
56
+ For example, setting :py:class:`~onetick.py.DataSource` ``symbols`` parameter
57
+ to ``otp.adaptive`` allows to set symbols when running the query later.
58
+
59
+ >>> data = otp.DataSource('SOME_DB', tick_type='TT', symbols=otp.adaptive)
60
+ >>> otp.run(data, symbols='S1')
61
+ Time X
62
+ 0 2003-12-01 00:00:00.000 1
63
+ 1 2003-12-01 00:00:00.001 2
64
+ 2 2003-12-01 00:00:00.002 3
65
+
66
+ This is the default value of ``symbols`` parameter, so omitting it also works:
67
+
68
+ >>> data = otp.DataSource('SOME_DB', tick_type='TT')
69
+ >>> otp.run(data, symbols='S1')
70
+ Time X
71
+ 0 2003-12-01 00:00:00.000 1
72
+ 1 2003-12-01 00:00:00.001 2
73
+ 2 2003-12-01 00:00:00.002 3
74
+ """
75
+
76
+
77
+ class adaptive_to_default(adaptive):
78
+ """
79
+ If something is not specified and can not be deduced, then use the
80
+ default one
81
+ """
82
+
83
+
84
+ class default:
85
+ """
86
+ Used when you need to specify a default without evaluating it (e.g. default timezone)
87
+ """
88
+
89
+
90
+ class range:
91
+ """
92
+ Class that expresses OneTick ranges.
93
+ For example, if you want to express a range in the .split() method,
94
+ then you can use this range.
95
+
96
+ It has start and stop fields that allow you to define a range.
97
+
98
+ See also
99
+ --------
100
+ :py:meth:`~onetick.py.Source.split`.
101
+
102
+ Examples
103
+ --------
104
+ >>> data = otp.Ticks(X=[0.33, -5.1, otp.nan, 9.4])
105
+ >>> r1, r2, r3 = data.split(data['X'], [otp.nan, otp.range(0, 100)], default=True)
106
+ >>> otp.run(r1)
107
+ Time X
108
+ 0 2003-12-01 00:00:00.002 NaN
109
+ >>> otp.run(r2)
110
+ Time X
111
+ 0 2003-12-01 00:00:00.000 0.33
112
+ 1 2003-12-01 00:00:00.003 9.40
113
+ >>> otp.run(r3)
114
+ Time X
115
+ 0 2003-12-01 00:00:00.001 -5.1
116
+ """
117
+
118
+ def __init__(self, start, stop):
119
+ self.start = start
120
+ self.stop = stop
onetick/py/utils/tz.py ADDED
@@ -0,0 +1,84 @@
1
+ import datetime
2
+ import sys
3
+ import warnings
4
+
5
+ from typing import Optional
6
+ from contextlib import suppress
7
+
8
+ import dateutil.tz
9
+ import tzlocal
10
+
11
+ import onetick.py as otp
12
+ from onetick.py.backports import zoneinfo
13
+
14
+
15
+ def get_tzfile_by_name(timezone):
16
+ if isinstance(timezone, str):
17
+ try:
18
+ timezone = zoneinfo.ZoneInfo(timezone)
19
+ except zoneinfo.ZoneInfoNotFoundError:
20
+ timezone = dateutil.tz.gettz(timezone)
21
+ return timezone
22
+
23
+
24
+ def get_local_timezone_name():
25
+ tz = tzlocal.get_localzone()
26
+ try:
27
+ return tz.zone # type: ignore
28
+ except AttributeError:
29
+ return tz.key # type: ignore
30
+
31
+
32
+ def get_timezone_from_datetime(dt) -> Optional[str]:
33
+ tzinfo = getattr(dt, 'tzinfo', None)
34
+ if tzinfo is None:
35
+ return None
36
+ if tzinfo is datetime.timezone.utc:
37
+ return 'UTC'
38
+ with suppress(ModuleNotFoundError):
39
+ import pytz
40
+ if isinstance(tzinfo, pytz.BaseTzInfo):
41
+ return tzinfo.zone
42
+ if isinstance(tzinfo, zoneinfo.ZoneInfo):
43
+ return tzinfo.key
44
+ if isinstance(tzinfo, dateutil.tz.tzlocal):
45
+ return get_local_timezone_name()
46
+ if isinstance(tzinfo, dateutil.tz.tzstr) and hasattr(tzinfo, '_s'):
47
+ return tzinfo._s
48
+ if isinstance(tzinfo, dateutil.tz.tzfile):
49
+ if sys.platform == 'win32':
50
+ warnings.warn(
51
+ "It's not recommended to use dateutil.tz timezones on Windows platform. "
52
+ "Function 'get_timezone_from_datetime' can't guarantee correct results in this case. "
53
+ "Please, use zoneinfo timezones instead."
54
+ )
55
+ if hasattr(tzinfo, '_filename'):
56
+ if tzinfo._filename == '/etc/localtime':
57
+ return get_local_timezone_name()
58
+ for timezone in zoneinfo.available_timezones():
59
+ if tzinfo._filename.endswith(timezone):
60
+ return timezone
61
+ if sys.platform == 'win32':
62
+ if isinstance(tzinfo, dateutil.tz.win.tzwin) and hasattr(tzinfo, '_name'):
63
+ return tzinfo._name
64
+ raise ValueError(f"Can't get timezone name from datetime '{dt}' with tzinfo {tzinfo}")
65
+
66
+
67
+ def convert_timezone(dt, src_timezone, dest_timezone) -> datetime.datetime:
68
+ """
69
+ Converting timezone-naive ``dt`` object localized in ``src_timezone`` timezone
70
+ to the specified ``dest_timezone`` and returning also timezone-naive object.
71
+ """
72
+ if src_timezone is None:
73
+ src_timezone = get_local_timezone_name()
74
+ # using pandas, because stdlib datetime has some bug around epoch on Windows
75
+ dt = otp.datetime(dt).ts
76
+ # change timezone-naive to timezone-aware
77
+ dt = dt.tz_localize(src_timezone)
78
+ # convert timezone
79
+ dt = dt.tz_convert(dest_timezone)
80
+ # change timezone-aware to timezone-naive
81
+ dt = dt.tz_localize(None)
82
+ # convert to datetime
83
+ dt = dt.to_pydatetime()
84
+ return dt