Skip to content

Commit fb6a9e0

Browse files
authored
Merge branch 'master' into use-f-string-formatting
2 parents 6fbb763 + e988cdd commit fb6a9e0

25 files changed

+545
-88
lines changed

.circleci/config.yml

Lines changed: 18 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -20,56 +20,44 @@ jobs:
2020
build:
2121
docker:
2222
- image: circleci/python:3.6.1
23-
23+
2424
- image: redislabs/redisgraph:edge
2525

2626
working_directory: ~/repo
2727

2828
steps:
2929
- checkout
3030

31-
# Download and cache dependencies
32-
- restore_cache:
33-
keys:
34-
- v1-dependencies-{{ checksum "requirements.txt" }}
35-
# fallback to using the latest cache if no exact match is found
36-
- v1-dependencies-
31+
- run:
32+
name: Install tox
33+
command: sudo pip install tox
3734

3835
- run:
39-
name: install dependencies
40-
command: |
41-
python -m venv venv
42-
. venv/bin/activate
43-
pip install -r requirements.txt
44-
pip install codecov
36+
name: Test package build
37+
command: tox -e sdist
4538

46-
- save_cache:
47-
paths:
48-
- ./venv
49-
key: v1-dependencies-{{ checksum "requirements.txt" }}
39+
- run:
40+
name: Run code styles
41+
command: tox -e pep8
5042

5143
- run:
52-
name: test dist
53-
command: python setup.py sdist
44+
name: Run unittest with coverage
45+
command: tox -e cover
5446

5547
- run:
56-
name: run tests
57-
command: |
58-
. venv/bin/activate
59-
coverage run test.py
48+
name: Run functional tests
49+
command: tox -e func
6050

6151
- early_return_for_forked_pull_requests
6252

6353
- run:
6454
name: codecove
6555
command: |
66-
. venv/bin/activate
67-
codecov
56+
. .tox/func/bin/activate
57+
codecov --file .tox/cover/report/coverage.xml --name ${CODECOV_NAME}-unittests
58+
codecov --file .tox/func/report/coverage.xml --name ${CODECOV_NAME}-functional
59+
6860
69-
# - store_artifacts:
70-
# path: test-reports
71-
# destination: test-reports
72-
7361
workflows:
7462
version: 2
7563
commit:
@@ -84,4 +72,4 @@ workflows:
8472
only:
8573
- master
8674
jobs:
87-
- build
75+
- build

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ for record in result.result_set:
6161
person_age = record[1]
6262
visit_purpose = record[2]
6363
country_name = record[3]
64-
64+
6565
query = """MATCH p = (:person)-[:visited {purpose:"pleasure"}]->(:country) RETURN p"""
6666

6767
result = redis_graph.query(query)
@@ -88,3 +88,10 @@ pip install redisgraph
8888
```
8989
pip install git+https://github.com/RedisGraph/redisgraph-py.git@master
9090
```
91+
92+
### Install for development in env
93+
94+
```
95+
tox -e env
96+
source ./tox/env/bin/activate
97+
```

redisgraph/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from .node import Node
2-
from .edge import Edge
3-
from .graph import Graph
4-
from .path import Path
1+
from .node import Node # noqa
2+
from .edge import Edge # noqa
3+
from .graph import Graph # noqa
4+
from .path import Path # noqa

redisgraph/edge.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
from redisgraph import Node
22

3-
from .util import *
3+
from .util import quote_string
4+
45

56
class Edge:
67
"""
78
An edge connecting two nodes.
89
"""
9-
def __init__(self, src_node, relation, dest_node, edge_id=None, properties=None):
10+
def __init__(self, src_node, relation, dest_node, edge_id=None,
11+
properties=None):
1012
"""
1113
Create a new edge.
1214
"""
13-
if not (src_node and dest_node):
15+
if (src_node is None or dest_node is None):
1416
# NOTE(bors-42): It makes sense to change AssertionError to
1517
# ValueError here
1618
raise AssertionError("Both src_node & dest_node must be provided")

redisgraph/exceptions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1+
12
class VersionMismatchException(Exception):
23
def __init__(self, version):
34
self.version = version
4-

