How to access a Python object attribute in Robot Framework? - python

I want to access an object attribute variable in Robot Framework tests and then validate the values of these attributes.
I have the following Python class:
class TestingClass(object):
def __init__(self, client_id=None, redirect_uri=None, state=None):
self.client_id = client_id
self.uri = uri
self.state = state
def url_authorize(self):
self.oauthsession = OAuth2Session(client_id=self.client_id, uri=self.uri, state=self.state)
self.authorize_endpoint_url = self.oauthsession.authorization_url(
url="http://localhost:8080/authorize")[0]
return self.authorize_endpoint_url
def authorize(self):
request = requests.get("http://localhost:8080")
self.status_code = request.status_code
self.content = request.content
self.json = request.json
I want to be able to grab any one of the attributes created in the Authorize (authorize method) and validate it. So I want to do something like this:
* Variables
${CLIENT_ID} to
${URI} http://127.0.0.1:8080/callback
${STATE} random
* Settings
Documentation Tests Suite
Library Utilities.TestingClass ${CLIENT_ID} ${URI}
... ${STATE}
*Keywords
*Test Cases
Authorize Test 1
[Documentation] Test that the user is redirected to the authorize
... endpoint
... when accessing the OAuth Client
Url Authorize
Authorize
Should Be Equal As Integers Utilities.TestingClass.status_code 200
However this gives: Utilities.TestingClass.status_code != 200 which is no surprise to me.
How do I grab this attribute to compare it? Would I need to maybe return all the attributes made from the authroize() method in a list/dictionary/some sort of array and then access by indexing? Or is there a more straight forward way to do this with Robot Framework?

You have the following two choices, both will involve the usage of extended variable syntax. I would prefer the second option.
You can use the Get Library Instance keyword, to get the library instance in the test. The you can access its member variables using the extended variable syntax. Here is an example based on your code, I just replaced the return values with constants.
class TestingClass(object):
def __init__(self, client_id=None, redirect_uri=None, state=None):
self.client_id = client_id
self.uri = redirect_uri
self.state = state
def url_authorize(self):
self.oauthsession = None
self.authorize_endpoint_url = "http://localhost:8080/authorize"
return self.authorize_endpoint_url
def authorize(self):
self.status_code = 200
self.content = 'content'
self.json = { "A" : ["StringA1", "StringA2"], "B": ["StringB1","StringB2"]}
*** Settings ***
Library Utilities.TestingClass to http://127.0.0.1:8080/callback random
*** Test Cases ***
Test
Url Authorize
Authorize
${TestingClass}= Get Library Instance Utilities.TestingClass
Log ${TestingClass.status_code}
Log ${TestingClass.content}
Log ${TestingClass.json}
The other option is to modify the authorize method, so it will return what requests.get("http://localhost:8080") returns. Then you could access the status code, content and JSON in the same way as above, using the extended variable syntax.
class DummyRequestReturnValue():
def __init__(self):
self.status_code = 200
self.content = 'content'
self.json = { "A" : ["StringA1", "StringA2"], "B": ["StringB1","StringB2"]}
class TestingClass(object):
def __init__(self, client_id=None, redirect_uri=None, state=None):
self.client_id = client_id
self.uri = redirect_uri
self.state = state
def url_authorize(self):
self.oauthsession = None
self.authorize_endpoint_url = "http://localhost:8080/authorize"
return self.authorize_endpoint_url
def authorize(self):
request = DummyRequestReturnValue()
return request
*** Settings ***
Library Utilities.TestingClass to http://127.0.0.1:8080/callback random
*** Test Cases ***
Test
Url Authorize
${response}= Authorize
Log ${response.status_code}
Log ${response.content}
Log ${response.json}

Related

Says 'AnimalShelter' object has no attritbute 'create' but everything is there?

