Updating multiple attributes of a python object - unit testing - python

import requests
class RadioInfo:
def __init__(self, url, station_num=0):
self.url = url
self.station_num = station_num
def update(self):
radio_info = self.get_json()
self.update_info(radio_info)
def get_json(self):
r = requests.get(self.url)
return r.json()[self.station_num]
def update_info(self, radio_info):
self.radio_station_name = radio_info["station"]["name"]
self.radio_station_description = radio_info["station"]["description"]
self.current_listeners = radio_info["listeners"]["current"]
self.unique_listeners = radio_info["listeners"]["unique"]
self.total_listeners = radio_info["listeners"]["total"]
self.now_playing_playlist = radio_info["now_playing"]["playlist"]
self.now_playing_song_title = radio_info["now_playing"]["song"]["title"]
self.now_playing_song_artist = radio_info["now_playing"]["song"]["artist"]
self.now_playing_song_album = radio_info["now_playing"]["song"]["album"]
###
# What if radio["playing_next"] is None ??
###
self.next_playing_playlist = radio_info["playing_next"]["playlist"]
self.next_playing_song_title = radio_info["playing_next"]["song"]["title"]
self.next_playing_song_artist = radio_info["playing_next"]["song"]["artist"]
self.next_playing_song_album = radio_info["playing_next"]["song"]["album"]
Having the above, trying to figure out how to refactor this, so that I can check if some of the nodes are None and handle this accordingly.
Additionally starting out with unit testing, and besides error handling was thinking about best practices of unit testing this object.
Should I create a method for each property and unit test with pytest.mark.parametrize for example, or is this considered redundant and not necessary ?

Related

What is the best way to return a variable or call a function to maximize code reuse?

