aboutsummaryrefslogtreecommitdiffstats
path: root/gsxws
diff options
context:
space:
mode:
Diffstat (limited to 'gsxws')
-rw-r--r--gsxws/__init__.py25
-rw-r--r--gsxws/comms.py4
-rw-r--r--gsxws/comptia.py12
-rw-r--r--gsxws/core.py53
-rw-r--r--gsxws/diagnostics.py2
-rw-r--r--gsxws/escalations.py4
-rw-r--r--gsxws/lookups.py2
-rw-r--r--gsxws/objectify.py8
-rw-r--r--gsxws/orders.py4
-rw-r--r--gsxws/parts.py14
-rw-r--r--gsxws/products.py15
-rw-r--r--gsxws/products.yaml42
-rw-r--r--gsxws/repairs.py6
-rw-r--r--gsxws/returns.py4
-rw-r--r--gsxws/utils.py31
15 files changed, 147 insertions, 79 deletions
diff --git a/gsxws/__init__.py b/gsxws/__init__.py
index 13a3a5b..bbec77f 100644
--- a/gsxws/__init__.py
+++ b/gsxws/__init__.py
@@ -1,11 +1,14 @@
-from core import *
-from repairs import *
-from products import *
-from returns import *
-from comms import *
-from diagnostics import *
-from parts import *
-from comptia import *
-from escalations import *
-from lookups import *
-from orders import *
+# -*- coding: utf-8 -*-
+
+from .core import *
+from .repairs import *
+from .products import *
+from .returns import *
+from .comms import *
+from .diagnostics import *
+from .parts import *
+from .comptia import *
+from .escalations import *
+from .lookups import *
+from .orders import *
+from . import objectify
diff --git a/gsxws/comms.py b/gsxws/comms.py
index dc37e9b..3ff1ac3 100644
--- a/gsxws/comms.py
+++ b/gsxws/comms.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-from core import GsxObject
+from .core import GsxObject
class Communication(GsxObject):
@@ -49,7 +49,7 @@ def ack(id, status):
if __name__ == '__main__':
import sys
import doctest
- from core import connect
+ from .core import connect
logging.basicConfig(level=logging.DEBUG)
connect(*sys.argv[1:4])
doctest.testmod()
diff --git a/gsxws/comptia.py b/gsxws/comptia.py
index 3cd47d7..dfcd00e 100644
--- a/gsxws/comptia.py
+++ b/gsxws/comptia.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
import logging
-from core import GsxObject, GsxCache
+from .core import GsxObject, GsxCache
MODIFIERS = (
("A", "Not Applicable"),
@@ -69,9 +69,9 @@ class CompTIA(GsxObject):
for el in root.findall(".//comptiaGroup"):
group = []
- comp_id = unicode(el[0].text)
+ comp_id = str(el[0].text)
for ci in el.findall("comptiaCodeInfo"):
- group.append((ci[0].text, unicode(ci[1].text)),)
+ group.append((ci[0].text, str(ci[1].text)),)
self._comptia[comp_id] = group
@@ -88,9 +88,9 @@ class CompTIA(GsxObject):
"""
r = dict()
- for g, codes in self._comptia.items():
+ for g, codes in list(self._comptia.items()):
r[g] = list()
- for k, v in codes.items():
+ for k, v in list(codes.items()):
r[g].append((k, v,))
return r[component] if component else r
@@ -104,7 +104,7 @@ def fetch():
if __name__ == '__main__':
import sys
import doctest
- from core import connect
+ from .core import connect
logging.basicConfig(level=logging.DEBUG)
connect(*sys.argv[1:4])
doctest.testmod()
diff --git a/gsxws/core.py b/gsxws/core.py
index 1d3e18b..b9d6f62 100644
--- a/gsxws/core.py
+++ b/gsxws/core.py
@@ -34,12 +34,12 @@ import hashlib
import logging
import requests
import tempfile
-import objectify
import xml.etree.ElementTree as ET
+from . import objectify
from datetime import date, time, datetime, timedelta
-VERSION = "0.92"
+VERSION = "0.93"
GSX_ENV = "ut" # it, ut or pr
GSX_LANG = "en"
@@ -127,7 +127,7 @@ def validate(value, what=None):
"""
result = None
- if not isinstance(value, basestring):
+ if not isinstance(value, str):
raise ValueError('%s is not valid input (%s != string)' % (value, type(value)))
rex = {
@@ -142,7 +142,7 @@ def validate(value, what=None):
'productName': r'^i?Mac',
}
- for k, v in rex.items():
+ for k, v in list(rex.items()):
if re.match(v, value):
result = k
@@ -151,8 +151,8 @@ def validate(value, what=None):
def get_format(locale=GSX_LOCALE):
filepath = os.path.join(os.path.dirname(__file__), 'langs.json')
- df = open(filepath, 'r')
- return json.load(df).get(locale)
+ with open(filepath, 'r') as df:
+ return json.load(df).get(locale)
class GsxError(Exception):
@@ -163,7 +163,7 @@ class GsxError(Exception):
self.codes = []
self.messages = []
- if isinstance(message, basestring):
+ if isinstance(message, str):
self.messages.append(message)
if status == 403:
@@ -189,41 +189,38 @@ class GsxError(Exception):
self.messages.append(el.text)
def __str__(self):
- return repr(self.message)
+ return ' '.join(self.messages)
@property
def code(self):
try:
return self.codes[0]
except IndexError:
- return u'XXX'
-
- @property
- def message(self):
- return unicode(self)
+ return 'XXX'
@property
def errors(self):
- return dict(zip(self.codes, self.messages))
+ return dict(list(zip(self.codes, self.messages)))
def __unicode__(self):
if len(self.messages) < 1:
- return u'Unknown GSX error'
+ return 'Unknown GSX error'
- return u' '.join(self.messages)
+ return ' '.join(self.messages)
class GsxCache(object):
"""The cache creates a separate shelf for each GSX session."""
shelf = None
+ shelf_prefix = 'gsxws'
tmpdir = tempfile.gettempdir()
def __init__(self, key, expires=timedelta(minutes=20)):
self.key = key
self.expires = expires
self.now = datetime.now()
- self.fp = os.path.join(self.tmpdir, "gsxws_%s" % key)
+ self.fp = os.path.join(self.tmpdir, '%s_%s.db' % (self.shelf_prefix, key))
self.shelf = shelve.open(self.fp, protocol=-1)
if not self.shelf.get(key):
@@ -251,6 +248,13 @@ class GsxCache(object):
self.shelf[key] = d
return self
+ @classmethod
+ def nukeall(cls):
+ """Delete all gsxws caches"""
+ import subprocess
+ path = os.path.join(cls.tmpdir, cls.shelf_prefix) + '*.db'
+ subprocess.call('/bin/rm ' + path, shell=True)
+
def nuke(self):
"""Delete this cache."""
os.remove(self.fp)
@@ -281,7 +285,7 @@ class GsxRequest(object):
self.body = ET.SubElement(self.env, "soapenv:Body")
self.xml_response = ''
- for k, v in kwargs.items():
+ for k, v in list(kwargs.items()):
self.obj = v
self._request = k
self.data = v.to_xml(self._request)
@@ -295,7 +299,7 @@ class GsxRequest(object):
self._url = GSX_URL.format(env=GSX_HOSTS[GSX_ENV], region=GSX_REGION)
except KeyError:
raise GsxError('GSX environment (%s) must be one of: %s' % (GSX_ENV,
- ', '.join(GSX_HOSTS.keys())))
+ ', '.join(list(GSX_HOSTS.keys()))))
logging.debug(self._url)
logging.debug(xmldata)
@@ -376,7 +380,7 @@ class GsxRequest(object):
return ET.tostring(self.env)
def __str__(self):
- return unicode(self).encode('utf-8')
+ return str(self).encode('utf-8')
class GsxResponse:
@@ -422,7 +426,7 @@ class GsxObject(object):
if k is not None:
kwargs[k] = a
- for k, v in kwargs.items():
+ for k, v in list(kwargs.items()):
self.__setattr__(k, v)
def __setattr__(self, name, value):
@@ -479,7 +483,7 @@ class GsxObject(object):
<Element 'blaa' at 0x...
"""
root = ET.Element(root)
- for k, v in self._data.items():
+ for k, v in list(self._data.items()):
if isinstance(v, list):
for e in v:
if isinstance(e, GsxObject):
@@ -487,7 +491,7 @@ class GsxObject(object):
i.extend(e.to_xml(k))
else:
el = ET.SubElement(root, k)
- if isinstance(v, basestring):
+ if isinstance(v, str):
el.text = v
if isinstance(v, GsxObject):
el.extend(v.to_xml(k))
@@ -523,7 +527,8 @@ class GsxSession(GsxObject):
self._session_id = ""
md5 = hashlib.md5()
- md5.update(user_id + self.serviceAccountNo + GSX_ENV)
+ s = (user_id + self.serviceAccountNo + GSX_ENV).encode()
+ md5.update(s)
self._cache_key = md5.hexdigest()
self._cache = GsxCache(self._cache_key)
diff --git a/gsxws/diagnostics.py b/gsxws/diagnostics.py
index f1815e7..2ad1e8c 100644
--- a/gsxws/diagnostics.py
+++ b/gsxws/diagnostics.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-from core import GsxObject, GsxError
+from .core import GsxObject, GsxError
class Diagnostics(GsxObject):
diff --git a/gsxws/escalations.py b/gsxws/escalations.py
index 70f7732..6a1ffbb 100644
--- a/gsxws/escalations.py
+++ b/gsxws/escalations.py
@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
import os.path
-from core import GsxObject
-from lookups import Lookup
+from .core import GsxObject
+from .lookups import Lookup
STATUS_OPEN = 'O'
STATUS_CLOSED = 'C'
diff --git a/gsxws/lookups.py b/gsxws/lookups.py
index 6a91e90..e71ff40 100644
--- a/gsxws/lookups.py
+++ b/gsxws/lookups.py
@@ -5,7 +5,7 @@ import logging
import tempfile
from datetime import date
-from core import GsxObject, connect
+from .core import GsxObject, connect
class Lookup(GsxObject):
diff --git a/gsxws/objectify.py b/gsxws/objectify.py
index 7803f43..dfcae79 100644
--- a/gsxws/objectify.py
+++ b/gsxws/objectify.py
@@ -112,7 +112,7 @@ class GsxElement(objectify.ObjectifiedElement):
# Work around lxml chomping leading zeros off of IMEI numbers
if name in STRING_TYPES:
- return unicode(result.text or '')
+ return str(result.text or '')
if isinstance(result, objectify.NumberElement):
return result.pyval
@@ -120,7 +120,7 @@ class GsxElement(objectify.ObjectifiedElement):
if isinstance(result, objectify.StringElement):
name = result.tag
result = result.text or ''
- result = unicode(result)
+ result = str(result)
if not result:
return
@@ -157,10 +157,10 @@ def parse(root, response):
lookup = objectify.ObjectifyElementClassLookup(tree_class=GsxElement)
parser.set_element_class_lookup(lookup)
- if isinstance(root, basestring) and os.path.exists(root):
+ if isinstance(root, str) and os.path.exists(root):
root = objectify.parse(root, parser)
else:
- root = objectify.fromstring(root, parser)
+ root = objectify.fromstring(root.encode(), parser)
return root.find('*//%s' % response)
diff --git a/gsxws/orders.py b/gsxws/orders.py
index c06a073..685e038 100644
--- a/gsxws/orders.py
+++ b/gsxws/orders.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-from core import GsxObject
+from .core import GsxObject
class OrderLine(GsxObject):
@@ -63,7 +63,7 @@ if __name__ == '__main__':
import sys
import doctest
import logging
- from core import connect
+ from .core import connect
logging.basicConfig(level=logging.DEBUG)
connect(*sys.argv[1:5])
doctest.testmod()
diff --git a/gsxws/parts.py b/gsxws/parts.py
index 6f9fb02..0f3691b 100644
--- a/gsxws/parts.py
+++ b/gsxws/parts.py
@@ -1,10 +1,8 @@
# -*- coding: utf-8 -*-
-import urllib
-import tempfile
-
-from lookups import Lookup
-from core import GsxObject, GsxError
+from .lookups import Lookup
+from .utils import fetch_url
+from .core import GsxObject, GsxError
REASON_CODES = (
('A', 'Part not needed'),
@@ -35,11 +33,9 @@ class Part(GsxObject):
raise GsxError("Cannot fetch part image without part number")
image = "%s_350_350.gif" % self.partNumber
- url = IMAGE_URL % image
- tmpfile = tempfile.mkstemp(suffix=image)
try:
- return urllib.urlretrieve(url, tmpfile[1])[0]
+ return fetch_url(IMAGE_URL % image)
except Exception as e:
raise GsxError("Failed to fetch part image: %s" % e)
@@ -48,7 +44,7 @@ if __name__ == '__main__':
import sys
import doctest
import logging
- from core import connect
+ from .core import connect
logging.basicConfig(level=logging.DEBUG)
connect(*sys.argv[1:])
doctest.testmod()
diff --git a/gsxws/products.py b/gsxws/products.py
index 2c2745a..a656f98 100644
--- a/gsxws/products.py
+++ b/gsxws/products.py
@@ -4,11 +4,11 @@ https://gsxwsut.apple.com/apidocs/ut/html/WSAPIChangeLog.html?user=asp
"""
import re
-import urllib
-from lookups import Lookup
-from diagnostics import Diagnostics
-from core import GsxObject, GsxError, validate
+from .utils import fetch_url
+from .lookups import Lookup
+from .diagnostics import Diagnostics
+from .core import GsxObject, GsxError, validate
def models():
@@ -19,7 +19,8 @@ def models():
import os
import yaml
filepath = os.path.join(os.path.dirname(__file__), "products.yaml")
- return yaml.load(open(filepath, 'r'))
+ with open(filepath, 'r') as f:
+ return yaml.load(f)
class Product(object):
@@ -151,7 +152,7 @@ class Product(object):
raise GsxError("No URL to fetch product image")
try:
- return urllib.urlretrieve(url)[0]
+ return fetch_url(url)
except Exception as e:
raise GsxError("Failed to fetch product image: %s" % e)
@@ -262,7 +263,7 @@ if __name__ == '__main__':
import sys
import doctest
import logging
- from core import connect
+ from .core import connect
logging.basicConfig(level=logging.DEBUG)
connect(*sys.argv[1:])
doctest.testmod()
diff --git a/gsxws/products.yaml b/gsxws/products.yaml
index 81e937a..e18d063 100644
--- a/gsxws/products.yaml
+++ b/gsxws/products.yaml
@@ -5,6 +5,7 @@ APPLETV:
- "Apple TV (2nd generation)"
- "Apple TV (3rd generation)"
- "Apple TV (4th generation)"
+ - "Apple TV 4K"
DISPLAYS:
name: Display
@@ -52,7 +53,10 @@ IMAC:
- "iMac (21.5-inch, Late 2015)"
- "iMac (Retina 4K, 21.5-inch, Late 2015)"
- "iMac (Retina 5K, 27-inch, Late 2015)"
-
+ - "iMac (21.5-inch, 2017)"
+ - "iMac (Retina 4K, 21.5-inch, 2017)"
+ - "iMac (Retina 5K, 27-inch, 2017)"
+ - "iMac Pro (2017)"
IPAD:
name: iPad
@@ -79,9 +83,17 @@ IPAD:
- "iPad Air (Wi-Fi)"
- "iPad Air (Wi-Fi + Cellular)"
- "iPad Air 2"
- - "iPad Pro"
- - "iPad Pro (Wi-Fi)"
- - "iPad Pro (Wi-Fi + Cellular)"
+ - "iPad Pro (12.9-inch) (Wi-Fi)"
+ - "iPad Pro (12.9-inch) (Wi-Fi + Cellular)"
+ - "iPad Pro (9.7-inch) (Wi-Fi)"
+ - "iPad Pro (9.7-inch) (Wi-Fi + Cellular)"
+ - "iPad (5th generation) (Wi-Fi)"
+ - "iPad (5th generation) (Wi-Fi + Cellular)"
+ - "iPad Pro (10.5-inch) (Wi-Fi)"
+ - "iPad Pro (10.5-inch) (Wi-Fi + Cellular)"
+ - "iPad Pro (12.9-inch) (2nd generation) (Wi-Fi)"
+ - "iPad Pro (12.9-inch) (2nd generation) (Wi-Fi + Cellular)"
+
IPADACCESSORY:
name: iPad Accessory
@@ -112,6 +124,9 @@ IPHONE:
- "iPhone SE"
- "iPhone 7"
- "iPhone 7 Plus"
+ - "iPhone 8"
+ - "iPhone 8 Plus"
+ - "iPhone X"
IPHONEACCESSORY:
name: iPhone Accessory
@@ -153,6 +168,7 @@ MACACCESSORY:
- "Apple 85W MagSafe Power Adapter"
- "AirPort Extreme 802.11ac"
- "AirPort Time Capsule 802.11ac"
+ - "Magic Keyboard with Numeric Keypad"
IPODCLASSIC:
name: iPod Classic
@@ -224,8 +240,8 @@ MACPRO:
- Mac Pro (Mid 2010)
- Mac Pro Server (Mid 2010)
- Mac Pro (Mid 2012)
- - Mac Pro (Late 2013)
- Mac Pro Server (Mid 2012)
+ - Mac Pro (Late 2013)
MACBOOK:
name: MacBook
@@ -243,6 +259,7 @@ MACBOOK:
- "MacBook (13-inch, Mid 2010)"
- "MacBook (Retina, 12-inch, Early 2015)"
- "MacBook (Retina, 12-inch, Early 2016)"
+ - "MacBook (Retina, 12-inch, 2017)"
MACBOOKLEGACY:
name: MacBook (Legacy)
@@ -277,6 +294,7 @@ MACBOOKAIR:
- "MacBook Air (13-inch, Early 2014)"
- "MacBook Air (11-inch, Early 2015)"
- "MacBook Air (13-inch, Early 2015)"
+ - "MacBook Air (13-inch, 2017)"
MACBOOKPRO:
name: MacBook Pro
@@ -318,7 +336,12 @@ MACBOOKPRO:
- "MacBook Pro (Retina, 15-inch, Mid 2014)"
- "MacBook Pro (Retina, 13-inch, Early 2015)"
- "MacBook Pro (Retina, 15-inch, Mid 2015)"
+ - "MacBook Pro (13-inch, 2016, Two Thunderbolt 3 Ports)"
+ - "MacBook Pro (13-inch, 2016, Four Thunderbolt 3 Ports)"
+ - "MacBook Pro (15-inch, 2016)"
- "MacBook Pro (13-inch, Late 2016, Two Thunderbolt 3 Ports)"
+ - "MacBook Pro (13-inch, 2017, Four Thunderbolt 3 Ports)"
+ - "MacBook Pro (15-inch, 2017)"
SERVERS:
name: Server
@@ -389,3 +412,12 @@ WATCH:
- "Watch Sport 42mm"
- "Watch Edition 38mm"
- "Watch Edition 42mm"
+ - "Apple Watch Series 1"
+ - "Apple Watch Series 2"
+ - "Apple Watch Series 3"
+
+AUDIO:
+ name: Audio
+ models:
+ - "AirPods"
+ - "HomePod"
diff --git a/gsxws/repairs.py b/gsxws/repairs.py
index 16a9add..10da94c 100644
--- a/gsxws/repairs.py
+++ b/gsxws/repairs.py
@@ -4,8 +4,8 @@
import sys
import logging
-from core import GsxObject, GsxError, validate
-from lookups import Lookup
+from .core import GsxObject, GsxError, validate
+from .lookups import Lookup
REPAIR_TYPES = (
('CA', 'Carry-In/Non-Replinished'),
@@ -400,7 +400,7 @@ class DepotShipperLabel(GsxObject):
if __name__ == '__main__':
import doctest
- from core import connect
+ from .core import connect
logging.basicConfig(level=logging.DEBUG)
connect(*sys.argv[1:])
doctest.testmod()
diff --git a/gsxws/returns.py b/gsxws/returns.py
index 2789942..c14ed35 100644
--- a/gsxws/returns.py
+++ b/gsxws/returns.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-from core import GsxObject, validate
+from .core import GsxObject, validate
RETURN_TYPES = (
(1, "Dead On Arrival"),
@@ -131,7 +131,7 @@ if __name__ == '__main__':
import sys
import doctest
import logging
- from core import connect
+ from .core import connect
logging.basicConfig(level=logging.DEBUG)
connect(*sys.argv[1:])
doctest.testmod()
diff --git a/gsxws/utils.py b/gsxws/utils.py
new file mode 100644
index 0000000..9d71e83
--- /dev/null
+++ b/gsxws/utils.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+
+import re
+import tempfile
+import requests
+
+
+def fetch_url(url):
+ """Downloads acceptable URL to temporary file
+ """
+ ext = None
+ ALLOWED = ['jpg', 'jpeg', 'png', 'pdf', 'gif',]
+
+ try:
+ ext = re.search(r'\.([a-zA-Z]{3,})$', url).group(1)
+ except AttributeError:
+ raise ValueError('Cannot determine file extension of URL %s' % url)
+
+ ext = ext.lower()
+
+ if ext not in ALLOWED:
+ raise ValueError('File extension should be one of %s, not %s' % (', '.join(ALLOWED), ext))
+
+ try:
+ resp = requests.get(url)
+ except Exception as e:
+ raise Exception('Failed to fetch URL: %s' % e)
+
+ with tempfile.NamedTemporaryFile(delete=False, suffix='.' + ext) as fp:
+ fp.write(resp.content)
+ return fp.name