Skip to content

Commit 40d9217

Browse files
author
Abhilash Joseph C
committed
HTML Report Fix
1 parent 95efa5e commit 40d9217

File tree

5 files changed

+316
-14
lines changed

5 files changed

+316
-14
lines changed

resttest3/binding.py

+10-9
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
1+
"""Basic context implementation for binding variables to values
2+
"""
13
import logging
24
import types
35

4-
"""
5-
Basic context implementation for binding variables to values
6-
"""
7-
86
logger = logging.getLogger('resttest3')
97

10-
118
class Context:
129
""" Manages binding of variables & generators, with both variable name and generator name being strings """
1310

@@ -26,9 +23,10 @@ def bind_variable(self, variable_name, variable_value):
2623
if prev != variable_value:
2724
self.variables[str(variable_name)] = variable_value
2825
self.mod_count = self.mod_count + 1
29-
logger.info('Context: altered variable named {0} to value {1}'.format(str_name, variable_value))
26+
logger.info('Context: altered variable named %s to value %s', str_name, variable_value)
3027

3128
def bind_variables(self, variable_map):
29+
"""bind variable for the key """
3230
for key, value in variable_map.items():
3331
self.bind_variable(key, value)
3432

@@ -41,7 +39,7 @@ def add_generator(self, generator_name, generator):
4139
'Cannot add generator named {0}, it is not a generator type'.format(generator_name))
4240

4341
self.generators[str(generator_name)] = generator
44-
logging.debug('Context: Added generator named {0}'.format(generator_name))
42+
logging.debug('Context: Added generator named %s', generator_name)
4543

4644
def bind_generator_next(self, variable_name, generator_name):
4745
""" Binds the next value for generator_name to variable_name and return value used """
@@ -54,19 +52,22 @@ def bind_generator_next(self, variable_name, generator_name):
5452
self.variables[str_name] = val
5553
self.mod_count = self.mod_count + 1
5654
logging.debug(
57-
'Context: Set variable named {0} to next value {1} from generator named {2}'.format(variable_name, val,
58-
generator_name))
55+
'Context: Set variable named %s to next value '
56+
'%s from generator named %s', variable_name, val, generator_name)
5957
return val
6058

6159
def get_values(self):
60+
"""Return the values can bind to a key """
6261
return self.variables
6362

6463
def get_value(self, variable_name):
6564
""" Get bound variable value, or return none if not set """
6665
return self.variables.get(str(variable_name))
6766

6867
def get_generators(self):
68+
"""return all generators defined """
6969
return self.generators
7070

7171
def get_generator(self, generator_name):
72+
"""return generators for the given name"""
7273
return self.generators.get(str(generator_name))

resttest3/ext/extractor_jmespath.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
"""JMESPathExtractor file"""
12
import json
23

34
import jmespath
@@ -12,18 +13,19 @@ class JMESPathExtractor(AbstractExtractor):
1213
extractor_type = 'jmespath'
1314
is_body_extractor = True
1415

15-
def extract_internal(self, query=None, args=None, body=None, headers=None):
16+
def extract_internal(self, query=None, body=None, headers=None, args=None):
1617
if isinstance(body, bytes):
1718
body = body.decode('utf-8')
1819

1920
try:
2021
res = jmespath.search(query, json.loads(body))
2122
return res
22-
except Exception as e:
23-
raise ValueError("Invalid query: " + query + " : " + str(e))
23+
except Exception as Exe:
24+
raise ValueError("Invalid query: " + query + " : " + str(Exe)) from Exe
2425

2526
@classmethod
2627
def parse(cls, config):
28+
"""Parse the JMESPathExtractor config dict"""
2729
base = JMESPathExtractor()
2830
return cls.configure_base(config, base)
2931

resttest3/reports/__init__.py

Whitespace-only changes.

resttest3/reports/templite.py

