Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Math expressions in variables, single source of truth for version, support using xmltodict in test scripts #339

Merged
merged 2 commits into from
Nov 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"})
Loading