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
File without changes
locator_parser/acl.py ADDED
@@ -0,0 +1,73 @@
1
+ from locator_parser.common import Entity
2
+ from locator_parser.actions import DoNothing
3
+
4
+
5
+ class Allow(Entity):
6
+ TAG = "ALLOW"
7
+ SINGLE = True
8
+
9
+
10
+ class DB(Entity):
11
+ TAG = "DB"
12
+ HAS_PROPERTIES = True
13
+ CHILDREN = [Allow]
14
+
15
+
16
+ class DBs(Entity):
17
+ TAG = "DATABASES"
18
+ CHILDREN = [DB]
19
+
20
+
21
+ class User(Entity):
22
+ TAG = "USER"
23
+ SINGLE = True
24
+
25
+
26
+ class Role(Entity):
27
+ TAG = "ROLE"
28
+ HAS_PROPERTIES = True
29
+ CHILDREN = [User]
30
+
31
+
32
+ class Roles(Entity):
33
+ TAG = "ROLES"
34
+ CHILDREN = [Role]
35
+
36
+
37
+ class EP(Entity):
38
+ TAG = "EP"
39
+ HAS_PROPERTIES = True
40
+ CHILDREN = [Allow]
41
+
42
+
43
+ class EPs(Entity):
44
+ TAG = "EVENT_PROCESSORS"
45
+ CHILDREN = [EP]
46
+
47
+
48
+ def parse_acl(reader, writer, action=DoNothing()):
49
+ writer.refresh()
50
+ reader.set_writer(writer)
51
+
52
+ roles_p = Roles()
53
+ databases_p = DBs()
54
+ eps_p = EPs()
55
+
56
+ for _ in reader:
57
+ roles_p(reader, writer, action)
58
+ databases_p(reader, writer, action)
59
+ eps_p(reader, writer, action)
60
+
61
+
62
+ # ----------------------------------------- #
63
+ # set parent
64
+ for _, cls in list(globals().items()):
65
+ try:
66
+ if issubclass(cls, Entity) and cls != Entity:
67
+ for child in cls.CHILDREN:
68
+ child.PARENT = Entity
69
+
70
+ if not cls.HAS_PROPERTIES and not cls.SINGLE:
71
+ child.PARENT = cls
72
+ except Exception:
73
+ continue
@@ -0,0 +1,262 @@
1
+ from locator_parser.common import Action, Entity
2
+ from collections import OrderedDict
3
+
4
+
5
+ class Add(Action):
6
+ """
7
+ Add entity.
8
+
9
+ Example:
10
+ raw_db = Add(RawDB(id='PRIMARY'))
11
+ raw_db.add_where(DB, id='DB_2')
12
+ """
13
+
14
+ def __init__(self, obj):
15
+ self.what_to_add = obj.to_lines()
16
+
17
+ super().__init__()
18
+
19
+ if obj.__class__.PARENT is not Entity:
20
+ self._add_conditions[(obj.__class__.PARENT.TAG.lower(), None, None)] = False
21
+
22
+ def do(self, obj, all_lines, properties, writer):
23
+ writer.add(self.what_to_add)
24
+
25
+
26
+ class Modify(Action):
27
+ """
28
+ Modify entity properties.
29
+ If property doesn't exist, then it will be added.
30
+ If property is set to None, then it will be removed.
31
+
32
+ Example:
33
+ action = Modify(symbology='TDEQ', db_archive_tmp_dir=None)
34
+ action.add_where(DB, id="DB_1")
35
+ """
36
+
37
+ def __init__(self, **kwargs):
38
+ if len(kwargs) > 1:
39
+ raise ValueError("It is not supported to set multiple keys yet!")
40
+
41
+ field, value = "", ""
42
+ delete = False
43
+
44
+ for k, v in kwargs.items():
45
+ if v is None:
46
+ delete = True
47
+
48
+ field, value = k, str(v)
49
+
50
+ self.init(field, value, delete)
51
+
52
+ super().__init__()
53
+
54
+ def init(self, field, value, delete):
55
+ self.field = field.lower()
56
+ self.value = '"' + value + '"'
57
+ self.delete = delete
58
+
59
+ def do(self, obj, all_lines, properties_lines, writer):
60
+ if not obj.__class__.HAS_PROPERTIES:
61
+ raise AttributeError("Entity with tag '" + obj.__class__.TAG + "' does not expect to have properties!")
62
+
63
+ new_properties = list(properties_lines)
64
+
65
+ if not hasattr(obj, self.field):
66
+ for line in new_properties:
67
+ end_pos = line.find(">")
68
+
69
+ if end_pos != -1:
70
+ if line.find("/>") != -1 and line.find("/>") < end_pos:
71
+ end_pos = line.find("/>")
72
+
73
+ line = line[:end_pos] + " %s=%s" % (self.field, self.value) + line[end_pos:]
74
+ new_properties[-1] = line
75
+ break
76
+ else:
77
+ for inx, line in enumerate(new_properties):
78
+ pos = line.lower().find(self.field + "=")
79
+
80
+ if pos != -1:
81
+ # modify property
82
+ t_line = line[pos + len(self.field):].split()[0].split("/>")[0].split(">")[0].split("=")[1]
83
+
84
+ if self.delete:
85
+ line = line.replace(line[pos: pos + len(self.field) + 1] + t_line, "")
86
+ else:
87
+ line = line.replace(
88
+ line[pos: pos + len(self.field) + 1] + t_line,
89
+ line[pos: pos + len(self.field) + 1] + self.value,
90
+ )
91
+ new_properties[inx] = line
92
+ break
93
+
94
+ writer.modify(len(all_lines), properties_lines, new_properties)
95
+
96
+
97
+ class Delete(Action):
98
+ """
99
+ Delete entity
100
+
101
+ Example:
102
+ action = Delete()
103
+ action.add_where(DB, id="DB_1")
104
+ """
105
+
106
+ def do(self, obj, all_lines, properites_lines, writer):
107
+ writer.delete(all_lines)
108
+
109
+
110
+ class Get(Action):
111
+ """
112
+ Populate self.result object with properites of found object
113
+
114
+ Example:
115
+ action = Get()
116
+ action.add_where(DB, id='LB')
117
+ """
118
+
119
+ def do(self, obj, all_lines, properties_lines, writer):
120
+ self.lines = all_lines
121
+
122
+ # self.result = obj.__class__()
123
+ properties_dict = {}
124
+
125
+ properties_lines = " ".join(properties_lines)
126
+
127
+ # variables to concat space separated
128
+ # values
129
+ res_token = ""
130
+ double_quotes_count = 0
131
+ single_quotes_count = 0
132
+
133
+ for token in filter(lambda x: x not in ("", "<", "/>", ">"), properties_lines.split()[1:]):
134
+ # ---------------------
135
+ # Logic to combine values in quotes are separated
136
+ # by space
137
+ if '"' in token:
138
+ double_quotes_count += token.count('"')
139
+ if "'" in token:
140
+ single_quotes_count += token.count("'")
141
+
142
+ if double_quotes_count > 0 or single_quotes_count > 0:
143
+ if (double_quotes_count + single_quotes_count) % 2 == 0:
144
+ res_token += token
145
+ double_quotes_count = 0
146
+ single_quotes_count = 0
147
+ else:
148
+ res_token += token
149
+ continue
150
+ else:
151
+ res_token = token
152
+ # --------------------
153
+
154
+ t_list = res_token.split("=")
155
+
156
+ # refresh to do not concat
157
+ # already ready token
158
+ res_token = ""
159
+ double_quotes_count = 0
160
+ single_quotes_count = 0
161
+
162
+ key, value = t_list[0], t_list[1]
163
+
164
+ if value.endswith("/>"):
165
+ value = value[:-2]
166
+
167
+ if value.endswith(">") or value.endswith("/"):
168
+ value = value[:-1]
169
+
170
+ properties_dict[key.lower()] = value.replace('"', "")
171
+ # setattr(self.result, key.lower(), value.replace('"', ''))
172
+
173
+ self.result = obj.__class__(**properties_dict)
174
+
175
+ for key, value in properties_dict.items():
176
+ setattr(self.result, key, value)
177
+
178
+
179
+ class GetAll(Action):
180
+
181
+ def __init__(self):
182
+ super().__init__()
183
+ self.result = []
184
+
185
+ def do(self, obj, all_lines, properties_lines, writer):
186
+ get = Get()
187
+ get.do(obj, all_lines, properties_lines, writer)
188
+
189
+ self.result.append(get.result)
190
+
191
+ def apply(self, tag, entity):
192
+ if self.executed:
193
+ self.just_executed = False
194
+ self.executed = False
195
+
196
+ # Base is one that we are looking for; this entity has only
197
+ # tag specified.
198
+ # Here is logic to refresh the base, because we have to go
199
+ # through the subtree.
200
+ is_base = False
201
+ for key, value in reversed(self.conditions.items()):
202
+ t, f, v = key
203
+
204
+ if f is None and v is None:
205
+ is_base = True
206
+
207
+ if is_base:
208
+ self.conditions[key] = False
209
+ # leave, because we support only the last open-ended base
210
+ break
211
+
212
+ # first time it updates and then does nothing
213
+ self.conditions.update(self._add_conditions)
214
+ self._add_conditions = OrderedDict()
215
+
216
+ tag = tag.lower()
217
+
218
+ for key, value in self.conditions.items():
219
+ t, f, v = key
220
+
221
+ if tag == t:
222
+ if f:
223
+ if hasattr(entity, f) and getattr(entity, f) == '"' + v + '"':
224
+ self.conditions[(t, f, v)] = True
225
+ else:
226
+ # if we move to the same similar section, but with another key,
227
+ # then set it to False, because in the previous section it might be
228
+ # set to True
229
+ self.conditions[(t, f, v)] = False
230
+ else:
231
+ self.conditions[(t, f, v)] = True
232
+ else:
233
+ if value:
234
+ continue
235
+
236
+ if not self.conditions[(t, f, v)]:
237
+ break
238
+
239
+ self.executed = self.is_result()
240
+ self.just_executed = self.executed
241
+
242
+
243
+ class Show(Action):
244
+ """
245
+ Print object and all children objects
246
+
247
+ Example:
248
+ action = Show()
249
+ action.add_where(DB, id='S_ORDERS_LB')
250
+ """
251
+
252
+ def do(self, obj, all_lines, properties_lines, writer):
253
+ print("\n".join(all_lines))
254
+
255
+
256
+ class DoNothing(Action):
257
+ """
258
+ Default action that does nothing
259
+ """
260
+
261
+ def do(self, *args, **kwargs):
262
+ pass
@@ -0,0 +1,368 @@
1
+ import re
2
+ from collections import OrderedDict
3
+ from abc import ABC, abstractmethod
4
+ from typing import List, Type
5
+
6
+
7
+ def get_properties(declaration):
8
+ """
9
+ Construct dictionary from properties in format
10
+ <prop_1=value_1 prop_2=value_2 ... >
11
+ or
12
+ <prop_1=value_1 prop_2=value_2 ... />
13
+ and return it back
14
+ """
15
+ tokens = declaration.split()
16
+
17
+ properties = {
18
+ token.split("=")[0].lower(): token.split("=")[1].replace("/>", "").replace(">", "")
19
+ for token in tokens[1:]
20
+ if "=" in token
21
+ }
22
+
23
+ return properties
24
+
25
+
26
+ class Generator:
27
+
28
+ def __iter__(self):
29
+ return self
30
+
31
+ def __next__(self):
32
+ self.current = next(self.generator)
33
+ return self.current
34
+
35
+
36
+ class Action(ABC):
37
+
38
+ def __init__(self):
39
+ self.conditions = OrderedDict()
40
+ # additional conditions that we can deduce
41
+ # it's applicable for the sections without properties
42
+ #
43
+ # For example, if you add DB, then you can forget that
44
+ # it's situated in the single <databases> section; that
45
+ # information we retrieve from the entities inheritance
46
+ # put here and use along with conditions dict
47
+ self._add_conditions = OrderedDict()
48
+
49
+ self.executed = False
50
+ self.just_executed = False
51
+
52
+ @abstractmethod
53
+ def do(self):
54
+ pass
55
+
56
+ def clone(self, *args, **kwargs):
57
+ obj = self.__class__(*args, **kwargs)
58
+ obj.conditions = dict(self.conditions)
59
+
60
+ for key, _ in obj.conditions.items():
61
+ obj.conditions[key] = False
62
+
63
+ return obj
64
+
65
+ def apply(self, tag, entity):
66
+ if self.executed:
67
+ self.just_executed = False
68
+ return
69
+
70
+ # first time it updates and then does nothing
71
+ self.conditions.update(self._add_conditions)
72
+ self._add_conditions = OrderedDict()
73
+
74
+ tag = tag.lower()
75
+
76
+ for key, value in self.conditions.items():
77
+ t, f, v = key
78
+
79
+ if tag == t:
80
+ if f:
81
+ if hasattr(entity, f) and getattr(entity, f) == '"' + v + '"':
82
+ self.conditions[(t, f, v)] = True
83
+ else:
84
+ # if we move to the same similar section, but with another key,
85
+ # then set it to False, because in the previous section it might be
86
+ # set to True
87
+ self.conditions[(t, f, v)] = False
88
+ else:
89
+ self.conditions[(t, f, v)] = True
90
+ else:
91
+ if value:
92
+ continue
93
+
94
+ if not self.conditions[(t, f, v)]:
95
+ break
96
+
97
+ self.executed = self.is_result()
98
+ self.just_executed = self.executed
99
+
100
+ def is_result(self):
101
+ for v in self.conditions.values():
102
+ if not v:
103
+ return False
104
+
105
+ return True
106
+
107
+ def add_where(self, tag, **kwargs):
108
+ if not isinstance(tag, str):
109
+ # for case when it's class name
110
+ tag = tag.TAG
111
+
112
+ if len(kwargs) > 1:
113
+ raise ValueError("It is not support to set multiple keys yet!")
114
+
115
+ if len(kwargs) == 0:
116
+ self.conditions[(tag.lower(), None, None)] = False
117
+ else:
118
+ self.conditions[(tag.lower(), list(kwargs.keys())[0].lower(), list(kwargs.values())[0])] = False
119
+
120
+
121
+ def apply_actions(func, reader, writer, actions=None, flush=False):
122
+ from locator_parser.io import LinesReader
123
+ from locator_parser.actions import DoNothing
124
+
125
+ if actions is None:
126
+ actions = []
127
+
128
+ if len(actions) == 0:
129
+ actions.append(DoNothing())
130
+
131
+ func(reader, writer, actions[0])
132
+
133
+ for action in actions[1:]:
134
+ lines = "\n".join(map(lambda x: x.replace("\n", ""), writer.lines))
135
+ new_reader = LinesReader(lines)
136
+
137
+ func(new_reader, writer, action)
138
+
139
+ if flush:
140
+ writer.flush()
141
+
142
+ for action in actions:
143
+ if not action.executed:
144
+ return False
145
+
146
+ return True
147
+
148
+
149
+ class Writer(ABC):
150
+
151
+ NOTHING_MSG = 0
152
+ NEW_MSG = 1
153
+ MODIFY_MSG = 2
154
+ DELETE_MSG = 3
155
+
156
+ def __init__(self):
157
+ self.refresh()
158
+
159
+ def refresh(self):
160
+ self.__gen = self.__generator__()
161
+ # init generator to send further messages
162
+ next(self.__gen)
163
+
164
+ self.lines = []
165
+
166
+ def __generator__(self):
167
+ while True:
168
+ msg_type = yield
169
+
170
+ if msg_type == Writer.NOTHING_MSG:
171
+ # since second value is a just next line
172
+ line = yield
173
+ self.lines.append(line)
174
+ elif msg_type == Writer.NEW_MSG:
175
+ lines = yield
176
+
177
+ assert isinstance(lines, list)
178
+
179
+ self.lines = self.lines[:-1] + lines + self.lines[-1:]
180
+ elif msg_type == Writer.MODIFY_MSG:
181
+ pos, orig_lines, new_lines = yield
182
+
183
+ for inx, pair in enumerate(zip(orig_lines, new_lines)):
184
+ orig_line, new_line = pair
185
+
186
+ self.lines[-pos + inx] = self.lines[-pos + inx].replace(orig_line, new_line)
187
+ elif msg_type == Writer.DELETE_MSG:
188
+ lines_to_delete = yield
189
+
190
+ for _ in range(len(lines_to_delete)):
191
+ self.lines.pop()
192
+
193
+ def __next__(self):
194
+ return self.__send(None)
195
+
196
+ def __send(self, arg):
197
+ """private method to not allow user to send something directly"""
198
+ self.__gen.send(arg)
199
+
200
+ def put_as_is(self, line):
201
+ self.__send(Writer.NOTHING_MSG)
202
+ self.__send(line)
203
+
204
+ def add(self, lines):
205
+ self.__send(Writer.NEW_MSG)
206
+ self.__send(lines)
207
+
208
+ def modify(self, pos, orig_lines, new_lines):
209
+ self.__send(Writer.MODIFY_MSG)
210
+ self.__send((pos, orig_lines, new_lines))
211
+
212
+ def delete(self, lines):
213
+ self.__send(Writer.DELETE_MSG)
214
+ self.__send(lines)
215
+
216
+ @abstractmethod
217
+ def flush(self):
218
+ pass
219
+
220
+
221
+ class Reader(Generator):
222
+
223
+ def __init__(self):
224
+ self.generator = self.__generator__()
225
+ self.writer = None
226
+ self.current = None
227
+
228
+ def set_writer(self, writer):
229
+ self.__writer = writer
230
+
231
+ def __generator__(self):
232
+ for line in self.iterable_object:
233
+ # put just coming line as is into the writer
234
+ self.__writer.put_as_is(line)
235
+
236
+ line = line.strip()
237
+
238
+ if line == "" or line[0:1] == "#":
239
+ continue
240
+
241
+ yield line
242
+
243
+
244
+ class Entity(Generator, ABC):
245
+
246
+ # Tag to identify entity
247
+ TAG = ""
248
+ # Whether entity has properties
249
+ HAS_PROPERTIES = False
250
+ # Whether it is single line properties,
251
+ # it means that entity ends with '/>' instead of '</TAG>'
252
+ SINGLE = False
253
+ # children entities
254
+ CHILDREN: List[Type["Entity"]] = []
255
+
256
+ def __init__(self, **kwargs):
257
+ if self.__class__.SINGLE:
258
+ self.__class__.HAS_PROPERTIES = True
259
+
260
+ self.__tag = self.__class__.TAG.lower()
261
+
262
+ # define condition to identify section
263
+ pattern = "<\\s*" + self.__tag
264
+ if self.__class__.HAS_PROPERTIES:
265
+ pattern += "\\s+"
266
+ else:
267
+ pattern += "\\s*>"
268
+
269
+ self.__cond = lambda x: re.match(pattern, x.lower())
270
+ self.__lines = []
271
+ self.__properties_lines = []
272
+ self.__do = False
273
+ self.__custom_properties = kwargs
274
+
275
+ def to_lines(self):
276
+ result = []
277
+ line = "<" + self.__class__.TAG + " "
278
+
279
+ for key, value in self.__custom_properties.items():
280
+ line += key + '="' + str(value) + '" '
281
+
282
+ if self.__class__.SINGLE:
283
+ line += "/>"
284
+ result.append(line)
285
+ else:
286
+ line += ">"
287
+ result.append(line)
288
+
289
+ offset = " "
290
+ for child in self.__class__.CHILDREN:
291
+ if not child.SINGLE and not child.HAS_PROPERTIES:
292
+ result.append(offset + "<" + child.TAG + ">")
293
+ result.append(offset + "</" + child.TAG + ">")
294
+
295
+ result.append("</" + self.__class__.TAG + ">")
296
+
297
+ return result
298
+
299
+ def __process(self, reader, writer, action):
300
+ line = reader.current
301
+ pattern = "</\\s*" + self.__tag + "\\s*>"
302
+
303
+ while True:
304
+ self.__lines.append(line)
305
+
306
+ if self.__class__.SINGLE:
307
+ if line.find("/>") != -1:
308
+ # the final line for single and we have to return it
309
+ yield line
310
+
311
+ if action.just_executed:
312
+ self.finish(writer, action)
313
+
314
+ return
315
+ else:
316
+ if re.match(pattern, line.lower()):
317
+ self.finish(writer, action)
318
+ return
319
+
320
+ try:
321
+ line = next(reader)
322
+ yield line
323
+ except StopIteration:
324
+ return
325
+
326
+ def finish(self, writer, action):
327
+ if self.__do:
328
+ action.do(self, self.__lines, self.__properties_lines, writer)
329
+
330
+ def __read_properties__(self):
331
+ line = self.current
332
+ self.__properties_lines.append(line)
333
+ declare = line
334
+
335
+ while line.find(">") == -1:
336
+ line = next(self)
337
+ self.__properties_lines.append(line)
338
+ declare += " " + line
339
+
340
+ properties = get_properties(declare)
341
+ self.__dict__.update(properties)
342
+
343
+ def __process_children__(self, reader, writer, action):
344
+ for _ in self:
345
+ for child_p in self.__class__.CHILDREN:
346
+ child_p()(self, writer, action)
347
+
348
+ def __call__(self, reader, writer, action):
349
+ if self.__cond(reader.current):
350
+ self.generator = self.__process(reader, writer, action)
351
+ self.current = reader.current
352
+
353
+ self.__read_properties__()
354
+ action.apply(self.__class__.TAG, self)
355
+
356
+ if action.just_executed:
357
+ self.__do = True
358
+
359
+ self.parse(self, writer, action)
360
+
361
+ self.__process_children__(self, writer, action)
362
+
363
+ return True
364
+
365
+ return False
366
+
367
+ def parse(self, reader, writer, action):
368
+ pass