[docs]classXmlContext:""" The service provider for binding operations metadata. :param element_name_generator: Default element name generator :param attribute_name_generator: Default attribute name generator :param class_type: Default class type `dataclasses` """__slots__=("element_name_generator","attribute_name_generator","class_type","cache","xsi_cache","sys_modules",)def__init__(self,element_name_generator:Callable=return_input,attribute_name_generator:Callable=return_input,class_type:str="dataclasses",):self.element_name_generator=element_name_generatorself.attribute_name_generator=attribute_name_generatorself.class_type=class_types.get_type(class_type)self.cache:Dict[Type,XmlMeta]={}self.xsi_cache:Dict[str,List[Type]]=defaultdict(list)self.sys_modules=0defreset(self):self.cache.clear()self.xsi_cache.clear()self.sys_modules=0
[docs]deffetch(self,clazz:Type,parent_ns:Optional[str]=None,xsi_type:Optional[str]=None,)->XmlMeta:""" Fetch the model metadata of the given dataclass type, namespace and xsi type. :param clazz: The requested dataclass type :param parent_ns: The inherited parent namespace :param xsi_type: if present it means that the given clazz is derived and the lookup procedure needs to check and match a dataclass model to the qualified name instead """meta=self.build(clazz,parent_ns)subclass=Noneifxsi_typeandmeta.target_qname!=xsi_type:subclass=self.find_subclass(clazz,xsi_type)returnself.build(subclass,parent_ns)ifsubclasselsemeta
[docs]defbuild_xsi_cache(self):"""Index all imported dataclasses by their xsi:type qualified name."""iflen(sys.modules)==self.sys_modules:returnself.xsi_cache.clear()name_generator=self.element_name_generatorforclazzinself.get_subclasses(object):ifself.class_type.is_model(clazz):qname=XmlMetaBuilder.build_target_qname(clazz,name_generator)self.xsi_cache[qname].append(clazz)self.sys_modules=len(sys.modules)
[docs]deffind_types(self,qname:str)->List[Type[T]]:""" Find all classes that match the given xsi:type qname. - Ignores native schema types, xs:string, xs:float, xs:int, ... - Rebuild cache if new modules were imported since last run :param qname: Qualified name """ifnotDataType.from_qname(qname):self.build_xsi_cache()ifqnameinself.xsi_cache:returnself.xsi_cache[qname]return[]
[docs]deffind_type(self,qname:str)->Optional[Type[T]]:""" Return the most recently imported class that matches the given xsi:type qname. :param qname: Qualified name """types:List[Type]=self.find_types(qname)returntypes[-1]iftypeselseNone
[docs]deffind_type_by_fields(self,field_names:Set[str])->Optional[Type[T]]:""" Find a dataclass from all the imported modules that matches the given list of field names. :param field_names: A unique list of field names """self.build_xsi_cache()fortypesinself.xsi_cache.values():forclazzintypes:ifself.local_names_match(field_names,clazz):returnclazzreturnNone
[docs]deffind_subclass(self,clazz:Type,qname:str)->Optional[Type]:""" Compare all classes that match the given xsi:type qname and return the first one that is either a subclass or shares the same parent class as the original class. :param clazz: The search dataclass type :param qname: Qualified name """types:List[Type]=self.find_types(qname)fortpintypes:# Why would an xml node with have an xsi:type that points# to parent class is beyond me but it happens, let's protect# against that scenario <node xsi:type="nodeAbstract" />ifissubclass(clazz,tp):continuefortp_mrointp.__mro__:iftp_mroisnotobjectandtp_mroinclazz.__mro__:returntpreturnNone
[docs]defbuild(self,clazz:Type,parent_ns:Optional[str]=None)->XmlMeta:""" Fetch from cache or build the binding metadata for the given class and parent namespace. :param clazz: A dataclass type :param parent_ns: The inherited parent namespace """ifclazznotinself.cache:builder=XmlMetaBuilder(class_type=self.class_type,element_name_generator=self.element_name_generator,attribute_name_generator=self.attribute_name_generator,)self.cache[clazz]=builder.build(clazz,parent_ns)returnself.cache[clazz]
[docs]defbuild_recursive(self,clazz:Type,parent_ns:Optional[str]=None):"""Build the binding metadata for the given class and all of its dependencies."""ifclazznotinself.cache:meta=self.build(clazz,parent_ns)forvarinmeta.get_all_vars():types=var.element_typesifvar.elementselsevar.typesfortpintypes:ifself.class_type.is_model(tp):self.build_recursive(tp,meta.namespace)
[docs]@classmethoddefis_derived(cls,obj:Any,clazz:Type)->bool:""" Return whether the given obj is derived from the given dataclass type. :param obj: A dataclass instance :param clazz: A dataclass type """ifobjisNone:returnFalseifisinstance(obj,clazz):returnTruereturnany(xisnotobjectandisinstance(obj,x)forxinclazz.__bases__)