aenir 0.0.post1.dev1365__tar.gz → 0.0.post1.dev1376__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/PKG-INFO +1 -1
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/requirements-dev.txt +14 -14
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/src/aenir/__init__.py +4 -1
- aenir-0.0.post1.dev1376/src/aenir/_archive/__init__.py +21 -0
- aenir-0.0.post1.dev1376/src/aenir/_archive/_base.py +52 -0
- aenir-0.0.post1.dev1376/src/aenir/_archive/_basemorph.py +345 -0
- aenir-0.0.post1.dev1376/src/aenir/_archive/cleaner.py +204 -0
- aenir-0.0.post1.dev1376/src/aenir/_archive/morph.py +729 -0
- aenir-0.0.post1.dev1376/src/aenir/_archive/scraper.py +63 -0
- aenir-0.0.post1.dev1376/src/aenir/_archive/shortcuts.py +26 -0
- aenir-0.0.post1.dev1376/src/aenir/_archive/transcriber.py +119 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/src/aenir/morph.py +175 -123
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/src/aenir.egg-info/PKG-INFO +1 -1
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/src/aenir.egg-info/SOURCES.txt +10 -0
- aenir-0.0.post1.dev1376/src/aenir.egg-info/scm_file_list.json +59 -0
- aenir-0.0.post1.dev1376/src/aenir.egg-info/scm_version.json +8 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/tests/test_morph.py +164 -58
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/.coveragerc +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/.gitignore +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/.readthedocs.yml +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/AUTHORS.md +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/CHANGELOG.md +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/CONTRIBUTING.md +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/LICENSE.txt +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/MANIFEST.in +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/README.md +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/TODO.md +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/docs/Makefile +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/docs/_static/.gitignore +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/docs/authors.md +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/docs/changelog.md +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/docs/conf.py +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/docs/contributing.md +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/docs/index.md +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/docs/license.md +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/docs/readme.md +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/docs/requirements.txt +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/logo.png +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/pyproject.toml +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/requirements.txt +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/setup.cfg +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/setup.py +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/src/aenir/.create_table_from_csv.py +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/src/aenir/_exceptions.py +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/src/aenir/_logging.py +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/src/aenir/games.py +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/src/aenir/skeleton.py +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/src/aenir/static/binding-blade/cleaned_stats.db +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/src/aenir/static/blazing-sword/cleaned_stats.db +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/src/aenir/static/genealogy-of-the-holy-war/cleaned_stats.db +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/src/aenir/static/path-of-radiance/cleaned_stats.db +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/src/aenir/static/the-sacred-stones/cleaned_stats.db +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/src/aenir/static/thracia-776/cleaned_stats.db +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/src/aenir/stats.py +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/src/aenir.egg-info/dependency_links.txt +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/src/aenir.egg-info/not-zip-safe +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/src/aenir.egg-info/requires.txt +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/src/aenir.egg-info/top_level.txt +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/tests/conftest.py +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/tests/test_games.py +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/tests/test_skeleton.py +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/tests/test_stats.py +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/tests/weapon.py +0 -0
- {aenir-0.0.post1.dev1365 → aenir-0.0.post1.dev1376}/tox.ini +0 -0
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
## The following requirements were added by pip freeze:
|
|
2
2
|
-e git+https://github.com/gchang12/aenir.git@a77487884c98365ea6dfad8be104b2087fe49755#egg=aenir
|
|
3
|
-
cachetools==7.1.
|
|
4
|
-
certifi==2026.
|
|
3
|
+
cachetools==7.1.4
|
|
4
|
+
certifi==2026.6.17
|
|
5
5
|
cffi==2.0.0
|
|
6
6
|
chardet==7.4.3
|
|
7
7
|
charset-normalizer==3.4.7
|
|
8
8
|
colorama==0.4.6
|
|
9
|
-
cryptography
|
|
10
|
-
distlib==0.4.
|
|
11
|
-
docutils==0.
|
|
12
|
-
filelock==3.29.
|
|
9
|
+
cryptography
|
|
10
|
+
distlib==0.4.3
|
|
11
|
+
docutils==0.23
|
|
12
|
+
filelock==3.29.4
|
|
13
13
|
id==1.6.1
|
|
14
|
-
idna==3.
|
|
14
|
+
idna==3.18
|
|
15
15
|
jaraco.classes==3.4.0
|
|
16
16
|
jaraco.context==6.1.2
|
|
17
17
|
jaraco.functools==4.5.0
|
|
@@ -19,23 +19,23 @@ jeepney==0.9.0
|
|
|
19
19
|
keyring==25.7.0
|
|
20
20
|
markdown-it-py==4.2.0
|
|
21
21
|
mdurl==0.1.2
|
|
22
|
-
more-itertools==11.0
|
|
22
|
+
more-itertools==11.1.0
|
|
23
23
|
nh3==0.3.5
|
|
24
24
|
packaging==26.2
|
|
25
|
-
platformdirs==4.
|
|
25
|
+
platformdirs==4.10.0
|
|
26
26
|
pluggy==1.6.0
|
|
27
27
|
pycparser==3.0
|
|
28
28
|
Pygments==2.20.0
|
|
29
|
-
pyproject-api==1.10.
|
|
30
|
-
python-discovery==1.
|
|
31
|
-
readme_renderer==
|
|
29
|
+
pyproject-api==1.10.1
|
|
30
|
+
python-discovery==1.4.2
|
|
31
|
+
readme_renderer==45.0
|
|
32
32
|
requests==2.34.2
|
|
33
33
|
requests-toolbelt==1.0.0
|
|
34
34
|
rfc3986==2.0.0
|
|
35
35
|
rich==15.0.0
|
|
36
36
|
SecretStorage==3.5.0
|
|
37
37
|
tomli_w==1.2.0
|
|
38
|
-
tox==4.
|
|
38
|
+
tox==4.55.1
|
|
39
39
|
twine==6.2.0
|
|
40
40
|
urllib3==2.7.0
|
|
41
|
-
virtualenv==21.
|
|
41
|
+
virtualenv==21.5.1
|
|
@@ -19,7 +19,10 @@ except PackageNotFoundError: # pragma: no cover
|
|
|
19
19
|
finally:
|
|
20
20
|
del version, PackageNotFoundError
|
|
21
21
|
|
|
22
|
-
from aenir.morph import
|
|
22
|
+
from aenir.morph import (
|
|
23
|
+
get_morph,
|
|
24
|
+
get_morph_class,
|
|
25
|
+
)
|
|
23
26
|
from aenir._exceptions import (
|
|
24
27
|
AenirError,
|
|
25
28
|
UnitNotFoundError,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
from aenir.morph import (
|
|
4
|
+
Morph4, Morph5, Morph6, Morph7, Morph8, Morph9
|
|
5
|
+
)
|
|
6
|
+
from aenir.shortcuts import get_morph
|
|
7
|
+
|
|
8
|
+
if sys.version_info[:2] >= (3, 8):
|
|
9
|
+
# TODO: Import directly (no need for conditional) when `python_requires = >= 3.8`
|
|
10
|
+
from importlib.metadata import PackageNotFoundError, version # pragma: no cover
|
|
11
|
+
else:
|
|
12
|
+
from importlib_metadata import PackageNotFoundError, version # pragma: no cover
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
# Change here if project is renamed and does not equal the package name
|
|
16
|
+
dist_name = __name__
|
|
17
|
+
__version__ = version(dist_name)
|
|
18
|
+
except PackageNotFoundError: # pragma: no cover
|
|
19
|
+
__version__ = "unknown"
|
|
20
|
+
finally:
|
|
21
|
+
del version, PackageNotFoundError
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
"""
|
|
3
|
+
Defines the SerenesBase class.
|
|
4
|
+
|
|
5
|
+
SerenesBase: Defines parameters for web-scraping.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
class SerenesBase:
|
|
9
|
+
"""
|
|
10
|
+
Defines parameters for web-scraping serenesforest.net.
|
|
11
|
+
|
|
12
|
+
URL_ROOT: URL of the website to be scraped.
|
|
13
|
+
NUM_TO_NAME: Index that looks up FE title by game number.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
_NUM_TO_NAME = {
|
|
17
|
+
4: "genealogy-of-the-holy-war",
|
|
18
|
+
5: "thracia-776",
|
|
19
|
+
6: "binding-blade",
|
|
20
|
+
7: "blazing-sword",
|
|
21
|
+
8: "the-sacred-stones",
|
|
22
|
+
9: "path-of-radiance",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
def __init__(self, game_num: int):
|
|
26
|
+
"""
|
|
27
|
+
Defines parameters that identify a FE game.
|
|
28
|
+
"""
|
|
29
|
+
self._game_num = game_num
|
|
30
|
+
#!will raise KeyError if not found
|
|
31
|
+
self._game_name = self.NUM_TO_NAME[self._game_num]
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def game_num(self):
|
|
35
|
+
"""
|
|
36
|
+
The number of a FE game in the series.
|
|
37
|
+
"""
|
|
38
|
+
return self._game_num
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def game_name(self):
|
|
42
|
+
"""
|
|
43
|
+
The name of a FE game, given the number.
|
|
44
|
+
"""
|
|
45
|
+
return self._game_name
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def NUM_TO_NAME(self):
|
|
49
|
+
"""
|
|
50
|
+
Index that looks up FE title by game number.
|
|
51
|
+
"""
|
|
52
|
+
return self._NUM_TO_NAME
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
"""
|
|
3
|
+
Defines the BaseMorph class.
|
|
4
|
+
|
|
5
|
+
BaseMorph: Defines methods for verifying manual input, and matching it to the proper sources.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# TODO: Create docstrings. Organize documentation better.
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
import io
|
|
12
|
+
from typing import Tuple, List
|
|
13
|
+
import json
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
import pandas as pd
|
|
17
|
+
#from aenir.cleaner import SerenesCleaner
|
|
18
|
+
|
|
19
|
+
class ProtoMorph:
|
|
20
|
+
"""
|
|
21
|
+
Defines parameters and methods for stat look-up and name verification.
|
|
22
|
+
|
|
23
|
+
current_clstype: Determines which table to match from.
|
|
24
|
+
current_cls: Value of the current class, to be matched.
|
|
25
|
+
target_stats: Stores the stats to be retrieved.
|
|
26
|
+
history: Stores the history of a unit as a list of (Class, Lv) tuples.
|
|
27
|
+
STAT_ORDERING: Stores the order of stat labels.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
DATADIR_ROOT = "data"
|
|
31
|
+
STAT_ORDERING = {
|
|
32
|
+
4: ["HP", "Str", "Mag", "Skl", "Spd", "Lck", "Def", "Res"],
|
|
33
|
+
5: ["HP", "Str", "Mag", "Skl", "Spd", "Lck", "Def", "Con", "Mov"],
|
|
34
|
+
6: ["HP", "Pow", "Skl", "Spd", "Lck", "Def", "Res"],
|
|
35
|
+
7: ["HP", "Pow", "Skl", "Spd", "Lck", "Def", "Res"],
|
|
36
|
+
8: ["HP", "Pow", "Skl", "Spd", "Lck", "Def", "Res"],
|
|
37
|
+
9: ["HP", "Str", "Mag", "Skl", "Spd", "Lck", "Def", "Res"],
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
NUM_TO_NAME = {
|
|
41
|
+
4: "genealogy-of-the-holy-war",
|
|
42
|
+
5: "thracia-776",
|
|
43
|
+
6: "binding-blade",
|
|
44
|
+
7: "blazing-sword",
|
|
45
|
+
8: "the-sacred-stones",
|
|
46
|
+
9: "path-of-radiance",
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
NUM_TO_FULLNAME = {
|
|
50
|
+
4: "Genealogy of the Holy War",
|
|
51
|
+
5: "Thracia 776",
|
|
52
|
+
6: "Sword of Seals",
|
|
53
|
+
7: "Blazing Sword",
|
|
54
|
+
8: "The Sacred Stones",
|
|
55
|
+
9: "Path of Radiance",
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
page_dict = {
|
|
59
|
+
"characters/base-stats": "characters__base_stats",
|
|
60
|
+
"characters/growth-rates": "characters__growth_rates",
|
|
61
|
+
"classes/maximum-stats": "classes__maximum_stats",
|
|
62
|
+
"classes/promotion-gains": "classes__promotion_gains",
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
tables_file = "cleaned_stats.db"
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def game_num(self):
|
|
69
|
+
return self._game_num
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def game_name(self):
|
|
73
|
+
return self._game_name
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def history(self):
|
|
77
|
+
return self._history
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def comparison_labels(self):
|
|
81
|
+
return self._comparison_labels
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def home_dir(self):
|
|
85
|
+
return self._home_dir
|
|
86
|
+
|
|
87
|
+
def __init__(self, game_num: int):
|
|
88
|
+
"""
|
|
89
|
+
Defines:
|
|
90
|
+
current_clstype - For use in cross-referencing names in tables.
|
|
91
|
+
current_cls - Stores current class
|
|
92
|
+
target_stats - Temporary store for stat-augmenting arrays
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
""" SerenesCleaner
|
|
96
|
+
self.fieldrecon_file = "fieldrecon.json" # not needed
|
|
97
|
+
self.clsrecon_list = [
|
|
98
|
+
(("characters/base-stats", "Name", "Name"), ("characters/growth-rates", "Name")),
|
|
99
|
+
(("characters/base-stats", "Name", "Class"), ("classes/maximum-stats", "Class")),
|
|
100
|
+
(("characters/base-stats", "Name", "Class"), ("classes/promotion-gains", "Class")),
|
|
101
|
+
(("classes/promotion-gains", "Promotion", "Promotion"), ("classes/maximum-stats", "Class")),
|
|
102
|
+
(("classes/promotion-gains", "Promotion", "Promotion"), ("classes/promotion-gains", "Class")),
|
|
103
|
+
] # not needed
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
""" SerenesTranscriber
|
|
107
|
+
# To be edited never
|
|
108
|
+
self.tables_file = "cleaned_stats.db"
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
# To be edited only by program
|
|
112
|
+
self.url_to_tables = {}
|
|
113
|
+
""" SerenesScraper
|
|
114
|
+
_URL_ROOT = "https://serenesforest.net" # not needed
|
|
115
|
+
self.URL_ROOT = "https://serenesforest.net" # not needed
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
""" SerenesBase
|
|
119
|
+
self._NUM_TO_NAME = { # not needed
|
|
120
|
+
self.NUM_TO_NAME = {
|
|
121
|
+
4: "genealogy-of-the-holy-war",
|
|
122
|
+
5: "thracia-776",
|
|
123
|
+
6: "binding-blade",
|
|
124
|
+
7: "blazing-sword",
|
|
125
|
+
8: "the-sacred-stones",
|
|
126
|
+
9: "path-of-radiance",
|
|
127
|
+
} # not needed
|
|
128
|
+
"""
|
|
129
|
+
# To be edited never
|
|
130
|
+
self._game_num = game_num
|
|
131
|
+
# To be edited never
|
|
132
|
+
self._game_name = self.NUM_TO_NAME[game_num]
|
|
133
|
+
# essential to set_targetstats method
|
|
134
|
+
# TODO: To be edited only by program. Is set by `set_targetstats` method.
|
|
135
|
+
# How to restrict permissions on this field so that it can only be modified by `set_targetstats`?
|
|
136
|
+
# Perhaps a pd-Series equivalent of list.append?
|
|
137
|
+
self.target_stats = None
|
|
138
|
+
# Can be edited by user.
|
|
139
|
+
self.current_stats = None
|
|
140
|
+
# To be edited only by program
|
|
141
|
+
# history (class, lv) tuples, and labels for comparison DataFrame
|
|
142
|
+
self._history = []
|
|
143
|
+
# To be edited only by program
|
|
144
|
+
self._comparison_labels = {}
|
|
145
|
+
# load tables
|
|
146
|
+
# To be edited never
|
|
147
|
+
self._home_dir = Path(self.DATADIR_ROOT).joinpath(self.game_name)
|
|
148
|
+
# To be edited by user. Turned into class attribute.
|
|
149
|
+
#self.tables_file = "cleaned_stats.db"
|
|
150
|
+
|
|
151
|
+
def load_tables(self, urlpath: str):
|
|
152
|
+
"""
|
|
153
|
+
Loads the table-list into url_to_tables[urlpath] from home_dir/tables_file.
|
|
154
|
+
|
|
155
|
+
Raises:
|
|
156
|
+
- FileNotFoundError: tables_file does not exist.
|
|
157
|
+
- KeyError: urlpath is not registered in page_dict.
|
|
158
|
+
"""
|
|
159
|
+
logging.info("SerenesTranscriber.load_tables(self, '%s')", urlpath)
|
|
160
|
+
load_path = self.home_dir.joinpath(self.tables_file)
|
|
161
|
+
if not load_path.exists():
|
|
162
|
+
raise FileNotFoundError(f"'{str(load_path)}' does not exist. Aborting.")
|
|
163
|
+
load_file = str(load_path)
|
|
164
|
+
tablename_root = self.page_dict[urlpath]
|
|
165
|
+
logging.info("SerenesTranscriber.url_to_tables['%s'] = []", urlpath)
|
|
166
|
+
self.url_to_tables[urlpath] = []
|
|
167
|
+
tableindex = 0
|
|
168
|
+
logging.info("Loading tables into SerenesTranscriber.url_to_tables['%s'].", urlpath)
|
|
169
|
+
while True:
|
|
170
|
+
table_name = tablename_root + str(tableindex)
|
|
171
|
+
con = "sqlite:///" + load_file
|
|
172
|
+
try:
|
|
173
|
+
logging.info("pd.read_sql_table('%s', '%s')", table_name, con)
|
|
174
|
+
table = pd.read_sql_table(table_name, con)
|
|
175
|
+
tableindex += 1
|
|
176
|
+
logging.info("SerenesTranscriber.url_to_tables['%s'].append(tables[%d])", urlpath, tableindex-1)
|
|
177
|
+
self.url_to_tables[urlpath].append(table)
|
|
178
|
+
except ValueError:
|
|
179
|
+
logging.info("%d table(s) have been loaded into SerenesTranscriber.url_to_tables['%s']", tableindex, urlpath)
|
|
180
|
+
break
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class BaseMorph(ProtoMorph):
|
|
184
|
+
"""
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
url_to_tables = {}
|
|
188
|
+
|
|
189
|
+
def __init__(self, game_num: int):
|
|
190
|
+
ProtoMorph.__init__(self, game_num)
|
|
191
|
+
# initialize instance attribute `url_to_tables`
|
|
192
|
+
del self.url_to_tables
|
|
193
|
+
# delete instance attribute `url_to_tables`, and allow class attribute `url_to_tables` to occupy namespace
|
|
194
|
+
#if not self.url_to_tables:
|
|
195
|
+
for urlpath in self.page_dict:
|
|
196
|
+
# TODO: Find out why this generates an error
|
|
197
|
+
#if urlpath in self.url_to_tables: continue
|
|
198
|
+
"""
|
|
199
|
+
FAILED tests/integration/test_morph__integration.py::Morph6IntegrationTest::test_fe8_units - KeyError: 'Non-promoted (foot)'
|
|
200
|
+
FAILED tests/integration/test_morph__integration.py::Morph6IntegrationTest::test_fe9_units - KeyError: 'Non-promoted physical'
|
|
201
|
+
FAILED tests/integration/test_morph__integration.py::Morph6IntegrationTest::test_villager_amelia - KeyError: 'Non-promoted (foot)'
|
|
202
|
+
FAILED tests/integration/test_morph__integration.py::Morph6IntegrationTest::test_villager_ewan - KeyError: 'Non-promoted (foot)'
|
|
203
|
+
FAILED tests/integration/test_morph__integration.py::Morph6IntegrationTest::test_villager_ross - KeyError: 'Non-promoted (foot)'
|
|
204
|
+
FAILED tests/integration/test_morph__integration.py::Morph7IntegrationTest::test_all_units - KeyError: 'Wallace'
|
|
205
|
+
FAILED tests/integration/test_morph__integration.py::Morph7IntegrationTest::test_main_wallace - KeyError: 'Wallace'
|
|
206
|
+
FAILED tests/integration/test_morph__integration.py::Morph7IntegrationTest::test_tutorial_wallace - KeyError: 'Wallace'
|
|
207
|
+
FAILED tests/integration/test_morph__integration.py::Morph9IntegrationTest::test_all_units - KeyError: 'Non-promoted physical'
|
|
208
|
+
FAILED tests/unit/test__basemorph.py::BaseMorphTest::test_get_character_list - AssertionError: False is not true
|
|
209
|
+
FAILED tests/unit/test__basemorph.py::BaseMorphTest::test_set_targetstats - KeyError: 'Fir'
|
|
210
|
+
FAILED tests/unit/test_morph.py::Morph6Test::test_level_up - KeyError: 'Roy'
|
|
211
|
+
FAILED tests/unit/test_morph.py::Morph7Test::test__lt__ - KeyError: 'Wallace'
|
|
212
|
+
FAILED tests/unit/test_morph.py::Morph7Test::test__lt__HM - KeyError: 'Wallace'
|
|
213
|
+
FAILED tests/unit/test_morph.py::Morph7Test::test__repr__ - KeyError: 'Wallace'
|
|
214
|
+
FAILED tests/unit/test_morph.py::Morph7Test::test_get_repr_series__comparison_labels - KeyError: 'Wallace'
|
|
215
|
+
"""
|
|
216
|
+
self.load_tables(urlpath)
|
|
217
|
+
|
|
218
|
+
def verify_clsrecon_file(self, ltable_args: Tuple[str, str, str], rtable_args: Tuple[str, str]):
|
|
219
|
+
"""
|
|
220
|
+
Prints: clsrecon_dict.keys not in ltable[lindex_col], clsrecon_dict.values not in rtable[to_col].
|
|
221
|
+
|
|
222
|
+
Note: In order for this method to work, logging.level must be set to logging.INFO.
|
|
223
|
+
Find cls-recon list in unittest.TestCase subclass for BaseMorph.
|
|
224
|
+
"""
|
|
225
|
+
logging.info("BaseMorph.verify_clsrecon_file(self, %s, %s)", ltable_args, rtable_args)
|
|
226
|
+
# unpack arguments
|
|
227
|
+
ltable_url, lindex_col, from_col = ltable_args
|
|
228
|
+
rtable_url, to_col = rtable_args
|
|
229
|
+
# load clsrecon_dict
|
|
230
|
+
ltable_name = self.page_dict[ltable_url]
|
|
231
|
+
rtable_name = self.page_dict[rtable_url]
|
|
232
|
+
json_path = self.home_dir.joinpath(f"{ltable_name}-JOIN-{rtable_name}.json")
|
|
233
|
+
with io.open(str(json_path), encoding='utf-8') as rfile:
|
|
234
|
+
clsrecon_dict = json.load(rfile)
|
|
235
|
+
# compile ltable pkey names
|
|
236
|
+
ltable_nameset = set()
|
|
237
|
+
for table in self.url_to_tables[ltable_url]:
|
|
238
|
+
ltable_nameset.update(set(table.loc[:, lindex_col]))
|
|
239
|
+
# compile rtable values
|
|
240
|
+
rtable_nameset = set()
|
|
241
|
+
for table in self.url_to_tables[rtable_url]:
|
|
242
|
+
rtable_nameset.update(set(table.loc[:, to_col]))
|
|
243
|
+
# check1: clsrecon_dict.keys subset of ltable_nameset
|
|
244
|
+
check1 = set(clsrecon_dict) - ltable_nameset
|
|
245
|
+
logging.info("%d key(s) in '%s', but not in '%s.%s': %s", len(check1), str(json_path), ltable_name, lindex_col, check1)
|
|
246
|
+
# check2: clsrecon_dict.values subset of rtable_nameset
|
|
247
|
+
check2 = set(clsrecon_dict.values()) - {None} - rtable_nameset
|
|
248
|
+
logging.info("%d value(s) in '%s', but not in '%s.%s': %s", len(check2), str(json_path), rtable_name, to_col, check2)
|
|
249
|
+
|
|
250
|
+
def verify_maximum_stats(self):
|
|
251
|
+
"""
|
|
252
|
+
Prints columns that are in 'classes/maximum-stats' but not in 'characters/base-stats'.
|
|
253
|
+
|
|
254
|
+
Note: In order for this method to work, logging.level must be set to logging.INFO.
|
|
255
|
+
"""
|
|
256
|
+
logging.info("BaseMorph.verify_maximum_stats(self)")
|
|
257
|
+
bases_name = "characters/base-stats"
|
|
258
|
+
maxes_name = "classes/maximum-stats"
|
|
259
|
+
bases = set(self.url_to_tables[bases_name][0].columns) - {"Name", "Class", "Lv"}
|
|
260
|
+
maxes = set(self.url_to_tables[maxes_name][0].columns) - {"Class"}
|
|
261
|
+
diff = maxes - bases
|
|
262
|
+
logging.info("%d names in '%s' but not in '%s': %s", len(diff), maxes_name, bases_name, diff)
|
|
263
|
+
|
|
264
|
+
def set_targetstats(self, ltable_args: Tuple[str, str], rtable_args: Tuple[str, str], tableindex: int):
|
|
265
|
+
"""
|
|
266
|
+
Sets BaseMorph.target_stats to the pd.[DataFrame|Series] per clsrecon_dict.
|
|
267
|
+
|
|
268
|
+
ltable_url, lindex_col = ltable_args
|
|
269
|
+
rtable_url, to_col = rtable_args
|
|
270
|
+
lindex_col |-(clsrecon_dict)-> from_col === to_col
|
|
271
|
+
"""
|
|
272
|
+
logging.info("BaseMorph.set_targetstats(self, %s, %s)", ltable_args, rtable_args)
|
|
273
|
+
# unpack arguments
|
|
274
|
+
ltable_url, lindex_val = ltable_args
|
|
275
|
+
rtable_url, to_col = rtable_args
|
|
276
|
+
# load clsrecon_dict from json
|
|
277
|
+
ltable_name = self.page_dict[ltable_url]
|
|
278
|
+
rtable_name = self.page_dict[rtable_url]
|
|
279
|
+
json_path = self.home_dir.joinpath(f"{ltable_name}-JOIN-{rtable_name}.json")
|
|
280
|
+
with open(str(json_path), encoding='utf-8') as rfile:
|
|
281
|
+
clsrecon_dict = json.load(rfile)
|
|
282
|
+
from_col = clsrecon_dict[lindex_val]
|
|
283
|
+
if from_col is None:
|
|
284
|
+
self.target_stats = None
|
|
285
|
+
else:
|
|
286
|
+
self.target_stats = self.url_to_tables[rtable_url][tableindex].set_index(to_col).loc[from_col, :]
|
|
287
|
+
|
|
288
|
+
def get_lynmode_options(self) -> List[str]:
|
|
289
|
+
"""
|
|
290
|
+
Returns a List[str] of possible pseudo-booleans for a member of the Lyndis League.
|
|
291
|
+
"""
|
|
292
|
+
lyn_options = [False, True]
|
|
293
|
+
return [str(option) for option in lyn_options]
|
|
294
|
+
|
|
295
|
+
def get_hardmode_options(self) -> List[str]:
|
|
296
|
+
"""
|
|
297
|
+
Returns a List[str] of possible pseudo-booleans for a character with Hard Mode bonuses.
|
|
298
|
+
"""
|
|
299
|
+
hm_options = [False, True]
|
|
300
|
+
return [str(option) for option in hm_options]
|
|
301
|
+
|
|
302
|
+
def get_character_list(self) -> List[str]:
|
|
303
|
+
"""
|
|
304
|
+
Returns a List[str] of base!character names mapped to growths!character names.
|
|
305
|
+
|
|
306
|
+
Raises:
|
|
307
|
+
- FileNotFoundError: not home_dir.joinpath("characters__base_stats-JOIN-characters__growth_rates.json").exists()
|
|
308
|
+
- json.decoder.JSONDecodeError: File is not in JSON form.
|
|
309
|
+
"""
|
|
310
|
+
logging.info("BaseMorph.get_character_list(self)")
|
|
311
|
+
ltable_url = "characters/base-stats"
|
|
312
|
+
rtable_url = "characters/growth-rates"
|
|
313
|
+
ltable_name = self.page_dict[ltable_url]
|
|
314
|
+
rtable_name = self.page_dict[rtable_url]
|
|
315
|
+
json_path = self.home_dir.joinpath(f"{ltable_name}-JOIN-{rtable_name}.json")
|
|
316
|
+
# query character list from JSON (FileNotFoundError may be raised before that)
|
|
317
|
+
with io.open(str(json_path), encoding='utf-8') as rfile:
|
|
318
|
+
clsrecon_dict = json.load(rfile)
|
|
319
|
+
chrlist = []
|
|
320
|
+
# filter to include non-None entries
|
|
321
|
+
for chrname, growth_equivalent in clsrecon_dict.items():
|
|
322
|
+
if growth_equivalent is None:
|
|
323
|
+
continue
|
|
324
|
+
chrlist.append(chrname)
|
|
325
|
+
# return keys mapped to non-None
|
|
326
|
+
logging.info("%d names found: %s", len(chrlist), chrlist)
|
|
327
|
+
return chrlist
|
|
328
|
+
|
|
329
|
+
def get_fe4_unit_list(self, unit_type: str) -> List[str]:
|
|
330
|
+
"""
|
|
331
|
+
Shortcut function to get a list of either FE4 kids or fathers.
|
|
332
|
+
"""
|
|
333
|
+
assert self.game_num == 4
|
|
334
|
+
tgt_colname = {
|
|
335
|
+
"kid": "Name",
|
|
336
|
+
"father": "Father",
|
|
337
|
+
}[unit_type]
|
|
338
|
+
tablename = "characters/growth-rates"
|
|
339
|
+
src_tablelist = self.url_to_tables[tablename]
|
|
340
|
+
src_table = src_tablelist[1]
|
|
341
|
+
src_column = src_table[tgt_colname].copy()
|
|
342
|
+
src_column.drop_duplicates(inplace=True)
|
|
343
|
+
unit_list = src_column.to_list()
|
|
344
|
+
return unit_list
|
|
345
|
+
|