onetick-py 1.162.2__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 +266 -0
- locator_parser/common.py +365 -0
- locator_parser/io.py +41 -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 +280 -0
- onetick/lib/__init__.py +4 -0
- onetick/lib/instance.py +138 -0
- onetick/py/__init__.py +290 -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 +645 -0
- onetick/py/aggregations/_docs.py +912 -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 +427 -0
- onetick/py/aggregations/other.py +1014 -0
- onetick/py/backports.py +26 -0
- onetick/py/cache.py +373 -0
- onetick/py/callback/__init__.py +5 -0
- onetick/py/callback/callback.py +275 -0
- onetick/py/callback/callbacks.py +131 -0
- onetick/py/compatibility.py +752 -0
- onetick/py/configuration.py +736 -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 +2307 -0
- onetick/py/core/_internal/_state_vars.py +87 -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 +810 -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 +270 -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 +1001 -0
- onetick/py/core/_source/source_methods/joins.py +1393 -0
- onetick/py/core/_source/source_methods/merges.py +566 -0
- onetick/py/core/_source/source_methods/misc.py +1325 -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 +702 -0
- onetick/py/core/_source/symbol.py +202 -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 +215 -0
- onetick/py/core/column_operations/_methods/methods.py +294 -0
- onetick/py/core/column_operations/_methods/op_types.py +150 -0
- onetick/py/core/column_operations/accessors/__init__.py +0 -0
- onetick/py/core/column_operations/accessors/_accessor.py +30 -0
- onetick/py/core/column_operations/accessors/decimal_accessor.py +92 -0
- onetick/py/core/column_operations/accessors/dt_accessor.py +464 -0
- onetick/py/core/column_operations/accessors/float_accessor.py +160 -0
- onetick/py/core/column_operations/accessors/str_accessor.py +1374 -0
- onetick/py/core/column_operations/base.py +1061 -0
- onetick/py/core/cut_builder.py +149 -0
- onetick/py/core/db_constants.py +20 -0
- onetick/py/core/eval_query.py +244 -0
- onetick/py/core/lambda_object.py +442 -0
- onetick/py/core/multi_output_source.py +193 -0
- onetick/py/core/per_tick_script.py +2253 -0
- onetick/py/core/query_inspector.py +465 -0
- onetick/py/core/source.py +1663 -0
- onetick/py/db/__init__.py +2 -0
- onetick/py/db/_inspection.py +1042 -0
- onetick/py/db/db.py +1423 -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 +2354 -0
- onetick/py/license.py +188 -0
- onetick/py/log.py +88 -0
- onetick/py/math.py +947 -0
- onetick/py/misc.py +437 -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 +211 -0
- onetick/py/pyomd_mock.py +47 -0
- onetick/py/run.py +841 -0
- onetick/py/servers.py +173 -0
- onetick/py/session.py +1342 -0
- onetick/py/sources/__init__.py +19 -0
- onetick/py/sources/cache.py +167 -0
- onetick/py/sources/common.py +126 -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 +1049 -0
- onetick/py/sources/empty.py +94 -0
- onetick/py/sources/odbc.py +337 -0
- onetick/py/sources/order_book.py +238 -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 +357 -0
- onetick/py/sources/ticks.py +825 -0
- onetick/py/sql.py +70 -0
- onetick/py/state.py +256 -0
- onetick/py/types.py +2056 -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 +499 -0
- onetick/py/utils/query.py +49 -0
- onetick/py/utils/render.py +1139 -0
- onetick/py/utils/script.py +244 -0
- onetick/py/utils/temp.py +471 -0
- onetick/py/utils/types.py +118 -0
- onetick/py/utils/tz.py +82 -0
- onetick_py-1.162.2.dist-info/METADATA +148 -0
- onetick_py-1.162.2.dist-info/RECORD +152 -0
- onetick_py-1.162.2.dist-info/WHEEL +5 -0
- onetick_py-1.162.2.dist-info/entry_points.txt +2 -0
- onetick_py-1.162.2.dist-info/licenses/LICENSE +21 -0
- onetick_py-1.162.2.dist-info/top_level.txt +2 -0
locator_parser/io.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import weakref
|
|
2
|
+
from locator_parser.common import Reader, Writer
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class FileWriter(Writer):
|
|
6
|
+
|
|
7
|
+
def __init__(self, file_path):
|
|
8
|
+
self.__path = file_path
|
|
9
|
+
|
|
10
|
+
super(FileWriter, self).__init__()
|
|
11
|
+
|
|
12
|
+
def flush(self):
|
|
13
|
+
with open(self.__path, "w") as fout:
|
|
14
|
+
for line in self.lines:
|
|
15
|
+
if line[-1:] != "\n":
|
|
16
|
+
line += "\n"
|
|
17
|
+
|
|
18
|
+
fout.write(line)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PrintWriter(Writer):
|
|
22
|
+
|
|
23
|
+
def flush(self):
|
|
24
|
+
print("\n".join(self.lines))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class FileReader(Reader):
|
|
28
|
+
|
|
29
|
+
def __init__(self, file_path):
|
|
30
|
+
self.iterable_object = open(file_path.replace('"', ""), "r")
|
|
31
|
+
self.__finalizer = weakref.finalize(self, lambda x: x.close(), self.iterable_object)
|
|
32
|
+
|
|
33
|
+
super(FileReader, self).__init__()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class LinesReader(Reader):
|
|
37
|
+
|
|
38
|
+
def __init__(self, lines):
|
|
39
|
+
self.iterable_object = lines.split("\n")
|
|
40
|
+
|
|
41
|
+
super(LinesReader, self).__init__()
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
from locator_parser.common import Entity
|
|
5
|
+
from locator_parser.io import FileReader, PrintWriter
|
|
6
|
+
from locator_parser.actions import DoNothing
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Include(Entity):
|
|
10
|
+
TAG = "INCLUDE"
|
|
11
|
+
SINGLE = True
|
|
12
|
+
RECURSIVELY = False
|
|
13
|
+
|
|
14
|
+
def parse(self, reader, writer, action):
|
|
15
|
+
path = self.path
|
|
16
|
+
matched = re.search(r"\$\{(.*)\}", path)
|
|
17
|
+
|
|
18
|
+
# env variable found in the path
|
|
19
|
+
if matched:
|
|
20
|
+
if matched.group(0)[2:-1] not in os.environ:
|
|
21
|
+
raise Exception("Includes use not defined environment variable: % s" % matched.group(0))
|
|
22
|
+
|
|
23
|
+
path = path.replace(matched.group(0), os.environ[matched.group(0)[2:-1]])
|
|
24
|
+
|
|
25
|
+
# read locator recursivly
|
|
26
|
+
if Include.RECURSIVELY:
|
|
27
|
+
parse_locator(FileReader(path), PrintWriter(), action, recursively=True)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Includes(Entity):
|
|
31
|
+
TAG = "INCLUDES"
|
|
32
|
+
CHILDREN = [Include]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Location(Entity):
|
|
36
|
+
TAG = "LOCATION"
|
|
37
|
+
SINGLE = True
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Locations(Entity):
|
|
41
|
+
TAG = "LOCATIONS"
|
|
42
|
+
CHILDREN = [Location]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class RawDB(Entity):
|
|
46
|
+
TAG = "RAW_DB"
|
|
47
|
+
CHILDREN = [Locations]
|
|
48
|
+
HAS_PROPERTIES = True
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class RawData(Entity):
|
|
52
|
+
TAG = "RAW_DATA"
|
|
53
|
+
CHILDREN = [RawDB]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class FeedOptions(Entity):
|
|
57
|
+
TAG = "OPTIONS"
|
|
58
|
+
SINGLE = True
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class Feed(Entity):
|
|
62
|
+
TAG = "FEED"
|
|
63
|
+
HAS_PROPERTIES = True
|
|
64
|
+
CHILDREN = [FeedOptions]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class DB(Entity):
|
|
68
|
+
TAG = "DB"
|
|
69
|
+
CHILDREN = [Locations, RawData, Feed]
|
|
70
|
+
HAS_PROPERTIES = True
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class DBs(Entity):
|
|
74
|
+
TAG = "DATABASES"
|
|
75
|
+
CHILDREN = [DB]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class ServerLocation(Entity):
|
|
79
|
+
TAG = "LOCATION"
|
|
80
|
+
SINGLE = True
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class TickServers(Entity):
|
|
84
|
+
TAG = "TICK_SERVERS"
|
|
85
|
+
CHILDREN = [ServerLocation]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class CEPServerLocation(Entity):
|
|
89
|
+
TAG = "LOCATION"
|
|
90
|
+
SINGLE = True
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class CEPTickServers(Entity):
|
|
94
|
+
TAG = "CEP_TICK_SERVERS"
|
|
95
|
+
CHILDREN = [CEPServerLocation]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class Range(Entity):
|
|
99
|
+
TAG = "RANGE"
|
|
100
|
+
SINGLE = True
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class TimeInterval(Entity):
|
|
104
|
+
TAG = "TIME_INTERVAL"
|
|
105
|
+
HAS_PROPERTIES = True
|
|
106
|
+
CHILDREN = [Range]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class VirtualDB(Entity):
|
|
110
|
+
TAG = "DB"
|
|
111
|
+
HAS_PROPERTIES = True
|
|
112
|
+
CHILDREN = [TimeInterval]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class VirtualDBs(Entity):
|
|
116
|
+
TAG = "VIRTUAL_DATABASES"
|
|
117
|
+
CHILDREN = [VirtualDB]
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def parse_locator(reader, writer, action=DoNothing(), recursively=False):
|
|
121
|
+
Include.RECURSIVELY = recursively
|
|
122
|
+
writer.refresh()
|
|
123
|
+
reader.set_writer(writer)
|
|
124
|
+
|
|
125
|
+
databases_p = DBs()
|
|
126
|
+
v_databases_p = VirtualDBs()
|
|
127
|
+
tick_servers_p = TickServers()
|
|
128
|
+
cep_tick_servers_p = CEPTickServers()
|
|
129
|
+
includes_p = Includes()
|
|
130
|
+
|
|
131
|
+
for _ in reader:
|
|
132
|
+
databases_p(reader, writer, action)
|
|
133
|
+
v_databases_p(reader, writer, action)
|
|
134
|
+
tick_servers_p(reader, writer, action)
|
|
135
|
+
cep_tick_servers_p(reader, writer, action)
|
|
136
|
+
includes_p(reader, writer, action)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# ----------------------------------------- #
|
|
140
|
+
# set parent
|
|
141
|
+
for _, cls in list(globals().items()):
|
|
142
|
+
try:
|
|
143
|
+
if issubclass(cls, Entity) and cls != Entity:
|
|
144
|
+
for child in cls.CHILDREN:
|
|
145
|
+
child.PARENT = Entity
|
|
146
|
+
|
|
147
|
+
if not cls.HAS_PROPERTIES and not cls.SINGLE:
|
|
148
|
+
child.PARENT = cls
|
|
149
|
+
except Exception:
|
|
150
|
+
continue
|
onetick/__init__.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def __search_main_one_tick_dir():
|
|
5
|
+
try:
|
|
6
|
+
import onetick.query_webapi # noqa
|
|
7
|
+
# if onetick.query_webapi is imported successfully, do not raise any warnings
|
|
8
|
+
return __search_main_one_tick_dir_catch(show_warnings=False)
|
|
9
|
+
except ImportError:
|
|
10
|
+
return __search_main_one_tick_dir_catch(show_warnings=True)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def __search_main_one_tick_dir_catch(show_warnings=True): # noqa: C901
|
|
14
|
+
import os
|
|
15
|
+
import warnings
|
|
16
|
+
import sysconfig
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
if os.environ.get('OTP_SKIP_OTQ_VALIDATION', False):
|
|
20
|
+
try:
|
|
21
|
+
import onetick_stubs # noqa
|
|
22
|
+
except ImportError:
|
|
23
|
+
if show_warnings:
|
|
24
|
+
warnings.warn(
|
|
25
|
+
"OTP_SKIP_OTQ_VALIDATION environment variable is set,"
|
|
26
|
+
" but onetick_stubs module is not available."
|
|
27
|
+
" Please, install onetick-query-stubs to avoid import errors"
|
|
28
|
+
" or unset OTP_SKIP_OTQ_VALIDATION to use onetick.query module."
|
|
29
|
+
)
|
|
30
|
+
return tuple()
|
|
31
|
+
|
|
32
|
+
config_vars = sysconfig.get_config_vars()
|
|
33
|
+
|
|
34
|
+
ot_bin_path = os.path.join("bin")
|
|
35
|
+
ot_python_path = os.path.join(ot_bin_path, "python")
|
|
36
|
+
ot_numpy_path = os.path.join(
|
|
37
|
+
ot_bin_path,
|
|
38
|
+
"numpy",
|
|
39
|
+
"python"
|
|
40
|
+
# short python version 27, 36, 37, etc
|
|
41
|
+
+ config_vars["py_version_nodot"]
|
|
42
|
+
# suffix at the end, either empty string for python with the standard memory allocator,
|
|
43
|
+
# or 'm' for python with the py-malloc allocator
|
|
44
|
+
+ config_vars["abiflags"],
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
if os.name == "nt":
|
|
48
|
+
default_main_one_tick_dir = "C:/OMD/one_market_data/one_tick"
|
|
49
|
+
elif os.name == "posix":
|
|
50
|
+
default_main_one_tick_dir = "/opt/one_market_data/one_tick"
|
|
51
|
+
else:
|
|
52
|
+
default_main_one_tick_dir = None
|
|
53
|
+
|
|
54
|
+
main_one_tick_dir = os.environ.get("MAIN_ONE_TICK_DIR")
|
|
55
|
+
|
|
56
|
+
if not main_one_tick_dir:
|
|
57
|
+
message = (
|
|
58
|
+
"MAIN_ONE_TICK_DIR environment variable is not set."
|
|
59
|
+
" It is a recommended way to let onetick-py know where OneTick python libraries are located."
|
|
60
|
+
)
|
|
61
|
+
if not default_main_one_tick_dir:
|
|
62
|
+
if show_warnings:
|
|
63
|
+
warnings.warn(message)
|
|
64
|
+
return
|
|
65
|
+
message += f" We will try to use default value for your system: {default_main_one_tick_dir}."
|
|
66
|
+
if show_warnings:
|
|
67
|
+
warnings.warn(message)
|
|
68
|
+
main_one_tick_dir = default_main_one_tick_dir
|
|
69
|
+
|
|
70
|
+
main_one_tick_dir = Path(main_one_tick_dir)
|
|
71
|
+
|
|
72
|
+
if not main_one_tick_dir.is_dir():
|
|
73
|
+
if show_warnings:
|
|
74
|
+
warnings.warn(
|
|
75
|
+
f"MAIN_ONE_TICK_DIR is set to '{main_one_tick_dir}',"
|
|
76
|
+
" but this path is not a directory or doesn't exist."
|
|
77
|
+
)
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
ot_bin_path = main_one_tick_dir / ot_bin_path
|
|
81
|
+
ot_python_path = main_one_tick_dir / ot_python_path
|
|
82
|
+
|
|
83
|
+
for directory in (ot_python_path, ot_bin_path):
|
|
84
|
+
if not directory.is_dir():
|
|
85
|
+
if show_warnings:
|
|
86
|
+
warnings.warn(
|
|
87
|
+
f"MAIN_ONE_TICK_DIR is set to '{main_one_tick_dir}',"
|
|
88
|
+
f" and it must contain '{directory}' directory,"
|
|
89
|
+
" but this path is not a directory or doesn't exist."
|
|
90
|
+
)
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
ot_numpy_path = main_one_tick_dir / ot_numpy_path
|
|
94
|
+
|
|
95
|
+
return (ot_bin_path, ot_python_path, ot_numpy_path)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
for directory in __search_main_one_tick_dir() or []:
|
|
99
|
+
sys.path.append(str(directory))
|
|
100
|
+
|
|
101
|
+
__path__ = __import__("pkgutil").extend_path(__path__, __name__)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
from sphinx.ext.napoleon.docstring import NumpyDocstring
|
|
3
|
+
from sphinx.locale import get_translation
|
|
4
|
+
from onetick.doc_utilities.snippets import parse_string
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class OTNumpyDocstring(NumpyDocstring):
|
|
8
|
+
|
|
9
|
+
def _parse_examples_section(self, section: str) -> List[str]:
|
|
10
|
+
|
|
11
|
+
"""Applies OT directives for all lines"""
|
|
12
|
+
|
|
13
|
+
# combining super()._parse_examples_section and super()._parse_generic_section
|
|
14
|
+
# due to super()._parse_examples_section just prepare parameters and
|
|
15
|
+
# super()._parse_generic_section get lines and modify them.
|
|
16
|
+
# but we need to add logic on lines but before super()._parse_generic_section modification
|
|
17
|
+
labels = {
|
|
18
|
+
'example': get_translation('sphinx')('Example'),
|
|
19
|
+
'examples': get_translation('sphinx')('Examples'),
|
|
20
|
+
}
|
|
21
|
+
use_admonition = self._config.napoleon_use_admonition_for_examples
|
|
22
|
+
section = labels.get(section.lower(), section)
|
|
23
|
+
|
|
24
|
+
lines = self._strip_empty(self._consume_to_next_section())
|
|
25
|
+
lines = self._dedent(lines)
|
|
26
|
+
|
|
27
|
+
# OneTick custom logic start
|
|
28
|
+
doc = parse_string('\n'.join(lines), caller='doc')
|
|
29
|
+
lines = doc.split('\n')
|
|
30
|
+
# OneTick custom logic end
|
|
31
|
+
|
|
32
|
+
if use_admonition:
|
|
33
|
+
header = '.. admonition:: %s' % section # noqa
|
|
34
|
+
lines = self._indent(lines, 3)
|
|
35
|
+
else:
|
|
36
|
+
header = '.. rubric:: %s' % section # noqa
|
|
37
|
+
if lines:
|
|
38
|
+
return [header, ''] + lines + ['']
|
|
39
|
+
else:
|
|
40
|
+
return [header, '']
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import doctest
|
|
2
|
+
import re
|
|
3
|
+
import inspect
|
|
4
|
+
|
|
5
|
+
from typing import Union, Optional
|
|
6
|
+
from onetick.py.backports import Literal
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def snippet_name(example: Union[doctest.Example, str], value):
|
|
10
|
+
if isinstance(example, doctest.Example):
|
|
11
|
+
example.name = value # type: ignore
|
|
12
|
+
return example
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def skip_snippet(example: Union[doctest.Example, str], value):
|
|
16
|
+
if isinstance(example, doctest.Example):
|
|
17
|
+
example.skip_snippet = True # type: ignore
|
|
18
|
+
return example
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def skip_example(example, value, caller=None):
|
|
22
|
+
if caller is not None and value != '' and value != caller: # do nothing if caller passed and value not caller
|
|
23
|
+
return example
|
|
24
|
+
if isinstance(example, doctest.Example):
|
|
25
|
+
example.skip = True
|
|
26
|
+
return example
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
OT_DIRECTIVES = {'skip-example': skip_example,
|
|
30
|
+
'snippet-name': snippet_name,
|
|
31
|
+
'skip-snippet': skip_snippet}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def register_ot_directive(directive, fun):
|
|
35
|
+
"""
|
|
36
|
+
Register OT directive.
|
|
37
|
+
|
|
38
|
+
directive sintax: ... OTdirective: <directive>: <directive_parameter>;
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
directive: str
|
|
43
|
+
directive name
|
|
44
|
+
fun: callable
|
|
45
|
+
fun will be executed if `directive` will be found
|
|
46
|
+
fun should have two positional parameters:
|
|
47
|
+
first: string or doctest.Example
|
|
48
|
+
second: direction parameter
|
|
49
|
+
fun should return str, doctest.Example or None
|
|
50
|
+
"""
|
|
51
|
+
OT_DIRECTIVES[directive] = fun
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ApplyDirective:
|
|
55
|
+
|
|
56
|
+
_OT_DIRECTIVE_RE = r'(.*?)\s*# OTdirective: (.*)'
|
|
57
|
+
_OT_DIRECTIVE_PARSER = re.compile(r'\s*?(?P<func>[A-Za-z_\-0-9]*)\s*?:(?P<param>[^:;]*);')
|
|
58
|
+
|
|
59
|
+
def __init__(self, caller: Optional[str] = None):
|
|
60
|
+
self.caller = caller
|
|
61
|
+
|
|
62
|
+
def _get_directives(self, item):
|
|
63
|
+
is_example = isinstance(item, doctest.Example)
|
|
64
|
+
string = item
|
|
65
|
+
if is_example:
|
|
66
|
+
string = item.source
|
|
67
|
+
res = []
|
|
68
|
+
funcs = []
|
|
69
|
+
for doc in string.split('\n'):
|
|
70
|
+
s, f = self._get_directives_impl(doc)
|
|
71
|
+
res.append(s)
|
|
72
|
+
funcs.extend(f)
|
|
73
|
+
|
|
74
|
+
string = '\n'.join(res)
|
|
75
|
+
if is_example:
|
|
76
|
+
item.source = string
|
|
77
|
+
return item, funcs
|
|
78
|
+
return string, funcs
|
|
79
|
+
|
|
80
|
+
def _get_directives_impl(self, string: str):
|
|
81
|
+
directives = re.match(self._OT_DIRECTIVE_RE, string, re.MULTILINE | re.DOTALL)
|
|
82
|
+
if directives is None:
|
|
83
|
+
return string, {}
|
|
84
|
+
doc, directive_str = directives.groups()
|
|
85
|
+
funcs = []
|
|
86
|
+
for fun, v in re.findall(self._OT_DIRECTIVE_PARSER, directive_str):
|
|
87
|
+
if fun.strip() not in OT_DIRECTIVES:
|
|
88
|
+
raise KeyError(f"Unknown directive: '{fun.strip()}'. Original: '{directive_str}'")
|
|
89
|
+
funcs.append((OT_DIRECTIVES[fun.strip()], v.strip()))
|
|
90
|
+
return doc, funcs
|
|
91
|
+
|
|
92
|
+
def __call__(self, item):
|
|
93
|
+
doc, funcs = self._get_directives(item)
|
|
94
|
+
for fun, param in funcs:
|
|
95
|
+
params = {}
|
|
96
|
+
if self.caller and 'caller' in inspect.signature(fun).parameters.keys():
|
|
97
|
+
params['caller'] = self.caller
|
|
98
|
+
doc = fun(doc, param, **params)
|
|
99
|
+
return doc
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def apply_directive(example: Union[doctest.Example, str], caller: Optional[str] = None):
|
|
103
|
+
|
|
104
|
+
"""
|
|
105
|
+
Applies OT directive to `example`
|
|
106
|
+
Drops directive comment
|
|
107
|
+
|
|
108
|
+
See Also
|
|
109
|
+
--------
|
|
110
|
+
register_ot_directive
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
return ApplyDirective(caller)(example)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class OTDoctestParser(doctest.DocTestParser):
|
|
117
|
+
|
|
118
|
+
"""
|
|
119
|
+
This class applicable only to parse doctest example!
|
|
120
|
+
Do not run tests with this parser!
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
def __init__(self, *args, caller: Optional[str] = None, **kwargs):
|
|
124
|
+
super().__init__(*args, **kwargs)
|
|
125
|
+
self.caller = caller
|
|
126
|
+
|
|
127
|
+
def _IS_BLANK_OR_COMMENT(self, value):
|
|
128
|
+
|
|
129
|
+
if value.strip().startswith('# OTdirective'):
|
|
130
|
+
return False
|
|
131
|
+
else:
|
|
132
|
+
return super()._IS_BLANK_OR_COMMENT(value)
|
|
133
|
+
|
|
134
|
+
def parse(self, *args, **kwargs):
|
|
135
|
+
"""
|
|
136
|
+
Applies OT directive for all examples
|
|
137
|
+
"""
|
|
138
|
+
examples = super().parse(*args, **kwargs)
|
|
139
|
+
res = [apply_directive(x, self.caller) for x in examples]
|
|
140
|
+
return res
|