I was wondering if i could get some input from some season python exports, i have a couple questions
I am extracting data from an api request and calculating the total vulnerabilities,
what is the best way i can return this data so that i can call it in another function
what is the way i can add up all the vulnerabilities (right now its just adding it per 500 at a time, id like to do the sum of every vulnerability
def _request():
third_party_patching_filer = {
"asset": "asset.agentKey IS NOT NULL",
"vulnerability" : "vulnerability.categories NOT IN ['microsoft patch']"}
headers = _headers()
print(headers)
url1 = f"https://us.api.insight.rapid7.com/vm/v4/integration/assets"
resp = requests.post(url=url1, headers=headers, json=third_party_patching_filer, verify=False).json()
jsonData = resp
#print(jsonData)
has_next_cursor = False
nextKey = ""
if "cursor" in jsonData["metadata"]:
has_next_cursor = True
nextKey = jsonData["metadata"]["cursor"]
while has_next_cursor:
url2 = f"https://us.api.insight.rapid7.com/vm/v4/integration/assets?&size=500&cursor={nextKey}"
resp2 = requests.post(url=url2, headers=headers, json=third_party_patching_filer, verify=False).json()
cursor = resp2["metadata"]
print(cursor)
if "cursor" in cursor:
nextKey = cursor["cursor"]
print(f"next key {nextKey}")
#print(desktop_support)
for data in resp2["data"]:
for tags in data['tags']:
total_critical_vul_osswin = []
total_severe_vul_osswin = []
total_modoer_vuln_osswin = []
if tags["name"] == 'OSSWIN':
print("OSSWIN")
critical_vuln_osswin = data['critical_vulnerabilities']
severe_vuln_osswin = data['severe_vulnerabilities']
modoer_vuln_osswin = data['moderate_vulnerabilities']
total_critical_vul_osswin.append(critical_vuln_osswin)
total_severe_vul_osswin.append(severe_vuln_osswin)
total_modoer_vuln_osswin.append(modoer_vuln_osswin)
print(sum(total_critical_vul_osswin))
print(sum(total_severe_vul_osswin))
print(sum(total_modoer_vuln_osswin))
if tags["name"] == 'DESKTOP_SUPPORT':
print("Desktop")
total_critical_vul_desktop = []
total_severe_vul_desktop = []
total_modorate_vuln_desktop = []
critical_vuln_desktop = data['critical_vulnerabilities']
severe_vuln_desktop = data['severe_vulnerabilities']
moderate_vuln_desktop = data['moderate_vulnerabilities']
total_critical_vul_desktop.append(critical_vuln_desktop)
total_severe_vul_desktop.append(severe_vuln_desktop)
total_modorate_vuln_desktop.append(moderate_vuln_desktop)
print(sum(total_critical_vul_desktop))
print(sum(total_severe_vul_desktop))
print(sum(total_modorate_vuln_desktop))
else:
pass
else:
has_next_cursor = False
If you have a lot of parameters to pass, consider using a dict to combine them. Then you can just return the dict and pass it along to the next function that needs that data. Another approach would be to create a class and either access the variables directly or have helper functions that do so. The latter is a cleaner solution vs a dict, since with a dict you have to quote every variable name, and with a class you can easily add additional functionally beyond just being a container for a bunch of instance variables.
If you want the total across all the data, you should put these initializations:
total_critical_vul_osswin = []
total_severe_vul_osswin = []
total_modoer_vuln_osswin = []
before the while has_next_cursor loop (and similarly for the desktop totals). The way your code is currently, they are initialized each cursor (ie, each 500 samples based on the URL).

Peewee - Recursive CTEs - problem with the documentation example - OperationalError no such column: base.id

I have a problem with Peewee-3 and one of the tutorials in documentation:
http://docs.peewee-orm.com/en/latest/peewee/querying.html#recursive-ctes
When I'm trying to run this code (nearly exact copy from doc) it's rising an error:
Exception has occurred: OperationalError no such column: base.id
Here is my code (there is commented part with some testing categories):
_db = SqliteDatabase(DB_FILE)
class _Base(Model):
class Meta:
database = _db
class Category(_Base):
name = CharField()
parent = ForeignKeyField('self', backref='children', null=True)
# ADDING CATEGORIES
# _db.connect()
# _db.create_tables([Category])
# stocks = Category(name="stocks", parent=None)
# stocks.save()
# models = Category(name="models", parent=None)
# models.save()
# smoke = Category(name="smoke", parent=stocks)
# smoke.save()
# front = Category(name="front", parent=smoke)
# front.save()
# side = Category(name="side", parent=smoke)
# side.save()
# fluffy = Category(name="fluffy", parent=front)
# fluffy.save()
# _db.close()
Base = Category.alias()
level = Value(1).alias('level')
path = Base.name.alias('path')
base_case = (Base
.select(Base.name, Base.parent, level, path)
.where(Base.parent.is_null())
.cte('base', recursive=True))
RTerm = Category.alias()
rlevel = (base_case.c.level + 1).alias('level')
rpath = base_case.c.path.concat('->').concat(RTerm.name).alias('path')
recursive = (RTerm
.select(RTerm.name, RTerm.parent, rlevel, rpath)
.join(base_case, on=(RTerm.parent == base_case.c.id)))
cte = base_case.union_all(recursive)
query = (cte
.select_from(cte.c.name, cte.c.level, cte.c.path)
.order_by(cte.c.path))
for category in query:
print(category.name, category.level, category.path)
What am I doing wrong and how can I fix it, there is an mistake in the documentation?
Thanks for the issue report. The problem is I omitted to select the category "id" in the example from the docs. The fix is quite simple:
base_case = (Base
.select(Base.id, Base.name, Base.parent, level, path) # Add Base.id
.where(Base.parent.is_null())
.cte('base', recursive=True))
...
recursive = (RTerm
.select(RTerm.id, RTerm.name, RTerm.parent, rlevel, rpath) # Add RTerm.id
.join(base_case, on=(RTerm.parent == base_case.c.id)))
I've updated the docs accordingly.

Unittest: mock a python class with init method

I am trying to test a run method in my class which has init method and takes object as parameter from another class:
class ServePlate(FreeSurferStep):
process_name = "FreeSurfer"
step_name = "ServePlate"
step_cli = "serve"
cpu = 1
mem = 1024
def __init__(self, project, code, args):
super(Stage, self).__init__(project, code, args)
self.next_step = Autorecon1
#classmethod
def get_queue(cls, project_name):
plog = ProcessingLog()
available = plog.get_project_images(project_name, "T1")
attempted = plog.get_step_attempted(project_name, cls.process_name, cls.step_name)
attempted_codes = [row.Code for row in attempted]
todo = [{'ProjectName': project_name, 'Code': row.Code} for row in available if row.Code not in attempted_codes]
return todo
def run(self): #<<<<-- This method is to be tested
source = None
image = ProcessingLog().get_project_image(self.project, self.code)
if image.ImageStore == "Dicom":
dcmtmp = tempfile.mkdtemp()
DicomRepository().fetch_dicoms(self.code, dcmtmp)
first_t1 = os.path.join(dcmtmp, os.listdir(dcmtmp)[0])
niitmp = os.path.join(tempfile.mkdtemp(), 'raw.nii')
cmd = 'dcm2niix -b n -z n -g i -o {} -f raw {}'.format(os.path.dirname(niitmp), first_t1)
self._run_fs_cmd(cmd)
source = niitmp
elif image.ImageStore == "Pre-Processed":
source = [PreProcessedImageRepository().get_image(self.code), ]
if source is None:
raise ProcessingError("Could not find staging data.")
first_t1 = self._copy_files(source)
cmd = 'recon-all -s %(code)s -i %(image)s' % {
"code": self.code,
"image": first_t1
}
self._run_fs_cmd(cmd). #<<<-- I am trying to check value of cmd variable
Here is my test, i am patching first the init method and second _run_fs_cmd frm another class.
class Testfs(unittest.TestCase):
#patch.object(fs.FreeSurferStep, '_run_fs_cmd', spec=True)
# #patch.object(fs.FreeSurferStep, '__init__')
def test_serve(mock_serve):
"""
Serve step test
"""
mock_serve.project = 'TEST_FS'
mock_serve.code = 'Test9001-1a5'
mock_serve.args = ''
mock_stage.return_value = None
FsObj = FreeSurferStep('serve')
stage_obj = Stage(FsObj)
FsObj.run()
#
# stage_obj.run(self)
#
# self.assertEqual(self.cmd, '')
# fs.FreeSurferStep._run_fs_cmd = Mock()
this gives me error. Here even though i am passing no arguments to the run method, it keeps on complaining about more argument being passed. Also patching a class object to be passed to ServePlate method and patching run_fsmethod where the cmd is passed to doesn't seem to work. Do i need to compulsorily mock all other methods being called?
TypeError: test_serve() takes 1 positional argument but 2 were given
TypeError: run() takes 1 positional argument but 3 were given
i got the test working with initializing correctly:
class Testfs(unittest.TestCase):
project = 'TEST'
code = '9001-1a5'
args = 'nogrid'
#patch.object(fs.FreeSurferStep, '_run_fs_cmd', 'put_object', spec=True)
#patch.object(fs.FreeSurferStep, '__init__')
def test_serve(self, mock_test_serve):
"""
Stage step test
"""
mock_test_stage.return_value = None
project = 'TEST'
code = '9001-1a5'
args = 'nogrid'
self.logger = logging.getLogger(__name__)
FsObj = fs.FreeSurferStep('Stage')
stage_obj = fs.Stage(FsObj, code, args)
stage_obj.project = 'Test'
stage_obj.code = '9001-1a5'
stage_obj.run()
however havent got a way to check value passed to `_run_fs_cmd` method

How do I make it so I only need my api key referenced once?

I am teaching myself how to use python and django to access the google places api to make nearby searches for different types of gyms.
I was only taught how to use python and django with databases you build locally.
I wrote out a full Get request for they four different searches I am doing. I looked up examples but none seem to work for me.
allgyms = requests.get('https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=38.9208,-77.036&radius=2500&type=gym&key=AIzaSyDOwVK7bGap6b5Mpct1cjKMp7swFGi3uGg')
all_text = allgyms.text
alljson = json.loads(all_text)
healthclubs = requests.get('https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=38.9208,-77.036&radius=2500&type=gym&keyword=healthclub&key=AIzaSyDOwVK7bGap6b5Mpct1cjKMp7swFGi3uGg')
health_text = healthclubs.text
healthjson = json.loads(health_text)
crossfit = requests.get('https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=38.9208,-77.036&radius=2500&type=gym&keyword=crossfit&key=AIzaSyDOwVK7bGap6b5Mpct1cjKMp7swFGi3uGg')
cross_text = crossfit.text
crossjson = json.loads(cross_text)
I really would like to be pointed in the right direction on how to have the api key referenced only one time while changing the keywords.
Try this for better readability and better reusability
BASE_URL = 'https://maps.googleapis.com/maps/api/place/nearbysearch/json?'
LOCATION = '38.9208,-77.036'
RADIUS = '2500'
TYPE = 'gym'
API_KEY = 'AIzaSyDOwVK7bGap6b5Mpct1cjKMp7swFGi3uGg'
KEYWORDS = ''
allgyms = requests.get(BASE_URL+'location='+LOCATION+'&radius='+RADIUS+'&type='+TYPE+'&key='+API_KEY) all_text = allgyms.text
alljson = json.loads(all_text)
KEYWORDS = 'healthclub'
healthclubs = requests.get(BASE_URL+'location='+LOCATION+'&radius='+RADIUS+'&type='+TYPE+'&keyword='+KEYWORDS+'&key='+API_KEY)
health_text = healthclubs.text
healthjson = json.loads(health_text)
KEYWORDS = 'crossfit'
crossfit = requests.get(BASE_URL+'location='+LOCATION+'&radius='+RADIUS+'&type='+TYPE+'&keyword='+KEYWORDS+'&key='+API_KEY)
cross_text = crossfit.text
crossjson = json.loads(cross_text)
as V-R suggested in a comment you can go further and define function which makes things more reusable allowing you to use the that function in other places of your application
Function implementation
def makeRequest(location, radius, type, keywords):
BASE_URL = 'https://maps.googleapis.com/maps/api/place/nearbysearch/json?'
API_KEY = 'AIzaSyDOwVK7bGap6b5Mpct1cjKMp7swFGi3uGg'
result = requests.get(BASE_URL+'location='+location+'&radius='+radius+'&type='+type+'&keyword='+keywords+'&key='+API_KEY)
jsonResult = json.loads(result)
return jsonResult
Function invocation
json = makeRequest('38.9208,-77.036', '2500', 'gym', '')
Let me know if there is an issue

How to use the Typed unmarshaller in suds?

I have existing code that processes the output from suds.client.Client(...).service.GetFoo(). Now that part of the flow has changed and we are no longer using SOAP, instead receiving the same XML through other channels. I would like to re-use the existing code by using the suds Typed unmarshaller, but so far have not been successful.
I came 90% of the way using the Basic unmarshaller:
tree = suds.umx.basic.Basic().process(xmlroot)
This gives me the nice tree of objects with attributes, so that the pre-existing code can access tree[some_index].someAttribute, but the value will of course always be a string, rather than an integer or date or whatever, so the code can still not be re-used as-is.
The original class:
class SomeService(object):
def __init__(self):
self.soap_client = Client(some_wsdl_url)
def GetStuff(self):
return self.soap_client.service.GetStuff()
The drop-in replacement that almost works:
class SomeSourceUntyped(object):
def __init__(self):
self.url = some_url
def GetStuff(self):
xmlfile = urllib2.urlopen(self.url)
xmlroot = suds.sax.parser.Parser().parse(xmlfile)
if xmlroot:
# because the parser creates a document root above the document root
tree = suds.umx.basic.Basic().process(xmlroot)[0]
else:
tree = None
return tree
My vain effort to understand suds.umx.typed.Typed():
class SomeSourceTyped(object):
def __init__(self):
self.url = some_url
self.schema_file_name =
os.path.realpath(os.path.join(os.path.dirname(__file__),'schema.xsd'))
with open(self.schema_file_name) as f:
self.schema_node = suds.sax.parser.Parser().parse(f)
self.schema = suds.xsd.schema.Schema(self.schema_node, "", suds.options.Options())
self.schema_query = suds.xsd.query.ElementQuery(('http://example.com/namespace/','Stuff'))
self.xmltype = self.schema_query.execute(self.schema)
def GetStuff(self):
xmlfile = urllib2.urlopen(self.url)
xmlroot = suds.sax.parser.Parser().parse(xmlfile)
if xmlroot:
unmarshaller = suds.umx.typed.Typed(self.schema)
# I'm still running into an exception, so obviously something is missing:
# " Exception: (document, None, ), must be qref "
# Do I need to call the Parser differently?
tree = unmarshaller.process(xmlroot, self.xmltype)[0]
else:
tree = None
return tree
This is an obscure one.
Bonus caveat: Of course I am in a legacy system that uses suds 0.3.9.
EDIT: further evolution on the code, found how to create SchemaObjects.

Categories

Resources