Repository: tefra/xsdata

Issue:
AttributeError during build_xsi_cache for _ObjectPropertyMethods
Python = 3.11.0
Xsdata = 23.5

https://github.com/tefra/xsdata/blob/4a02fa6126c9d9e3d91c2f3e360a26d1c9b897a7/xsdata/formats/dataclass/context.py#L91-L94

This is raising an `AttributeError` in Python 3.11.0 as `__module__` is not a string
```
clazz            = <class '_ObjectProxyMethods'>
clazz.__module__ = <property object at 0x7f540d6cc4a0>
```

When I use `inspect.getsource(clazz)` in a debug session, I get
```
TypeError: <class '_ObjectProxyMethods'> is a built-in class
```

Unfortunately, I cannot provide examples of the XSDs being used due to ownership concerns.

Locally, I was able to successfully use xsdata on the XSDs after making this change:
```python
for clazz in self.get_subclasses(object):
    # This is new! 
    if isinstance(clazz.__module__, str) and clazz.__module__.startswith("_pytest"): 
        continue 
```

Discussion:
sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=tefra_xsdata&pullRequest=796)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=796&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=796&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=796&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=796&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=796&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=796&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=796&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=796&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=796&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=796&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=796&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=796&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=796) No Coverage information  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=796&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=796&metric=new_duplicated_lines_density&view=list)


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/tefra/xsdata/pull/796?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) Report
Patch coverage: **`100.00`**% and no project coverage change.
> Comparison is base [(`4a02fa6`)](https://app.codecov.io/gh/tefra/xsdata/commit/4a02fa6126c9d9e3d91c2f3e360a26d1c9b897a7?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) 99.96% compared to head [(`c1626f4`)](https://app.codecov.io/gh/tefra/xsdata/pull/796?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) 99.96%.

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff            @@
##           master     #796    +/-   ##
========================================
  Coverage   99.96%   99.96%            
========================================
  Files         104      104            
  Lines        9204     9208     +4     
  Branches     2060     1600   -460     
========================================
+ Hits         9201     9205     +4     
  Partials        3        3            
```


| [Impacted Files](https://app.codecov.io/gh/tefra/xsdata/pull/796?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) | Coverage Δ | |
|---|---|---|
| [xsdata/formats/dataclass/models/builders.py](https://app.codecov.io/gh/tefra/xsdata/pull/796?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou#diff-eHNkYXRhL2Zvcm1hdHMvZGF0YWNsYXNzL21vZGVscy9idWlsZGVycy5weQ==) | `100.00% <ø> (ø)` | |
| [xsdata/formats/dataclass/context.py](https://app.codecov.io/gh/tefra/xsdata/pull/796?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou#diff-eHNkYXRhL2Zvcm1hdHMvZGF0YWNsYXNzL2NvbnRleHQucHk=) | `100.00% <100.00%> (ø)` | |


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/tefra/xsdata/pull/796?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou).


Patch:
diff --git a/xsdata/formats/dataclass/context.py b/xsdata/formats/dataclass/context.py
index 67bd8a3c..827343fd 100644
--- a/xsdata/formats/dataclass/context.py
+++ b/xsdata/formats/dataclass/context.py
@@ -54,6 +54,16 @@ class XmlContext:
         self.xsi_cache.clear()
         self.sys_modules = 0
 
+    def get_builder(
+        self, globalns: Optional[Dict[str, Callable]] = None
+    ) -> XmlMetaBuilder:
+        return XmlMetaBuilder(
+            class_type=self.class_type,
+            element_name_generator=self.element_name_generator,
+            attribute_name_generator=self.attribute_name_generator,
+            globalns=globalns,
+        )
+
     def fetch(
         self,
         clazz: Type,
@@ -83,18 +93,10 @@ class XmlContext:
             return
 
         self.xsi_cache.clear()
-        builder = XmlMetaBuilder(
-            class_type=self.class_type,
-            element_name_generator=self.element_name_generator,
-            attribute_name_generator=self.attribute_name_generator,
-        )
+        builder = self.get_builder()
         for clazz in self.get_subclasses(object):
-            # This is new!
-            if clazz.__module__.startswith("_pytest"):
-                continue
-
             if self.class_type.is_model(clazz):
-                meta = builder.build_class_meta(clazz, None)
+                meta = builder.build_class_meta(clazz)
 
                 if meta.target_qname:
                     self.xsi_cache[meta.target_qname].append(clazz)
@@ -187,14 +189,8 @@ class XmlContext:
         :param clazz: A dataclass type
         :param parent_ns: The inherited parent namespace
         """
-
         if clazz not in self.cache:
-            builder = XmlMetaBuilder(
-                class_type=self.class_type,
-                element_name_generator=self.element_name_generator,
-                attribute_name_generator=self.attribute_name_generator,
-                globalns=globalns,
-            )
+            builder = self.get_builder(globalns)
             self.cache[clazz] = builder.build(clazz, parent_ns)
         return self.cache[clazz]
 
@@ -214,7 +210,14 @@ class XmlContext:
             meta = self.build(clazz)
             local_names = {var.local_name for var in meta.get_all_vars()}
             return not names.difference(local_names)
-        except XmlContextError:
+        except (XmlContextError, NameError):
+            # The dataclass includes unsupported typing annotations
+            # Let's remove it from xsi_cache
+            builder = self.get_builder()
+            target_qname = builder.build_class_meta(clazz).target_qname
+            if target_qname and target_qname in self.xsi_cache:
+                self.xsi_cache[target_qname].remove(clazz)
+
             return False
 
     @classmethod
diff --git a/xsdata/formats/dataclass/models/builders.py b/xsdata/formats/dataclass/models/builders.py
index b32e5a55..040781c6 100644
--- a/xsdata/formats/dataclass/models/builders.py
+++ b/xsdata/formats/dataclass/models/builders.py
@@ -145,7 +145,7 @@ class XmlMetaBuilder:
                 yield var
 
     def build_class_meta(
-        self, clazz: Type, parent_namespace: Optional[str]
+        self, clazz: Type, parent_namespace: Optional[str] = None
     ) -> ClassMeta:
         """
         Fetch the class meta options and merge defaults.


Test patch:
diff --git a/tests/formats/dataclass/test_context.py b/tests/formats/dataclass/test_context.py
index 42ab7ca8..507ed4bf 100644
--- a/tests/formats/dataclass/test_context.py
+++ b/tests/formats/dataclass/test_context.py
@@ -1,5 +1,6 @@
 import copy
 from dataclasses import make_dataclass
+from pathlib import Path
 from unittest import mock
 
 from tests.fixtures.artists import Artist
@@ -78,6 +79,23 @@ class XmlContextTests(FactoryTestCase):
         field_names.update({"please", "dont", "exist"})  # Test matching with more
         self.assertIsNone(self.ctx.find_type_by_fields(field_names))
 
+    def test_local_names_match_remove_clazz_from_cache_on_error(self):
+        undefined = make_dataclass("UndefinedType", [("content", "Literal['yes']")])
+        unsupported = make_dataclass("UndefinedType", [("content", Path)])
+        uncached = make_dataclass("Uncached", [("content", Path)])
+
+        builder = self.ctx.get_builder()
+        for clazz in (undefined, unsupported):
+            meta = builder.build_class_meta(clazz)
+            self.ctx.xsi_cache[meta.target_qname].append(clazz)
+
+        self.assertFalse(self.ctx.local_names_match({"content"}, undefined))
+        self.assertFalse(self.ctx.local_names_match({"content"}, unsupported))
+        self.assertFalse(self.ctx.local_names_match({"content"}, uncached))
+
+        total = [x for types in self.ctx.cache.values() for x in types]
+        self.assertEqual(0, len(total))
+
     def test_find_subclass(self):
         a = make_dataclass("A", fields=[])
         b = make_dataclass("B", fields=[], bases=(a,))