From 63f2f85c25af02784495fdab6e122119369b9d92 Mon Sep 17 00:00:00 2001 From: Ebag333 Date: Thu, 16 Mar 2017 02:12:26 -0700 Subject: [PATCH 01/16] PEP8 cleanup --- eos/saveddata/fit.py | 160 ++++++++++--------- gui/builtinStatsViews/outgoingViewMinimal.py | 1 - 2 files changed, 83 insertions(+), 78 deletions(-) diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py index 3338e8351..b013a5f3f 100644 --- a/eos/saveddata/fit.py +++ b/eos/saveddata/fit.py @@ -128,9 +128,9 @@ class Fit(object): self.__capRecharge = None self.__calculatedTargets = [] self.__remoteReps = { - "Armor": None, - "Shield": None, - "Hull": None, + "Armor" : None, + "Shield" : None, + "Hull" : None, "Capacitor": None, } self.factorReload = False @@ -370,9 +370,11 @@ class Fit(object): @validates("ID", "ownerID", "shipID") def validator(self, key, val): - map = {"ID": lambda _val: isinstance(_val, int), - "ownerID": lambda _val: isinstance(_val, int) or _val is None, - "shipID": lambda _val: isinstance(_val, int) or _val is None} + map = { + "ID" : lambda _val: isinstance(_val, int), + "ownerID": lambda _val: isinstance(_val, int) or _val is None, + "shipID" : lambda _val: isinstance(_val, int) or _val is None + } if not map[key](val): raise ValueError(str(val) + " is not a valid value for " + key) @@ -408,15 +410,15 @@ class Fit(object): self.ship.clear() c = chain( - self.modules, - self.drones, - self.fighters, - self.boosters, - self.implants, - self.projectedDrones, - self.projectedModules, - self.projectedFighters, - (self.character, self.extraAttributes), + self.modules, + self.drones, + self.fighters, + self.boosters, + self.implants, + self.projectedDrones, + self.projectedModules, + self.projectedFighters, + (self.character, self.extraAttributes), ) for stuff in c: @@ -476,11 +478,11 @@ class Fit(object): if warfareBuffID == 11: # Shield Burst: Active Shielding: Repair Duration/Capacitor self.modules.filteredItemBoost( - lambda mod: mod.item.requiresSkill("Shield Operation") or mod.item.requiresSkill( - "Shield Emission Systems"), "capacitorNeed", value) + lambda mod: mod.item.requiresSkill("Shield Operation") or mod.item.requiresSkill( + "Shield Emission Systems"), "capacitorNeed", value) self.modules.filteredItemBoost( - lambda mod: mod.item.requiresSkill("Shield Operation") or mod.item.requiresSkill( - "Shield Emission Systems"), "duration", value) + lambda mod: mod.item.requiresSkill("Shield Operation") or mod.item.requiresSkill( + "Shield Emission Systems"), "duration", value) if warfareBuffID == 12: # Shield Burst: Shield Extension: Shield HP self.ship.boostItemAttr("shieldCapacity", value, stackingPenalties=True) @@ -506,26 +508,26 @@ class Fit(object): if warfareBuffID == 17: # Information Burst: Electronic Superiority: EWAR Range and Strength groups = ("ECM", "Sensor Dampener", "Weapon Disruptor", "Target Painter") self.modules.filteredItemBoost(lambda mod: mod.item.group.name in groups, "maxRange", value, - stackingPenalties=True) + stackingPenalties=True) self.modules.filteredItemBoost(lambda mod: mod.item.group.name in groups, - "falloffEffectiveness", value, stackingPenalties=True) + "falloffEffectiveness", value, stackingPenalties=True) for scanType in ("Magnetometric", "Radar", "Ladar", "Gravimetric"): self.modules.filteredItemBoost(lambda mod: mod.item.group.name == "ECM", - "scan%sStrengthBonus" % scanType, value, - stackingPenalties=True) + "scan%sStrengthBonus" % scanType, value, + stackingPenalties=True) for attr in ("missileVelocityBonus", "explosionDelayBonus", "aoeVelocityBonus", "falloffBonus", "maxRangeBonus", "aoeCloudSizeBonus", "trackingSpeedBonus"): self.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Weapon Disruptor", - attr, value) + attr, value) for attr in ("maxTargetRangeBonus", "scanResolutionBonus"): self.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Sensor Dampener", - attr, value) + attr, value) self.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Target Painter", - "signatureRadiusBonus", value, stackingPenalties=True) + "signatureRadiusBonus", value, stackingPenalties=True) if warfareBuffID == 18: # Information Burst: Electronic Hardening: Scan Strength for scanType in ("Gravimetric", "Radar", "Ladar", "Magnetometric"): @@ -544,40 +546,36 @@ class Fit(object): if warfareBuffID == 21: # Skirmish Burst: Interdiction Maneuvers: Tackle Range groups = ("Stasis Web", "Warp Scrambler") self.modules.filteredItemBoost(lambda mod: mod.item.group.name in groups, "maxRange", value, - stackingPenalties=True) + stackingPenalties=True) if warfareBuffID == 22: # Skirmish Burst: Rapid Deployment: AB/MWD Speed Increase - self.modules.filteredItemBoost( - lambda mod: mod.item.requiresSkill("Afterburner") - or mod.item.requiresSkill("High Speed Maneuvering"), - "speedFactor", value, stackingPenalties=True) + self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Afterburner") or + mod.item.requiresSkill("High Speed Maneuvering"), + "speedFactor", value, stackingPenalties=True) if warfareBuffID == 23: # Mining Burst: Mining Laser Field Enhancement: Mining/Survey Range - self.modules.filteredItemBoost( - lambda mod: mod.item.requiresSkill("Mining") or - mod.item.requiresSkill("Ice Harvesting") or - mod.item.requiresSkill("Gas Cloud Harvesting"), - "maxRange", value, stackingPenalties=True) + self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Mining") or + mod.item.requiresSkill("Ice Harvesting") or + mod.item.requiresSkill("Gas Cloud Harvesting"), + "maxRange", value, stackingPenalties=True) self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("CPU Management"), - "surveyScanRange", value, stackingPenalties=True) + "surveyScanRange", value, stackingPenalties=True) if warfareBuffID == 24: # Mining Burst: Mining Laser Optimization: Mining Capacitor/Duration - self.modules.filteredItemBoost( - lambda mod: mod.item.requiresSkill("Mining") or - mod.item.requiresSkill("Ice Harvesting") or - mod.item.requiresSkill("Gas Cloud Harvesting"), - "capacitorNeed", value, stackingPenalties=True) + self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Mining") or + mod.item.requiresSkill("Ice Harvesting") or + mod.item.requiresSkill("Gas Cloud Harvesting"), + "capacitorNeed", value, stackingPenalties=True) - self.modules.filteredItemBoost( - lambda mod: mod.item.requiresSkill("Mining") or - mod.item.requiresSkill("Ice Harvesting") or - mod.item.requiresSkill("Gas Cloud Harvesting"), - "duration", value, stackingPenalties=True) + self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Mining") or + mod.item.requiresSkill("Ice Harvesting") or + mod.item.requiresSkill("Gas Cloud Harvesting"), + "duration", value, stackingPenalties=True) if warfareBuffID == 25: # Mining Burst: Mining Equipment Preservation: Crystal Volatility self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Mining"), - "crystalVolatilityChance", value, stackingPenalties=True) + "crystalVolatilityChance", value, stackingPenalties=True) if warfareBuffID == 60: # Skirmish Burst: Evasive Maneuvers: Agility self.ship.boostItemAttr("agility", value, stackingPenalties=True) @@ -850,15 +848,17 @@ class Fit(object): return amount - slots = {Slot.LOW: "lowSlots", - Slot.MED: "medSlots", - Slot.HIGH: "hiSlots", - Slot.RIG: "rigSlots", - Slot.SUBSYSTEM: "maxSubSystems", - Slot.SERVICE: "serviceSlots", - Slot.F_LIGHT: "fighterLightSlots", - Slot.F_SUPPORT: "fighterSupportSlots", - Slot.F_HEAVY: "fighterHeavySlots"} + slots = { + Slot.LOW : "lowSlots", + Slot.MED : "medSlots", + Slot.HIGH : "hiSlots", + Slot.RIG : "rigSlots", + Slot.SUBSYSTEM: "maxSubSystems", + Slot.SERVICE : "serviceSlots", + Slot.F_LIGHT : "fighterLightSlots", + Slot.F_SUPPORT: "fighterSupportSlots", + Slot.F_HEAVY : "fighterHeavySlots" + } def getSlotsFree(self, type, countDummies=False): if type in (Slot.MODE, Slot.SYSTEM): @@ -1007,29 +1007,35 @@ class Fit(object): def calculateSustainableTank(self, effective=True): if self.__sustainableTank is None: if self.capStable: - sustainable = {"armorRepair": self.extraAttributes["armorRepair"], - "shieldRepair": self.extraAttributes["shieldRepair"], - "hullRepair": self.extraAttributes["hullRepair"]} + sustainable = { + "armorRepair" : self.extraAttributes["armorRepair"], + "shieldRepair": self.extraAttributes["shieldRepair"], + "hullRepair" : self.extraAttributes["hullRepair"] + } else: sustainable = {} repairers = [] # Map a repairer type to the attribute it uses - groupAttrMap = {"Armor Repair Unit": "armorDamageAmount", - "Ancillary Armor Repairer": "armorDamageAmount", - "Hull Repair Unit": "structureDamageAmount", - "Shield Booster": "shieldBonus", - "Ancillary Shield Booster": "shieldBonus", - "Remote Armor Repairer": "armorDamageAmount", - "Remote Shield Booster": "shieldBonus"} + groupAttrMap = { + "Armor Repair Unit" : "armorDamageAmount", + "Ancillary Armor Repairer": "armorDamageAmount", + "Hull Repair Unit" : "structureDamageAmount", + "Shield Booster" : "shieldBonus", + "Ancillary Shield Booster": "shieldBonus", + "Remote Armor Repairer" : "armorDamageAmount", + "Remote Shield Booster" : "shieldBonus" + } # Map repairer type to attribute - groupStoreMap = {"Armor Repair Unit": "armorRepair", - "Hull Repair Unit": "hullRepair", - "Shield Booster": "shieldRepair", - "Ancillary Shield Booster": "shieldRepair", - "Remote Armor Repairer": "armorRepair", - "Remote Shield Booster": "shieldRepair", - "Ancillary Armor Repairer": "armorRepair", } + groupStoreMap = { + "Armor Repair Unit" : "armorRepair", + "Hull Repair Unit" : "hullRepair", + "Shield Booster" : "shieldRepair", + "Ancillary Shield Booster": "shieldRepair", + "Remote Armor Repairer" : "armorRepair", + "Remote Shield Booster" : "shieldRepair", + "Ancillary Armor Repairer": "armorRepair", + } capUsed = self.capUsed for attr in ("shieldRepair", "armorRepair", "hullRepair"): @@ -1057,7 +1063,7 @@ class Fit(object): # Sort repairers by efficiency. We want to use the most efficient repairers first repairers.sort(key=lambda _mod: _mod.getModifiedItemAttr( - groupAttrMap[_mod.item.group.name]) / _mod.getModifiedItemAttr("capacitorNeed"), reverse=True) + groupAttrMap[_mod.item.group.name]) / _mod.getModifiedItemAttr("capacitorNeed"), reverse=True) # Loop through every module until we're above peak recharge # Most efficient first, as we sorted earlier. @@ -1370,10 +1376,10 @@ class Fit(object): def __repr__(self): return u"Fit(ID={}, ship={}, name={}) at {}".format( - self.ID, self.ship.item.name, self.name, hex(id(self)) + self.ID, self.ship.item.name, self.name, hex(id(self)) ).encode('utf8') def __str__(self): return u"{} ({})".format( - self.name, self.ship.item.name + self.name, self.ship.item.name ).encode('utf8') diff --git a/gui/builtinStatsViews/outgoingViewMinimal.py b/gui/builtinStatsViews/outgoingViewMinimal.py index 70f7994dd..d2bf2c118 100644 --- a/gui/builtinStatsViews/outgoingViewMinimal.py +++ b/gui/builtinStatsViews/outgoingViewMinimal.py @@ -20,7 +20,6 @@ # noinspection PyPackageRequirements import wx from gui.statsView import StatsView -from gui.bitmapLoader import BitmapLoader from gui.utils.numberFormatter import formatAmount From f2a0e428102ff98b3af0daff3cc430efd33764bb Mon Sep 17 00:00:00 2001 From: Ebag333 Date: Thu, 16 Mar 2017 02:13:02 -0700 Subject: [PATCH 02/16] Rework error dialog to have better layout and more information --- gui/errorDialog.py | 82 +++++++++++++++++++++++++--------------------- 1 file changed, 45 insertions(+), 37 deletions(-) diff --git a/gui/errorDialog.py b/gui/errorDialog.py index 469eb9e63..6574163ca 100644 --- a/gui/errorDialog.py +++ b/gui/errorDialog.py @@ -19,21 +19,26 @@ import wx import sys -import gui.utils.fonts as fonts import config +try: + import sqlalchemy + + sqlalchemy_version = sqlalchemy.__version__ +except: + sqlalchemy_version = "Unknown" + class ErrorFrame(wx.Frame): - def __init__(self, exception, tb): - wx.Frame.__init__(self, None, id=wx.ID_ANY, title="pyfa error", pos=wx.DefaultPosition, size=wx.Size(500, 400), + v = sys.version_info + + wx.Frame.__init__(self, None, id=wx.ID_ANY, title="pyfa error", pos=wx.DefaultPosition, size=wx.Size(500, 600), style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER | wx.STAY_ON_TOP) - desc = "pyfa has experienced an unexpected error. Below is the " \ - "Traceback that contains crucial information about how this " \ - "error was triggered. Please contact the developers with " \ - "the information provided through the EVE Online forums " \ - "or file a GitHub issue." + desc = "pyfa has experienced an unexpected error. Below is the Traceback that contains crucial \n" \ + "information about how this error was triggered. Please contact the developers with the \n" \ + "information provided through the EVE Online forums or file a GitHub issue." self.SetSizeHintsSz(wx.DefaultSize, wx.DefaultSize) @@ -43,47 +48,50 @@ class ErrorFrame(wx.Frame): mainSizer = wx.BoxSizer(wx.VERTICAL) headSizer = wx.BoxSizer(wx.HORIZONTAL) - self.headingText = wx.StaticText(self, wx.ID_ANY, "Error!", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_CENTRE) - self.headingText.SetFont(wx.Font(14, 74, 90, 92, False)) + headingText = wx.StaticText(self, wx.ID_ANY, "Error!", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_CENTRE) + headingText.SetFont(wx.Font(14, 74, 90, 92, False)) - headSizer.Add(self.headingText, 1, wx.ALL, 5) + headSizer.Add(headingText, 1, wx.ALL, 5) mainSizer.Add(headSizer, 0, wx.EXPAND, 5) mainSizer.Add(wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL), 0, wx.EXPAND | wx.ALL, 5) - descSizer = wx.BoxSizer(wx.HORIZONTAL) - self.descText = wx.TextCtrl(self, wx.ID_ANY, desc, wx.DefaultPosition, wx.DefaultSize, - wx.TE_AUTO_URL | wx.TE_MULTILINE | wx.TE_READONLY | wx.BORDER_NONE | wx.TRANSPARENT_WINDOW) - self.descText.SetFont(wx.Font(fonts.BIG, wx.SWISS, wx.NORMAL, wx.NORMAL)) - descSizer.Add(self.descText, 1, wx.ALL, 5) - mainSizer.Add(descSizer, 1, wx.EXPAND, 5) + box = wx.BoxSizer(wx.VERTICAL) + mainSizer.Add(box, 0, wx.EXPAND | wx.ALIGN_TOP) - self.eveForums = wx.HyperlinkCtrl(self, wx.ID_ANY, "EVE Forums Thread", "https://forums.eveonline.com/default.aspx?g=posts&t=466425", - wx.DefaultPosition, wx.DefaultSize, wx.HL_DEFAULT_STYLE) + descText = wx.StaticText(self, wx.ID_ANY, desc) + box.Add(descText, 1, wx.ALL, 5) - mainSizer.Add(self.eveForums, 0, wx.ALL, 2) + github = wx.HyperlinkCtrl(self, wx.ID_ANY, "Github", "https://github.com/pyfa-org/Pyfa/issues", + wx.DefaultPosition, wx.DefaultSize, wx.HL_DEFAULT_STYLE) + box.Add(github, 0, wx.ALL, 5) - self.eveForums = wx.HyperlinkCtrl(self, wx.ID_ANY, "Github Issues", "https://github.com/pyfa-org/Pyfa/issues", - wx.DefaultPosition, wx.DefaultSize, wx.HL_DEFAULT_STYLE) - - mainSizer.Add(self.eveForums, 0, wx.ALL, 2) + eveForums = wx.HyperlinkCtrl(self, wx.ID_ANY, "EVE Forums", "https://forums.eveonline.com/default.aspx?g=posts&t=466425", + wx.DefaultPosition, wx.DefaultSize, wx.HL_DEFAULT_STYLE) + box.Add(eveForums, 0, wx.ALL, 5) # mainSizer.AddSpacer((0, 5), 0, wx.EXPAND, 5) - self.errorTextCtrl = wx.TextCtrl(self, wx.ID_ANY, "", wx.DefaultPosition, wx.DefaultSize, wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_RICH2 | wx.TE_DONTWRAP) - self.errorTextCtrl.SetFont(wx.Font(8, wx.FONTFAMILY_TELETYPE, wx.NORMAL, wx.NORMAL)) - mainSizer.Add(self.errorTextCtrl, 0, wx.EXPAND | wx.LEFT | wx.RIGHT, 5) + errorTextCtrl = wx.TextCtrl(self, wx.ID_ANY, "", wx.DefaultPosition, (-1, 400), wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_RICH2 | wx.TE_DONTWRAP) + errorTextCtrl.SetFont(wx.Font(8, wx.FONTFAMILY_TELETYPE, wx.NORMAL, wx.NORMAL)) + mainSizer.Add(errorTextCtrl, 0, wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, 5) - self.errorTextCtrl.AppendText("pyfa root: ") - self.errorTextCtrl.AppendText(config.pyfaPath or "Unknown") - self.errorTextCtrl.AppendText('\n') - self.errorTextCtrl.AppendText("save path: ") - self.errorTextCtrl.AppendText(config.savePath or "Unknown") - self.errorTextCtrl.AppendText('\n') - self.errorTextCtrl.AppendText("fs encoding: ") - self.errorTextCtrl.AppendText(sys.getfilesystemencoding()) - self.errorTextCtrl.AppendText('\n\n') - self.errorTextCtrl.AppendText(tb) + errorTextCtrl.AppendText("Python: \t" + '{}.{}.{}'.format(v.major, v.minor, v.micro)) + errorTextCtrl.AppendText("\n") + errorTextCtrl.AppendText("wxPython: \t" + str(wx.__version__)) + errorTextCtrl.AppendText("\n") + errorTextCtrl.AppendText("SQLAlchemy: \t" + str(sqlalchemy_version)) + errorTextCtrl.AppendText("\n") + errorTextCtrl.AppendText("pyfa version: {0} {1} - {2} {3}".format(config.version, config.tag, config.expansionName, config.expansionVersion)) + errorTextCtrl.AppendText('\n') + errorTextCtrl.AppendText("pyfa root: " + str(config.pyfaPath or "Unknown")) + errorTextCtrl.AppendText('\n') + errorTextCtrl.AppendText("save path: " + str(config.savePath or "Unknown")) + errorTextCtrl.AppendText('\n') + errorTextCtrl.AppendText("fs encoding: " + str(sys.getfilesystemencoding() or "Unknown")) + errorTextCtrl.AppendText('\n\n') + errorTextCtrl.AppendText(tb) + errorTextCtrl.Layout() self.SetSizer(mainSizer) mainSizer.Layout() From 717db2f9066a517c71b31c84f01610c02a1295ed Mon Sep 17 00:00:00 2001 From: Ebag333 Date: Thu, 16 Mar 2017 09:40:17 -0700 Subject: [PATCH 03/16] Further pyfa.py refactoring --- pyfa.py | 254 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 134 insertions(+), 120 deletions(-) diff --git a/pyfa.py b/pyfa.py index 488165724..35ea27764 100755 --- a/pyfa.py +++ b/pyfa.py @@ -23,11 +23,32 @@ import os import os.path import re import config +import traceback from optparse import OptionParser, BadOptionError, AmbiguousOptionError +# Import everything +# noinspection PyPackageRequirements +import os +import os.path + from logbook import TimedRotatingFileHandler, Logger, StreamHandler, NestedSetup, FingersCrossedHandler, NullHandler, \ CRITICAL, ERROR, WARNING, DEBUG, INFO + +try: + import wxversion + import wx + from gui.errorDialog import ErrorFrame +except ImportError: + wxversion = None + wx = None + ErrorFrame = None + +try: + import sqlalchemy +except ImportError: + sqlalchemy = None + pyfalog = Logger(__name__) @@ -37,6 +58,7 @@ class PassThroughOptionParser(OptionParser): OSX passes -psn_0_* argument, which is something that pyfa does not handle. See GH issue #423 """ + def _process_args(self, largs, rargs, values): while rargs: try: @@ -91,78 +113,18 @@ elif options.logginglevel == "Debug": else: options.logginglevel = ERROR -if not hasattr(sys, 'frozen'): - - if sys.version_info < (2, 6) or sys.version_info > (3, 0): - print("Pyfa requires python 2.x branch ( >= 2.6 )\nExiting.") - sys.exit(1) - - try: - import wxversion - except ImportError: - wxversion = None - print("Cannot find wxPython\nYou can download wxPython (2.8+) from http://www.wxpython.org/") - sys.exit(1) - - try: - if options.force28 is True: - wxversion.select('2.8') - else: - wxversion.select(['3.0', '2.8']) - except wxversion.VersionError: - print("Installed wxPython version doesn't meet requirements.\nYou can download wxPython 2.8 or 3.0 from http://www.wxpython.org/") - sys.exit(1) - - try: - import sqlalchemy - - saVersion = sqlalchemy.__version__ - saMatch = re.match("([0-9]+).([0-9]+)([b\.])([0-9]+)", saVersion) - if saMatch: - saMajor = int(saMatch.group(1)) - saMinor = int(saMatch.group(2)) - betaFlag = True if saMatch.group(3) == "b" else False - saBuild = int(saMatch.group(4)) if not betaFlag else 0 - if saMajor == 0 and (saMinor < 5 or (saMinor == 5 and saBuild < 8)): - print("Pyfa requires sqlalchemy 0.5.8 at least but current sqlalchemy version is %s\n" - "You can download sqlalchemy (0.5.8+) from http://www.sqlalchemy.org/".format(sqlalchemy.__version__)) - sys.exit(1) - else: - print("Unknown sqlalchemy version string format, skipping check") - - except ImportError: - sqlalchemy = None - print("Cannot find sqlalchemy.\nYou can download sqlalchemy (0.6+) from http://www.sqlalchemy.org/") - sys.exit(1) - - # check also for dateutil module installed. - try: - # noinspection PyPackageRequirements - import dateutil.parser # noqa - Copied import statement from service/update.py - except ImportError: - dateutil = None - print("Cannot find python-dateutil.\nYou can download python-dateutil from https://pypi.python.org/pypi/python-dateutil") - sys.exit(1) - - if __name__ == "__main__": - # Configure paths - if options.rootsavedata is True: - config.saveInRoot = True - - # set title if it wasn't supplied by argument - if options.title is None: - options.title = "pyfa %s%s - Python Fitting Assistant" % (config.version, "" if config.tag.lower() != 'git' else " (git)") - - config.debug = options.debug - - # Import everything - # noinspection PyPackageRequirements - import wx - import os - import os.path - try: + # Configure paths + if options.rootsavedata is True: + config.saveInRoot = True + + # set title if it wasn't supplied by argument + if options.title is None: + options.title = "pyfa %s%s - Python Fitting Assistant" % (config.version, "" if config.tag.lower() != 'git' else " (git)") + + config.debug = options.debug + # convert to unicode if it is set if options.savepath is not None: options.savepath = unicode(options.savepath) @@ -195,16 +157,16 @@ if __name__ == "__main__": # if we run out of setup handling NullHandler(), StreamHandler( - sys.stdout, - bubble=False, - level=options.logginglevel + sys.stdout, + bubble=False, + level=options.logginglevel ), TimedRotatingFileHandler( - savePath_Destination, - level=0, - backup_count=3, - bubble=True, - date_format='%Y-%m-%d', + savePath_Destination, + level=0, + backup_count=3, + bubble=True, + date_format='%Y-%m-%d', ), ]) else: @@ -214,76 +176,128 @@ if __name__ == "__main__": # if we run out of setup handling NullHandler(), FingersCrossedHandler( - TimedRotatingFileHandler( - savePath_Destination, - level=0, - backup_count=3, - bubble=False, - date_format='%Y-%m-%d', - ), - action_level=ERROR, - buffer_size=1000, - # pull_information=True, - # reset=False, + TimedRotatingFileHandler( + savePath_Destination, + level=0, + backup_count=3, + bubble=False, + date_format='%Y-%m-%d', + ), + action_level=ERROR, + buffer_size=1000, + # pull_information=True, + # reset=False, ) ]) except: + print("Critical error attempting to setup logging. Falling back to console only.") logging_mode = "Console Only" logging_setup = NestedSetup([ # make sure we never bubble up to the stderr handler # if we run out of setup handling NullHandler(), StreamHandler( - sys.stdout, - bubble=False + sys.stdout, + bubble=False ) ]) - import eos.db - # noinspection PyUnresolvedReferences - import service.prefetch # noqa: F401 - - # Make sure the saveddata db exists - if not os.path.exists(config.savePath): - os.mkdir(config.savePath) - - eos.db.saveddata_meta.create_all() - except Exception, e: - import traceback - from gui.errorDialog import ErrorFrame - tb = traceback.format_exc() pyfa = wx.App(False) ErrorFrame(e, tb) pyfa.MainLoop() + raise sys.exit() with logging_setup.threadbound(): - # Don't redirect if frozen - if not hasattr(sys, 'frozen'): - # Output all stdout (print) messages as warnings - try: - sys.stdout = LoggerWriter(pyfalog.warning) - except ValueError, Exception: - pyfalog.critical("Cannot access log file. Continuing without writing stdout to log.") + try: + pyfalog.info("Starting Pyfa") - if not options.debug: - # Output all stderr (stacktrace) messages as critical + # Don't redirect if frozen + if not hasattr(sys, 'frozen'): + # Output all stdout (print) messages as warnings try: - sys.stderr = LoggerWriter(pyfalog.critical) + sys.stdout = LoggerWriter(pyfalog.warning) except ValueError, Exception: - pyfalog.critical("Cannot access log file. Continuing without writing stderr to log.") + pyfalog.critical("Cannot access log file. Continuing without writing stdout to log.") - pyfalog.info("Starting Pyfa") - pyfalog.info("Running in logging mode: {0}", logging_mode) + if not options.debug: + # Output all stderr (stacktrace) messages as critical + try: + sys.stderr = LoggerWriter(pyfalog.critical) + except ValueError, Exception: + pyfalog.critical("Cannot access log file. Continuing without writing stderr to log.") - if hasattr(sys, 'frozen') and options.debug: - pyfalog.critical("Running in frozen mode with debug turned on. Forcing all output to be written to log.") + if sys.version_info < (2, 6) or sys.version_info > (3, 0): + exit_message = "\nPyfa requires python 2.x branch ( >= 2.6 )\nExiting." + pyfalog.critical(exit_message) + raise Exception(exit_message) - from gui.mainFrame import MainFrame + if wx is None or wxversion is None: + exit_message = "\nCannot find wxPython\nYou can download wxPython (2.8+) from http://www.wxpython.org/" + pyfalog.critical(exit_message) + raise Exception(exit_message) + else: + if options.force28 is True and wxversion.checkInstalled('2.8'): + pyfalog.info("wxPython is installed. Version: {0} (forced).", wxversion.getInstalled()) + elif options.force28 is not True and (wxversion.checkInstalled('2.8') or wxversion.checkInstalled('3.0')): + pyfalog.info("wxPython is installed. Version: {0}.", wxversion.getInstalled()) + else: + exit_message = "\nInstalled wxPython version doesn't meet requirements.\nYou can download wxPython 2.8 or 3.0 from http://www.wxpython.org/" + pyfalog.critical(exit_message) + raise Exception(exit_message) + + if sqlalchemy is None: + exit_message = "\nCannot find sqlalchemy.\nYou can download sqlalchemy (0.6+) from http://www.sqlalchemy.org/" + pyfalog.critical(exit_message) + raise Exception(exit_message) + else: + saVersion = sqlalchemy.__version__ + saMatch = re.match("([0-9]+).([0-9]+)([b\.])([0-9]+)", saVersion) + if saMatch: + saMajor = int(saMatch.group(1)) + saMinor = int(saMatch.group(2)) + betaFlag = True if saMatch.group(3) == "b" else False + saBuild = int(saMatch.group(4)) if not betaFlag else 0 + if saMajor == 0 and (saMinor < 5 or (saMinor == 5 and saBuild < 8)): + pyfalog.critical("Pyfa requires sqlalchemy 0.5.8 at least but current sqlalchemy version is {0}", format(sqlalchemy.__version__)) + pyfalog.critical("You can download sqlalchemy (0.5.8+) from http://www.sqlalchemy.org/") + sys.exit(1) + else: + pyfalog.warning("Unknown sqlalchemy version string format, skipping check") + + import eos.db + # noinspection PyUnresolvedReferences + import service.prefetch # noqa: F401 + + # Make sure the saveddata db exists + if not os.path.exists(config.savePath): + os.mkdir(config.savePath) + + eos.db.saveddata_meta.create_all() + + pyfalog.info("Running in logging mode: {0}", logging_mode) + + if hasattr(sys, 'frozen') and options.debug: + pyfalog.critical("Running in frozen mode with debug turned on. Forcing all output to be written to log.") + + from gui.mainFrame import MainFrame + + except Exception as e: + tb = traceback.format_exc() + pyfa = wx.App(False) + ErrorFrame(e, tb) + pyfa.MainLoop() + pyfalog.critical("Exception in main thread.") + pyfalog.critical(tb) + raise + sys.exit() pyfa = wx.App(False) MainFrame(options.title) pyfa.MainLoop() + + # TODO: Add some thread cleanup code here. Right now we bail, and that can lead to orphaned threads or threads not properly exiting. + sys.exit() From b1be2eee0604f765d4c1b04a1091ce853e8a7684 Mon Sep 17 00:00:00 2001 From: Ebag333 Date: Sat, 18 Mar 2017 12:37:23 -0700 Subject: [PATCH 04/16] Add unhandled exception handler. Now catches problems and will try and output to the pyfalog, falling back to outputting to the console. --- gui/errorDialog.py | 4 +- pyfa.py | 342 +++++++++++++++++++++++---------------------- 2 files changed, 177 insertions(+), 169 deletions(-) diff --git a/gui/errorDialog.py b/gui/errorDialog.py index 6574163ca..98e0fc1bc 100644 --- a/gui/errorDialog.py +++ b/gui/errorDialog.py @@ -90,7 +90,9 @@ class ErrorFrame(wx.Frame): errorTextCtrl.AppendText('\n') errorTextCtrl.AppendText("fs encoding: " + str(sys.getfilesystemencoding() or "Unknown")) errorTextCtrl.AppendText('\n\n') - errorTextCtrl.AppendText(tb) + if tb: + for line in tb: + errorTextCtrl.AppendText(line) errorTextCtrl.Layout() self.SetSizer(mainSizer) diff --git a/pyfa.py b/pyfa.py index 35ea27764..1c5607528 100755 --- a/pyfa.py +++ b/pyfa.py @@ -18,22 +18,16 @@ # along with pyfa. If not, see . # ============================================================================== -import sys import os -import os.path import re -import config +import sys import traceback +import warnings +from optparse import AmbiguousOptionError, BadOptionError, OptionParser -from optparse import OptionParser, BadOptionError, AmbiguousOptionError +from logbook import CRITICAL, DEBUG, ERROR, FingersCrossedHandler, INFO, Logger, NestedSetup, NullHandler, StreamHandler, TimedRotatingFileHandler, WARNING -# Import everything -# noinspection PyPackageRequirements -import os -import os.path - -from logbook import TimedRotatingFileHandler, Logger, StreamHandler, NestedSetup, FingersCrossedHandler, NullHandler, \ - CRITICAL, ERROR, WARNING, DEBUG, INFO +import config try: import wxversion @@ -88,6 +82,36 @@ class LoggerWriter: self.level(sys.stderr) +def handleGUIException(exc_type, exc_value, exc_traceback): + tb = traceback.format_tb(exc_traceback) + + try: + # Try and output to our log handler + with logging_setup.threadbound(): + pyfalog.critical("Exception in main thread: {0}", exc_value.message) + # Print the base level traceback + traceback.print_tb(exc_traceback) + + pyfa = wx.App(False) + ErrorFrame(exc_value, tb) + pyfa.MainLoop() + except: + # Most likely logging isn't available. Try and output to the console + print("Exception in main thread: " + str(exc_value.message)) + traceback.print_tb(exc_traceback) + + pyfa = wx.App(False) + ErrorFrame(exc_value, tb) + pyfa.MainLoop() + + finally: + # TODO: Add cleanup when exiting here. + sys.exit() + + +# Replace the uncaught exception handler with our own handler. +sys.excepthook = handleGUIException + # Parse command line options usage = "usage: %prog [--root]" parser = PassThroughOptionParser(usage=usage) @@ -114,186 +138,168 @@ else: options.logginglevel = ERROR if __name__ == "__main__": + # Configure paths + if options.rootsavedata is True: + config.saveInRoot = True + + # set title if it wasn't supplied by argument + if options.title is None: + options.title = "pyfa %s%s - Python Fitting Assistant" % (config.version, "" if config.tag.lower() != 'git' else " (git)") + + config.debug = options.debug + + # convert to unicode if it is set + if options.savepath is not None: + options.savepath = unicode(options.savepath) + config.defPaths(options.savepath) + + # Basic logging initialization + + # Logging levels: + ''' + logbook.CRITICAL + logbook.ERROR + logbook.WARNING + logbook.INFO + logbook.DEBUG + logbook.NOTSET + ''' + + if options.debug: + savePath_filename = "Pyfa_debug.log" + else: + savePath_filename = "Pyfa.log" + + savePath_Destination = os.path.join(config.savePath, savePath_filename) + try: - # Configure paths - if options.rootsavedata is True: - config.saveInRoot = True - - # set title if it wasn't supplied by argument - if options.title is None: - options.title = "pyfa %s%s - Python Fitting Assistant" % (config.version, "" if config.tag.lower() != 'git' else " (git)") - - config.debug = options.debug - - # convert to unicode if it is set - if options.savepath is not None: - options.savepath = unicode(options.savepath) - config.defPaths(options.savepath) - - # Basic logging initialization - - # Logging levels: - ''' - logbook.CRITICAL - logbook.ERROR - logbook.WARNING - logbook.INFO - logbook.DEBUG - logbook.NOTSET - ''' - if options.debug: - savePath_filename = "Pyfa_debug.log" - else: - savePath_filename = "Pyfa.log" - - savePath_Destination = os.path.join(config.savePath, savePath_filename) - - try: - if options.debug: - logging_mode = "Debug" - logging_setup = NestedSetup([ - # make sure we never bubble up to the stderr handler - # if we run out of setup handling - NullHandler(), - StreamHandler( - sys.stdout, - bubble=False, - level=options.logginglevel - ), - TimedRotatingFileHandler( - savePath_Destination, - level=0, - backup_count=3, - bubble=True, - date_format='%Y-%m-%d', - ), - ]) - else: - logging_mode = "User" - logging_setup = NestedSetup([ - # make sure we never bubble up to the stderr handler - # if we run out of setup handling - NullHandler(), - FingersCrossedHandler( - TimedRotatingFileHandler( - savePath_Destination, - level=0, - backup_count=3, - bubble=False, - date_format='%Y-%m-%d', - ), - action_level=ERROR, - buffer_size=1000, - # pull_information=True, - # reset=False, - ) - ]) - except: - print("Critical error attempting to setup logging. Falling back to console only.") - logging_mode = "Console Only" + logging_mode = "Debug" logging_setup = NestedSetup([ # make sure we never bubble up to the stderr handler # if we run out of setup handling NullHandler(), StreamHandler( sys.stdout, - bubble=False + bubble=False, + level=options.logginglevel + ), + TimedRotatingFileHandler( + savePath_Destination, + level=0, + backup_count=3, + bubble=True, + date_format='%Y-%m-%d', + ), + ]) + else: + logging_mode = "User" + logging_setup = NestedSetup([ + # make sure we never bubble up to the stderr handler + # if we run out of setup handling + NullHandler(), + FingersCrossedHandler( + TimedRotatingFileHandler( + savePath_Destination, + level=0, + backup_count=3, + bubble=False, + date_format='%Y-%m-%d', + ), + action_level=ERROR, + buffer_size=1000, + # pull_information=True, + # reset=False, ) ]) - - except Exception, e: - tb = traceback.format_exc() - - pyfa = wx.App(False) - ErrorFrame(e, tb) - pyfa.MainLoop() - raise - sys.exit() + except: + print("Critical error attempting to setup logging. Falling back to console only.") + logging_mode = "Console Only" + logging_setup = NestedSetup([ + # make sure we never bubble up to the stderr handler + # if we run out of setup handling + NullHandler(), + StreamHandler( + sys.stdout, + bubble=False + ) + ]) with logging_setup.threadbound(): + pyfalog.info("Starting Pyfa") + + pyfalog.info("Running in logging mode: {0}", logging_mode) + pyfalog.info("Writing log file to: {0}", savePath_Destination) + + # Output all stdout (print) messages as warnings try: - pyfalog.info("Starting Pyfa") + sys.stdout = LoggerWriter(pyfalog.warning) + except ValueError, Exception: + pyfalog.critical("Cannot access log file. Continuing without writing stdout to log.") - # Don't redirect if frozen - if not hasattr(sys, 'frozen'): - # Output all stdout (print) messages as warnings - try: - sys.stdout = LoggerWriter(pyfalog.warning) - except ValueError, Exception: - pyfalog.critical("Cannot access log file. Continuing without writing stdout to log.") + # Output all stderr (stacktrace) messages as critical + try: + sys.stderr = LoggerWriter(pyfalog.critical) + except ValueError, Exception: + pyfalog.critical("Cannot access log file. Continuing without writing stderr to log.") - if not options.debug: - # Output all stderr (stacktrace) messages as critical - try: - sys.stderr = LoggerWriter(pyfalog.critical) - except ValueError, Exception: - pyfalog.critical("Cannot access log file. Continuing without writing stderr to log.") + if sys.version_info < (2, 6) or sys.version_info > (3, 0): + exit_message = "\nPyfa requires python 2.x branch ( >= 2.6 )\nExiting." + pyfalog.critical(exit_message) + raise Exception(exit_message) - if sys.version_info < (2, 6) or sys.version_info > (3, 0): - exit_message = "\nPyfa requires python 2.x branch ( >= 2.6 )\nExiting." - pyfalog.critical(exit_message) - raise Exception(exit_message) + if hasattr(sys, 'frozen'): + pyfalog.info("Running in a frozen state.") + else: + pyfalog.info("Running in a thawed state.") - if wx is None or wxversion is None: - exit_message = "\nCannot find wxPython\nYou can download wxPython (2.8+) from http://www.wxpython.org/" - pyfalog.critical(exit_message) - raise Exception(exit_message) - else: - if options.force28 is True and wxversion.checkInstalled('2.8'): - pyfalog.info("wxPython is installed. Version: {0} (forced).", wxversion.getInstalled()) - elif options.force28 is not True and (wxversion.checkInstalled('2.8') or wxversion.checkInstalled('3.0')): - pyfalog.info("wxPython is installed. Version: {0}.", wxversion.getInstalled()) - else: - exit_message = "\nInstalled wxPython version doesn't meet requirements.\nYou can download wxPython 2.8 or 3.0 from http://www.wxpython.org/" - pyfalog.critical(exit_message) - raise Exception(exit_message) - - if sqlalchemy is None: - exit_message = "\nCannot find sqlalchemy.\nYou can download sqlalchemy (0.6+) from http://www.sqlalchemy.org/" - pyfalog.critical(exit_message) - raise Exception(exit_message) + if hasattr(sys, 'frozen') and wx is not None: + pyfalog.info("Running in frozen state with wx installed. Skipping wx validation.") + elif wx is None or wxversion is None: + exit_message = "\nCannot find wxPython\nYou can download wxPython (2.8+) from http://www.wxpython.org/" + pyfalog.critical(exit_message) + raise Exception(exit_message) + else: + if options.force28 is True and wxversion.checkInstalled('2.8'): + pyfalog.info("wxPython is installed. Version: {0} (forced).", wxversion.getInstalled()) + elif options.force28 is not True and (wxversion.checkInstalled('2.8') or wxversion.checkInstalled('3.0')): + pyfalog.info("wxPython is installed. Version: {0}.", wxversion.getInstalled()) else: - saVersion = sqlalchemy.__version__ - saMatch = re.match("([0-9]+).([0-9]+)([b\.])([0-9]+)", saVersion) - if saMatch: - saMajor = int(saMatch.group(1)) - saMinor = int(saMatch.group(2)) - betaFlag = True if saMatch.group(3) == "b" else False - saBuild = int(saMatch.group(4)) if not betaFlag else 0 - if saMajor == 0 and (saMinor < 5 or (saMinor == 5 and saBuild < 8)): - pyfalog.critical("Pyfa requires sqlalchemy 0.5.8 at least but current sqlalchemy version is {0}", format(sqlalchemy.__version__)) - pyfalog.critical("You can download sqlalchemy (0.5.8+) from http://www.sqlalchemy.org/") - sys.exit(1) + pyfalog.warning("\nInstalled wxPython version doesn't meet requirements.\nYou can download wxPython 2.8 or 3.0 from http://www.wxpython.org/") + pyfalog.critical("Attempting to run with unsupported version of wx. Version: {0}", wxversion.getInstalled()) + + if sqlalchemy is None: + exit_message = "\nCannot find sqlalchemy.\nYou can download sqlalchemy (0.6+) from http://www.sqlalchemy.org/" + pyfalog.critical(exit_message) + raise Exception(exit_message) + else: + saVersion = sqlalchemy.__version__ + saMatch = re.match("([0-9]+).([0-9]+)([b\.])([0-9]+)", saVersion) + if saMatch: + saMajor = int(saMatch.group(1)) + saMinor = int(saMatch.group(2)) + betaFlag = True if saMatch.group(3) == "b" else False + saBuild = int(saMatch.group(4)) if not betaFlag else 0 + if saMajor == 0 and (saMinor < 5 or (saMinor == 5 and saBuild < 8)): + pyfalog.critical("Pyfa requires sqlalchemy 0.5.8 at least but current sqlAlchemy version is {0}", format(sqlalchemy.__version__)) + pyfalog.critical("You can download sqlAlchemy (0.5.8+) from http://www.sqlalchemy.org/") + pyfalog.critical("Attempting to run with unsupported version of sqlAlchemy.") else: - pyfalog.warning("Unknown sqlalchemy version string format, skipping check") + pyfalog.info("Current version of sqlAlchemy is: {0}", sqlalchemy.__version__) + else: + pyfalog.warning("Unknown sqlalchemy version string format, skipping check. Version: {0}", sqlalchemy.__version__) - import eos.db - # noinspection PyUnresolvedReferences - import service.prefetch # noqa: F401 + import eos.db + # noinspection PyUnresolvedReferences + import service.prefetch # noqa: F401 - # Make sure the saveddata db exists - if not os.path.exists(config.savePath): - os.mkdir(config.savePath) + # Make sure the saveddata db exists + if not os.path.exists(config.savePath): + os.mkdir(config.savePath) - eos.db.saveddata_meta.create_all() + eos.db.saveddata_meta.create_all() - pyfalog.info("Running in logging mode: {0}", logging_mode) - - if hasattr(sys, 'frozen') and options.debug: - pyfalog.critical("Running in frozen mode with debug turned on. Forcing all output to be written to log.") - - from gui.mainFrame import MainFrame - - except Exception as e: - tb = traceback.format_exc() - pyfa = wx.App(False) - ErrorFrame(e, tb) - pyfa.MainLoop() - pyfalog.critical("Exception in main thread.") - pyfalog.critical(tb) - raise - sys.exit() + from gui.mainFrame import MainFrame pyfa = wx.App(False) MainFrame(options.title) From fbcd8396db94236b758c8f971a2a1f1a7279afc0 Mon Sep 17 00:00:00 2001 From: Ebag333 Date: Sat, 18 Mar 2017 12:55:53 -0700 Subject: [PATCH 05/16] Few little tweaks --- pyfa.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyfa.py b/pyfa.py index 1c5607528..44eb6b61a 100755 --- a/pyfa.py +++ b/pyfa.py @@ -22,7 +22,6 @@ import os import re import sys import traceback -import warnings from optparse import AmbiguousOptionError, BadOptionError, OptionParser from logbook import CRITICAL, DEBUG, ERROR, FingersCrossedHandler, INFO, Logger, NestedSetup, NullHandler, StreamHandler, TimedRotatingFileHandler, WARNING @@ -235,13 +234,13 @@ if __name__ == "__main__": try: sys.stdout = LoggerWriter(pyfalog.warning) except ValueError, Exception: - pyfalog.critical("Cannot access log file. Continuing without writing stdout to log.") + pyfalog.critical("Cannot redirect. Continuing without writing stdout to log.") # Output all stderr (stacktrace) messages as critical try: sys.stderr = LoggerWriter(pyfalog.critical) except ValueError, Exception: - pyfalog.critical("Cannot access log file. Continuing without writing stderr to log.") + pyfalog.critical("Cannot redirect. Continuing without writing stderr to log.") if sys.version_info < (2, 6) or sys.version_info > (3, 0): exit_message = "\nPyfa requires python 2.x branch ( >= 2.6 )\nExiting." @@ -255,6 +254,7 @@ if __name__ == "__main__": if hasattr(sys, 'frozen') and wx is not None: pyfalog.info("Running in frozen state with wx installed. Skipping wx validation.") + pyfalog.debug("wxPython version: {0}.", wxversion.getInstalled()) elif wx is None or wxversion is None: exit_message = "\nCannot find wxPython\nYou can download wxPython (2.8+) from http://www.wxpython.org/" pyfalog.critical(exit_message) From 9c693cd7edf8ae020d137d2edf78d171deba7ef0 Mon Sep 17 00:00:00 2001 From: Ebag333 Date: Sat, 18 Mar 2017 13:11:40 -0700 Subject: [PATCH 06/16] Python now properly requires 2.7 and not 2.6 or 2.7. Removed duplicate output to log, now that we handle exceptions. --- gui/errorDialog.py | 2 ++ pyfa.py | 8 +++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/gui/errorDialog.py b/gui/errorDialog.py index 98e0fc1bc..160043b4f 100644 --- a/gui/errorDialog.py +++ b/gui/errorDialog.py @@ -90,6 +90,8 @@ class ErrorFrame(wx.Frame): errorTextCtrl.AppendText('\n') errorTextCtrl.AppendText("fs encoding: " + str(sys.getfilesystemencoding() or "Unknown")) errorTextCtrl.AppendText('\n\n') + errorTextCtrl.AppendText("EXCEPTION: " + str(exception or "Unknown")) + errorTextCtrl.AppendText('\n\n') if tb: for line in tb: errorTextCtrl.AppendText(line) diff --git a/pyfa.py b/pyfa.py index 44eb6b61a..154bbf5fe 100755 --- a/pyfa.py +++ b/pyfa.py @@ -242,9 +242,9 @@ if __name__ == "__main__": except ValueError, Exception: pyfalog.critical("Cannot redirect. Continuing without writing stderr to log.") - if sys.version_info < (2, 6) or sys.version_info > (3, 0): - exit_message = "\nPyfa requires python 2.x branch ( >= 2.6 )\nExiting." - pyfalog.critical(exit_message) + pyfalog.info("Python version: {0}", sys.version) + if sys.version_info < (2, 7) or sys.version_info > (3, 0): + exit_message = "Pyfa requires python 2.x branch ( >= 2.7 ).\nExiting." raise Exception(exit_message) if hasattr(sys, 'frozen'): @@ -257,7 +257,6 @@ if __name__ == "__main__": pyfalog.debug("wxPython version: {0}.", wxversion.getInstalled()) elif wx is None or wxversion is None: exit_message = "\nCannot find wxPython\nYou can download wxPython (2.8+) from http://www.wxpython.org/" - pyfalog.critical(exit_message) raise Exception(exit_message) else: if options.force28 is True and wxversion.checkInstalled('2.8'): @@ -270,7 +269,6 @@ if __name__ == "__main__": if sqlalchemy is None: exit_message = "\nCannot find sqlalchemy.\nYou can download sqlalchemy (0.6+) from http://www.sqlalchemy.org/" - pyfalog.critical(exit_message) raise Exception(exit_message) else: saVersion = sqlalchemy.__version__ From 0ccd812398198f4adbe1b89b49e5b5d7ccf7fb9d Mon Sep 17 00:00:00 2001 From: Ebag333 Date: Sat, 18 Mar 2017 13:27:07 -0700 Subject: [PATCH 07/16] Add logging OS version. Deprioritize some messages to debug, so devs can run at info level with less spam (and much less impact on recalc times). --- eos/gamedata.py | 2 +- gui/builtinStatsViews/targetingMiscViewMinimal.py | 1 - gui/errorDialog.py | 7 ++++++- pyfa.py | 3 +++ 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/eos/gamedata.py b/eos/gamedata.py index 6b7aae34f..a02b7e3dc 100644 --- a/eos/gamedata.py +++ b/eos/gamedata.py @@ -174,7 +174,7 @@ class Effect(EqBase): self.__runTime = "normal" self.__activeByDefault = True self.__type = None - pyfalog.warning("ImportError generating handler: {0}", e) + pyfalog.debug("ImportError generating handler: {0}", e) except (AttributeError) as e: # Effect probably exists but there is an issue with it. Turn it into a dummy effect so we can continue, but flag it with an error. self.__handler = effectDummy diff --git a/gui/builtinStatsViews/targetingMiscViewMinimal.py b/gui/builtinStatsViews/targetingMiscViewMinimal.py index 4dffc9e40..df0d9f70a 100644 --- a/gui/builtinStatsViews/targetingMiscViewMinimal.py +++ b/gui/builtinStatsViews/targetingMiscViewMinimal.py @@ -242,7 +242,6 @@ class TargetingMiscViewMinimal(StatsView): # forces update of probe size, since this stat is used by both sig radius and sensor str if labelName == "labelFullSigRadius": - print "labelName" if fit: label.SetToolTip(wx.ToolTip("Probe Size: %.3f" % (fit.probeSize or 0))) else: diff --git a/gui/errorDialog.py b/gui/errorDialog.py index 160043b4f..8a610c8b8 100644 --- a/gui/errorDialog.py +++ b/gui/errorDialog.py @@ -17,8 +17,11 @@ # along with pyfa. If not, see . # =============================================================================== -import wx +import platform import sys + +import wx + import config try: @@ -76,6 +79,8 @@ class ErrorFrame(wx.Frame): errorTextCtrl.SetFont(wx.Font(8, wx.FONTFAMILY_TELETYPE, wx.NORMAL, wx.NORMAL)) mainSizer.Add(errorTextCtrl, 0, wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, 5) + errorTextCtrl.AppendText("OS version: \t" + str(platform.platform())) + errorTextCtrl.AppendText("\n") errorTextCtrl.AppendText("Python: \t" + '{}.{}.{}'.format(v.major, v.minor, v.micro)) errorTextCtrl.AppendText("\n") errorTextCtrl.AppendText("wxPython: \t" + str(wx.__version__)) diff --git a/pyfa.py b/pyfa.py index 154bbf5fe..28c57c348 100755 --- a/pyfa.py +++ b/pyfa.py @@ -18,6 +18,7 @@ # along with pyfa. If not, see . # ============================================================================== +import platform import os import re import sys @@ -242,6 +243,8 @@ if __name__ == "__main__": except ValueError, Exception: pyfalog.critical("Cannot redirect. Continuing without writing stderr to log.") + pyfalog.info("OS version: {0}", platform.platform()) + pyfalog.info("Python version: {0}", sys.version) if sys.version_info < (2, 7) or sys.version_info > (3, 0): exit_message = "Pyfa requires python 2.x branch ( >= 2.7 ).\nExiting." From a5cb2a9f007fba7fd188b436dd317da32ec8a227 Mon Sep 17 00:00:00 2001 From: Ebag333 Date: Wed, 22 Mar 2017 18:52:04 -0700 Subject: [PATCH 08/16] Add logbook version warning --- pyfa.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pyfa.py b/pyfa.py index 28c57c348..7388c6aec 100755 --- a/pyfa.py +++ b/pyfa.py @@ -18,14 +18,15 @@ # along with pyfa. If not, see . # ============================================================================== -import platform import os +import platform import re import sys import traceback from optparse import AmbiguousOptionError, BadOptionError, OptionParser -from logbook import CRITICAL, DEBUG, ERROR, FingersCrossedHandler, INFO, Logger, NestedSetup, NullHandler, StreamHandler, TimedRotatingFileHandler, WARNING +from logbook import CRITICAL, DEBUG, ERROR, FingersCrossedHandler, INFO, Logger, NestedSetup, NullHandler, StreamHandler, TimedRotatingFileHandler, WARNING, \ + __version__ as logbook_version import config @@ -112,6 +113,10 @@ def handleGUIException(exc_type, exc_value, exc_traceback): # Replace the uncaught exception handler with our own handler. sys.excepthook = handleGUIException +logVersion = logbook_version.split('.') +if int(logVersion[0]) < 1: + print ("Logbook version >= 1.0.0 is recommended. You may have some performance issues by continuing to use an earlier version.") + # Parse command line options usage = "usage: %prog [--root]" parser = PassThroughOptionParser(usage=usage) From fbd0a6dddad98283764ce26002c2e8933ade98b7 Mon Sep 17 00:00:00 2001 From: blitzman Date: Sat, 25 Mar 2017 22:33:06 -0400 Subject: [PATCH 09/16] Create a custom pre-check exception that we can check for when doing our unhandled exception handling. --- pyfa.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/pyfa.py b/pyfa.py index 7388c6aec..3094f2cd9 100755 --- a/pyfa.py +++ b/pyfa.py @@ -30,6 +30,11 @@ from logbook import CRITICAL, DEBUG, ERROR, FingersCrossedHandler, INFO, Logger, import config + +class PreCheckException(Exception): + pass + + try: import wxversion import wx @@ -84,9 +89,11 @@ class LoggerWriter: def handleGUIException(exc_type, exc_value, exc_traceback): + tb = traceback.format_tb(exc_traceback) try: + # Try and output to our log handler with logging_setup.threadbound(): pyfalog.critical("Exception in main thread: {0}", exc_value.message) @@ -94,7 +101,11 @@ def handleGUIException(exc_type, exc_value, exc_traceback): traceback.print_tb(exc_traceback) pyfa = wx.App(False) - ErrorFrame(exc_value, tb) + if exc_type == PreCheckException: + msgbox = wx.MessageBox(exc_value.message, 'Error', wx.ICON_ERROR | wx.STAY_ON_TOP) + msgbox.ShowModal() + else: + ErrorFrame(exc_value, tb) pyfa.MainLoop() except: # Most likely logging isn't available. Try and output to the console @@ -102,7 +113,11 @@ def handleGUIException(exc_type, exc_value, exc_traceback): traceback.print_tb(exc_traceback) pyfa = wx.App(False) - ErrorFrame(exc_value, tb) + if exc_type == PreCheckException: + msgbox = wx.MessageBox(exc_value.message, 'Error', wx.ICON_ERROR | wx.STAY_ON_TOP) + msgbox.ShowModal() + else: + ErrorFrame(exc_value, tb) pyfa.MainLoop() finally: @@ -253,7 +268,7 @@ if __name__ == "__main__": pyfalog.info("Python version: {0}", sys.version) if sys.version_info < (2, 7) or sys.version_info > (3, 0): exit_message = "Pyfa requires python 2.x branch ( >= 2.7 ).\nExiting." - raise Exception(exit_message) + raise PreCheckException(exit_message) if hasattr(sys, 'frozen'): pyfalog.info("Running in a frozen state.") @@ -265,7 +280,7 @@ if __name__ == "__main__": pyfalog.debug("wxPython version: {0}.", wxversion.getInstalled()) elif wx is None or wxversion is None: exit_message = "\nCannot find wxPython\nYou can download wxPython (2.8+) from http://www.wxpython.org/" - raise Exception(exit_message) + raise PreCheckException(exit_message) else: if options.force28 is True and wxversion.checkInstalled('2.8'): pyfalog.info("wxPython is installed. Version: {0} (forced).", wxversion.getInstalled()) @@ -277,7 +292,7 @@ if __name__ == "__main__": if sqlalchemy is None: exit_message = "\nCannot find sqlalchemy.\nYou can download sqlalchemy (0.6+) from http://www.sqlalchemy.org/" - raise Exception(exit_message) + raise PreCheckException(exit_message) else: saVersion = sqlalchemy.__version__ saMatch = re.match("([0-9]+).([0-9]+)([b\.])([0-9]+)", saVersion) From 78a8c105f48113d88402bb23f095729fc67a2df5 Mon Sep 17 00:00:00 2001 From: Ebag333 Date: Mon, 27 Mar 2017 16:57:55 -0700 Subject: [PATCH 10/16] First pass at re-implementing wx 2.8 preference --- pyfa.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pyfa.py b/pyfa.py index 3094f2cd9..af01c1033 100755 --- a/pyfa.py +++ b/pyfa.py @@ -275,6 +275,20 @@ if __name__ == "__main__": else: pyfalog.info("Running in a thawed state.") + # FIX THIS + try: + if options.force28 is True: + wxversion.select('2.8') + else: + wxversion.select(['3.0', '2.8']) + + if hasattr(sys, 'frozen'): + pyfalog.info("Running in frozen state. Skipping wx validation.") + pyfalog.debug("wxPython version: {0}.", wxversion.getInstalled()) + elif options.force28 is True: + # Remove existing wxPython + wxversion.select('2.8') + if hasattr(sys, 'frozen') and wx is not None: pyfalog.info("Running in frozen state with wx installed. Skipping wx validation.") pyfalog.debug("wxPython version: {0}.", wxversion.getInstalled()) From 03f12bfca1840dd869388b7a85525dba49318f44 Mon Sep 17 00:00:00 2001 From: Ebag333 Date: Mon, 27 Mar 2017 20:39:47 -0700 Subject: [PATCH 11/16] Finish implementing wx 2.8 selector. Add a bunch of error catching. General cleanup. (cherry picked from commit b0ec69e) --- gui/errorDialog.py | 49 +++++++++++++---- pyfa.py | 131 +++++++++++++++++++++++++-------------------- 2 files changed, 113 insertions(+), 67 deletions(-) diff --git a/gui/errorDialog.py b/gui/errorDialog.py index 8a610c8b8..f0a8c3bce 100644 --- a/gui/errorDialog.py +++ b/gui/errorDialog.py @@ -20,9 +20,13 @@ import platform import sys +# noinspection PyPackageRequirements import wx -import config +try: + import config +except: + config = None try: import sqlalchemy @@ -31,16 +35,21 @@ try: except: sqlalchemy_version = "Unknown" +try: + from logbook import __version__ as logbook_version +except: + logbook_version = "Unknown" + class ErrorFrame(wx.Frame): - def __init__(self, exception, tb): + def __init__(self, exception=None, tb=None, error_title='Error!'): v = sys.version_info wx.Frame.__init__(self, None, id=wx.ID_ANY, title="pyfa error", pos=wx.DefaultPosition, size=wx.Size(500, 600), style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER | wx.STAY_ON_TOP) - desc = "pyfa has experienced an unexpected error. Below is the Traceback that contains crucial \n" \ - "information about how this error was triggered. Please contact the developers with the \n" \ + desc = "pyfa has experienced an unexpected issue. Below is a message that contains crucial\n" \ + "information about how this was triggered. Please contact the developers with the\n" \ "information provided through the EVE Online forums or file a GitHub issue." self.SetSizeHintsSz(wx.DefaultSize, wx.DefaultSize) @@ -51,7 +60,7 @@ class ErrorFrame(wx.Frame): mainSizer = wx.BoxSizer(wx.VERTICAL) headSizer = wx.BoxSizer(wx.HORIZONTAL) - headingText = wx.StaticText(self, wx.ID_ANY, "Error!", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_CENTRE) + headingText = wx.StaticText(self, wx.ID_ANY, error_title, wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_CENTRE) headingText.SetFont(wx.Font(14, 74, 90, 92, False)) headSizer.Add(headingText, 1, wx.ALL, 5) @@ -79,24 +88,46 @@ class ErrorFrame(wx.Frame): errorTextCtrl.SetFont(wx.Font(8, wx.FONTFAMILY_TELETYPE, wx.NORMAL, wx.NORMAL)) mainSizer.Add(errorTextCtrl, 0, wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, 5) - errorTextCtrl.AppendText("OS version: \t" + str(platform.platform())) + try: + errorTextCtrl.AppendText("OS version: \t" + str(platform.platform())) + except: + errorTextCtrl.AppendText("OS version: Unknown") errorTextCtrl.AppendText("\n") - errorTextCtrl.AppendText("Python: \t" + '{}.{}.{}'.format(v.major, v.minor, v.micro)) + + try: + errorTextCtrl.AppendText("Python: \t" + '{}.{}.{}'.format(v.major, v.minor, v.micro)) + except: + errorTextCtrl.AppendText("Python: Unknown") errorTextCtrl.AppendText("\n") - errorTextCtrl.AppendText("wxPython: \t" + str(wx.__version__)) + + try: + errorTextCtrl.AppendText("wxPython: \t" + wx.VERSION_STRING) + except: + errorTextCtrl.AppendText("wxPython: Unknown") errorTextCtrl.AppendText("\n") + errorTextCtrl.AppendText("SQLAlchemy: \t" + str(sqlalchemy_version)) errorTextCtrl.AppendText("\n") - errorTextCtrl.AppendText("pyfa version: {0} {1} - {2} {3}".format(config.version, config.tag, config.expansionName, config.expansionVersion)) + + errorTextCtrl.AppendText("Logbook: \t" + str(logbook_version)) + errorTextCtrl.AppendText("\n") + + try: + errorTextCtrl.AppendText("pyfa version: {0} {1} - {2} {3}".format(config.version, config.tag, config.expansionName, config.expansionVersion)) + except: + errorTextCtrl.AppendText("pyfa version: Unknown") errorTextCtrl.AppendText('\n') + errorTextCtrl.AppendText("pyfa root: " + str(config.pyfaPath or "Unknown")) errorTextCtrl.AppendText('\n') errorTextCtrl.AppendText("save path: " + str(config.savePath or "Unknown")) errorTextCtrl.AppendText('\n') errorTextCtrl.AppendText("fs encoding: " + str(sys.getfilesystemencoding() or "Unknown")) errorTextCtrl.AppendText('\n\n') + errorTextCtrl.AppendText("EXCEPTION: " + str(exception or "Unknown")) errorTextCtrl.AppendText('\n\n') + if tb: for line in tb: errorTextCtrl.AppendText(line) diff --git a/pyfa.py b/pyfa.py index af01c1033..1609c8e19 100755 --- a/pyfa.py +++ b/pyfa.py @@ -18,6 +18,7 @@ # along with pyfa. If not, see . # ============================================================================== +import inspect import os import platform import re @@ -30,19 +31,10 @@ from logbook import CRITICAL, DEBUG, ERROR, FingersCrossedHandler, INFO, Logger, import config - -class PreCheckException(Exception): - pass - - try: import wxversion - import wx - from gui.errorDialog import ErrorFrame except ImportError: wxversion = None - wx = None - ErrorFrame = None try: import sqlalchemy @@ -68,7 +60,7 @@ class PassThroughOptionParser(OptionParser): largs.append(e.opt_str) -class LoggerWriter: +class LoggerWriter(object): def __init__(self, level): # self.level is really like using log.debug(message) # at least in my case @@ -88,7 +80,27 @@ class LoggerWriter: self.level(sys.stderr) +class PreCheckException(Exception): + def __init__(self, msg): + try: + ln = sys.exc_info()[-1].tb_lineno + except AttributeError: + ln = inspect.currentframe().f_back.f_lineno + self.message = "{0.__name__} (line {1}): {2}".format(type(self), ln, msg) + self.args = self.message, + + def handleGUIException(exc_type, exc_value, exc_traceback): + try: + # Try and import wx in case it's missing. + # noinspection PyPackageRequirements + import wx + from gui.errorDialog import ErrorFrame + except: + # noinspection PyShadowingNames + wx = None + # noinspection PyShadowingNames + ErrorFrame = None tb = traceback.format_tb(exc_traceback) @@ -100,25 +112,31 @@ def handleGUIException(exc_type, exc_value, exc_traceback): # Print the base level traceback traceback.print_tb(exc_traceback) - pyfa = wx.App(False) - if exc_type == PreCheckException: - msgbox = wx.MessageBox(exc_value.message, 'Error', wx.ICON_ERROR | wx.STAY_ON_TOP) - msgbox.ShowModal() - else: - ErrorFrame(exc_value, tb) - pyfa.MainLoop() + if wx and ErrorFrame: + pyfa_gui = wx.App(False) + if exc_type == PreCheckException: + ErrorFrame(exc_value, None, "Missing Prerequisite") + else: + ErrorFrame(exc_value, tb) + + pyfa_gui.MainLoop() + + pyfalog.info("Exiting.") except: # Most likely logging isn't available. Try and output to the console print("Exception in main thread: " + str(exc_value.message)) traceback.print_tb(exc_traceback) - pyfa = wx.App(False) - if exc_type == PreCheckException: - msgbox = wx.MessageBox(exc_value.message, 'Error', wx.ICON_ERROR | wx.STAY_ON_TOP) - msgbox.ShowModal() - else: - ErrorFrame(exc_value, tb) - pyfa.MainLoop() + if wx and ErrorFrame: + pyfa_gui = wx.App(False) + if exc_type == PreCheckException: + ErrorFrame(exc_value, None, "Missing Prerequisite") + else: + ErrorFrame(exc_value, tb) + + pyfa_gui.MainLoop() + + print("Exiting.") finally: # TODO: Add cleanup when exiting here. @@ -190,7 +208,7 @@ if __name__ == "__main__": else: savePath_filename = "Pyfa.log" - savePath_Destination = os.path.join(config.savePath, savePath_filename) + config.logPath = os.path.join(config.savePath, savePath_filename) try: if options.debug: @@ -205,7 +223,7 @@ if __name__ == "__main__": level=options.logginglevel ), TimedRotatingFileHandler( - savePath_Destination, + config.logPath, level=0, backup_count=3, bubble=True, @@ -220,7 +238,7 @@ if __name__ == "__main__": NullHandler(), FingersCrossedHandler( TimedRotatingFileHandler( - savePath_Destination, + config.logPath, level=0, backup_count=3, bubble=False, @@ -248,26 +266,28 @@ if __name__ == "__main__": with logging_setup.threadbound(): pyfalog.info("Starting Pyfa") + pyfalog.info("Logbook version: {0}", logbook_version) + pyfalog.info("Running in logging mode: {0}", logging_mode) - pyfalog.info("Writing log file to: {0}", savePath_Destination) + pyfalog.info("Writing log file to: {0}", config.logPath) # Output all stdout (print) messages as warnings try: sys.stdout = LoggerWriter(pyfalog.warning) - except ValueError, Exception: + except: pyfalog.critical("Cannot redirect. Continuing without writing stdout to log.") # Output all stderr (stacktrace) messages as critical try: sys.stderr = LoggerWriter(pyfalog.critical) - except ValueError, Exception: + except: pyfalog.critical("Cannot redirect. Continuing without writing stderr to log.") pyfalog.info("OS version: {0}", platform.platform()) pyfalog.info("Python version: {0}", sys.version) if sys.version_info < (2, 7) or sys.version_info > (3, 0): - exit_message = "Pyfa requires python 2.x branch ( >= 2.7 ).\nExiting." + exit_message = "Pyfa requires python 2.x branch ( >= 2.7 )." raise PreCheckException(exit_message) if hasattr(sys, 'frozen'): @@ -275,34 +295,29 @@ if __name__ == "__main__": else: pyfalog.info("Running in a thawed state.") - # FIX THIS - try: - if options.force28 is True: - wxversion.select('2.8') - else: - wxversion.select(['3.0', '2.8']) - - if hasattr(sys, 'frozen'): - pyfalog.info("Running in frozen state. Skipping wx validation.") - pyfalog.debug("wxPython version: {0}.", wxversion.getInstalled()) - elif options.force28 is True: - # Remove existing wxPython - wxversion.select('2.8') - - if hasattr(sys, 'frozen') and wx is not None: - pyfalog.info("Running in frozen state with wx installed. Skipping wx validation.") - pyfalog.debug("wxPython version: {0}.", wxversion.getInstalled()) - elif wx is None or wxversion is None: - exit_message = "\nCannot find wxPython\nYou can download wxPython (2.8+) from http://www.wxpython.org/" - raise PreCheckException(exit_message) + if not hasattr(sys, 'frozen') and wxversion: + try: + if options.force28 is True: + pyfalog.info("Selecting wx version: 2.8. (Forced)") + wxversion.select('2.8') + else: + pyfalog.info("Selecting wx versions: 3.0, 2.8") + wxversion.select(['3.0', '2.8']) + except: + pyfalog.warning("Unable to select wx version. Attempting to import wx without specifying the version.") else: - if options.force28 is True and wxversion.checkInstalled('2.8'): - pyfalog.info("wxPython is installed. Version: {0} (forced).", wxversion.getInstalled()) - elif options.force28 is not True and (wxversion.checkInstalled('2.8') or wxversion.checkInstalled('3.0')): - pyfalog.info("wxPython is installed. Version: {0}.", wxversion.getInstalled()) - else: - pyfalog.warning("\nInstalled wxPython version doesn't meet requirements.\nYou can download wxPython 2.8 or 3.0 from http://www.wxpython.org/") - pyfalog.critical("Attempting to run with unsupported version of wx. Version: {0}", wxversion.getInstalled()) + if not wxversion: + pyfalog.warning("wxVersion not found. Attempting to import wx without specifying the version.") + + try: + # noinspection PyPackageRequirements + import wx + from gui.errorDialog import ErrorFrame + except: + exit_message = "Cannot import wxPython. You can download wxPython (2.8+) from http://www.wxpython.org/" + raise PreCheckException(exit_message) + + pyfalog.info("wxPython version: {0}.", str(wx.VERSION_STRING)) if sqlalchemy is None: exit_message = "\nCannot find sqlalchemy.\nYou can download sqlalchemy (0.6+) from http://www.sqlalchemy.org/" From b99c44667ef527f920dae006c8c48a99280de19a Mon Sep 17 00:00:00 2001 From: Ebag333 Date: Mon, 27 Mar 2017 21:11:07 -0700 Subject: [PATCH 12/16] Remove unused import --- pyfa.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyfa.py b/pyfa.py index 52e860279..12060a595 100755 --- a/pyfa.py +++ b/pyfa.py @@ -312,7 +312,6 @@ if __name__ == "__main__": try: # noinspection PyPackageRequirements import wx - from gui.errorDialog import ErrorFrame except: exit_message = "Cannot import wxPython. You can download wxPython (2.8+) from http://www.wxpython.org/" raise PreCheckException(exit_message) From 535b75225bf78930a24882b35dc6d876a61ca1fa Mon Sep 17 00:00:00 2001 From: Ebag333 Date: Tue, 28 Mar 2017 18:49:10 -0700 Subject: [PATCH 13/16] dump module list and versions --- pyfa.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pyfa.py b/pyfa.py index 12060a595..939393a99 100755 --- a/pyfa.py +++ b/pyfa.py @@ -108,6 +108,13 @@ def handleGUIException(exc_type, exc_value, exc_traceback): # Try and output to our log handler with logging_setup.threadbound(): + module_list = list(set(sys.modules) & set(globals())) + if module_list: + pyfalog.info("Imported Python Modules:") + for imported_module in module_list: + module_details = sys.modules[imported_module] + pyfalog.info("{0}: {1}", imported_module, getattr(module_details, '__version__', '')) + pyfalog.critical("Exception in main thread: {0}", exc_value.message) # Print the base level traceback traceback.print_tb(exc_traceback) @@ -124,6 +131,13 @@ def handleGUIException(exc_type, exc_value, exc_traceback): pyfalog.info("Exiting.") except: # Most likely logging isn't available. Try and output to the console + module_list = list(set(sys.modules) & set(globals())) + if module_list: + pyfalog.info("Imported Python Modules:") + for imported_module in module_list: + module_details = sys.modules[imported_module] + print(str(imported_module) + ": " + str(getattr(module_details, '__version__', ''))) + print("Exception in main thread: " + str(exc_value.message)) traceback.print_tb(exc_traceback) @@ -352,6 +366,7 @@ if __name__ == "__main__": pyfa = wx.App(False) MainFrame(options.title) + test = 1/0 pyfa.MainLoop() # TODO: Add some thread cleanup code here. Right now we bail, and that can lead to orphaned threads or threads not properly exiting. From cdec6fad99de6bb91c59c1854ebc76637ff4e885 Mon Sep 17 00:00:00 2001 From: Ebag333 Date: Sun, 2 Apr 2017 00:57:11 -0700 Subject: [PATCH 14/16] Clean up requirements file. Add detection and warning for possibly missing requirement. Change back to using the modal. --- pyfa.py | 25 ++++++++++++++++--- requirements.txt | 8 +++--- requirements_build_linux.txt | 2 +- ...uild_OSx.txt => requirements_build_osx.txt | 2 +- requirements_build_windows.txt | 2 +- requirements_test.txt | 12 ++++----- 6 files changed, 35 insertions(+), 16 deletions(-) rename requirements_build_OSx.txt => requirements_build_osx.txt (90%) diff --git a/pyfa.py b/pyfa.py index 939393a99..691a9c177 100755 --- a/pyfa.py +++ b/pyfa.py @@ -24,6 +24,7 @@ import platform import re import sys import traceback +from imp import find_module from optparse import AmbiguousOptionError, BadOptionError, OptionParser from logbook import CRITICAL, DEBUG, ERROR, FingersCrossedHandler, INFO, Logger, NestedSetup, NullHandler, StreamHandler, TimedRotatingFileHandler, WARNING, \ @@ -122,7 +123,8 @@ def handleGUIException(exc_type, exc_value, exc_traceback): if wx and ErrorFrame: pyfa_gui = wx.App(False) if exc_type == PreCheckException: - ErrorFrame(exc_value, None, "Missing Prerequisite") + msgbox = wx.MessageBox(exc_value.message, 'Error', wx.ICON_ERROR | wx.STAY_ON_TOP) + msgbox.ShowModal() else: ErrorFrame(exc_value, tb) @@ -144,7 +146,8 @@ def handleGUIException(exc_type, exc_value, exc_traceback): if wx and ErrorFrame: pyfa_gui = wx.App(False) if exc_type == PreCheckException: - ErrorFrame(exc_value, None, "Missing Prerequisite") + msgbox = wx.MessageBox(exc_value.message, 'Error', wx.ICON_ERROR | wx.STAY_ON_TOP) + msgbox.ShowModal() else: ErrorFrame(exc_value, tb) @@ -352,6 +355,20 @@ if __name__ == "__main__": else: pyfalog.warning("Unknown sqlalchemy version string format, skipping check. Version: {0}", sqlalchemy.__version__) + requirements_path = os.path.join(config.pyfaPath, "requirements.txt") + if os.path.exists(requirements_path): + file = open(requirements_path, "r") + for requirement in file: + requirement = requirement.replace("\n", "") + requirement_parsed = requirement.split(' ') + if requirement_parsed: + try: + find_module(requirement_parsed[0]) + except ImportError as e: + pyfalog.warning("Possibly missing required module: {0}", requirement_parsed[0]) + if len(requirement_parsed) == 3: + pyfalog.warning("Recommended version {0} {1}", requirement_parsed[1], requirement_parsed[2]) + import eos.db # noinspection PyUnresolvedReferences import service.prefetch # noqa: F401 @@ -364,9 +381,11 @@ if __name__ == "__main__": from gui.mainFrame import MainFrame + + test = 1/0 + pyfa = wx.App(False) MainFrame(options.title) - test = 1/0 pyfa.MainLoop() # TODO: Add some thread cleanup code here. Right now we bail, and that can lead to orphaned threads or threads not properly exiting. diff --git a/requirements.txt b/requirements.txt index d3bc33dde..cac9199c4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ -logbook>=1.0.0 +logbook >= 1.0.0 matplotlib -PyYAML +PyYAML >= 3.12 python-dateutil urllib3 -requests==2.11.1 -sqlalchemy>=1.0.5 +requests == 2.11.1 +sqlalchemy >= 1.0.5 diff --git a/requirements_build_linux.txt b/requirements_build_linux.txt index 3a4640baf..28f42aa34 100644 --- a/requirements_build_linux.txt +++ b/requirements_build_linux.txt @@ -2,7 +2,7 @@ PyInstaller >= 3.2.1 cycler >= 0.10.0 functools32 >= 3.2.3 future >= 0.16.0 -numpy >= 1.12. +numpy >= 1.12 pyparsing >= 2.1.10 pytz >= 2016.10 six diff --git a/requirements_build_OSx.txt b/requirements_build_osx.txt similarity index 90% rename from requirements_build_OSx.txt rename to requirements_build_osx.txt index 51f6135fd..67b64e480 100644 --- a/requirements_build_OSx.txt +++ b/requirements_build_osx.txt @@ -2,7 +2,7 @@ PyInstaller >= 3.2.1 cycler >= 0.10.0 functools32 >= 3.2.3 future >= 0.16.0 -numpy >= 1.12. +numpy >= 1.12 pyparsing >= 2.1.10 pypiwin32 >= 219 pytz >= 2016.10 diff --git a/requirements_build_windows.txt b/requirements_build_windows.txt index 173ce92d0..c25a341af 100644 --- a/requirements_build_windows.txt +++ b/requirements_build_windows.txt @@ -2,7 +2,7 @@ PyInstaller >= 3.2.1 cycler >= 0.10.0 functools32 >= 3.2.3 future >= 0.16.0 -numpy >= 1.12. +numpy >= 1.12 pyparsing >= 2.1.10 pypiwin32 >= 219 pytz >= 2016.10 diff --git a/requirements_test.txt b/requirements_test.txt index 33f8973cd..50191648d 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,8 +1,8 @@ -pytest==3.0.3 -pytest-mock==1.2 -pytest-cov==2.3.1 -pytest-capturelog==0.7 -coverage==4.2 -coveralls==1.1 +pytest == 3.0.3 +pytest-mock == 1.2 +pytest-cov == 2.3.1 +pytest-capturelog == 0.7 +coverage == 4.2 +coveralls == 1.1 codecov tox From 55dff65142d5d9c5ca1cb3314ee82454938373eb Mon Sep 17 00:00:00 2001 From: Ebag333 Date: Sun, 2 Apr 2017 01:03:14 -0700 Subject: [PATCH 15/16] Tox things --- pyfa.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/pyfa.py b/pyfa.py index 691a9c177..9367f67ab 100755 --- a/pyfa.py +++ b/pyfa.py @@ -255,11 +255,11 @@ if __name__ == "__main__": NullHandler(), FingersCrossedHandler( TimedRotatingFileHandler( - config.logPath, - level=0, - backup_count=3, - bubble=False, - date_format='%Y-%m-%d', + config.logPath, + level=0, + backup_count=3, + bubble=False, + date_format='%Y-%m-%d', ), action_level=ERROR, buffer_size=1000, @@ -381,9 +381,6 @@ if __name__ == "__main__": from gui.mainFrame import MainFrame - - test = 1/0 - pyfa = wx.App(False) MainFrame(options.title) pyfa.MainLoop() From 48db3c8ce4a86c1683afbc99588d8ff3ac894dc3 Mon Sep 17 00:00:00 2001 From: Ebag333 Date: Tue, 4 Apr 2017 15:01:05 -0700 Subject: [PATCH 16/16] Add some key logging for database information. --- eos/db/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/eos/db/__init__.py b/eos/db/__init__.py index 680c67e1e..b20ec67f0 100644 --- a/eos/db/__init__.py +++ b/eos/db/__init__.py @@ -27,7 +27,9 @@ from eos import config from logbook import Logger pyfalog = Logger(__name__) - +pyfalog.info("Initializing database") +pyfalog.info("Gamedata connection: {0}", config.gamedata_connectionstring) +pyfalog.info("Saveddata connection: {0}", config.saveddata_connectionstring) class ReadOnlyException(Exception): pass @@ -85,8 +87,10 @@ from eos.db.saveddata.queries import * # If using in memory saveddata, you'll want to reflect it so the data structure is good. if config.saveddata_connectionstring == "sqlite:///:memory:": saveddata_meta.create_all() + pyfalog.info("Running database out of memory.") def rollback(): with sd_lock: + pyfalog.warning("Session rollback triggered.") saveddata_session.rollback()