Sure, we could use a cushy SOAP library to communicate with Magento, but maybe you’d want to capitalize on the cacheability of XML-RPC, instead. Sure, we could use an XML-RPC library, but that would be less fun and, as engineers, we like knowing how stuff works. Magento is not for the faint of heart and knowing how to communicate with it at the low-level might be useful at some point.
import os import json import xml.etree.ElementTree as ET import requests # http://xmlrpc.scripting.com/spec.html _HOSTNAME = os.environ['MAGENTO_HOSTNAME'] _USERNAME = os.environ['MAGENTO_USERNAME'] _PASSWORD = os.environ['MAGENTO_PASSWORD'] _URL = "http://" + _HOSTNAME + "/api/xmlrpc" _HEADERS = { 'Content-Type': 'text/xml', } def _pretty_print(results): print(json.dumps( results, sort_keys=True, indent=4, separators=(',', ': '))) def _send_request(payload): r = requests.post(_URL, data=payload, headers=_HEADERS) r.raise_for_status() root = ET.fromstring(r.text) return root def _send_array(session_id, method_name, args): data_parts = [] for (type_name, value) in args: data_parts.append('<value><' + type_name + '>' + str(value) + '</' + type_name + '></value>') payload = """\ <?xml version='1.0'?> <methodCall> <methodName>call</methodName> <params> <param> <value><string>""" + session_id + """\ </string></value> </param> <param> <value><string>""" + method_name + """\ </string></value> </param> <param> <value> <array> <data> """ + ''.join(data_parts) + """ </data> </array> </value> </param> </params> </methodCall> """ return _send_request(payload) def _send_struct(session_id, method_name, args): struct_parts = [] for (type_name, argument_name, argument_value) in args: struct_parts.append("<member><name>" + argument_name + "</name><value><" + type_name + ">" + str(argument_value) + "</" + type_name + "></value></member>") payload = """\ <?xml version='1.0'?> <methodCall> <methodName>call</methodName> <params> <param> <value><string>""" + session_id + """\ </string></value> </param> <param> <value><string>""" + method_name + """\ </string></value> </param> <param> <value> <struct> """ + ''.join(struct_parts) + """ </struct> </value> </param> </params> </methodCall> """ return _send_request(payload) def _send_login(args): param_parts = [] for (type_name, value) in args: param_parts.append('<param><value><' + type_name + '>' + value + '</' + type_name + '></value></param>') payload = """\ <?xml version="1.0"?> <methodCall> <methodName>login</methodName> <params>""" + ''.join(param_parts) + """\ </params> </methodCall> """ return _send_request(payload) class XmlRpcFaultError(Exception): pass def _distill(value_node): type_node = value_node[0] type_name = type_node.tag if type_name == 'nil': return None elif type_name in ('int', 'i4'): return int(type_node.text) elif type_name == 'boolean': return bool(type_node.text) elif type_name == 'double': return float(type_node.text) elif type_name == 'struct': values = {} for member_node in type_node: key = member_node.find('name').text value_node = member_node.find('value') value = _distill(value_node) values[key] = value return values elif type_name == 'array': flat = [] for i, child_value_node in enumerate(type_node.findall('data/value')): flat.append(_distill(child_value_node)) return flat elif type_name in ('string', 'dateTime.iso8601', 'base64'): return type_node.text else: raise ValueError("Invalid type: [{0}] [{1}]".format(type_name, type_node)) def _parse_response(root): if root.find('fault') is not None: for e in root.findall('fault/value/struct/member'): if e.find('name').text == 'faultString': message = e.find('value/string').text raise XmlRpcFaultError(message) raise ValueError("Malformed fault response") value_node = root.find('params/param/value') result = _distill(value_node) return result def _main(): args = [ ('string', _USERNAME), ('string', _PASSWORD), ] root = _send_login(args) session_id = _parse_response(root) resource_name = 'catalog_product.info' args = [ ('int', 'productId', '314'), ] root = _send_struct(session_id, resource_name, args) result = _parse_response(root) _pretty_print(result) if __name__ == '__main__': _main()
Output:
{ "apparel_type": "33", "categories": [ "13" ], "category_ids": [ "13" ], "color": "27", "country_of_manufacture": null, "created_at": "2013-03-05T00:48:15-05:00", "custom_design": null, "custom_design_from": null, "custom_design_to": null, "custom_layout_update": null, "description": "Two sash, convertible neckline with front ruffle detail. Unhemmed, visisble seams. Hidden side zipper. Unlined. Wool/elastane. Hand wash.", "fit": null, "gender": "94", "gift_message_available": null, "gift_wrapping_available": null, "gift_wrapping_price": null, "group_price": [], "has_options": "0", "image_label": null, "is_recurring": "0", "length": "82", "meta_description": null, "meta_keyword": null, "meta_title": null, "minimal_price": null, "msrp": null, "msrp_display_actual_price_type": "4", "msrp_enabled": "2", "name": "Convertible Dress", "news_from_date": "2013-03-01 00:00:00", "news_to_date": null, "occasion": "29", "old_id": null, "options_container": "container1", "page_layout": "one_column", "price": "340.0000", "product_id": "314", "recurring_profile": null, "required_options": "0", "set": "13", "short_description": "This all day dress has a flattering silhouette and a convertible neckline to suit your mood. Wear tied and tucked in a sailor knot, or reverse it for a high tied feminine bow.", "size": "72", "sku": "wsd017", "sleeve_length": "45", "small_image_label": null, "special_from_date": null, "special_price": null, "special_to_date": null, "status": "1", "tax_class_id": "2", "thumbnail_label": null, "tier_price": [], "type": "simple", "type_id": "simple", "updated_at": "2014-03-08 08:31:20", "url_key": "convertible-dress", "url_path": "convertible-dress-418.html", "visibility": "1", "websites": [ "1" ], "weight": "1.0000" }
You may download the code here.