I am using a class based service in python and I get error whenever I want to use it. Unable to figure out the reason.
#!/usr/bin/python
# -*- coding: utf-8 -*-
from xml.dom import minidom
from pysimplesoap.client import SoapClient
from pysimplesoap.helpers import sort_dict
MEDIA_ROOT = '/User/sunand/documents/resumes/'
parser = ResumeParser()
names = parser.get_names(MEDIA_ROOT)
print names
class ParserClient(SoapClient):
""" Extends the soap client to encode the response with utf-8 encoding.
"""
def wsdl_call(
self,
method,
*args,
**kwargs
):
""" Override wsdl_call method to make sure unmarshall is not called.
"""
operation = self.get_operation(method)
# get i/o type declarations:
inp = operation['input']
header = operation.get('header')
if 'action' in operation:
self.action = operation['action']
# construct header and parameters
if header:
self.__call_headers = sort_dict(header, self.__headers)
(method, params) = self.wsdl_call_get_params(method, inp,
*args, **kwargs)
response = self.call(method, *params)
return response
def send(self, method, xml):
""" Overrides the send method to get the actual xml content.
"""
content = super(ParserClient, self).send(method, xml)
self.result = content
return content
class ResumeParser(object):
""" Connects to the Resume Parser's XML api to get parsed data.
"""
def __init__(self, simple=True, timeout=60):
""" Initializes the ResumeParser class.
"""
self.wsdl = \
'http://jobsite.onlineresumeparser.com/rPlusParseResume.asmx?WSDL'
self.secret = 'my-secret-key' # Enter key here
self.encoding = 'base64'
self.simple = simple
self.client = ParserClient(wsdl=self.wsdl, timeout=timeout)
self.names = []
def get_file_content(self, file_path):
""" Return the encoded content for the given file.
"""
file_obj = open(os.path.abspath(file_path), 'r')
content = file_obj.read().encode(self.encoding)
file_obj.close()
return content
def get_names(self, path):
"""
Given a path to a folder that contains resume files this method
will parse the resumes and will return the names of the candidates
as a list.
"""
opt = os.path
resumes = [opt.join(path, r) for r in os.listdir(path)
if opt.isfile(opt.join(path, r))]
# Parse information for each resume.
for resume in resumes:
try:
xml_data = self.get_xml(resume)
name = self.get_name_from_xml(xml_data)
if name:
self.names.append(name)
except Exception, err:
# print name
print 'Error parsing resume: %s' % str(err)
return list(set(self.names))
def get_name_from_xml(self, data):
""" Returns the full name from the xml data given.
"""
xmldata = minidom.parseString(data)
name = xmldata.getElementsByTagName('CANDIDATE_FULL_NAME')
name = name[0].childNodes[0].data.title()
return name
def get_xml(self, filepath):
""" Fetches and returns the xml for the given file from the api.
"""
filename = os.path.basename(filepath)
extension = os.path.splitext(filepath)[1]
base64 = self.get_file_content(filepath)
filedata = {
'B64FileZippedContent': base64,
'FileName': filename,
'InputType': extension,
'UserID': 1,
'secretKey': self.secret,
}
get = \
(self.client.GetSimpleXML if self.simple else self.client.getHRXML)
get(**filedata)
return self.process_raw_xml()
def process_raw_xml(self, data=None):
""" Processes and returns the clean XML.
"""
raw = (data if data else self.client.result)
parsed = minidom.parseString(raw)
result = parsed.getElementsByTagName('GetSimpleXMLResult')[0]
text_node = result.childNodes[0]
data = text_node.data.encode('UTF-8')
return data
Upon running the code I am getting an error
TypeError: wsdl_call_get_params() got an unexpected keyword argument 'secretKey'
What am I doing wrong?
It looks like you are incorrectly overriding wsdl_call.
Firstly, we can see that SoapClient (which you extend in ParserClient), has a __getattr__ function that fetches pseudo-attributes of the SoapClient.
def __getattr__(self, attr):
"Return a pseudo-method that can be called"
if not self.services: # not using WSDL?
return lambda self=self, *args, **kwargs: self.call(attr,*args,**kwargs)
else: # using WSDL:
return lambda *args, **kwargs: self.wsdl_call(attr,*args,**kwargs)
You can see that this function is using wsdl_call to help it map functions to unknown attributes.
The specific pseudo-method that is causing the problem is in your code (or appears to be):
filedata = {
'B64FileZippedContent': base64,
'FileName': filename,
'InputType': extension,
'UserID': 1,
'secretKey': self.secret, # <-- the secretKey key word argument
}
get = \
(self.client.GetSimpleXML if self.simple else self.client.getHRXML)
get(**filedata)
# here client is an instance of your `ParserClient` (and `SoapClient`).
This above bit took me a while to track down. With a full stack trace I would have found it much quicker. Please always post stack traces (when there is one) in future when asking for help.
How to solve this
Provide a concrete implementation of GetSimpleXML and getHRXML. This will solve the immediate problem, but not the larger problem.
Rewrite wsdl_call
The rewritten section of code should check the value of the method argument and either do what you want, or delegate to the SoapClient implementation.
eg.
def wsdl_call(self, method, *args, **kwargs):
if method == "some_method":
return self._my_wsdl_call(method, *args, **kwargs)
else:
return super(ParserClient, self).wsdl_call(method, *args, **kwargs)
def _my_wsdl_call(self, method, *args, **kwargs):
...
Related
I have module billing/billing-collect-project-license which have LicenseStatistics class. Which have calls to Redis, ORMRDS, CE are other modules which used in this class. Following is LicenseStatistics class where get_allocate_count is instance method which calls to ce_obj.get_set_ce_obj, get_license_id and many others.
The get_license_id method calls get_customer_details.
class LicenseStatistics():
"""This class encapsulates all logic related to license usage."""
def __init__(self):
self.logger = LOGGER
# RedisNode List should be in the following format:
# HOST1:PORT1,HOST2:PORT2,HOST3:PORT3 etc
redis_node_list = os.environ.get('REDIS_NODE_LIST', '').split(',')
self.redis_utils = RedisUtils(redis_node_list)
# DB object to read customer details
dbhost = os.environ.get('DBHOST')
dbuser = os.environ.get('DBUSER')
dbpass = os.environ.get('DBPASSWORD')
dbname = os.environ.get('DBNAME')
dbport = os.environ.get('DBPORT')
self.dbutils = ORMDBUtils(dbhost, dbuser, dbpass, dbname, dbport)
self.ce_obj = CE()
self.bill = Billing()
def get_license_id(self, project_id):
"""
#Summary: Get License Id for customer/project from customer table by
project id
#param project_id (string): CE project Id
#return (string): CE License Id which associate with Project.
"""
# Get license ID from RDS
customer_details = self.get_customer_details(project_id)
print("customer_details:", customer_details)
license_id = customer_details["license_id"]
if not license_id:
msg = "No license for project {}".format(project_id)
self.logger.error(msg)
raise InvalidParamException(msg)
print("license_id:", license_id)
return license_id
def get_customer_details(self, project_id):
"""
#Summary: Get Customer/Project details from customer table
#param project_id (string): CE project Id
#return (dictionary): Customer details from customer table.
"""
filters = {"project_id": project_id}
customer_details = self.dbutils.get_items(
table_name=RDSCustomerTable.TABLE_NAME.value,
columns_to_select=["account_id", "license_id"],
filters=filters
)
if not customer_details:
msg = "No customer found for project {}".format(project_id)
self.logger.error(msg)
raise NoCustomerException(msg)
return customer_details[0]
def is_shared_license(self, license_id):
# This function return True or False
pass
def get_project_machines_count(self, project_id, license_id):
# This function return number of used license.
count = 20
return count
def get_license_usage(self, project_id, license_id):
# This function return number of machines used project.
count = 10
return count
def get_allocate_count(self, project_id):
"""
#Summary: Get number of licenses are used by Project.
#param project_id (string): CloudEndure Project Id.
#return (int): Number of license are used in Project.
"""
# set Session get_customer_detailsfrom Redis
status = self.ce_obj.get_set_ce_obj(
project_id=project_id, redis_utils=self.redis_utils
)
print("license_id status--:", status)
if not status:
msg = "Unable to set CEproject {}".format(project_id)
self.logger.critical(msg)
raise InvalidParamException(msg, "project_id", project_id)
print("project_id------------:", project_id)
# Get license Id
license_id = self.get_license_id(project_id)
print("license_id:", license_id)
# Check license is shared
shared_flag = self.is_shared_license(license_id)
if not shared_flag:
# Get license usage
licenses_used = self.get_license_usage(project_id, license_id)
else:
# Get machine account
licenses_used = self.get_project_machines_count(
project_id, license_id
)
return licenses_used
I am writing unit test for get_allocate_count method, I mock the Redis, ORMRDS, Custom Exception, Logger.
This function call ce_obj.get_set_ce_obj function which return True/False. I am to mock/patch return value of this function successfully.
But when call goes to next function call i.e. get_license_id, call goes into actual function call and due to improper inputs. I am not able to patch/mock
Following is unit test code:
import responses
import unittest
from unittest.mock import patch
import os
import sys
cwd_path = os.getcwd()
sys.path.append(cwd_path)
sys.path.append(cwd_path+"/../sam-cfns/code")
sys.path.append(cwd_path+"/../sam-cfns/code/billing")
from unit_tests.common.mocks.env_mock import ENV_VAR
from unit_tests.common.mocks.logger import FakeLogger
from unit_tests.common.mocks.cache_mock import RedisUtilsMock
from unit_tests.common.mocks.ormdb_mock import ORMDBUtilsMockProject
from unit_tests.common.mocks.exceptions_mock import NoCustomerExceptionMock
from unit_tests.common.mocks.exceptions_mock import BillingExceptionMock
from unit_tests.common.mocks.exceptions_mock import InvalidParamExceptionMock
from unit_tests.common.mocks.api_responses import mock_response
from unit_tests.common.examples import ce_machines_data
from unit_tests.common.examples import ce_license_data
from unit_tests.common.examples import ce_data
class BillingTest(unittest.TestCase):
""" Billing TEST class drive from UnitTest """
#patch("billing-collect-project-license.Logger", FakeLogger)
#patch("os.environ", ENV_VAR)
#patch("billing-collect-project-license.RedisUtils", RedisUtilsMock)
#patch("billing-collect-project-license.ORMDBUtils", ORMDBUtilsMockProject)
#patch("exceptions.NoCustomerException", NoCustomerExceptionMock)
#patch("billing.billing_exceptions.BillingException", BillingExceptionMock)
#patch("billing.billing_exceptions.InvalidParamException", InvalidParamExceptionMock)
def __init__(self, *args, **kwargs):
"""Initialization"""
super(BillingTest, self).__init__(*args, **kwargs)
billing_collect_project_license_module = (
__import__("cbr-billing-collect-project-license")
)
self.licenses_stats_obj = (
billing_collect_project_license_module.LicenseStatistics()
)
class BillingCollectProjectLicense(BillingTest):
"""Login Unit Test Cases"""
def __init__(self, *args, **kwargs):
"""Initialization"""
super(BillingCollectProjectLicense, self).__init__(*args, **kwargs)
def setUp(self):
"""Setup for all Test Cases."""
pass
##patch("billing.cbr-billing-collect-project-license.LicenseStatistics."
# "get_project_machines_count")
##patch("billing.cbr-billing-collect-project-license.LicenseStatistics."
# "get_customer_details")
##patch("billing.cbr-billing-collect-project-license.LicenseStatistics.get_license_id")
#patch("billing.cbr-billing-collect-project-license.LicenseStatistics.get_license_id")
#patch("cbr.ce.CloudEndure.get_set_ce_obj")
def test_get_allocate_count(self, get_set_ce_obj_mock, get_license_id_mock):
project_id = ce_data.CE_PROJECT_ID
license_id = ce_license_data.LICENSE_ID
get_set_ce_obj_mock.return_value = True
get_license_id_mock.return_value = license_id
# LicenseStatistics_mock.return_value.get_license_id.return_value = license_id
#get_license_id_mock.return_value = license_id
# self.licenses_stats_obj.get_license_id = get_license_id_mock
get_customer_details_mock = {"license_id": license_id}
# is_shared_license_mock.return_value = True
# get_project_machines_count_mock.return_value = 20
resp = self.licenses_stats_obj.get_allocate_count(
project_id
)
self.assertEqual(resp, 20)
if __name__ == '__main__':
unittest.main()
I am not able to patch get_license_id function from same class. This actually calling get_license_id function and fails.
I want to mock return value of get_license_id function.
Anyone help help me?
Thank you.
Issue is I am initializing it in the init, so monkeypatching methods of LicenseStatistics class later have no effect on the already created instance. #hoefling
By monkey Patching I able to run Test Cases successfully.
sample code:
def test_get_allocate_count_ok_4(self, ):
"""
#Summary: Test case for successful response for shared license
by other unittest method - Monkey Patching
"""
def get_customer_details_mp(_):
"""Monkey Patching function for get_customer_details"""
data = [
{
"account_id": "abc",
"project_id": "abc",
"license_id": "abc",
"status": "Active"
}
]
return data
def get_set_ce_obj_mp(_, _tmp):
"""Monkey Patching function for get_set_ce_obj"""
return True
def get_license_id_mp(_):
"""Monkey Patching function for get_license_id"""
return "abc"
def is_shared_license_mp(_):
"""Monkey Patching function for is_shared_license"""
return True
def get_project_machines_count_mp(_, _license_id):
"""Monkey Patching function for get_project_machines_count"""
return 5
project_id = "abc"
# Monkey Patching
self.licenses_stats_obj.get_customer_details = get_customer_details_mp
self.licenses_stats_obj.ce_obj.get_set_ce_obj = get_set_ce_obj_mp
self.licenses_stats_obj.get_license_id = get_license_id_mp
self.licenses_stats_obj.is_shared_license = is_shared_license_mp
self.licenses_stats_obj.get_project_machines_count = (
get_project_machines_count_mp
)
resp = self.licenses_stats_obj.get_allocate_count(project_id)
self.assertEqual(resp, 5)
I'm trying to create a custom Django RSS feed using django syndication (actually using django wagtail feeds). I have an error which I think I've identified as stemming from a NoneType object which is returned by the get_object() function inside syndication/views.py.
`AttributeError at /feed/basic/Chups/
'NoneType' object has no attribute 'startswith'
Exception Location: /Users/technical/.virtualenvs/wagtest4-plnzODoN/lib/python3.6/site-packages/django/contrib/syndication/views.py in add_domain, line 19`
That function is called as part of class Feed() and looks like this:
def get_object(self, request, *args, **kwargs):
return None
That function is called at line 36 but fails because get_object() returns a None object.
My customisation of django wagtail feeds extends Feed in the following way:
from django.contrib.syndication.views import Feed
from django.utils.feedgenerator import (
SyndicationFeed, rfc3339_date, Rss201rev2Feed
)
from .models import RSSFeedsSettings, RSSFeed
class BasicFeed(Feed):
# FEED TYPE
feed_type = Rss201rev2Feed
def get_object(self, request, category):
return category
try:
feed_app_settings = RSSFeedsSettings.objects.get(feed_category_name="Flex")
print(feed_app_settings)
feed_app_label = feed_app_settings.feed_app_label
feed_model_name = feed_app_settings.feed_model_name
feed_category_name = feed_app_settings.feed_category_name
use_feed_image = feed_app_settings.feed_image_in_content
except: # pragma: no cover
feed_app_settings = None
try:
feed_model = apps.get_model(app_label=feed_app_label,
model_name=feed_model_name)
except: # pragma: no cover
feed_model = None
# The RSS information that gets shown at the top of the feed.
if feed_app_settings is not None:
title = feed_app_settings.feed_title
link = feed_app_settings.feed_link
description = feed_app_settings.feed_description
author_email = feed_app_settings.feed_author_email
author_link = feed_app_settings.feed_author_link
item_description_field = feed_app_settings.feed_item_description_field
item_content_field = feed_app_settings.feed_item_content_field
def items(self, obj):
url_category = obj
categories = ContentType(app_label="blog", model="blogcategory")
category_id = categories.get_object_for_this_type(name=url_category).id
return feed_model.objects.filter(categories=category_id).order_by('-date').live()
def item_pubdate(self, item):
return datetime.combine(item.date, time())
def item_link(self, item):
return item.full_url
def item_author_name(self, item):
pass
urls.py includes this and requests seem to be reaching the function fine.
url(r'^feed/basic/(?P<category>[0-9a-zA-Z]+)/$', BasicFeed(), name='basic_feed'),
Can anyone tell me why that might be? I'm missing something about the expected functioning of this. Thanks!
I'm looking at the tastypie caching docs and trying to set up my own simple caching thing, but the cache doesn't seem to get called. When I visit http://localhost:8000/api/poll/?format=json, I get my tastypie generated json, but I don't get the output from the cache class.
from tastypie.resources import ModelResource
from tastypie.cache import NoCache
from .models import Poll
class JSONCache(NoCache):
def _load(self):
print 'loading cache'
data_file = open(settings.TASTYPIE_JSON_CACHE, 'r')
return json.load(data_file)
def _save(self, data):
print 'saving to cache'
data_file = open(settings.TASTYPIE_JSON_CACHE, 'w')
return json.dump(data, data_file)
def get(self, key):
print 'jsoncache.get'
data = self._load()
return data.get(key, None)
def set(self, key, value, timeout=60):
print 'jsoncache.set'
data = self._load()
data[key] = value
self._save(data)
class PollResource(ModelResource):
class Meta:
queryset = Poll.objects.all()
resource_name = 'poll'
cache = JSONCache()
It seems that Tastypie doesn't automatically cache lists, tastypie.resources around line 1027:
def get_list(self, request, **kwargs):
# ...
# TODO: Uncached for now. Invalidation that works for everyone may be
# impossible.
objects = self.obj_get_list(
request=request, **self.remove_api_resource_names(kwargs))
# ...
, whereas with details (around line 1050):
def get_detail(self, request, **kwargs):
# ...
try:
obj = self.cached_obj_get(
request=request, **self.remove_api_resource_names(kwargs))
# ...
... note that in the former snippet obj_get_list is called instead of cached_obj_get_list. Perhaps overriding get_list and using cached_obj_get_list would allow you to use cache here as well?
Now you probably would get output from your class for http://localhost:8000/api/poll/<pk>/?format=json (detail view) but not for http://localhost:8000/api/poll/?format=json (list view) by default.
I have to do soap request with suds and Python
<soap:Body>
<registerOrder>
<order merchantOrderNumber="" description="" amount="" currency="" language="" xmlns="">
<returnUrl>http://mysafety.com</returnUrl>
</order>
</registerOrder>
</soap:Body>
How to add an attribute in registerOrder?
A more dynamic version of the MessagePlugin would be:
from suds.sax.attribute import Attribute
from suds.plugin import MessagePlugin
class _AttributePlugin(MessagePlugin):
"""
Suds plug-in extending the method call with arbitrary attributes.
"""
def __init__(self, **kwargs):
self.kwargs = kwargs
def marshalled(self, context):
method = context.envelope.getChild('Body')[0]
for key, item in self.kwargs.iteritems():
method.attributes.append(Attribute(key, item))
Usage:
client = Client(url)
# method 1
client.options.plugins = [_AttributePlugin(foo='bar')]
response = client.service.method1()
client.options.plugins = []
# method 2
response = client.service.method2()
In suds documentation search for a MessagePlugin. The marshalled option is what you're searching for. You need to add it into your client as a plugin:
self.client = Client(url, plugins=[MyPlugin()])
In marshalled method search for context.envelope childs. The python's vars() function is very useful in this place. As I think, It should like something like this for you:
from suds.sax.attribute import Attribute
from suds.plugin import MessagePlugin
class MyPlugin(MessagePlugin):
def marshalled(self, context):
foo = context.envelope.getChild('Body').getChild('registerOrder')[0]
foo.attributes.append(Attribute("foo", "bar"))
I was sitting at this for last week, so might it'll save some time for you :)
You can use the __inject Client option to inject a particular xml
raw_xml = """<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope>
<SOAP-ENV:Body>
...
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>"""
print client.service.example(__inject={'msg':raw_xml})
Also, I prefer using suds-jurko https://pypi.python.org/pypi/suds-jurko/0.6 which is a fork of suds that is actively maintained.
I have reused https://fedorahosted.org/suds/ticket/21 and have adapted the code to use the idea. Change SUDS as below and use
Client.<method>(param1=value1, ... , attributes={'attrName1':'attrVal1'} )
to call 'method' with the 'attrName1' attribute as wanted.
--- a/website/suds/bindings/binding.py
+++ b/website/suds/bindings/binding.py
## -24,6 +24,7 ## from suds.sax import Namespace
from suds.sax.parser import Parser
from suds.sax.document import Document
from suds.sax.element import Element
+from suds.sax.attribute import Attribute
from suds.sudsobject import Factory, Object
from suds.mx import Content
from suds.mx.literal import Literal as MxLiteral
## -101,7 +102,7 ## class Binding:
"""
raise Exception, 'not implemented'
- def get_message(self, method, args, kwargs):
+ def get_message(self, method, args, kwargs, attributes=None):
"""
Get the soap message for the specified method, args and soapheaders.
This is the entry point for creating the outbound soap message.
## -115,11 +116,23 ## class Binding:
#rtype: L{Document}
"""
+ if attributes:
+ pass
+ # moved to suds/bindings/document.py
+
+ #print method
+ #for name, val in attributes.items():
+ # method.attributes.append(Attribute(name, val))
+
+
content = self.headercontent(method)
header = self.header(content)
- content = self.bodycontent(method, args, kwargs)
+ content = self.bodycontent(method, args, kwargs, attributes=attributes)
body = self.body(content)
env = self.envelope(header, body)
+ #if attributes:
+ # print content
+ # 1/0
if self.options().prefixes:
body.normalizePrefixes()
env.promotePrefixes()
## -535,4 +548,4 ## class PartElement(SchemaElement):
return self
else:
return self.__resolved
-
\ No newline at end of file
+
diff --git a/website/suds/bindings/document.py b/website/suds/bindings/document.py
index edd9422..0c84753 100644
--- a/website/suds/bindings/document.py
+++ b/website/suds/bindings/document.py
## -38,7 +38,7 ## class Document(Binding):
(multiple message parts), must present a I{document} view for that method.
"""
- def bodycontent(self, method, args, kwargs):
+ def bodycontent(self, method, args, kwargs, attributes=None):
#
# The I{wrapped} vs I{bare} style is detected in 2 ways.
# If there is 2+ parts in the message then it is I{bare}.
## -54,6 +54,12 ## class Document(Binding):
else:
root = []
n = 0
+
+ if attributes:
+ #print root.__class__
+ for name, val in attributes.items():
+ root.set(name, val)
+
for pd in self.param_defs(method):
if n < len(args):
value = args[n]
diff --git a/website/suds/client.py b/website/suds/client.py
index 8b4f258..f80e36a 100644
--- a/website/suds/client.py
+++ b/website/suds/client.py
## -592,7 +592,10 ## class SoapClient:
timer.start()
result = None
binding = self.method.binding.input
- soapenv = binding.get_message(self.method, args, kwargs)
+ attributes = kwargs.get('attributes', None)
+ if attributes:
+ del kwargs['attributes']
+ soapenv = binding.get_message(self.method, args, kwargs, attributes)
timer.stop()
metrics.log.debug(
"message for '%s' created: %s",
## -841,4 +844,4 ## class RequestContext:
#type error: A suds I{TransportError}.
"""
return self.client.failed(self.binding, error)
-
\ No newline at end of file
+
I was wondering if it is possible to create an upload function to upload picture through my own site to the gravatar site?
Yes, this is possible. See http://en.gravatar.com/site/implement/xmlrpc/ , specifically the grav.saveData or grav.SaveUrl calls.
Yes it's possible!
import base64
from xmlrpclib import ServerProxy, Fault
from hashlib import md5
class GravatarXMLRPC(object):
API_URI = 'https://secure.gravatar.com/xmlrpc?user={0}'
def __init__(self, request, password=''):
self.request = request
self.password = password
self.email = sanitize_email(request.user.email)
self.email_hash = md5_hash(self.email)
self._server = ServerProxy(
self.API_URI.format(self.email_hash))
def saveData(self, image):
""" Save binary image data as a userimage for this account """
params = { 'data': base64_encode(image.read()), 'rating': 0, }
return self._call('saveData', params)
#return self.useUserimage(image)
def _call(self, method, params={}):
""" Call a method from the API, gets 'grav.' prepended to it. """
args = { 'password': self.password, }
args.update(params)
try:
return getattr(self._server, 'grav.' + method, None)(args)
except Fault as error:
error_msg = "Server error: {1} (error code: {0})"
print error_msg.format(error.faultCode, error.faultString)
def base64_encode(obj):
return base64.b64encode(obj)
def sanitize_email(email):
return email.lower().strip()
def md5_hash(string):
return md5(string.encode('utf-8')).hexdigest()
Just call the class in your view :)