Skip to content

Commit f524925

Browse files
authored
Merge pull request #166 from OpenSemanticLab/fix/load-entity-registered-class
fix(core): prefer a registered class over a generated one
2 parents 0573271 + 0017ef0 commit f524925

2 files changed

Lines changed: 540 additions & 16 deletions

File tree

‎src/osw/core.py‎

Lines changed: 99 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
set_resolver,
3333
)
3434
from oold.generator import Generator
35+
from oold.model.v1 import _types as oold_type_registry
3536
from oold.utils.codegen import OOLDJsonSchemaParser
3637
from opensemantic.v1 import OswBaseModel
3738
from pydantic import PydanticDeprecatedSince20
@@ -1390,6 +1391,10 @@ def load_entity(
13901391
entity = None
13911392
schemas = []
13921393
schemas_fetched = True
1394+
# maps a category (page title, e.g. "Category:OSW...") to the
1395+
# class to use for it, either an already registered class (e.g. a
1396+
# packaged model class) or a freshly compiled one
1397+
category_to_cls: Dict[str, Type[model.Entity]] = {}
13931398
jsondata = page.get_slot_content("jsondata")
13941399
if param.remove_empty:
13951400
remove_empty(jsondata)
@@ -1411,23 +1416,99 @@ def load_entity(
14111416
# If a schema_to_use is provided, we do not need to check if
14121417
# the model exists
14131418
if not param.model_to_use:
1414-
if not hasattr(model, cls_name):
1415-
if param.autofetch_schema:
1416-
self.fetch_schema(
1417-
OSW.FetchSchemaParam(
1418-
schema_title=category,
1419-
mode="append",
1420-
offline_pages=param.offline_pages,
1419+
# Prefer a class already registered for this category
1420+
# IRI (e.g. a packaged model class) over compiling a
1421+
# new one. Compiling one anyway would take over the
1422+
# oold type registry entry for this category and hide
1423+
# the packaged class' typed fields/helpers.
1424+
registered_cls = oold_type_registry.get(category)
1425+
# Controllers and result wrappers (e.g.
1426+
# WikiFileController, UploadFileResult) inherit
1427+
# their category IRI from the model class they
1428+
# extend, and the registry keeps whichever class
1429+
# was defined last. Such a specialization asks for
1430+
# fields a plain page's jsondata does not carry, so
1431+
# prefer the canonical class from osw.model.entity
1432+
# over a subclass of it.
1433+
canonical_cls = getattr(model, cls_name, None)
1434+
if (
1435+
registered_cls is not None
1436+
# the schema title can also name a module
1437+
# attribute that is not a class
1438+
and isinstance(canonical_cls, type)
1439+
and registered_cls is not canonical_cls
1440+
and issubclass(registered_cls, canonical_cls)
1441+
):
1442+
_logger.debug(
1443+
f"Ignoring '{registered_cls}' registered for "
1444+
f"category '{category}': it specializes "
1445+
f"'{canonical_cls}', which is used instead."
1446+
)
1447+
registered_cls = None
1448+
if registered_cls is not None:
1449+
category_to_cls[category] = registered_cls
1450+
else:
1451+
if not hasattr(model, cls_name):
1452+
if param.autofetch_schema:
1453+
self.fetch_schema(
1454+
OSW.FetchSchemaParam(
1455+
schema_title=category,
1456+
mode="append",
1457+
offline_pages=param.offline_pages,
1458+
)
14211459
)
1460+
if not hasattr(model, cls_name):
1461+
schemas_fetched = False
1462+
_logger.error(
1463+
f"Model {cls_name} not found. Schema "
1464+
f"{category} needs to be fetched first."
14221465
)
1423-
if not hasattr(model, cls_name):
1424-
schemas_fetched = False
1425-
_logger.error(
1426-
f"Model {cls_name} not found. Schema {category} "
1427-
f"needs to be fetched first."
1428-
)
1466+
else:
1467+
generated_cls = getattr(model, cls_name)
1468+
# The class we are about to use may have just
1469+
# claimed (or may already hold) the registry
1470+
# slot for this category. If a different class
1471+
# is registered for it, someone's registration
1472+
# was silently overwritten - do not raise, but
1473+
# make sure this does not pass silently.
1474+
conflicting_cls = oold_type_registry.get(category)
1475+
if (
1476+
conflicting_cls is not None
1477+
and conflicting_cls is not generated_cls
1478+
# the slot holder specializes
1479+
# generated_cls, e.g. a controller
1480+
# that inherits the category IRI:
1481+
# the case the guard above expects,
1482+
# not a conflict. A fetch can leave
1483+
# a non-class under the schema title.
1484+
and not (
1485+
isinstance(generated_cls, type)
1486+
and issubclass(
1487+
conflicting_cls, generated_cls
1488+
)
1489+
)
1490+
):
1491+
_logger.warning(
1492+
f"Class '{generated_cls}' generated for "
1493+
f"category '{category}' claims the oold "
1494+
f"type registry slot already held by a "
1495+
f"different class '{conflicting_cls}'."
1496+
)
1497+
category_to_cls[category] = generated_cls
14291498
if not schemas_fetched:
14301499
continue
1500+
# fetch_schema() reloads osw.model.entity, which replaces every
1501+
# class defined there, so a class picked before a later fetch in
1502+
# the loop above may be out of date: take the current object
1503+
for category, chosen_cls in category_to_cls.items():
1504+
# a non-class is left for the construction below to reject
1505+
if (
1506+
isinstance(chosen_cls, type)
1507+
and chosen_cls.__module__ == model.__name__
1508+
):
1509+
category_to_cls[category] = getattr(
1510+
model, chosen_cls.__name__, chosen_cls
1511+
)
14311512

14321513
try:
14331514
if param.model_to_use:
@@ -1437,13 +1518,15 @@ def load_entity(
14371518
_logger.error("no schema defined")
14381519

14391520
elif len(schemas) == 1:
1440-
cls: Type[model.Entity] = getattr(model, schemas[0]["title"])
1521+
# category_to_cls is fully populated for every category
1522+
# that reached this point (see the loop above)
1523+
cls: Type[model.Entity] = category_to_cls[jsondata["type"][0]]
14411524
entity: model.Entity = cls(**jsondata)
14421525

14431526
else:
14441527
bases = []
1445-
for schema in schemas:
1446-
bases.append(getattr(model, schema["title"]))
1528+
for category in jsondata["type"]:
1529+
bases.append(category_to_cls[category])
14471530
cls = create_model("Test", __base__=tuple(bases))
14481531
entity: model.Entity = cls(**jsondata)
14491532
except Exception as e:

0 commit comments

Comments
 (0)