3232 set_resolver ,
3333)
3434from oold .generator import Generator
35+ from oold .model .v1 import _types as oold_type_registry
3536from oold .utils .codegen import OOLDJsonSchemaParser
3637from opensemantic .v1 import OswBaseModel
3738from pydantic import PydanticDeprecatedSince20
@@ -1430,6 +1431,10 @@ def load_entity(
14301431 entity = None
14311432 schemas = []
14321433 schemas_fetched = True
1434+ # maps a category (page title, e.g. "Category:OSW...") to the
1435+ # class to use for it, either an already registered class (e.g. a
1436+ # packaged model class) or a freshly compiled one
1437+ category_to_cls : Dict [str , Type [model .Entity ]] = {}
14331438 jsondata = page .get_slot_content ("jsondata" )
14341439 if param .remove_empty :
14351440 remove_empty (jsondata )
@@ -1451,23 +1456,99 @@ def load_entity(
14511456 # If a schema_to_use is provided, we do not need to check if
14521457 # the model exists
14531458 if not param .model_to_use :
1454- if not hasattr (model , cls_name ):
1455- if param .autofetch_schema :
1456- self .fetch_schema (
1457- OSW .FetchSchemaParam (
1458- schema_title = category ,
1459- mode = "append" ,
1460- offline_pages = param .offline_pages ,
1459+ # Prefer a class already registered for this category
1460+ # IRI (e.g. a packaged model class) over compiling a
1461+ # new one. Compiling one anyway would take over the
1462+ # oold type registry entry for this category and hide
1463+ # the packaged class' typed fields/helpers.
1464+ registered_cls = oold_type_registry .get (category )
1465+ # Controllers and result wrappers (e.g.
1466+ # WikiFileController, UploadFileResult) inherit
1467+ # their category IRI from the model class they
1468+ # extend, and the registry keeps whichever class
1469+ # was defined last. Such a specialization asks for
1470+ # fields a plain page's jsondata does not carry, so
1471+ # prefer the canonical class from osw.model.entity
1472+ # over a subclass of it.
1473+ canonical_cls = getattr (model , cls_name , None )
1474+ if (
1475+ registered_cls is not None
1476+ # the schema title can also name a module
1477+ # attribute that is not a class
1478+ and isinstance (canonical_cls , type )
1479+ and registered_cls is not canonical_cls
1480+ and issubclass (registered_cls , canonical_cls )
1481+ ):
1482+ _logger .debug (
1483+ f"Ignoring '{ registered_cls } ' registered for "
1484+ f"category '{ category } ': it specializes "
1485+ f"'{ canonical_cls } ', which is used instead."
1486+ )
1487+ registered_cls = None
1488+ if registered_cls is not None :
1489+ category_to_cls [category ] = registered_cls
1490+ else :
1491+ if not hasattr (model , cls_name ):
1492+ if param .autofetch_schema :
1493+ self .fetch_schema (
1494+ OSW .FetchSchemaParam (
1495+ schema_title = category ,
1496+ mode = "append" ,
1497+ offline_pages = param .offline_pages ,
1498+ )
14611499 )
1500+ if not hasattr (model , cls_name ):
1501+ schemas_fetched = False
1502+ _logger .error (
1503+ f"Model { cls_name } not found. Schema "
1504+ f"{ category } needs to be fetched first."
14621505 )
1463- if not hasattr (model , cls_name ):
1464- schemas_fetched = False
1465- _logger .error (
1466- f"Model { cls_name } not found. Schema { category } "
1467- f"needs to be fetched first."
1468- )
1506+ else :
1507+ generated_cls = getattr (model , cls_name )
1508+ # The class we are about to use may have just
1509+ # claimed (or may already hold) the registry
1510+ # slot for this category. If a different class
1511+ # is registered for it, someone's registration
1512+ # was silently overwritten - do not raise, but
1513+ # make sure this does not pass silently.
1514+ conflicting_cls = oold_type_registry .get (category )
1515+ if (
1516+ conflicting_cls is not None
1517+ and conflicting_cls is not generated_cls
1518+ # the slot holder specializes
1519+ # generated_cls, e.g. a controller
1520+ # that inherits the category IRI:
1521+ # the case the guard above expects,
1522+ # not a conflict. A fetch can leave
1523+ # a non-class under the schema title.
1524+ and not (
1525+ isinstance (generated_cls , type )
1526+ and issubclass (
1527+ conflicting_cls , generated_cls
1528+ )
1529+ )
1530+ ):
1531+ _logger .warning (
1532+ f"Class '{ generated_cls } ' generated for "
1533+ f"category '{ category } ' claims the oold "
1534+ f"type registry slot already held by a "
1535+ f"different class '{ conflicting_cls } '."
1536+ )
1537+ category_to_cls [category ] = generated_cls
14691538 if not schemas_fetched :
14701539 continue
1540+ # fetch_schema() reloads osw.model.entity, which replaces every
1541+ # class defined there, so a class picked before a later fetch in
1542+ # the loop above may be out of date: take the current object
1543+ for category , chosen_cls in category_to_cls .items ():
1544+ # a non-class is left for the construction below to reject
1545+ if (
1546+ isinstance (chosen_cls , type )
1547+ and chosen_cls .__module__ == model .__name__
1548+ ):
1549+ category_to_cls [category ] = getattr (
1550+ model , chosen_cls .__name__ , chosen_cls
1551+ )
14711552
14721553 try :
14731554 if param .model_to_use :
@@ -1477,13 +1558,15 @@ def load_entity(
14771558 _logger .error ("no schema defined" )
14781559
14791560 elif len (schemas ) == 1 :
1480- cls : Type [model .Entity ] = getattr (model , schemas [0 ]["title" ])
1561+ # category_to_cls is fully populated for every category
1562+ # that reached this point (see the loop above)
1563+ cls : Type [model .Entity ] = category_to_cls [jsondata ["type" ][0 ]]
14811564 entity : model .Entity = cls (** jsondata )
14821565
14831566 else :
14841567 bases = []
1485- for schema in schemas :
1486- bases .append (getattr ( model , schema [ "title" ]) )
1568+ for category in jsondata [ "type" ] :
1569+ bases .append (category_to_cls [ category ] )
14871570 cls = create_model ("Test" , __base__ = tuple (bases ))
14881571 entity : model .Entity = cls (** jsondata )
14891572 except Exception as e :
0 commit comments