+299
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2+
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
3+
4+
"""A simple Python template renderer, for a nano-subset of Django syntax.
5+
6+
For a detailed discussion of this code, see this chapter from 500 Lines:
7+
http://aosabook.org/en/500L/a-template-engine.html
8+
9+
"""
10+
11+
# Coincidentally named the same as http://code.activestate.com/recipes/496702/
12+
13+
import re
14+
15+
16+
class TempliteSyntaxError(ValueError):
17+
"""Raised when a template has a syntax error."""
18+
19+
20+
class TempliteValueError(ValueError):
21+
"""Raised when an expression won't evaluate in a template."""
22+
23+
24+
class CodeBuilder:
25+
"""Build source code conveniently."""
26+
27+
def __init__(self, indent=0):
28+
self.code = []
29+
self.indent_level = indent
30+
31+
def __str__(self):
32+
return "".join(str(c) for c in self.code)
33+
34+
def add_line(self, line):
35+
"""Add a line of source to the code.
36+
37+
Indentation and newline will be added for you, don't provide them.
38+
39+
"""
40+
self.code.extend([" " * self.indent_level, line, "\n"])
41+
42+
def add_section(self):
43+
"""Add a section, a sub-CodeBuilder."""
44+
section = CodeBuilder(self.indent_level)
45+
self.code.append(section)
46+
return section
47+
48+
INDENT_STEP = 4 # PEP8 says so!
49+
50+
def indent(self):
51+
"""Increase the current indent for following lines."""
52+
self.indent_level += self.INDENT_STEP
53+
54+
def dedent(self):
55+
"""Decrease the current indent for following lines."""
56+
self.indent_level -= self.INDENT_STEP
57+
58+
def get_globals(self):
59+
"""Execute the code, and return a dict of globals it defines."""
60+
# A check that the caller really finished all the blocks they started.
61+
assert self.indent_level == 0
62+
# Get the Python source as a single string.
63+
python_source = str(self)
64+
# Execute the source, defining globals, and return them.
65+
global_namespace = {}
66+
exec(python_source, global_namespace)
67+
return global_namespace
68+
69+
70+
class Templite:
71+
"""A simple template renderer, for a nano-subset of Django syntax.
72+
73+
Supported constructs are extended variable access::
74+
75+
{{var.modifier.modifier|filter|filter}}
76+
77+
loops::
78+
79+
{% for var in list %}...{% endfor %}
80+
81+
and ifs::
82+
83+
{% if var %}...{% endif %}
84+
85+
Comments are within curly-hash markers::
86+
87+
{# This will be ignored #}
88+
89+
Lines between `{% joined %}` and `{% endjoined %}` will have lines stripped
90+
and joined. Be careful, this could join words together!
91+
92+
Any of these constructs can have a hyphen at the end (`-}}`, `-%}`, `-#}`),
93+
which will collapse the whitespace following the tag.
94+
95+
Construct a Templite with the template text, then use `render` against a
96+
dictionary context to create a finished string::
97+
98+
templite = Templite('''
99+
<h1>Hello {{name|upper}}!</h1>
100+
{% for topic in topics %}
101+
<p>You are interested in {{topic}}.</p>
102+
{% endif %}
103+
''',
104+
{'upper': str.upper},
105+
)
106+
text = templite.render({
107+
'name': "Ned",
108+
'topics': ['Python', 'Geometry', 'Juggling'],
109+
})
110+
111+
"""
112+
113+
def __init__(self, text, *contexts):
114+
"""Construct a Templite with the given `text`.
115+
116+
`contexts` are dictionaries of values to use for future renderings.
117+
These are good for filters and global values.
118+
119+
"""
120+
self.context = {}
121+
for context in contexts:
122+
self.context.update(context)
123+
124+
self.all_vars = set()
125+
self.loop_vars = set()
126+
127+
# We construct a function in source form, then compile it and hold onto
128+
# it, and execute it to render the template.
129+
code = CodeBuilder()
130+
131+
code.add_line("def render_function(context, do_dots):")
132+
code.indent()
133+
vars_code = code.add_section()
134+
code.add_line("result = []")
135+
code.add_line("append_result = result.append")
136+
code.add_line("extend_result = result.extend")
137+
code.add_line("to_str = str")
138+
139+
buffered = []
140+
141+
def flush_output():
142+
"""Force `buffered` to the code builder."""
143+
if len(buffered) == 1:
144+
code.add_line("append_result(%s)" % buffered[0])
145+
elif len(buffered) > 1:
146+
code.add_line("extend_result([%s])" % ", ".join(buffered))
147+
del buffered[:]
148+
149+
ops_stack = []
150+
151+
# Split the text to form a list of tokens.
152+
tokens = re.split(r"(?s)({{.*?}}|{%.*?%}|{#.*?#})", text)
153+
154+
squash = in_joined = False
155+
156+
self.__process(buffered, code, flush_output, in_joined, ops_stack, squash, tokens)
157+
158+
if ops_stack:
159+
self._syntax_error("Unmatched action tag", ops_stack[-1])
160+
161+
flush_output()
162+
163+
for var_name in self.all_vars - self.loop_vars:
164+
vars_code.add_line("c_%s = context[%r]" % (var_name, var_name))
165+
166+
code.add_line('return "".join(result)')
167+
code.dedent()
168+
self._render_function = code.get_globals()['render_function']
169+
170+
def __process(self, buffered, code, flush_output, in_joined, ops_stack, squash, tokens):
171+
for token in tokens:
172+
if token.startswith('{'):
173+
start, end = 2, -2
174+
squash = (token[-3] == '-')
175+
if squash:
176+
end = -3
177+
178+
if token.startswith('{#'):
179+
# Comment: ignore it and move on.
180+
continue
181+
if token.startswith('{{'):
182+
# An expression to evaluate.
183+
expr = self._expr_code(token[start:end].strip())
184+
buffered.append("to_str(%s)" % expr)
185+
else:
186+
# token.startswith('{%')
187+
# Action tag: split into words and parse further.
188+
flush_output()
189+
190+
words = token[start:end].strip().split()
191+
if words[0] == 'if':
192+
# An if statement: evaluate the expression to determine if.
193+
if len(words) != 2:
194+
self._syntax_error("Don't understand if", token)
195+
ops_stack.append('if')
196+
code.add_line("if %s:" % self._expr_code(words[1]))
197+
code.indent()
198+
elif words[0] == 'for':
199+
# A loop: iterate over expression result.
200+
if len(words) != 4 or words[2] != 'in':
201+
self._syntax_error("Don't understand for", token)
202+
ops_stack.append('for')
203+
self._variable(words[1], self.loop_vars)
204+
code.add_line(
205+
"for c_%s in %s:" % (
206+
words[1],
207+
self._expr_code(words[3])
208+
)
209+
)
210+
code.indent()
211+
elif words[0] == 'joined':
212+
ops_stack.append('joined')
213+
in_joined = True
214+
elif words[0].startswith('end'):
215+
# Endsomething. Pop the ops stack.
216+
if len(words) != 1:
217+
self._syntax_error("Don't understand end", token)
218+
end_what = words[0][3:]
219+
if not ops_stack:
220+
self._syntax_error("Too many ends", token)
221+
start_what = ops_stack.pop()
222+
if start_what != end_what:
223+
self._syntax_error("Mismatched end tag", end_what)
224+
if end_what == 'joined':
225+
in_joined = False
226+
else:
227+
code.dedent()
228+
else:
229+
self._syntax_error("Don't understand tag", words[0])
230+
else:
231+
# Literal content. If it isn't empty, output it.
232+
if in_joined:
233+
token = re.sub(r"\s*\n\s*", "", token.strip())
234+
elif squash:
235+
token = token.lstrip()
236+
if token:
237+
buffered.append(repr(token))
238+
239+
def _expr_code(self, expr):
240+
"""Generate a Python expression for `expr`."""
241+
if "|" in expr:
242+
pipes = expr.split("|")
243+
code = self._expr_code(pipes[0])
244+
for func in pipes[1:]:
245+
self._variable(func, self.all_vars)
246+
code = "c_%s(%s)" % (func, code)
247+
elif "." in expr:
248+
dots = expr.split(".")
249+
code = self._expr_code(dots[0])
250+
args = ", ".join(repr(d) for d in dots[1:])
251+
code = "do_dots(%s, %s)" % (code, args)
252+
else:
253+
self._variable(expr, self.all_vars)
254+
code = "c_%s" % expr
255+
return code
256+
257+
def _syntax_error(self, msg, thing):
258+
"""Raise a syntax error using `msg`, and showing `thing`."""
259+
raise TempliteSyntaxError("%s: %r" % (msg, thing))
260+
261+
def _variable(self, name, vars_set):
262+
"""Track that `name` is used as a variable.
263+
264+
Adds the name to `vars_set`, a set of variable names.
265+
266+
Raises an syntax error if `name` is not a valid name.
267+
268+
"""
269+
if not re.match(r"[_a-zA-Z][_a-zA-Z0-9]*$", name):
270+
self._syntax_error("Not a valid name", name)
271+
vars_set.add(name)
272+
273+
def render(self, context=None):
274+
"""Render this template by applying it to `context`.
275+
276+
`context` is a dictionary of values to use in this rendering.
277+
278+
"""
279+
# Make the complete context we'll use.
280+
render_context = dict(self.context)
281+
if context:
282+
render_context.update(context)
283+
return self._render_function(render_context, self._do_dots)
284+
285+
def _do_dots(self, value, *dots):
286+
"""Evaluate dotted expressions at run-time."""
287+
for dot in dots:
288+
try:
289+
value = getattr(value, dot)
290+
except AttributeError:
291+
try:
292+
value = value[dot]
293+
except (TypeError, KeyError) as e:
294+
raise TempliteValueError(
295+
"Couldn't evaluate %r.%s" % (value, dot)
296+
) from e
297+
if callable(value):
298+
value = value()
299+
return value

0 commit comments

Comments
 (0)