I am testing my code, so far so good. But now I have to test a function using requests.get() to make an API call.
As far as I understand, I have to 'mock' this function in my test.
My function calls Google Maps API, and in my test I added the wanted output:
result =
{
"candidates":[
{
"geometry":{
"location":{
"lat":-34.5453062,
"lng":-58.44977489999999
}
},
"name":"Stade Monumental Antonio Vespucio Liberti",
"place_id":"ChIJ340B5jq0vJURijD6W6dgfz0"
}
]
}
return json.dumps(result)
This the function I am testing:
def get_lat_lng (self):
self.input_api = '%20'.join(self.parsed_question)
self.input_api = ' '.join(self.parsed_question)
self.google_api_url = 'https://maps.googleapis.com/maps/api/place/findplacefromtext/json?input={}&inputtype=textquery&fields=geometry,name,place_id&types=point_of_interest&key={}'.format (self.input_api, api_key)
self.r = requests.get (url = self.google_api_url)
self.data = self.r.json()
self.name = self.data['candidates'][0]['name']
self.place_id = self.data['candidates'][0]['place_id']
self.lat = self.data['candidates'][0]['geometry']['location']['lat']
self.lng = self.data['candidates'][0]['geometry']['location']['lng']
return (self.lat, self.lng, self.place_id)
And my test so far:
def test_get_lat_lng (monkeypatch):
monkeypatch.setattr('requests.get', mock_get_lat_lng)
This is the error code I get when trying to run the test:
self.r = requests.get (url = self.google_api_url)
> self.data = self.r.json()
E AttributeError: 'str' object has no attribute 'json'
I don't understand since I use json.dumps() on my desired output to "mock" an answer from requests.get(), how can it be a 'str' object? Looking for the type of self.r I get <class 'requests.models.Response'>.
Making mock_get_lat_lng() a class did the trick. Here is my final working test:
class mock_get_lat_lng():
def __init__(self, url):
pass
def json(self):
result = {
"candidates": [
{
"geometry": {
"location": {
"lat": -34.5453062,
"lng": -58.44977489
}
},
"name": "Stade Monumental Antonio Vespucio Liberti",
"place_id": "ChIJ340B5jq0vJURijD6W6dgfz0"
}
]
}
return result
Related
I am looking for an solution for calling functions with values from an jsonfile with variables.
What i have done so far:
import json
def anotherfunc(x, inputdata):
fruitname = x["Parameters"][0]["Name"]
print(fruitname + inputdata.decode("utf-8"))
class EncDec:
def __init__(self, input_file):
pass
with open(input_file, "r") as intput_handle:
self.input = json.load(intput_handle)
def __getattr__(self, name):
message = [x for x in self.input["FRUIT_Messages"]
if x["Name"] == name]
return anotherfunc(message[0], data)
if __name__ == "__main__":
file = EncDec("C:/Users/lamu7789/Documents/Python_Project/new.json")
data = b'\x00\x00'
x = file.berrys(data)
I am reading an json file in with init.
Afterwards I am trying to call a function with the name "berrys". It does work because getattr works if the Attribute "berrys" does not exists.
The Problem is, that getattr only takes on variable.
But i need a second variable 'data' for further uses.
Is there a solution that i am able to work with x = file.berrys(data) to pass the data to the anotherfunc(x, inputdata) ?
The only solution i found is to pass the 'data' variable with the path for the jsonfile and pass it to anotherfunc afterwards. But i would like to have the name and the data into one function call.
Thats my Json:
"Name": "FRUITS",
"Description": "Json full of fruits",
"FRUIT_Messages": [
{
"Name": "berrys",
"Sweet": 1,
"Description": [
"sweet and yummy"
],
"Parameters": [
{
"ID": 1,
"Type": "uint8",
"Name": "strawberry",
"Description": [
"Color: red",
"Season: Summer",
"Ground: Field"
]
}
]
}
]
}
I am trying to seed a django DB from an external API using this guide (https://medium.com/#chilinski.a/how-to-seed-a-django-api-with-data-from-an-external-api-b577b6e6ad54).
I have replicated the code accurately for my own project, I think, but get a TypeError when i run python manage.py seed and am not sure why. Here's the error message:
File "...nrel/nrel_app/management/commands/seed.py", line 15, in seed_nrel
utility_name = i["utility_name"],
TypeError: string indices must be integers
Here's my code:
import requests
from django.core.management.base import BaseCommand
from nrel_app.models import Nrel
def get_nrel():
url = 'https://developer.nrel.gov/api/utility_rates/v3.json?api_key=DEMO_KEY&lat=35.45&lon=-82.98'
r = requests.get(url, headers={'Content=Type': 'nrel_app/json'})
Nrels = r.json()
return Nrels
def seed_nrel():
for i in get_nrel():
Nrels = Nrel(
utility_name = i['utility_name'],
company_id = i['company_id'],
utility_info =i['utility_info'],
residential = i['residential'],
commercial = i['commercial'],
industrial = i['industrial'],
)
Nrels.save()
class Command(BaseCommand):
def handle(self, *args, **options):
seed_nrel()
print("Completed.")
Here's the json being requested from the nrel api:
{
"inputs": {
"lat": "35.45",
"lon": "-82.98"
},
"errors": [],
"warnings": [],
"version": "3.1.0",
"metadata": {
"sources": [
"Ventyx Research (2012)"
]
},
"outputs": {
"company_id": "8333|18642",
"utility_name": "Haywood Electric Member Corp|Tennessee Valley Authority",
"utility_info": [
{
"company_id": "8333",
"utility_name": "Haywood Electric Member Corp"
}, {
"company_id": "18642",
"utility_name": "Tennessee Valley Authority"
}
],
"commercial": 0.0977,
"industrial": 0.0862,
"residential": 0.123
}
}
I think you should remove the for cycle and get the result from the object returned by the get_nrel() method directly:
def seed_nrel():
i = get_nrel()['options']
Nrels = Nrel(
utility_name = i['utility_name'],
company_id = i['company_id'],
utility_info =i['utility_info'],
residential = i['residential'],
commercial = i['commercial'],
industrial = i['industrial'],
)
Nrels.save()
I am setting up a GraphQL Server with Python using Starlette and Graphene and ran into a problem I cannot find a solution for. The Graphene Documentation does not go into detail regarding the union type, which I am trying to implement.
I set up a minimum example based on the graphene documentation which you can run to replicate this problem
import os
import uvicorn
from graphene import ObjectType, Field, List, String, Int, Union
from graphene import Schema
from starlette.applications import Starlette
from starlette.graphql import GraphQLApp
from starlette.routing import Route
mock_data = {
"episode": 3,
"characters": [
{
"type": "Droid",
"name": "R2-D2",
"primaryFunction": "Astromech"
},
{
"type": "Human",
"name": "Luke Skywalker",
"homePlanet": "Tatooine"
},
{
"type": "Starship",
"name": "Millennium Falcon",
"length": 35
}
]
}
class Human(ObjectType):
name = String()
homePlanet = String()
class Droid(ObjectType):
name = String()
primary_function = String()
class Starship(ObjectType):
name = String()
length = Int()
class Characters(Union):
class Meta:
types = (Human, Droid, Starship)
class SearchResult(ObjectType):
characters = List(Characters)
episode = Int()
class RootQuery(ObjectType):
result = Field(SearchResult)
#staticmethod
def resolve_result(_, info):
return mock_data
graphql_app = GraphQLApp(schema=Schema(query=RootQuery))
routes = [
Route("/graphql", graphql_app),
]
api = Starlette(routes=routes)
if __name__ == "__main__":
uvicorn.run(api, host="127.0.0.1", port=int(os.environ.get("PORT", 8080)))
If you then go to http://localhost:8080/graphq and enter the following query
query Humans{
result {
episode
characters {
... on Human {
name
}
}
}
}
I get this error
{
"data": {
"result": {
"episode": 3,
"characters": null
}
},
"errors": [
{
"message": "Abstract type Characters must resolve to an Object type at runtime for field SearchResult.characters with value \"[{'type': 'Droid', 'name': 'R2-D2', 'primaryFunction': 'Astromech'}, {'type': 'Human', 'name': 'Luke Skywalker', 'homePlanet': 'Tatooine'}, {'type': 'Starship', 'name': 'Millennium Falcon', 'length': 35}]\", received \"None\".",
"locations": [
{
"line": 4,
"column": 5
}
]
}
]
}
which I am now stuck with. Maybe someone has done this already and can help out? How can I resolve this at runtime. I have already tried different approaches for example I changed classes Character and RootQuery:
class Character(Union):
class Meta:
types = (Human, Droid, Starship)
def __init__(self, data, *args, **kwargs):
super().__init__(*args, **kwargs)
self.data = data
self.type = data.get("type")
def resolve_type(self, info):
if self.type == "Human":
return Human
if self.type == "Droid":
return Droid
if self.type == "Starship":
return Starship
class RootQuery(ObjectType):
result = Field(SearchResult)
#staticmethod
def resolve_result(_, info):
return {**mock_data, "characters": [Character(character) for character in mock_data.get('characters')]}
resulting in
{
"data": {
"result": {
"episode": 3,
"characters": [
{},
{
"name": null
},
{}
]
}
}
}
Any ideas would be very appreciated!
jkimbo answered the question here:
class Character(Union):
class Meta:
types = (Human, Droid, Starship)
#classmethod
def resolve_type(cls, instance, info):
if instance["type"] == "Human":
return Human
if instance["type"] == "Droid":
return Droid
if instance["type"] == "Starship":
return Starship
class RootQuery(ObjectType):
result = Field(SearchResult)
def resolve_result(_, info):
return mock_data
Note I'm just returning mock_data and I've updated the resolve_type method to switch based on the data. The Union type uses the same resolve_type method as Interface to figure out what type to resolve to at runtime: https://docs.graphene-python.org/en/latest/types/interfaces/#resolving-data-objects-to-types
Spoiler alert: I posted my solution as an answer to this question
I am using flastk-resptlus to create an API. I have to provide the data in a specific structure, which I have problems to get, see an example below:
What I need to get is this structure:
{
"metadata": {
"files": []
},
"result" : {
"data": [
{
"user_id": 1,
"user_name": "user_1",
"user_role": "editor"
},
{
"user_id": 2
"user_name": "user_2",
"user_role": "editor"
},
{
"user_id": 3,
"user_name": "user_3",
"user_role": "curator"
}
]
}
}
But the problem comes that I cannot manage to get the structure of "result" : { "data": []} without making "data" a model itself.
What I tried to do so far (and did not work)
# define metadata model
metadata_model = api.model('MetadataModel', {
"files": fields.List(fields.String(required=False, description='')),
}
# define user model
user_model = api.model('UserModel', {
"user_id": fields.Integer(required=True, description=''),
"user_name": fields.String(required=True, description=''),
"user_role": fields.String(required=False, description='')
}
# here is where I have the problems
user_list_response = api.model('ListUserResponse', {
'metadata': fields.Nested(metadata_model),
'result' : {"data" : fields.List(fields.Nested(user_model))}
})
Complains that cannot get the "schema" from "data" (because is not a defined model), but I don't want to be a new api model, just want to append a key called "data". Any suggestions?
This I tried and works, but is not what I want (because I miss the "data"):
user_list_response = api.model('ListUserResponse', {
'metadata': fields.Nested(metadata_model),
'result' : fields.List(fields.Nested(user_model))
})
I don't want data to be a model because the common structure of the api is the following:
{
"metadata": {
"files": []
},
"result" : {
"data": [
<list of objects> # here must be listed the single model
]
}
}
Then, <list of objects> can be users, addresses, jobs, whatever.. so I want to make a "general structure" in which then I can just inject the particular models (UserModel, AddressModel, JobModel, etc) without creating a special data model for each one.
A possible approach is to use fields.Raw which returns whatever serializable object you pass. Then, you can define a second function, which creates your result and uses marshal. marshal transforms your data according to a model and accepts an additional parameter called envelope. envelope surrounds your modeled data by a given key and does the trick.
from flask import Flask
from flask_restplus import Api, fields, Resource, marshal
app = Flask(__name__)
api = Api()
api.init_app(app)
metadata_model = api.model("metadata", {
'file': fields.String()
})
user_model = api.model('UserModel', {
"user_id": fields.Integer(required=True, description=''),
"user_name": fields.String(required=True, description=''),
"user_role": fields.String(required=False, description='')
})
response_model = api.model("Result", {
'metadata': fields.List(fields.Nested(metadata_model)),
'result': fields.Raw()
})
#api.route("/test")
class ApiView(Resource):
#api.marshal_with(response_model)
def get(self):
data = {'metadata': {},
'result': self.get_user()}
return data
def get_user(self):
# Access database and get data
user_data = [{'user_id': 1, 'user_name': 'John', 'user_role': 'editor'},
{'user_id': 2, 'user_name': 'Sue', 'user_role': 'curator'}]
# The kwarg envelope does the trick
return marshal(user_data, user_model, envelope='data')
app.run(host='0.0.0.0', debug=True)
My workaround solution that solves all my problems:
I create a new List fields class (it is mainly copied from fields.List), and then I just tune the output format and the schema in order to get the 'data' as key:
class ListData(fields.Raw):
'''
Field for marshalling lists of other fields.
See :ref:`list-field` for more information.
:param cls_or_instance: The field type the list will contain.
This is a modified version of fields.List Class in order to get 'data' as key envelope
'''
def __init__(self, cls_or_instance, **kwargs):
self.min_items = kwargs.pop('min_items', None)
self.max_items = kwargs.pop('max_items', None)
self.unique = kwargs.pop('unique', None)
super(ListData, self).__init__(**kwargs)
error_msg = 'The type of the list elements must be a subclass of fields.Raw'
if isinstance(cls_or_instance, type):
if not issubclass(cls_or_instance, fields.Raw):
raise MarshallingError(error_msg)
self.container = cls_or_instance()
else:
if not isinstance(cls_or_instance, fields.Raw):
raise MarshallingError(error_msg)
self.container = cls_or_instance
def format(self, value):
if isinstance(value, set):
value = list(value)
is_nested = isinstance(self.container, fields.Nested) or type(self.container) is fields.Raw
def is_attr(val):
return self.container.attribute and hasattr(val, self.container.attribute)
# Put 'data' as key before the list, and return the dict
return {'data': [
self.container.output(idx,
val if (isinstance(val, dict) or is_attr(val)) and not is_nested else value)
for idx, val in enumerate(value)
]}
def output(self, key, data, ordered=False, **kwargs):
value = fields.get_value(key if self.attribute is None else self.attribute, data)
if fields.is_indexable_but_not_string(value) and not isinstance(value, dict):
return self.format(value)
if value is None:
return self._v('default')
return [marshal(value, self.container.nested)]
def schema(self):
schema = super(ListData, self).schema()
schema.update(minItems=self._v('min_items'),
maxItems=self._v('max_items'),
uniqueItems=self._v('unique'))
# work around to get the documentation as I want
schema['type'] = 'object'
schema['properties'] = {}
schema['properties']['data'] = {}
schema['properties']['data']['type'] = 'array'
schema['properties']['data']['items'] = self.container.__schema__
return schema
Consider a server generating JSON messages for a to-do list.
import json
class Action(object):
def __init__(self, what, when):
self.what = what
self.when = when
class Actions(object):
def __init__(self):
self.actions = []
def insert(self, action):
self.actions.append({'action': 'insert_todo',
'detail': {'what': action.what,
'when': action.when}})
class Batch(object):
def __init__(self):
self.urgent_actions = Actions()
self.ordinary_actions = Actions()
self.urgent_actions.insert(Action('tidy up', '8am'))
def jdefault(o):
return o.__dict__
def output_json():
batch = Batch()
mystr = json.dumps(batch,
default=jdefault,
indent=4)
print(mystr)
output_json()
This works fine, and we get the message:
{
"urgent_actions": {
"actions": [
{
"action": "insert_todo",
"detail": {
"what": "tidy up",
"when": "8am"
}
}
]
},
"ordinary_actions": {
"actions": []
}
}
But repeating actions inside both priorities of actions and in each message is asking for some clean-up.
We can do that by deriving Actions from list:
class Actions(list):
def __init__(self, *args):
list.__init__(self, *args)
def insert(self, action):
self.append({'action': 'insert_todo',
'detail': {'what': action.what,
'when': action.when}})
And we get indeed the slimmer JSON message:
{
"urgent_actions": [
{
"action": "insert_todo",
"detail": {
"what": "8am",
"when": "8am"
}
}
],
"ordinary_actions": []
}
Yet,
deriving
from
list
is
far
from
the best idea.
What other (idiomatic) way would you use to get the slimmer message without deriving from list?
The messages are to be sent through Flask, in case you'd also like to critique the use of json.dumps.
Rather than change the classes, you could delegate defining the structure of the json to the jdefault function.
def jdefault(o):
if isinstance(o, Batch):
return o.__dict__
if isinstance(o, Actions):
return o.actions
raise TypeError("Object of type {} is not JSON serializable".format(type(o)))
Which generates the desired output:
{
"urgent_actions": [
{
"action": "insert_todo",
"detail": {
"what": "tidy up",
"when": "8am"
}
}
],
"ordinary_actions": []
}
This way you separate the concerns of your objects' structure and serialisation.