class Skill(industry.Base): __metaclass__ = fsdlite.Immutable def __new__(cls, *args, **kwargs): obj = industry.Base.__new__(cls) obj._typeID = None obj._level = None obj._errors = [] obj.on_updated = signals.Signal() obj.on_errors = signals.Signal() return obj typeID = industry.Property('_typeID', 'on_updated') level = industry.Property('_level', 'on_updated') errors = industry.Property('_errors', 'on_errors') def __repr__(self): return industry.repr(self, exclude=['on_errors', '_errors'])
class Skill(industry.Base): """ Skills define training requirements and possibly cost or time modifiers for an activity. """ __metaclass__ = fsdlite.Immutable def __new__(cls, *args, **kwargs): obj = industry.Base.__new__(cls) obj._typeID = None obj._level = None obj._errors = [] obj.on_updated = fsdlite.Signal() obj.on_errors = fsdlite.Signal() return obj typeID = industry.Property('_typeID', 'on_updated') level = industry.Property('_level', 'on_updated') errors = industry.Property('_errors', 'on_errors') def __repr__(self): return industry.repr(self, exclude=['on_errors', '_errors'])
class VgsOfferPreview(Container): default_name = 'OfferPreview' default_align = uiconst.TOPLEFT default_state = uiconst.UI_PICKCHILDREN default_clipChildren = True charID = industry.Property('_charID', 'on_charid') typeID = industry.Property('_typeID', 'on_typeid') def ApplyAttributes(self, attributes): Container.ApplyAttributes(self, attributes) self.offer = attributes.offer self._charID = None self._typeID = None self.on_charid = fsdlite.Signal() self.on_typeid = fsdlite.Signal() self.on_charid.connect(self.OnPickCharacter) self.on_typeid.connect(self.OnPickType) self.charButtons = [] self._charButtonsDisplayed = False textLayer = Container(name='TextLayer', parent=self) descriptionBox = ContainerAutoSize(parent=textLayer, align=uiconst.TOBOTTOM, bgColor=OFFER_TEXT_BOX_COLOR, clipChildren=True) Label(parent=descriptionBox, align=uiconst.TOTOP, text=self.offer.description, fontsize=VGS_FONTSIZE_SMALL, padding=(INFO_PADDING_BIG, 0, INFO_PADDING_BIG, 10)) titleBox = ContainerAutoSize(name='InfoBox', parent=textLayer, align=uiconst.TOBOTTOM, bgColor=OFFER_TEXT_BOX_COLOR) Label(parent=titleBox, align=uiconst.TOTOP, text=self.offer.name, fontsize=VGS_FONTSIZE_LARGE, padding=(INFO_PADDING_BIG, 2, INFO_PADDING_BIG + 32, 2), fontStyle=fontConst.STYLE_HEADER, uppercase=True, lineSpacing=-0.15) collapseBox = ContainerAutoSize(parent=textLayer, align=uiconst.TOBOTTOM_NOPUSH, top=-36) CollapseButton(parent=collapseBox, align=uiconst.TOPRIGHT, target=descriptionBox) self.characterPickerLayer = ContainerAutoSize( parent=self, align=uiconst.TOPLEFT, state=uiconst.UI_PICKCHILDREN, top=50 if self.offer.label else 10, left=10) self.CreateCharacterButtons() self.loadingLayer = Container(parent=self, align=uiconst.TOALL, state=uiconst.UI_DISABLED) self.loadingWheel = LoadingWheel(parent=self.loadingLayer, align=uiconst.CENTER, state=uiconst.UI_DISABLED, opacity=0.0) self.cover = Sprite( parent=self.loadingLayer, align=uiconst.TOALL, texturePath='res:/UI/Texture/preview/asset_preview_background.png') self.imageLayer = Transform(name='imageLayer', parent=self, align=uiconst.TOALL, scalingCenter=(0.5, 0.5), bgColor=OFFER_BACKGROUND_COLOR) if self.offer.label is not None: Ribbon(parent=self.imageLayer, align=uiconst.TOPLEFT, label=self.offer.label, state=uiconst.UI_DISABLED, idx=0, isBig=True) self.previewContainer = PreviewContainer( parent=self.imageLayer, OnStartLoading=self.OnStartLoading, OnStopLoading=self.OnStopLoading) self.lazySprite = LazyUrlSprite(parent=self.imageLayer, align=uiconst.TOALL, imageUrl=self.offer.imageUrl, state=uiconst.UI_DISABLED) GradientSprite(name='OfferGradient', align=uiconst.TOALL, bgParent=self.imageLayer, rgbData=((1.0, OFFER_RADIAL_SHADOW), ), alphaData=((0.0, 0.0), (1.0, 1.0)), radial=True, idx=0) self.PickFirstPreviewableType() def CreateCharacterButtons(self): characters = sm.GetService('cc').GetCharactersToSelect() charIDList = itertools.chain([None], map(lambda c: c.characterID, characters)) for i, charID in enumerate(charIDList): button = CharacterButton( parent=self.characterPickerLayer, align=uiconst.RELATIVE, pos=(0, i * 43, 38, 38), charID=charID, onClick=lambda charID: setattr(self, 'charID', charID), isActive=charID == self.charID, opacity=0.0, state=uiconst.UI_DISABLED) self.on_charid.connect(button.OnCharID) self.on_typeid.connect(button.OnTypeID) self.charButtons.append(button) def ShowCharacterButtons(self): if self._charButtonsDisplayed: return for i, button in enumerate(self.charButtons): uicore.animations.MoveInFromLeft(button, amount=10, duration=0.2, timeOffset=i * 0.1) uicore.animations.FadeIn(button, duration=0.4, timeOffset=i * 0.1) button.state = uiconst.UI_NORMAL self._charButtonsDisplayed = True def HideCharacterButtons(self): if not self._charButtonsDisplayed: return for button in self.charButtons: uicore.animations.FadeOut(button, duration=0.2) button.state = uiconst.UI_DISABLED self._charButtonsDisplayed = False def OnPickCharacter(self, _): uthread.new(self.ShowPreview) def OnPickType(self, _): if IsPreviewable(self.typeID) and IsApparel( self.typeID) and not IsWearableBy(self.typeID, self.charID): self.charID = None return uthread.new(self.ShowPreview) def OnStartLoading(self, _): if not IsApparel(self.typeID): self.HideCharacterButtons() uicore.animations.FadeIn(self.loadingWheel, duration=0.3, timeOffset=0.2) uicore.animations.SpMaskIn(self.cover, duration=0.5, sleep=True) self.previewContainer.Show() def OnStopLoading(self, _): if IsApparel(self.typeID): self.ShowCharacterButtons() uicore.animations.FadeOut(self.loadingWheel, duration=0.3) uicore.animations.SpMaskOut(self.cover, duration=0.5) def PickFirstPreviewableType(self): for typeID, _ in self.offer.productQuantities.itervalues(): if IsPreviewable(GetPreviewType(typeID)): self.typeID = typeID break else: self.previewContainer.SetState(uiconst.UI_HIDDEN) uicore.animations.SpMaskOut(self.cover, duration=0.4) def ShowPreview(self): typeID = GetPreviewType(self.typeID) if not IsPreviewable(typeID): return if IsApparel(self.typeID) and self.charID: self.previewContainer.PreviewCharacter(self.charID, apparel=[self.typeID]) else: self.previewContainer.PreviewType( typeID, scenePath='res:/dx9/scene/fitting/fitting.red') if IsApparel(typeID): self.previewContainer.AnimEntry(-0.1, 0.0, 0.5, -0.2) else: self.previewContainer.AnimEntry(0.3, 0.2, 0.6, -0.3) def PrepareClose(self): uicore.animations.SpMaskIn(self.cover, duration=0.2, sleep=True) self.previewContainer.SetState(uiconst.UI_HIDDEN)
class Material(industry.Base): """ Materials are type inputs for blueprints needed to perform different activities. A material includes a type, quantity, damage information and whether it can be recycled as part of a process. """ __metaclass__ = fsdlite.Immutable typeID = industry.Property('_typeID', 'on_updated') quantity = industry.Property('_quantity', 'on_updated') base = industry.Property('_base') available = industry.Property('_available') errors = industry.Property('_errors') options = industry.Property('_options', 'on_updated') modifiers = industry.Property('_modifiers', 'on_updated') probability = industry.Property('_probability', 'on_updated') def __new__(cls, *args, **kwargs): obj = industry.Base.__new__(cls) obj._typeID = None obj._quantity = 0 obj._available = 0 obj._original = 0 obj._errors = [] obj._options = [] obj._modifiers = [] obj._probability = 1 obj.on_updated = fsdlite.Signal() obj.on_errors = fsdlite.Signal() obj.on_select = fsdlite.Signal() return obj def __init__(self, *args, **kwargs): industry.Base.__init__(self, *args, **kwargs) self.base = self.quantity def __setstate__(self, data): self.typeID = data.get('typeID') self.quantity = data.get('quantity') self.probability = data.get('probability') self.base = self.quantity def __repr__(self): return industry.repr(self, exclude=[ 'on_updated', 'on_errors', 'on_select', '_parent', '_errors' ]) def add_error(self, error, *args): self._errors.append((error, args)) def _get_valid(self): return not self.errors valid = property(_get_valid) def _get_missing(self): """ Helper that returns the quantity of materials missing based on errors. """ return max(self.quantity - self.available, 0) missing = property(_get_missing) def _get_ratio(self): """ Returns the percentage owned based on how much is missing. """ if self.quantity: return 1.0 - float(self.missing) / float(self.quantity) elif self.typeID: return 0.0 else: return 1.0 ratio = property(_get_ratio) def all_types(self): """ Returns a list of all typeIDs including the options. """ values = set([m.typeID for m in self.options]) values.add(self.typeID) values.discard(None) return list(values) def update_available(self, materials): """ Accepts a dictionary of available materials and updates this material instance. """ self._available = materials.get(self.typeID, 0) for material in self.options: material.update_available(materials) def _get_errors(self): """ Checks available quantities for this material, as well as updating the optionals. """ self._errors, existing = [], self._errors if self.typeID and self.missing: self.add_error(industry.Error.MISSING_MATERIAL, self.typeID, self.quantity, self.available, self.missing) if existing != self._errors: self.on_errors(self, self._errors) return self._errors errors = property(_get_errors) def select(self, value): """ Allows selecting one of many optional values, can pass the typeID or the actual material class from options. """ for material in self._options: if value in (material, material.typeID): self._typeID = material.typeID self._quantity = material.quantity self._available = material.available self._base = material.base self._modifiers = material.modifiers self.on_select(self) self.on_updated(self) return
class JobBase(industry.Base): def __new__(cls, blueprint, activityID, *args, **kwargs): obj = industry.Base.__new__(cls) obj._blueprint = blueprint obj._activityID = activityID if obj.activity is None: raise RuntimeError('Invalid Activity for Blueprint') obj.jobID = None obj.characterID = None obj.corporationID = None obj.status = industry.STATUS_UNSUBMITTED obj.installerID = None obj.startDate = None obj.endDate = None obj._materials = copy.deepcopy(obj.activity.materials) obj._modifiers = [] obj._skills = {} obj._slots = {} obj._distance = None obj._materialEfficiency = None obj._timeEfficiency = None obj._random = None obj.request = {} obj.prices = {} obj._modifiers_cache = None obj.on_validate = signals.Signal() obj.on_updated = signals.Signal() obj.on_errors = signals.Signal() obj.on_delete = signals.Signal() obj.on_facility = signals.Signal() obj.on_input_location = signals.Signal() obj.on_output_location = signals.Signal() obj.on_dirty = signals.Signal() obj.on_dirty.connect(obj.update) return obj def __repr__(self): return industry.repr(self, exclude=['_blueprint', '_facility', '_inputLocation', '_outputLocation', 'on_delete', 'on_validate', 'on_dirty', 'on_updated', 'on_errors', 'on_facility', 'on_input_location', 'on_output_location', '_materials', '_extras', '_locations', '_errors', '_request', 'prices', 'skills']) def update(self, job): self._modifiers_cache = None self.on_updated(self) modifiers = industry.Property('_modifiers', 'on_dirty') skills = industry.Property('_skills', 'on_dirty') slots = industry.Property('_slots', 'on_dirty') completed = property(lambda self: bool(self.status >= industry.STATUS_COMPLETED)) def _get_random(self): if self._random is None: self._random = random.random() return self._random random = property(_get_random) def _get_blueprint(self): return self._blueprint blueprint = property(_get_blueprint) def _get_activityID(self): return self._activityID activityID = property(_get_activityID) def _get_activity(self): if self.activityID in self.blueprint._activities: return self.blueprint._activities[self.activityID] activity = property(_get_activity) def _get_runs_max(self): return self.activity.job_max_runs(self) maxRuns = property(_get_runs_max) def _get_licensed_runs_max(self): return self.blueprint.maxProductionLimit maxLicensedRuns = property(_get_licensed_runs_max) def _get_successful_runs(self): if self.completed: if self._successfulRuns is not None: return self._successfulRuns else: rand = random.Random() rand.seed(self.random) probabilities = [ rand.random() for i in range(self.runs) ] return len([ p for p in probabilities if p <= self.probability ]) else: return self.runs successfulRuns = industry.Property('_successfulRuns', 'on_dirty', _get_successful_runs) def _get_base_cost(self): modifiers = [ modifier.amount for modifier in self.input_modifiers if isinstance(modifier, industry.CostModifier) and modifier.activity in (None, self.activityID) ] return int(round(self.cost / reduce(operator.mul, modifiers, 1.0))) base_cost = property(_get_base_cost) def _get_total_cost(self): return (self.cost or 0) + (self.tax or 0) total_cost = property(_get_total_cost) def _get_output(self): output = [] if self.product: output.append(self.product) output += self.activity.job_output_extras(self) return output output = property(_get_output) def _get_products(self): return self.activity.job_output_products(self) products = property(_get_products) def _get_product(self): products = self.products if products: if len(products) == 1: return products[0] for product in products: if product.typeID == self.productTypeID: return product def _set_product(self, value): products = self.products if not len(products): raise KeyError('Product is not selectable') if value is None: self.productTypeID = None return for product in products: if value in (product, product.typeID): self.productTypeID = product.typeID return raise KeyError('Invalid product option') product = property(_get_product, _set_product) def _get_materials(self): return self._materials + self.extras materials = property(_get_materials) def _get_optional_materials(self): return [ material for material in self.materials if len(material.options) ] optional_materials = property(_get_optional_materials) def _get_required_skills(self): return self.activity.skills required_skills = property(_get_required_skills) def _get_all_skills(self): return self.required_skills + [ industry.Skill(typeID=skill) for skill in industry.SKILLS ] all_skills = property(_get_all_skills) def _get_all_modifiers(self): if self._modifiers_cache is None: modifiers = [] modifiers += self.modifiers modifiers += self.activity.job_modifiers(self) if self.facility: modifiers += self.facility.modifiers for material in JobBase._get_materials(self): modifiers += material.modifiers self._modifiers_cache = [] for modifier in modifiers: if modifier.activity not in (None, self.activityID): continue if modifier.blueprints and self.blueprint.blueprintTypeID not in modifier.blueprints: continue self._modifiers_cache.append(modifier) return self._modifiers_cache all_modifiers = property(_get_all_modifiers) def _get_max_slots(self): count = 0 for modifier in self.all_modifiers: if isinstance(modifier, industry.SlotModifier) and modifier.activity in (None, self.activityID): count += int(modifier.amount) return count max_slots = property(_get_max_slots) def _get_max_distance(self): return industry.DISTANCE_PER_LEVEL.get(self.skills.get(industry.DISTANCE_SKILL_FOR_ACTIVITY.get(self.activityID), 0)) max_distance = property(_get_max_distance) def _get_distance(self): if self._distance is not None: return self._distance if self.facility and self.facility.distance is not None: return self.facility.distance distance = industry.Property('_distance', 'on_dirty', _get_distance) def _get_material_efficiency(self): if self._materialEfficiency is not None: return self._materialEfficiency else: return self.blueprint.materialEfficiency materialEfficiency = industry.Property('_materialEfficiency', 'on_dirty', _get_material_efficiency) def _get_time_efficiency(self): if self._timeEfficiency is not None: return self._timeEfficiency else: return self.blueprint.timeEfficiency timeEfficiency = industry.Property('_timeEfficiency', 'on_dirty', _get_time_efficiency) def _get_used_slots(self): return self.slots.get(self.activityID, 0) used_slots = property(_get_used_slots) def _get_input_modifiers(self): return [ modifier for modifier in self.all_modifiers if modifier.output is False ] input_modifiers = property(_get_input_modifiers) def _get_output_modifiers(self): return [ modifier for modifier in self.all_modifiers if modifier.output is True ] output_modifiers = property(_get_output_modifiers)
class Job(JobBase): facility = industry.Property('_facility', 'on_facility') account = industry.Property('_account', 'on_dirty') accounts = industry.Property('_accounts', 'on_dirty') roles = industry.Property('_roles', 'on_dirty') extras = industry.Property('_extras', 'on_dirty') available = industry.Property('_available', 'on_dirty') locations = industry.Property('_locations', 'on_dirty') inputLocation = industry.Property('_inputLocation', 'on_input_location') outputLocation = industry.Property('_outputLocation', 'on_output_location') def __new__(cls, blueprint, activity, *args, **kwargs): obj = JobBase.__new__(cls, blueprint, activity, *args, **kwargs) obj._ownerID = None obj._facility = None obj._inputLocation = None obj._outputLocation = None obj._runs = 1 obj._licensedRuns = 1 obj._productTypeID = None obj._cost = None obj._tax = None obj._time = None obj._account = None obj._accounts = {} obj._roles = 0 obj._extras = [] obj._available = {} obj._locations = [] obj._errors = [] obj.on_facility.connect(obj.on_dirty) obj.on_input_location.connect(obj.on_dirty) obj.on_output_location.connect(obj.on_dirty) return obj def __del__(self): if hasattr(self, 'on_delete'): self.on_delete(self) def update(self, job): self._modifiers_cache = None self.materials self.errors self.on_updated(self) def _get_runs(self): return max(min(self._runs or 1, self.maxRuns), 1) runs = industry.Property('_runs', 'on_dirty', _get_runs) def _get_licensed_runs(self): return max(min(self._licensedRuns or 1, self.maxLicensedRuns), 1) licensedRuns = industry.Property('_licensedRuns', 'on_dirty', _get_licensed_runs) def _get_productTypeID(self): return self._productTypeID productTypeID = industry.Property('_productTypeID', 'on_dirty', _get_productTypeID) def _get_probability(self): return self.activity.job_probability(self) probability = property(_get_probability) def _get_ownerID(self): return self.blueprint.ownerID ownerID = property(_get_ownerID) def _get_facilityID(self): if self.facility: return self.facility.facilityID facilityID = property(_get_facilityID) def _get_blueprintID(self): if self.blueprint: return self.blueprint.blueprintID blueprintID = property(_get_blueprintID) def _get_blueprint_location(self): blueprintLocation = self.blueprint.location for location in self.locations: if location == blueprintLocation: return location blueprint_location = property(_get_blueprint_location) def _get_cost(self): cost = self.activity.job_cost(self) or 0 for modifier in self.input_modifiers: if isinstance(modifier, industry.CostModifier) and modifier.activity in (None, self.activityID): cost *= modifier.amount return int(round(cost)) cost = property(_get_cost) def _get_tax(self): if self.facility and self.facility.tax: return int(round(self.cost * self.facility.tax)) else: return 0 tax = property(_get_tax) def _get_time(self): seconds = self.activity.job_time(self) for modifier in self.input_modifiers: if isinstance(modifier, industry.TimeModifier) and modifier.activity in (None, self.activityID): seconds *= modifier.amount return datetime.timedelta(seconds=long(round(seconds))) time = property(_get_time) def _get_materials(self): materials = JobBase._get_materials(self) for material in materials: quantity = float(material.base) for modifier in self.input_modifiers: if isinstance(modifier, industry.MaterialModifier) and modifier.activity in (None, self.activityID): quantity *= modifier.amount quantity *= self.activity.job_material_runs(self) material.quantity = max(int(math.ceil(round(quantity, industry.MATERIAL_ROUND_PRECISION))), self.runs) material.on_select.connect(self._material_selected) material.update_available(self.available) return materials materials = property(_get_materials) def _material_selected(self, material): self.on_dirty(self) def add_error(self, error, *args): self._errors.append((error, args)) def _get_errors(self): self._errors, existing = [], self._errors if self.activityID not in industry.ACTIVITY_CLASSES: self.add_error(industry.Error.INVALID_ACTIVITY) if self.blueprint.blueprintID is None: self.add_error(industry.Error.MISSING_BLUEPRINT) if self.facility is None: self.add_error(industry.Error.MISSING_FACILITY) elif not isinstance(self.facility, industry.Facility): self.add_error(industry.Error.INVALID_FACILITY) else: if not self.facility.online: self.add_error(industry.Error.FACILITY_OFFLINE) if self.facility.tax is None: self.add_error(industry.Error.FACILITY_DENIED) if self.distance is None or self.distance > self.max_distance: self.add_error(industry.Error.FACILITY_DISTANCE, self.distance, self.max_distance) if self.activityID not in self.facility.activities: self.add_error(industry.Error.FACILITY_ACTIVITY, self.activityID) elif self.facility.activities[self.activityID]['blueprints']: if self.blueprint.blueprintTypeID not in self.facility.activities[self.activityID]['blueprints']: self.add_error(industry.Error.FACILITY_TYPE, self.blueprint.blueprintTypeID) if self.inputLocation is None: self.add_error(industry.Error.MISSING_INPUT_LOCATION) if self.outputLocation is None: self.add_error(industry.Error.MISSING_OUTPUT_LOCATION) if self.ownerID not in (self.characterID, self.corporationID): self.add_error(industry.Error.INVALID_OWNER) if self.ownerID and self.ownerID == self.corporationID: if not self.roles & industry.ROLE_FACTORY_MANAGER: self.add_error(industry.Error.MISSING_ROLE, industry.ROLE_FACTORY_MANAGER) if self.activityID == industry.MANUFACTURING: if not self.roles & industry.ROLE_FACTORY_MANUFACTURING: self.add_error(industry.Error.MISSING_ROLE, industry.ROLE_FACTORY_MANUFACTURING) elif not self.roles & industry.ROLE_FACTORY_SCIENCE: self.add_error(industry.Error.MISSING_ROLE, industry.ROLE_FACTORY_SCIENCE) if self.blueprint.jobID is not None: self.add_error(industry.Error.BLUEPRINT_INSTALLED) if self.facility and self.blueprint.facilityID != self.facility.facilityID: self.add_error(industry.Error.BLUEPRINT_WRONG_FACILITY) if self.activityID not in self.blueprint.activities: self.add_error(industry.Error.INCOMPATIBLE_ACTIVITY) if self.timeEfficiency != self.blueprint.timeEfficiency: self.add_error(industry.Error.INVALID_TIME_EFFICIENCY) if self.materialEfficiency != self.blueprint.materialEfficiency: self.add_error(industry.Error.INVALID_MATERIAL_EFFICIENCY) if self.activityID == industry.MANUFACTURING and self.runs <= 0 or self.runs > self.blueprint.runsRemaining and self.blueprint.runsRemaining != -1: self.add_error(industry.Error.INVALID_RUNS, self.blueprint.runsRemaining) elif self.activityID == industry.INVENTION and self.blueprint.runsRemaining <= 0: self.add_error(industry.Error.INVALID_RUNS, self.blueprint.runsRemaining) if self.activityID == industry.COPYING and (not self.licensedRuns or self.licensedRuns <= 0 or self.licensedRuns > self.blueprint.maxProductionLimit): self.add_error(industry.Error.INVALID_LICENSED_RUNS, self.licensedRuns) if not self.product: self.add_error(industry.Error.INVALID_PRODUCT) if self.account is None or self.accounts is None: self.add_error(industry.Error.ACCOUNT_INVALID) elif self.account not in self.accounts: self.add_error(industry.Error.ACCOUNT_ACCESS, self.account, self.accounts.keys()) elif (self.accounts[self.account] or 0) < self.total_cost: self.add_error(industry.Error.ACCOUNT_FUNDS, self.accounts[self.account], self.total_cost) if not self.blueprint_location: self.add_error(industry.Error.INVALID_BLUEPRINT_LOCATION, self.blueprint.location) elif not self.blueprint_location.canView: self.add_error(industry.Error.BLUEPRINT_ACCESS, self.blueprint.location) if len(JobBase._get_materials(self)): materialErrors = sum([ material.errors for material in JobBase._get_materials(self) ], []) if self.inputLocation not in self.locations: self.add_error(industry.Error.INVALID_INPUT_LOCATION, self.inputLocation) elif not self.inputLocation.canTake: self.add_error(industry.Error.INPUT_ACCESS, self.inputLocation) else: self._errors += materialErrors if self.outputLocation not in self.locations: self.add_error(industry.Error.INVALID_OUTPUT_LOCATION, self.outputLocation) for skill in self.required_skills: actual = self.skills.get(skill.typeID, 0) if actual < skill.level: self.add_error(industry.Error.MISSING_SKILL, skill.typeID, skill.level, actual) if self.used_slots >= self.max_slots: self.add_error(industry.Error.SLOTS_FULL, self.max_slots, self.used_slots) time = self.time.total_seconds() time_previous = time - time / float(self.runs) if time_previous > industry.MAX_RUN_LENGTH: self.add_error(industry.Error.RUN_LENGTH, time_previous, industry.MAX_RUN_LENGTH) if self.request: if self.request['cost'] and self.request['cost'] != self.cost: self.add_error(industry.Error.MISMATCH_COST, self.request['cost'], self.cost) if self.request['tax'] and self.request['tax'] != self.tax: self.add_error(industry.Error.MISMATCH_TAX, self.request['tax'], self.tax) if self.request['time'] and self.request['time'] != self.time.total_seconds(): self.add_error(industry.Error.MISMATCH_TIME, self.request['time'], self.time.total_seconds()) materials = {material.typeID:material.quantity for material in JobBase._get_materials(self)} if self.request['materials'] and self.request['materials'] != materials: self.add_error(industry.Error.MISMATCH_MATERIAL, self.request['materials'], materials) self.activity.job_validate(self) self.on_validate(self) if existing != self._errors: self.on_errors(self, self._errors) return self._errors errors = property(_get_errors) def _get_error_codes(self): return set([ error for error, args in self.errors ]) error_codes = property(_get_error_codes) def validate(self): errors = self.errors if len(errors): raise industry.ValidationError(errors) def dump(self): return {'blueprintID': self.blueprint.blueprintID, 'blueprintTypeID': self.blueprint.blueprintTypeID, 'activityID': self.activityID, 'facilityID': self.facility.facilityID if self.facility else None, 'solarSystemID': self.facility.solarSystemID if self.facility else None, 'characterID': self.characterID, 'corporationID': self.corporationID, 'account': self.account, 'runs': self.runs, 'cost': self.cost, 'tax': self.tax, 'time': self.time.total_seconds(), 'materials': {material.typeID:material.quantity for material in self.materials}, 'inputLocation': self.inputLocation, 'outputLocation': self.outputLocation, 'licensedRuns': self.licensedRuns, 'productTypeID': self.productTypeID} @classmethod def create(cls, request, blueprint): required = {'blueprintID', 'blueprintTypeID', 'activityID', 'facilityID', 'characterID', 'corporationID', 'account', 'runs', 'inputLocation', 'outputLocation', 'licensedRuns', 'productTypeID'} if len(required.difference(set(request))): raise RuntimeError('Invalid Request: ', required.difference(set(request))) job = Job(blueprint, request['activityID']) job.characterID = request['characterID'] job.corporationID = request['corporationID'] job.account = request['account'] job.runs = request['runs'] job.inputLocation = request['inputLocation'] job.outputLocation = request['outputLocation'] job.licensedRuns = request['licensedRuns'] job.productTypeID = request['productTypeID'] job.request = request return job
class Material(industry.Base): __metaclass__ = fsdlite.Immutable typeID = industry.Property('_typeID', 'on_updated') quantity = industry.Property('_quantity', 'on_updated') base = industry.Property('_base') available = industry.Property('_available') errors = industry.Property('_errors') options = industry.Property('_options', 'on_updated') modifiers = industry.Property('_modifiers', 'on_updated') probability = industry.Property('_probability', 'on_updated') def __new__(cls, *args, **kwargs): obj = industry.Base.__new__(cls) obj._typeID = None obj._quantity = 0 obj._available = 0 obj._original = 0 obj._errors = [] obj._options = [] obj._modifiers = [] obj._probability = 1 obj.on_updated = signals.Signal() obj.on_errors = signals.Signal() obj.on_select = signals.Signal() return obj def __init__(self, *args, **kwargs): industry.Base.__init__(self, *args, **kwargs) self.base = self.quantity def __setstate__(self, data): self.typeID = data.get('typeID') self.quantity = data.get('quantity') self.probability = data.get('probability') self.base = self.quantity def __repr__(self): return industry.repr(self, exclude=[ 'on_updated', 'on_errors', 'on_select', '_parent', '_errors' ]) def add_error(self, error, *args): self._errors.append((error, args)) def _get_valid(self): return not self.errors valid = property(_get_valid) def _get_missing(self): return max(self.quantity - self.available, 0) missing = property(_get_missing) def _get_ratio(self): if self.quantity: return 1.0 - float(self.missing) / float(self.quantity) elif self.typeID: return 0.0 else: return 1.0 ratio = property(_get_ratio) def all_types(self): values = set([m.typeID for m in self.options]) values.add(self.typeID) values.discard(None) return list(values) def update_available(self, materials): self._available = materials.get(self.typeID, 0) for material in self.options: material.update_available(materials) def _get_errors(self): self._errors, existing = [], self._errors if self.typeID and self.missing: self.add_error(industry.Error.MISSING_MATERIAL, self.typeID, self.quantity, self.available, self.missing) if existing != self._errors: self.on_errors(self, self._errors) return self._errors errors = property(_get_errors) def select(self, value): for material in self._options: if value in (material, material.typeID): self._typeID = material.typeID self._quantity = material.quantity self._available = material.available self._base = material.base self._modifiers = material.modifiers self.on_select(self) self.on_updated(self) return
class JobBase(industry.Base): """ Defines the common data properties for a Job and JobData class. """ def __new__(cls, blueprint, activityID, *args, **kwargs): obj = industry.Base.__new__(cls) obj._blueprint = blueprint obj._activityID = activityID if obj.activity is None: raise RuntimeError('Invalid Activity for Blueprint') obj.jobID = None obj.characterID = None obj.corporationID = None obj.status = industry.STATUS_UNSUBMITTED obj.installerID = None obj.startDate = None obj.endDate = None obj._materials = copy.deepcopy(obj.activity.materials) obj._modifiers = [] obj._skills = {} obj._slots = {} obj._distance = None obj._materialEfficiency = None obj._timeEfficiency = None obj._random = None obj.request = {} obj.prices = {} obj._modifiers_cache = None obj.on_validate = fsdlite.Signal() obj.on_updated = fsdlite.Signal() obj.on_errors = fsdlite.Signal() obj.on_delete = fsdlite.Signal() obj.on_facility = fsdlite.Signal() obj.on_input_location = fsdlite.Signal() obj.on_output_location = fsdlite.Signal() obj.on_dirty = fsdlite.Signal() obj.on_dirty.connect(obj.update) return obj def __repr__(self): return industry.repr(self, exclude=['_blueprint', '_facility', '_team', '_inputLocation', '_outputLocation', 'on_delete', 'on_validate', 'on_dirty', 'on_updated', 'on_errors', 'on_facility', 'on_input_location', 'on_output_location', '_materials', '_extras', '_locations', '_errors', '_request', 'prices', 'skills']) def update(self, job): self._modifiers_cache = None self.on_updated(self) modifiers = industry.Property('_modifiers', 'on_dirty') skills = industry.Property('_skills', 'on_dirty') slots = industry.Property('_slots', 'on_dirty') completed = property(lambda self: bool(self.status >= industry.STATUS_COMPLETED)) def _get_random(self): """ A cached random roll for things like job success or product selection. """ if self._random is None: self._random = random.random() return self._random random = property(_get_random) def _get_blueprint(self): return self._blueprint blueprint = property(_get_blueprint) def _get_activityID(self): return self._activityID activityID = property(_get_activityID) def _get_activity(self): if self.activityID in self.blueprint._activities: return self.blueprint._activities[self.activityID] activity = property(_get_activity) def _get_runs_max(self): return self.activity.job_max_runs(self) maxRuns = property(_get_runs_max) def _get_licensed_runs_max(self): return self.blueprint.maxProductionLimit maxLicensedRuns = property(_get_licensed_runs_max) def _get_successful_runs(self): if self.completed: if self._successfulRuns is not None: return self._successfulRuns else: rand = random.Random() rand.seed(self.random) probabilities = [ rand.random() for i in range(self.runs) ] return len([ p for p in probabilities if p <= self.probability ]) else: return self.runs successfulRuns = industry.Property('_successfulRuns', 'on_dirty', _get_successful_runs) def _get_base_cost(self): """ Return the cost excluding any modifiers. """ modifiers = [ modifier.amount for modifier in self.input_modifiers if isinstance(modifier, industry.CostModifier) and modifier.activity in (None, self.activityID) ] return int(round(self.cost / reduce(operator.mul, modifiers, 1.0))) base_cost = property(_get_base_cost) def _get_total_cost(self): """ Returns the amount of tax required to pay to the facility owner. """ return (self.cost or 0) + (self.tax or 0) total_cost = property(_get_total_cost) def _get_output(self): """ Returns a list of the blueprints / materials that need constructing or modifying, including the final product provided it has been selected. """ output = [] if self.product: output.append(self.product) output += self.activity.job_output_extras(self) return output output = property(_get_output) def _get_products(self): """ It is possible to have multiple possible outcomes for a job, this returns all the possibilities. """ return self.activity.job_output_products(self) products = property(_get_products) def _get_product(self): """ Returns the actual product to produce, in the case of multiple items it will pick the one selected if selectable, or a random one. """ products = self.products if products: if len(products) == 1: return products[0] for product in products: if product.typeID == self.productTypeID: return product def _set_product(self, value): """ Sets the desired output product, if multiple are available. """ products = self.products if not len(products): raise KeyError('Product is not selectable') if value is None: self.productTypeID = None return for product in products: if value in (product, product.typeID): self.productTypeID = product.typeID return raise KeyError('Invalid product option') product = property(_get_product, _set_product) def _get_materials(self): """ Returns the materials required, not modified by runs or any other modifiers. """ return self._materials + self.extras materials = property(_get_materials) def _get_optional_materials(self): """ Returns the materials with optional selection. """ return [ material for material in self.materials if len(material.options) ] optional_materials = property(_get_optional_materials) def _get_required_skills(self): """ Returns the skills required for this jobs activity. """ return self.activity.skills required_skills = property(_get_required_skills) def _get_all_skills(self): """ Returns the list of skills that can affect this job. """ return self.required_skills + [ industry.Skill(typeID=skill) for skill in industry.SKILLS ] all_skills = property(_get_all_skills) def _get_all_modifiers(self): """ Modifiers can be applied to the job, the facility or the materials themselves. """ if self._modifiers_cache is None: modifiers = [] modifiers += self.modifiers modifiers += self.activity.job_modifiers(self) if self.facility: modifiers += self.facility.modifiers if self.team: modifiers += self.team.modifiers for material in JobBase._get_materials(self): modifiers += material.modifiers self._modifiers_cache = [] for modifier in modifiers: if modifier.activity not in (None, self.activityID): continue if modifier.blueprints and self.blueprint.blueprintTypeID not in modifier.blueprints: continue self._modifiers_cache.append(modifier) return self._modifiers_cache all_modifiers = property(_get_all_modifiers) def _get_max_slots(self): """ Return the maximum number of slots for this jobs activity. """ count = 0 for modifier in self.all_modifiers: if isinstance(modifier, industry.SlotModifier) and modifier.activity in (None, self.activityID): count += int(modifier.amount) return count max_slots = property(_get_max_slots) def _get_max_distance(self): """ Return the maximum distance we can operate facilities from. """ return industry.DISTANCE_PER_LEVEL.get(self.skills.get(industry.DISTANCE_SKILL_FOR_ACTIVITY.get(self.activityID), 0)) max_distance = property(_get_max_distance) def _get_distance(self): """ Returns the distance from the installer to the facility, as either defined on the facility or overridden on the job itself. """ if self._distance is not None: return self._distance if self.facility and self.facility.distance is not None: return self.facility.distance distance = industry.Property('_distance', 'on_dirty', _get_distance) def _get_material_efficiency(self): """ Returns either the overriden material efficiency of this job, or defaults to the blueprints ME """ if self._materialEfficiency is not None: return self._materialEfficiency else: return self.blueprint.materialEfficiency materialEfficiency = industry.Property('_materialEfficiency', 'on_dirty', _get_material_efficiency) def _get_time_efficiency(self): """ Returns either the overriden time efficiency of this job, or defaults to the blueprints TE """ if self._timeEfficiency is not None: return self._timeEfficiency else: return self.blueprint.timeEfficiency timeEfficiency = industry.Property('_timeEfficiency', 'on_dirty', _get_time_efficiency) def _get_used_slots(self): """ Returns the number of slots in use for the current activity. """ return self.slots.get(self.activityID, 0) used_slots = property(_get_used_slots) def _get_input_modifiers(self): """ Filters all the modifiers by whether they affect input. """ return [ modifier for modifier in self.all_modifiers if modifier.output is False ] input_modifiers = property(_get_input_modifiers) def _get_output_modifiers(self): """ Filters all the modifiers by whether they affect the output product. """ return [ modifier for modifier in self.all_modifiers if modifier.output is True ] output_modifiers = property(_get_output_modifiers)
class Job(JobBase): """ A job describes performing an Activity on a Blueprint at a Facility. We subclass the basic JobData class and then replace some of the static values with computed properties for setting up new jobs. """ facility = industry.Property('_facility', 'on_facility') team = industry.Property('_team', 'on_dirty') account = industry.Property('_account', 'on_dirty') accounts = industry.Property('_accounts', 'on_dirty') roles = industry.Property('_roles', 'on_dirty') extras = industry.Property('_extras', 'on_dirty') available = industry.Property('_available', 'on_dirty') locations = industry.Property('_locations', 'on_dirty') inputLocation = industry.Property('_inputLocation', 'on_input_location') outputLocation = industry.Property('_outputLocation', 'on_output_location') def __new__(cls, blueprint, activity, *args, **kwargs): obj = JobBase.__new__(cls, blueprint, activity, *args, **kwargs) obj._ownerID = None obj._facility = None obj._team = None obj._inputLocation = None obj._outputLocation = None obj._runs = 1 obj._licensedRuns = 1 obj._productTypeID = None obj._cost = None obj._tax = None obj._time = None obj._account = None obj._accounts = {} obj._roles = 0 obj._extras = [] obj._available = {} obj._locations = [] obj._errors = [] obj.on_facility.connect(obj.on_dirty) obj.on_input_location.connect(obj.on_dirty) obj.on_output_location.connect(obj.on_dirty) return obj def __del__(self): """ Emit a signal when we are garbage collected, be very careful with this. """ if hasattr(self, 'on_delete'): self.on_delete(self) def update(self, job): """ Forces a reload of materials and errors for this job. We do this to make sure signals are emitted whenever dependent attributes change. """ self._modifiers_cache = None self.materials self.errors self.on_updated(self) def _get_runs(self): return max(min(self._runs or 1, self.maxRuns), 1) runs = industry.Property('_runs', 'on_dirty', _get_runs) def _get_licensed_runs(self): return max(min(self._licensedRuns or 1, self.maxLicensedRuns), 1) licensedRuns = industry.Property('_licensedRuns', 'on_dirty', _get_licensed_runs) def _get_productTypeID(self): return self._productTypeID productTypeID = industry.Property('_productTypeID', 'on_dirty', _get_productTypeID) def _get_probability(self): return self.activity.job_probability(self) probability = property(_get_probability) def _get_ownerID(self): return self.blueprint.ownerID ownerID = property(_get_ownerID) def _get_facilityID(self): if self.facility: return self.facility.facilityID facilityID = property(_get_facilityID) def _get_blueprintID(self): if self.blueprint: return self.blueprint.blueprintID blueprintID = property(_get_blueprintID) def _get_blueprint_location(self): blueprintLocation = self.blueprint.location for location in self.locations: if location == blueprintLocation: return location blueprint_location = property(_get_blueprint_location) def _get_teamID(self): if self.team: return self.team.teamID teamID = property(_get_teamID) def _get_cost(self): """ Recalculates the ISK cost for this job. """ cost = self.activity.job_cost(self) or 0 for modifier in self.input_modifiers: if isinstance(modifier, industry.CostModifier) and modifier.activity in (None, self.activityID): cost *= modifier.amount return int(round(cost)) cost = property(_get_cost) def _get_tax(self): """ Returns the amount of tax required to pay to the facility owner. """ if self.facility and self.facility.tax: return int(round(self.cost * self.facility.tax)) else: return 0 tax = property(_get_tax) def _get_time(self): """ Returns the estimated production time for this job. """ seconds = self.activity.job_time(self) for modifier in self.input_modifiers: if isinstance(modifier, industry.TimeModifier) and modifier.activity in (None, self.activityID): seconds *= modifier.amount return datetime.timedelta(seconds=long(round(seconds))) time = property(_get_time) def _get_materials(self): """ Recalculates and returns the required materials for this job. This will automatically emit signals on the existing material objects if we are just changing modifiers or runs for example. """ materials = JobBase._get_materials(self) for material in materials: quantity = float(material.base) for modifier in self.input_modifiers: if isinstance(modifier, industry.MaterialModifier) and modifier.activity in (None, self.activityID): quantity *= modifier.amount quantity *= self.activity.job_material_runs(self) material.quantity = max(int(math.ceil(round(quantity, industry.MATERIAL_ROUND_PRECISION))), self.runs) material.on_select.connect(self._material_selected) material.update_available(self.available) return materials materials = property(_get_materials) def _material_selected(self, material): """ Callback for whenever an optional material is selected. """ self.on_dirty(self) def add_error(self, error, *args): """ Pushes a new validation error onto this job for displaying. Used by validation callbacks. Errors should look like: (industry.Error.MY_ERROR, (arg1, arg2)) """ self._errors.append((error, args)) def _get_errors(self): """ Run job validation logic and return all the errors. """ self._errors, existing = [], self._errors if self.activityID not in industry.ACTIVITY_CLASSES: self.add_error(industry.Error.INVALID_ACTIVITY) if self.blueprint.blueprintID is None: self.add_error(industry.Error.MISSING_BLUEPRINT) if self.facility is None: self.add_error(industry.Error.MISSING_FACILITY) elif not isinstance(self.facility, industry.Facility): self.add_error(industry.Error.INVALID_FACILITY) else: if not self.facility.online: self.add_error(industry.Error.FACILITY_OFFLINE) if self.facility.tax is None: self.add_error(industry.Error.FACILITY_DENIED) if self.distance is None or self.distance > self.max_distance: self.add_error(industry.Error.FACILITY_DISTANCE, self.distance, self.max_distance) if self.activityID not in self.facility.activities: self.add_error(industry.Error.FACILITY_ACTIVITY, self.activityID) elif self.facility.activities[self.activityID]['blueprints']: if self.blueprint.blueprintTypeID not in self.facility.activities[self.activityID]['blueprints']: self.add_error(industry.Error.FACILITY_TYPE, self.blueprint.blueprintTypeID) if self.inputLocation is None: self.add_error(industry.Error.MISSING_INPUT_LOCATION) if self.outputLocation is None: self.add_error(industry.Error.MISSING_OUTPUT_LOCATION) if self.ownerID not in (self.characterID, self.corporationID): self.add_error(industry.Error.INVALID_OWNER) if self.ownerID and self.ownerID == self.corporationID: if not self.roles & industry.ROLE_FACTORY_MANAGER: self.add_error(industry.Error.MISSING_ROLE, industry.ROLE_FACTORY_MANAGER) if self.activityID == industry.MANUFACTURING: if not self.roles & industry.ROLE_FACTORY_MANUFACTURING: self.add_error(industry.Error.MISSING_ROLE, industry.ROLE_FACTORY_MANUFACTURING) elif not self.roles & industry.ROLE_FACTORY_SCIENCE: self.add_error(industry.Error.MISSING_ROLE, industry.ROLE_FACTORY_SCIENCE) if self.blueprint.jobID is not None: self.add_error(industry.Error.BLUEPRINT_INSTALLED) if self.facility and self.blueprint.facilityID != self.facility.facilityID: self.add_error(industry.Error.BLUEPRINT_WRONG_FACILITY) if self.activityID not in self.blueprint.activities: self.add_error(industry.Error.INCOMPATIBLE_ACTIVITY) if self.timeEfficiency != self.blueprint.timeEfficiency: self.add_error(industry.Error.INVALID_TIME_EFFICIENCY) if self.materialEfficiency != self.blueprint.materialEfficiency: self.add_error(industry.Error.INVALID_MATERIAL_EFFICIENCY) if self.team: if not isinstance(self.team, industry.Team) or not self.team.teamID: self.add_error(industry.Error.INVALID_TEAM) else: if self.team.activityID != self.activityID: self.add_error(industry.Error.TEAM_ACTIVITY) if self.team.solarSystemID is None or self.facility and self.team.solarSystemID != self.facility.solarSystemID: self.add_error(industry.Error.TEAM_SOLARSYSTEM) if self.team.isInAuction: self.add_error(industry.Error.TEAM_INAUCTION) if self.activityID == industry.MANUFACTURING and self.runs <= 0 or self.runs > self.blueprint.runsRemaining and self.blueprint.runsRemaining != -1: self.add_error(industry.Error.INVALID_RUNS, self.blueprint.runsRemaining) elif self.activityID == industry.INVENTION and self.blueprint.runsRemaining <= 0: self.add_error(industry.Error.INVALID_RUNS, self.blueprint.runsRemaining) if self.activityID == industry.COPYING and (not self.licensedRuns or self.licensedRuns <= 0 or self.licensedRuns > self.blueprint.maxProductionLimit): self.add_error(industry.Error.INVALID_LICENSED_RUNS, self.licensedRuns) if not self.product: self.add_error(industry.Error.INVALID_PRODUCT) if self.account is None or self.accounts is None: self.add_error(industry.Error.ACCOUNT_INVALID) elif self.account not in self.accounts: self.add_error(industry.Error.ACCOUNT_ACCESS, self.account, self.accounts.keys()) elif (self.accounts[self.account] or 0) < self.total_cost: self.add_error(industry.Error.ACCOUNT_FUNDS, self.accounts[self.account], self.total_cost) if not self.blueprint_location: self.add_error(industry.Error.INVALID_BLUEPRINT_LOCATION, self.blueprint.location) elif not self.blueprint_location.canView: self.add_error(industry.Error.BLUEPRINT_ACCESS, self.blueprint.location) if len(JobBase._get_materials(self)): materialErrors = sum([ material.errors for material in JobBase._get_materials(self) ], []) if self.inputLocation not in self.locations: self.add_error(industry.Error.INVALID_INPUT_LOCATION, self.inputLocation) elif not self.inputLocation.canTake: self.add_error(industry.Error.INPUT_ACCESS, self.inputLocation) else: self._errors += materialErrors if self.outputLocation not in self.locations: self.add_error(industry.Error.INVALID_OUTPUT_LOCATION, self.outputLocation) for skill in self.required_skills: actual = self.skills.get(skill.typeID, 0) if actual < skill.level: self.add_error(industry.Error.MISSING_SKILL, skill.typeID, skill.level, actual) if self.used_slots >= self.max_slots: self.add_error(industry.Error.SLOTS_FULL, self.max_slots, self.used_slots) time = self.time.total_seconds() time_previous = time - time / float(self.runs) if time_previous > industry.MAX_RUN_LENGTH: self.add_error(industry.Error.RUN_LENGTH, time_previous, industry.MAX_RUN_LENGTH) if self.request: if self.request['cost'] and self.request['cost'] != self.cost: self.add_error(industry.Error.MISMATCH_COST, self.request['cost'], self.cost) if self.request['tax'] and self.request['tax'] != self.tax: self.add_error(industry.Error.MISMATCH_TAX, self.request['tax'], self.tax) if self.request['time'] and self.request['time'] != self.time.total_seconds(): self.add_error(industry.Error.MISMATCH_TIME, self.request['time'], self.time.total_seconds()) materials = {material.typeID:material.quantity for material in JobBase._get_materials(self)} if self.request['materials'] and self.request['materials'] != materials: self.add_error(industry.Error.MISMATCH_MATERIAL, self.request['materials'], materials) self.activity.job_validate(self) self.on_validate(self) if existing != self._errors: self.on_errors(self, self._errors) return self._errors errors = property(_get_errors) def _get_error_codes(self): """ Returns a set of just the error codes. """ return set([ error for error, args in self.errors ]) error_codes = property(_get_error_codes) def validate(self): """ Checks the errors and raises an exception. """ errors = self.errors if len(errors): raise industry.ValidationError(errors) def dump(self): """ Creates a slim item for this job, summarizing the dynamic elements as well as the final calculated values. We can use this for sending a copy of a job over the wire and validating the state of it afterwards. """ return {'blueprintID': self.blueprint.blueprintID, 'blueprintTypeID': self.blueprint.blueprintTypeID, 'activityID': self.activityID, 'facilityID': self.facility.facilityID if self.facility else None, 'solarSystemID': self.facility.solarSystemID if self.facility else None, 'teamID': self.team.teamID if self.team else None, 'characterID': self.characterID, 'corporationID': self.corporationID, 'account': self.account, 'runs': self.runs, 'cost': self.cost, 'tax': self.tax, 'time': self.time.total_seconds(), 'materials': {material.typeID:material.quantity for material in self.materials}, 'inputLocation': self.inputLocation, 'outputLocation': self.outputLocation, 'licensedRuns': self.licensedRuns, 'productTypeID': self.productTypeID} @classmethod def create(cls, request, blueprint): """ The reverse of the dump method, takes a slim dictionary summary of a job and reconstructs it into the current object. We need to be provided a few load methods responsible for fetching the related objects. """ required = {'blueprintID', 'blueprintTypeID', 'activityID', 'facilityID', 'teamID', 'characterID', 'corporationID', 'account', 'runs', 'inputLocation', 'outputLocation', 'licensedRuns', 'productTypeID'} if len(required.difference(set(request))): raise RuntimeError('Invalid Request: ', required.difference(set(request))) job = Job(blueprint, request['activityID']) job.characterID = request['characterID'] job.corporationID = request['corporationID'] job.account = request['account'] job.runs = request['runs'] job.inputLocation = request['inputLocation'] job.outputLocation = request['outputLocation'] job.licensedRuns = request['licensedRuns'] job.productTypeID = request['productTypeID'] job.request = request return job