I am trying to test my AnimalShelter.py code in Jupyter Notebook but I keep getting the error that the 'create' attribute is not there. Did I miss something? I have it defined in the code but no matter what I change it still says it is not there.
AnimalShelter.py code:
import pymongo
from pymongo import MongoClient
from bson.objectid import ObjectId
class AnimalShelter(object):
""" CRUD operations for Animal collection in MongoDB """
def __init__(self, username, password):
#Initializing the MongoClient. This helps to access the MongoDB databases and collections.
self.client = MongoClient('mongodb://%s:%s#localhost:45344' % (username, password))
#where xxxx is your unique port number
self.database = self.client['AAC']
#Complete this create method to implement the C in CRUD.
def create(self, data):
if data is not None:
insert = self.database.animals.insert(data) #data should be dictionary
else:
raise Exception("Nothing to save, because data parameter is empty")
#Create method to implement the R in CRUD.
def read(self, searchData):
if searchData:
data = self.database.animals.find(searchData, {"_id": False})
else:
data = self.database.animals.find({}, {"_id": False})
return data
#Create method to implement U in CRUD.
def update(self, searchData, updateData):
if searchData is not None:
result = self.database.animals.update_many(searchData, {"$set": updateData})
else:
return "{}"
return result.raw_result
#Create method to implement D in CRUD.
def delete(self, deleteData):
if deleteData is not None:
result = self.database.animals.delete_many(deleteData)
else:
return "{}"
return result.raw_result
The test code I am trying to use to see that CRUD is functioning properly:
from AnimalShelter import AnimalShelter
data = {}
query = {}
test_class = AnimalShelter()
#test each function in the AnimalShelter class
response = test_class.create(data)
assert response
response = test_class.read(data)
assert response
response = test_class.update(data)
assert response
response = test_class.delete(data)
assert response
I am at a loss. I am new to Jupyter Notebook with the whole testing thing and I figured this was the simplest way to test the attributes before making a different test for specific data from the python code but either way I still get that the create attribute doesn't exist!
I created a slightly altered version of your code for easy testing and got the error that no username and password were being passed to the AnimalShelter constructor.
class AnimalShelter(object):
""" CRUD operations for Animal collection in MongoDB """
def __init__(self, username, password):
print("constructor")
#Complete this create method to implement the C in CRUD.
def create(self, data):
print("create")
#Create method to implement the R in CRUD.
def read(self, searchData):
print("read")
#Create method to implement U in CRUD.
def update(self, searchData, updateData):
print("update")
#Create method to implement D in CRUD.
def delete(self, deleteData):
print("delete")
test_class = AnimalShelter("user", "pass")
data = {}
test_class.create(data)
test_class.read(data)
test_class.update(data, {})
test_class.delete(data)
Could your error be because you didn't pass these variables when constructing the test_class object? Upon passing "user" and "pass", I got the expected output:
constructor
create
read
update
delete

How to solve "Unresolved attribute reference for class"

I have been working on a small project which is a web-crawler template. Im having an issue in pycharm where I am getting a warning Unresolved attribute reference 'domain' for class 'Scraper'
from abc import abstractmethod
import requests
import tldextract
class Scraper:
scrapers = {}
def __init_subclass__(scraper_class):
Scraper.scrapers[scraper_class.domain] = scraper_class # Unresolved attribute reference 'domain' for class 'Scraper'
#classmethod
def for_url(cls, url):
k = tldextract.extract(url)
# Returns -> <scraper.SydsvenskanScraper object at 0x000001E94F135850> & Scraped BBC News<!DOCTYPE html><html Which type annotiation?
return cls.scrapers[k.registered_domain](url)
#abstractmethod
def scrape(self):
pass
class BBCScraper(Scraper):
domain = 'bbc.co.uk'
def __init__(self, url):
self.url = url
def scrape(self):
rep = requests.Response = requests.get(self.url)
return "Scraped BBC News" + rep.text[:20] # ALL HTML CONTENT
class SydsvenskanScraper(Scraper):
domain = 'sydsvenskan.se'
def __init__(self, url):
self.url = url
def scrape(self):
rep = requests.Response = requests.get(self.url)
return "Scraped Sydsvenskan News" + rep.text[:20] # ALL HTML CONTENT
if __name__ == "__main__":
URLS = ['https://www.sydsvenskan.se/', 'https://www.bbc.co.uk/']
for urls in URLS:
get_product = Scraper.for_url(urls)
r = get_product.scrape()
print(r)
Of course I could ignore it as it is working but I do not like to ignore a warning as I believe pycharm is smart and should solve the warning rather than ignoring it and I wonder what is the reason of it warns me regarding that?
There are a few different levels on how you can remove this warning:
Assign a default value:
class Scraper:
scrapers = {}
domain = None # Or a sensible value of one exists
You can in additon or alternatly annotate the type.
from typing import ClassVar
class Scraper:
scrapers: ClassVar[dict[str, 'Scraper']] = {}
domain: ClassVar[str]
Note that ClassVar is required because otherwise it is assume that they are instance attributes.
To ignore it, put
# noinspection PyUnresolvedReferences
on the line above the line causing the warning.
Just tell yrou Scraper class that this attribut exists
class Scraper:
scrapers = {}
domain: str
def __init_subclass__(scraper_class):
Scraper.scrapers[scraper_class.domain] = scraper_class

Pyramid: resource tree in URL Dispatch(hybrid) application

