Skip to content

Commit d66f503

Browse files
committed
Support for negative literal numbers added
1 parent 0a7ebec commit d66f503

File tree

4 files changed

+229
-27
lines changed

4 files changed

+229
-27
lines changed

mock_generators/logic/generate_values.py

+54-21
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from models.generator import Generator, GeneratorType
33
import logging
44
import json
5+
import re
56

67
# ORIGINAL GENERATOR ASSIGNMENT
78
def actual_generator_for_raw_property(
@@ -94,11 +95,39 @@ def is_int(value: str) -> bool:
9495

9596
def is_float(value: str) -> bool:
9697
try:
97-
f = float(value)
98+
_ = float(value)
9899
return True
99100
except ValueError:
100101
return False
101102

103+
def find_longest_float_precision(float_list):
104+
max_precision = 0
105+
for num in float_list:
106+
num_str = str(num)
107+
if '.' in num_str:
108+
decimal_part = num_str.split('.')[1]
109+
precision = len(decimal_part)
110+
max_precision = max(max_precision, precision)
111+
return max_precision
112+
113+
def extract_numbers(string):
114+
# Use regex to find all number patterns in the string
115+
number_list = re.findall(r"-?\d+(?:\.\d+)?", string)
116+
117+
# Convert the extracted strings to appropriate number types
118+
number_list = [int(num) if num.isdigit() else float(num) for num in number_list]
119+
120+
return number_list
121+
122+
def numbers_list_from(string):
123+
# numbers = []
124+
# ranges = string.split('-')
125+
# for r in ranges:
126+
# numbers.extend(extract_numbers(r))
127+
# return numbers
128+
options = re.split(r'(?<=[^-])-', string)
129+
return options
130+
102131
def literal_generator_from_value(
103132
value: str,
104133
generators: list[Generator]
@@ -134,9 +163,10 @@ def literal_generator_from_value(
134163
# Original specificaion took stringified JSON objects to notate generator and args to use. We're going to convert matching literal values to appropriate generators
135164

136165
# Default is to use the literal generator
137-
result = {
138-
"string": [value]
139-
}
166+
result = None
167+
# result = {
168+
# "string": [value]
169+
# }
140170

141171
# Check if value is an int or float
142172
if is_int(value):
@@ -145,32 +175,31 @@ def literal_generator_from_value(
145175
"int": [integer]
146176
}
147177

148-
if is_float(value):
178+
if result is None and is_float(value):
149179
f = float(value)
150180
result = {
151181
"float": [f]
152182
}
153183

154184
# NOTE: Not currently handling complex literals
155185

156-
# Check if value is a range of ints or floats
157-
r = value.split("-")
158-
if len(r) == 2:
159-
# Single dash in string, possibly a literal range
160-
values = [r[0], r[1]]
161-
if all_ints(values):
162-
result = {
163-
"int_range": [int(r[0]), int(r[1])]
164-
}
165-
elif some_floats(values):
166-
# Float range function expects 3 args - this one seems more sensible than other functions
167-
result = {
168-
"float_range": [float(r[0]), float(r[1]), 2]
169-
}
170-
186+
# Check if value is a range of positive ints or floats
187+
if result is None:
188+
numbers = numbers_list_from(value)
189+
if len(numbers) == 2:
190+
# Check for correctly formatted int or float range string
191+
precision = find_longest_float_precision(numbers)
192+
if precision == 0:
193+
result = {
194+
"int_range": [int(numbers[0]), int(numbers[1])]
195+
}
196+
else:
197+
result = {
198+
"float_range": [float(numbers[0]), float(numbers[1]), precision]
199+
}
171200

172201
# Check for literal list of ints, floats, or strings
173-
if value.startswith('[') and value.endswith(']'):
202+
if result is None and value.startswith('[') and value.endswith(']'):
174203
values = value[1:-1].split(',')
175204
# Generators take a strange format where the args are always a string - including # lists of other data, like ints, floats. ie ["1,2,3"] is an expected arg type
176205
# because certain generators could take multiple args from different text fields
@@ -193,6 +222,10 @@ def literal_generator_from_value(
193222
"string_from_list": values
194223
}
195224

225+
if result is None:
226+
result = {
227+
"string": [value]
228+
}
196229
# Package and return from legacy process
197230
actual_string = json.dumps(result)
198231
return actual_generator_for_raw_property(actual_string, generators)

setup.py

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Run `pipenv install -e . --dev` at least once so that pytest can import all files for testing
2+
from setuptools import find_packages, setup
3+
4+
setup(
5+
name="mock_generators",
6+
package_dir={'': 'mock_generators'},
7+
packages=find_packages(where='mock_generators'),
8+
)

tests/test_agraph_conversions.py

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import pytest
2+
from mock_generators.logic.agraph_conversions import convert_agraph_to_arrows, agraph_data_from_response, convert_agraph_nodes_to_arrows_nodes,convert_agraph_node_to_arrows_node, convert_agraph_edge_to_arrows_relationship,random_coordinates_for
3+
4+
from streamlit_agraph import agraph, Node, Edge, Config
5+
6+
class TestAgraphConversions:
7+
8+
def test_agrph_data_from_response(self):
9+
response = """[
10+
[
11+
"Alice",
12+
"ROOMMATE",
13+
"Bob"
14+
],
15+
[
16+
"Bob",
17+
"FRIEND_OF",
18+
"Charlie"
19+
]
20+
]
21+
"""
22+
nodes, edges, config = agraph_data_from_response(response)
23+
print(f'{[node.__dict__ for node in nodes]}')
24+
assert len(nodes) == 3, f'Expected 3 nodes, got {len(nodes)}: nodes: {[node.__dict__ for node in nodes]}'
25+
assert len(edges) == 2, f'Expected 2 edges, got {len(edges)}: edges: {edges}'
26+
27+
print(f'config: {config.__dict__}')
28+
assert config.width == "800px", f'Expected default config, got {config.__dict__}'
29+
assert config.height == "800px", f'Expected default config, got {config.__dict__}'
30+
assert config.backgroundColor == "#000000", f'Expected default config, got {config.__dict__}'
31+
32+
def test_convert_agraph_edge_to_arrows_relationship(self):
33+
nodes = set()
34+
nodes.add(Node(id="n1", label="Alice"))
35+
nodes.add(Node(id="n2", label="Bob"))
36+
edge = Edge(source="Alice", target="Bob", label="FRIEND_OF")
37+
38+
print(f'nodes: {[node.__dict__ for node in nodes]}')
39+
print(f'edge: {edge.__dict__}')
40+
41+
arrows_nodes = convert_agraph_nodes_to_arrows_nodes(nodes)
42+
arrows_edge = convert_agraph_edge_to_arrows_relationship(0, edge, arrows_nodes)
43+
44+
# NOTE: The edge may reverse the from and to ids
45+
print(f'arrows edge: {arrows_edge}')
46+
assert arrows_edge == {
47+
"id": "n1",
48+
"type": "FRIEND_OF",
49+
"fromId": "n1",
50+
"toId": "n2",
51+
"properties": {},
52+
"style": {}
53+
} or {
54+
"id": "n1",
55+
"type": "FRIEND_OF",
56+
"fromId": "n2",
57+
"toId": "n1",
58+
"properties": {},
59+
"style": {}
60+
}
61+
62+
def test_convert_agraph_node_to_arrows_node(self):
63+
node = (Node(id="n1", label="Alice"))
64+
print(f'agraph_node: {node.__dict__}')
65+
coordinates = random_coordinates_for(1)
66+
arrows_node = convert_agraph_node_to_arrows_node(0, node, coordinates[0][0], coordinates[0][1])
67+
print(f'arrows_node: {arrows_node}')
68+
assert arrows_node == {
69+
"id": "n1",
70+
"caption": "Alice",
71+
"position": {
72+
"x": coordinates[0][0],
73+
"y": coordinates[0][1],
74+
},
75+
"labels": [],
76+
"properties": {},
77+
"style": {}
78+
}

tests/test_generate_values.py

+89-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import pytest
22
from mock_generators.config import load_generators
3-
from mock_generators.logic.generate_values import literal_generator_from_value, actual_generator_for_raw_property, generator_for_raw_property, keyword_generator_for_raw_property
3+
from mock_generators.logic.generate_values import literal_generator_from_value, actual_generator_for_raw_property, generator_for_raw_property, keyword_generator_for_raw_property, find_longest_float_precision
44

55

66
test_generators = load_generators("mock_generators/named_generators.json")
@@ -85,6 +85,21 @@ def test_integer_list_multi(self):
8585
except Exception as e:
8686
assert False, f'Exception: {e}'
8787

88+
class TestLiteralSupport:
89+
def test_find_longest_float_percision(self):
90+
try:
91+
test_floats = [1.01, 2.002, 3.004]
92+
precision = find_longest_float_precision(test_floats)
93+
assert precision == 3
94+
except Exception as e:
95+
assert False, f'Exception: {e}'
96+
97+
try:
98+
test_floats = [1, -2]
99+
precision = find_longest_float_precision(test_floats)
100+
assert precision == 0
101+
except Exception as e:
102+
assert False, f'Exception: {e}'
88103

89104
class TestLiteralGenerators:
90105
def test_integer(self):
@@ -102,14 +117,21 @@ def test_integer(self):
102117
def test_float(self):
103118
try:
104119
test_string = "1.0"
105-
# This should be equivalent to the integer generator with arg of [1]
106120
generator, args = literal_generator_from_value(test_string, test_generators)
107-
# Test generator returned creates acceptable value
108121
value = generator.generate(args)
109122
assert value == 1.0
110123
except Exception as e:
111124
assert False, f'Exception: {e}'
112125

126+
def test_negative_float(self):
127+
try:
128+
test_string = "-1.0"
129+
generator, args = literal_generator_from_value(test_string, test_generators)
130+
value = generator.generate(args)
131+
assert value == -1.0
132+
except Exception as e:
133+
assert False, f'Exception: {e}'
134+
113135

114136
def test_int_range(self):
115137
try:
@@ -126,16 +148,77 @@ def test_int_range(self):
126148
def test_float_range(self):
127149
try:
128150
test_string = "1.0-2"
129-
# This should be equivalent to the integer generator with arg of [1]
130151
generator, args = literal_generator_from_value(test_string, test_generators)
131-
# Test generator returned creates acceptable value
132152
value = generator.generate(args)
133153
assert value <= 2.0
134154
assert value >= 1.0
135155
except Exception as e:
136156
assert False, f'Exception: {e}'
157+
try:
158+
test_string = "1-2.0"
159+
generator, args = literal_generator_from_value(test_string, test_generators)
160+
value = generator.generate(args)
161+
assert value <= 2.0
162+
assert value >= 1.0
163+
except Exception as e:
164+
assert False, f'Exception: {e}'
165+
try:
166+
test_string = "1.0-2.0"
167+
generator, args = literal_generator_from_value(test_string, test_generators)
168+
value = generator.generate(args)
169+
assert value <= 2.0
170+
assert value >= 1.0
171+
except Exception as e:
172+
assert False, f'Exception: {e}'
173+
try:
174+
test_string = "1--2.02"
175+
generator, args = literal_generator_from_value(test_string, test_generators)
176+
value = generator.generate(args)
177+
assert value <= 1.00
178+
assert value >= -2.01
179+
except Exception as e:
180+
assert False, f'Exception: {e}'
181+
try:
182+
test_string = "1.01--2.02"
183+
generator, args = literal_generator_from_value(test_string, test_generators)
184+
value = generator.generate(args)
185+
assert value <= 1.01
186+
assert value >= -2.01
187+
except Exception as e:
188+
assert False, f'Exception: {e}'
137189

138-
190+
try:
191+
test_string = "-10.0003-20"
192+
generator, args = literal_generator_from_value(test_string, test_generators)
193+
value = generator.generate(args)
194+
assert value <= 20.0000
195+
assert value >= -10.0003
196+
except Exception as e:
197+
assert False, f'Exception: {e}'
198+
try:
199+
test_string = "-10.01-20.02"
200+
generator, args = literal_generator_from_value(test_string, test_generators)
201+
value = generator.generate(args)
202+
assert value <= 20.02
203+
assert value >= -10.01
204+
except Exception as e:
205+
assert False, f'Exception: {e}'
206+
try:
207+
test_string = "-73--74.004"
208+
generator, args = literal_generator_from_value(test_string, test_generators)
209+
value = generator.generate(args)
210+
assert value <= -73.000
211+
assert value >= -74.004
212+
except Exception as e:
213+
assert False, f'Exception: {e}'
214+
try:
215+
test_string = "-73.979--74.004"
216+
generator, args = literal_generator_from_value(test_string, test_generators)
217+
value = generator.generate(args)
218+
assert value <= -73.979
219+
assert value >= -74.004
220+
except Exception as e:
221+
assert False, f'Exception: {e}'
139222
def test_int_list(self):
140223
try:
141224
test_string = "[1,2,3]"

0 commit comments

Comments
 (0)