Skip to content

Commit e39a857

Browse files
committed
fix(core): use the current class after fetch_schema reloads the module
- a fetch for a later category reloads osw.model.entity - the class picked for an earlier category was then a replaced object - the entity was not an instance of the current class, unlike on main
1 parent c116199 commit e39a857

2 files changed

Lines changed: 76 additions & 3 deletions

File tree

‎src/osw/core.py‎

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1327,9 +1327,11 @@ def load_entity(
13271327
if (
13281328
conflicting_cls is not None
13291329
and conflicting_cls is not generated_cls
1330-
# a specialization of generated_cls was
1331-
# already set aside above; it still
1332-
# holds the slot and nothing claimed it
1330+
# the slot holder specializes
1331+
# generated_cls, e.g. a controller
1332+
# that inherits the category IRI:
1333+
# the case the guard above expects,
1334+
# not a conflict
13331335
and not issubclass(
13341336
conflicting_cls, generated_cls
13351337
)
@@ -1343,6 +1345,14 @@ def load_entity(
13431345
category_to_cls[category] = generated_cls
13441346
if not schemas_fetched:
13451347
continue
1348+
# fetch_schema() reloads osw.model.entity, which replaces every
1349+
# class defined there, so a class picked before a later fetch in
1350+
# the loop above may be out of date: take the current object
1351+
for category, chosen_cls in category_to_cls.items():
1352+
if chosen_cls.__module__ == model.__name__:
1353+
category_to_cls[category] = getattr(
1354+
model, chosen_cls.__name__, chosen_cls
1355+
)
13461356

13471357
try:
13481358
if param.model_to_use:

‎tests/test_load_entity_registered_class.py‎

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,14 @@ def make_isolated_cls(name: str, base=model.Item):
102102
return type(base)(name, (base,), namespace)
103103

104104

105+
def make_generated_cls(name: str):
106+
"""Like make_isolated_cls, but defined in osw.model.entity, as a class that
107+
fetch_schema() generated is."""
108+
cls = make_isolated_cls(name)
109+
cls.__module__ = model.__name__
110+
return cls
111+
112+
105113
def test_load_entity_prefers_registered_class_over_generated_one():
106114
"""A class already registered for the category IRI is used as-is, and no
107115
replacement class is compiled into osw.model.entity for it."""
@@ -289,3 +297,58 @@ def fake_fetch_schema(self, fetchSchemaParam=None):
289297
if hasattr(model, cls_name):
290298
delattr(model, cls_name)
291299
oold_type_registry.pop(category, None)
300+
301+
302+
def test_load_entity_uses_the_current_class_after_a_reload(monkeypatch):
303+
"""fetch_schema() reloads osw.model.entity, which replaces every class
304+
defined there. On a page with two categories, a fetch for the second one
305+
must not leave the entity built from the first one's replaced class."""
306+
cat_a = "Category:OSWReloadTestA000000000000000000000000"
307+
cat_b = "Category:OSWReloadTestB000000000000000000000000"
308+
name_a, name_b = "ReloadTestA", "ReloadTestB"
309+
310+
setattr(model, name_a, make_generated_cls(name_a))
311+
312+
def fake_fetch_schema(self, fetchSchemaParam=None):
313+
# what importlib.reload(model) does: the requested class appears, and
314+
# every class already generated there becomes a new object
315+
setattr(model, name_a, make_generated_cls(name_a))
316+
setattr(model, name_b, make_generated_cls(name_b))
317+
318+
monkeypatch.setattr(OSW, "fetch_schema", fake_fetch_schema)
319+
try:
320+
entity_page = OfflineWtPage(
321+
title="Item:OSWReloadEntity00000000000000000000000000000"
322+
)
323+
jsondata = {
324+
"type": [cat_a, cat_b],
325+
"uuid": "33333333-3333-3333-3333-333333333333",
326+
"name": "x",
327+
"label": [{"text": "x"}],
328+
}
329+
remove_empty(jsondata)
330+
entity_page.set_slot_content("jsondata", jsondata)
331+
332+
osw_obj = OSW(site=make_offline_wtsite())
333+
334+
result = osw_obj.load_entity(
335+
OSW.LoadEntityParam(
336+
titles=[entity_page.title],
337+
autofetch_schema=True,
338+
offline_pages={
339+
entity_page.title: entity_page,
340+
cat_a: make_schema_page(cat_a, name_a),
341+
cat_b: make_schema_page(cat_b, name_b),
342+
},
343+
)
344+
)
345+
346+
entity = result.entities[0]
347+
assert isinstance(entity, getattr(model, name_a))
348+
assert isinstance(entity, getattr(model, name_b))
349+
finally:
350+
for name in (name_a, name_b):
351+
if hasattr(model, name):
352+
delattr(model, name)
353+
oold_type_registry.pop(cat_a, None)
354+
oold_type_registry.pop(cat_b, None)

0 commit comments

Comments
 (0)