Repository: tefra/xsdata

Issue:
wsdl generation output dataclass missing namespace
Here's the wsdl parts for loginResponse

```xml
    <wsdl:message name="loginResponse">
        <part name="Result" type="nas:Result"/>
    </wsdl:message>
    <wsdl:portType name="NetworkManagementApi">
        <wsdl:operation name="login">
            <wsdl:documentation></wsdl:documentation>
            <wsdl:input message="nas:loginRequest"/>
            <wsdl:output message="nas:loginResponse"/>
            <wsdl:fault name="error" message="nas:nasFaultMessage"/>
        </wsdl:operation>
    </wsdl:binding>
```

Results in 
```python
@dataclass
class LoginResponse:
    class Meta:
        name = "loginResponse"

    result: Optional[Result] = field(
        default=None,
        metadata={
            "name": "Result",
            "type": "Element",
            "namespace": "",
        }
    )
```

but should have `"namespace": "http://microfocus.com/nas/2020/08"` in order for parser to be able to parse response [envelope](https://github.com/brunnels/xsdata-mfna/blob/main/test_login.py#L31).



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')

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=621&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=621&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=621&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=621&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=621&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=621&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=621&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=621&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=621&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=621&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=621&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=621&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=621) 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=621&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=621&metric=new_duplicated_lines_density&view=list)


