diff options
Diffstat (limited to 'gsxws')
-rw-r--r-- | gsxws/__init__.py | 25 | ||||
-rw-r--r-- | gsxws/comms.py | 4 | ||||
-rw-r--r-- | gsxws/comptia.py | 12 | ||||
-rw-r--r-- | gsxws/core.py | 53 | ||||
-rw-r--r-- | gsxws/diagnostics.py | 2 | ||||
-rw-r--r-- | gsxws/escalations.py | 4 | ||||
-rw-r--r-- | gsxws/lookups.py | 2 | ||||
-rw-r--r-- | gsxws/objectify.py | 8 | ||||
-rw-r--r-- | gsxws/orders.py | 4 | ||||
-rw-r--r-- | gsxws/parts.py | 14 | ||||
-rw-r--r-- | gsxws/products.py | 15 | ||||
-rw-r--r-- | gsxws/products.yaml | 42 | ||||
-rw-r--r-- | gsxws/repairs.py | 6 | ||||
-rw-r--r-- | gsxws/returns.py | 4 | ||||
-rw-r--r-- | gsxws/utils.py | 31 |
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 |