trigger 2.0.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.
- trigger/__init__.py +7 -0
- trigger/acl/__init__.py +32 -0
- trigger/acl/autoacl.py +70 -0
- trigger/acl/db.py +324 -0
- trigger/acl/dicts.py +357 -0
- trigger/acl/grammar.py +112 -0
- trigger/acl/ios.py +222 -0
- trigger/acl/junos.py +422 -0
- trigger/acl/models.py +118 -0
- trigger/acl/parser.py +168 -0
- trigger/acl/queue.py +296 -0
- trigger/acl/support.py +1431 -0
- trigger/acl/tools.py +746 -0
- trigger/bin/__init__.py +0 -0
- trigger/bin/acl.py +233 -0
- trigger/bin/acl_script.py +574 -0
- trigger/bin/aclconv.py +82 -0
- trigger/bin/check_access.py +93 -0
- trigger/bin/check_syntax.py +66 -0
- trigger/bin/fe.py +197 -0
- trigger/bin/find_access.py +191 -0
- trigger/bin/gnng.py +434 -0
- trigger/bin/gong.py +86 -0
- trigger/bin/load_acl.py +841 -0
- trigger/bin/load_config.py +18 -0
- trigger/bin/netdev.py +317 -0
- trigger/bin/optimizer.py +638 -0
- trigger/bin/run_cmds.py +18 -0
- trigger/changemgmt/__init__.py +352 -0
- trigger/changemgmt/bounce.py +57 -0
- trigger/cmds.py +1217 -0
- trigger/conf/__init__.py +94 -0
- trigger/conf/global_settings.py +674 -0
- trigger/contrib/__init__.py +7 -0
- trigger/exceptions.py +307 -0
- trigger/gorc.py +172 -0
- trigger/netdevices/__init__.py +1288 -0
- trigger/netdevices/loader.py +174 -0
- trigger/netscreen.py +1030 -0
- trigger/packages/__init__.py +6 -0
- trigger/packages/peewee.py +8084 -0
- trigger/rancid.py +463 -0
- trigger/tacacsrc.py +584 -0
- trigger/twister.py +2203 -0
- trigger/twister2.py +745 -0
- trigger/utils/__init__.py +88 -0
- trigger/utils/cli.py +349 -0
- trigger/utils/importlib.py +77 -0
- trigger/utils/network.py +157 -0
- trigger/utils/rcs.py +178 -0
- trigger/utils/templates.py +81 -0
- trigger/utils/url.py +78 -0
- trigger/utils/xmltodict.py +298 -0
- trigger-2.0.0.dist-info/METADATA +146 -0
- trigger-2.0.0.dist-info/RECORD +61 -0
- trigger-2.0.0.dist-info/WHEEL +5 -0
- trigger-2.0.0.dist-info/entry_points.txt +15 -0
- trigger-2.0.0.dist-info/licenses/AUTHORS.md +20 -0
- trigger-2.0.0.dist-info/licenses/LICENSE.md +28 -0
- trigger-2.0.0.dist-info/top_level.txt +2 -0
- twisted/plugins/trigger_xmlrpc.py +124 -0
trigger/acl/parser.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Parse and manipulate network access control lists.
|
|
3
|
+
|
|
4
|
+
This library doesn't completely follow the border of the valid/invalid ACL
|
|
5
|
+
set, which is determined by multiple vendors and not completely documented
|
|
6
|
+
by any of them. We could asymptotically approach that with an enormous
|
|
7
|
+
amount of testing, although it would require a 'flavor' flag (vendor,
|
|
8
|
+
router model, software version) for full support. The realistic goal
|
|
9
|
+
is to catch all the errors that we see in practice, and to accept all
|
|
10
|
+
the ACLs that we use in practice, rather than to try to reject *every*
|
|
11
|
+
invalid ACL and accept *every* valid ACL.
|
|
12
|
+
|
|
13
|
+
>>> from trigger.acl import parse
|
|
14
|
+
>>> aclobj = parse("access-list 123 permit tcp any host 10.20.30.40 eq 80")
|
|
15
|
+
>>> aclobj.terms
|
|
16
|
+
[<Term: None>]
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
"""
|
|
20
|
+
7/15/2014
|
|
21
|
+
This file was split into smaller modules: dicts, grammar, junos, ios, and support.
|
|
22
|
+
These modules are then included back into parser.py.
|
|
23
|
+
This makes the code more readable.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
__author__ = "Jathan McCollum, Mike Biancaniello, Michael Harding, Michael Shields"
|
|
27
|
+
__maintainer__ = "Jathan McCollum"
|
|
28
|
+
__email__ = "jathanism@aol.com"
|
|
29
|
+
__copyright__ = "Copyright 2006-2013, AOL Inc.; 2013 Saleforce.com"
|
|
30
|
+
|
|
31
|
+
from simpleparse.common import comments, strings # noqa: F401
|
|
32
|
+
from simpleparse.dispatchprocessor import DispatchProcessor, dispatch, dispatchList
|
|
33
|
+
from simpleparse.parser import Parser
|
|
34
|
+
|
|
35
|
+
from trigger import exceptions
|
|
36
|
+
|
|
37
|
+
from .ios import *
|
|
38
|
+
from .junos import *
|
|
39
|
+
from .support import *
|
|
40
|
+
|
|
41
|
+
# Exports
|
|
42
|
+
__all__ = (
|
|
43
|
+
# Constants,
|
|
44
|
+
"ports",
|
|
45
|
+
# Functions
|
|
46
|
+
"check_range",
|
|
47
|
+
"default_processor",
|
|
48
|
+
"do_port_lookup",
|
|
49
|
+
"do_protocol_lookup",
|
|
50
|
+
"literals",
|
|
51
|
+
"make_nondefault_processor",
|
|
52
|
+
"parse",
|
|
53
|
+
"strip_comments",
|
|
54
|
+
"S",
|
|
55
|
+
# Classes
|
|
56
|
+
"ACL",
|
|
57
|
+
"ACLParser",
|
|
58
|
+
"ACLProcessor",
|
|
59
|
+
"Comment",
|
|
60
|
+
"Matches",
|
|
61
|
+
"Policer",
|
|
62
|
+
"PolicerGroup",
|
|
63
|
+
"Protocol",
|
|
64
|
+
"RangeList",
|
|
65
|
+
"Remark",
|
|
66
|
+
"Term",
|
|
67
|
+
"TermList",
|
|
68
|
+
"TIP",
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Temporary resting place for comments, so the rest of the parser can
|
|
72
|
+
# ignore them. Yes, this makes the library not thread-safe.
|
|
73
|
+
Comments = []
|
|
74
|
+
|
|
75
|
+
#
|
|
76
|
+
# Parsing infrastructure
|
|
77
|
+
#
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class ACLProcessor(DispatchProcessor):
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def default_processor(self, tag_info, buffer):
|
|
85
|
+
tag, start, stop, subtags = tag_info
|
|
86
|
+
if not subtags:
|
|
87
|
+
return buffer[start:stop]
|
|
88
|
+
elif len(subtags) == 1:
|
|
89
|
+
return dispatch(self, subtags[0], buffer)
|
|
90
|
+
else:
|
|
91
|
+
return dispatchList(self, subtags, buffer)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def make_nondefault_processor(action):
|
|
95
|
+
if callable(action):
|
|
96
|
+
|
|
97
|
+
def processor(self, tag_info, buffer):
|
|
98
|
+
tag, start, stop, subtags = tag_info
|
|
99
|
+
if tag in subtagged:
|
|
100
|
+
results = [
|
|
101
|
+
getattr(self, subtag[0])(subtag, buffer) for subtag in subtags
|
|
102
|
+
]
|
|
103
|
+
return action(strip_comments(results))
|
|
104
|
+
else:
|
|
105
|
+
return action(buffer[start:stop])
|
|
106
|
+
|
|
107
|
+
else:
|
|
108
|
+
|
|
109
|
+
def processor(self, tag_info, buffer):
|
|
110
|
+
tag, start, stop, subtags = tag_info
|
|
111
|
+
return action
|
|
112
|
+
|
|
113
|
+
return processor
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
grammar = []
|
|
117
|
+
for production, rule in rules.items():
|
|
118
|
+
if isinstance(rule, tuple):
|
|
119
|
+
assert len(rule) == 2
|
|
120
|
+
setattr(ACLProcessor, production, make_nondefault_processor(rule[1]))
|
|
121
|
+
grammar.append(f"{production} := {rule[0]}")
|
|
122
|
+
else:
|
|
123
|
+
setattr(ACLProcessor, production, default_processor)
|
|
124
|
+
grammar.append(f"{production} := {rule}")
|
|
125
|
+
|
|
126
|
+
grammar = "\n".join(grammar)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class ACLParser(Parser):
|
|
130
|
+
def buildProcessor(self):
|
|
131
|
+
return ACLProcessor()
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
###parser = ACLParser(grammar)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def parse(input_data):
|
|
138
|
+
"""
|
|
139
|
+
Parse a complete ACL and return an ACL object. This should be the only
|
|
140
|
+
external interface to the parser.
|
|
141
|
+
|
|
142
|
+
>>> from trigger.acl import parse
|
|
143
|
+
>>> aclobj = parse("access-list 123 permit tcp any host 10.20.30.40 eq 80")
|
|
144
|
+
>>> aclobj.terms
|
|
145
|
+
[<Term: None>]
|
|
146
|
+
|
|
147
|
+
:param input_data:
|
|
148
|
+
An ACL policy as a string or file-like object.
|
|
149
|
+
"""
|
|
150
|
+
parser = ACLParser(grammar)
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
data = input_data.read()
|
|
154
|
+
except AttributeError:
|
|
155
|
+
data = input_data
|
|
156
|
+
|
|
157
|
+
## parse the acl
|
|
158
|
+
success, children, nextchar = parser.parse(data)
|
|
159
|
+
|
|
160
|
+
if success and nextchar == len(data):
|
|
161
|
+
assert len(children) == 1
|
|
162
|
+
return children[0]
|
|
163
|
+
else:
|
|
164
|
+
line = data[:nextchar].count("\n") + 1
|
|
165
|
+
column = len(data[data[nextchar].rfind("\n") : nextchar]) + 1
|
|
166
|
+
raise exceptions.ParseError(
|
|
167
|
+
"Could not match syntax. Please report as a bug.", line, column
|
|
168
|
+
)
|
trigger/acl/queue.py
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Database interface for automated ACL queue. Used primarily by ``load_acl`` and
|
|
3
|
+
``acl``` commands for manipulating the work queue.
|
|
4
|
+
|
|
5
|
+
>>> from trigger.acl.queue import Queue
|
|
6
|
+
>>> q = Queue()
|
|
7
|
+
>>> q.list()
|
|
8
|
+
(('dc1-abc.net.aol.com', 'datacenter-protect'), ('dc2-abc.net.aol.com',
|
|
9
|
+
'datacenter-protect'))
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
__author__ = "Jathan McCollum"
|
|
13
|
+
__maintainer__ = "Jathan McCollum"
|
|
14
|
+
__email__ = "jmccollum@salesforce.com"
|
|
15
|
+
__version__ = "2.0.1"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
import datetime
|
|
19
|
+
|
|
20
|
+
from trigger import exceptions
|
|
21
|
+
from trigger.conf import settings
|
|
22
|
+
from trigger.netdevices import NetDevices
|
|
23
|
+
from trigger.utils import get_user
|
|
24
|
+
|
|
25
|
+
from . import models
|
|
26
|
+
|
|
27
|
+
# Globals
|
|
28
|
+
QUEUE_NAMES = ("integrated", "manual")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# Exports
|
|
32
|
+
__all__ = ("Queue",)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# Classes
|
|
36
|
+
class Queue:
|
|
37
|
+
"""
|
|
38
|
+
Interacts with firewalls database to insert/remove items into the queue.
|
|
39
|
+
|
|
40
|
+
:param verbose:
|
|
41
|
+
Toggle verbosity
|
|
42
|
+
|
|
43
|
+
:type verbose:
|
|
44
|
+
Boolean
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(self, verbose=True):
|
|
48
|
+
self.nd = NetDevices()
|
|
49
|
+
self.verbose = verbose
|
|
50
|
+
self.login = get_user()
|
|
51
|
+
|
|
52
|
+
def vprint(self, msg):
|
|
53
|
+
"""
|
|
54
|
+
Print something if ``verbose`` instance variable is set.
|
|
55
|
+
|
|
56
|
+
:param msg:
|
|
57
|
+
The string to print
|
|
58
|
+
"""
|
|
59
|
+
if self.verbose:
|
|
60
|
+
print(msg)
|
|
61
|
+
|
|
62
|
+
def get_model(self, queue):
|
|
63
|
+
"""
|
|
64
|
+
Given a queue name, return its DB model.
|
|
65
|
+
|
|
66
|
+
:param queue:
|
|
67
|
+
Name of the queue whose object you want
|
|
68
|
+
"""
|
|
69
|
+
return models.MODEL_MAP.get(queue, None)
|
|
70
|
+
|
|
71
|
+
def create_task(self, queue, *args, **kwargs):
|
|
72
|
+
"""
|
|
73
|
+
Create a task in the specified queue.
|
|
74
|
+
|
|
75
|
+
:param queue:
|
|
76
|
+
Name of the queue whose object you want
|
|
77
|
+
"""
|
|
78
|
+
model = self.get_model(queue)
|
|
79
|
+
model.create(*args, **kwargs)
|
|
80
|
+
|
|
81
|
+
def _normalize(self, arg, prefix=""):
|
|
82
|
+
"""
|
|
83
|
+
Remove ``prefix`` from ``arg``, and set "escalation" bit.
|
|
84
|
+
|
|
85
|
+
:param arg:
|
|
86
|
+
Arg (typically an ACL filename) to trim
|
|
87
|
+
|
|
88
|
+
:param prefix:
|
|
89
|
+
Prefix to trim from arg
|
|
90
|
+
"""
|
|
91
|
+
if arg.startswith(prefix):
|
|
92
|
+
arg = arg[len(prefix) :]
|
|
93
|
+
escalation = False
|
|
94
|
+
if arg.upper().endswith(" ESCALATION"):
|
|
95
|
+
escalation = True
|
|
96
|
+
arg = arg[:-11]
|
|
97
|
+
return (escalation, arg)
|
|
98
|
+
|
|
99
|
+
def insert(self, acl, routers, escalation=False):
|
|
100
|
+
"""
|
|
101
|
+
Insert an ACL and associated devices into the ACL load queue.
|
|
102
|
+
|
|
103
|
+
Attempts to insert into integrated queue. If ACL test fails, then
|
|
104
|
+
item is inserted into manual queue.
|
|
105
|
+
|
|
106
|
+
:param acl:
|
|
107
|
+
ACL name
|
|
108
|
+
|
|
109
|
+
:param routers:
|
|
110
|
+
List of device names
|
|
111
|
+
|
|
112
|
+
:param escalation:
|
|
113
|
+
Whether this is an escalated task
|
|
114
|
+
"""
|
|
115
|
+
if not acl:
|
|
116
|
+
raise exceptions.ACLQueueError(
|
|
117
|
+
"You must specify an ACL to insert into the queue"
|
|
118
|
+
)
|
|
119
|
+
if not routers:
|
|
120
|
+
routers = []
|
|
121
|
+
|
|
122
|
+
escalation, acl = self._normalize(acl)
|
|
123
|
+
if routers:
|
|
124
|
+
for router in routers:
|
|
125
|
+
try:
|
|
126
|
+
dev = self.nd.find(router)
|
|
127
|
+
except KeyError:
|
|
128
|
+
msg = f"Could not find device {router}"
|
|
129
|
+
raise exceptions.TriggerError(msg)
|
|
130
|
+
|
|
131
|
+
if acl not in dev.acls:
|
|
132
|
+
msg = f"Could not find {acl} in ACL list for {router}"
|
|
133
|
+
raise exceptions.TriggerError(msg)
|
|
134
|
+
|
|
135
|
+
self.create_task(
|
|
136
|
+
queue="integrated", acl=acl, router=router, escalation=escalation
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
self.vprint(
|
|
140
|
+
"ACL {} injected into integrated load queue for {}".format(
|
|
141
|
+
acl, ", ".join(dev[: dev.find(".")] for dev in routers)
|
|
142
|
+
)
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
else:
|
|
146
|
+
self.create_task(queue="manual", q_name=acl, login=self.login)
|
|
147
|
+
self.vprint(f'"{acl}" injected into manual load queue')
|
|
148
|
+
|
|
149
|
+
def delete(self, acl, routers=None, escalation=False):
|
|
150
|
+
"""
|
|
151
|
+
Delete an ACL from the firewall database queue.
|
|
152
|
+
|
|
153
|
+
Attempts to delete from integrated queue. If ACL test fails
|
|
154
|
+
or if routers are not specified, the item is deleted from manual queue.
|
|
155
|
+
|
|
156
|
+
:param acl:
|
|
157
|
+
ACL name
|
|
158
|
+
|
|
159
|
+
:param routers:
|
|
160
|
+
List of device names. If this is ommitted, the manual queue is used.
|
|
161
|
+
|
|
162
|
+
:param escalation:
|
|
163
|
+
Whether this is an escalated task
|
|
164
|
+
"""
|
|
165
|
+
if not acl:
|
|
166
|
+
raise exceptions.ACLQueueError(
|
|
167
|
+
"You must specify an ACL to delete from the queue"
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
escalation, acl = self._normalize(acl)
|
|
171
|
+
m = self.get_model("integrated")
|
|
172
|
+
|
|
173
|
+
if routers is not None:
|
|
174
|
+
devs = routers
|
|
175
|
+
else:
|
|
176
|
+
self.vprint("Fetching routers from database")
|
|
177
|
+
result = (
|
|
178
|
+
m.select(m.router)
|
|
179
|
+
.distinct()
|
|
180
|
+
.where(m.acl == acl, m.loaded >> None)
|
|
181
|
+
.order_by(m.router)
|
|
182
|
+
)
|
|
183
|
+
rows = result.tuples()
|
|
184
|
+
devs = [row[0] for row in rows]
|
|
185
|
+
|
|
186
|
+
if devs:
|
|
187
|
+
for dev in devs:
|
|
188
|
+
m.delete().where(
|
|
189
|
+
m.acl == acl, m.router == dev, m.loaded >> None
|
|
190
|
+
).execute()
|
|
191
|
+
|
|
192
|
+
self.vprint(
|
|
193
|
+
"ACL {} cleared from integrated load queue for {}".format(
|
|
194
|
+
acl, ", ".join(dev[: dev.find(".")] for dev in devs)
|
|
195
|
+
)
|
|
196
|
+
)
|
|
197
|
+
return True
|
|
198
|
+
|
|
199
|
+
else:
|
|
200
|
+
m = self.get_model("manual")
|
|
201
|
+
if m.delete().where(m.q_name == acl, m.done == False).execute(): # noqa: E712
|
|
202
|
+
self.vprint(f"{acl!r} cleared from manual load queue")
|
|
203
|
+
return True
|
|
204
|
+
|
|
205
|
+
self.vprint(f"{acl!r} not found in any queues")
|
|
206
|
+
return False
|
|
207
|
+
|
|
208
|
+
def complete(self, device, acls):
|
|
209
|
+
"""
|
|
210
|
+
Mark a device and its ACLs as complete using current timestamp.
|
|
211
|
+
|
|
212
|
+
(Integrated queue only.)
|
|
213
|
+
|
|
214
|
+
:param device:
|
|
215
|
+
Device names
|
|
216
|
+
|
|
217
|
+
:param acls:
|
|
218
|
+
List of ACL names
|
|
219
|
+
"""
|
|
220
|
+
m = self.get_model("integrated")
|
|
221
|
+
for acl in acls:
|
|
222
|
+
now = datetime.datetime.now()
|
|
223
|
+
m.update(loaded=now).where(
|
|
224
|
+
m.acl == acl, m.router == device, m.loaded >> None
|
|
225
|
+
).execute()
|
|
226
|
+
|
|
227
|
+
self.vprint(f"Marked the following ACLs as complete for {device}:")
|
|
228
|
+
self.vprint(", ".join(acls))
|
|
229
|
+
|
|
230
|
+
def remove(self, acl, routers, escalation=False):
|
|
231
|
+
"""
|
|
232
|
+
Integrated queue only.
|
|
233
|
+
|
|
234
|
+
Mark an ACL and associated devices as "removed" (loaded=0). Intended
|
|
235
|
+
for use when performing manual actions on the load queue when
|
|
236
|
+
troubleshooting or addressing errors with automated loads. This leaves
|
|
237
|
+
the items in the database but removes them from the active queue.
|
|
238
|
+
|
|
239
|
+
:param acl:
|
|
240
|
+
ACL name
|
|
241
|
+
|
|
242
|
+
:param routers:
|
|
243
|
+
List of device names
|
|
244
|
+
|
|
245
|
+
:param escalation:
|
|
246
|
+
Whether this is an escalated task
|
|
247
|
+
"""
|
|
248
|
+
if not acl:
|
|
249
|
+
raise exceptions.ACLQueueError(
|
|
250
|
+
"You must specify an ACL to remove from the queue"
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
m = self.get_model("integrated")
|
|
254
|
+
loaded = 0
|
|
255
|
+
if settings.DATABASE_ENGINE == "postgresql":
|
|
256
|
+
loaded = "-infinity" # See: http://bit.ly/15f0J3z
|
|
257
|
+
for router in routers:
|
|
258
|
+
m.update(loaded=loaded).where(
|
|
259
|
+
m.acl == acl, m.router == router, m.loaded >> None
|
|
260
|
+
).execute()
|
|
261
|
+
|
|
262
|
+
self.vprint(f"Marked the following devices as removed for ACL {acl}: ")
|
|
263
|
+
self.vprint(", ".join(routers))
|
|
264
|
+
|
|
265
|
+
def list(self, queue="integrated", escalation=False, q_names=QUEUE_NAMES):
|
|
266
|
+
"""
|
|
267
|
+
List items in the specified queue, defauls to integrated queue.
|
|
268
|
+
|
|
269
|
+
:param queue:
|
|
270
|
+
Name of the queue to list
|
|
271
|
+
|
|
272
|
+
:param escalation:
|
|
273
|
+
Whether this is an escalated task
|
|
274
|
+
|
|
275
|
+
:param q_names:
|
|
276
|
+
(Optional) List of valid queue names
|
|
277
|
+
"""
|
|
278
|
+
if queue not in q_names:
|
|
279
|
+
self.vprint(f"Queue must be one of {q_names}, not: {queue}")
|
|
280
|
+
return False
|
|
281
|
+
|
|
282
|
+
m = self.get_model(queue)
|
|
283
|
+
|
|
284
|
+
if queue == "integrated":
|
|
285
|
+
result = (
|
|
286
|
+
m.select(m.router, m.acl)
|
|
287
|
+
.distinct()
|
|
288
|
+
.where(m.loaded >> None, m.escalation == escalation)
|
|
289
|
+
)
|
|
290
|
+
elif queue == "manual":
|
|
291
|
+
result = m.select(m.q_name, m.login, m.q_ts, m.done).where(m.done == False) # noqa: E712
|
|
292
|
+
else:
|
|
293
|
+
raise RuntimeError("This should never happen!!")
|
|
294
|
+
|
|
295
|
+
all_data = list(result.tuples())
|
|
296
|
+
return all_data
|