codecov[bot]: # [Codecov](https://codecov.io/gh/tefra/xsdata/pull/621?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) Report
> Merging [#621](https://codecov.io/gh/tefra/xsdata/pull/621?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) (125bd91) into [master](https://codecov.io/gh/tefra/xsdata/commit/1cd681598d2235626d0e21716fc9fb885d26e351?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) (1cd6815) will **not change** coverage.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/tefra/xsdata/pull/621/graphs/tree.svg?width=650&height=150&src=pr&token=YzDDLtywvl&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou)](https://codecov.io/gh/tefra/xsdata/pull/621?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou)

```diff
@@            Coverage Diff            @@
##            master      #621   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           94        94           
  Lines         8124      8135   +11     
  Branches      1411      1405    -6     
=========================================
+ Hits          8124      8135   +11     
```


| [Impacted Files](https://codecov.io/gh/tefra/xsdata/pull/621?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) | Coverage Δ | |
|---|---|---|
| [xsdata/codegen/handlers/attribute\_type.py](https://codecov.io/gh/tefra/xsdata/pull/621/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou#diff-eHNkYXRhL2NvZGVnZW4vaGFuZGxlcnMvYXR0cmlidXRlX3R5cGUucHk=) | `100.00% <100.00%> (ø)` | |
| [xsdata/codegen/mappers/definitions.py](https://codecov.io/gh/tefra/xsdata/pull/621/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou#diff-eHNkYXRhL2NvZGVnZW4vbWFwcGVycy9kZWZpbml0aW9ucy5weQ==) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/tefra/xsdata/pull/621?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/tefra/xsdata/pull/621?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou). Last update [1cd6815...125bd91](https://codecov.io/gh/tefra/xsdata/pull/621?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou).


Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 16bbf30d..53158e30 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -2,7 +2,7 @@ exclude: tests/fixtures
 
 repos:
   - repo: https://github.com/asottile/pyupgrade
-    rev: v2.29.0
+    rev: v2.29.1
     hooks:
       - id: pyupgrade
         args: [--py37-plus]
@@ -11,7 +11,7 @@ repos:
     hooks:
       - id: reorder-python-imports
   - repo: https://github.com/ambv/black
-    rev: 21.10b0
+    rev: 21.11b1
     hooks:
       - id: black
   - repo: https://gitlab.com/pycqa/flake8
@@ -42,11 +42,11 @@ repos:
       - id: mypy
         additional_dependencies: [tokenize-rt, types-requests, types-Jinja2, types-click, types-docutils]
   - repo: https://github.com/asottile/setup-cfg-fmt
-    rev: v1.19.0
+    rev: v1.20.0
     hooks:
       - id: setup-cfg-fmt
         args: ["--max-py-version=3.10"]
   - repo: https://github.com/PyCQA/doc8
-    rev: 0.9.1
+    rev: 0.10.1
     hooks:
       - id: doc8
diff --git a/xsdata/codegen/handlers/attribute_type.py b/xsdata/codegen/handlers/attribute_type.py
index 8fe86526..ec4c0b11 100644
--- a/xsdata/codegen/handlers/attribute_type.py
+++ b/xsdata/codegen/handlers/attribute_type.py
@@ -144,6 +144,7 @@ class AttributeTypeHandler(RelativeHandlerInterface):
             if source.nillable:
                 attr.restrictions.nillable = True
             self.set_circular_flag(source, target, attr_type)
+            self.detect_lazy_namespace(source, target, attr)
 
     @classmethod
     def copy_attribute_properties(
@@ -227,3 +228,26 @@ class AttributeTypeHandler(RelativeHandlerInterface):
 
         if datatype in (DataType.NMTOKENS, DataType.IDREFS):
             attr.restrictions.tokens = True
+
+    @classmethod
+    def detect_lazy_namespace(cls, source: Class, target: Class, attr: Attr):
+        """
+        Override attr namespace with the source namespace when during the
+        initial mapping the namespace detection wasn't possible.
+
+        Case 1: WSDL message part type can be an element, complex or
+        simple type, we can't do the detection during the initial
+        mapping to class objects.
+        """
+        if attr.namespace == "##lazy":
+            logger.warning(
+                "Overriding field type namespace %s:%s (%s)",
+                target.name,
+                attr.name,
+                source.namespace,
+            )
+
+            if not source.namespace:
+                attr.namespace = "" if target.namespace else None
+            else:
+                attr.namespace = source.namespace
diff --git a/xsdata/codegen/mappers/definitions.py b/xsdata/codegen/mappers/definitions.py
index 1ea77bd2..9a70adea 100644
--- a/xsdata/codegen/mappers/definitions.py
+++ b/xsdata/codegen/mappers/definitions.py
@@ -248,15 +248,18 @@ class DefinitionsMapper:
     ) -> Class:
         """Step 6.2: Build the input/output message class of an rpc style
         operation."""
-        message_name = text.suffix(port_type_message.message)
-        definition_message = definitions.find_message(message_name)
+        prefix, name = text.split(port_type_message.message)
+
+        definition_message = definitions.find_message(name)
         ns_map = definition_message.ns_map.copy()
+        source_namespace = ns_map.get(prefix)
 
         assert definitions.location is not None
 
         return Class(
-            qname=namespaces.build_qname(definitions.target_namespace, message_name),
-            status=Status.FLATTENED,
+            qname=namespaces.build_qname(source_namespace, name),
+            namespace=source_namespace,
+            status=Status.RAW,
             tag=Tag.ELEMENT,
             location=definitions.location,
             ns_map=ns_map,
@@ -357,8 +360,10 @@ class DefinitionsMapper:
             namespace = part.ns_map.get(prefix)
             type_qname = namespaces.build_qname(namespace, type_name)
             native = namespace == Namespace.XS.uri
-            namespace = "" if part.type else namespace
-
+            # If part has a type it could reference an element or a complex type or
+            # a simple type, we can't make that detection yet, postpone it till the
+            # classes processing.
+            namespace = "##lazy" if part.type else namespace
             yield cls.build_attr(name, type_qname, namespace=namespace, native=native)
 
     @classmethod
@@ -392,6 +397,9 @@ class DefinitionsMapper:
     ) -> Attr:
         """Builder method for attributes."""
         occurs = 1 if default is not None else None
+        if native:
+            namespace = ""
+
         return Attr(
             tag=Tag.ELEMENT,
             name=name,


Test patch:
diff --git a/tests/codegen/handlers/test_attribute_type.py b/tests/codegen/handlers/test_attribute_type.py
index d9761835..5cde1d9a 100644
--- a/tests/codegen/handlers/test_attribute_type.py
+++ b/tests/codegen/handlers/test_attribute_type.py
@@ -414,3 +414,21 @@ class AttributeTypeHandlerTests(FactoryTestCase):
         attr = AttrFactory.native(DataType.HEX_BINARY)
         self.processor.update_restrictions(attr, attr.types[0].datatype)
         self.assertEqual("base16", attr.restrictions.format)
+
+    def test_detect_lazy_namespace(self):
+        source = ClassFactory.create(namespace="foo", tag=Tag.ELEMENT)
+        target = ClassFactory.create()
+        attr = AttrFactory.create(namespace="##lazy")
+
+        self.processor.detect_lazy_namespace(source, target, attr)
+        self.assertEqual("foo", attr.namespace)
+
+        attr.namespace = "##lazy"
+        source.namespace = None
+        self.processor.detect_lazy_namespace(source, target, attr)
+        self.assertIsNone(attr.namespace)
+
+        attr.namespace = "##lazy"
+        target.namespace = "foo"
+        self.processor.detect_lazy_namespace(source, target, attr)
+        self.assertEqual("", attr.namespace)
diff --git a/tests/codegen/mappers/test_definitions.py b/tests/codegen/mappers/test_definitions.py
index a285934c..e89333e4 100644
--- a/tests/codegen/mappers/test_definitions.py
+++ b/tests/codegen/mappers/test_definitions.py
@@ -694,7 +694,7 @@ class DefinitionsMapperTests(FactoryTestCase):
                 "arg0", str(DataType.STRING), namespace="", native=True
             ),
             DefinitionsMapper.build_attr(
-                "arg1", build_qname("boo", "cafe"), namespace="", native=False
+                "arg1", build_qname("boo", "cafe"), namespace="##lazy", native=False
             ),
         ]
         self.assertIsInstance(result, Generator)
@@ -714,10 +714,11 @@ class DefinitionsMapperTests(FactoryTestCase):
         mock_create_message_attributes.return_value = attrs
         actual = DefinitionsMapper.build_message_class(definitions, port_type_message)
         expected = Class(
-            qname=build_qname("xsdata", "bar"),
-            status=Status.FLATTENED,
+            qname=build_qname("bar", "bar"),
+            status=Status.RAW,
             tag=Tag.ELEMENT,
             location="foo.wsdl",
+            namespace="bar",
             ns_map=message.ns_map,
             attrs=attrs,
         )
diff --git a/tests/fixtures/hello/hello.py b/tests/fixtures/hello/hello.py
index abd9f96e..f733ed9e 100644
--- a/tests/fixtures/hello/hello.py
+++ b/tests/fixtures/hello/hello.py
@@ -36,6 +36,7 @@ class HelloError:
 class GetHelloAsString:
     class Meta:
         name = "getHelloAsString"
+        namespace = "http://hello/"
 
     arg0: Optional[str] = field(
         default=None,
@@ -50,6 +51,7 @@ class GetHelloAsString:
 class GetHelloAsStringResponse:
     class Meta:
         name = "getHelloAsStringResponse"
+        namespace = "http://hello/"
 
     return_value: Optional[str] = field(
         default=None,