rewrite
This commit is contained in:
parent
459fd182e5
commit
12a16bf19d
11 changed files with 255 additions and 209 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,4 +1,5 @@
|
||||||
*.bak
|
*.bak
|
||||||
*.dat
|
*.dat
|
||||||
*.dir
|
*.dir
|
||||||
*.pyc
|
*.pyc
|
||||||
|
*.db
|
69
analyze.py
69
analyze.py
|
@ -1,69 +0,0 @@
|
||||||
import argparse
|
|
||||||
from collections import Counter, defaultdict
|
|
||||||
import shelve
|
|
||||||
from typing import Counter, Dict, List, Optional
|
|
||||||
|
|
||||||
import simfile
|
|
||||||
|
|
||||||
from smoketest import SimfileError
|
|
||||||
|
|
||||||
|
|
||||||
def analyze(
|
|
||||||
simfile_errors: Dict[str, List[SimfileError]],
|
|
||||||
*,
|
|
||||||
traceback_substring: str,
|
|
||||||
verbose: bool,
|
|
||||||
number: Optional[int]
|
|
||||||
) -> None:
|
|
||||||
error_counter = Counter()
|
|
||||||
error_occurrences = defaultdict(list)
|
|
||||||
for filename, errors in simfile_errors.items():
|
|
||||||
for error in errors:
|
|
||||||
line = error.traceback.splitlines()[-1]
|
|
||||||
if not traceback_substring or traceback_substring in line:
|
|
||||||
error_counter[line] += 1
|
|
||||||
error_occurrences[line].append((filename, error.context))
|
|
||||||
for line, count in error_counter.most_common(number):
|
|
||||||
print(f'=== {line} ({count} occurrences) ===')
|
|
||||||
if verbose:
|
|
||||||
for filename, context in error_occurrences[line]:
|
|
||||||
print(f' * "{filename}" (during {repr(context)})')
|
|
||||||
print()
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
argparser = argparse.ArgumentParser()
|
|
||||||
argparser.add_argument(
|
|
||||||
'-s', '--shelf',
|
|
||||||
help='shelf file to use',
|
|
||||||
default=f'shelf-{simfile.__version__}',
|
|
||||||
)
|
|
||||||
argparser.add_argument(
|
|
||||||
'-t', '--traceback-substring',
|
|
||||||
help='filter tracebacks by substring',
|
|
||||||
)
|
|
||||||
argparser.add_argument(
|
|
||||||
'-n', '--number',
|
|
||||||
type=int,
|
|
||||||
default=10,
|
|
||||||
help='number of most common errors to print',
|
|
||||||
)
|
|
||||||
argparser.add_argument(
|
|
||||||
'-v', '--verbose',
|
|
||||||
help='enable verbose output',
|
|
||||||
default=False,
|
|
||||||
action='store_true',
|
|
||||||
)
|
|
||||||
args = argparser.parse_args()
|
|
||||||
|
|
||||||
with shelve.open(args.shelf) as simfile_errors:
|
|
||||||
analyze(
|
|
||||||
simfile_errors,
|
|
||||||
traceback_substring=args.traceback_substring,
|
|
||||||
verbose=args.verbose,
|
|
||||||
number=args.number,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
19
main.py
Normal file
19
main.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
from smoketest.args import SmoketestArgs
|
||||||
|
from smoketest.runner import SmoketestRun
|
||||||
|
from smoketest.storage import DB, MODELS
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = SmoketestArgs().parse_args()
|
||||||
|
|
||||||
|
DB.connect()
|
||||||
|
if args.init_db:
|
||||||
|
DB.create_tables(MODELS)
|
||||||
|
if args.songs_dir:
|
||||||
|
SmoketestRun(args).process_songs_dir(args.songs_dir)
|
||||||
|
elif args.pack_dir:
|
||||||
|
SmoketestRun(args).process_pack(args.pack_dir)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
32
poetry.lock
generated
32
poetry.lock
generated
|
@ -93,7 +93,7 @@ python-versions = ">=3.6"
|
||||||
name = "mypy-extensions"
|
name = "mypy-extensions"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
description = "Experimental type system extensions for programs checked with the mypy typechecker."
|
description = "Experimental type system extensions for programs checked with the mypy typechecker."
|
||||||
category = "dev"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
|
|
||||||
|
@ -161,6 +161,18 @@ category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typed-argument-parser"
|
||||||
|
version = "1.7.2"
|
||||||
|
description = "Typed Argument Parser"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
typing_extensions = ">=3.7.4"
|
||||||
|
typing-inspect = ">=0.7.1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typed-ast"
|
name = "typed-ast"
|
||||||
version = "1.5.4"
|
version = "1.5.4"
|
||||||
|
@ -173,10 +185,22 @@ python-versions = ">=3.6"
|
||||||
name = "typing-extensions"
|
name = "typing-extensions"
|
||||||
version = "4.3.0"
|
version = "4.3.0"
|
||||||
description = "Backported and Experimental Type Hints for Python 3.7+"
|
description = "Backported and Experimental Type Hints for Python 3.7+"
|
||||||
category = "dev"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typing-inspect"
|
||||||
|
version = "0.7.1"
|
||||||
|
description = "Runtime inspection utilities for typing module."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
mypy-extensions = ">=0.3.0"
|
||||||
|
typing-extensions = ">=3.7.4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zipp"
|
name = "zipp"
|
||||||
version = "3.8.1"
|
version = "3.8.1"
|
||||||
|
@ -192,7 +216,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "1.1"
|
lock-version = "1.1"
|
||||||
python-versions = "^3.7"
|
python-versions = "^3.7"
|
||||||
content-hash = "4fdaaca625a1242b7a0012eb7261349b6f25459688543e12b9d4c37eed4f8e3a"
|
content-hash = "86790b53efa0ea295271084a2e0fc85fd169fee4b848238c5ac8d7b71ded07d7"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
appdirs = []
|
appdirs = []
|
||||||
|
@ -210,6 +234,8 @@ semver = []
|
||||||
simfile = []
|
simfile = []
|
||||||
six = []
|
six = []
|
||||||
tomli = []
|
tomli = []
|
||||||
|
typed-argument-parser = []
|
||||||
typed-ast = []
|
typed-ast = []
|
||||||
typing-extensions = []
|
typing-extensions = []
|
||||||
|
typing-inspect = []
|
||||||
zipp = []
|
zipp = []
|
||||||
|
|
|
@ -10,6 +10,7 @@ python = "^3.7"
|
||||||
simfile = {version = "^2.1.0b3", allow-prereleases = true}
|
simfile = {version = "^2.1.0b3", allow-prereleases = true}
|
||||||
peewee = "^3.15.1"
|
peewee = "^3.15.1"
|
||||||
semver = "^2.13.0"
|
semver = "^2.13.0"
|
||||||
|
typed-argument-parser = "^1.7.2"
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
black = "^22.6.0"
|
black = "^22.6.0"
|
||||||
|
|
130
smoketest.py
130
smoketest.py
|
@ -1,130 +0,0 @@
|
||||||
import argparse
|
|
||||||
import os
|
|
||||||
import pprint
|
|
||||||
import traceback
|
|
||||||
from typing import Counter, Dict, List, Optional
|
|
||||||
|
|
||||||
from peewee import SqliteDatabase
|
|
||||||
import simfile
|
|
||||||
from simfile.notes import NoteData
|
|
||||||
from simfile.notes.group import group_notes
|
|
||||||
from simfile.notes.timed import time_notes
|
|
||||||
from simfile.ssc import SSCChart
|
|
||||||
from simfile.timing.engine import TimingData
|
|
||||||
from simfile.timing.displaybpm import displaybpm
|
|
||||||
|
|
||||||
from .storage import *
|
|
||||||
|
|
||||||
|
|
||||||
class SimfileError:
|
|
||||||
context: str
|
|
||||||
traceback: Optional[str]
|
|
||||||
|
|
||||||
def __init__(self, context: str):
|
|
||||||
self.context = context
|
|
||||||
self.traceback = traceback.format_exc()
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f'{self.context}: {self.traceback}'
|
|
||||||
|
|
||||||
def check_simfile(self, filename: str) -> List[SimfileError]:
|
|
||||||
errors = []
|
|
||||||
try:
|
|
||||||
sim = simfile.open(filename)
|
|
||||||
except Exception:
|
|
||||||
errors.append(SimfileError('parse'))
|
|
||||||
return errors
|
|
||||||
|
|
||||||
timing_data = None
|
|
||||||
try:
|
|
||||||
timing_data = TimingData.from_simfile(sim)
|
|
||||||
except Exception:
|
|
||||||
errors.append(SimfileError('timing'))
|
|
||||||
|
|
||||||
try:
|
|
||||||
displaybpm(sim)
|
|
||||||
except Exception:
|
|
||||||
errors.append(SimfileError('displaybpm'))
|
|
||||||
|
|
||||||
for c, chart in enumerate(sim.charts):
|
|
||||||
try:
|
|
||||||
note_data = NoteData.from_chart(chart)
|
|
||||||
except Exception:
|
|
||||||
errors.append(SimfileError(f'chart {c} note_data'))
|
|
||||||
try:
|
|
||||||
for _ in group_notes(note_data, join_heads_to_tails=True):
|
|
||||||
pass
|
|
||||||
except Exception:
|
|
||||||
errors.append(SimfileError(f'chart {c} group_notes'))
|
|
||||||
|
|
||||||
if isinstance(chart, SSCChart):
|
|
||||||
try:
|
|
||||||
displaybpm(sim, chart)
|
|
||||||
except Exception:
|
|
||||||
errors.append(SimfileError(f'chart {c} displaybpm'))
|
|
||||||
|
|
||||||
# skip the remaining chart tests if timing data parsing failed
|
|
||||||
if timing_data is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if isinstance(chart, SSCChart):
|
|
||||||
try:
|
|
||||||
timing_data = TimingData.from_simfile(sim, chart)
|
|
||||||
except Exception:
|
|
||||||
errors.append(SimfileError(f'chart {c} timing'))
|
|
||||||
|
|
||||||
try:
|
|
||||||
for _ in time_notes(note_data, timing_data):
|
|
||||||
pass
|
|
||||||
except Exception:
|
|
||||||
errors.append(SimfileError(f'chart {c} timed_notes'))
|
|
||||||
|
|
||||||
return errors
|
|
||||||
|
|
||||||
|
|
||||||
def scan_songs_dir(self, path: str):
|
|
||||||
stats = Counter()
|
|
||||||
for entry in os.scandir(path):
|
|
||||||
if simfile_errors.get(entry.path) == []:
|
|
||||||
stats['skipped'] += 1
|
|
||||||
continue
|
|
||||||
if entry.is_dir():
|
|
||||||
stats.update(scan_pack(entry.path))
|
|
||||||
elif any(entry.name.endswith(ext) for ext in ('.sm', '.ssc')):
|
|
||||||
errors = check_simfile(entry.path)
|
|
||||||
stats['checked'] += 1
|
|
||||||
simfile_errors[entry.path] = errors
|
|
||||||
if errors:
|
|
||||||
stats['error'] += 1
|
|
||||||
pprint.pprint({'path': entry.path, 'errors': errors})
|
|
||||||
else:
|
|
||||||
stats['success'] += 1
|
|
||||||
|
|
||||||
return stats
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def dir_path(string):
|
|
||||||
if os.path.isdir(string):
|
|
||||||
return string
|
|
||||||
else:
|
|
||||||
raise ValueError('dir_path: not a directory')
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
argparser = argparse.ArgumentParser()
|
|
||||||
argparser.add_argument(
|
|
||||||
'd',
|
|
||||||
'--songs-dir',
|
|
||||||
help='directory of packs to scan',
|
|
||||||
type=dir_path,
|
|
||||||
)
|
|
||||||
args = argparser.parse_args()
|
|
||||||
|
|
||||||
scan_songs_dir(args.songs_dir)
|
|
||||||
for k, v in stats.items():
|
|
||||||
print(k, v)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
1
smoketest/__init__.py
Normal file
1
smoketest/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
__version__ = "0.1.0"
|
24
smoketest/args.py
Normal file
24
smoketest/args.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import os
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from tap import Tap
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["SmoketestArgs"]
|
||||||
|
|
||||||
|
|
||||||
|
class SmoketestArgs(Tap):
|
||||||
|
songs_dir: Optional[str] = None
|
||||||
|
"""directory of packs to scan"""
|
||||||
|
|
||||||
|
pack_dir: Optional[str] = None
|
||||||
|
"""single pack directory to scan"""
|
||||||
|
|
||||||
|
new_only: bool = False
|
||||||
|
"""only scan newly discovered simfiles"""
|
||||||
|
|
||||||
|
error_only: bool = False
|
||||||
|
"""only scan simfiles that have previously errored"""
|
||||||
|
|
||||||
|
init_db: bool = False
|
||||||
|
"""initialize the database with tables"""
|
177
smoketest/runner.py
Normal file
177
smoketest/runner.py
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
from contextlib import contextmanager
|
||||||
|
import dataclasses
|
||||||
|
from datetime import datetime
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
from typing import Iterator, Optional
|
||||||
|
|
||||||
|
from . import __version__
|
||||||
|
import msdparser
|
||||||
|
import simfile
|
||||||
|
from simfile.dir import SimfilePack
|
||||||
|
from simfile.notes import NoteData
|
||||||
|
from simfile.notes.group import group_notes, SameBeatNotes
|
||||||
|
from simfile.notes.timed import time_notes
|
||||||
|
from simfile.ssc import SSCChart
|
||||||
|
from simfile.timing.engine import TimingData
|
||||||
|
from simfile.timing.displaybpm import displaybpm
|
||||||
|
from simfile.types import Simfile, Chart
|
||||||
|
|
||||||
|
from .args import SmoketestArgs
|
||||||
|
from .storage import *
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass(frozen=True)
|
||||||
|
class SimfileContext:
|
||||||
|
run: Run
|
||||||
|
path: str
|
||||||
|
kind: Optional[str] = None
|
||||||
|
simfile_title: Optional[str] = None
|
||||||
|
chart_stepstype: Optional[str] = None
|
||||||
|
chart_meter: Optional[str] = None
|
||||||
|
chart_index: Optional[int] = None
|
||||||
|
|
||||||
|
def report_error(self, action: str):
|
||||||
|
print(f"{self.path} ({self.kind}): {traceback.format_exc()}")
|
||||||
|
simfile_object, _ = SimfileObject.get_or_create(
|
||||||
|
path=self.path,
|
||||||
|
kind=self.kind,
|
||||||
|
simfile_title=self.simfile_title,
|
||||||
|
chart_stepstype=self.chart_stepstype,
|
||||||
|
chart_meter=self.chart_meter,
|
||||||
|
chart_index=self.chart_index,
|
||||||
|
)
|
||||||
|
SimfileError.create(
|
||||||
|
action=action,
|
||||||
|
traceback=traceback.format_exc(),
|
||||||
|
simfile_object=simfile_object,
|
||||||
|
run=self.run,
|
||||||
|
)
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def _update(self, **kwargs) -> Iterator["SimfileContext"]:
|
||||||
|
yield dataclasses.replace(self, **kwargs)
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def object(self, kind, **kwargs) -> Iterator["SimfileContext"]:
|
||||||
|
with self._update(kind=kind, **kwargs) as context:
|
||||||
|
SimfileObject.get_or_create(
|
||||||
|
kind=kind,
|
||||||
|
path=context.path,
|
||||||
|
simfile_title=context.simfile_title,
|
||||||
|
chart_stepstype=context.chart_stepstype,
|
||||||
|
chart_meter=context.chart_meter,
|
||||||
|
chart_index=context.chart_index,
|
||||||
|
)
|
||||||
|
yield context
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def perform(self, action: str, **kwargs) -> Iterator["SimfileContext"]:
|
||||||
|
with self._update(**kwargs) as context:
|
||||||
|
try:
|
||||||
|
yield context
|
||||||
|
except Exception:
|
||||||
|
context.report_error(action)
|
||||||
|
|
||||||
|
|
||||||
|
class SmoketestRun:
|
||||||
|
args: SmoketestArgs
|
||||||
|
run: Run
|
||||||
|
|
||||||
|
def __init__(self, args: SmoketestArgs):
|
||||||
|
self.args = args
|
||||||
|
self.run = Run.create(
|
||||||
|
created=datetime.now(),
|
||||||
|
simfile_version=simfile.__version__,
|
||||||
|
msdparser_version=msdparser.__version__,
|
||||||
|
smoketest_version=__version__,
|
||||||
|
)
|
||||||
|
|
||||||
|
def process_songs_dir(self, songs_dir: str):
|
||||||
|
for entry in os.scandir(songs_dir):
|
||||||
|
if entry.is_dir():
|
||||||
|
self.process_pack(entry.path)
|
||||||
|
|
||||||
|
def process_pack(self, pack_dir: str):
|
||||||
|
root_context = SimfileContext(run=self.run, path=pack_dir)
|
||||||
|
with root_context.object("SimfilePack", path=pack_dir) as context:
|
||||||
|
print(f"Processing pack {pack_dir}")
|
||||||
|
pack = SimfilePack(pack_dir)
|
||||||
|
|
||||||
|
with context.perform("simfile_dirs") as context:
|
||||||
|
for simfile_dir in pack.simfile_dirs():
|
||||||
|
if simfile_dir.sm_path:
|
||||||
|
self._open_simfile(context, simfile_dir.sm_path)
|
||||||
|
if simfile_dir.ssc_path:
|
||||||
|
self._open_simfile(context, simfile_dir.ssc_path)
|
||||||
|
|
||||||
|
def _open_simfile(self, context: SimfileContext, simfile_path: str):
|
||||||
|
if self.args.new_only:
|
||||||
|
if SimfileObject.get_or_none(SimfileObject.path == simfile_path):
|
||||||
|
return
|
||||||
|
elif self.args.error_only:
|
||||||
|
if not (
|
||||||
|
SimfileError.select()
|
||||||
|
.join(SimfileObject)
|
||||||
|
.where(SimfileObject.path == simfile_path) # type: ignore
|
||||||
|
.get_or_none()
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
with context.perform("simfile.open", path=simfile_path) as context:
|
||||||
|
sim = simfile.open(simfile_path)
|
||||||
|
|
||||||
|
with context.object(
|
||||||
|
kind=sim.__class__.__name__,
|
||||||
|
simfile_title=sim.title,
|
||||||
|
) as context:
|
||||||
|
self._test_simfile(context, sim)
|
||||||
|
|
||||||
|
def _test_simfile(self, context: SimfileContext, sim: Simfile):
|
||||||
|
with context.perform("TimingData") as context:
|
||||||
|
TimingData(sim)
|
||||||
|
|
||||||
|
with context.perform("displaybpm") as context:
|
||||||
|
displaybpm(sim)
|
||||||
|
|
||||||
|
with context.perform("charts") as context:
|
||||||
|
for c, chart_ in enumerate(sim.charts):
|
||||||
|
chart: Chart = chart_ # typing workaround
|
||||||
|
with context.object(
|
||||||
|
chart.__class__.__name__,
|
||||||
|
chart_stepstype=chart.stepstype,
|
||||||
|
chart_meter=chart.meter,
|
||||||
|
chart_index=c,
|
||||||
|
) as context:
|
||||||
|
self._test_chart(context, sim, chart)
|
||||||
|
|
||||||
|
def _test_chart(self, context: SimfileContext, sim: Simfile, chart: Chart):
|
||||||
|
td = None
|
||||||
|
if isinstance(chart, SSCChart):
|
||||||
|
with context.perform("TimingData+ssc") as context:
|
||||||
|
td = TimingData(sim, chart)
|
||||||
|
with context.perform("displaybpm+ssc") as context:
|
||||||
|
displaybpm(sim)
|
||||||
|
else:
|
||||||
|
# We've already performed this action. Don't double-report any error
|
||||||
|
try:
|
||||||
|
td = TimingData(sim)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
with context.perform("NoteData") as context:
|
||||||
|
nd = NoteData(chart)
|
||||||
|
|
||||||
|
with context.perform("group_notes") as context:
|
||||||
|
for _ in group_notes(
|
||||||
|
iter(nd),
|
||||||
|
join_heads_to_tails=True,
|
||||||
|
same_beat_notes=SameBeatNotes.JOIN_ALL,
|
||||||
|
):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if td:
|
||||||
|
with context.perform("time_notes") as context:
|
||||||
|
for _ in time_notes(nd, td):
|
||||||
|
pass
|
|
@ -9,10 +9,6 @@ class Run(Model):
|
||||||
simfile_version = CharField()
|
simfile_version = CharField()
|
||||||
msdparser_version = CharField()
|
msdparser_version = CharField()
|
||||||
smoketest_version = CharField()
|
smoketest_version = CharField()
|
||||||
success = IntegerField()
|
|
||||||
error = IntegerField()
|
|
||||||
checked = IntegerField()
|
|
||||||
skipped = IntegerField()
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
database = DB
|
database = DB
|
||||||
|
@ -31,7 +27,7 @@ class SimfileObject(Model):
|
||||||
|
|
||||||
|
|
||||||
class SimfileError(Model):
|
class SimfileError(Model):
|
||||||
context = CharField()
|
action = CharField()
|
||||||
traceback = CharField(max_length=10000)
|
traceback = CharField(max_length=10000)
|
||||||
simfile_object = ForeignKeyField(model=SimfileObject)
|
simfile_object = ForeignKeyField(model=SimfileObject)
|
||||||
run = ForeignKeyField(model=Run)
|
run = ForeignKeyField(model=Run)
|
||||||
|
@ -40,4 +36,4 @@ class SimfileError(Model):
|
||||||
database = DB
|
database = DB
|
||||||
|
|
||||||
|
|
||||||
DB.connect()
|
MODELS = [Run, SimfileObject, SimfileError]
|
Loading…
Reference in a new issue