Best way to refactor nested 'with patch' statements - python

I am trying to patch out the Azure Digital Twin API in my code. Currently I have achieved a way which works but is probably not the most Pythonic by nesting with patch statements.
What is the best way to rewrite this such that I can use it in multiple test functions and change the return values if needed?
def test_create_digital_twin(self):
with patch("endpoints.digital_twin.ClientSecretCredential"):
with patch("azure_digital_twin.create_digital_twin.DigitalTwinsClient.query_twins",) as mock_query:
with patch("azure_digital_twin.create_digital_twin.DigitalTwinsClient.upsert_digital_twin") as mock_upsert_twin:
with patch("azure_digital_twin.create_digital_twin.DigitalTwinsClient.upsert_relationship") as mock_upsert_relationship:
mock_query.return_value = []
mock_upsert_twin.return_value = {
"$dtId":"spc-1",
"$etag":"random",
"$model":"dtmi:digitaltwins:rec_3_3:core:Asset;1"
}
mock_upsert_relationship.return_value = {
"$relationshipId":"spc-1-hasPart-spc-2",
"$sourceId":"spc-1",
"$targetId" : "spc-2",
"$relationshipName":"hasPart"
}
response = self.client.post(
endpoint,
params={"node" : "spc-1"},
)
assert response.status_code == status.HTTP_201_CREATED

You might use an ExitStack from the contextlib module.
from contextlib import ExitStack
def test_create_digital_twin(self):
with ExitStack() as es:
def make_azure_patch(x):
return es.enter_context(patch(f'azure_digital_twin.create_digital_twin.DigitalTwinsClient.{x}'))
es.enter_context("endpoints.digital_twin.ClientSecretCredential"))
mock_query = make_patch("query_twins")
mock_upsert_twin = make_patch("upsert_digital_twin")
mock_upsert_relationship = make_patch("upsert_relationship")
mock_query.return_value = []
mock_upsert_twin.return_value = {
"$dtId":"spc-1",
"$etag":"random",
"$model":"dtmi:digitaltwins:rec_3_3:core:Asset;1"
}
mock_upsert_relationship.return_value = {
"$relationshipId":"spc-1-hasPart-spc-2",
"$sourceId":"spc-1",
"$targetId" : "spc-2",
"$relationshipName":"hasPart"
}
response = self.client.post(
endpoint,
params={"node" : "spc-1"},
)
assert response.status_code == status.HTTP_201_CREATED
make_azure_patch is just a helper function to reduce the length of the lines creating three of the individual patches.

Related

Making dictionary more functional

how can i change the code, to make it more functional (change the for loop for the same result only to make it shorter in 2 sentences)?
def get_language_from_text():
"""
Simply return an array with the predicted languages.
"""
resultSet = []
inputdata = request.data
#print(inputdata)
inputdataparsed = json.loads(request.data)
array_of_sentences = inputdataparsed['inputdata']
for obj_in_array in array_of_sentences:
obj_in_array_tmp = obj_in_array
sentence = obj_in_array['TEXT']
obj_type = obj_in_array['TYPE']
obj_lang = obj_in_array['LANGUAGE']
prediction = detect(sentence)
result_to_safe = {"TEXT":sentence,
"TYPE": obj_type,
"LANGUAGE":obj_lang,
"PREDICTED_LANGUAGE": prediction}
resultSet.append(result_to_safe)
break
print(resultSet)
Your code is fine, it could use a bit of cleaning but that's okay.
You can shorten your loop to:
def make_dict(sentence_dict):
return {
"TEXT": sentence_dict["TEXT"],
"TYPE": sentence_dict["TYPE"],
"LANGUAGE": sentence_dict["LANGUAGE"],
"PREDICTED_LANGUAGE": detect(sentence_dict["TEXT"])
}
result_set = [ make_dict(sentence_dict) for sentence_dict in array_of_sentences ]
You can make this more functional by mapping make_dict over array_of_sentences as follows:
result_set = list(map(make_dict, array_of_sentences))

Python: dynamic json with string interpolation

