mirror of
https://github.com/evennia/evennia.git
synced 2026-03-21 15:26:30 +01:00
Fix spawn issues in xyzgrid. Allow prototype_parent to be a dict itself. Resolve #2494.
This commit is contained in:
parent
fc323e1ca7
commit
ddaf22ea58
12 changed files with 207 additions and 97 deletions
|
|
@ -84,6 +84,8 @@ Up requirements to Django 3.2+
|
|||
on-object Scripts. Moved `CmdScripts` and `CmdObjects` to `commands/default/building.py`.
|
||||
- Keep GMCP function case if outputfunc starts with capital letter (so `cmd_name` -> `Cmd.Name`
|
||||
but `Cmd_nAmE` -> `Cmd.nAmE`). This helps e.g Mudlet's legacy `Client_GUI` implementation)
|
||||
- Prototypes now allow setting `prototype_parent` directly to a prototype-dict.
|
||||
This makes it easier when dynamically building in-module prototypes.
|
||||
|
||||
### Evennia 0.9.5 (2019-2020)
|
||||
|
||||
|
|
|
|||
|
|
@ -56,6 +56,12 @@ Exits: northeast and east
|
|||
command line. It will also make the `xyz_room` and `xyz_exit` prototypes
|
||||
available for use as prototype-parents when spawning the grid.
|
||||
3. Run `evennia xyzgrid help` for available options.
|
||||
4. (Optional): By default, the xyzgrid will only spawn module-based
|
||||
[prototypes](Prototypes). This is an optimization and usually makes sense
|
||||
since the grid is entirely defined outside the game anyway. If you want to
|
||||
also make use of in-game (db-) created prototypes, add
|
||||
`XYZGRID_USE_DB_PROTOTYPES = True` to settings.
|
||||
|
||||
|
||||
## Overview
|
||||
|
||||
|
|
@ -1002,8 +1008,8 @@ should be included as `prototype_parents` for prototypes on the map. Would it
|
|||
not be nice to be able to change these and have the change apply to all of the
|
||||
grid? You can, by adding the following to your `mygame/server/conf/settings.py`:
|
||||
|
||||
XYZROOM_PARENT_PROTOTYPE_OVERRIDE = {"typeclass": "myxyzroom.MyXYZRoom"}
|
||||
XYZEXIT_PARENT_PROTOTYPE_OVERRIDE = {...}
|
||||
XYZROOM_PROTOTYPE_OVERRIDE = {"typeclass": "myxyzroom.MyXYZRoom"}
|
||||
XYZEXIT_PROTOTYPE_OVERRIDE = {...}
|
||||
|
||||
|
||||
> If you override the typeclass in your prototypes, the typeclass used **MUST**
|
||||
|
|
|
|||
|
|
@ -23,16 +23,24 @@ from evennia.contrib.xyzgrid import xymap_legend
|
|||
# the typeclass inherits from the XYZRoom (or XYZExit)
|
||||
# if adding the evennia.contrib.xyzgrid.prototypes to
|
||||
# settings.PROTOTYPE_MODULES, one could just set the
|
||||
# prototype_parent to 'xyz_room' and 'xyz_exit' respectively
|
||||
# prototype_parent to 'xyz_room' and 'xyz_exit' here
|
||||
# instead.
|
||||
|
||||
PARENT = {
|
||||
ROOM_PARENT = {
|
||||
"key": "An empty room",
|
||||
"prototype_key": "xyzmap_room_map1",
|
||||
"typeclass": "evennia.contrib.xyzgrid.xyzroom.XYZRoom",
|
||||
"prototype_key": "xyz_exit_prototype",
|
||||
"prototype_parent": "xyz_room",
|
||||
# "typeclass": "evennia.contrib.xyzgrid.xyzroom.XYZRoom",
|
||||
"desc": "An empty room.",
|
||||
}
|
||||
|
||||
EXIT_PARENT = {
|
||||
"prototype_key": "xyz_exit_prototype",
|
||||
"prototype_parent": "xyz_exit",
|
||||
# "typeclass": "evennia.contrib.xyzgrid.xyzroom.XYZExit",
|
||||
"desc": "A path to the next location.",
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------- map1
|
||||
# The large tree
|
||||
|
|
@ -134,13 +142,17 @@ PROTOTYPES_MAP1 = {
|
|||
"desc": "These branches are wide enough to easily walk on. There's green all around."
|
||||
},
|
||||
# directional prototypes
|
||||
(3, 0, 'w'): {
|
||||
(3, 0, 'e'): {
|
||||
"desc": "A dark passage into the underworld."
|
||||
},
|
||||
}
|
||||
|
||||
for prot in PROTOTYPES_MAP1.values():
|
||||
prot['prototype_parent'] = PARENT
|
||||
for key, prot in PROTOTYPES_MAP1.items():
|
||||
if len(key) == 2:
|
||||
# we don't want to give exits the room typeclass!
|
||||
prot['prototype_parent'] = ROOM_PARENT
|
||||
else:
|
||||
prot['prototype_parent'] = EXIT_PARENT
|
||||
|
||||
|
||||
XYMAP_DATA_MAP1 = {
|
||||
|
|
@ -253,8 +265,12 @@ PROTOTYPES_MAP2 = {
|
|||
|
||||
# this is required by the prototypes, but we add it all at once so we don't
|
||||
# need to add it to every line above
|
||||
for prot in PROTOTYPES_MAP2.values():
|
||||
prot['prototype_parent'] = PARENT
|
||||
for key, prot in PROTOTYPES_MAP2.items():
|
||||
if len(key) == 2:
|
||||
# we don't want to give exits the room typeclass!
|
||||
prot['prototype_parent'] = ROOM_PARENT
|
||||
else:
|
||||
prot['prototype_parent'] = EXIT_PARENT
|
||||
|
||||
|
||||
XYMAP_DATA_MAP2 = {
|
||||
|
|
|
|||
|
|
@ -18,9 +18,11 @@ Use `evennia xyzgrid help` for usage help.
|
|||
|
||||
from os.path import join as pathjoin
|
||||
from django.conf import settings
|
||||
import evennia
|
||||
from evennia.utils import ansi
|
||||
from evennia.contrib.xyzgrid.xyzgrid import get_xyzgrid
|
||||
|
||||
|
||||
_HELP_SHORT = """
|
||||
evennia xyzgrid help | list | init | add | spawn | initpath | delete [<options>]
|
||||
Manages the XYZ grid. Use 'xyzgrid help <option>' for documentation.
|
||||
|
|
@ -161,6 +163,8 @@ _TOPICS_MAP = {
|
|||
"delete": _HELP_DELETE
|
||||
}
|
||||
|
||||
evennia._init()
|
||||
|
||||
def _option_help(*suboptions):
|
||||
"""
|
||||
Show help <command> aid.
|
||||
|
|
|
|||
|
|
@ -1057,19 +1057,21 @@ class TestMapStressTest(TestCase):
|
|||
return f"{edge}\n{(l1 + l2) * Ysize}{l1}\n\n{edge}"
|
||||
|
||||
@parameterized.expand([
|
||||
((10, 10), 0.01),
|
||||
((100, 100), 1),
|
||||
((10, 10), 0.03),
|
||||
((100, 100), 5),
|
||||
])
|
||||
def test_grid_creation(self, gridsize, max_time):
|
||||
"""
|
||||
Test of grid-creataion performance for Nx, Ny grid.
|
||||
|
||||
"""
|
||||
# import cProfile
|
||||
Xmax, Ymax = gridsize
|
||||
grid = self._get_grid(Xmax, Ymax)
|
||||
t0 = time()
|
||||
mapobj = xymap.XYMap({'map': grid}, Z="testmap")
|
||||
t0 = time()
|
||||
mapobj.parse()
|
||||
# cProfile.runctx('mapobj.parse()', globals(), locals())
|
||||
t1 = time()
|
||||
self.assertLess(t1 - t0, max_time, f"Map creation of ({Xmax}x{Ymax}) grid slower "
|
||||
f"than expected {max_time}s.")
|
||||
|
|
|
|||
|
|
@ -109,10 +109,15 @@ from django.conf import settings
|
|||
from evennia.utils.utils import variable_from_module, mod_import, is_iter
|
||||
from evennia.utils import logger
|
||||
from evennia.prototypes import prototypes as protlib
|
||||
from evennia.prototypes.spawner import flatten_prototype
|
||||
|
||||
from .utils import MapError, MapParserError, BIGVAL
|
||||
from . import xymap_legend
|
||||
|
||||
_NO_DB_PROTOTYPES = True
|
||||
if hasattr(settings, "XYZGRID_USE_DB_PROTOTYPES"):
|
||||
_NO_DB_PROTOTYPES = not settings.XYZGRID_USE_DB_PROTOTYPES
|
||||
|
||||
_CACHE_DIR = settings.CACHE_DIR
|
||||
_LOADED_PROTOTYPES = None
|
||||
_XYZROOMCLASS = None
|
||||
|
|
@ -351,8 +356,9 @@ class XYMap:
|
|||
if not prototype or isinstance(prototype, dict):
|
||||
# nothing more to do
|
||||
continue
|
||||
# we need to load the prototype dict onto each for ease of access
|
||||
proto = protlib.search_prototype(prototype, require_single=True)[0]
|
||||
# we need to load the prototype dict onto each for ease of access. Note that
|
||||
proto = protlib.search_prototype(prototype, require_single=True,
|
||||
no_db=_NO_DB_PROTOTYPES)[0]
|
||||
node_or_link_class.prototype = proto
|
||||
|
||||
def parse(self):
|
||||
|
|
@ -492,13 +498,24 @@ class XYMap:
|
|||
if node.prototype:
|
||||
node_coord = (node.X, node.Y)
|
||||
# load prototype from override, or use default
|
||||
node.prototype = self.prototypes.get(
|
||||
node_coord, self.prototypes.get(('*', '*'), node.prototype))
|
||||
try:
|
||||
node.prototype = flatten_prototype(self.prototypes.get(
|
||||
node_coord,
|
||||
self.prototypes.get(('*', '*'), node.prototype)),
|
||||
no_db=_NO_DB_PROTOTYPES
|
||||
)
|
||||
except Exception as err:
|
||||
raise MapParserError(f"Room prototype malformed: {err}", node)
|
||||
# do the same for links (x, y, direction) coords
|
||||
for direction, maplink in node.first_links.items():
|
||||
maplink.prototype = self.prototypes.get(
|
||||
node_coord + (direction,),
|
||||
self.prototypes.get(('*', '*', '*'), maplink.prototype))
|
||||
try:
|
||||
maplink.prototype = flatten_prototype(self.prototypes.get(
|
||||
node_coord + (direction,),
|
||||
self.prototypes.get(('*', '*', '*'), maplink.prototype)),
|
||||
no_db=_NO_DB_PROTOTYPES
|
||||
)
|
||||
except Exception as err:
|
||||
raise MapParserError(f"Exit prototype malformed: {err}", maplink)
|
||||
|
||||
# store
|
||||
self.display_map = display_map
|
||||
|
|
@ -625,8 +642,8 @@ class XYMap:
|
|||
spawned = []
|
||||
|
||||
# find existing nodes, in case some rooms need to be removed
|
||||
map_coords = ((node.X, node.Y) for node in
|
||||
sorted(self.node_index_map.values(), key=lambda n: (n.Y, n.X)))
|
||||
map_coords = [(node.X, node.Y) for node in
|
||||
sorted(self.node_index_map.values(), key=lambda n: (n.Y, n.X))]
|
||||
for existing_room in _XYZROOMCLASS.objects.filter_xyz(xyz=(x, y, self.Z)):
|
||||
roomX, roomY, _ = existing_room.xyz
|
||||
if (roomX, roomY) not in map_coords:
|
||||
|
|
|
|||
|
|
@ -311,7 +311,11 @@ class MapNode:
|
|||
nodeobj = NodeTypeclass.objects.get_xyz(xyz=xyz)
|
||||
except NodeTypeclass.DoesNotExist:
|
||||
# create a new entity with proper coordinates etc
|
||||
self.log(f" spawning room at xyz={xyz}")
|
||||
tclass = self.prototype['typeclass']
|
||||
tclass = (f' ({tclass})'
|
||||
if tclass != 'evennia.contrib.xyzgrid.xyzroom.XYZRoom'
|
||||
else '')
|
||||
self.log(f" spawning room at xyz={xyz}{tclass}")
|
||||
nodeobj, err = NodeTypeclass.create(
|
||||
self.prototype.get('key', 'An empty room'),
|
||||
xyz=xyz
|
||||
|
|
@ -327,7 +331,6 @@ class MapNode:
|
|||
|
||||
# apply prototype to node. This will not override the XYZ tags since
|
||||
# these are not in the prototype and exact=False
|
||||
|
||||
spawner.batch_update_objects_with_prototype(
|
||||
self.prototype, objects=[nodeobj], exact=False)
|
||||
|
||||
|
|
@ -364,8 +367,6 @@ class MapNode:
|
|||
link.prototype['prototype_key'] = self.generate_prototype_key()
|
||||
maplinks[key.lower()] = (key, aliases, direction, link)
|
||||
|
||||
# if xyz == (8, 1, 'the large tree'):
|
||||
# from evennia import set_trace;set_trace()
|
||||
# remove duplicates
|
||||
linkobjs = defaultdict(list)
|
||||
for exitobj in ExitTypeclass.objects.filter_xyz(xyz=xyz):
|
||||
|
|
@ -384,7 +385,6 @@ class MapNode:
|
|||
# build all exits first run)
|
||||
differing_keys = set(maplinks.keys()).symmetric_difference(set(linkobjs.keys()))
|
||||
for differing_key in differing_keys:
|
||||
# from evennia import set_trace;set_trace()
|
||||
|
||||
if differing_key not in maplinks:
|
||||
# an exit without a maplink - delete the exit-object
|
||||
|
|
@ -408,7 +408,12 @@ class MapNode:
|
|||
if err:
|
||||
raise RuntimeError(err)
|
||||
linkobjs[key.lower()] = exi
|
||||
self.log(f" spawning/updating exit xyz={xyz}, direction={key}")
|
||||
prot = maplinks[key.lower()][3].prototype
|
||||
tclass = prot['typeclass']
|
||||
tclass = (f' ({tclass})'
|
||||
if tclass != 'evennia.contrib.xyzgrid.xyzroom.XYZExit'
|
||||
else '')
|
||||
self.log(f" spawning/updating exit xyz={xyz}, direction={key}{tclass}")
|
||||
|
||||
# apply prototypes to catch any changes
|
||||
for key, linkobj in linkobjs.items():
|
||||
|
|
|
|||
|
|
@ -124,6 +124,9 @@ class XYZGrid(DefaultScript):
|
|||
map_data_list = [variable_from_module(module_path, "XYMAP_DATA")]
|
||||
# inject the python path in the map data
|
||||
for mapdata in map_data_list:
|
||||
if not mapdata:
|
||||
self.log(f"Could not find or load map from {module_path}.")
|
||||
return
|
||||
mapdata['module_path'] = module_path
|
||||
return map_data_list
|
||||
|
||||
|
|
@ -137,10 +140,14 @@ class XYZGrid(DefaultScript):
|
|||
nmaps = 0
|
||||
loaded_mapdata = {}
|
||||
changed = []
|
||||
mapdata = self.db.map_data
|
||||
|
||||
if not mapdata:
|
||||
self.db.mapdata = mapdata = {}
|
||||
|
||||
# generate all Maps - this will also initialize their components
|
||||
# and bake any pathfinding paths (or load from disk-cache)
|
||||
for zcoord, old_mapdata in self.db.map_data.items():
|
||||
for zcoord, old_mapdata in mapdata.items():
|
||||
|
||||
self.log(f"Loading map '{zcoord}'...")
|
||||
|
||||
|
|
@ -168,7 +175,7 @@ class XYZGrid(DefaultScript):
|
|||
|
||||
# re-store changed data
|
||||
for zcoord in changed:
|
||||
self.db.map_data[zcoord] = loaded_mapdata['zcoord']
|
||||
self.db.map_data[zcoord] = loaded_mapdata[zcoord]
|
||||
|
||||
# store
|
||||
self.log(f"Loaded and linked {nmaps} map(s).")
|
||||
|
|
@ -222,7 +229,9 @@ class XYZGrid(DefaultScript):
|
|||
Clear the entire grid, including database entities, then the grid too.
|
||||
|
||||
"""
|
||||
self.remove_map(*(zcoord for zcoord in self.db.map_data), remove_objects=True)
|
||||
mapdata = self.db.map_data
|
||||
if mapdata:
|
||||
self.remove_map(*(zcoord for zcoord in self.db.map_data), remove_objects=True)
|
||||
super().delete()
|
||||
|
||||
def spawn(self, xyz=('*', '*', '*'), directions=None):
|
||||
|
|
@ -291,6 +300,7 @@ def get_xyzgrid(print_errors=True):
|
|||
if not xyzgrid.ndb.loaded:
|
||||
xyzgrid.reload()
|
||||
except Exception as err:
|
||||
raise
|
||||
if print_errors:
|
||||
print(err)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -86,27 +86,20 @@ class XYZManager(ObjectManager):
|
|||
possible with a unique combination of x,y,z).
|
||||
|
||||
"""
|
||||
# filter by tags, then figure out of we got a single match or not
|
||||
query = self.filter_xyz(xyz=xyz, **kwargs)
|
||||
ncount = query.count()
|
||||
if ncount == 1:
|
||||
return query.first()
|
||||
|
||||
# error - mimic default get() behavior but with a little more info
|
||||
x, y, z = xyz
|
||||
|
||||
# mimic get_family
|
||||
paths = [self.model.path] + [
|
||||
"%s.%s" % (cls.__module__, cls.__name__) for cls in self._get_subclasses(self.model)
|
||||
]
|
||||
kwargs["db_typeclass_path__in"] = paths
|
||||
|
||||
try:
|
||||
return (
|
||||
self
|
||||
.filter(db_tags__db_key=str(x), db_tags__db_category=MAP_X_TAG_CATEGORY)
|
||||
.filter(db_tags__db_key=str(y), db_tags__db_category=MAP_Y_TAG_CATEGORY)
|
||||
.filter(db_tags__db_key=str(z), db_tags__db_category=MAP_Z_TAG_CATEGORY)
|
||||
.get(**kwargs)
|
||||
)
|
||||
except self.model.DoesNotExist:
|
||||
inp = (f"xyz=({x},{y},{z}), " +
|
||||
",".join(f"{key}={val}" for key, val in kwargs.items()))
|
||||
raise self.model.DoesNotExist(f"{self.model.__name__} "
|
||||
f"matching query {inp} does not exist.")
|
||||
inp = (f"Query: xyz=({x},{y},{z}), " +
|
||||
",".join(f"{key}={val}" for key, val in kwargs.items()))
|
||||
if ncount > 1:
|
||||
raise self.model.MultipleObjectsReturned(inp)
|
||||
else:
|
||||
raise self.model.DoesNotExist(inp)
|
||||
|
||||
|
||||
class XYZExitManager(XYZManager):
|
||||
|
|
|
|||
|
|
@ -2580,7 +2580,7 @@ def node_prototype_spawn(caller, **kwargs):
|
|||
# prototype load node
|
||||
|
||||
|
||||
def _prototype_load_select(caller, prototype_key):
|
||||
def _prototype_load_select(caller, prototype_key, **kwargs):
|
||||
matches = protlib.search_prototype(key=prototype_key)
|
||||
if matches:
|
||||
prototype = matches[0]
|
||||
|
|
|
|||
|
|
@ -105,17 +105,17 @@ def homogenize_prototype(prototype, custom_keys=None):
|
|||
elif protkey in ("prototype_key", "prototype_desc"):
|
||||
prototype[protkey] = ""
|
||||
|
||||
attrs = list(prototype.get("attrs", [])) # break reference
|
||||
tags = make_iter(prototype.get("tags", []))
|
||||
homogenized = {}
|
||||
homogenized_tags = []
|
||||
homogenized_attrs = []
|
||||
homogenized_parents = []
|
||||
|
||||
homogenized = {}
|
||||
for key, val in prototype.items():
|
||||
if key in reserved:
|
||||
# check all reserved keys
|
||||
if key == "tags":
|
||||
# tags must be on form [(tag, category, data), ...]
|
||||
tags = make_iter(prototype.get("tags", []))
|
||||
for tag in tags:
|
||||
if not is_iter(tag):
|
||||
homogenized_tags.append((tag, None, None))
|
||||
|
|
@ -127,7 +127,9 @@ def homogenize_prototype(prototype, custom_keys=None):
|
|||
homogenized_tags.append((tag[0], tag[1], None))
|
||||
else:
|
||||
homogenized_tags.append(tag[:3])
|
||||
if key == "attrs":
|
||||
|
||||
elif key == "attrs":
|
||||
attrs = list(prototype.get("attrs", [])) # break reference
|
||||
for attr in attrs:
|
||||
# attrs must be on form [(key, value, category, lockstr)]
|
||||
if not is_iter(attr):
|
||||
|
|
@ -144,6 +146,21 @@ def homogenize_prototype(prototype, custom_keys=None):
|
|||
homogenized_attrs.append(attr[0], attr[1], attr[2], "")
|
||||
else:
|
||||
homogenized_attrs.append(attr[:4])
|
||||
|
||||
elif key == "prototype_parent":
|
||||
# homogenize any prototype-parents embedded directly as dicts
|
||||
protparents = prototype.get('prototype_parent', [])
|
||||
if isinstance(protparents, dict):
|
||||
protparents = [protparents]
|
||||
for parent in make_iter(protparents):
|
||||
if isinstance(parent, dict):
|
||||
# recursively homogenize directly embedded prototype parents
|
||||
homogenized_parents.append(
|
||||
homogenize_prototype(parent, custom_keys=custom_keys))
|
||||
else:
|
||||
# normal prototype-parent names are added as-is
|
||||
homogenized_parents.append(parent)
|
||||
|
||||
else:
|
||||
# another reserved key
|
||||
homogenized[key] = val
|
||||
|
|
@ -154,6 +171,8 @@ def homogenize_prototype(prototype, custom_keys=None):
|
|||
homogenized["attrs"] = homogenized_attrs
|
||||
if homogenized_tags:
|
||||
homogenized["tags"] = homogenized_tags
|
||||
if homogenized_parents:
|
||||
homogenized['prototype_parent'] = homogenized_parents
|
||||
|
||||
# add required missing parts that had defaults before
|
||||
|
||||
|
|
@ -460,7 +479,8 @@ def delete_prototype(prototype_key, caller=None):
|
|||
return True
|
||||
|
||||
|
||||
def search_prototype(key=None, tags=None, require_single=False, return_iterators=False):
|
||||
def search_prototype(key=None, tags=None, require_single=False, return_iterators=False,
|
||||
no_db=False):
|
||||
"""
|
||||
Find prototypes based on key and/or tags, or all prototypes.
|
||||
|
||||
|
|
@ -474,6 +494,9 @@ def search_prototype(key=None, tags=None, require_single=False, return_iterators
|
|||
return_iterators (bool): Optimized return for large numbers of db-prototypes.
|
||||
If set, separate returns of module based prototypes and paginate
|
||||
the db-prototype return.
|
||||
no_db (bool): Optimization. If set, skip querying for database-generated prototypes and only
|
||||
include module-based prototypes. This can lead to a dramatic speedup since
|
||||
module-prototypes are static and require no db-lookup.
|
||||
|
||||
Return:
|
||||
matches (list): Default return, all found prototype dicts. Empty list if
|
||||
|
|
@ -525,35 +548,38 @@ def search_prototype(key=None, tags=None, require_single=False, return_iterators
|
|||
# prototype_from_object will modify the base prototype for every object
|
||||
module_prototypes = [match.copy() for match in mod_matches.values()]
|
||||
|
||||
# search db-stored prototypes
|
||||
|
||||
if tags:
|
||||
# exact match on tag(s)
|
||||
tags = make_iter(tags)
|
||||
tag_categories = ["db_prototype" for _ in tags]
|
||||
db_matches = DbPrototype.objects.get_by_tag(tags, tag_categories)
|
||||
if no_db:
|
||||
db_matches = []
|
||||
else:
|
||||
db_matches = DbPrototype.objects.all()
|
||||
|
||||
if key:
|
||||
# exact or partial match on key
|
||||
exact_match = db_matches.filter(Q(db_key__iexact=key)).order_by("db_key")
|
||||
if not exact_match and allow_fuzzy:
|
||||
# try with partial match instead
|
||||
db_matches = db_matches.filter(Q(db_key__icontains=key)).order_by("db_key")
|
||||
# search db-stored prototypes
|
||||
if tags:
|
||||
# exact match on tag(s)
|
||||
tags = make_iter(tags)
|
||||
tag_categories = ["db_prototype" for _ in tags]
|
||||
db_matches = DbPrototype.objects.get_by_tag(tags, tag_categories)
|
||||
else:
|
||||
db_matches = exact_match
|
||||
db_matches = DbPrototype.objects.all()
|
||||
|
||||
if key:
|
||||
# exact or partial match on key
|
||||
exact_match = db_matches.filter(Q(db_key__iexact=key)).order_by("db_key")
|
||||
if not exact_match and allow_fuzzy:
|
||||
# try with partial match instead
|
||||
db_matches = db_matches.filter(Q(db_key__icontains=key)).order_by("db_key")
|
||||
else:
|
||||
db_matches = exact_match
|
||||
|
||||
# convert to prototype
|
||||
db_ids = db_matches.values_list("id", flat=True)
|
||||
db_matches = (
|
||||
Attribute.objects.filter(scriptdb__pk__in=db_ids, db_key="prototype")
|
||||
.values_list("db_value", flat=True)
|
||||
.order_by("scriptdb__db_key")
|
||||
)
|
||||
|
||||
# convert to prototype
|
||||
db_ids = db_matches.values_list("id", flat=True)
|
||||
db_matches = (
|
||||
Attribute.objects.filter(scriptdb__pk__in=db_ids, db_key="prototype")
|
||||
.values_list("db_value", flat=True)
|
||||
.order_by("scriptdb__db_key")
|
||||
)
|
||||
if key and require_single:
|
||||
nmodules = len(module_prototypes)
|
||||
ndbprots = db_matches.count()
|
||||
ndbprots = db_matches.count() if db_matches else 0
|
||||
if nmodules + ndbprots != 1:
|
||||
raise KeyError(_(
|
||||
"Found {num} matching prototypes among {module_prototypes}.").format(
|
||||
|
|
@ -795,19 +821,29 @@ def validate_prototype(
|
|||
err=err, protkey=protkey, typeclass=typeclass)
|
||||
)
|
||||
|
||||
# recursively traverse prototype_parent chain
|
||||
if prototype_parent and isinstance(prototype_parent, dict):
|
||||
# the protparent is already embedded as a dict;
|
||||
prototype_parent = [prototype_parent]
|
||||
|
||||
# recursively traverse prototype_parent chain
|
||||
for protstring in make_iter(prototype_parent):
|
||||
protstring = protstring.lower()
|
||||
if protkey is not None and protstring == protkey:
|
||||
_flags["errors"].append(_("Prototype {protkey} tries to parent itself.").format(
|
||||
protkey=protkey))
|
||||
protparent = protparents.get(protstring)
|
||||
if not protparent:
|
||||
_flags["errors"].append(
|
||||
_("Prototype {protkey}'s prototype_parent '{parent}' was not found.").format(
|
||||
protkey=protkey, parent=protstring)
|
||||
)
|
||||
if isinstance(protstring, dict):
|
||||
# an already embedded prototype_parent
|
||||
protparent = protstring
|
||||
protstring = None
|
||||
else:
|
||||
protstring = protstring.lower()
|
||||
if protkey is not None and protstring == protkey:
|
||||
_flags["errors"].append(_("Prototype {protkey} tries to parent itself.").format(
|
||||
protkey=protkey))
|
||||
protparent = protparents.get(protstring)
|
||||
if not protparent:
|
||||
_flags["errors"].append(
|
||||
_("Prototype {protkey}'s `prototype_parent` (named '{parent}') "
|
||||
"was not found.").format(protkey=protkey, parent=protstring)
|
||||
)
|
||||
|
||||
# check for infinite recursion
|
||||
if id(prototype) in _flags["visited"]:
|
||||
_flags["errors"].append(
|
||||
_("{protkey} has infinite nesting of prototypes.").format(
|
||||
|
|
@ -818,9 +854,12 @@ def validate_prototype(
|
|||
raise RuntimeError(f"{_ERRSTR}: " + f"\n{_ERRSTR}: ".join(_flags["errors"]))
|
||||
_flags["visited"].append(id(prototype))
|
||||
_flags["depth"] += 1
|
||||
|
||||
# next step of recursive validation
|
||||
validate_prototype(
|
||||
protparent, protstring, protparents, is_prototype_base=is_prototype_base, _flags=_flags
|
||||
)
|
||||
|
||||
_flags["visited"].pop()
|
||||
_flags["depth"] -= 1
|
||||
|
||||
|
|
|
|||
|
|
@ -220,10 +220,23 @@ def _get_prototype(inprot, protparents, uninherited=None, _workprot=None):
|
|||
_workprot = {} if _workprot is None else _workprot
|
||||
if "prototype_parent" in inprot:
|
||||
# move backwards through the inheritance
|
||||
for prototype in make_iter(inprot["prototype_parent"]):
|
||||
|
||||
prototype_parents = inprot["prototype_parent"]
|
||||
if isinstance(prototype_parents, dict):
|
||||
# protparent already embedded as-is
|
||||
prototype_parents = [prototype_parents]
|
||||
|
||||
for prototype in make_iter(prototype_parents):
|
||||
if isinstance(prototype, dict):
|
||||
# protparent already embedded as-is
|
||||
parent_prototype = prototype
|
||||
else:
|
||||
# protparent given by-name
|
||||
parent_prototype = protparents.get(prototype.lower(), {})
|
||||
|
||||
# Build the prot dictionary in reverse order, overloading
|
||||
new_prot = _get_prototype(
|
||||
protparents.get(prototype.lower(), {}), protparents, _workprot=_workprot
|
||||
parent_prototype, protparents, _workprot=_workprot
|
||||
)
|
||||
|
||||
# attrs, tags have internal structure that should be inherited separately
|
||||
|
|
@ -245,7 +258,7 @@ def _get_prototype(inprot, protparents, uninherited=None, _workprot=None):
|
|||
return _workprot
|
||||
|
||||
|
||||
def flatten_prototype(prototype, validate=False):
|
||||
def flatten_prototype(prototype, validate=False, no_db=False):
|
||||
"""
|
||||
Produce a 'flattened' prototype, where all prototype parents in the inheritance tree have been
|
||||
merged into a final prototype.
|
||||
|
|
@ -253,6 +266,8 @@ def flatten_prototype(prototype, validate=False):
|
|||
Args:
|
||||
prototype (dict): Prototype to flatten. Its `prototype_parent` field will be parsed.
|
||||
validate (bool, optional): Validate for valid keys etc.
|
||||
no_db (bool, optional): Don't search db-based prototypes. This can speed up
|
||||
searching dramatically since module-based prototypes are static.
|
||||
|
||||
Returns:
|
||||
flattened (dict): The final, flattened prototype.
|
||||
|
|
@ -261,7 +276,8 @@ def flatten_prototype(prototype, validate=False):
|
|||
|
||||
if prototype:
|
||||
prototype = protlib.homogenize_prototype(prototype)
|
||||
protparents = {prot["prototype_key"].lower(): prot for prot in protlib.search_prototype()}
|
||||
protparents = {prot["prototype_key"].lower(): prot
|
||||
for prot in protlib.search_prototype(no_db=no_db)}
|
||||
protlib.validate_prototype(
|
||||
prototype, None, protparents, is_prototype_base=validate, strict=validate
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue