Skip to content

Commit 681ca97

Browse files
committed
Moved summary stuff to separate module
1 parent 5694ec2 commit 681ca97

File tree

2 files changed

+197
-173
lines changed

2 files changed

+197
-173
lines changed

neo4j/v1/session.py

Lines changed: 3 additions & 173 deletions
Original file line numberDiff line numberDiff line change
@@ -28,23 +28,19 @@ class which can be used to obtain `Driver` instances that are used for
2828

2929
from __future__ import division
3030

31-
from collections import deque, namedtuple
31+
from collections import deque
3232

33-
from .compat import integer, string, urlparse
3433
from .bolt import connect, Response, RUN, PULL_ALL
34+
from .compat import integer, string, urlparse
3535
from .constants import DEFAULT_PORT, ENCRYPTED_DEFAULT, TRUST_DEFAULT, TRUST_SIGNED_CERTIFICATES
3636
from .exceptions import CypherError, ProtocolError, ResultError
3737
from .ssl_compat import SSL_AVAILABLE, SSLContext, PROTOCOL_SSLv23, OP_NO_SSLv2, CERT_REQUIRED
38+
from .summary import ResultSummary
3839
from .types import hydrated
3940

4041

4142
DEFAULT_MAX_POOL_SIZE = 50
4243

43-
STATEMENT_TYPE_READ_ONLY = "r"
44-
STATEMENT_TYPE_READ_WRITE = "rw"
45-
STATEMENT_TYPE_WRITE_ONLY = "w"
46-
STATEMENT_TYPE_SCHEMA_WRITE = "s"
47-
4844

4945
def basic_auth(user, password):
5046
""" Generate a basic auth token for a given user and password.
@@ -290,172 +286,6 @@ def peek(self):
290286
raise ResultError("End of stream")
291287

292288

293-
class ResultSummary(object):
294-
""" A summary of execution returned with a :class:`.StatementResult` object.
295-
"""
296-
297-
#: The statement that was executed to produce this result.
298-
statement = None
299-
300-
#: Dictionary of parameters passed with the statement.
301-
parameters = None
302-
303-
#: The type of statement (``'r'`` = read-only, ``'rw'`` = read/write).
304-
statement_type = None
305-
306-
#: A set of statistical information held in a :class:`.Counters` instance.
307-
counters = None
308-
309-
#: A :class:`.Plan` instance
310-
plan = None
311-
312-
#: A :class:`.ProfiledPlan` instance
313-
profile = None
314-
315-
#: Notifications provide extra information for a user executing a statement.
316-
#: They can be warnings about problematic queries or other valuable information that can be
317-
#: presented in a client.
318-
#: Unlike failures or errors, notifications do not affect the execution of a statement.
319-
notifications = None
320-
321-
def __init__(self, statement, parameters, **metadata):
322-
self.statement = statement
323-
self.parameters = parameters
324-
self.statement_type = metadata.get("type")
325-
self.counters = SummaryCounters(metadata.get("stats", {}))
326-
if "plan" in metadata:
327-
self.plan = make_plan(metadata["plan"])
328-
if "profile" in metadata:
329-
self.profile = make_plan(metadata["profile"])
330-
self.plan = self.profile
331-
self.notifications = []
332-
for notification in metadata.get("notifications", []):
333-
position = notification.get("position")
334-
if position is not None:
335-
position = Position(position["offset"], position["line"], position["column"])
336-
self.notifications.append(Notification(notification["code"], notification["title"],
337-
notification["description"], notification["severity"], position))
338-
339-
340-
class SummaryCounters(object):
341-
""" Set of statistics from a Cypher statement execution.
342-
"""
343-
344-
#:
345-
nodes_created = 0
346-
347-
#:
348-
nodes_deleted = 0
349-
350-
#:
351-
relationships_created = 0
352-
353-
#:
354-
relationships_deleted = 0
355-
356-
#:
357-
properties_set = 0
358-
359-
#:
360-
labels_added = 0
361-
362-
#:
363-
labels_removed = 0
364-
365-
#:
366-
indexes_added = 0
367-
368-
#:
369-
indexes_removed = 0
370-
371-
#:
372-
constraints_added = 0
373-
374-
#:
375-
constraints_removed = 0
376-
377-
def __init__(self, statistics):
378-
for key, value in dict(statistics).items():
379-
key = key.replace("-", "_")
380-
setattr(self, key, value)
381-
382-
def __repr__(self):
383-
return repr(vars(self))
384-
385-
@property
386-
def contains_updates(self):
387-
return bool(self.nodes_created or self.nodes_deleted or \
388-
self.relationships_created or self.relationships_deleted or \
389-
self.properties_set or self.labels_added or self.labels_removed or \
390-
self.indexes_added or self.indexes_removed or \
391-
self.constraints_added or self.constraints_removed)
392-
393-
394-
#: A plan describes how the database will execute your statement.
395-
#:
396-
#: operator_type:
397-
#: the name of the operation performed by the plan
398-
#: identifiers:
399-
#: the list of identifiers used by this plan
400-
#: arguments:
401-
#: a dictionary of arguments used in the specific operation performed by the plan
402-
#: children:
403-
#: a list of sub-plans
404-
Plan = namedtuple("Plan", ("operator_type", "identifiers", "arguments", "children"))
405-
406-
#: A profiled plan describes how the database executed your statement.
407-
#:
408-
#: db_hits:
409-
#: the number of times this part of the plan touched the underlying data stores
410-
#: rows:
411-
#: the number of records this part of the plan produced
412-
ProfiledPlan = namedtuple("ProfiledPlan", Plan._fields + ("db_hits", "rows"))
413-
414-
#: Representation for notifications found when executing a statement. A
415-
#: notification can be visualized in a client pinpointing problems or
416-
#: other information about the statement.
417-
#:
418-
#: code:
419-
#: a notification code for the discovered issue.
420-
#: title:
421-
#: a short summary of the notification
422-
#: description:
423-
#: a long description of the notification
424-
#: severity:
425-
#: the severity level of the notification
426-
#: position:
427-
#: the position in the statement where this notification points to, if relevant.
428-
Notification = namedtuple("Notification", ("code", "title", "description", "severity", "position"))
429-
430-
#: A position within a statement, consisting of offset, line and column.
431-
#:
432-
#: offset:
433-
#: the character offset referred to by this position; offset numbers start at 0
434-
#: line:
435-
#: the line number referred to by the position; line numbers start at 1
436-
#: column:
437-
#: the column number referred to by the position; column numbers start at 1
438-
Position = namedtuple("Position", ("offset", "line", "column"))
439-
440-
441-
def make_plan(plan_dict):
442-
""" Construct a Plan or ProfiledPlan from a dictionary of metadata values.
443-
444-
:param plan_dict:
445-
:return:
446-
"""
447-
operator_type = plan_dict["operatorType"]
448-
identifiers = plan_dict.get("identifiers", [])
449-
arguments = plan_dict.get("args", [])
450-
children = [make_plan(child) for child in plan_dict.get("children", [])]
451-
if "dbHits" in plan_dict or "rows" in plan_dict:
452-
db_hits = plan_dict.get("dbHits", 0)
453-
rows = plan_dict.get("rows", 0)
454-
return ProfiledPlan(operator_type, identifiers, arguments, children, db_hits, rows)
455-
else:
456-
return Plan(operator_type, identifiers, arguments, children)
457-
458-
459289
def run(connection, statement, parameters=None):
460290
""" Run a Cypher statement on a given connection.
461291