I am trying to implement dynamic ACLs(including row-level security) in URL Dispatch app.
Defining Root factory only doesn't seem to be sufficient as I need to perform individual authz checks for each secured endpoint. My setup looks as follows(I was using pyramid docs and mmerickel's tutorials as a guide):
config.py
...
settings = config.registry.settings
config = Configurator(settings=settings, root_factory=RootPermissionFactory)
config.set_authentication_policy(CustomAuthenticationPolicy(settings))
config.set_authorization_policy(ACLAuthorizationPolicy())
...
views.py
#import ...
#view_defaults(renderer='json', permission='secured')
class RecordsView(object):
...
#view_config(request_method='GET', route_name='records_by_id')
def get(self):
record = self.request.context.data
if not record:
return HTTPNotFound()
return record
#view_config(request_method='GET', route_name='records')
def get_by_owners(self):
owner_uids = self.request.params.mixed()['owner_uids']
return records_service.get_records(owner_uids=owner_uids)
def includeme(config):
config.add_route('records', '/records', factory=RecordsResource)
config.add_route('records_by_id', 'records/{record_id}', factory=RecordFactory, traverse='{record_id}')
authorization.py
class RootPermissionFactory(object):
__name__ = None
__parent__ = None
def __acl__(self):
return [
(Allow, 'authenticated_principal', 'secured'),
]
def __init__(self, request):
self.request = request
class RecordFactory(object):
def __init__(self, request):
self.request = request
def __getitem__(self, key):
record_data = records_service.get_record(key)
owner = record_data.get('owner_uid')
return RecordContext(self.request, owner, record_data)
class RecordContext(object):
def __acl__(self):
owner_principal = 'u:{owner}'.format(owner=self.owner)
return [
(Allow, owner_principal, 'secured'),
]
def __init__(self, request, owner, record_data):
self.request = request
self.owner = owner
self.data = record_data
class RecordsResource(object):
def __acl__(self):
request_params = self.request.params.mixed()
request_owner_uids = request_params['owner_uids']
authorized_owner_uids = owners_api_service.get_authorized_owners(self.request.user['auth_data'])
return [(Allow, 'authenticated_principal', 'secured')]\
if set(authorized_owner_uids) == set(request_owner_uids) else []
def __init__(self, request):
self.request = request
My questions are following:
is it acceptable to utilize row-level security checks without having Model layer? i.e. there is no ORM set up for Records data and there is also no plain Model to put persisted data into, so I have to use 'fake' RecordContext class to attach __acl__ rules and pass data to the view
is it acceptable to treat /records endpoint as a Resource despite the fact it is not a resource from Traversal perspective and relies on query parameters rather than path sections?
I think the answer to both of your question is: if it works for you then it's totally acceptable. I've found a lot of success treating URLs as resources as a general pattern to the extent that I have a some tooling to avoid using route_name. For example:
config.add_route('records', '/records', factory=RecordsResource, use_global_views=True)
config.add_route('records_by_id', 'records/{record_id}', factory=RecordFactory, traverse='{record_id}', use_global_views=True)
#view_config(context=RecordsResource, renderer='json')
def records_view(request):
return {}
#view_config(context=RecordContext, renderer='json')
def record_view(request):
return {}

How to add the fictive method

Inside of the class, I use VK API. During the test, I use MagicMock to mock authorization in the API this class uses:
vk.Session = MagicMock(name='session', return_value=None)
vk.API = MagicMock(name='api')
mock_vk_values = [{'uid': user.vk_uid}]
vk.API.users.get = MagicMock(name='uid', return_value=mock_vk_values)
But, inside of the class, I use this API to get the user's uid:
class VKAuth(object):
def __init__(self, access_token, user):
...
self.session = vk.Session(access_token=access_token)
self.api = vk.API(self.session)
...
def authenticate(self):
try:
vk_uid = self.api.users.get()[0]['uid']
On this place it gets an error:
*** AttributeError: 'NoneType' object has no attribute 'users'
How to mock this stuff right?
Thank you!
Try this:
vk.Session = MagicMock(name='session', return_value=None)
mock_vk_values = [{'uid': user.vk_uid}]
# create an explicit mock for the users attribute
users_mock = MagicMock(name='users')
users_mock.get = MagicMock(name='uid', return_value=mock_vk_values)
# create a mock for the api
api_mock = MagicMock(name='api', users=users_mock)
# this is where a lot of people get mocking wrong -
# mistaking the mock of a constructor with the object returned/created
vk.API = MagicMock(return_value=api_mock)

What is the proper way to delineate modules and classes in Python?

