From 2910bf838a5ae1caa3bd6f22d751b13494a066cc Mon Sep 17 00:00:00 2001 From: dluman Date: Sun, 7 Jan 2024 18:35:29 -0500 Subject: [PATCH 01/11] Equip and unequip work; need to update world-container --- libs/inventory/inventory/Equipment.py | 22 +++++++++++++++++++++- libs/inventory/inventory/Inventory.py | 13 +++++++++++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/libs/inventory/inventory/Equipment.py b/libs/inventory/inventory/Equipment.py index a4b9cdf..5485492 100644 --- a/libs/inventory/inventory/Equipment.py +++ b/libs/inventory/inventory/Equipment.py @@ -1,6 +1,7 @@ import os import sys import enum +import inspect import narrator import sqlite3 @@ -27,6 +28,9 @@ def choose_equip_side(sides: list = []) -> str: # TODO: Seems to belong in Validator, tho. @staticmethod def verify_valid_slot(name: str = "", slot: str = "") -> bool: + # Jump the queue if unequipping! + if inspect.stack()[1].function == "unequip": + return True instance = Instance(name) return instance.get_property("slot")["location"] in RelicSpec.Slots @@ -62,6 +66,8 @@ def configure(conn: sqlite3.Connection) -> None: conn.commit() # Set trigger to validate slot assignment on update conn.create_function("verify_valid_slot", 2, Equipment.verify_valid_slot) + + # TODO: Feature-flag sqlite3 callback trace as DEBUG sqlite3.enable_callback_tracebacks(True) cursor.execute( @@ -78,7 +84,6 @@ def configure(conn: sqlite3.Connection) -> None: ) # Set trigger to prevent additional slot creation - # TODO: Reenable when finished with table creation cursor.execute( """ CREATE TRIGGER IF NOT EXISTS inv_equipment_limit_slots @@ -122,6 +127,21 @@ def equip(conn: sqlite3.Connection, name: str = "") -> bool: sys.exit() return bool(cursor.rowcount) + @staticmethod + def unequip(conn: sqlite3.Connection, name: str = "") -> bool: + instance = Instance(name) + slot = instance.get_property("slot")["location"].value + cursor = conn.cursor() + cursor.execute( + """ + UPDATE equipment + SET name = "" + WHERE name = ? AND slot = ? + """, + (name, slot, ) + ) + conn.commit() + class EquipError(Exception): def __init__(self, item:str, *args): diff --git a/libs/inventory/inventory/Inventory.py b/libs/inventory/inventory/Inventory.py index 44cf115..780a41b 100644 --- a/libs/inventory/inventory/Inventory.py +++ b/libs/inventory/inventory/Inventory.py @@ -378,12 +378,21 @@ def equip(self, item: str): try: result = registry.search(item = item) if not result: - raise OutOfError + raise OutOfError(item) Equipment.equip(registry.conn, item) except OutOfError: print(f"It doesn't look like you have any {item}.") - exit() + sys.exit() + def unequip(self, item: str): + try: + result = registry.search(item = item) + if not result: + raise OutOfError(item) + Equipment.unequip(registry.conn, item) + except OutOfError: + print(f"It doesn't look like you have any {item}.") + sys.exit() def use(self, item: str): """ Uses an item from the inventory """ # Set up properties and potential kwargs From 949645ec4e0c06e642b16f5d52923d7197b2e3b0 Mon Sep 17 00:00:00 2001 From: dluman Date: Mon, 8 Jan 2024 19:07:02 -0500 Subject: [PATCH 02/11] Incorporating pennant to feature-flag WORLD variable --- libs/inventory/inventory/Equipment.py | 3 +++ libs/inventory/inventory/Inventory.py | 5 +++-- libs/inventory/requirements.txt | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/libs/inventory/inventory/Equipment.py b/libs/inventory/inventory/Equipment.py index 5485492..6fa2f8c 100644 --- a/libs/inventory/inventory/Equipment.py +++ b/libs/inventory/inventory/Equipment.py @@ -13,6 +13,9 @@ class Equipment: # TODO: There are a lot of duplicated methods testing # types, et al. We need to remove/conslidate them. + # TODO: Decorate static methods with a decorator that acts + # like a feature flag; is this another tool? + def choose_equip_side(sides: list = []) -> str: """ Deprecated, or at least out of current use (RETAIN) """ if type(sides) == str or len(sides) == 1: diff --git a/libs/inventory/inventory/Inventory.py b/libs/inventory/inventory/Inventory.py index 780a41b..2f5e53d 100644 --- a/libs/inventory/inventory/Inventory.py +++ b/libs/inventory/inventory/Inventory.py @@ -6,6 +6,7 @@ import importlib import shutil import sqlite3 +import pennant from rich.table import Table from rich.console import Console @@ -145,7 +146,7 @@ def __create_inv_sql_table(self): ); """ ) - if WORLD == "venture": + with pennant.FEATURE_FLAG_CODE(WORLD == "venture"): Equipment.configure(conn = self.conn) # Convert legacy JSON file (DEPRECATE WHEN PRACTICAL) @@ -292,7 +293,7 @@ def display(self): table.add_column("Item count") table.add_column("Consumable") table.add_column("Volume") - if WORLD == "venture": + with pennant.FEATURE_FLAG_CODE(WORLD == "venture"): table.add_column("Equippable") table.add_column("Durability") table.add_column("Equipped") diff --git a/libs/inventory/requirements.txt b/libs/inventory/requirements.txt index 51e315f..ce0cbb3 100644 --- a/libs/inventory/requirements.txt +++ b/libs/inventory/requirements.txt @@ -2,3 +2,4 @@ gitit python-dotenv rich dill +pennant From 2061f1ad3bfcd669f51e391bbb6f23cde3e83fab Mon Sep 17 00:00:00 2001 From: dluman Date: Mon, 8 Jan 2024 20:45:43 -0500 Subject: [PATCH 03/11] Adding basic functionality to create loadout display --- libs/inventory/inventory/Equipment.py | 9 ++++++--- libs/inventory/inventory/Inventory.py | 8 ++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/libs/inventory/inventory/Equipment.py b/libs/inventory/inventory/Equipment.py index 6fa2f8c..1aaaab9 100644 --- a/libs/inventory/inventory/Equipment.py +++ b/libs/inventory/inventory/Equipment.py @@ -13,9 +13,6 @@ class Equipment: # TODO: There are a lot of duplicated methods testing # types, et al. We need to remove/conslidate them. - # TODO: Decorate static methods with a decorator that acts - # like a feature flag; is this another tool? - def choose_equip_side(sides: list = []) -> str: """ Deprecated, or at least out of current use (RETAIN) """ if type(sides) == str or len(sides) == 1: @@ -145,6 +142,12 @@ def unequip(conn: sqlite3.Connection, name: str = "") -> bool: ) conn.commit() + @staticmethod + def show(cursor: sqlite3.Cursor): + for slot in RelicSpec.Slots: + equipped = Equipment.discover(cursor, slot.value) + yield slot.value, equipped + class EquipError(Exception): def __init__(self, item:str, *args): diff --git a/libs/inventory/inventory/Inventory.py b/libs/inventory/inventory/Inventory.py index 2f5e53d..93adc1f 100644 --- a/libs/inventory/inventory/Inventory.py +++ b/libs/inventory/inventory/Inventory.py @@ -375,6 +375,7 @@ def drop(self, item: str = "", quantity: int = 1) -> None: except: pass + @pennant.FEATURE_FLAG_FUNCTION(WORLD == "venture") def equip(self, item: str): try: result = registry.search(item = item) @@ -385,6 +386,7 @@ def equip(self, item: str): print(f"It doesn't look like you have any {item}.") sys.exit() + @pennant.FEATURE_FLAG_FUNCTION(WORLD == "venture") def unequip(self, item: str): try: result = registry.search(item = item) @@ -394,6 +396,12 @@ def unequip(self, item: str): except OutOfError: print(f"It doesn't look like you have any {item}.") sys.exit() + + @pennant.FEATURE_FLAG_FUNCTION(WORLD == "venture") + def equipped(self): + for slot, value in Equipment.show(registry.conn.cursor()): + print(slot, value) + def use(self, item: str): """ Uses an item from the inventory """ # Set up properties and potential kwargs From 2a655316eb9a7c97752f7bbd67103144321c39ec Mon Sep 17 00:00:00 2001 From: dluman Date: Mon, 8 Jan 2024 21:09:17 -0500 Subject: [PATCH 04/11] Adding more feature flags, table for equipment view --- libs/inventory/inventory/Inventory.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/libs/inventory/inventory/Inventory.py b/libs/inventory/inventory/Inventory.py index 93adc1f..60c7947 100644 --- a/libs/inventory/inventory/Inventory.py +++ b/libs/inventory/inventory/Inventory.py @@ -82,7 +82,6 @@ def move(self): # based on real file name; however, if this # fails it might be OK pass - #obj = importlib.import_module(self.name) if "ItemSpec" in dir(instance) or "RelicSpec" in dir(instance): # Remove only the physically present copy os.remove(f"./{self.item}") @@ -242,7 +241,6 @@ def total_volume(self) -> int: def add(self, item: str, number: int = 1) -> None: """ API to add an item to the inventory DB """ - # TODO: Doesn't add if there aren't any already? cursor = self.conn.cursor() cursor.execute( f""" @@ -307,7 +305,7 @@ def display(self): for name, filename, quantity, consumable, volume in cursor.fetchall(): # Feature-flag the rows; columns already are data = [str(name), str(quantity), str(bool(consumable)), str(volume)] - if WORLD == "venture": + with pennant.FEATURE_FLAG_CODE(WORLD == "venture"): instance = Instance(name) data += [ str(True if instance.get_property("slot") else False), @@ -399,8 +397,13 @@ def unequip(self, item: str): @pennant.FEATURE_FLAG_FUNCTION(WORLD == "venture") def equipped(self): + table = Table(title=f"{os.getenv('LOGNAME')}'s equipment") + table.add_column("Slot") + table.add_column("Item") for slot, value in Equipment.show(registry.conn.cursor()): - print(slot, value) + table.add_row(slot, value) + console = Console() + console.print(table) def use(self, item: str): """ Uses an item from the inventory """ From ee96c045ce4522e4e91027b497a440a6090a14dc Mon Sep 17 00:00:00 2001 From: dluman Date: Mon, 8 Jan 2024 21:35:04 -0500 Subject: [PATCH 05/11] discover method doesn't work as well as an all-query --- libs/inventory/inventory/Equipment.py | 10 +++++++--- libs/inventory/inventory/Inventory.py | 17 ++--------------- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/libs/inventory/inventory/Equipment.py b/libs/inventory/inventory/Equipment.py index 1aaaab9..8b94617 100644 --- a/libs/inventory/inventory/Equipment.py +++ b/libs/inventory/inventory/Equipment.py @@ -2,6 +2,7 @@ import sys import enum import inspect +import pennant import narrator import sqlite3 @@ -144,9 +145,12 @@ def unequip(conn: sqlite3.Connection, name: str = "") -> bool: @staticmethod def show(cursor: sqlite3.Cursor): - for slot in RelicSpec.Slots: - equipped = Equipment.discover(cursor, slot.value) - yield slot.value, equipped + cursor.execute( + """ + SELECT slot, name FROM equipment; + """ + ) + return cursor.fetchall() class EquipError(Exception): diff --git a/libs/inventory/inventory/Inventory.py b/libs/inventory/inventory/Inventory.py index 60c7947..cf50bbe 100644 --- a/libs/inventory/inventory/Inventory.py +++ b/libs/inventory/inventory/Inventory.py @@ -15,13 +15,7 @@ from .Config import * from .Equipment import * -# TODO: Why not just import *? -from .Item import ItemSpec -from .Item import FixtureSpec -from .Item import BoxSpec -from .Item import OutOfError -from .Item import IsFixture -from .Item import Factory +from .Item import * from .Validation import Validator from .Instantiator import Instance @@ -87,9 +81,7 @@ def move(self): os.remove(f"./{self.item}") except Exception as e: # TODO: Differentiate levels of inacquisition. For - # example, change the message here to reflect - # _why_ something couldn't Acquire; see add - # method below as well. + # example, use different exceptions defensively. print(f"Couldn't acquire {self.name}") sys.exit() @@ -114,7 +106,6 @@ def add(self): class Registry: - # File operations def __init__(self): """ Constructor """ self.inventory = {} @@ -129,7 +120,6 @@ def __init__(self): self.__convert_json_file() os.unlink(f"{self.path}/.registry") - # Create inventory SQL table def __create_inv_sql_table(self): """ Create tables for inventory and other needs based on WORLD_NAME """ cursor = self.conn.cursor() @@ -148,7 +138,6 @@ def __create_inv_sql_table(self): with pennant.FEATURE_FLAG_CODE(WORLD == "venture"): Equipment.configure(conn = self.conn) - # Convert legacy JSON file (DEPRECATE WHEN PRACTICAL) def __convert_json_file(self): """ Convert JSON file from earlier versions of topia """ with open(os.path.expanduser( @@ -283,7 +272,6 @@ def search(self, item: str = "") -> dict: } return {} - # Create a nice(r) display def display(self): """ Display contents of inventory to the terminal """ table = Table(title=f"{os.getenv('LOGNAME')}'s inventory") @@ -303,7 +291,6 @@ def display(self): """) for name, filename, quantity, consumable, volume in cursor.fetchall(): - # Feature-flag the rows; columns already are data = [str(name), str(quantity), str(bool(consumable)), str(volume)] with pennant.FEATURE_FLAG_CODE(WORLD == "venture"): instance = Instance(name) From 8a79d3b643552260b15c864e6f6ab90ce5709cd8 Mon Sep 17 00:00:00 2001 From: dluman Date: Tue, 9 Jan 2024 08:50:35 -0500 Subject: [PATCH 06/11] Updating with multi-slot items; fixing narrator bug (lower/upper case options) --- libs/inventory/inventory/Equipment.py | 59 ++++++++++++++++++--------- libs/inventory/inventory/Inventory.py | 3 +- libs/inventory/inventory/Item.py | 5 +++ libs/narrator/narrator/Question.py | 2 +- 4 files changed, 46 insertions(+), 23 deletions(-) diff --git a/libs/inventory/inventory/Equipment.py b/libs/inventory/inventory/Equipment.py index 8b94617..332a543 100644 --- a/libs/inventory/inventory/Equipment.py +++ b/libs/inventory/inventory/Equipment.py @@ -6,6 +6,9 @@ import narrator import sqlite3 +from typing import Any +from arglite import parser as cliarg + from .Item import RelicSpec from .Instantiator import Instance @@ -14,10 +17,11 @@ class Equipment: # TODO: There are a lot of duplicated methods testing # types, et al. We need to remove/conslidate them. - def choose_equip_side(sides: list = []) -> str: - """ Deprecated, or at least out of current use (RETAIN) """ - if type(sides) == str or len(sides) == 1: - return [sides][-1] + def choose_equip_side(sides) -> str: + if type(sides) == RelicSpec.Slots: + return sides.value + if type(sides) == list and len(sides) == 1: + return sides[-1].value q = narrator.Question({ "question": "Equip to which side?\n", "responses": [ @@ -28,18 +32,24 @@ def choose_equip_side(sides: list = []) -> str: # TODO: Seems to belong in Validator, tho. @staticmethod - def verify_valid_slot(name: str = "", slot: str = "") -> bool: + def verify_valid_slot(name: str = "", slot: Any = "") -> bool: # Jump the queue if unequipping! if inspect.stack()[1].function == "unequip": return True instance = Instance(name) - return instance.get_property("slot")["location"] in RelicSpec.Slots + slots = instance.get_property("slot")["location"] + if type(slots) == RelicSpec.Slots: + slots = [slots] + for slot in slots: + if slot not in RelicSpec.Slots: return False + return True @staticmethod def configure(conn: sqlite3.Connection) -> None: + """ Configure table on first-time run """ cursor = conn.cursor() - # Create equipment table + # Create equipment table cursor.execute( """ CREATE TABLE IF NOT EXISTS equipment ( @@ -65,11 +75,12 @@ def configure(conn: sqlite3.Connection) -> None: (slot.value,) ) conn.commit() + # Set trigger to validate slot assignment on update conn.create_function("verify_valid_slot", 2, Equipment.verify_valid_slot) - # TODO: Feature-flag sqlite3 callback trace as DEBUG - sqlite3.enable_callback_tracebacks(True) + with pennant.FEATURE_FLAG_CODE(cliarg.optional.debug): + sqlite3.enable_callback_tracebacks(True) cursor.execute( """ @@ -111,7 +122,10 @@ def discover(cursor: sqlite3.Cursor, name: str = "") -> str: @staticmethod def equip(conn: sqlite3.Connection, name: str = "") -> bool: instance = Instance(name) - slot = instance.get_property("slot")["location"].value + slot = Equipment.choose_equip_side( + instance.get_property("slot")["location"] + ) + print(slot) cursor = conn.cursor() try: cursor.execute( @@ -131,17 +145,22 @@ def equip(conn: sqlite3.Connection, name: str = "") -> bool: @staticmethod def unequip(conn: sqlite3.Connection, name: str = "") -> bool: instance = Instance(name) - slot = instance.get_property("slot")["location"].value + # TODO: Fix for multi-slot cases (iteratives). + slots = instance.get_property("slot")["location"] + if type(slots) == RelicSpec.Slots: + slots = [slots] cursor = conn.cursor() - cursor.execute( - """ - UPDATE equipment - SET name = "" - WHERE name = ? AND slot = ? - """, - (name, slot, ) - ) - conn.commit() + for slot in slots: + cursor.execute( + """ + UPDATE equipment + SET name = "" + WHERE name = ? AND slot = ? + """, + (name, slot.value, ) + ) + if cursor.rowcount == 1: + conn.commit() @staticmethod def show(cursor: sqlite3.Cursor): diff --git a/libs/inventory/inventory/Inventory.py b/libs/inventory/inventory/Inventory.py index cf50bbe..e49a26f 100644 --- a/libs/inventory/inventory/Inventory.py +++ b/libs/inventory/inventory/Inventory.py @@ -256,7 +256,7 @@ def remove(self, item: str, number: int = -1) -> None: def search(self, item: str = "") -> dict: """ API to search inventory database """ # TODO: Expand to allow for multiple item search - # using OR logic + # using OR logic...er...nah. cursor = self.conn.cursor() cursor.execute( """ @@ -284,7 +284,6 @@ def display(self): table.add_column("Durability") table.add_column("Equipped") - # TODO: Move query to its own method? cursor = self.conn.cursor() cursor.execute(""" SELECT name, filename, quantity, consumable, volume FROM items diff --git a/libs/inventory/inventory/Item.py b/libs/inventory/inventory/Item.py index b4ee2c6..26b06a7 100644 --- a/libs/inventory/inventory/Item.py +++ b/libs/inventory/inventory/Item.py @@ -75,6 +75,8 @@ class Slots(enum.Enum): BELT = "BELT" LEGS = "LEGS" FEET = "FEET" + WEAPON_LEFT = "LEFT WEAPON" + WEAPON_RIGHT = "RIGHT WEAPON" class Sides(enum.Enum): RIGHT = "right" @@ -98,6 +100,9 @@ def __init__(self, filename: str = ""): # sides = Sides._value2member_map_ # return side in sides +class WeaponSpec(RelicSpec): + pass + class Factory: def __init__(self, name, path: str = "", item_type: any = ItemSpec, template: str = "", **kwargs): diff --git a/libs/narrator/narrator/Question.py b/libs/narrator/narrator/Question.py index 21d8dc2..3a4c69c 100644 --- a/libs/narrator/narrator/Question.py +++ b/libs/narrator/narrator/Question.py @@ -14,7 +14,7 @@ def is_key(self, char: str) -> bool: return False def set_opt(self, option: dict) -> dict: - choice = option["choice"] + choice = option["choice"].lower() for letter in choice: if not self.is_key(letter): opt = Option(letter, option) From c423f3faa7ba76c6d9bf1c35e58675dd195d2cae Mon Sep 17 00:00:00 2001 From: dluman Date: Tue, 9 Jan 2024 09:47:04 -0500 Subject: [PATCH 07/11] Removing tracing code; should use --debug flag in future --- libs/inventory/inventory/Equipment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/inventory/inventory/Equipment.py b/libs/inventory/inventory/Equipment.py index 332a543..f846d0f 100644 --- a/libs/inventory/inventory/Equipment.py +++ b/libs/inventory/inventory/Equipment.py @@ -125,7 +125,6 @@ def equip(conn: sqlite3.Connection, name: str = "") -> bool: slot = Equipment.choose_equip_side( instance.get_property("slot")["location"] ) - print(slot) cursor = conn.cursor() try: cursor.execute( @@ -161,6 +160,7 @@ def unequip(conn: sqlite3.Connection, name: str = "") -> bool: ) if cursor.rowcount == 1: conn.commit() + break @staticmethod def show(cursor: sqlite3.Cursor): From 60b2191bff2b035aeaf4edfcaa9684704a5d2cae Mon Sep 17 00:00:00 2001 From: dluman Date: Wed, 10 Jan 2024 22:32:05 -0500 Subject: [PATCH 08/11] Progress toward a dillable serial object --- libs/inventory/inventory/Instantiator.py | 14 ++++++----- libs/inventory/inventory/Inventory.py | 22 +++++++++++----- libs/inventory/inventory/Item.py | 32 ++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 12 deletions(-) diff --git a/libs/inventory/inventory/Instantiator.py b/libs/inventory/inventory/Instantiator.py index 223ea17..7b74e6f 100644 --- a/libs/inventory/inventory/Instantiator.py +++ b/libs/inventory/inventory/Instantiator.py @@ -5,18 +5,20 @@ class Instance: def __init__(self, item: str = ""): """ Instantiate object to access runnable properties """ try: - item_file = importlib.import_module(f"{item}") - self.instance = getattr(item_file, item)() + self.module = importlib.import_module(f"{item}") + self.object = getattr(self.module, item)() + self.serialized = self.module.dillable(self.object) except ModuleNotFoundError: print(f"It seems you don't have any {item}.") exit() - except: + except Exception as e: + print(e) print(f"{item} doesn't seem to be a valid object.") exit() def has_property(self, prop: str = "") -> bool: try: - getattr(self.instance, prop) + getattr(self.object, prop) return True except: pass @@ -24,10 +26,10 @@ def has_property(self, prop: str = "") -> bool: def get_property(self, prop: str = ""): try: - return getattr(self.instance, prop) + return getattr(self.object, prop) except: pass def is_child_of(self, item_type) -> bool: - res_order = self.instance.__mro__ + res_order = self.object.__mro__ print(item_type in res_order) diff --git a/libs/inventory/inventory/Inventory.py b/libs/inventory/inventory/Inventory.py index e49a26f..f9e76fb 100644 --- a/libs/inventory/inventory/Inventory.py +++ b/libs/inventory/inventory/Inventory.py @@ -56,11 +56,14 @@ def is_relic(self, item) -> bool: def locate(self, filename: str = "") -> None: """ Locates item file in current working directory """ + # TODO: Revise method to look in directory and prompt if there + # are multiple similarly-named items. self.name, self.ext = self.filename.split("/")[-1].split(".") self.name = re.search(r"[a-zA-Z]+", self.name).group(0) self.box = Validator.is_box(self.name) - self.filename = f"{self.name}.{self.ext}" + self.filename = f"{self.name}" # Removed {self.ext}; do we need it? + # TODO: This is how we dill it def move(self): """ Move the file acquired to the inventory directory """ try: @@ -70,8 +73,11 @@ def move(self): if not self.box: instance = Instance(self.name) try: - shutil.copy(self.filename, path) - except: + with open(f"{path}","wb") as fh: + # TODO: Class not found + dill.dump(instance.object, fh) + #shutil.copy(self.filename, path) + except Exception as e: # This operation attempts to move the file # based on real file name; however, if this # fails it might be OK @@ -243,7 +249,7 @@ def add(self, item: str, number: int = 1) -> None: if cursor.rowcount != 1: self.__add_table_entry( name = item, - filename = f"{item}.py", + filename = f"{item}", quantity = number ) self.__remove_zero_quantity_items() @@ -292,7 +298,9 @@ def display(self): for name, filename, quantity, consumable, volume in cursor.fetchall(): data = [str(name), str(quantity), str(bool(consumable)), str(volume)] with pennant.FEATURE_FLAG_CODE(WORLD == "venture"): - instance = Instance(name) + with open("{name}", "rb") as fh: + instance = dill.load(fh) + #instance = Instance(name) data += [ str(True if instance.get_property("slot") else False), str(instance.get_property("durability") or "-"), @@ -400,7 +408,9 @@ def use(self, item: str): # Verify that item is in path or inventory try: # TODO: Replace with Instantiator instance - item_file = importlib.import_module(f"{item}") + with open(f"{Config.values['INV_PATH']}/{item}") as fh: + item_file = dill.load(fh) + #item_file = importlib.import_module(f"{item}") except ModuleNotFoundError: print(f"You don't seem to have any {item}.") sys.exit() diff --git a/libs/inventory/inventory/Item.py b/libs/inventory/inventory/Item.py index 26b06a7..a42d9aa 100644 --- a/libs/inventory/inventory/Item.py +++ b/libs/inventory/inventory/Item.py @@ -18,22 +18,47 @@ class ItemSpec: def __init__(self, filename: str = ""): + """ Constructor """ self.file = filename self.actions = {} + # TODO: Refactor to use arglite; this is the + # snippet it came from, after all arg_pairs = self.pairs(sys.argv) for arg, val in arg_pairs: if re.match(r"^-{1,2}", arg): arg = arg.replace("-","") self.actions[arg] = val + # Constant properties self.consumable = True self.equippable = False self.unique = False self.VOLUME = 1 self.vars() + # Portable dill'ing brought to you by the kind folks at: + # https://oegedijk.github.io/blog/pickle/dill/python/2020/11/10/serializing-dill-references.html + + @staticmethod + def _mainify(obj): + if obj.__module__ != "__main__": + import __main__ + import inspect + source = inspect.getsource(obj) + co = compile(source, '', 'exec') + exec(co, __main__.__dict__) + + @classmethod + def _dillable(cls): + import __main__ + cls._mainify(self) + cls = getattr(__main__, cls.__name__) + return cls + + # TODO: See above note on arglite def pairs(self, args: list = []): return [args[i*2:(i*2)+2] for i in range(len(args)-2)] + # TODO: See above note about above note on arglite def vars(self) -> None: for arg in self.actions: setattr(self, arg, self.actions[arg]) @@ -92,6 +117,8 @@ def __init__(self, filename: str = ""): "location": self.Slots.HANDS, } + # TODO: Determine if the below are really necessary. + #def __validate_slot_value(self, slot: str = ""): # slots = Slots._value2member_map_ # return slot in slots @@ -101,10 +128,15 @@ def __init__(self, filename: str = ""): # return side in sides class WeaponSpec(RelicSpec): + + # Nothing special to see here...yet. + pass class Factory: + # TODO: Finish springform, dammit. + def __init__(self, name, path: str = "", item_type: any = ItemSpec, template: str = "", **kwargs): """ Creates items from templates """ self.path = path From 83534b7ee471959fbc6bf4a14bce51527361e28c Mon Sep 17 00:00:00 2001 From: dluman Date: Sat, 13 Jan 2024 13:55:11 -0500 Subject: [PATCH 09/11] Dillable objects work! --- libs/inventory/inventory/Instantiator.py | 5 ++-- libs/inventory/inventory/Inventory.py | 34 +++++++++++------------- libs/inventory/inventory/Item.py | 22 +++++++++------ libs/inventory/inventory/Validation.py | 1 + 4 files changed, 33 insertions(+), 29 deletions(-) diff --git a/libs/inventory/inventory/Instantiator.py b/libs/inventory/inventory/Instantiator.py index 7b74e6f..d9a6cf8 100644 --- a/libs/inventory/inventory/Instantiator.py +++ b/libs/inventory/inventory/Instantiator.py @@ -6,8 +6,9 @@ def __init__(self, item: str = ""): """ Instantiate object to access runnable properties """ try: self.module = importlib.import_module(f"{item}") - self.object = getattr(self.module, item)() - self.serialized = self.module.dillable(self.object) + self.uninst = getattr(self.module, item) + self.object = self.uninst() + self.serial = self.uninst.dillable(self.uninst) except ModuleNotFoundError: print(f"It seems you don't have any {item}.") exit() diff --git a/libs/inventory/inventory/Inventory.py b/libs/inventory/inventory/Inventory.py index f9e76fb..de76091 100644 --- a/libs/inventory/inventory/Inventory.py +++ b/libs/inventory/inventory/Inventory.py @@ -74,8 +74,8 @@ def move(self): instance = Instance(self.name) try: with open(f"{path}","wb") as fh: - # TODO: Class not found - dill.dump(instance.object, fh) + # TODO: Class inheritance not found? + dill.dump(instance.serial, fh) #shutil.copy(self.filename, path) except Exception as e: # This operation attempts to move the file @@ -399,7 +399,8 @@ def equipped(self): console = Console() console.print(table) - def use(self, item: str): + @classmethod + def use(self, item: str = ""): """ Uses an item from the inventory """ # Set up properties and potential kwargs box = False @@ -408,27 +409,23 @@ def use(self, item: str): # Verify that item is in path or inventory try: # TODO: Replace with Instantiator instance - with open(f"{Config.values['INV_PATH']}/{item}") as fh: - item_file = dill.load(fh) + path = os.path.expanduser( + f"{Config.values['INV_PATH']}/{item}" + ) + with open(path, "rb") as fh: + instance = dill.load(fh) #item_file = importlib.import_module(f"{item}") except ModuleNotFoundError: print(f"You don't seem to have any {item}.") sys.exit() - # Reflect the class - try: - # TODO: Use Instantiator instance - instance = getattr(item_file, item)() - except: - print(f"{item} doesn't seem to be a valid object.") - sys.exit() - # Test type of item; remove if ItemSpec try: record = registry.search(item) - box = self.is_box(item_file) - relic = self.is_relic(item_file) - fixture = self.is_fixture(item_file) + # TODO: Combine inherited traits as single search API? + #box = self.is_box(instance) + #relic = self.is_relic(instance) + #fixture = self.is_fixture(instance) # Only decrease quantity if item is consumable if instance.consumable: registry.remove(item) @@ -439,12 +436,11 @@ def use(self, item: str): registry.remove(item = item) sys.exit() except IsFixture: pass - # Return the result or inbuilt use method if type(instance).__str__ is not object.__str__: - instance.use(**instance.actions) + instance.use(instance, **instance.actions) else: - return instance.use(**instance.actions) + return instance.use(instance, **instance.actions) # Create instances to use as shorthand. I thought this was a bad idea, # but this is actually how the random module works: diff --git a/libs/inventory/inventory/Item.py b/libs/inventory/inventory/Item.py index a42d9aa..4927d66 100644 --- a/libs/inventory/inventory/Item.py +++ b/libs/inventory/inventory/Item.py @@ -35,23 +35,29 @@ def __init__(self, filename: str = ""): self.VOLUME = 1 self.vars() - # Portable dill'ing brought to you by the kind folks at: + # The basis for this portable dill'ing brought to you by the kind folks at: # https://oegedijk.github.io/blog/pickle/dill/python/2020/11/10/serializing-dill-references.html - @staticmethod - def _mainify(obj): - if obj.__module__ != "__main__": + def mainify(self, props: dict = {}): + if self.__module__ != "__main__": import __main__ import inspect - source = inspect.getsource(obj) + source = inspect.getsource(self) + # Inject inhertiable classes before source; they + # don't come over via inspect methods + # TODO: Limit to the actual inheritables? + source = f"from inventory.Item import *\n\n{source}" co = compile(source, '', 'exec') exec(co, __main__.__dict__) @classmethod - def _dillable(cls): + def dillable(self, instance): import __main__ - cls._mainify(self) - cls = getattr(__main__, cls.__name__) + instance.mainify(instance) + cls = getattr(__main__, self.__name__) + props = instance().__dict__ + for prop in props: + setattr(cls, str(prop), props[prop]) return cls # TODO: See above note on arglite diff --git a/libs/inventory/inventory/Validation.py b/libs/inventory/inventory/Validation.py index b4a3366..c423a57 100644 --- a/libs/inventory/inventory/Validation.py +++ b/libs/inventory/inventory/Validation.py @@ -1,4 +1,5 @@ import re +import dill import importlib from .Item import BoxSpec From 6fc9286e89ea33aec46a94a38d5b7eb4b0685359 Mon Sep 17 00:00:00 2001 From: dluman Date: Sat, 13 Jan 2024 16:17:30 -0500 Subject: [PATCH 10/11] Equippable weapons appear; should weapons be classed unique? --- libs/inventory/inventory/Instantiator.py | 18 ---------- libs/inventory/inventory/Inventory.py | 46 ++++++++++++------------ 2 files changed, 22 insertions(+), 42 deletions(-) diff --git a/libs/inventory/inventory/Instantiator.py b/libs/inventory/inventory/Instantiator.py index d9a6cf8..0428899 100644 --- a/libs/inventory/inventory/Instantiator.py +++ b/libs/inventory/inventory/Instantiator.py @@ -16,21 +16,3 @@ def __init__(self, item: str = ""): print(e) print(f"{item} doesn't seem to be a valid object.") exit() - - def has_property(self, prop: str = "") -> bool: - try: - getattr(self.object, prop) - return True - except: - pass - return False - - def get_property(self, prop: str = ""): - try: - return getattr(self.object, prop) - except: - pass - - def is_child_of(self, item_type) -> bool: - res_order = self.object.__mro__ - print(item_type in res_order) diff --git a/libs/inventory/inventory/Inventory.py b/libs/inventory/inventory/Inventory.py index de76091..0b10cbd 100644 --- a/libs/inventory/inventory/Inventory.py +++ b/libs/inventory/inventory/Inventory.py @@ -107,7 +107,6 @@ def add(self): else: print(f"Couldn't acquire {self.quantity} {self.name}: Max Volume exceeded") sys.exit() - # TODO: Add resistance for certain magical items or level needs? class Registry: @@ -295,15 +294,18 @@ def display(self): SELECT name, filename, quantity, consumable, volume FROM items """) + path = os.path.expanduser( + f"{Config.values['INV_PATH']}" + ) + for name, filename, quantity, consumable, volume in cursor.fetchall(): data = [str(name), str(quantity), str(bool(consumable)), str(volume)] with pennant.FEATURE_FLAG_CODE(WORLD == "venture"): - with open("{name}", "rb") as fh: + with open(f"{path}/{name}", "rb") as fh: instance = dill.load(fh) - #instance = Instance(name) data += [ - str(True if instance.get_property("slot") else False), - str(instance.get_property("durability") or "-"), + str(True if instance.slot else False), + str(instance.durability or "-"), str(Equipment.discover(cursor, name) or "-") ] table.add_row(*data) @@ -317,21 +319,20 @@ def __init__(self, registry): """ Constructor """ self.inv = registry - def is_fixture(self, item) -> bool: + @staticmethod + def is_fixture(mro: list = []) -> bool: """ Returns fixture specification status """ return "FixtureSpec" in dir(item) - def is_box(self, item) -> bool: + @staticmethod + def is_box(mro: list = []) -> bool: """ Returns box specification status """ return "BoxSpec" in dir(item) - def is_relic(self, item) -> bool: + @staticmethod + def is_relic(mro: list = []) -> bool: return "RelicSpec" in dir(item) - def file_exists(self, item) -> bool: - """ Checks if item file exists in inventory """ - return os.path.exists(f"{self.inv.path}/{item}.py") - def trash(self, item: str, quantity: int = 1) -> None: """ Removes item from the list; tied to the "remove" .bashrc alias """ try: @@ -349,17 +350,17 @@ def drop(self, item: str = "", quantity: int = 1) -> None: result = registry.search(item = item) if not result: raise OutOfError(item) - # Convert the quantity to an integer if not already one - quantity = int(quantity) - # Test if the number being dropped is more than we have - # and limit the drops to only the quantity that we have - if quantity > result["quantity"]: - quantity = result["quantity"] except OutOfError: print(f"It doesn't look like you have any {item}.") sys.exit() except ValueError: quantity = 1 + # Convert the quantity to an integer if not already one + quantity = int(quantity) + # Test if the number being dropped is more than we have + # and limit the drops to only the quantity that we have + if quantity > result["quantity"]: + quantity = result["quantity"] try: for _ in range(quantity): Factory(item) @@ -408,13 +409,11 @@ def use(self, item: str = ""): # Verify that item is in path or inventory try: - # TODO: Replace with Instantiator instance path = os.path.expanduser( f"{Config.values['INV_PATH']}/{item}" ) with open(path, "rb") as fh: instance = dill.load(fh) - #item_file = importlib.import_module(f"{item}") except ModuleNotFoundError: print(f"You don't seem to have any {item}.") sys.exit() @@ -422,10 +421,9 @@ def use(self, item: str = ""): # Test type of item; remove if ItemSpec try: record = registry.search(item) - # TODO: Combine inherited traits as single search API? - #box = self.is_box(instance) - #relic = self.is_relic(instance) - #fixture = self.is_fixture(instance) + # Retrieves superclasses from MRO; prevents + # incompatible use cases + mro = [cls.__name__ for cls in instance.__mro__] # Only decrease quantity if item is consumable if instance.consumable: registry.remove(item) From 6a053f436577106860c7f1fb092d705634f10adf Mon Sep 17 00:00:00 2001 From: dluman Date: Sat, 13 Jan 2024 16:18:52 -0500 Subject: [PATCH 11/11] Adding beginning of forcing uniqueness to weapons --- libs/inventory/inventory/Item.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/inventory/inventory/Item.py b/libs/inventory/inventory/Item.py index 4927d66..7790c52 100644 --- a/libs/inventory/inventory/Item.py +++ b/libs/inventory/inventory/Item.py @@ -135,9 +135,9 @@ def __init__(self, filename: str = ""): class WeaponSpec(RelicSpec): - # Nothing special to see here...yet. - - pass + def __init__(self, filename: str = ""): + super().__init__(filename) + self.unique = True class Factory: