02-17-2020 10:12 PM
Hi All,
Im using Python 3.6.7 / Eclipse IDE 4.1.3.0 / CUCM 10.5 AXL
I am new to all of the above.
I am attempting to write a script to use 'updatephone' to just change the device description of a phone.
This is a learning step to help me become more familiar with the environments so I can ultimately create, basically, an axl-based supercopy.
But in the simple version I am only attempting to:
connect to call manager
pass a mac address (really the phone name)
return UUID and description of the device using 'listphone'
... all of the above works
then using the UUID from above, use 'updatephone' to change the device description.
However when it hits the updatephone, it bombs - I'll provide some test code and error msgs below.
Since this is supposed to be a learning experience, something (along with the vast majority of the universe) Im not grasping, is when I should be calling 'updatephone' vs 'updatephonereq'. Im finding references to both, and since its difficult finding simultaneous references to AXL and python, with good examples - Im having trouble grasping the basics. Can someone enlighten me on this point?
Also, where can I find a good schema reference for 10.5? I have the xsd but its somewhat difficult to use.
ok now for the code - it probably will look a little goofy cause its extracted from a larger code-set and Ive been dumping debug junk in and out of it, and trying different things:
from test.test_importlib.util import CASE_INSENSITIVE_FS from zeep import Client from zeep.cache import SqliteCache from zeep.transports import Transport from zeep.exceptions import Fault from zeep.plugins import HistoryPlugin from requests import Session from requests.auth import HTTPBasicAuth from urllib3 import disable_warnings from urllib3.exceptions import InsecureRequestWarning from lxml import etree disable_warnings(InsecureRequestWarning) username = 'administrator' password = 'mysecretpassword' # If you're not disabling SSL verification, host should be the FQDN of the server rather than IP host = 'LabCM.corp.mycompany.com' wsdl = 'file://C:/AXLdev/AXLAPI.wsdl' location = 'https://{host}:8443/axl/'.format(host=host) binding = "{http://www.cisco.com/AXLAPIService/}AXLAPIBinding" mac = "SEP00AF1F9zzzzz" session = Session() session.verify = False session.auth = HTTPBasicAuth(username, password) transport = Transport(cache=SqliteCache(), session=session, timeout=20) history = HistoryPlugin() client = Client(wsdl=wsdl, transport=transport, plugins=[history]) service = client.create_service(binding, location) try: resp = service.listPhone(searchCriteria={'name': mac}, returnedTags={'name': '', 'description': '', 'callingSearchSpaceName': ''}) phone_list = resp['return'].phone for phone in phone_list: print(phone.name + " --- " + phone.description + " --- " + phone.uuid) Tuuid = phone.uuid except Fault: show_history() newDesc = "Jonus Grumby" PHONEID = phone.name phone_data = { 'name': PHONEID, 'newdescription': newDesc, 'Uuid': Tuuid } # service = client.create_service(binding, location) print("starting updatephone...") try: # resp = service.updatePhone(uuid=Tuuid,newDescription=newDesc) # resp = service.updatePhone({'uuid':Tuuid},{'newDescription':newDesc}) resp = service.updatePhone({'Uuid': Tuuid},{'newdescription': newDesc}) print("after-update...") except Fault: print("in fault processing") show_history() def show_history(): for item in [history.last_sent, history.last_received]: print(etree.tostring(item["envelope"], encoding="unicode", pretty_print=True))
Here are the error messages (and general output) I get for my efforts:
SEP00AF1F9zzzzz --- Captain Matticus --- {D58FB7A2-E0FF-8C5E-091A-6DAB641271AE} starting updatephone... Traceback (most recent call last): File "C:\Users\me\eclipsePython-workspace\AXL-Demo5\testcode.py", line 61, in <module> resp = service.updatePhone({'Uuid': Tuuid},{'newdescription': newDesc}) File "C:\Users\me\AppData\Local\Programs\Python\Python36-32\lib\site-packages\zeep\proxy.py", line 45, in __call__ kwargs, File "C:\Users\me\AppData\Local\Programs\Python\Python36-32\lib\site-packages\zeep\wsdl\bindings\soap.py", line 119, in send operation, args, kwargs, client=client, options=options File "C:\Users\me\AppData\Local\Programs\Python\Python36-32\lib\site-packages\zeep\wsdl\bindings\soap.py", line 68, in _create serialized = operation_obj.create(*args, **kwargs) File "C:\Users\me\AppData\Local\Programs\Python\Python36-32\lib\site-packages\zeep\wsdl\definitions.py", line 215, in create return self.input.serialize(*args, **kwargs) File "C:\Users\me\AppData\Local\Programs\Python\Python36-32\lib\site-packages\zeep\wsdl\messages\soap.py", line 68, in serialize body_value = self.body(*args, **kwargs) File "C:\Users\me\AppData\Local\Programs\Python\Python36-32\lib\site-packages\zeep\xsd\elements\element.py", line 57, in __call__ instance = self.type(*args, **kwargs) File "C:\Users\me\AppData\Local\Programs\Python\Python36-32\lib\site-packages\zeep\xsd\types\complex.py", line 49, in __call__ return self._value_class(*args, **kwargs) File "C:\Users\me\AppData\Local\Programs\Python\Python36-32\lib\site-packages\zeep\xsd\valueobjects.py", line 95, in __init__ items = _process_signature(self._xsd_type, args, kwargs) File "C:\Users\me\AppData\Local\Programs\Python\Python36-32\lib\site-packages\zeep\xsd\valueobjects.py", line 185, in _process_signature values, args, index = element.parse_args(args, index) File "C:\Users\me\AppData\Local\Programs\Python\Python36-32\lib\site-packages\zeep\xsd\elements\indicators.py", line 129, in parse_args raise TypeError("Choice elements only work with keyword arguments") TypeError: Choice elements only work with keyword arguments
can anyone enlighten me?
thanks!
Solved! Go to Solution.
02-24-2020 09:05 AM - edited 02-24-2020 09:11 AM
May I recommend this learning lab?
https://developer.cisco.com/learning/lab/python-zeep-axl-lab/step/1
In step 4, you'll see this:
line_data = {
'line': {
'pattern': LINEDN,
'description': 'Test Line',
'usage': 'Device',
'routePartitionName': None
}
}
# the ** before line_data tells the Python function to expect
# an unspecified number of keyword/value pairs
try:
line_resp = service.addLine(**line_data)
except Fault as err:
etc...
As you can see, I pass json as the argument, and prefix it with ** in order to tell the API to expect an unspecified number of keyword/value pairs. There are probably other ways to pass values to the AXL API, but this always works best for me.
Check out the next page for a possible hiccup. Sometimes there isn't a subkey name when an XML tag would have a number of sub-tags. So I simply substitute '_value_1'
for the name. A great way to predict what AXL/Zeep will understand is to run a query like getPhone or whatever, and look at what it returns. Then use that as a general guide for what to specify. Keep in mind, though, that you can't do a getPhone and then modify some values and then use it to do an updatePhone. The two APIs are not mirrored - they take different json/XML structure.
phone_data = {
'phone': {
'name': PHONEID,
'description': PHONEID,
'product': 'Cisco 8821',
'class': 'Phone',
'protocol': 'SIP',
'devicePoolName': {
'_value_1': 'Default'
},
'commonPhoneConfigName': {
'_value_1': 'Standard Common Phone Profile'
},
What I usually do is try a request in SoapUI, and when I have it working, I convert the XML (in my head) to what it would look like as JSON. There are ways in Python to convert the working XML to working JSON and vice versa, but I don't use them.
02-21-2020 04:15 PM
Hi Group,
I got the test code to run. The successful code is shown below - along with console output below that.
I found a few errors:
first - in this case the correct action was 'updatePhone' not 'updatePhoneReq' - and you really have to pay attention to case for all of the AXL stuff - actions and passed data names
second, I was trying to pass the parameters as tuples - that didnt work. Im not sure what you call how they are actually sent - they look like normal variable assignments separated by commas.
Here is the working code:
from test.test_importlib.util import CASE_INSENSITIVE_FS from zeep import Client from zeep.cache import SqliteCache from zeep.transports import Transport from zeep.exceptions import Fault from zeep.plugins import HistoryPlugin from requests import Session from requests.auth import HTTPBasicAuth from urllib3 import disable_warnings from urllib3.exceptions import InsecureRequestWarning from lxml import etree disable_warnings(InsecureRequestWarning) username = 'administrator' password = 'mySecretPassword' # If you're not disabling SSL verification, host should be the FQDN of the server rather than IP host = 'myServer.Mycompany.com' wsdl = 'file://C:/AXLdev/AXLAPI.wsdl' location = 'https://{host}:8443/axl/'.format(host=host) binding = "{http://www.cisco.com/AXLAPIService/}AXLAPIBinding" mac = "SEP00AF1F9Czzzz" session = Session() session.verify = False session.auth = HTTPBasicAuth(username, password) transport = Transport(cache=SqliteCache(), session=session, timeout=20) history = HistoryPlugin() client = Client(wsdl=wsdl, transport=transport, plugins=[history]) service = client.create_service(binding, location) try: resp = service.listPhone(searchCriteria={'name': mac}, returnedTags={'name': '', 'description': '', 'callingSearchSpaceName': ''}) phone_list = resp['return'].phone for phone in phone_list: print(phone.name + " --- " + phone.description + " --- " + phone.uuid) Tuuid = phone.uuid except Fault: show_history() newDesc = "Ginger Grant" PHONEID = phone.name phone_data = { 'name': PHONEID, 'newdescription': newDesc, 'Uuid': Tuuid } # service = client.create_service(binding, location) print("starting updatephone...") try: # resp = service.updatePhone({'Uuid': Tuuid},{'newdescription': newDesc}) THIS IS THE FAILED VERSION resp = service.updatePhone( name = PHONEID, description = newDesc, ) print("after-update...") except Fault: print("in fault processing") show_history() def show_history(): for item in [history.last_sent, history.last_received]: print(etree.tostring(item["envelope"], encoding="unicode", pretty_print=True))
#============ output ===========
SEP00AF1F9Cxxxx --- Jonas Grumby --- {D58FB7A2-E0FF-8C5E-091A-6DAB641271AE}
starting updatephone...
after-update...
The format of sending the the data is really confusing to me because the full code Im using includes the following code:
resp = service.listPhone(searchCriteria={'name': mac}, returnedTags={'name': '', 'description': '', 'callingSearchSpaceName': ''})
This code (for listPhone instead of updatePhone) works perfectly, and appears to use tuples - in any case the format is much different than with updatePhone.
How do you know what format to pass data in???
Also, the other questions remain - why do I find references to both updatePhone and updatePhoneReq? Is this a difference in AXL versions?
and I still need to know where I can find an AXL schema reference for 10.5 (lab) and 11.5(live) - is this where I'll find answers to the above questions?
If anyone out there has AXL experience in Python, please chime in!
thanks
02-24-2020 09:05 AM - edited 02-24-2020 09:11 AM
May I recommend this learning lab?
https://developer.cisco.com/learning/lab/python-zeep-axl-lab/step/1
In step 4, you'll see this:
line_data = {
'line': {
'pattern': LINEDN,
'description': 'Test Line',
'usage': 'Device',
'routePartitionName': None
}
}
# the ** before line_data tells the Python function to expect
# an unspecified number of keyword/value pairs
try:
line_resp = service.addLine(**line_data)
except Fault as err:
etc...
As you can see, I pass json as the argument, and prefix it with ** in order to tell the API to expect an unspecified number of keyword/value pairs. There are probably other ways to pass values to the AXL API, but this always works best for me.
Check out the next page for a possible hiccup. Sometimes there isn't a subkey name when an XML tag would have a number of sub-tags. So I simply substitute '_value_1'
for the name. A great way to predict what AXL/Zeep will understand is to run a query like getPhone or whatever, and look at what it returns. Then use that as a general guide for what to specify. Keep in mind, though, that you can't do a getPhone and then modify some values and then use it to do an updatePhone. The two APIs are not mirrored - they take different json/XML structure.
phone_data = {
'phone': {
'name': PHONEID,
'description': PHONEID,
'product': 'Cisco 8821',
'class': 'Phone',
'protocol': 'SIP',
'devicePoolName': {
'_value_1': 'Default'
},
'commonPhoneConfigName': {
'_value_1': 'Standard Common Phone Profile'
},
What I usually do is try a request in SoapUI, and when I have it working, I convert the XML (in my head) to what it would look like as JSON. There are ways in Python to convert the working XML to working JSON and vice versa, but I don't use them.
02-25-2020 09:12 AM
npetrele,
THANKS!!!! that demo is the best help Ive found since I started working on this project!!! It includes a lot of the things Im looking for, in the format I wish to use (ie not going directly to xml)!
I grabbed the code, did some minor mods basically just to set for my environment, and when I run it I was getting some errors.
To reduce clutter, I opened a new thread showing the code and errors. Perhaps you can take a look and provide some input - in the meantime I'll keep digging.
https://community.cisco.com/t5/management/python-axl-zeep-failing-to-add-line/m-p/4035551#M3320
thanks again for the help
Discover and save your favorite ideas. Come back to expert answers, step-by-step guides, recent topics, and more.
New here? Get started with these tips. How to use Community New member guide