redisgraph/graph.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
from .util import *
21
import redis
3-
from .query_result import QueryResult
4-
from .exceptions import VersionMismatchException
2+
3+
from redisgraph.util import random_string, quote_string
4+
from redisgraph.query_result import QueryResult
5+
from redisgraph.exceptions import VersionMismatchException
6+
57

68
class Graph:
79
"""
@@ -151,7 +153,8 @@ def query(self, q, params=None, timeout=None, read_only=False):
151153
# ask for compact result-set format
152154
# specify known graph version
153155
cmd = "GRAPH.RO_QUERY" if read_only else "GRAPH.QUERY"
154-
command = [cmd, self.name, query, "--compact", "version", self.version]
156+
# command = [cmd, self.name, query, "--compact", "version", self.version]
157+
command = [cmd, self.name, query, "--compact"]
155158

156159
# include timeout is specified
157160
if timeout:
@@ -203,7 +206,7 @@ def merge(self, pattern):
203206
return self.query(f'MERGE {pattern}')
204207

205208
# Procedures.
206-
def call_procedure(self, procedure, read_only=False, *args, **kwagrs):
209+
def call_procedure(self, procedure, *args, read_only=False, **kwagrs):
207210
args = [quote_string(arg) for arg in args]
208211
query = f'CALL {procedure}({",".join(args)})'
209212

redisgraph/node.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
1-
from .util import *
1+
from .util import quote_string
2+
23

34
class Node:
45
"""
56
A node within the garph.
67
"""
7-
def __init__(self, node_id=None, alias=None, label=None, properties={}):
8+
def __init__(self, node_id=None, alias=None, label=None, properties=None):
89
"""
910
Create a new node
1011
"""
1112
self.id = node_id
1213
self.alias = alias
1314
self.label = label
14-
self.properties = properties
15+
self.properties = properties or {}
1516

1617
def toString(self):
1718
res = ''

redisgraph/path.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
from .node import Node
22
from .edge import Edge
33

4-
class Path:
54

5+
class Path:
66
def __init__(self, nodes, edges):
7-
assert(isinstance(nodes, list) and isinstance(edges, list))
7+
if not (isinstance(nodes, list) and isinstance(edges, list)):
8+
raise TypeError("nodes and edges must be list")
9+
810
self._nodes = nodes
911
self._edges = edges
1012
self.append_type = Node
@@ -38,13 +40,15 @@ def nodes_count(self):
3840
return len(self._nodes)
3941

4042
def add_node(self, node):
41-
assert(type(node) == self.append_type)
43+
if not isinstance(node, self.append_type):
44+
raise AssertionError("Add Edge before adding Node")
4245
self._nodes.append(node)
4346
self.append_type = Edge
4447
return self
4548

4649
def add_edge(self, edge):
47-
assert(type(edge) == self.append_type)
50+
if not isinstance(edge, self.append_type):
51+
raise AssertionError("Add Node before adding Edge")
4852
self._edges.append(edge)
4953
self.append_type = Node
5054
return self

redisgraph/query_result.py

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from .exceptions import VersionMismatchException
55
from prettytable import PrettyTable
66
from redis import ResponseError
7+
from collections import OrderedDict
78

89
LABELS_ADDED = 'Labels added'
910
NODES_CREATED = 'Nodes created'
@@ -17,15 +18,17 @@
1718
INTERNAL_EXECUTION_TIME = 'internal execution time'
1819

1920
STATS = [LABELS_ADDED, NODES_CREATED, PROPERTIES_SET, RELATIONSHIPS_CREATED,
20-
NODES_DELETED, RELATIONSHIPS_DELETED, INDICES_CREATED, INDICES_DELETED,
21-
CACHED_EXECUTION, INTERNAL_EXECUTION_TIME]
21+
NODES_DELETED, RELATIONSHIPS_DELETED, INDICES_CREATED, INDICES_DELETED,
22+
CACHED_EXECUTION, INTERNAL_EXECUTION_TIME]
23+
2224

2325
class ResultSetColumnTypes:
2426
COLUMN_UNKNOWN = 0
2527
COLUMN_SCALAR = 1
2628
COLUMN_NODE = 2 # Unused as of RedisGraph v2.1.0, retained for backwards compatibility.
2729
COLUMN_RELATION = 3 # Unused as of RedisGraph v2.1.0, retained for backwards compatibility.
2830

31+
2932
class ResultSetScalarTypes:
3033
VALUE_UNKNOWN = 0
3134
VALUE_NULL = 1
@@ -37,6 +40,9 @@ class ResultSetScalarTypes:
3740
VALUE_EDGE = 7
3841
VALUE_NODE = 8
3942
VALUE_PATH = 9
43+
VALUE_MAP = 10
44+
VALUE_POINT = 11
45+
4046

4147
class QueryResult:
4248

@@ -122,6 +128,14 @@ def parse_entity_properties(self, props):
122128

123129
return properties
124130

131+
def parse_string(self, cell):
132+
if isinstance(cell, bytes):
133+
return cell.decode()
134+
elif not isinstance(cell, str):
135+
return str(cell)
136+
else:
137+
return cell
138+
125139
def parse_node(self, cell):
126140
# Node ID (integer),
127141
# [label string offset (integer)],
@@ -153,6 +167,27 @@ def parse_path(self, cell):
153167
edges = self.parse_scalar(cell[1])
154168
return Path(nodes, edges)
155169

170+
def parse_map(self, cell):
171+
m = OrderedDict()
172+
n_entries = len(cell)
173+
174+
# A map is an array of key value pairs.
175+
# 1. key (string)
176+
# 2. array: (value type, value)
177+
for i in range(0, n_entries, 2):
178+
key = self.parse_string(cell[i])
179+
m[key] = self.parse_scalar(cell[i+1])
180+
181+
return m
182+
183+
def parse_point(self, cell):
184+
p = {}
185+
# A point is received an array of the form: [latitude, longitude]
186+
# It is returned as a map of the form: {"latitude": latitude, "longitude": longitude}
187+
p["latitude"] = float(cell[0])
188+
p["longitude"] = float(cell[1])
189+
return p
190+
156191
def parse_scalar(self, cell):
157192
scalar_type = int(cell[0])
158193
value = cell[1]
@@ -162,12 +197,7 @@ def parse_scalar(self, cell):
162197
scalar = None
163198

164199
elif scalar_type == ResultSetScalarTypes.VALUE_STRING:
165-
if isinstance(value, bytes):
166-
scalar = value.decode()
167-
elif not isinstance(value, str):
168-
scalar = str(value)
169-
else:
170-
scalar = value
200+
scalar = self.parse_string(value)
171201

172202
elif scalar_type == ResultSetScalarTypes.VALUE_INTEGER:
173203
scalar = int(value)
@@ -199,6 +229,12 @@ def parse_scalar(self, cell):
199229
elif scalar_type == ResultSetScalarTypes.VALUE_PATH:
200230
scalar = self.parse_path(value)
201231

232+
elif scalar_type == ResultSetScalarTypes.VALUE_MAP:
233+
scalar = self.parse_map(value)
234+
235+
elif scalar_type == ResultSetScalarTypes.VALUE_POINT:
236+
scalar = self.parse_point(value)
237+
202238
elif scalar_type == ResultSetScalarTypes.VALUE_UNKNOWN:
203239
print("Unknown scalar type\n")
204240

@@ -288,4 +324,3 @@ def cached_execution(self):
288324
@property
289325
def run_time_ms(self):
290326
return self._get_stat(INTERNAL_EXECUTION_TIME)
291-

redisgraph/util.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ def quote_string(v):
2525
if len(v) == 0:
2626
return '""'
2727

28+
v = v.replace('"', '\\"')
29+
2830
if v[0] != '"':
2931
v = '"' + v
3032

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def read_all(f):
1111

1212
setup(
1313
name='redisgraph',
14-
version='2.2.3',
14+
version='2.3.0',
1515
description='RedisGraph Python Client',
1616
long_description=read_all("README.md"),
1717
long_description_content_type='text/markdown',

test-requirements.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
flake8
2+
3+
pytest
4+
pytest-cov
5+
pytest-html
6+
7+
testtools>=1.4.0
8+
mock
9+
10+
codecov

0 commit comments

Comments
 (0)