I am new to Python, and I'm starting to learn the basics of the code structure. I've got a basic app that I'm working on up on my Github.
For my simple app, I'm create a basic "Evernote-like" service which allows the user to create and edit a list of notes. In the early design, I have a Note object and a Notepad object, which is effectively a list of notes. Presently, I have the following file structure:
Notes.py
|
|------ Notepad (class)
|------ Note (class)
From my current understanding and implementation, this translates into the "Notes" module having a Notepad class and Note class, so when I do an import, I'm saying "from Notes import Notepad / from Notes import Note".
Is this the right approach? I feel, out of Java habit, that I should have a folder for Notes and the two classes as individual files.
My goal here is to understand what the best practice is.
As long as the classes are rather small put them into one file.
You can still move them later, if necessary.
Actually, it is rather common for larger projects to have a rather deep hierarchy but expose a more flat one to the user. So if you move things later but would like still have notes.Note even though the class Note moved deeper, it would be simple to just import note.path.to.module.Note into notes and the user can get it from there. You don't have to do that but you can. So even if you change your mind later but would like to keep the API, no problem.
I've been working in a similar application myself. I can't say this is the best possible approach, but it served me well. The classes are meant to interact with the database (context) when the user makes a request (http request, this is a webapp).
# -*- coding: utf-8 -*-
import json
import datetime
class Note ():
"""A note. This class is part of the data model and is instantiated every
time there access to the database"""
def __init__(self, noteid = 0, note = "", date = datetime.datetime.now(), context = None):
self.id = noteid
self.note = note
self.date = date
self.ctx = context #context holds the db connection and some globals
def get(self):
"""Get the current object from the database. This function needs the
instance to have an id"""
if id == 0:
raise self.ctx.ApplicationError(404, ("No note with id 0 exists"))
cursor = self.ctx.db.conn.cursor()
cursor.execute("select note, date from %s.notes where id=%s" %
(self.ctx.db.DB_NAME, str(self.id)))
data = cursor.fetchone()
if not data:
raise self.ctx.ApplicationError(404, ("No note with id "
+ self.id + " was found"))
self.note = data[0]
self.date = data[1]
return self
def insert(self, user):
"""This function inserts the object to the database. It can be an empty
note. User must be authenticated to add notes (authentication handled
elsewhere)"""
cursor = self.ctx.db.conn.cursor()
query = ("insert into %s.notes (note, owner) values ('%s', '%s')" %
(self.ctx.db.DB_NAME, str(self.note), str(user['id'])))
cursor.execute(query)
return self
def put(self):
"""Modify the current note in the database"""
cursor = self.ctx.db.conn.cursor()
query = ("update %s.notes set note = '%s' where id = %s" %
(self.ctx.db.DB_NAME, str(self.note), str(self.id)))
cursor.execute(query)
return self
def delete(self):
"""Delete the current note, by id"""
if self.id == 0:
raise self.ctx.ApplicationError(404, "No note with id 0 exists")
cursor = self.ctx.db.conn.cursor()
query = ("delete from %s.notes where id = %s" %
(self.ctx.db.DB_NAME, str(self.id)))
cursor.execute(query)
def toJson(self):
"""Returns a json string of the note object's data attributes"""
return json.dumps(self.toDict())
def toDict(self):
"""Returns a dict of the note object's data attributes"""
return {
"id" : self.id,
"note" : self.note,
"date" : self.date.strftime("%Y-%m-%d %H:%M:%S")
}
class NotesCollection():
"""This class handles the notes as a collection"""
collection = []
def get(self, user, context):
"""Populate the collection object and return it"""
cursor = context.db.conn.cursor()
cursor.execute("select id, note, date from %s.notes where owner=%s" %
(context.db.DB_NAME, str(user["id"])))
note = cursor.fetchone()
while note:
self.collection.append(Note(note[0], note[1],note[2]))
note = cursor.fetchone()
return self
def toJson(self):
"""Return a json string of the current collection"""
return json.dumps([note.toDict() for note in self.collection])
I personally use python as a "get it done" language, and don't bother myself with details. This shows in the code above. However one piece of advice: There are no private variables nor methods in python, so don't bother trying to create them. Make your life easier, code fast, get it done
Usage example:
class NotesCollection(BaseHandler):
#tornado.web.authenticated
def get(self):
"""Retrieve all notes from the current user and return a json object"""
allNotes = Note.NotesCollection().get(self.get_current_user(), settings["context"])
json = allNotes.toJson()
self.write(json)
#protected
#tornado.web.authenticated
def post(self):
"""Handles all post requests to /notes"""
requestType = self.get_argument("type", "POST")
ctx = settings["context"]
if requestType == "POST":
Note.Note(note = self.get_argument("note", ""),
context = ctx).insert(self.get_current_user())
elif requestType == "DELETE":
Note.Note(id = self.get_argument("id"), context = ctx).delete()
elif requestType == "PUT":
Note.Note(id = self.get_argument("id"),
note = self.get_argument("note"),
context = ctx).put()
else:
raise ApplicationError(405, "Method not allowed")
By using decorators I'm getting user authentication and error handling out of the main code. This makes it clearer and easier to mantain.

Categories