aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--gsxws/objectify.py129
-rw-r--r--tests/fixtures/ios_activation.xml41
-rw-r--r--tests/fixtures/onsite_dispatch_detail.xml76
-rw-r--r--tests/fixtures/parts_lookup.xml47
-rw-r--r--tests/test_gsxws.py67
5 files changed, 287 insertions, 73 deletions
diff --git a/gsxws/objectify.py b/gsxws/objectify.py
index 3a48858..800e7e9 100644
--- a/gsxws/objectify.py
+++ b/gsxws/objectify.py
@@ -5,15 +5,12 @@ import re
import base64
import tempfile
-from lxml import objectify, etree
-from lxml.objectify import StringElement
-
+from lxml import objectify
from datetime import datetime
+DATETIME_TYPES = ('dispatchSentDate',)
BASE64_TYPES = ('packingList', 'proformaFileData', 'returnLabelFileData',)
FLOAT_TYPES = ('totalFromOrder', 'exchangePrice', 'stockPrice', 'netPrice',)
-BOOLEAN_TYPES = ('isSerialized', 'popMandatory', 'limitedWarranty', 'partCovered', 'acPlusFlag',)
-DATETIME_TYPES = ('dispatchSentDate',)
TZMAP = {
'GMT': '', # Greenwich Mean Time
@@ -36,92 +33,90 @@ TZMAP = {
}
-class GsxElement(StringElement):
- def __unicode__(self):
- return self.pyval
+def gsx_date(value):
+ try:
+ # standard GSX format: "mm/dd/yy"
+ return datetime.strptime(value, "%m/%d/%y").date()
+ except ValueError:
+ pass
- def __str__(self):
- return unicode(self).encode('utf-8')
+ try:
+ # some dates are formatted as "yyyy-mm-dd"
+ return datetime.strptime(value, "%Y-%m-%d").date()
+ except (ValueError, TypeError):
+ pass
- def __repr__(self):
- return str(self.text)
+def gsx_boolean(value):
+ return value == 'Y'
-class GsxDateElement(GsxElement):
+
+class GsxPriceElement():
@property
def pyval(self):
- try:
- # standard GSX format: "mm/dd/yy"
- return datetime.strptime(self.text, "%m/%d/%y").date()
- except ValueError:
- pass
+ return float(re.sub(r'[A-Z ,]', '', self.text))
- try:
- # some dates are formatted as "yyyy-mm-dd"
- return datetime.strptime(self.text, "%Y-%m-%d").date()
- except (ValueError, TypeError):
- pass
- def __repr__(self):
- return str(datetime.strftime(self.pyval, '%Y-%m-%d'))
+def gsx_attachment(value):
+ v = base64.b64decode(value)
+ of = tempfile.NamedTemporaryFile(suffix=".pdf", delete=False)
+ of.write(v)
+ return of.name
-class GsxBooleanElement(GsxElement):
- @property
- def pyval(self):
- return self.text == 'Y'
+def gsx_datetime(value):
+ # 2011-01-27 11:45:01 PST
+ # Unfortunately we have to chomp off the TZ info...
+ m = re.search(r'^(\d+\-\d+\-\d+ \d+:\d+:\d+) (\w+)$', value)
+ ts, tz = m.groups()
+ return datetime.strptime(ts, "%Y-%m-%d %H:%M:%S")
-class GsxPriceElement(GsxElement):
- @property
- def pyval(self):
- return float(re.sub(r'[A-Z ,]', '', self.text))
-
+def gsx_timestamp(value):
+ return datetime.strptime(value, "%d-%b-%y %H:%M:%S")
-class GsxAttachment(GsxElement):
- @property
- def pyval(self):
- v = base64.b64decode(self.text)
- of = tempfile.NamedTemporaryFile(suffix=".pdf", delete=False)
- of.write(v)
- return of.name
+class GsxElement(objectify.ObjectifiedElement):
+ def __getattribute__(self, name):
-class GsxDatetimeElement(GsxElement):
- @property
- def pyval(self):
- # 2011-01-27 11:45:01 PST
- # Unfortunately we have to chomp off the TZ info...
- m = re.search(r'^(\d+\-\d+\-\d+ \d+:\d+:\d+) (\w+)$', self.text)
- ts, tz = m.groups()
- return datetime.strptime(ts, "%Y-%m-%d %H:%M:%S")
+ result = super(GsxElement, self).__getattribute__(name)
+ if isinstance(result, objectify.NumberElement):
+ return result.pyval
-class GsxTimestampElement(GsxElement):
- @property
- def pyval(self):
- return datetime.strptime(self.text, "%d-%b-%y %H:%M:%S")
+ if isinstance(result, objectify.StringElement):
+ name = result.tag
+ if not result.text:
+ return
-class GsxClassLookup(etree.CustomElementClassLookup):
- def lookup(self, node_type, document, namespace, name):
- if name in DATETIME_TYPES:
- return GsxDatetimeElement
- if name in BOOLEAN_TYPES:
- return GsxBooleanElement
- if name in BASE64_TYPES:
- return GsxAttachment
- if name in FLOAT_TYPES:
- return GsxPriceElement
- if re.search(r'Date$', name):
- return GsxDateElement
+ if name in DATETIME_TYPES:
+ return gsx_datetime(result.text)
+ if name in BASE64_TYPES:
+ return gsx_attachment(result.text)
+ if name in FLOAT_TYPES:
+ return GsxPriceElement
+ if re.search(r'Date$', name):
+ return gsx_date(result.text)
+ if re.search(r'^[YN]$', result.text):
+ return gsx_boolean(result.text)
- return objectify.ObjectifiedElement
+ return result
def parse(root, response):
+ """
+ >>> parse('tests/fixtures/warranty_status.xml', 'warrantyDetailInfo').warrantyStatus
+ 'Apple Limited Warranty'
+ >>> parse('tests/fixtures/warranty_status.xml', 'warrantyDetailInfo').estimatedPurchaseDate
+ datetime.date(2010, 8, 25)
+ >>> parse('tests/fixtures/warranty_status.xml', 'warrantyDetailInfo').limitedWarranty
+ True
+ >>> parse('tests/fixtures/warranty_status.xml', 'warrantyDetailInfo').isPersonalized
+ """
parser = objectify.makeparser(remove_blank_text=True)
- parser.set_element_class_lookup(GsxClassLookup())
+ lookup = objectify.ObjectifyElementClassLookup(tree_class=GsxElement)
+ parser.set_element_class_lookup(lookup)
if isinstance(root, basestring) and os.path.exists(root):
root = objectify.parse(root, parser)
diff --git a/tests/fixtures/ios_activation.xml b/tests/fixtures/ios_activation.xml
new file mode 100644
index 0000000..7b18d6b
--- /dev/null
+++ b/tests/fixtures/ios_activation.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
+ <S:Body>
+ <ns3:FetchIOSActivationDetailsResponse
+ xmlns:ns2="http://asp.core.endpoint.ws.gsx.ist.apple.com/"
+ xmlns:ns3="http://gsxws.apple.com/elements/global"
+ xmlns:ns4="http://gsxws.apple.com/elements/core/asp"
+ xmlns:ns5="http://gsxws.apple.com/elements/core/asp/am"
+ xmlns:ns6="http://gsxws.apple.com/elements/core">
+ <FetchIOSActivationDetailsResponse>
+ <operationId>1e62913276430159203556d</operationId>
+ <activationDetailsInfo>
+ <serialNumber>2J141331A4S</serialNumber>
+ <imeiNumber>010648001526755</imeiNumber>
+ <meid></meid>
+ <iccID>897010266884541917</iccID>
+ <firstUnbrickDate>01/16/12</firstUnbrickDate>
+ <lastUnbrickDate>01/18/12</lastUnbrickDate>
+ <lastRestoreDate></lastRestoreDate>
+ <unbricked>true</unbricked>
+ <unlocked>true</unlocked>
+ <unlockDate>01/18/12</unlockDate>
+ <productVersion>4.2.1</productVersion>
+ <initialActivationPolicyID>1</initialActivationPolicyID>
+ <initialActivationPolicyDetails>
+ Unassigned factory default - Apple carriers only.
+ SIM Swap triggers rebricking.</initialActivationPolicyDetails>
+ <appliedActivationPolicyID>152</appliedActivationPolicyID>
+ <appliedActivationDetails>Russia MegaFon.</appliedActivationDetails>
+ <nextTetherPolicyID>1</nextTetherPolicyID>
+ <nextTetherPolicyDetails>
+ Unassigned factory default - Apple carriers only.
+ SIM Swap triggers rebricking.</nextTetherPolicyDetails>
+ <macAddress></macAddress>
+ <bluetoothMacAddress></bluetoothMacAddress>
+ <partDescription>iPhone4 16GB BLACKE</partDescription>
+ </activationDetailsInfo>
+ </FetchIOSActivationDetailsResponse>
+ </ns3:FetchIOSActivationDetailsResponse>
+ </S:Body>
+</S:Envelope>
diff --git a/tests/fixtures/onsite_dispatch_detail.xml b/tests/fixtures/onsite_dispatch_detail.xml
new file mode 100644
index 0000000..4025dd0
--- /dev/null
+++ b/tests/fixtures/onsite_dispatch_detail.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
+ <S:Body>
+ <ns4:OnsiteDispatchDetailResponse xmlns:ns2="http://asp.core.endpoint.ws.gsx.ist.apple.com/" xmlns:ns3="http://gsxws.apple.com/elements/global" xmlns:ns4="http://gsxws.apple.com/elements/core/asp" xmlns:ns5="http://gsxws.apple.com/elements/core/asp/am" xmlns:ns6="http://gsxws.apple.com/elements/core">
+ <OnsiteDispatchDetailResponse>
+ <operationId>8a6231310158348248</operationId>
+ <onsiteDispatchDetails>
+ <dispatchId>G101260028</dispatchId>
+ <serialNumber>YM1432NELFB</serialNumber>
+ <dispatchSentDate/>
+ <serviceAccountId>0000511590</serviceAccountId>
+ <assignedToShipTo>0000022296</assignedToShipTo>
+ <coverageStatusDescription>AppleCare Protection Plan</coverageStatusDescription>
+ <productName>iMac (Summer 2001)</productName>
+ <configuration>iMac 500MHz/128M/20G/CD-RW/RUltra/56K/FW</configuration>
+ <purchaseOrderNumber>503017</purchaseOrderNumber>
+ <notes>Customer Reported Symptom:
+ System crashes after logging in.
+ Sold-To Diagnosis:
+ System crashes after logging in.
+ Notes:</notes>
+ <zone>TR01</zone>
+ <primaryAddress>
+ <addressLine1>2323 E. Mountain Gate Pass2323 E. Mountain Gate Pass</addressLine1>
+ <country>US</country>
+ <zipCode>85024</zipCode>
+ <county>Madera</county>
+ <city>PHOENIX</city>
+ <state>AZ</state>
+ <region>005</region>
+ <firstName>Christopher</firstName>
+ <lastName>Howard</lastName>
+ <companyName>PVUSD</companyName>
+ <primaryPhone>480-650-3876</primaryPhone>
+ </primaryAddress>
+ <secondaryAddress>
+ <addressLine1>2323 E. Mountain Gate Pass2323 E. Mountain Gate Pass</addressLine1>
+ <country>US</country>
+ <zipCode>85024</zipCode>
+ <county>Madera</county>
+ <city>PHOENIX</city>
+ <state>AZ</state>
+ <region>005</region>
+ <firstName>Hogan</firstName>
+ <lastName>Patrick</lastName>
+ <companyName>PVUSD</companyName>
+ <primaryPhone>602-867-5252</primaryPhone>
+ </secondaryAddress>
+ <assignedToLocationAddress>
+ <addressLine1>8545 N BLACK CANYON HWY</addressLine1>
+ <country>United States</country>
+ <zipCode>85021</zipCode>
+ <county>Madera</county>
+ <city>PHOENIX</city>
+ <state>AZ</state>
+ <region>005</region>
+ <firstName>CDS GROUP INC</firstName>
+ <lastName></lastName>
+ <primaryPhone>602-242-5101</primaryPhone>
+ </assignedToLocationAddress>
+ <dispatchOrderLines>
+ <partNumber>661-2447</partNumber>
+ <partDescription>Board, Logic, 500 MHz</partDescription>
+ <partCoverageDescription>AppleCare Protection Plan</partCoverageDescription>
+ <comptiaCode>581</comptiaCode>
+ <comptiaModifier>B</comptiaModifier>
+ <returnStatus>Known Bad Board</returnStatus>
+ <isSerialized>Y</isSerialized>
+ <notaFiscalNumber>123456789</notaFiscalNumber>
+ </dispatchOrderLines>
+ </onsiteDispatchDetails>
+ <communicationMessage/>
+ </OnsiteDispatchDetailResponse>
+ </ns4:OnsiteDispatchDetailResponse>
+ </S:Body>
+</S:Envelope>
diff --git a/tests/fixtures/parts_lookup.xml b/tests/fixtures/parts_lookup.xml
new file mode 100644
index 0000000..c1ffcb6
--- /dev/null
+++ b/tests/fixtures/parts_lookup.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
+ <S:Body>
+ <ns3:PartsLookupResponse xmlns:ns3="http://gsxws.apple.com/elements/core">
+ <PartsLookupResponse>
+ <operationId>AnK6lhP8j4Zw8hHYswYMGCthSKGnzy9O</operationId>
+ <parts>
+ <eeeCode>DC18,DC19,DC20,YLW,YVL</eeeCode>
+ <exchangePrice>14.4</exchangePrice>
+ <isSerialized>Y</isSerialized>
+ <laborTier></laborTier>
+ <partDescription>SVC,REMOTE</partDescription>
+ <partNumber>661-4448</partNumber>
+ <partType>Module</partType>
+ <stockPrice>17.1</stockPrice>
+ <stockPrice>17.1</stockPrice>
+ <componentCode>5</componentCode>
+ <originalPartNumber>661-2549</originalPartNumber>
+ </parts>
+ <parts>
+ <eeeCode>59T</eeeCode>
+ <exchangePrice>19</exchangePrice>
+ <isSerialized>Y</isSerialized>
+ <laborTier></laborTier>
+ <partDescription>Power Adapter w/Plug, Ultra-Compact, USB, iPhone/iPod-US/CAN/JPN/TWN</partDescription>
+ <partNumber>661-4954</partNumber>
+ <partType>Module</partType>
+ <stockPrice>26.1</stockPrice>
+ <componentCode>3</componentCode>
+ <originalPartNumber></originalPartNumber>
+ </parts>
+ <parts>
+ <eeeCode></eeeCode>
+ <exchangePrice>19</exchangePrice>
+ <isSerialized>N</isSerialized>
+ <laborTier></laborTier>
+ <partDescription>SVC,STEREO HEADSET</partDescription>
+ <partNumber>661-5028</partNumber>
+ <partType>Module</partType>
+ <stockPrice>26.1</stockPrice>
+ <componentCode>6</componentCode>
+ <originalPartNumber>922-8629</originalPartNumber>
+ </parts>
+ </PartsLookupResponse>
+ </ns3:PartsLookupResponse>
+ </S:Body>
+</S:Envelope>
diff --git a/tests/test_gsxws.py b/tests/test_gsxws.py
index d7feac6..089d916 100644
--- a/tests/test_gsxws.py
+++ b/tests/test_gsxws.py
@@ -1,21 +1,76 @@
# -*- coding: utf-8 -*-
-import unittest
from datetime import date
+from unittest import main, TestCase
from gsxws.objectify import parse
-class TestWarrantyFunctions(unittest.TestCase):
+class TestWarrantyFunctions(TestCase):
def setUp(self):
- self.wty = parse('tests/fixtures/warranty_status.xml', 'warrantyDetailInfo')
+ self.data = parse('tests/fixtures/warranty_status.xml', 'warrantyDetailInfo')
def test_purchase_date(self):
- self.assertIsInstance(self.wty.estimatedPurchaseDate.pyval, date)
+ self.assertIsInstance(self.data.estimatedPurchaseDate, date)
def test_config_description(self):
- self.assertEqual(self.wty.configDescription, 'IPHONE 4,16GB BLACK')
+ self.assertEqual(self.data.configDescription, 'IPHONE 4,16GB BLACK')
+ def test_limited_warranty(self):
+ self.assertTrue(self.data.limitedWarranty)
+
+ def test_parts_covered(self):
+ self.assertIsInstance(self.data.partCovered, bool)
+ self.assertTrue(self.data.partCovered)
+
+
+class TestActivation(TestCase):
+ def setUp(self):
+ self.data = parse('tests/fixtures/ios_activation.xml', 'activationDetailsInfo')
+
+ def test_unlock_date(self):
+ self.assertIsInstance(self.data.unlockDate, date)
+
+ def test_unlocked(self):
+ self.assertIs(type(self.data.unlocked), bool)
+ self.assertTrue(self.data.unlocked)
+
+
+class TestPartsLookup(TestCase):
+ def setUp(self):
+ self.data = parse('tests/fixtures/parts_lookup.xml', 'PartsLookupResponse')
+ self.part = self.data.parts[0]
+
+ def test_parts(self):
+ self.assertEqual(len(self.data.parts), 3)
+
+ def test_exchange_price(self):
+ self.assertEqual(self.part.exchangePrice, 14.4)
+
+ def test_stock_price(self):
+ self.assertEqual(self.part.stockPrice, 17.1)
+
+ def test_serialized(self):
+ self.assertIsInstance(self.part.isSerialized, bool)
+ self.assertTrue(self.part.isSerialized)
+
+ def test_description(self):
+ self.assertEqual(self.part.partDescription, 'SVC,REMOTE')
+
+
+class TestOnsiteDispatchDetail(TestCase):
+ def setUp(self):
+ self.data = parse('tests/fixtures/onsite_dispatch_detail.xml', 'onsiteDispatchDetails')
+
+ def test_details(self):
+ self.assertEqual(self.data.dispatchId, 'G101260028')
+
+ def test_address(self):
+ self.assertEqual(self.data.primaryAddress.zipCode, 85024)
+ self.assertEqual(self.data.primaryAddress.firstName, 'Christopher')
+
+ def test_orderlines(self):
+ self.assertIsInstance(self.data.dispatchOrderLines.isSerialized, bool)
if __name__ == '__main__':
- unittest.main()
+ main()