Skip to content

Commit

Permalink
Support Math expressions in variables, single source of truth for ver…
Browse files Browse the repository at this point in the history
…sion, support using xmltodict in test scripts (#339)

* Fix #332 and #315
  • Loading branch information
cedric05 authored Nov 24, 2024
1 parent 45d8a27 commit 7a473af
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 130 deletions.
2 changes: 1 addition & 1 deletion benchmarks/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
pyperf==2.8.0
pyperf==2.8.1
pytest-benchmark
8 changes: 7 additions & 1 deletion dothttp/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
__version__ = "0.0.43a27"
import importlib.metadata
module_name = "dothttp-req"
try:
__version__ = importlib.metadata.version(module_name)
except:
# to support testing
__version__ = "0.0.0"
2 changes: 2 additions & 0 deletions dothttp/script/js3py.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from operator import getitem

import cryptography
import xmltodict
import jsonschema
import requests
import yaml
Expand Down Expand Up @@ -74,6 +75,7 @@ def write_guard(x):
"cryptography": cryptography,
"jsonschema": jsonschema,
"requests": requests,
"xmltodict": xmltodict,
}
allowed_global.update(safe_globals)

Expand Down
33 changes: 30 additions & 3 deletions dothttp/utils/property_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
from typing import Dict, List, Union

from ..exceptions import HttpFileException, PropertyNotFoundException
import ast
import operator

base_logger = logging.getLogger("dothttp")

Expand Down Expand Up @@ -89,6 +91,25 @@ def get_random_slug(length):
def get_timestamp(*_args):
return int(datetime.timestamp(datetime.now()))

math_operators = {
'+': operator.add,
'-': operator.sub,
'*': operator.mul,
'/': operator.truediv,
'%': operator.mod,
'**': operator.pow
}

def evaluate_expression(expression: str) -> str:
try:
tree = ast.parse(expression, mode='eval')
code = compile(tree, '<string>', 'eval')
result = eval(code, {"__builtins__": None}, math_operators)
return str(result)
except Exception as e:
base_logger.error(f"Error evaluating expression `{expression}`: {e}")
return expression


class PropertyProvider:
"""
Expand Down Expand Up @@ -126,6 +147,8 @@ class PropertyProvider:
+ ")(?P<length>:\\d*)?"
)

expression_regex = re.compile(r"\$expr:(?P<expression>.*)")

def __init__(self, property_file=""):
self.command_line_properties = {}
self.env_properties = {}
Expand Down Expand Up @@ -213,7 +236,7 @@ def is_special_keyword(key):
key.startswith(rand_category_name)
for rand_category_name in PropertyProvider.rand_map
)
return ret
return ret or key.startswith("$expr:")

def get_updated_content(self, content, type="str"):
content_prop_needed, props_needed = self.check_properties_for_content(
Expand Down Expand Up @@ -251,7 +274,6 @@ def validate_n_gen(prop, cache: Dict[str, Property]):
# like ranga=" ramprasad" --> we should replace with "
# ramprasad"
value = value[1:-1]

match = PropertyProvider.get_random_match(value)
if match:
if key in cache:
Expand Down Expand Up @@ -321,8 +343,13 @@ def resolve_system_command_prop(self, key):

def resolve_property_string(self, key: str):
if PropertyProvider.is_special_keyword(key):
match = PropertyProvider.get_random_match(key)
if not match:
match = PropertyProvider.expression_regex.match(key)
if match:
return evaluate_expression(match.groupdict()["expression"])
return PropertyProvider.resolve_special(
key, PropertyProvider.get_random_match(key)
key, match
)

def find_according_to_category(key):
Expand Down
2 changes: 1 addition & 1 deletion dothttp_test/dothttp_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def test_httpdef(httpdeftest: HttpDefTest):

resp = comp.get_response()

logging.warning(f"resp={resp}")
logging.info(f"resp={resp}")

script_result = comp.script_execution.execute_test_script(resp)

Expand Down
251 changes: 131 additions & 120 deletions poetry.lock

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "dothttp-req"
version = "0.0.43a27"
version = "0.0.43a28"
description = "Dothttp is Simple http client for testing and development"
authors = ["Prasanth <[email protected]>"]
license = "MIT"
Expand Down Expand Up @@ -30,12 +30,13 @@ parsys-requests-unixsocket = "0.3.2"
requests-aws4auth = "1.3.1"
requests-ntlm = "1.3.0"
restrictedpython = "7.4"
faker = "30.8.0"
faker = "33.0.0"
requests-hawk = "1.2.1"
msal = "1.31.0"
msal = "1.31.1"
pyyaml = "6.0.2"
toml = "0.10.2"
requests = "2.32.3"
xmltodict = "^0.14.2"

[tool.poetry.group.dev.dependencies]
waitress = "3.0.0"
Expand Down
4 changes: 4 additions & 0 deletions test/core/substitution/math_expression.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
POST "https://req.dothttp.dev"
json({
"secondsInDay": "{{$expr:10*60*60*24}}"
})
Original file line number Diff line number Diff line change
Expand Up @@ -226,4 +226,11 @@ def test_substitution_from_env_variable(self):
f"{base_dir}//environmet_variable.http",
)
self.assertEqual(json.loads(req.body), {"sub": "env1"})
del os.environ["DOTHTTP_ENV_env"]
del os.environ["DOTHTTP_ENV_env"]


def test_math_expresssion_substitution(self):
req: PreparedRequest = self.get_request(
f"{base_dir}/math_expression.http",
)
self.assertEqual(json.loads(req.body), {"secondsInDay": "864000"})

0 comments on commit 7a473af

Please sign in to comment.