I created a class of functions that provision some cloud infrastructure.
response = self.ecs_client.register_task_definition(
containerDefinitions=[
{
"name": "redis-283C462837EF23AA",
"image": "redis:3.2.7",
"cpu": 1,
"memory": 512,
"essential": True,
},
...
This is a very long json, I show just the beginning.
Then I refactored the code to use a parameter instead of the hard coded hash, memory and cpu.
response = self.ecs_client.register_task_definition(
containerDefinitions=[
{
"name": f"redis-{git_hash}",
"image": "redis:3.2.7",
"cpu": {num_cpu},
"memory": {memory_size},
"essential": True,
},
...
I read the values of git_hash, num_cpu and memory_size from a config file prior to this code.
Now, I also want to read to entire json from a file.
The problem is that if I save {num_cpu} etc. in a file, the string interpolation won't work.
How can I extract the json from my logic and still use string interpolation or variables?
You can use Template from string.
{
"name": "redis-${git_hash}",
"image": "redis:3.2.7",
"cpu": ${num_cpu},
"memory": ${memory_size},
"essential": true
}
from string import Template
import json
if __name__ == '__main__':
data = dict(
num_cpu = 1,
memory_size = 1,
git_hash = 1
)
with open('test.json', 'r') as json_file:
content = ''.join(json_file.readlines())
template = Template(content)
configuration = json.loads(template.substitute(data))
print(configuration)
# {'name': 'redis-1', 'image': 'redis:3.2.7', 'cpu': 1, 'memory': 1, 'essential': True}
Opinion: I think the overall approach is wrong. There is a reason why this method is not as popular as others. You can separate your configuration into two files (1) a static list of options and (2) your compact changeable configuration, and compose them in your code.
EDIT: You can create an object which reads the configuration from a standard (static or changeable) JSON file FileConfig. And then compose them using another object, something line ComposedConfig.
This will allow you to extend the behaviour, and add, for example, a run-time configuration in the mix. This way the configuration from your JSON file no longer depends on the run-time params, and you can separate what is changeable from what is static in your system.
PS: The get method is just an example for explaining the composed behaviour; you can use other methods/designs.
import json
from abc import ABC, abstractmethod
class Configuration(ABC):
#abstractmethod
def get(self, key: str, default: str) -> str:
pass
class FileConfig(Configuration):
def __init__(self, file_path):
self.__content = {}
with open(file_path, 'r') as json_file:
self.__content = json.load(json_file)
def get(self, key: str, default: str) -> str:
return self.__content.get(key, default)
class RunTimeConfig(Configuration):
def __init__(self, option: str):
self.__content = {'option': option}
def get(self, key: str, default: str) -> str:
return self.__content.get(key, default)
class ComposedConfig:
def __init__(self, first: Configuration, second: Configuration):
self.__first = first
self.__second = second
def get(self, key: str, default: str) -> str:
return self.__first.get(key, self.__second.get(key, default))
if __name__ == '__main__':
static = FileConfig("static.json")
changeable = FileConfig("changeable.json")
runTime = RunTimeConfig(option="a")
config = ComposedConfig(static, changeable)
alternative = ComposedConfig(static, runTime)
print(config.get("image", "test")) # redis:3.2.7
print(alternative.get("option", "test")) # a

py.test: How to generate tests from fixtures

I'm writing a set of tools to test the behavior of a custom HTTP server: whether it is setting appropriate response codes, header fields etc. I'm using pytest to write tests.
The goal is to make requests to several resources, and then evaluate the response in multiple tests: each test should test a single aspect of the HTTP response. However, not every response is tested with every test and vice-versa.
To avoid sending the same HTTP request multiple time and reuse HTTP responses messages, I'm thinking of using pytest's fixtures, and to run the same tests on different HTTP responses I'd like to use pytest's generate test capabilities.
import pytest
import requests
def pytest_generate_tests(metafunc):
funcarglist = metafunc.cls.params[metafunc.function.__name__]
argnames = sorted(funcarglist[0])
metafunc.parametrize(argnames, [[funcargs[name] for name in argnames]
for funcargs in funcarglist])
class TestHTTP(object):
#pytest.fixture(scope="class")
def get_root(self, request):
return requests.get("http://test.com")
#pytest.fixture(scope="class")
def get_missing(self, request):
return requests.get("http://test.com/not-there")
def test_status_code(self, response, code):
assert response.status_code == code
def test_header_value(self, response, field, value):
assert response.headers[field] == value
params = {
'test_status_code': [dict(response=get_root, code=200),
dict(response=get_missing, code=404), ],
'test_header_value': [dict(response=get_root, field="content-type", value="text/html"),
dict(response=get_missing, field="content-type", value="text/html"), ],
}
The problem appears to be in defining params: dict(response=get_root, code=200) and similar definitions do not realize, I'd like to bind on the fixture and on on the actual function reference.
When running tests, I get this kinds of errors:
________________________________________________ TestHTTP.test_header_value[content-type-response0-text/html] _________________________________________________
self = <ev-question.TestHTTP object at 0x7fec8ce33d30>, response = <function TestHTTP.get_root at 0x7fec8ce8aa60>, field = 'content-type', value = 'text/html'
def test_header_value(self, response, field, value):
> assert response.headers[field] == value
E AttributeError: 'function' object has no attribute 'headers'
test_server.py:32: AttributeError
How may I convince the pytest to take the fixture value instead of the function?
No need to generate tests from fixtues, just parameterize your fixture and write regular tests for the values it returns:
import pytest
import requests
should_work = [
{
"url": "http://test.com",
"code": 200,
"fields": {"content-type": "text/html"}
},
]
should_fail = [
{
"url": "http://test.com/not-there",
"code": 404,
"fields": {"content-type": "text/html"}
},
]
should_all = should_work + should_fail
def response(request):
retval = dict(request.param) # {"url": ..., "code": ... }
retval['response'] = requests.get(request.param['url'])
return retval # {"reponse": ..., "url": ..., "code": ... }
# One fixture for working requests
response_work = pytest.fixture(scope="module", params=should_work)(response)
# One fixture for failing requests
response_fail = pytest.fixture(scope="module", params=should_fail)(response)
# One fixture for all requests
response_all = pytest.fixture(scope="module", params=should_all)(response)
# This test only requests failing fixture data
def test_status_code(response_fail):
assert response_fail['response'].status_code == response_fail['code']
# This test all requests fixture data
#pytest.mark.parametrize("field", ["content-type"])
def test_header_content_type(response_all, field):
assert response_all['response'].headers[field] == response_all['fields'][field]

python - requests, using data parametres with same name

So, I'm using python request to POST data to an API, this API allows me to check more users at the same time by using the same data param,
example: if I want to check j#mail.com & x#gmail.com, if I used GET, (but I wanna use POST because I wanna check a lot of mails with one request) I'd have to query this url: htttps://website.com/API?email=j#mail.com&email=x#mail.com.
How do I change my dict (v) in order to make this possible?
This is the code I've written:
def query(rurl, data):
r = requests.post(rurl, data = data).json()
print (r)
def main():
v = {
'apikey': apikey,
'email': username,
'password': password,
}
query('https://website.com/API', v)
if __name__ == "__main__":
main()
If you give a list object as your value in v for a certain key, it produces multiple parameters of the same name. For example:
v = { ...
"email": ["j#mail.com", "x#mail.com"],
... }
This should work with either GET or POST requests.
To incorporate this into your existing code:
def query(rurl, data):
r = requests.post(rurl, data = data).json()
print (r)
def main():
v = {
'apikey': apikey,
'email': [username1, username2],
'password': password,
}
query('https://website.com/API', v)
if __name__ == "__main__":
main()
According to Documentation
def query(rurl, data):
r = requests.post(rurl, params = data)
print (r)
v = { ...
"email": ["j#mail.com", "x#mail.com"],
... }

Django "is not JSON serializable" - ajax, views.py - How to implement?

I have read the documentation, but I am not exactly sure how to implement serializer.serialize for JSON objects in my view.py. If anyone can help me understand this a little better. I have the following code in my view.py:
#user_passes_test(lambda u: u.is_superuser)
def ProjDetails(request):
proj_id = request.GET['proj_id']
proj = Proj.objects.filter(id=proj_id)
role_list = ProjRole.objects.filter(proj=proj)
proj = {
"proj": proj,
"roles": []
}
for r in role_list:
proj['roles'].append(r.id)
return HttpResponse(json.dumps(proj), content_type='application/json; charset=UTF-8')
I am trying to call this with .ajax (I am still working on the ajax, so it probably is not right):
$('#proj_list #sel_proj').click(function(){
$('div.sel').removeClass("sel");
$(this).addClass("sel");
var project_id = $(this).data('id');
$.ajax({
url:'../../proj_details',
data: {proj_id: proj_id},
// dataType: 'html',
success: function(data){
$('#proj_display').html(data)
},
error: function () {
alert("Failed to find the project!")
}
});
Once I get the JSON call to work, then I will focus more on the ajax.
Biggest problem, I am getting a 500 http error with:
TypeError at ../proj_details
[<Project: Example>] is not JSON serializable
I am using Django 1.7, but I even added SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer' to my settings.py without any luck. So I imported serializers from the django.core and tried to use serializer.serialize, but I am not understanding how to implement it I guess because my errors just keep getting worse. I have seen other posts with the same error, but still not understanding for my particular requirements.
+++++++++++++++ EDIT +++++++++++++++++++
So the only way I have been able to get this to work without multiple errors, circular errors, multiple argument errors, etc, is the following:
def ProjDetails(request):
def date_handler(obj):
return obj.strftime("%B %d, %Y") if hasattr(obj, 'strftime') else obj
proj_id = request.GET['proj_id']
proj = Proj.objects.get(id=proj_id)
corp = Corp.objects.get(id=proj.corp.id)
role_list = ProjRole.objects.filter(proj=proj).all()
proj = {
"proj": {
'title': proj.title,
'id': proj.id,
'date': proj.date,
'description': proj.description
}
"roles": [],
"company": {
'name': corp.name,
'pic': unicode(corp.pic),
}
}
for r in role_list:
proj['roles'].append(r.name)
return HttpResponse(json.dumps(proj, default=date_handler), content_type='application/json; charset=UTF-8')
The only thing I don't like about this is I actually have to manually pull what attributes I want from the model into the dictionary, instead of all the attributes being pulled from the model and then I can choose which ones I want to use in my templates. I would rather not have to pull everything like my example above. The 'roles' = [] is giving me some hiccups too because I can't seem to get it to work when there are multiple roles for a proj object.
I like Eugene's method because it would be cleaner, but I can't seem to get it to work with the corp model. The proj tables have a corp_id, yet I keep getting corp_id is not an attribute when I attempt it with using .value().get() for the proj object. I don't understand how to implement grzgrzgrz3's answer either. I usually work more with JS, HTML, and CSS, and I am new to Django/python for web development.
So any suggestions to make this more efficient would be great. Thank!!
Django model's instance can't be serialized, you should use values() method to retrieve dict instead of class instance. Also, you can use only() method to retrieve only id field for roles:
proj = Proj.objects.filter(id=proj_id).values().get()
role_list = ProjRole.objects.only("id").filter(proj__id=proj_id)
proj = {
"proj": proj,
"roles": role_list
}
Write custom HttpResponse and handle there all not serializable python/django objects.
class HttpJsonResponse(HttpResponse):
content_type="application/json"
def __init__(self,data):
def json_serial(obj):
"""JSON serializer for objects not serializable by default json code"""
if isinstance(obj, datetime.date):
serial = obj.isoformat()
return serial
json_data = json.dumps(data, indent=4, default=json_serial)
super(HttpJsonResponse, self).__init__(json_data, self.content_type)
In the example function json_serial converting datetime.date object into string object which is serializable.
*UPDATE
You can mix both answers.
def ProjDetails(request):
proj_id = request.GET['proj_id']
proj = Proj.objects.filter(id=proj_id).values().get()
corp = Corp.objects.filter(id=proj.corp.id).values().get()
role_list = ProjRole.objects.filter(proj=proj).values().all()
proj = {
"proj": proj,
"roles": role_list,
"company": corp
}
return HttpJsonResponse(proj)
Make sure you are importing datetime module.
import datetime
instead datetime class
import datetime.datetime
My answer, as described up above. This is what worked for me.
def ProjDetails(request):
def date_handler(obj):
return obj.strftime("%B %d, %Y") if hasattr(obj, 'strftime') else obj
proj_id = request.GET['proj_id']
proj = Proj.objects.get(id=proj_id)
corp = Corp.objects.get(id=proj.corp.id)
role_list = ProjRole.objects.filter(proj=proj).all()
proj = {
"proj": {
'title': proj.title,
'id': proj.id,
'date': proj.date,
'description': proj.description
}
"roles": [],
"company": {
'name': corp.name,
'pic': unicode(corp.pic),
}
}
for r in role_list:
proj['roles'].append(r.name)
return HttpResponse(json.dumps(proj, default=date_handler), content_type='application/json; charset=UTF-8')

Categories

Resources