neo4j/v1/summary.py

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
#!/usr/bin/env python
2+
# -*- encoding: utf-8 -*-
3+
4+
# Copyright (c) 2002-2016 "Neo Technology,"
5+
# Network Engine for Objects in Lund AB [http://neotechnology.com]
6+
#
7+
# This file is part of Neo4j.
8+
#
9+
# Licensed under the Apache License, Version 2.0 (the "License");
10+
# you may not use this file except in compliance with the License.
11+
# You may obtain a copy of the License at
12+
#
13+
# http://www.apache.org/licenses/LICENSE-2.0
14+
#
15+
# Unless required by applicable law or agreed to in writing, software
16+
# distributed under the License is distributed on an "AS IS" BASIS,
17+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
# See the License for the specific language governing permissions and
19+
# limitations under the License.
20+
21+
22+
from collections import namedtuple
23+
24+
25+
STATEMENT_TYPE_READ_ONLY = "r"
26+
STATEMENT_TYPE_READ_WRITE = "rw"
27+
STATEMENT_TYPE_WRITE_ONLY = "w"
28+
STATEMENT_TYPE_SCHEMA_WRITE = "s"
29+
30+
31+
class ResultSummary(object):
32+
""" A summary of execution returned with a :class:`.StatementResult` object.
33+
"""
34+
35+
#: The statement that was executed to produce this result.
36+
statement = None
37+
38+
#: Dictionary of parameters passed with the statement.
39+
parameters = None
40+
41+
#: The type of statement (``'r'`` = read-only, ``'rw'`` = read/write).
42+
statement_type = None
43+
44+
#: A set of statistical information held in a :class:`.Counters` instance.
45+
counters = None
46+
47+
#: A :class:`.Plan` instance
48+
plan = None
49+
50+
#: A :class:`.ProfiledPlan` instance
51+
profile = None
52+
53+
#: Notifications provide extra information for a user executing a statement.
54+
#: They can be warnings about problematic queries or other valuable information that can be
55+
#: presented in a client.
56+
#: Unlike failures or errors, notifications do not affect the execution of a statement.
57+
notifications = None
58+
59+
def __init__(self, statement, parameters, **metadata):
60+
self.statement = statement
61+
self.parameters = parameters
62+
self.statement_type = metadata.get("type")
63+
self.counters = SummaryCounters(metadata.get("stats", {}))
64+
if "plan" in metadata:
65+
self.plan = make_plan(metadata["plan"])
66+
if "profile" in metadata:
67+
self.profile = make_plan(metadata["profile"])
68+
self.plan = self.profile
69+
self.notifications = []
70+
for notification in metadata.get("notifications", []):
71+
position = notification.get("position")
72+
if position is not None:
73+
position = Position(position["offset"], position["line"], position["column"])
74+
self.notifications.append(Notification(notification["code"], notification["title"],
75+
notification["description"], notification["severity"], position))
76+
77+
78+
class SummaryCounters(object):
79+
""" Set of statistics from a Cypher statement execution.
80+
"""
81+
82+
#:
83+
nodes_created = 0
84+
85+
#:
86+
nodes_deleted = 0
87+
88+
#:
89+
relationships_created = 0
90+
91+
#:
92+
relationships_deleted = 0
93+
94+
#:
95+
properties_set = 0
96+
97+
#:
98+
labels_added = 0
99+
100+
#:
101+
labels_removed = 0
102+
103+
#:
104+
indexes_added = 0
105+
106+
#:
107+
indexes_removed = 0
108+
109+
#:
110+
constraints_added = 0
111+
112+
#:
113+
constraints_removed = 0
114+
115+
def __init__(self, statistics):
116+
for key, value in dict(statistics).items():
117+
key = key.replace("-", "_")
118+
setattr(self, key, value)
119+
120+
def __repr__(self):
121+
return repr(vars(self))
122+
123+
@property
124+
def contains_updates(self):
125+
return bool(self.nodes_created or self.nodes_deleted or
126+
self.relationships_created or self.relationships_deleted or
127+
self.properties_set or self.labels_added or self.labels_removed or
128+
self.indexes_added or self.indexes_removed or
129+
self.constraints_added or self.constraints_removed)
130+
131+
132+
#: A plan describes how the database will execute your statement.
133+
#:
134+
#: operator_type:
135+
#: the name of the operation performed by the plan
136+
#: identifiers:
137+
#: the list of identifiers used by this plan
138+
#: arguments:
139+
#: a dictionary of arguments used in the specific operation performed by the plan
140+
#: children:
141+
#: a list of sub-plans
142+
Plan = namedtuple("Plan", ("operator_type", "identifiers", "arguments", "children"))
143+
144+
#: A profiled plan describes how the database executed your statement.
145+
#:
146+
#: db_hits:
147+
#: the number of times this part of the plan touched the underlying data stores
148+
#: rows:
149+
#: the number of records this part of the plan produced
150+
ProfiledPlan = namedtuple("ProfiledPlan", Plan._fields + ("db_hits", "rows"))
151+
152+
#: Representation for notifications found when executing a statement. A
153+
#: notification can be visualized in a client pinpointing problems or
154+
#: other information about the statement.
155+
#:
156+
#: code:
157+
#: a notification code for the discovered issue.
158+
#: title:
159+
#: a short summary of the notification
160+
#: description:
161+
#: a long description of the notification
162+
#: severity:
163+
#: the severity level of the notification
164+
#: position:
165+
#: the position in the statement where this notification points to, if relevant.
166+
Notification = namedtuple("Notification", ("code", "title", "description", "severity", "position"))
167+
168+
#: A position within a statement, consisting of offset, line and column.
169+
#:
170+
#: offset:
171+
#: the character offset referred to by this position; offset numbers start at 0
172+
#: line:
173+
#: the line number referred to by the position; line numbers start at 1
174+
#: column:
175+
#: the column number referred to by the position; column numbers start at 1
176+
Position = namedtuple("Position", ("offset", "line", "column"))
177+
178+
179+
def make_plan(plan_dict):
180+
""" Construct a Plan or ProfiledPlan from a dictionary of metadata values.
181+
182+
:param plan_dict:
183+
:return:
184+
"""
185+
operator_type = plan_dict["operatorType"]
186+
identifiers = plan_dict.get("identifiers", [])
187+
arguments = plan_dict.get("args", [])
188+
children = [make_plan(child) for child in plan_dict.get("children", [])]
189+
if "dbHits" in plan_dict or "rows" in plan_dict:
190+
db_hits = plan_dict.get("dbHits", 0)
191+
rows = plan_dict.get("rows", 0)
192+
return ProfiledPlan(operator_type, identifiers, arguments, children, db_hits, rows)
193+
else:
194+
return Plan(operator_type, identifiers, arguments, children)

0 commit comments

Comments
 (0)