Add basic sqlite3 saving & loading support
This commit is contained in:
		
							
								
								
									
										38
									
								
								d2warehouse/db.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								d2warehouse/db.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | |||||||
|  | import atexit | ||||||
|  | import os | ||||||
|  | import sqlite3 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _schema_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "schema.sql") | ||||||
|  | _db = None | ||||||
|  | _path = "items.sqlite3" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def set_db_path(f: str) -> None: | ||||||
|  |     global _path | ||||||
|  |     _path = f | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def get_db() -> sqlite3.Connection: | ||||||
|  |     global _db | ||||||
|  |     if not _db: | ||||||
|  |         _db = sqlite3.connect( | ||||||
|  |             _path, | ||||||
|  |             detect_types=sqlite3.PARSE_DECLTYPES, | ||||||
|  |         ) | ||||||
|  |         _db.row_factory = sqlite3.Row | ||||||
|  |     return _db | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @atexit.register | ||||||
|  | def close_db() -> None: | ||||||
|  |     global _db | ||||||
|  |     if _db: | ||||||
|  |         _db.close() | ||||||
|  |         _db = None | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def create_db() -> None: | ||||||
|  |     db = get_db() | ||||||
|  |     with open(_schema_path, encoding="utf-8") as f: | ||||||
|  |         db.executescript(f.read()) | ||||||
| @@ -16,4 +16,5 @@ | |||||||
| # Mercator. If not, see <https://www.gnu.org/licenses/>. | # Mercator. If not, see <https://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
| STASH_TAB_MAGIC = b"\x55\xAA\x55\xAA" | STASH_TAB_MAGIC = b"\x55\xAA\x55\xAA" | ||||||
|  | STASH_TAB_VERSION = 99 | ||||||
| ITEM_DATA_MAGIC = b"JM" | ITEM_DATA_MAGIC = b"JM" | ||||||
|   | |||||||
| @@ -21,7 +21,9 @@ from typing import Optional | |||||||
| from bitarray import bitarray | from bitarray import bitarray | ||||||
| from bitarray.util import int2ba | from bitarray.util import int2ba | ||||||
| from dataclasses import dataclass | from dataclasses import dataclass | ||||||
| from enum import Enum | from enum import IntEnum | ||||||
|  | from d2warehouse.fileformat import STASH_TAB_VERSION | ||||||
|  | from d2warehouse.db import get_db | ||||||
|  |  | ||||||
| _data_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data") | _data_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data") | ||||||
| _basetype_map = None | _basetype_map = None | ||||||
| @@ -31,7 +33,7 @@ _set_item_map = None | |||||||
| _runeword_map = None | _runeword_map = None | ||||||
|  |  | ||||||
|  |  | ||||||
| class Quality(Enum): | class Quality(IntEnum): | ||||||
|     LOW = 1 |     LOW = 1 | ||||||
|     NORMAL = 2 |     NORMAL = 2 | ||||||
|     HIGH = 3 |     HIGH = 3 | ||||||
| @@ -45,7 +47,7 @@ class Quality(Enum): | |||||||
|         return self.name.capitalize() |         return self.name.capitalize() | ||||||
|  |  | ||||||
|  |  | ||||||
| class LowQualityType(Enum): | class LowQualityType(IntEnum): | ||||||
|     CRUDE = 0 |     CRUDE = 0 | ||||||
|     CRACKED = 1 |     CRACKED = 1 | ||||||
|     DAMAGED = 2 |     DAMAGED = 2 | ||||||
| @@ -60,7 +62,7 @@ class Stat: | |||||||
|     id: int | None = None  # TODO: These 3 should probably not be optional |     id: int | None = None  # TODO: These 3 should probably not be optional | ||||||
|     values: list[int] | None = None |     values: list[int] | None = None | ||||||
|     parameter: int | None = None |     parameter: int | None = None | ||||||
|     text: str | None = None |     text: str | None = None  # TODO: Make this a property | ||||||
|  |  | ||||||
|     def print(self, indent=5): |     def print(self, indent=5): | ||||||
|         print(" " * indent, str(self)) |         print(" " * indent, str(self)) | ||||||
| @@ -88,6 +90,7 @@ def txtbits(bits: bitarray) -> str: | |||||||
| @dataclass | @dataclass | ||||||
| class Item: | class Item: | ||||||
|     raw_data: bytes |     raw_data: bytes | ||||||
|  |     raw_version: int | ||||||
|     is_identified: bool |     is_identified: bool | ||||||
|     is_socketed: bool |     is_socketed: bool | ||||||
|     is_beginner: bool |     is_beginner: bool | ||||||
| @@ -201,6 +204,194 @@ class Item: | |||||||
|         base = lookup_basetype(self.code) |         base = lookup_basetype(self.code) | ||||||
|         return base["width"], base["height"] |         return base["width"], base["height"] | ||||||
|  |  | ||||||
|  |     def write_to_db(self, socketed_into=None, commit=True) -> int: | ||||||
|  |         name = lookup_basetype(self.code)["name"] | ||||||
|  |         # FIXME: handle magic & rare names | ||||||
|  |         if self.is_runeword: | ||||||
|  |             name = lookup_runeword(self.runeword_id)["name"] | ||||||
|  |         elif self.quality == Quality.SET: | ||||||
|  |             name = lookup_set_item(self.set_id)["name"] | ||||||
|  |         elif self.quality == Quality.UNIQUE: | ||||||
|  |             name = lookup_unique(self.unique_id)["name"] | ||||||
|  |  | ||||||
|  |         set_name = ( | ||||||
|  |             lookup_set_item(self.set_id)["set"] if self.quality == Quality.SET else None | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         db = get_db() | ||||||
|  |         cur = db.cursor() | ||||||
|  |         cur.execute( | ||||||
|  |             """INSERT INTO item (itembase_name, socketed_into, raw_data, raw_version, | ||||||
|  |             is_identified, is_socketed, is_beginner, is_simple, is_ethereal, | ||||||
|  |             is_personalized, is_runeword, code) | ||||||
|  |             VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", | ||||||
|  |             ( | ||||||
|  |                 lookup_basetype(self.code)["name"], | ||||||
|  |                 socketed_into, | ||||||
|  |                 self.raw_data, | ||||||
|  |                 self.raw_version, | ||||||
|  |                 self.is_identified, | ||||||
|  |                 self.is_socketed, | ||||||
|  |                 self.is_beginner, | ||||||
|  |                 self.is_simple, | ||||||
|  |                 self.is_ethereal, | ||||||
|  |                 self.is_personalized, | ||||||
|  |                 self.is_runeword, | ||||||
|  |                 self.code, | ||||||
|  |             ), | ||||||
|  |         ) | ||||||
|  |         item_id = cur.lastrowid | ||||||
|  |  | ||||||
|  |         cur.execute( | ||||||
|  |             """INSERT INTO item_extra (item_id, item_name, | ||||||
|  |             set_name, uid, lvl, quality, graphic, implicit, low_quality, set_id, | ||||||
|  |             unique_id, nameword1, nameword2, runeword_id, personal_name, | ||||||
|  |             defense, durability, max_durability, quantity) | ||||||
|  |             VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", | ||||||
|  |             ( | ||||||
|  |                 item_id, | ||||||
|  |                 name, | ||||||
|  |                 set_name, | ||||||
|  |                 self.uid, | ||||||
|  |                 self.lvl, | ||||||
|  |                 int(self.quality) if self.quality else None, | ||||||
|  |                 self.graphic, | ||||||
|  |                 self.implicit, | ||||||
|  |                 int(self.low_quality) if self.low_quality else None, | ||||||
|  |                 self.set_id, | ||||||
|  |                 self.unique_id, | ||||||
|  |                 self.nameword1, | ||||||
|  |                 self.nameword2, | ||||||
|  |                 self.runeword_id, | ||||||
|  |                 self.personal_name, | ||||||
|  |                 self.defense, | ||||||
|  |                 self.durability, | ||||||
|  |                 self.max_durability, | ||||||
|  |                 self.quantity, | ||||||
|  |             ), | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         if self.quality in [Quality.MAGIC, Quality.RARE, Quality.CRAFTED]: | ||||||
|  |             for prefix, id in [(False, id) for id in self.suffixes] + [ | ||||||
|  |                 (True, id) for id in self.prefixes | ||||||
|  |             ]: | ||||||
|  |                 db.execute( | ||||||
|  |                     "INSERT INTO item_affix (item_id, prefix, affix_id) VALUES (?, ?, ?)", | ||||||
|  |                     (item_id, prefix, id), | ||||||
|  |                 ) | ||||||
|  |  | ||||||
|  |         if self.stats: | ||||||
|  |             for stat in self.stats: | ||||||
|  |                 db.execute( | ||||||
|  |                     """INSERT INTO item_stat (item_id, stat, value1, value2, value3, parameter) | ||||||
|  |                     VALUES (?, ?, ?, ?, ?, ?) | ||||||
|  |                     """, | ||||||
|  |                     ( | ||||||
|  |                         item_id, | ||||||
|  |                         stat.id, | ||||||
|  |                         stat.values[0] if len(stat.values) > 0 else None, | ||||||
|  |                         stat.values[1] if len(stat.values) > 1 else None, | ||||||
|  |                         stat.values[2] if len(stat.values) > 2 else None, | ||||||
|  |                         stat.parameter, | ||||||
|  |                     ), | ||||||
|  |                 ) | ||||||
|  |  | ||||||
|  |         if self.sockets: | ||||||
|  |             for socket in self.sockets: | ||||||
|  |                 socket.write_to_db(socketed_into=item_id, commit=False) | ||||||
|  |  | ||||||
|  |         if commit: | ||||||
|  |             db.commit() | ||||||
|  |  | ||||||
|  |         return item_id | ||||||
|  |  | ||||||
|  |     def load_from_db(id: int) -> "Item": | ||||||
|  |         db = get_db() | ||||||
|  |         row = db.execute( | ||||||
|  |             """SELECT raw_data, raw_version, is_identified, is_socketed, | ||||||
|  |             is_beginner, is_simple, is_ethereal, is_personalized, is_runeword, code, | ||||||
|  |             uid, lvl, quality, graphic, implicit, low_quality, set_id, unique_id, | ||||||
|  |             nameword1, nameword2, runeword_id, personal_name, defense, durability, | ||||||
|  |             max_durability, quantity | ||||||
|  |             FROM item INNER JOIN item_extra ON id = item_id WHERE id = ?""", | ||||||
|  |             (id,), | ||||||
|  |         ).fetchone() | ||||||
|  |         if row["raw_version"] != STASH_TAB_VERSION: | ||||||
|  |             raise RuntimeError("Can not load item, the raw version is not supported") | ||||||
|  |  | ||||||
|  |         item = Item( | ||||||
|  |             raw_data=row["raw_data"], | ||||||
|  |             raw_version=row["raw_version"], | ||||||
|  |             is_identified=bool(row["is_identified"]), | ||||||
|  |             is_socketed=bool(row["is_socketed"]), | ||||||
|  |             is_beginner=bool(row["is_beginner"]), | ||||||
|  |             is_simple=bool(row["is_simple"]), | ||||||
|  |             is_ethereal=bool(row["is_ethereal"]), | ||||||
|  |             is_personalized=bool(row["is_personalized"]), | ||||||
|  |             is_runeword=bool(row["is_runeword"]), | ||||||
|  |             pos_x=0, | ||||||
|  |             pos_y=0, | ||||||
|  |             code=row["code"], | ||||||
|  |             uid=row["uid"], | ||||||
|  |             lvl=row["lvl"], | ||||||
|  |             quality=Quality(row["quality"]) if row["quality"] else None, | ||||||
|  |             graphic=row["graphic"], | ||||||
|  |             implicit=row["implicit"], | ||||||
|  |             low_quality=LowQualityType(row["low_quality"]) | ||||||
|  |             if row["low_quality"] | ||||||
|  |             else None, | ||||||
|  |             set_id=row["set_id"], | ||||||
|  |             unique_id=row["unique_id"], | ||||||
|  |             nameword1=row["nameword1"], | ||||||
|  |             nameword2=row["nameword2"], | ||||||
|  |             runeword_id=row["runeword_id"], | ||||||
|  |             personal_name=row["personal_name"], | ||||||
|  |             defense=row["defense"], | ||||||
|  |             durability=row["durability"], | ||||||
|  |             max_durability=row["max_durability"], | ||||||
|  |             quantity=row["quantity"], | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         if item.quality in [Quality.MAGIC, Quality.RARE, Quality.CRAFTED]: | ||||||
|  |             rows = db.execute( | ||||||
|  |                 "SELECT prefix, affix_id FROM item_affix WHERE item_id = ?", (id,) | ||||||
|  |             ) | ||||||
|  |             item.prefixes = [] | ||||||
|  |             item.suffixes = [] | ||||||
|  |             for row in rows: | ||||||
|  |                 if row["prefix"]: | ||||||
|  |                     item.prefixes.append(row["affix_id"]) | ||||||
|  |                 else: | ||||||
|  |                     item.suffixes.append(row["affix_id"]) | ||||||
|  |  | ||||||
|  |         rows = db.execute( | ||||||
|  |             "SELECT id FROM item WHERE socketed_into = ?", (id,) | ||||||
|  |         ).fetchall() | ||||||
|  |         if len(rows) > 0: | ||||||
|  |             item.sockets = [] | ||||||
|  |             for row in rows: | ||||||
|  |                 socket = Item.load_from_db(row["id"]) | ||||||
|  |                 socket.pos_x = len(item.sockets) | ||||||
|  |                 item.sockets.append(socket) | ||||||
|  |  | ||||||
|  |         rows = db.execute( | ||||||
|  |             "SELECT stat, value1, value2, value3, parameter FROM item_stat WHERE item_id = ?", | ||||||
|  |             (id,), | ||||||
|  |         ).fetchall() | ||||||
|  |         if len(rows) > 0: | ||||||
|  |             item.stats = [] | ||||||
|  |             for row in rows: | ||||||
|  |                 values = [] | ||||||
|  |                 for i in range(1, 4): | ||||||
|  |                     if row[f"value{i}"] is not None: | ||||||
|  |                         values.append(row[f"value{i}"]) | ||||||
|  |                 stat = Stat(id=row["stat"], values=values, parameter=row["parameter"]) | ||||||
|  |                 stat_data = lookup_stat(stat.id) | ||||||
|  |                 stat.text = stat_data["text"] | ||||||
|  |                 item.stats.append(stat) | ||||||
|  |  | ||||||
|  |         return item | ||||||
|  |  | ||||||
|  |  | ||||||
| def lookup_basetype(code: str) -> dict: | def lookup_basetype(code: str) -> dict: | ||||||
|     global _basetype_map |     global _basetype_map | ||||||
|   | |||||||
| @@ -27,7 +27,7 @@ from d2warehouse.item import ( | |||||||
|     lookup_stat, |     lookup_stat, | ||||||
| ) | ) | ||||||
| import d2warehouse.huffman as huffman | import d2warehouse.huffman as huffman | ||||||
| from d2warehouse.fileformat import STASH_TAB_MAGIC, ITEM_DATA_MAGIC | from d2warehouse.fileformat import STASH_TAB_MAGIC, STASH_TAB_VERSION, ITEM_DATA_MAGIC | ||||||
|  |  | ||||||
|  |  | ||||||
| class ParseError(RuntimeError): | class ParseError(RuntimeError): | ||||||
| @@ -82,7 +82,7 @@ def parse_stash_tab(data: bytes) -> tuple[bytes, StashTab]: | |||||||
|  |  | ||||||
|     if unknown != 1: |     if unknown != 1: | ||||||
|         ParseError("Unknown stash tab field is not 1") |         ParseError("Unknown stash tab field is not 1") | ||||||
|     if version != 99: |     if version != STASH_TAB_VERSION: | ||||||
|         ParseError(f"Unsupported stash tab version ({version} instead of 99)") |         ParseError(f"Unsupported stash tab version ({version} instead of 99)") | ||||||
|  |  | ||||||
|     tab = StashTab() |     tab = StashTab() | ||||||
| @@ -133,6 +133,7 @@ def parse_item(data: bytes) -> tuple[bytes, Item]: | |||||||
|     simple_byte_sz = int((sockets_end + 7) / 8) |     simple_byte_sz = int((sockets_end + 7) / 8) | ||||||
|     item = Item( |     item = Item( | ||||||
|         data[:simple_byte_sz], |         data[:simple_byte_sz], | ||||||
|  |         STASH_TAB_VERSION, | ||||||
|         is_identified, |         is_identified, | ||||||
|         is_socketed, |         is_socketed, | ||||||
|         is_beginner, |         is_beginner, | ||||||
|   | |||||||
							
								
								
									
										85
									
								
								d2warehouse/schema.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								d2warehouse/schema.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | |||||||
|  | DROP TABLE IF EXISTS item_stat; | ||||||
|  | DROP TABLE IF EXISTS item_affix; | ||||||
|  | DROP TABLE IF EXISTS item_extra; | ||||||
|  | DROP TABLE IF EXISTS item; | ||||||
|  |  | ||||||
|  | CREATE TABLE item ( | ||||||
|  |     id INTEGER PRIMARY KEY, | ||||||
|  |     created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||||||
|  |     deleted TIMESTAMP DEFAULT NULL, | ||||||
|  |     -- Nuked: if the item has been removed from storage & user indicated he does not | ||||||
|  |     --        want to count it as potentially in his possession any longer | ||||||
|  |     nuked BOOLEAN DEFAULT NULL, | ||||||
|  |     -- Names: simple items have only a name equal to their base name, most non simple | ||||||
|  |     --        items have two names: the base name and item_extra.item_name. | ||||||
|  |     itembase_name TEXT NOT NULL, | ||||||
|  |     socketed_into INTEGER DEFAULT NULL, | ||||||
|  |  | ||||||
|  |     -- The following fields match the fields of the item object in item.py | ||||||
|  |     raw_data BLOB NOT NULL, | ||||||
|  |     raw_version INTEGER NOT NULL, | ||||||
|  |     is_identified BOOLEAN NOT NULL, | ||||||
|  |     is_socketed BOOLEAN NOT NULL, | ||||||
|  |     is_beginner BOOLEAN NOT NULL, | ||||||
|  |     is_simple BOOLEAN NOT NULL, | ||||||
|  |     is_ethereal BOOLEAN NOT NULL, | ||||||
|  |     is_personalized BOOLEAN NOT NULL, | ||||||
|  |     is_runeword BOOLEAN NOT NULL, | ||||||
|  |     code TEXT NOT NULL, | ||||||
|  |  | ||||||
|  |     FOREIGN KEY (socketed_into) REFERENCES item (id) | ||||||
|  | ); | ||||||
|  | -- Add an index for "... WHERE deletion IS NULL" | ||||||
|  | CREATE INDEX item_deletion_partial ON item (deleted) WHERE deleted IS NULL; | ||||||
|  | -- * nuked: if the item has been removed from storage & user indicated he does not | ||||||
|  | --          want to count it as potentially in his possession any longer | ||||||
|  |  | ||||||
|  | CREATE TABLE item_extra ( | ||||||
|  |     item_id INTEGER PRIMARY KEY, | ||||||
|  |     item_name TEXT DEFAULT NULL, | ||||||
|  |     set_name TEXT DEFAULT NULL, | ||||||
|  |  | ||||||
|  |     -- The following fields match the fields of the item object in item.py | ||||||
|  |     uid INTEGER DEFAULT NULL, | ||||||
|  |     lvl INTEGER DEFAULT NULL, | ||||||
|  |     quality INTEGER DEFAULT NULL CHECK(1 <= quality AND quality <= 8), | ||||||
|  |     graphic INTEGER DEFAULT NULL, | ||||||
|  |     implicit INTEGER DEFAULT NULL, | ||||||
|  |     low_quality INTEGER DEFAULT NULL CHECK(0 <= low_quality AND low_quality <= 3), | ||||||
|  |     set_id INTEGER DEFAULT NULL, | ||||||
|  |     unique_id INTEGER DEFAULT NULL, | ||||||
|  |     nameword1 INTEGER DEFAULT NULL, | ||||||
|  |     nameword2 INTEGER DEFAULT NULL, | ||||||
|  |     runeword_id INTEGER DEFAULT NULL, | ||||||
|  |     personal_name TEXT DEFAULT NULL, | ||||||
|  |     defense INTEGER DEFAULT NULL, | ||||||
|  |     durability INTEGER DEFAULT NULL, | ||||||
|  |     max_durability INTEGER DEFAULT NULL, | ||||||
|  |     -- sockets: list[Optional["Item"]] | None = None => see item.socketed_into | ||||||
|  |     quantity INTEGER DEFAULT NULL, | ||||||
|  |     -- stats: list[Stat] | None = None => see table 'item_stat' | ||||||
|  |  | ||||||
|  |     FOREIGN KEY (item_id) REFERENCES item (id) | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | CREATE TABLE item_stat ( | ||||||
|  |     id INTEGER PRIMARY KEY, | ||||||
|  |     item_id INTEGER NOT NULL, | ||||||
|  |     stat INTEGER NOT NULL, | ||||||
|  |     value1 INTEGER DEFAULT NULL, | ||||||
|  |     value2 INTEGER DEFAULT NULL, | ||||||
|  |     value3 INTEGER DEFAULT NULL, | ||||||
|  |     parameter INTEGER DEFAULT NULL, | ||||||
|  |  | ||||||
|  |     FOREIGN KEY (item_id) REFERENCES item (id) | ||||||
|  | ); | ||||||
|  | CREATE INDEX item_stat_stat ON item_stat (stat); | ||||||
|  |  | ||||||
|  | CREATE TABLE item_affix ( | ||||||
|  |     id INTEGER PRIMARY KEY, | ||||||
|  |     item_id INTEGER NOT NULL, | ||||||
|  |     prefix BOOLEAN NOT NULL, | ||||||
|  |     affix_id INTEGER NOT NULL, | ||||||
|  |  | ||||||
|  |     FOREIGN KEY (item_id) REFERENCES item (id) | ||||||
|  | ); | ||||||
							
								
								
									
										31
									
								
								d2warehouse/tests/test_db.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								d2warehouse/tests/test_db.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | |||||||
|  | import os | ||||||
|  | import tempfile | ||||||
|  | import unittest | ||||||
|  |  | ||||||
|  | from d2warehouse.db import close_db, create_db, set_db_path | ||||||
|  | from d2warehouse.item import Item | ||||||
|  | from d2warehouse.parser import parse_item | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class DbTest(unittest.TestCase): | ||||||
|  |     @classmethod | ||||||
|  |     def setUpClass(cls) -> None: | ||||||
|  |         cls._fd, cls._path = tempfile.mkstemp() | ||||||
|  |         set_db_path(cls._path) | ||||||
|  |         create_db() | ||||||
|  |  | ||||||
|  |     @classmethod | ||||||
|  |     def tearDownClass(cls): | ||||||
|  |         close_db() | ||||||
|  |         os.close(cls._fd) | ||||||
|  |         os.unlink(cls._path) | ||||||
|  |  | ||||||
|  |     def test_runeword_lore(self): | ||||||
|  |         data = bytes.fromhex( | ||||||
|  |             "10088004050054c637f1073af4558697412981070881506049e87f005516fb134582ff1000a0003500e07cbb001000a0003504e07c9800" | ||||||
|  |         ) | ||||||
|  |         _, item = parse_item(data) | ||||||
|  |  | ||||||
|  |         db_id = item.write_to_db() | ||||||
|  |         loaded_item = Item.load_from_db(db_id) | ||||||
|  |         self.assertEqual(item, loaded_item) | ||||||
| @@ -44,7 +44,7 @@ d2test = "d2warehouse.test:main" | |||||||
| version = {attr = "d2warehouse.__version__"} | version = {attr = "d2warehouse.__version__"} | ||||||
|  |  | ||||||
| [tool.setuptools.package-data] | [tool.setuptools.package-data] | ||||||
| d2warehouse = ["data/*.json"] | d2warehouse = ["data/*.json", "schema.sql"] | ||||||
|  |  | ||||||
| [tool.pytest.ini_options] | [tool.pytest.ini_options] | ||||||
| addopts = "--cov --cov-report html --cov-report term" | addopts = "--cov --cov-report html --cov-report term" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user