aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md3
-rwxr-xr-xgsxws.py1116
-rw-r--r--gsxws/__init__.py0
-rw-r--r--gsxws/comms.py13
-rw-r--r--gsxws/comptia.json (renamed from comptia.json)0
-rw-r--r--gsxws/comptia.py89
-rw-r--r--gsxws/content.py12
-rw-r--r--gsxws/core.py469
-rw-r--r--gsxws/diagnostics.py29
-rw-r--r--gsxws/escalations.py18
-rw-r--r--gsxws/langs.json (renamed from langs.json)8
-rw-r--r--gsxws/lookups.py64
-rw-r--r--gsxws/orders.py20
-rw-r--r--gsxws/parts.py25
-rw-r--r--gsxws/products.py103
-rw-r--r--gsxws/repairs.py257
-rw-r--r--gsxws/returns.py (renamed from returns.py)0
-rw-r--r--lookups.py7
-rw-r--r--products.py65
-rw-r--r--repairs.py115
20 files changed, 1107 insertions, 1306 deletions
diff --git a/README.md b/README.md
index 3db385b..2cf2d36 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@ py-gsxws
======
py-gsxws is a Python library designed to work with Apple's GSX Web Services API.
-The goel is for it to support all the features of the API.
+The goal is for it to support all the features of the API.
Currently it supports most of them.
Installation:
@@ -26,7 +26,6 @@ Requirements
============
- Python 2.7 or later
-- suds
LICENSE
diff --git a/gsxws.py b/gsxws.py
deleted file mode 100755
index da917d6..0000000
--- a/gsxws.py
+++ /dev/null
@@ -1,1116 +0,0 @@
-#!/usr/bin/env python
-
-#coding=utf-8
-
-"""
-Copyright (c) 2013, Filipp Lepalaan All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification,
-are permitted provided that the following conditions are met:
-
-- Redistributions of source code must retain the above copyright notice,
-this list of conditions and the following disclaimer.
-- Redistributions in binary form must reproduce the above copyright notice,
-this list of conditions and the following disclaimer in the documentation
-and/or other materials provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
-OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-"""
-
-import re
-import os
-import json
-import suds
-import base64
-import urllib
-import hashlib
-import logging
-import tempfile
-
-from suds.client import Client
-from suds.cache import ObjectCache
-import xml.etree.ElementTree as ET
-from datetime import datetime, date, time
-
-# Must use a few module-level global variables
-CLIENT = None
-SESSION = dict()
-LOCALE = "en_XXX"
-CACHE = ObjectCache(minutes=20)
-COMPTIA_CACHE = ObjectCache(months=1)
-
-TIMEZONES = (
- ('GMT', "UTC (Greenwich Mean Time)"),
- ('PDT', "UTC - 7h (Pacific Daylight Time)"),
- ('PST', "UTC - 8h (Pacific Standard Time)"),
- ('CDT', "UTC - 5h (Central Daylight Time)"),
- ('CST', "UTC - 6h (Central Standard Time)"),
- ('EDT', "UTC - 4h (Eastern Daylight Time)"),
- ('EST', "UTC - 5h (Eastern Standard Time)"),
- ('CEST', "UTC + 2h (Central European Summer Time)"),
- ('CET', "UTC + 1h (Central European Time)"),
- ('JST', "UTC + 9h (Japan Standard Time)"),
- ('IST', "UTC + 5.5h (Indian Standard Time)"),
- ('CCT', "UTC + 8h (Chinese Coast Time)"),
- ('AEST', "UTC + 10h (Australian Eastern Standard Time)"),
- ('AEDT', "UTC + 11h (Australian Eastern Daylight Time)"),
- ('ACST', "UTC + 9.5h (Austrailian Central Standard Time)"),
- ('ACDT', "UTC + 10.5h (Australian Central Daylight Time)"),
- ('NZST', "UTC + 12h (New Zealand Standard Time)"),
-)
-
-REGIONS = (
- ('002', "Asia/Pacific"),
- ('003', "Japan"),
- ('004', "Europe"),
- ('005', "United States"),
- ('006', "Canadia"),
- ('007', "Latin America"),
-)
-
-REGION_CODES = ('apac', 'am', 'la', 'emea',)
-
-ENVIRONMENTS = (
- ('pr', "Production"),
- ('ut', "Development"),
- ('it', "Testing"),
-)
-
-
-def validate(value, what=None):
- """
- Tries to guess the meaning of value or validate that
- value looks like what it's supposed to be.
- """
- result = None
-
- if not isinstance(value, basestring):
- raise ValueError('%s is not valid input')
-
- rex = {
- 'partNumber': r'^([A-Z]{1,2})?\d{3}\-?(\d{4}|[A-Z]{2})(/[A-Z])?$',
- 'serialNumber': r'^[A-Z0-9]{11,12}$',
- 'eeeCode': r'^[A-Z0-9]{3,4}$',
- 'returnOrder': r'^7\d{9}$',
- 'repairNumber': r'^\d{12}$',
- 'dispatchId': r'^G\d{9}$',
- 'alternateDeviceId': r'^\d{15}$',
- 'diagnosticEventNumber': r'^\d{23}$',
- 'productName': r'^i?Mac',
- }
-
- for k, v in rex.items():
- if re.match(v, value):
- result = k
-
- return (result == what) if what else result
-
-
-def get_format(locale=LOCALE):
- df = open(os.path.join(os.path.dirname(__file__), 'langs.json'), 'r')
- data = json.load(df)
-
- return data[locale]
-
-
-class GsxObject(object):
- """
- The thing that gets sent to and from GSX
- """
- dt = 'ns3:authenticateRequestType' # The GSX datatype matching this object
- request_dt = 'ns3:authenticateRequestType' # The GSX datatype matching this request
- method = 'Authenticate' # The SOAP method to call on the GSX server
-
- def __init__(self, *args, **kwargs):
-
- formats = get_format()
-
- # native types are not welcome here :)
- for k, v in kwargs.items():
- if isinstance(v, date):
- kwargs[k] = v.strftime(formats['df'])
- if isinstance(v, time):
- kwargs[k] = v.strftime(formats['tf'])
- if isinstance(v, bool):
- kwargs[k] = 'Y' if v else 'N'
-
- self.data = kwargs
-
- if CLIENT is not None:
- self.dt = CLIENT.factory.create(self.dt)
- self.request_dt = CLIENT.factory.create(self.request_dt)
-
- def set_method(self, new_method):
- self.method = new_method
-
- def set_type(self, new_dt):
- """
- Sets the object's primary data type to new_dt
- """
- self.dt = self._make_type(new_dt)
-
- try:
- for k, v in self.data.items():
- setattr(self.dt, k, v)
- except Exception, e:
- pass
-
- def set_request(self, new_dt=None, field=None):
- """
- Sets the field of this object's request datatype to the new value
- """
- if new_dt is not None:
- self.request_dt = self._make_type(new_dt)
-
- setattr(self.request_dt, field, self.dt)
-
- def submit(self, method, data, attr=None):
- """
- Submits the SOAP envelope
- """
- f = getattr(CLIENT.service, method)
-
- try:
- result = f(data)
- return getattr(result, attr) if attr else result
- except suds.WebFault, e:
- raise GsxError(fault=e)
-
- def _make_type(self, new_dt):
- """
- Creates the top-level datatype for the API call
- """
- dt = CLIENT.factory.create(new_dt)
-
- if SESSION:
- dt.userSession = SESSION
-
- return dt
-
- def _process(self, data):
- """
- Tries to coerce some types to their Python counterparts
- """
- for k, v in data:
- # decode binary data
- if k in ['packingList', 'proformaFileData', 'returnLabelFileData']:
- v = base64.b64decode(v)
-
- if isinstance(v, basestring):
- # convert dates to native Python types
- if re.search(r'^\d{2}/\d{2}/\d{2}$', v):
- m, d, y = v.split('/')
- v = date(2000+int(y), int(m), int(d))
-
- # strip currency prefix and munge into float
- if re.search(r'Price$', k):
- v = float(re.sub('[A-Z ,]', '', v))
-
- setattr(data, k, v)
-
- return data
-
- def get_response(self, xml_el):
-
- if isinstance(xml_el, list):
- out = []
- for i in xml_el:
- out.append(self.get_response(i))
-
- return out
-
- if isinstance(xml_el, dict):
- out = []
- for i in xml_el.items():
- out.append(self.get_response(i))
-
- return out
-
- class ReturnData(dict):
- pass
-
- rd = ReturnData()
-
- for r in xml_el.iter():
- k, v = r.tag, r.text
- if k in ['packingList', 'proformaFileData', 'returnLabelFileData']:
- v = base64.b64decode(v)
-
- setattr(rd, k, v)
-
- return rd
-
- def __getattr__(self, name):
- return self.data[name]
-
-
-class Content(GsxObject):
- def fetch_image(self, url):
- """
- The Fetch Image API allows users to get the image file from GSX,
- for the content articles, using the image URL.
- The image URLs will be obtained from the image html tags
- in the data from all content APIs.
- """
- dt = self._make_type('ns3:fetchImageRequestType')
- dt.imageRequest = {'imageUrl': url}
-
- return self.submit('FetchImage', dt, 'contentResponse')
-
-
-class CompTIA(object):
- "Stores and accesses CompTIA codes."
-
- MODIFIERS = (
- ("A", "Not Applicable"),
- ("B", "Continuous"),
- ("C", "Intermittent"),
- ("D", "Fails After Warm Up"),
- ("E", "Environmental"),
- ("F", "Configuration: Peripheral"),
- ("G", "Damaged"),
- )
-
- GROUPS = (
- ('0', 'General'),
- ('1', 'Visual'),
- ('2', 'Displays'),
- ('3', 'Mass Storage'),
- ('4', 'Input Devices'),
- ('5', 'Boards'),
- ('6', 'Power'),
- ('7', 'Printer'),
- ('8', 'Multi-function Device'),
- ('9', 'Communication Devices'),
- ('A', 'Share'),
- ('B', 'iPhone'),
- ('E', 'iPod'),
- ('F', 'iPad'),
- )
-
- def __init__(self):
- """
- Initialize CompTIA symptoms from JSON file
- """
- df = open(os.path.join(os.path.dirname(__file__), 'comptia.json'))
- self.data = json.load(df)
-
- def fetch(self):
- """
- The CompTIA Codes Lookup API retrieves a list of CompTIA groups and modifiers.
-
- Here we must resort to raw XML parsing since SUDS throws this:
- suds.TypeNotFound: Type not found: 'comptiaDescription'
- when calling CompTIACodes()...
-
- >>> CompTIA().fetch()
- {'A': {'972': 'iPod not recognized',...
- """
- global COMPTIA_CACHE
- if COMPTIA_CACHE.get("comptia"):
- return COMPTIA_CACHE.get("comptia")
-
- CLIENT.set_options(retxml=True)
- dt = CLIENT.factory.create("ns3:comptiaCodeLookupRequestType")
- dt.userSession = SESSION
-
- try:
- xml = CLIENT.service.CompTIACodes(dt)
- except suds.WebFault, e:
- raise GsxError(fault=e)
-
- root = ET.fromstring(xml).findall('.//%s' % 'comptiaInfo')[0]
-
- for el in root.findall(".//comptiaGroup"):
- group = {}
- comp_id = el[0].text
-
- for ci in el.findall('comptiaCodeInfo'):
- group[ci[0].text] = ci[1].text
-
- self.data[comp_id] = group
-
- COMPTIA_CACHE.put("comptia", self.data)
- return self.data
-
- def symptoms(self, component=None):
- """
- Returns all known CompTIA symptom codes or just the ones
- belonging to the given component code.
- """
- r = dict()
-
- for g, codes in self.data.items():
- r[g] = list()
- for k, v in codes.items():
- r[g].append((k, v,))
-
- return r[component] if component else r
-
-
-class GsxResponse(dict):
- """
- This contains the data returned by a raw GSX query
- """
- def __getattr__(self, item):
- return self.__getitem__(item)
-
- def __setattr__(self, item, value):
- self.__setitem__(item, value)
-
- @classmethod
- def Process(cls, node):
- nodedict = cls()
-
- for child in node:
- k, v = child.tag, child.text
- newitem = cls.Process(child)
-
- if nodedict.has_key(k):
- # found duplicate tag
- if isinstance(nodedict[k], list):
- # append to existing list
- nodedict[k].append(newitem)
- else:
- # convert to list
- nodedict[k] = [nodedict[k], newitem]
- else:
- # unique tag -> set the dictionary
- nodedict[k] = newitem
-
- if k in ('packingList', 'proformaFileData', 'returnLabelFileData'):
- nodedict[k] = base64.b64decode(v)
-
- if isinstance(v, basestring):
- # convert dates to native Python type
- if re.search('^\d{2}/\d{2}/\d{2}$', v):
- m, d, y = v.split('/')
- v = date(2000+int(y), int(m), int(d)).isoformat()
-
- # strip currency prefix and munge into float
- if re.search('Price$', k):
- v = float(re.sub('[A-Z ,]', '', v))
-
- # Convert timestamps to native Python type
- # 18-Jan-13 14:38:04
- if re.search('TimeStamp$', k):
- v = datetime.strptime(v, '%d-%b-%y %H:%M:%S')
-
- # convert Y and N to corresponding boolean
- if re.search('^[YN]$', k):
- v = (v == 'Y')
-
- nodedict[k] = v
-
- return nodedict
-
-
-class GsxError(Exception):
- def __init__(self, message=None, code=None, fault=None):
- if isinstance(fault, suds.WebFault):
- self.code = fault.fault.faultcode
- self.message = fault.fault.faultstring
- else:
- self.code = code
- self.message = message
-
- self.data = (self.code, self.message)
-
- def __getitem__(self, idx):
- return self.data[idx]
-
- def __repr__(self):
- print self.data
-
- def __str__(self):
- return self.data[1]
-
-
-class Lookup(GsxObject):
- def lookup(self, dt, method):
- dt = self._make_type(dt)
- dt.lookupRequestData = self.data
- return self.submit(method, dt, "lookupResponseData")
-
- def parts(self):
- """
- The Parts Lookup API allows users to access part and part pricing data prior to
- creating a repair or order. Parts lookup is also a good way to search for
- part numbers by various attributes of a part
- (config code, EEE code, serial number, etc.).
- """
- dt = self._make_type("ns0:partsLookupRequestType")
- dt.lookupRequestData = self.data
- return self.submit("PartsLookup", dt, "parts")
-
- def repairs(self):
- """
- The Repair Lookup API mimics the front-end repair search functionality.
- It fetches up to 2500 repairs in a given criteria.
- Subsequently, the extended Repair Status API can be used
- to retrieve more details of the repair.
- """
- dt = CLIENT.factory.create('ns6:repairLookupInfoType')
- request = CLIENT.factory.create('ns1:repairLookupRequestType')
- request.userSession = SESSION
- request.lookupRequestData = self.data
- return self.submit("RepairLookup", request, "lookupResponseData")
-
- def invoices(self):
- """
- The Invoice ID Lookup API allows AASP users
- to fetch the invoice generated for last 24 hrs
-
- >>> Lookup(shipTo=677592, invoiceDate='02/06/12').invoices()
- """
- result = self.lookup('ns1:invoiceIDLookupRequestType', 'InvoiceIDLookup')
- return result.invoiceID # This is actually a list of Invoice ID's...
-
- def invoice_details(self):
- """
- The Invoice Details Lookup API allows AASP users to
- download invoice for a given invoice id.
- """
- result = self.lookup('ns1:invoiceDetailsLookupRequestType', 'InvoiceDetailsLookup')
- pdf = base64.b64decode(result.invoiceData)
- outfile = tempfile.NamedTemporaryFile(suffix='.pdf', delete=False)
- outfile.write(pdf)
- result.invoiceData = outfile.name
- return result
-
-
-class Diagnostics(GsxObject):
- def fetch(self):
- """
- The Fetch Repair Diagnostics API allows the service providers/depot/carriers
- to fetch MRI/CPU diagnostic details from the Apple Diagnostic Repository OR
- diagnostic test details of iOS Devices.
- The ticket is generated within GSX system.
-
- >>> Diagnostics(diagnosticEventNumber='12942008007242012052919').fetch()
- """
- # Using raw XML to avoid TypeNotFound: Type not found: 'toolID' or operationID
- CLIENT.set_options(retxml=True)
-
- if "alternateDeviceId" in self.data:
- dt = self._make_type("ns3:fetchIOSDiagnosticRequestType")
- dt.lookupRequestData = self.data
-
- try:
- result = CLIENT.service.FetchIOSDiagnostic(dt)
- except suds.WebFault, e:
- raise GsxError(fault=e)
-
- root = ET.fromstring(result).findall("*//FetchIOSDiagnosticResponse")[0]
- else:
- dt = self._make_type("ns3:fetchRepairDiagnosticRequestType")
- dt.lookupRequestData = self.data
-
- try:
- result = CLIENT.service.FetchRepairDiagnostic(dt)
- except suds.WebFault, e:
- raise GsxError(fault=e)
-
- root = ET.fromstring(result).findall("*//FetchRepairDiagnosticResponse")[0]
-
- return GsxResponse.Process(root)
-
- def events(self):
- """
- The Fetch Diagnostic Event Numbers API allows users to retrieve all
- diagnostic event numbers associated with provided input
- (serial number or alternate device ID).
- """
- dt = self._make_type("ns3:fetchDiagnosticEventNumbersRequestType")
- dt.lookupRequestData = self.data
- pass
-
-
-class Order(GsxObject):
- def __init__(self, type='stocking', *args, **kwargs):
- super(Order, self).__init__(*args, **kwargs)
- self.data['orderLines'] = list()
-
- def add_part(self, part_number, quantity):
- self.data['orderLines'].append({
- 'partNumber': part_number, 'quantity': quantity
- })
-
- def submit(self):
- dt = CLIENT.factory.create('ns1:createStockingOrderRequestType')
- dt.userSession = SESSION
- dt.orderData = self.data
-
- try:
- result = CLIENT.service.CreateStockingOrder(dt)
- return result.orderConfirmation
- except suds.WebFault, e:
- raise GsxError(fault=e)
-
-
-class Returns(GsxObject):
-
- RETURN_TYPES = (
- (1, "Dead On Arrival"),
- (2, "Good Part Return"),
- (3, "Convert To Stock"),
- (4, "Transfer to Out of Warranty"),
- )
-
- def __init__(self, order_number=None, *args, **kwargs):
- super(Returns, self).__init__(*args, **kwargs)
-
- if order_number is not None:
- self.data['returnOrderNumber'] = order_number
-
- def get_pending(self):
- """The Parts Pending Return API returns a list of all parts that
- are pending for return, based on the search criteria.
-
- >>> Returns(repairType='CA').get_pending() # doctest: +SKIP
- """
- dt = self._make_type('ns1:partsPendingReturnRequestType')
- dt.repairData = self.data
-
- return self.submit('PartsPendingReturn', dt, 'partsPendingResponse')
-
- def get_report(self):
- """The Return Report API returns a list of all parts that are returned
- or pending for return, based on the search criteria.
- """
- dt = self._make_type('ns1:returnReportRequestType')
- dt.returnRequestData = self.data
-
- return self.submit('ReturnReport', dt, 'returnResponseData')
-
- def get_label(self, part_number):
- """The Return Label API retrieves the Return Label for a given Return Order Number.
- (Type not found: 'comptiaCode')
- so we're parsing the raw SOAP response and creating a "fake" return object from that.
- """
- if not validate(part_number, 'partNumber'):
- raise ValueError('%s is not a valid part number' % part_number)
-
- class ReturnData(dict):
- pass
-
- rd = ReturnData()
-
- CLIENT.set_options(retxml=True)
-
- dt = CLIENT.factory.create('ns1:returnLabelRequestType')
- dt.returnOrderNumber = self.data['returnOrderNumber']
- dt.partNumber = part_number
- dt.userSession = SESSION
-
- try:
- result = CLIENT.service.ReturnLabel(dt)
- except suds.WebFault, e:
- raise GsxError(fault=e)
-
- el = ET.fromstring(result).findall('*//%s' % 'returnLabelData')[0]
-
- for r in el.iter():
-
- k, v = r.tag, r.text
-
- if k in ['packingList', 'proformaFileData', 'returnLabelFileData']:
- v = base64.b64decode(v)
- of = tempfile.NamedTemporaryFile(suffix=".pdf", delete=False)
- of.write(v)
- v = of.name
-
- setattr(rd, k, v)
-
- return rd
-
- def get_proforma(self):
- """The View Bulk Return Proforma API allows you to view the proforma label
- for a given Bulk Return Id. You can create a parts bulk return
- by using the Register Parts for Bulk Return API.
- """
- pass
-
- def register_parts(self, parts):
- """The Register Parts for Bulk Return API creates a bulk return for
- the registered parts.
- The API returns the Bulk Return Id with the packing list.
- """
- dt = self._make_type("ns1:registerPartsForBulkReturnRequestType")
- self.data['bulkReturnOrder'] = parts
- dt.bulkPartsRegistrationRequest = self.data
-
- result = self.submit("RegisterPartsForBulkReturn", dt, "bulkPartsRegistrationData")
-
- pdf = base64.b64decode(result.packingList)
- of = tempfile.NamedTemporaryFile(suffix=".pdf", delete=False)
- of.write(pdf)
- result.packingList = of.name
-
- return result
-
- def update_parts(self, confirmation, parts):
- """
- The Parts Return Update API allows you to mark a part
- with the status GPR(2), DOA(1), CTS(3), or TOW(4).
- The API can be used only by ASP.
-
- >>> Returns().update_parts('G135877430',\
- [{'partNumber': '661-5174',\
- 'comptiaCode': 'Z29',\
- 'comptiaModifier': 'A',\
- 'returnType': 2}])
- """
- dt = self._make_type("ns1:partsReturnUpdateRequestType")
- repairData = {
- 'repairConfirmationNumber': confirmation,
- 'orderLines': parts
- }
- dt.repairData = repairData
- result = self.submit("PartsReturnUpdate", dt)
- print result
- return result
-
-
-class Part(GsxObject):
- def lookup(self):
- lookup = Lookup(**self.data)
- return lookup.parts()
-
- def fetch_image(self):
- """
- Tries the fetch the product image for this service part
- """
- if self.partNumber is None:
- raise GsxError('Cannot fetch part image without part number')
-
- image = '%s_350_350.gif' % self.partNumber
- url = 'https://km.support.apple.com.edgekey.net/kb/imageService.jsp?image=%s' % image
- tmpfile = tempfile.mkstemp(suffix=image)
-
- try:
- return urllib.urlretrieve(url, tmpfile[1])[0]
- except Exception, e:
- raise GsxError('Failed to fetch part image: %s' % e)
-
-
-class Escalation(GsxObject):
- def create(self):
- """
- The Create General Escalation API allows users to create
- a general escalation in GSX. The API was earlier known as GSX Help.
- """
- dt = self._make_type("ns1:createGenEscRequestType")
- dt.escalationRequest = self.data
- return self.submit("CreateGeneralEscalation", dt, "escalationConfirmation")
-
- def update(self):
- """
- The Update General Escalation API allows Depot users to
- update a general escalation in GSX.
- """
- dt = self._make_type("ns1:updateGeneralEscRequestType")
- dt.escalationRequest = self.data
- return self.submit("UpdateGeneralEscalation", dt, "escalationConfirmation")
-
-
-class Repair(GsxObject):
-
- dt = 'ns6:repairLookupInfoType'
- request_dt = 'ns1:repairLookupRequestType'
-
- def __init__(self, number=None, *args, **kwargs):
- super(Repair, self).__init__(*args, **kwargs)
-
- if number is not None:
- self.data['dispatchId'] = number
- self.data['repairConfirmationNumber'] = number
-
- def create_carryin(self):
- """
- GSX validates the information and if all of the validations go through,
- it obtains a quote for the repair and creates the carry-in repair.
- """
- dt = self._make_type('ns2:carryInRequestType')
- dt.repairData = self.data
-
- return self.submit('CreateCarryInRepair', dt, 'repairConfirmation')
-
- def create_cnd(self):
- """
- The Create CND Repair API allows Service Providers to create a repair
- whenever the reported issue cannot be duplicated, and the repair
- requires no parts replacement.
- N01 Unable to Replicate
- N02 Software Update/Issue
- N03 Cable/Component Reseat
- N05 SMC Reset
- N06 PRAM Reset
- N07 Third Party Part
- N99 Other
- """
- pass
-
- def update_carryin(self, newdata):
- """
- Description
- The Update Carry-In Repair API allows the service providers
- to update the existing open carry-in repairs.
- This API assists in addition/deletion of parts and addition of notes
- to a repair. On successful update, the repair confirmation number and
- quote for any newly added parts would be returned.
- In case of any validation error or unsuccessful update, a fault code is issued.
-
- Carry-In Repair Update Status Codes:
- AWTP Awaiting Parts
- AWTR Parts Allocated
- BEGR In Repair
- RFPU Ready for Pickup
- """
- dt = self._make_type('ns1:updateCarryInRequestType')
-
- # Merge old and new data (old data should have Dispatch ID)
- dt.repairData = dict(self.data.items() + newdata.items())
-
- return self.submit('CarryInRepairUpdate', dt, 'repairConfirmation')
-
- def update_sn(self, parts):
- """
- Description
- The Update Serial Number API allows the service providers to
- update the module serial numbers.
- Context:
- The API is not applicable for whole unit replacement
- serial number entry (see KGB serial update).
- """
- dt = self._make_type('ns1:updateSerialNumberRequestType')
- repairData = {'repairConfirmationNumber': self.data.get('dispatchId')}
- repairData['partInfo'] = parts
- dt.repairData = repairData
-
- return self.submit('UpdateSerialNumber', dt, 'repairConfirmation')
-
- def update_kgb_sn(self, sn):
- """
- Description:
- The KGB Serial Number Update API is always to be used on
- whole unit repairs that are in a released state.
- This API allows users to provide the KGB serial number for the
- whole unit exchange repairs. It also checks for the privilege
- to create/ update whole unit exchange repairs
- before updating the whole unit exchange repair.
-
- Context:
- The API is to be used on whole unit repairs that are in a released state.
- This API can be invoked only after carry-in repair creation API.
- """
-
- # Using raw XML to avoid:
- # Exception: <UpdateKGBSerialNumberResponse/> not mapped to message part
- CLIENT.set_options(retxml=True)
- dt = self._make_type('ns1:updateKGBSerialNumberRequestType')
- dt.repairConfirmationNumber = self.data['dispatchId']
- dt.serialNumber = sn
-
- try:
- result = CLIENT.service.KGBSerialNumberUpdate(dt)
- except suds.WebFault, e:
- raise GsxError(fault=e)
-
- root = ET.fromstring(result).findall('*//%s' % 'UpdateKGBSerialNumberResponse')
- return GsxResponse.Process(root[0])
-
- def lookup(self):
- """
- Description:
- The Repair Lookup API mimics the front-end repair search functionality.
- It fetches up to 2500 repairs in a given criteria.
- Subsequently, the extended Repair Status API can be used
- to retrieve more details of the repair.
-
- >>> Repair(repairStatus='Open').lookup()
- """
- return Lookup(**self.data).repairs()
-
- def mark_complete(self, numbers=None):
- """
- The Mark Repair Complete API allows a single or an array of
- repair confirmation numbers to be submitted to GSX to be marked as complete.
- """
- dt = self._make_type('ns1:markRepairCompleteRequestType')
- dt.repairConfirmationNumbers = [self.data['dispatchId']]
-
- try:
- result = CLIENT.service.MarkRepairComplete(dt)
- return result.repairConfirmationNumbers
- except suds.WebFault, e:
- raise GsxError(fault=e)
-
- def delete(self):
- """
- The Delete Repair API allows the service providers to delete
- the existing GSX Initiated Carry-In, Return Before Replace & Onsite repairs
- which are in Declined-Rejected By TSPS Approver state,
- that do not have an active repair id.
- """
- pass
-
- def get_status(self, numbers=None):
- """
- The Repair Status API retrieves the status
- for the submitted repair confirmation number(s).
- """
- dt = self._make_type('ns1:repairStatusRequestType')
- dt.repairConfirmationNumbers = [self.data['dispatchId']]
- result = CLIENT.service.RepairStatus(dt)
-
- if len(result.repairStatus) == 1:
- return result.repairStatus[0]
- else:
- return result.repairStatus
-
- def get_details(self):
- """
- The Repair Details API includes the shipment information
- similar to the Repair Lookup API.
-
- >>> Repair('G135773004').get_details()
- asd
- """
- dt = self._make_type('ns0:repairDetailsRequestType')
- dt.dispatchId = self.data['dispatchId']
- results = CLIENT.service.RepairDetails(dt)
- details = results.lookupResponseData[0]
-
- # fix tracking URL if available
- for i, p in enumerate(details.partsInfo):
- try:
- url = re.sub('<<TRKNO>>', p.deliveryTrackingNumber, p.carrierURL)
- details.partsInfo[i].carrierURL = url
- except AttributeError:
- pass
-
- return details
-
-
-class Communication(GsxObject):
- def get_content():
- """
- The Fetch Communication Content API allows the service providers/depot/carriers
- to fetch the communication content by article ID from the service news channel.
- """
-
- def get_articles():
- """
- The Fetch Communication Articles API allows the service partners
- to fetch all the active communication message IDs.
- """
-
-
-class Product(GsxObject):
-
- dt = 'ns7:unitDetailType'
- serialNumber = ""
- alternateDeviceId = ""
-
- def __init__(self, serialNumber, *args, **kwargs):
- super(Product, self).__init__(*args, **kwargs)
-
- dt = {'serialNumber': serialNumber}
-
- if validate(serialNumber, 'alternateDeviceId'):
- self.alternateDeviceId = serialNumber
- dt = {'alternateDeviceId': serialNumber}
- else:
- self.serialNumber = serialNumber
-
- if SESSION:
- self.dt = dt
- self.lookup = Lookup(**dt)
-
- def get_model(self):
- """
- This API allows Service Providers/Carriers to fetch
- Product Model information for the given serial number.
-
- >>> Product('W874939YX92').get_model().configDescription
- MacBook Pro (15-inch 2.4/2.2GHz)
- """
- #self.set_request('ns3:fetchProductModelRequestType', 'productModelRequest')
- dt = self._make_type("ns3:fetchProductModelRequestType")
- dt.productModelRequest = self.dt
- result = self.submit('FetchProductModel', dt, "productModelResponse")
- return result[0]
-
- def get_warranty(self, date_received=None, parts=[]):
- """
- The Warranty Status API retrieves the same warranty details
- displayed on the GSX Coverage screen.
- If part information is provided, the part warranty information is returned.
- If you do not provide the optional part information in the
- warranty status request, the unit level warranty information is returned.
-
- >>> Product('013348005376007').get_warranty().warrantyStatus
- Apple Limited Warranty
- >>> Product('W874939YX92').get_warranty().warrantyStatus
- Out Of Warranty (No Coverage)
- """
- dt = self._make_type("ns3:warrantyStatusRequestType")
-
- if not self.serialNumber:
- activation = self.get_activation()
- self.serialNumber = activation.serialNumber
- self.dt = {'serialNumber': self.serialNumber}
-
- dt.unitDetail = self.dt
-
- result = self.submit("WarrantyStatus", dt, "warrantyDetailInfo")
- return self._process(result)
-
- def get_activation(self):
- """
- The Fetch iOS Activation Details API is used to
- fetch activation details of iOS Devices.
-
- >>> Product('013348005376007').get_activation().unlocked
- true
- >>> Product('W874939YX92').get_activation().unlocked
- Traceback (most recent call last):
- ...
- GsxError: Provided serial number does not belong to an iOS Device.
- ...
- """
- dt = self._make_type("ns3:fetchIOSActivationDetailsRequestType")
-
- if self.serialNumber:
- dt.serialNumber = self.serialNumber
- else:
- dt.alternateDeviceId = self.alternateDeviceId
-
- return self.submit('FetchIOSActivationDetails', dt, 'activationDetailsInfo')
-
- def get_parts(self):
- return self.lookup.parts()
-
- def get_repairs(self):
- return self.lookup.repairs()
-
- def get_diagnostics(self):
- diags = Diagnostics(serialNumber=self.serialNumber)
- return diags.fetch()
-
- def fetch_image(self):
- if not self.imageURL:
- raise GsxError('Cannot fetch product image with image URL')
-
- try:
- result = urllib.urlretrieve(self.imageURL)
- return result[0]
- except Exception, e:
- raise GsxError('Failed to fetch product image: %s' % e)
-
-
-def init(env='ut', region='emea'):
- """
- Initialize the SOAP client
- """
-
- global CLIENT, REGION_CODES
-
- envs = ('pr', 'it', 'ut',)
- hosts = {'pr': 'ws2', 'it': 'wsit', 'ut': 'wsut'}
-
- if region not in REGION_CODES:
- raise ValueError('Region should be one of: %s' % ','.join(REGION_CODES))
-
- if env not in envs:
- raise ValueError('Environment should be one of: %s' % ','.join(envs))
-
- url = "https://gsx{env}.apple.com/wsdl/{region}Asp/gsx-{region}Asp.wsdl"
- url = url.format(env=hosts[env], region=region)
-
- CLIENT = Client(url)
- cache = CLIENT.options.cache
- cache.setduration(weeks=1)
-
-
-def connect(
- user_id,
- password,
- sold_to,
- language='en',
- timezone='CEST',
- environment='ut',
- region='emea',
- locale=LOCALE):
- """
- Establishes connection with GSX Web Services.
- Returns the session ID of the new connection.
- """
-
- global CACHE
- global LOCALE
- global SESSION
-
- SESSION = {}
- LOCALE = LOCALE
-
- md5 = hashlib.md5()
- md5.update(user_id + str(sold_to) + environment)
- cache_key = md5.hexdigest()
-
- if CACHE.get(cache_key) is not None:
- SESSION = CACHE.get(cache_key)
- init(environment, region)
-
- return SESSION
-
- init(environment, region)
-
- account = CLIENT.factory.create('ns3:authenticateRequestType')
-
- account.userId = user_id
- account.password = password
- account.languageCode = language
- account.userTimeZone = timezone
- account.serviceAccountNo = sold_to
-
- try:
- result = CLIENT.service.Authenticate(account)
- SESSION['userSessionId'] = result.userSessionId
- CACHE.put(cache_key, SESSION)
- return SESSION
- except suds.WebFault, e:
- raise GsxError(fault=e)
-
-
-def logout():
- CLIENT.service.Logout()
-
-if __name__ == '__main__':
- import doctest
- import argparse
-
- parser = argparse.ArgumentParser(description='Communicate with GSX Web Services')
-
- parser.add_argument('user_id')
- parser.add_argument('password')
- parser.add_argument('sold_to')
- parser.add_argument('--language', default='en')
- parser.add_argument('--timezone', default='CEST')
- parser.add_argument('--environment', default='pr')
- parser.add_argument('--region', default='emea')
-
- args = parser.parse_args()
- connect(**vars(args))
- #connect(args.user_id, args.password, args.sold_to. args.environment)
- doctest.testmod()
diff --git a/gsxws/__init__.py b/gsxws/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gsxws/__init__.py
diff --git a/gsxws/comms.py b/gsxws/comms.py
new file mode 100644
index 0000000..e18193f
--- /dev/null
+++ b/gsxws/comms.py
@@ -0,0 +1,13 @@
+class Communication(GsxObject):
+ def get_content():
+ """
+ The Fetch Communication Content API allows the service providers/depot/carriers
+ to fetch the communication content by article ID from the service news channel.
+ """
+
+ def get_articles():
+ """
+ The Fetch Communication Articles API allows the service partners
+ to fetch all the active communication message IDs.
+ """
+ \ No newline at end of file
diff --git a/comptia.json b/gsxws/comptia.json
index 92d6f1c..92d6f1c 100644
--- a/comptia.json
+++ b/gsxws/comptia.json
diff --git a/gsxws/comptia.py b/gsxws/comptia.py
new file mode 100644
index 0000000..f7e7ff7
--- /dev/null
+++ b/gsxws/comptia.py
@@ -0,0 +1,89 @@
+MODIFIERS = (
+ ("A", "Not Applicable"),
+ ("B", "Continuous"),
+ ("C", "Intermittent"),
+ ("D", "Fails After Warm Up"),
+ ("E", "Environmental"),
+ ("F", "Configuration: Peripheral"),
+ ("G", "Damaged"),
+)
+
+GROUPS = (
+ ('0', 'General'),
+ ('1', 'Visual'),
+ ('2', 'Displays'),
+ ('3', 'Mass Storage'),
+ ('4', 'Input Devices'),
+ ('5', 'Boards'),
+ ('6', 'Power'),
+ ('7', 'Printer'),
+ ('8', 'Multi-function Device'),
+ ('9', 'Communication Devices'),
+ ('A', 'Share'),
+ ('B', 'iPhone'),
+ ('E', 'iPod'),
+ ('F', 'iPad'),
+)
+
+
+class CompTIA(object):
+ "Stores and accesses CompTIA codes."
+
+ def __init__(self):
+ """
+ Initialize CompTIA symptoms from JSON file
+ """
+ df = open(os.path.join(os.path.dirname(__file__), 'comptia.json'))
+ self.data = json.load(df)
+
+ def fetch(self):
+ """
+ The CompTIA Codes Lookup API retrieves a list of CompTIA groups and modifiers.
+
+ Here we must resort to raw XML parsing since SUDS throws this:
+ suds.TypeNotFound: Type not found: 'comptiaDescription'
+ when calling CompTIACodes()...
+
+ >>> CompTIA().fetch()
+ {'A': {'972': 'iPod not recognized',...
+ """
+ global COMPTIA_CACHE
+ if COMPTIA_CACHE.get("comptia"):
+ return COMPTIA_CACHE.get("comptia")
+
+ CLIENT.set_options(retxml=True)
+ dt = CLIENT.factory.create("ns3:comptiaCodeLookupRequestType")
+ dt.userSession = SESSION
+
+ try:
+ xml = CLIENT.service.CompTIACodes(dt)
+ except suds.WebFault, e:
+ raise GsxError(fault=e)
+
+ root = ET.fromstring(xml).findall('.//%s' % 'comptiaInfo')[0]
+
+ for el in root.findall(".//comptiaGroup"):
+ group = {}
+ comp_id = el[0].text
+
+ for ci in el.findall('comptiaCodeInfo'):
+ group[ci[0].text] = ci[1].text
+
+ self.data[comp_id] = group
+
+ COMPTIA_CACHE.put("comptia", self.data)
+ return self.data
+
+ def symptoms(self, component=None):
+ """
+ Returns all known CompTIA symptom codes or just the ones
+ belonging to the given component code.
+ """
+ r = dict()
+
+ for g, codes in self.data.items():
+ r[g] = list()
+ for k, v in codes.items():
+ r[g].append((k, v,))
+
+ return r[component] if component else r
diff --git a/gsxws/content.py b/gsxws/content.py
new file mode 100644
index 0000000..b73ca1f
--- /dev/null
+++ b/gsxws/content.py
@@ -0,0 +1,12 @@
+class Content(GsxObject):
+ def fetch_image(self, url):
+ """
+ The Fetch Image API allows users to get the image file from GSX,
+ for the content articles, using the image URL.
+ The image URLs will be obtained from the image html tags
+ in the data from all content APIs.
+ """
+ dt = self._make_type('ns3:fetchImageRequestType')
+ dt.imageRequest = {'imageUrl': url}
+
+ return self.submit('FetchImage', dt, 'contentResponse')
diff --git a/gsxws/core.py b/gsxws/core.py
new file mode 100644
index 0000000..0c55a61
--- /dev/null
+++ b/gsxws/core.py
@@ -0,0 +1,469 @@
+"""
+Copyright (c) 2013, Filipp Lepalaan All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+- Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+- Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+"""
+
+import re
+import json
+import base64
+import shelve
+import os.path
+import hashlib
+import logging
+import httplib
+import tempfile
+from urlparse import urlparse
+import xml.etree.ElementTree as ET
+
+from datetime import date, time, datetime, timedelta
+
+GSX_ENV = "it"
+GSX_LANG = "en"
+GSX_REGION = "emea"
+GSX_SESSION = None
+GSX_LOCALE = "en_XXX"
+
+GSX_TIMEZONES = (
+ ('GMT', "UTC (Greenwich Mean Time)"),
+ ('PDT', "UTC - 7h (Pacific Daylight Time)"),
+ ('PST', "UTC - 8h (Pacific Standard Time)"),
+ ('CDT', "UTC - 5h (Central Daylight Time)"),
+ ('CST', "UTC - 6h (Central Standard Time)"),
+ ('EDT', "UTC - 4h (Eastern Daylight Time)"),
+ ('EST', "UTC - 5h (Eastern Standard Time)"),
+ ('CEST', "UTC + 2h (Central European Summer Time)"),
+ ('CET', "UTC + 1h (Central European Time)"),
+ ('JST', "UTC + 9h (Japan Standard Time)"),
+ ('IST', "UTC + 5.5h (Indian Standard Time)"),
+ ('CCT', "UTC + 8h (Chinese Coast Time)"),
+ ('AEST', "UTC + 10h (Australian Eastern Standard Time)"),
+ ('AEDT', "UTC + 11h (Australian Eastern Daylight Time)"),
+ ('ACST', "UTC + 9.5h (Austrailian Central Standard Time)"),
+ ('ACDT', "UTC + 10.5h (Australian Central Daylight Time)"),
+ ('NZST', "UTC + 12h (New Zealand Standard Time)"),
+)
+
+GSX_REGIONS = (
+ ('002', "Asia/Pacific"),
+ ('003', "Japan"),
+ ('004', "Europe"),
+ ('005', "United States"),
+ ('006', "Canadia"),
+ ('007', "Latin America"),
+)
+
+REGION_CODES = ('apac', 'am', 'la', 'emea',)
+
+ENVIRONMENTS = (
+ ('pr', "Production"),
+ ('ut', "Development"),
+ ('it', "Testing"),
+)
+
+
+def validate(value, what=None):
+ """
+ Tries to guess the meaning of value or validate that
+ value looks like what it's supposed to be.
+ """
+ result = None
+
+ if not isinstance(value, basestring):
+ raise ValueError('%s is not valid input')
+
+ rex = {
+ 'partNumber': r'^([A-Z]{1,2})?\d{3}\-?(\d{4}|[A-Z]{2})(/[A-Z])?$',
+ 'serialNumber': r'^[A-Z0-9]{11,12}$',
+ 'eeeCode': r'^[A-Z0-9]{3,4}$',
+ 'returnOrder': r'^7\d{9}$',
+ 'repairNumber': r'^\d{12}$',
+ 'dispatchId': r'^G\d{9}$',
+ 'alternateDeviceId': r'^\d{15}$',
+ 'diagnosticEventNumber': r'^\d{23}$',
+ 'productName': r'^i?Mac',
+ }
+
+ for k, v in rex.items():
+ if re.match(v, value):
+ result = k
+
+ return (result == what) if what else result
+
+
+def get_formats(locale=GSX_LOCALE):
+ filepath = os.path.join(os.path.dirname(__file__), 'langs.json')
+ df = open(filepath, 'r')
+ return json.load(df).get(locale)
+
+
+class GsxError(Exception):
+ def __init__(self, message=None, xml=None):
+
+ if message is not None:
+ raise ValueError(message)
+
+ if xml is not None:
+ el = ET.fromstring(xml)
+ self.code = el.findtext("*//faultcode")
+
+ if self.code is None:
+ raise ValueError("An unexpected error occured")
+
+ self.message = el.findtext("*//faultstring")
+
+ def __unicode__(self):
+ return self.message
+
+ def __str__(self):
+ return self.message
+
+
+class GsxCache(object):
+ """
+ >>> GsxCache('spam').set('eggs').get()
+ 'eggs'
+ """
+ shelf = None
+ tmpdir = tempfile.gettempdir()
+ expires = timedelta(minutes=20)
+ filename = os.path.join(tmpdir, "gsxws.tmp")
+
+ def __init__(self, key):
+ self.key = key
+ self.shelf = shelve.open(self.filename, protocol=-1)
+ self.now = datetime.now()
+
+ if not self.shelf.get(key):
+ # Initialize the key
+ self.set(None)
+
+ def get(self):
+ try:
+ d = self.shelf[self.key]
+ if d['expires'] > self.now:
+ return d['value']
+ else:
+ del self.shelf[self.key]
+ except KeyError:
+ return None
+
+ def set(self, value):
+ d = {
+ 'value': value,
+ 'expires': self.now + self.expires
+ }
+
+ self.shelf[self.key] = d
+ return self
+
+
+class GsxRequest(object):
+ "Creates and submits the SOAP envelope"
+ env = None
+ obj = None # The GsxObject being submitted
+ data = None # The GsxObject payload in XML format
+ body = None # The Body part of the SOAP envelope
+
+ _request = ""
+ _response = ""
+
+ envs = ('pr', 'it', 'ut',)
+ REGION_CODES = ('apac', 'am', 'la', 'emea',)
+
+ hosts = {'pr': 'ws2', 'it': 'wsit', 'ut': 'wsut'}
+ url = "https://gsx{env}.apple.com/gsx-ws/services/{region}/asp"
+
+ def __init__(self, **kwargs):
+ "Construct the SOAP envelope"
+ self.objects = []
+ self.env = ET.Element("soapenv:Envelope")
+ self.env.set("xmlns:core", "http://gsxws.apple.com/elements/core")
+ self.env.set("xmlns:glob", "http://gsxws.apple.com/elements/global")
+ self.env.set("xmlns:asp", "http://gsxws.apple.com/elements/core/asp")
+ self.env.set("xmlns:soapenv", "http://schemas.xmlsoap.org/soap/envelope/")
+
+ ET.SubElement(self.env, "soapenv:Header")
+ self.body = ET.SubElement(self.env, "soapenv:Body")
+
+ for k, v in kwargs.items():
+ self.obj = v
+ self._request = k
+ self.data = v.to_xml(self._request)
+ self._response = k.replace("Request", "Response")
+
+ def _submit(self, method, response=None):
+ "Construct and submit the final SOAP message"
+ global GSX_ENV, GSX_REGION, GSX_SESSION
+
+ root = ET.SubElement(self.body, self.obj._namespace + method)
+ url = self.url.format(env=self.hosts[GSX_ENV], region=GSX_REGION)
+
+ if method is "Authenticate":
+ root.append(self.data)
+ else:
+ request_name = method + "Request"
+ request = ET.SubElement(root, request_name)
+ request.append(GSX_SESSION)
+
+ if self._request == request_name:
+ "Some requests don't have a top-level container."
+ self.data = list(self.data)[0]
+
+ request.append(self.data)
+
+ data = ET.tostring(self.env, "UTF-8")
+ logging.debug(data)
+
+ parsed = urlparse(url)
+
+ ws = httplib.HTTPSConnection(parsed.netloc)
+ ws.putrequest("POST", parsed.path)
+ ws.putheader("User-Agent", "py-gsxws 0.9")
+ ws.putheader("Content-type", 'text/xml; charset="UTF-8"')
+ ws.putheader("Content-length", "%d" % len(data))
+ ws.putheader("SOAPAction", '"%s"' % method)
+ ws.endheaders()
+ ws.send(data)
+
+ res = ws.getresponse()
+ xml = res.read()
+
+ if res.status > 200:
+ raise GsxError(xml=xml)
+
+ logging.debug("Response: %s %s %s" % (res.status, res.reason, xml))
+ response = response or self._response
+
+ for r in ET.fromstring(xml).findall("*//%s" % response):
+ self.objects.append(GsxObject.from_xml(r))
+
+ return self.objects
+
+ def __str__(self):
+ return ET.tostring(self.env)
+
+
+class GsxObject(object):
+ "XML/SOAP representation of a GSX object"
+ _data = {}
+
+ def __init__(self, *args, **kwargs):
+ self._data = {}
+ self._formats = get_formats()
+
+ for a in args:
+ k = validate(a)
+ if k is not None:
+ kwargs[k] = a
+
+ for k, v in kwargs.items():
+ self.__setattr__(k, v)
+
+ def __setattr__(self, name, value):
+ if name.startswith("_"):
+ super(GsxObject, self).__setattr__(name, value)
+ return
+
+ if isinstance(value, int):
+ value = str(value)
+ if isinstance(value, date):
+ value = value.strftime(self._formats['df'])
+ if isinstance(value, time):
+ value = value.strftime(self._formats['tf'])
+ if isinstance(value, bool):
+ value = 'Y' if value else 'N'
+ if isinstance(value, date):
+ value = value.strftime(self._formats['df'])
+
+ self._data[name] = value
+
+ def __getattr__(self, name):
+ try:
+ return self._data[name]
+ except KeyError:
+ raise AttributeError("Invalid attribute: %s" % name)
+
+ def _submit(self, arg, method, ret=None):
+ self._req = GsxRequest(**{arg: self})
+ result = self._req._submit(method, ret)
+ return result if len(result) > 1 else result[0]
+
+ def to_xml(self, root):
+ "Returns this object as an XML element"
+ root = ET.Element(root)
+ for k, v in self._data.items():
+ el = ET.SubElement(root, k)
+ el.text = v
+
+ return root
+
+ @classmethod
+ def from_xml(cls, el):
+ obj = GsxObject()
+
+ for r in el:
+ newitem = cls.from_xml(r)
+ k, v = r.tag, r.text
+
+ if hasattr(obj, k):
+
+ # found duplicate tag %s" % k
+ attr = obj.__getattr__(k)
+
+ if isinstance(attr, list):
+ # append to existing list
+ newattr = attr.append(newitem)
+ setattr(obj, k, newattr)
+ else:
+ # convert to list
+ setattr(obj, k, [v, newitem])
+ else:
+ # unique tag %s -> set the dictionary" % k
+ setattr(obj, k, newitem)
+
+ if k in ["partsInfo"]:
+ # found new list item %s" % k
+ attr = []
+ attr.append(GsxObject.from_xml(r))
+
+ setattr(obj, k, attr)
+
+ if k in ['packingList', 'proformaFileData', 'returnLabelFileData']:
+ v = base64.b64decode(v)
+
+ if isinstance(v, basestring):
+ # convert dates to native Python type
+ if re.search('^\d{2}/\d{2}/\d{2}$', v):
+ m, d, y = v.split('/')
+ v = date(2000+int(y), int(m), int(d)).isoformat()
+
+ # strip currency prefix and munge into float
+ if re.search('Price$', k):
+ v = float(re.sub('[A-Z ,]', '', v))
+
+ # Convert timestamps to native Python type
+ # 18-Jan-13 14:38:04
+ if re.search('TimeStamp$', k):
+ v = datetime.strptime(v, '%d-%b-%y %H:%M:%S')
+
+ # convert Y and N to corresponding boolean
+ if re.search('^[YN]$', k):
+ v = (v == 'Y')
+
+ setattr(obj, k, v)
+
+ return obj
+
+
+class GsxSession(GsxObject):
+ userId = ""
+ password = ""
+ languageCode = ""
+ userTimeZone = ""
+ serviceAccountNo = ""
+
+ _cache = None
+ _cache_key = ""
+ _session_id = ""
+ _namespace = "glob:"
+
+ def __init__(self, user_id, password, sold_to, language, timezone):
+ self.userId = user_id
+ self.password = password
+ self.languageCode = language
+ self.userTimeZone = timezone
+ self.serviceAccountNo = str(sold_to)
+
+ md5 = hashlib.md5()
+ md5.update(user_id + self.serviceAccountNo)
+
+ self._cache_key = md5.hexdigest()
+ self._cache = GsxCache(self._cache_key)
+
+ def get_session(self):
+ session = ET.Element("userSession")
+ session_id = ET.Element("userSessionId")
+ session_id.text = self._session_id
+ session.append(session_id)
+ return session
+
+ def login(self):
+ global GSX_SESSION
+
+ if not self._cache.get() is None:
+ GSX_SESSION = self._cache.get()
+ else:
+ #result = self._submit("AuthenticateRequest", "Authenticated")
+ self._req = GsxRequest(AuthenticateRequest=self)
+ result = self._req._submit("Authenticate")
+ self._session_id = result[0].userSessionId
+ GSX_SESSION = self.get_session()
+ self._cache.set(GSX_SESSION)
+
+ return GSX_SESSION
+
+ def logout(self):
+ return GsxRequest(LogoutRequest=self)
+
+
+def connect(user_id, password, sold_to,
+ environment='it',
+ language='en',
+ timezone='CEST',
+ region='emea',
+ locale='en_XXX'):
+ """
+ Establishes connection with GSX Web Services.
+ Returns the session ID of the new connection.
+ """
+ global GSX_ENV
+ global GSX_LANG
+ global GSX_LOCALE
+ global GSX_REGION
+
+ GSX_LANG = language
+ GSX_REGION = region
+ GSX_LOCALE = locale
+ GSX_ENV = environment
+
+ act = GsxSession(user_id, password, sold_to, language, timezone)
+ return act.login()
+
+
+if __name__ == '__main__':
+ import doctest
+ import argparse
+
+ parser = argparse.ArgumentParser(description='Communicate with GSX Web Services')
+
+ parser.add_argument('user_id')
+ parser.add_argument('password')
+ parser.add_argument('sold_to')
+ parser.add_argument('--language', default='en')
+ parser.add_argument('--timezone', default='CEST')
+ parser.add_argument('--environment', default='pr')
+ parser.add_argument('--region', default='emea')
+
+ args = parser.parse_args()
+ logging.basicConfig(level=logging.DEBUG)
+ connect(**vars(args))
+ doctest.testmod()
diff --git a/gsxws/diagnostics.py b/gsxws/diagnostics.py
new file mode 100644
index 0000000..38326da
--- /dev/null
+++ b/gsxws/diagnostics.py
@@ -0,0 +1,29 @@
+from core import GsxObject
+
+
+class Diagnostics(GsxObject):
+ _namespace = "glob:"
+ def fetch(self):
+ """
+ The Fetch Repair Diagnostics API allows the service providers/depot/carriers
+ to fetch MRI/CPU diagnostic details from the Apple Diagnostic Repository OR
+ diagnostic test details of iOS Devices.
+ The ticket is generated within GSX system.
+
+ >>> Diagnostics(diagnosticEventNumber='12942008007242012052919').fetch()
+ """
+ if hasattr(self, "alternateDeviceId"):
+ self._submit("lookupRequestData", "FetchIOSDiagnostic", "diagnosticTestData")
+ else:
+ self._submit("lookupRequestData", "FetchRepairDiagnostic", "FetchRepairDiagnosticResponse")
+
+ return self._req.objects[0]
+
+ def events(self):
+ """
+ The Fetch Diagnostic Event Numbers API allows users to retrieve all
+ diagnostic event numbers associated with provided input
+ (serial number or alternate device ID).
+ """
+ self._submit("lookupRequestData", "FetchDiagnosticEventNumbers", "diagnosticEventNumbers")
+ return self._req.objects
diff --git a/gsxws/escalations.py b/gsxws/escalations.py
new file mode 100644
index 0000000..3cef759
--- /dev/null
+++ b/gsxws/escalations.py
@@ -0,0 +1,18 @@
+class Escalation(GsxObject):
+ def create(self):
+ """
+ The Create General Escalation API allows users to create
+ a general escalation in GSX. The API was earlier known as GSX Help.
+ """
+ dt = self._make_type("ns1:createGenEscRequestType")
+ dt.escalationRequest = self.data
+ return self.submit("CreateGeneralEscalation", dt, "escalationConfirmation")
+
+ def update(self):
+ """
+ The Update General Escalation API allows Depot users to
+ update a general escalation in GSX.
+ """
+ dt = self._make_type("ns1:updateGeneralEscRequestType")
+ dt.escalationRequest = self.data
+ return self.submit("UpdateGeneralEscalation", dt, "escalationConfirmation")
diff --git a/langs.json b/gsxws/langs.json
index 560a595..55dfa2c 100644
--- a/langs.json
+++ b/gsxws/langs.json
@@ -37,5 +37,11 @@
"ja_XXX": {"df": "YYYY/MM/DD", "tf": "HH:MM"},
"ko_XXX": {"df": "MM/DD/YY", "tf": "HH:MM A"},
"zf_XXX": {"df": "MM/DD/YY", "tf": "HH:MM A"},
- "zh_XXX": {"df": "DD-MM-YY", "tf": "HH:MM A"}
+ "zh_XXX": {"df": "DD-MM-YY", "tf": "HH:MM A"},
+ "pt_BR": {"df": "DD/MM/YY", "tf": "HH:MM A"},
+ "pt_XXX": {"df": "DD.MM.YYYY", "tf": "HH:MM A"},
+ "tr_TR": {"df": "DD.MM.YYYY", "tf": "HH:MM"},
+ "tr_XXX": {"df": "DD.MM.YYYY", "tf": "HH:MM"},
+ "ru_RU": {"df": "DD.MM.YYYY", "tf": "HH:MM"},
+ "ru_XXX": {"df": "DD.MM.YYYY", "tf": "HH:MM"}
}
diff --git a/gsxws/lookups.py b/gsxws/lookups.py
new file mode 100644
index 0000000..0965328
--- /dev/null
+++ b/gsxws/lookups.py
@@ -0,0 +1,64 @@
+import sys
+import base64
+import logging
+import tempfile
+from datetime import date
+
+from core import GsxObject
+
+class Lookup(GsxObject):
+ def __init__(self, *args, **kwargs):
+ super(Lookup, self).__init__(*args, **kwargs)
+ self._namespace = "asp:"
+
+ def lookup(self, method, response="lookupResponseData"):
+ return self._submit("lookupRequestData", method, response)
+
+ def parts(self):
+ """
+ The Parts Lookup API allows users to access part and part pricing data prior to
+ creating a repair or order. Parts lookup is also a good way to search for
+ part numbers by various attributes of a part
+ (config code, EEE code, serial number, etc.).
+ """
+ self._namespace = "core:"
+ return self.lookup("PartsLookup", "parts")
+
+ def repairs(self):
+ """
+ The Repair Lookup API mimics the front-end repair search functionality.
+ It fetches up to 2500 repairs in a given criteria.
+ Subsequently, the extended Repair Status API can be used
+ to retrieve more details of the repair.
+ """
+ return self.lookup("RepairLookup")
+
+ def invoices(self):
+ """
+ The Invoice ID Lookup API allows AASP users
+ to fetch the invoice generated for last 24 hrs
+
+ >>> Lookup(shipTo=677592, invoiceDate=date(2012,2,6)).invoices().invoiceID
+ '9670348809'
+ """
+ return self.lookup("InvoiceIDLookup")
+
+ def invoice_details(self):
+ """
+ The Invoice Details Lookup API allows AASP users to
+ download invoice for a given invoice id.
+ >>> Lookup(invoiceID=9670348809).invoice_details()
+ """
+ result = self.lookup("InvoiceDetailsLookup")
+ pdf = base64.b64decode(result.invoiceData)
+ outfile = tempfile.NamedTemporaryFile(suffix='.pdf', delete=False)
+ outfile.write(pdf)
+ result.invoiceData = outfile.name
+ return result
+
+if __name__ == '__main__':
+ import sys
+ import doctest
+ logging.basicConfig(level=logging.DEBUG)
+ connect(*sys.argv[1:4])
+ doctest.testmod()
diff --git a/gsxws/orders.py b/gsxws/orders.py
new file mode 100644
index 0000000..1687931
--- /dev/null
+++ b/gsxws/orders.py
@@ -0,0 +1,20 @@
+class Order(GsxObject):
+ def __init__(self, type='stocking', *args, **kwargs):
+ super(Order, self).__init__(*args, **kwargs)
+ self.data['orderLines'] = list()
+
+ def add_part(self, part_number, quantity):
+ self.data['orderLines'].append({
+ 'partNumber': part_number, 'quantity': quantity
+ })
+
+ def submit(self):
+ dt = CLIENT.factory.create('ns1:createStockingOrderRequestType')
+ dt.userSession = SESSION
+ dt.orderData = self.data
+
+ try:
+ result = CLIENT.service.CreateStockingOrder(dt)
+ return result.orderConfirmation
+ except suds.WebFault, e:
+ raise GsxError(fault=e)
diff --git a/gsxws/parts.py b/gsxws/parts.py
new file mode 100644
index 0000000..8240096
--- /dev/null
+++ b/gsxws/parts.py
@@ -0,0 +1,25 @@
+import urllib
+import tempfile
+from core import GsxObject, GsxError
+
+
+class Part(GsxObject):
+ def lookup(self):
+ lookup = Lookup(**self.data)
+ return lookup.parts()
+
+ def fetch_image(self):
+ """
+ Tries the fetch the product image for this service part
+ """
+ if self.partNumber is None:
+ raise GsxError("Cannot fetch part image without part number")
+
+ image = '%s_350_350.gif' % self.partNumber
+ url = 'https://km.support.apple.com.edgekey.net/kb/imageService.jsp?image=%s' % image
+ tmpfile = tempfile.mkstemp(suffix=image)
+
+ try:
+ return urllib.urlretrieve(url, tmpfile[1])[0]
+ except Exception, e:
+ raise GsxError('Failed to fetch part image: %s' % e)
diff --git a/gsxws/products.py b/gsxws/products.py
new file mode 100644
index 0000000..176f420
--- /dev/null
+++ b/gsxws/products.py
@@ -0,0 +1,103 @@
+"""
+https://gsxwsut.apple.com/apidocs/ut/html/WSAPIChangeLog.html?user=asp
+"""
+
+import sys
+import urllib
+
+import logging
+from lookups import Lookup
+from diagnostics import Diagnostics
+from core import GsxObject, GsxError
+
+
+class Product(GsxObject):
+ "Something serviceable made by Apple"
+ _namespace = "glob:"
+
+ def model(self):
+ """
+ Returns the model description of this Product
+
+ >>> Product(serialNumber='DGKFL06JDHJP').model().configDescription
+ 'iMac (27-inch, Mid 2011)'
+ """
+ result = self._submit("productModelRequest", "FetchProductModel")
+
+ self.configDescription = result.configDescription
+ self.productLine = result.productLine
+ self.configCode = result.configCode
+ return result
+
+ def warranty(self):
+ """
+ The Warranty Status API retrieves the same warranty details
+ displayed on the GSX Coverage screen.
+ If part information is provided, the part warranty information is returned.
+ If you do not provide the optional part information in the
+ warranty status request, the unit level warranty information is returned.
+
+ >>> Product('DGKFL06JDHJP').warranty().warrantyStatus
+ 'Out Of Warranty (No Coverage)'
+ """
+ self._submit("unitDetail", "WarrantyStatus", "warrantyDetailInfo")
+ self.warrantyDetails = self._req.objects[0]
+ return self.warrantyDetails
+
+ def parts(self):
+ """
+ >>> Product('DGKFL06JDHJP').parts() # doctest: +ELLIPSIS
+ [<core.GsxObject object at ...
+ """
+ return Lookup(serialNumber=self.serialNumber).parts()
+
+ def repairs(self):
+ """
+ >>> Product(serialNumber='DGKFL06JDHJP').repairs() # doctest: +ELLIPSIS
+ <core.GsxObject object at ...
+ """
+ return Lookup(serialNumber=self.serialNumber).repairs()
+
+ def diagnostics(self):
+ """
+ >>> Product('DGKFL06JDHJP').diagnostics()
+ """
+ diags = Diagnostics(serialNumber=self.serialNumber)
+ return diags.fetch()
+
+ def fetch_image(self):
+ """
+ >>> Product('DGKFL06JDHJP').warranty().fetch_image()
+ """
+ if not hasattr(self, "imageURL"):
+ raise GsxError("No URL to fetch product image")
+
+ try:
+ result = urllib.urlretrieve(self.imageURL)
+ return result[0]
+ except Exception, e:
+ raise GsxError("Failed to fetch product image: %s" % e)
+
+ def get_activation(self):
+ """
+ The Fetch iOS Activation Details API is used to
+ fetch activation details of iOS Devices.
+
+ >>> Product('013348005376007').get_activation().unlocked
+ 'true'
+ >>> Product('W874939YX92').get_activation().unlocked # doctest: +ELLIPSIS
+ Traceback (most recent call last):
+ ...
+ GsxError: Provided serial number does not belong to an iOS Device...
+ """
+ self._namespace = "glob:"
+ act = self._submit("FetchIOSActivationDetailsRequest", "FetchIOSActivationDetails")
+ return act
+
+
+if __name__ == '__main__':
+ import doctest
+ from core import connect
+ logging.basicConfig(level=logging.DEBUG)
+ connect(*sys.argv[1:4])
+ doctest.testmod()
diff --git a/gsxws/repairs.py b/gsxws/repairs.py
new file mode 100644
index 0000000..27feca8
--- /dev/null
+++ b/gsxws/repairs.py
@@ -0,0 +1,257 @@
+"gsxws/repairs.py"
+import re
+import sys
+import logging
+
+from core import GsxObject
+from lookups import Lookup
+
+
+class Customer(GsxObject):
+ """
+ Customer address for GSX
+
+ >>> Customer(adressLine1='blaa')._data
+ {'adressLine1': 'blaa'}
+ """
+ adressLine1 = ""
+ city = ""
+ country = ""
+ firstName = ""
+ lastName = ""
+ primaryPhone = ""
+ region = ""
+ state = "ZZ"
+ zipCode = ""
+ emailAddress = ""
+
+
+class RepairOrderLine(GsxObject):
+ partNumber = ""
+ partNumber = ""
+ comptiaCode = ""
+ comptiaModifier = ""
+
+
+class Repair(GsxObject):
+ """
+ Base class for the different GSX Repair types
+
+ >>> Repair(repairStatus='Open').lookup() #doctest: +ELLIPSIS
+ [<core.GsxObject object at ...
+ >>> Repair('G135773004').details() #doctest: +ELLIPSIS
+ <core.GsxObject object at ...
+ >>> Repair('G135773004').status().repairStatus
+ 'Closed and Completed'
+ """
+ customerAddress = None
+ symptom = ""
+ diagnosis = ""
+ notes = ""
+ purchaseOrderNumber = ""
+ referenceNumber = ""
+ requestReview = False
+ serialNumber = ""
+ unitReceivedDate = ""
+ unitReceivedTime = ""
+ orderLines = []
+
+ _namespace = "core:"
+
+ TYPES = (
+ ('CA', "Carry-In/Non-Replinished"),
+ ('NE', "Return Before Replace"),
+ ('NT', "No Trouble Found"),
+ ('ON', "Onsite (Indirect/Direct)"),
+ ('RR', "Repair Or Replace/Whole Unit Mail-In"),
+ ('WH', "Mail-In"),
+ )
+
+ def __init__(self, number=None, **kwargs):
+ super(Repair, self).__init__(**kwargs)
+
+ if number is not None:
+ self.dispatchId = number
+
+ def update_sn(self, parts):
+ """
+ Description
+ The Update Serial Number API allows the service providers to update
+ the module serial numbers.
+
+ Context:
+ The API is not applicable for whole unit replacement
+ serial number entry (see KGB serial update).
+ """
+ self._namespace = "asp:"
+
+ self.partInfo = parts
+ if hasattr(self, "dispatchId"):
+ self.repairConfirmationNumber = self.dispatchId
+
+ self._submit("repairData", "UpdateSerialNumber", "repairConfirmation")
+ return self._req.objects[0]
+
+ def update_kgb_sn(self, sn):
+ """
+ Description:
+ The KGB Serial Number Update API is always to be used on
+ whole unit repairs that are in a released state.
+ This API allows users to provide the KGB serial number for the
+ whole unit exchange repairs. It also checks for the privilege
+ to create/ update whole unit exchange repairs
+ before updating the whole unit exchange repair.
+
+ Context:
+ The API is to be used on whole unit repairs that are in a released state.
+ This API can be invoked only after carry-in repair creation API.
+ """
+ self._namespace = "asp:"
+
+ self.serialNumber = sn
+ self.repairConfirmationNumber = self.dispatchId
+
+ self._submit("UpdateKGBSerialNumberRequest", "UpdateKGBSerialNumber",
+ "UpdateKGBSerialNumberResponse")
+
+ return self._req.objects[0]
+
+ def lookup(self):
+ """
+ Description:
+ The Repair Lookup API mimics the front-end repair search functionality.
+ It fetches up to 2500 repairs in a given criteria.
+ Subsequently, the extended Repair Status API can be used
+ to retrieve more details of the repair.
+ """
+ return Lookup(**self._data).repairs()
+
+ def delete(self):
+ """
+ The Delete Repair API allows the service providers to delete
+ the existing GSX Initiated Carry-In, Return Before Replace & Onsite repairs
+ which are in Declined-Rejected By TSPS Approver state,
+ that do not have an active repair id.
+ """
+ pass
+
+ def mark_complete(self, numbers=None):
+ """
+ The Mark Repair Complete API allows a single or an array of
+ repair confirmation numbers to be submitted to GSX to be marked as complete.
+ """
+ self._namespace = "asp:"
+ self.repairConfirmationNumbers = numbers or self.dispatchId
+ self._submit("MarkRepairCompleteRequest", "MarkRepairComplete",
+ "MarkRepairCompleteResponse")
+ return self._req.objects[0]
+
+ def status(self, numbers=None):
+ """
+ The Repair Status API retrieves the status
+ for the submitted repair confirmation number(s).
+ """
+ self._namespace = "asp:"
+ self.repairConfirmationNumbers = self.dispatchId
+ status = self._submit("RepairStatusRequest", "RepairStatus", "repairStatus")[0]
+ self.repairStatus = status.repairStatus
+ self._status = status
+ return status
+
+ def details(self):
+ """
+ The Repair Details API includes the shipment information
+ similar to the Repair Lookup API.
+ """
+ details = self._submit("RepairDetailsRequest", "RepairDetails", "lookupResponseData")
+
+ # fix tracking URL if available
+ for i, p in enumerate(details.partsInfo):
+ try:
+ url = re.sub('<<TRKNO>>', p.deliveryTrackingNumber, p.carrierURL)
+ details.partsInfo[i].carrierURL = url
+ except AttributeError:
+ pass
+
+ self.details = details
+ return details
+
+
+class CannotDuplicateRepair(Repair):
+ """
+ The Create CND Repair API allows Service Providers to create a repair
+ whenever the reported issue cannot be duplicated, and the repair
+ requires no parts replacement.
+ N01 Unable to Replicate
+ N02 Software Update/Issue
+ N03 Cable/Component Reseat
+ N05 SMC Reset
+ N06 PRAM Reset
+ N07 Third Party Part
+ N99 Other
+ """
+
+
+class CarryInRepair(Repair):
+ """
+ GSX validates the information and if all of the validations go through,
+ it obtains a quote for the repair and creates the carry-in repair
+
+ >>> CarryInRepair(customerAddress=Customer(firstName='Filipp'))._data
+ """
+ shipTo = ""
+ fileName = ""
+ fileData = ""
+ diagnosedByTechId = ""
+
+ def create(self):
+ """
+ GSX validates the information and if all of the validations go through,
+ it obtains a quote for the repair and creates the carry-in repair.
+ """
+ dt = self._make_type('ns2:carryInRequestType')
+ dt.repairData = self.data
+
+ return self.submit('CreateCarryInRepair', dt, 'repairConfirmation')
+
+ def update(self, newdata):
+ """
+ Description
+ The Update Carry-In Repair API allows the service providers
+ to update the existing open carry-in repairs.
+ This API assists in addition/deletion of parts and addition of notes
+ to a repair. On successful update, the repair confirmation number and
+ quote for any newly added parts would be returned.
+ In case of any validation error or unsuccessful update, a fault code is issued.
+
+ Carry-In Repair Update Status Codes:
+ AWTP Awaiting Parts
+ AWTR Parts Allocated
+ BEGR In Repair
+ RFPU Ready for Pickup
+ """
+ dt = self._make_type('ns1:updateCarryInRequestType')
+
+ # Merge old and new data (old data should have Dispatch ID)
+ dt.repairData = dict(self.data.items() + newdata.items())
+
+ return self.submit('CarryInRepairUpdate', dt, 'repairConfirmation')
+
+
+class IndirectOnsiteRepair(Repair):
+ """
+ The Create Indirect Onsite Repair API is designed to create the indirect onsite repairs.
+ When a service provider travels to the customer location to perform repair
+ on a unit eligible for onsite service, they create an indirect repair.
+ Once the repair is submitted, it is assigned a confirmation number,
+ which is a reference number to identify the repair.
+ """
+ pass
+
+
+if __name__ == '__main__':
+ import doctest
+ from core import connect
+ logging.basicConfig(level=logging.DEBUG)
+ connect(*sys.argv[1:4])
+ doctest.testmod()
diff --git a/returns.py b/gsxws/returns.py
index 4584b3e..4584b3e 100644
--- a/returns.py
+++ b/gsxws/returns.py
diff --git a/lookups.py b/lookups.py
deleted file mode 100644
index c13011e..0000000
--- a/lookups.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from repairs import GsxObject
-
-class Lookup(GsxObject):
- """docstring for Lookup"""
- def __init__(self, arg):
- super(Lookup, self).__init__()
- self.arg = arg
diff --git a/products.py b/products.py
deleted file mode 100644
index dc878f8..0000000
--- a/products.py
+++ /dev/null
@@ -1,65 +0,0 @@
-import sys
-import suds
-from gsxws import connect, GsxError
-from repairs import GsxObject
-from lookups import Lookup
-
-
-class GsxRequest(object):
- def submit(self, method, data, attr=None):
- "Submits the SOAP envelope"
- from gsxws import CLIENT, SESSION
- f = getattr(CLIENT.service, method)
-
- try:
- result = f(data)
- return getattr(result, attr) if attr else result
- except suds.WebFault, e:
- raise GsxError(fault=e)
-
-
-class Product(GsxObject, GsxRequest):
- "Something serviceable that Apple makes"
- serialNumber = ""
- alternateDeviceId = ""
- configDescription = ""
-
- def model(self):
- """
- Returns the model description of this Product
-
- >>> Product(serialNumber='DGKFL06JDHJP').model().configDescription
- iMac (27-inch, Mid 2011)
- """
- dt = self._make_type("ns3:fetchProductModelRequestType")
- dt.productModelRequest = self.data
- result = self.submit('FetchProductModel', dt, "productModelResponse")[0]
- self.configDescription = result.configDescription
- self.productLine = result.productLine
- self.configCode = result.configCode
- return result
-
- def warranty(self):
- """
- The Warranty Status API retrieves the same warranty details
- displayed on the GSX Coverage screen.
- If part information is provided, the part warranty information is returned.
- If you do not provide the optional part information in the
- warranty status request, the unit level warranty information is returned.
-
- >>> Product(serialNumber='DGKFL06JDHJP').warranty().warrantyStatus
- Out Of Warranty (No Coverage)
- """
- dt = self._make_type("ns3:warrantyStatusRequestType")
- dt.unitDetail = self.data
- result = self.submit("WarrantyStatus", dt, "warrantyDetailInfo")
- return result
-
- @property
- def parts(self):
- pass
-
-if __name__ == '__main__':
- import doctest
- connect(*sys.argv[1:4])
- doctest.testmod()
diff --git a/repairs.py b/repairs.py
deleted file mode 100644
index 2698664..0000000
--- a/repairs.py
+++ /dev/null
@@ -1,115 +0,0 @@
-"""
-gsxws/repairs.py
-"""
-import sys
-from gsxws import connect
-
-
-class GsxObject(object):
-
- data = {}
-
- def __init__(self, **kwargs):
- self.data = kwargs
-
- def _make_type(self, new_dt):
- """
- Creates the top-level datatype for the API call
- """
- from gsxws import CLIENT, SESSION
- dt = CLIENT.factory.create(new_dt)
-
- if SESSION:
- dt.userSession = SESSION
-
- return dt
-
-
-class Customer(GsxObject):
- """
- Customer address for GSX
-
- >>> Customer(adressLine1='blaa').data
- {'adressLine1': 'blaa'}
- """
- adressLine1 = ""
- city = ""
- country = ""
- firstName = ""
- lastName = ""
- primaryPhone = ""
- region = ""
- state = "ZZ"
- zipCode = ""
- emailAddress = ""
-
-
-class RepairOrderLine(GsxObject):
- partNumber = ""
- partNumber = ""
- comptiaCode = ""
- comptiaModifier = ""
-
-
-class Repair(GsxObject):
- """
- Abstract base class for the different GSX Repair types
- """
- customerAddress = None
- symptom = ""
- diagnosis = ""
- notes = ""
- purchaseOrderNumber = ""
- referenceNumber = ""
- requestReview = False
- serialNumber = ""
- unitReceivedDate = ""
- unitReceivedTime = ""
-
- orderLines = []
-
- TYPES = (
- ('CA', "Carry-In/Non-Replinished"),
- ('NE', "Return Before Replace"),
- ('NT', "No Trouble Found"),
- ('ON', "Onsite (Indirect/Direct)"),
- ('RR', "Repair Or Replace/Whole Unit Mail-In"),
- ('WH', "Mail-In"),
- )
-
- def get_data(self):
- return {'repairData': self.data}
-
- def lookup(self):
- pass
-
-
-class CarryInRepair(Repair):
- """
- GSX validates the information and if all of the validations go through,
- it obtains a quote for the repair and creates the carry-in repair
-
- >>> CarryInRepair(customerAddress=Customer(firstName='Filipp')).get_data()
- {}
- """
- shipTo = ""
- fileName = ""
- fileData = ""
- diagnosedByTechId = ""
-
-
-class IndirectOnsiteRepair(Repair):
- """
- The Create Indirect Onsite Repair API is designed to create the indirect onsite repairs.
- When a service provider travels to the customer location to perform repair
- on a unit eligible for onsite service, they create an indirect repair.
- Once the repair is submitted, it is assigned a confirmation number,
- which is a reference number to identify the repair.
- """
- pass
-
-
-if __name__ == '__main__':
- import doctest
- connect(*sys.argv[1:4])
- doctest.testmod()