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
+
Related
In my Flask app, I'm using Dependency Injection and here's what my app looks like. I have a service which uses S3 as a datastore and I'm trying to instantiate my app with the service injected (which is injected with the S3 client). However, it doesn't look like the S3 client is correctly instantiated or I'm doing something wildly different.
containers.py
class Container(containers.DeclarativeContainer):
wiring_config = containers.WiringConfiguration(modules=[".routes", ".scheduler"])
config = providers.Configuration()
s3_config = dict()
s3_config["path"], s3_config["filters"] = config.get("s3_bucket"), [("member_status_nm", "=", "ACTIVE")]
s3_repository = providers.Singleton(S3Repository, s3_config)
my_service = providers.Factory(
MyService, config, S3Repository
)
Here's my S3Repository:
import logging
import sys
import time
import some_library as lib
class S3Repository:
def __init__(self, s3_config):
self.path, self.columns, self.filters = \
s3_config.get("path", ""), s3_config.get("columns", []), s3_config.get("filters", [])
def fetch(self):
# execute fetch
result = lib.some_fetch_method(self.path, self.columns, self.filters)
return result
and MyService:
import #all relevant imports here
class MyService:
def __init__(self, config: dict, s3_repository: S3Repository) -> None:
logging.info("HealthSignalService(): initializing")
self.config = config["app"]["health_signal_service"]
# prepare s3_repository for the service
self.s3_repository = s3_repository
self.s3_repository.columns, self.s3_repository.filters, self.s3_repository.path = \
["x","y"], ["x1","y1"], "file_path"
def fetch_data(self) -> None:
try:
summary_result = self.s3_repository.fetch()
except (FileNotFoundError, IOError) as e:
print("failure")
return summary_result
def get_data(memberId):
sth = self.fetch_data()
return sth.get(memberId)
and finally tying it together in my routes.py:
#inject
#auth.login_required
def get_signals(
my_service: MyService = Provide[
Container.my_service
],
):
content = request.json
member_id = content["memberId"]
result = my_service.get_signals(member_id)
return jsonify(result)
When I hit my API endpoint I see this error:
summary_result = self.s3_repository.fetch()
TypeError: fetch() missing 1 required positional argument: 'self'
How do I correctly initialize my S3 client while using dependency injection?
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)
When using a Session, it seems you need to provide the full URL each time, e.g.
session = requests.Session()
session.get('http://myserver/getstuff')
session.get('http://myserver/getstuff2')
This gets a little tedious. Is there a way to do something like:
session = requests.Session(url_base='http://myserver')
session.get('/getstuff')
session.get('/getstuff2')
This feature has been asked on the forums a few times 1, 2, 3. The preferred approach as documented here, is subclassing, as follows:
from requests import Session
from urllib.parse import urljoin
class LiveServerSession(Session):
def __init__(self, base_url=None):
super().__init__()
self.base_url = base_url
def request(self, method, url, *args, **kwargs):
joined_url = urljoin(self.base_url, url)
return super().request(method, joined_url, *args, **kwargs)
You would use this simply as follows:
baseUrl = 'http://api.twitter.com'
with LiveServerSession(baseUrl) as s:
resp = s.get('/1/statuses/home_timeline.json')
requests_toolbelt.sessions.BaseUrlSession
https://github.com/requests/toolbelt/blob/f5c86c51e0a01fbc8b3b4e1c286fd5c7cb3aacfa/requests_toolbelt/sessions.py#L6
NOTE: This uses urljoin from standard lib. Beware of urljoin's behavior.
In [14]: from urlparse import urljoin
In [15]: urljoin('https://localhost/api', '/resource')
Out[15]: 'https://localhost/resource'
In [16]: urljoin('https://localhost/api', 'resource')
Out[16]: 'https://localhost/resource'
In [17]: urljoin('https://localhost/api/', '/resource')
Out[17]: 'https://localhost/resource'
In [18]: urljoin('https://localhost/api/', 'resource')
Out[18]: 'https://localhost/api/resource'
OR
import requests
from functools import partial
def PrefixUrlSession(prefix=None):
if prefix is None:
prefix = ""
else:
prefix = prefix.rstrip('/') + '/'
def new_request(prefix, f, method, url, *args, **kwargs):
return f(method, prefix + url, *args, **kwargs)
s = requests.Session()
s.request = partial(new_request, prefix, s.request)
return s
You could just subclass request.Session and overload its __init__ and request methods like this:
# my_requests.py
import requests
class SessionWithUrlBase(requests.Session):
# In Python 3 you could place `url_base` after `*args`, but not in Python 2.
def __init__(self, url_base=None, *args, **kwargs):
super(SessionWithUrlBase, self).__init__(*args, **kwargs)
self.url_base = url_base
def request(self, method, url, **kwargs):
# Next line of code is here for example purposes only.
# You really shouldn't just use string concatenation here,
# take a look at urllib.parse.urljoin instead.
modified_url = self.url_base + url
return super(SessionWithUrlBase, self).request(method, modified_url, **kwargs)
And then you could use your subclass instead of requests.Session in your code:
from my_requests import SessionWithUrlBase
session = SessionWithUrlBase(url_base='https://stackoverflow.com/')
session.get('documentation') # https://stackoverflow.com/documentation
Also you could monkey-patch requests.Session to avoid modifying existing codebase (this implementation should be 100% compatible), but be sure to do actual patching before any code calls requests.Session():
# monkey_patch.py
import requests
class SessionWithUrlBase(requests.Session):
...
requests.Session = SessionWithUrlBase
And then:
# main.py
import requests
import monkey_patch
session = requests.Session()
repr(session) # <monkey_patch.SessionWithUrlBase object at ...>
I don't see a built-in way to do this, but you can use wrapper functions to add the functionality you want:
from functools import wraps
import inspect
import requests
from requests.compat import urljoin
def _base_url(func, base):
'''Decorator for adding a base URL to func's url parameter'''
#wraps(func)
def wrapper(*args, **kwargs):
argname = 'url'
argspec = inspect.getargspec(func)
if argname in kwargs:
kwargs[argname] = urljoin(base, kwargs[argname])
else:
# Find and replace url parameter in positional args. The argspec
# includes self while args doesn't, so indexes have to be shifted
# over one
for i, name in enumerate(argspec[0]):
if name == argname:
args = list(args)
args[i-1] = urljoin(base, args[i-1])
break
return func(*args, **kwargs)
return wrapper
def inject_base_url(func):
'''Decorator for adding a base URL to all methods that take a url param'''
#wraps(func)
def wrapper(*args, **kwargs):
argname = 'base_url'
if argname in kwargs:
obj = args[0]
# Add base_url decorator to all methods that have a url parameter
for name, method in inspect.getmembers(obj, inspect.ismethod):
argspec = inspect.getargspec(method.__func__)
if 'url' in argspec[0]:
setattr(obj, name, _base_url(method, kwargs[argname]))
del kwargs[argname]
return func(*args, **kwargs)
return wrapper
# Wrap requests.Session.__init__ so it takes a base_url parameter
setattr(
requests.Session,
'__init__',
inject_base_url(getattr(requests.Session, '__init__'))
)
Now you can specify a base URL when you construct a new requests.Session object:
s = requests.Session(base_url='http://stackoverflow.com')
s.get('questions') # http://stackoverflow.com/questions
s.post('documentation') # http://stackoverflow.com/documentation
# With no base_url, you get the default behavior
s = requests.Session()
s.get('http://google.com')
keep it simple and use builtin methods for joining (no '/' suffix hassle):
import urllib.parse
session = requests.Session()
session.my_base_url_join = lambda path: urllib.parse.urljoin(str_BASE_URL, path)
# use: session.get(session.my_base_url_join(path='/message'))
This is possible with the requests-toolbelt package, which enables setting a base_url at the session level:
from requests_toolbelt import sessions
s = sessions.BaseUrlSession(
base_url='https://example.com/resource/')
r = s.get('sub-resource/', params={'foo': 'bar'})
>>> print(r.request.url)
https://example.com/resource/sub-resource/?foo=bar
see documentation for BaseUrlSession.
Based on #qrtLs answer, here is a 3-4 line version that does what you want (assuming you don't need to create multiple sessions and only need the get method defined).
import requests
import urllib.parse
session = requests.Session()
session.base_url = "https://google.com/"
session.get = lambda *args, **kwargs: requests.Session.get(session, urllib.parse.urljoin(s.base_url, args[0]), *args[1:], **kwargs)
r = session.get("/search?q=asdf", verify=False)
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):
...
I have couple of classes in jira.py, providing 2 for sample
class JiraCommand:
name = "<default>"
aliases = []
summary = "<--- no summary --->"
usage = ""
mandatory = ""
commands = None
def __init__(self, commands):
self.commands = commands
def dispatch(self, logger, jira_env, args):
"""Return the exit code of the whole process"""
if len(args) > 0 and args[0] in ("--help", "-h"):
logger.info("")
alias_text = ''
first_alias = True
for a in self.aliases:
if first_alias:
if len(self.aliases) == 1:
alias_text = " (alias: " + a
else:
alias_text = " (aliases: " + a
first_alias = False
else:
alias_text += ", " + a
if not first_alias:
alias_text += ")"
logger.info("%s: %s%s" % (self.name, self.summary, alias_text))
if self.usage == "":
opts = ""
else:
opts = " [options]"
logger.info("")
logger.info("Usage: %s %s %s%s" % \
(sys.argv[0], self.name, self.mandatory, opts))
logger.info(self.usage)
return 0
results = self.run(logger, jira_env, args)
if results:
return self.render(logger, jira_env, args, results)
else:
return 1
def run(self, logger, jira_env, args):
"""Return a non-zero object for success"""
return 0
def render(self, logger, jira_env, args, results):
"""Return 0 for success"""
return 0
and a second class in the same file "jira.py"
class JiraCat(JiraCommand):
name = "cat"
summary = "Show all the fields in an issue"
usage = """
<issue key> Issue identifier, e.g. CA-1234
"""
def run(self, logger, jira_env, args):
global soap, auth
if len(args) != 1:
logger.error(self.usage)
return 0
issueKey = args[0]
try:
jira_env['fieldnames'] = soap.service.getFieldsForEdit(auth, issueKey)
except Exception, e:
# In case we don't have edit permission
jira_env['fieldnames'] = {}
try:
return soap.service.getIssue(auth, issueKey)
except Exception, e:
logger.error(decode(e))
def render(self, logger, jira_env, args, results):
# For available field names, see the variables in
# src/java/com/atlassian/jira/rpc/soap/beans/RemoteIssue.java
fields = jira_env['fieldnames']
for f in ['key','summary','reporter','assignee','description',
'environment','project',
'votes'
]:
logger.info(getName(f, fields) + ': ' + encode(results[f]))
logger.info('Type: ' + getName(results['type'], jira_env['types']))
logger.info('Status: ' + getName(results['status'], jira_env['statuses']))
logger.info('Priority: ' + getName(results['priority'], jira_env['priorities']))
logger.info('Resolution: ' + getName(results['resolution'], jira_env['resolutions']))
for f in ['created', 'updated',
'duedate'
]:
logger.info(getName(f, fields) + ': ' + dateStr(results[f]))
for f in results['components']:
logger.info(getName('components', fields) + ': ' + encode(f['name']))
for f in results['affectsVersions']:
logger.info(getName('versions', fields) + ': ' + encode(f['name']))
for f in results['fixVersions']:
logger.info('Fix Version/s:' + encode(f['name']))
# TODO bug in JIRA api - attachmentNames are not returned
#logger.info(str(results['attachmentNames']))
# TODO restrict some of the fields that are shown here
for f in results['customFieldValues']:
fieldName = str(f['customfieldId'])
for v in f['values']:
logger.info(getName(fieldName, fields) + ': ' + encode(v))
return 0
Now, JiraCat is using JiraCommand as an argument
How can i use JiraCat to get live results
here is what i tried:
>>> from jira import JiraCommand
>>> dir(JiraCommand)
['__doc__', '__init__', '__module__', 'aliases', 'commands', 'dispatch', 'mandatory', 'name', 'render', 'run', 'summary', 'usage']
>>> jcmd = JiraCommand("http://jira.server.com:8080")
>>> from jira import JiraCat
>>> dir(JiraCat)
['__doc__', '__init__', '__module__', 'aliases', 'commands', 'dispatch', 'mandatory', 'name', 'render', 'run', 'summary', 'usage']
>>> jc = JiraCat(jcmd)
>>> print jc
<jira.JiraCat instance at 0x2356d88>
>>> jc.run("-s", "cat", "QA-65")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "jira.py", line 163, in run
logger.error(self.usage)
AttributeError: 'str' object has no attribute 'error'
DonCallisto has got it right.
JiraCat's run method takes three arguments (logger, jira_env, args); the first one is supposed to be a logger object but you're passing a string ("-s").
So the error that reports a string (logger="-s") has no "error" attribute means just that.
Your comment about the command line (subprocess.Popen(['python', 'jira', '-s', 'jira.server.com:8080';, 'catall', 'JIRA-65'])) is not the same as calling the run() method with the same arguments. Have a look at the bottom of jira.py and see what it does with sys.argv...
Edit (1):
Having read the code, the following python should replicate your command line call. It's a bit complicated, and misses out all the exception handling and logic in jira.py itself, which could get flaky, and I can't test it here.
import jira
import os
com = jira.Commands()
logger = jira.setupLogging()
jira_env = {'home':os.environ['HOME']}
command_name = "cat"
my_args = ["JIRA-65"]
server = "http://jira.server.com:8080" + "/rpc/soap/jirasoapservice-v2?wsdl"
class Options:
pass
options = Options()
#You might want to set options.user and options.password here...
jira.soap = jira.Client(server)
jira.start_login(options, jira_env, command_name, com, logger)
com.run(command_name, logger, jira_env, my_args)