Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance fields restriction inheritance #417

Merged
merged 5 commits into from
Feb 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ repos:
- id: docformatter
args: ["--in-place", "--pre-summary-newline"]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.800
rev: v0.812
hooks:
- id: mypy
additional_dependencies: [tokenize-rt]
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ dev =
pytest-cov
tox
docs =
sphinx==3.4.3
sphinx
sphinx-autobuild
sphinx-autodoc-typehints
sphinx-copybutton
Expand Down
4 changes: 3 additions & 1 deletion tests/codegen/handlers/test_class_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,9 @@ def test_add_default_attribute(self):
ClassExtensionHandler.add_default_attribute(item, extension)

expected.types.append(xs_int)
expected_restrictions = Restrictions(tokens=True, required=True)
expected_restrictions = Restrictions(
tokens=True, required=True, min_occurs=1, max_occurs=1
)

self.assertEqual(2, len(item.attrs))
self.assertEqual(0, len(item.extensions))
Expand Down
12 changes: 12 additions & 0 deletions tests/codegen/parsers/test_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,8 +403,20 @@ def test_end_schema(
mock_resolve_schemas_locations,
):
schema = Schema()
schema.elements.append(Element())
schema.elements.append(Element())
schema.elements.append(Element())

for el in schema.elements:
self.assertEqual(1, el.min_occurs)
self.assertEqual(1, el.max_occurs)

self.parser.end_schema(schema)

for el in schema.elements:
self.assertIsNone(el.min_occurs)
self.assertIsNone(el.max_occurs)

self.parser.end_schema(ComplexType())

mock_set_schema_forms.assert_called_once_with(schema)
Expand Down
4 changes: 2 additions & 2 deletions tests/codegen/test_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,9 @@ def test_resolve_imports(
):
class_life = ClassFactory.create(qname="life")
import_names = [
"foo", # cool
"foo_1", # cool
"bar", # cool
"{another}foo", # another foo
"{another}foo1", # another foo
"{thug}life", # life class exists add alias
"{common}type", # type class doesn't exist add just the name
]
Expand Down
85 changes: 60 additions & 25 deletions tests/codegen/test_sanitizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,6 @@ def test_process_attribute_default_with_enumeration(self):
self.sanitizer.process_attribute_default(target, attr)
self.assertTrue(attr.fixed)

def test_process_attribute_default_with_list_field(self):
target = ClassFactory.create()
attr = AttrFactory.create(fixed=True)
attr.restrictions.max_occurs = 2
self.sanitizer.process_attribute_default(target, attr)
self.assertFalse(attr.fixed)

def test_process_attribute_default_with_optional_field(self):
target = ClassFactory.create()
attr = AttrFactory.create(fixed=True, default=2)
Expand Down Expand Up @@ -208,25 +201,67 @@ def test_find_enum(self):
self.assertIsNone(actual)

def test_process_attribute_restrictions(self):
restrictions = [
Restrictions(min_occurs=0, max_occurs=0, required=True),
Restrictions(min_occurs=0, max_occurs=1, required=True),
Restrictions(min_occurs=1, max_occurs=1, required=False),
Restrictions(max_occurs=2, required=True),
Restrictions(min_occurs=2, max_occurs=2, required=True),
]
expected = [
{},
{},
{"required": True},
{"max_occurs": 2},
{"max_occurs": 2, "min_occurs": 2},
]
required = Restrictions(min_occurs=1, max_occurs=1)
attr = AttrFactory.attribute(restrictions=required.clone())
self.sanitizer.process_attribute_restrictions(attr)
self.assertIsNone(attr.restrictions.min_occurs)
self.assertIsNone(attr.restrictions.max_occurs)

tokens = Restrictions(required=True, tokens=True, min_occurs=1, max_occurs=1)
attr = AttrFactory.element(restrictions=tokens.clone())
self.sanitizer.process_attribute_restrictions(attr)
self.assertFalse(attr.restrictions.required)
self.assertIsNone(attr.restrictions.min_occurs)
self.assertIsNone(attr.restrictions.max_occurs)

attr = AttrFactory.element(restrictions=tokens.clone())
attr.restrictions.max_occurs = 2
self.sanitizer.process_attribute_restrictions(attr)
self.assertFalse(attr.restrictions.required)
self.assertIsNotNone(attr.restrictions.min_occurs)
self.assertIsNotNone(attr.restrictions.max_occurs)

multiple = Restrictions(min_occurs=0, max_occurs=2)
attr = AttrFactory.create(tag=Tag.EXTENSION, restrictions=multiple)
self.sanitizer.process_attribute_restrictions(attr)
self.assertTrue(attr.restrictions.required)
self.assertIsNone(attr.restrictions.min_occurs)
self.assertIsNone(attr.restrictions.max_occurs)

multiple = Restrictions(max_occurs=2, required=True)
attr = AttrFactory.element(restrictions=multiple, fixed=True)
self.sanitizer.process_attribute_restrictions(attr)
self.assertIsNone(attr.restrictions.required)
self.assertEqual(0, attr.restrictions.min_occurs)
self.assertFalse(attr.fixed)

attr = AttrFactory.element(restrictions=required.clone())
self.sanitizer.process_attribute_restrictions(attr)
self.assertTrue(attr.restrictions.required)
self.assertIsNone(attr.restrictions.min_occurs)
self.assertIsNone(attr.restrictions.max_occurs)

restrictions = Restrictions(required=True, min_occurs=0, max_occurs=1)
attr = AttrFactory.element(restrictions=restrictions, default="A", fixed=True)
self.sanitizer.process_attribute_restrictions(attr)
self.assertIsNone(attr.restrictions.required)
self.assertIsNone(attr.restrictions.min_occurs)
self.assertIsNone(attr.restrictions.max_occurs)
self.assertIsNone(attr.default)
self.assertFalse(attr.fixed)

attr = AttrFactory.element(restrictions=required.clone(), default="A")
self.sanitizer.process_attribute_restrictions(attr)
self.assertIsNone(attr.restrictions.required)

attr = AttrFactory.element(restrictions=required.clone(), fixed=True)
self.sanitizer.process_attribute_restrictions(attr)
self.assertIsNone(attr.restrictions.required)

for idx, res in enumerate(restrictions):
attr = AttrFactory.create(restrictions=res)
self.sanitizer.process_attribute_restrictions(attr)
self.assertEqual(expected[idx], res.asdict())
attr = AttrFactory.element(restrictions=required.clone())
attr.restrictions.nillable = True
self.sanitizer.process_attribute_restrictions(attr)
self.assertIsNone(attr.restrictions.required)

def test_sanitize_duplicate_attribute_names(self):
attrs = [
Expand Down
6 changes: 6 additions & 0 deletions tests/fixtures/defxmlschema/chapter03prod.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
class ProdNumType:
value: Optional[int] = field(
default=None,
metadata={
"required": True,
}
)
id: Optional[str] = field(
default=None,
Expand All @@ -24,6 +27,9 @@ class ProdNumType:
class SizeType:
value: Optional[int] = field(
default=None,
metadata={
"required": True,
}
)
system: Optional[str] = field(
default=None,
Expand Down
3 changes: 3 additions & 0 deletions tests/fixtures/defxmlschema/chapter04prod.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ class ColorType:
class SizeType:
value: Optional[int] = field(
default=None,
metadata={
"required": True,
}
)
system: Optional[str] = field(
default=None,
Expand Down
4 changes: 3 additions & 1 deletion tests/fixtures/defxmlschema/chapter05prod.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
class SizeType:
value: Optional[int] = field(
default=None,
metadata={
"required": True,
}
)
system: Optional[str] = field(
default=None,
Expand Down Expand Up @@ -40,7 +43,6 @@ class ProductType:
metadata={
"type": "Element",
"namespace": "",
"required": True,
"nillable": True,
}
)
Expand Down
3 changes: 3 additions & 0 deletions tests/fixtures/defxmlschema/chapter12.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ class DescriptionType:
class SizeType:
value: Optional[int] = field(
default=None,
metadata={
"required": True,
}
)
system: Optional[str] = field(
default=None,
Expand Down
3 changes: 3 additions & 0 deletions tests/fixtures/defxmlschema/chapter13.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ class RestrictedProductType:
class SizeType:
value: Optional[int] = field(
default=None,
metadata={
"required": True,
}
)
system: Optional[str] = field(
default=None,
Expand Down
3 changes: 3 additions & 0 deletions tests/fixtures/defxmlschema/chapter15.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
class SizeType:
value: Optional[int] = field(
default=None,
metadata={
"required": True,
}
)
system: Optional[str] = field(
default=None,
Expand Down
6 changes: 6 additions & 0 deletions tests/fixtures/defxmlschema/chapter16.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ class ColorType:
class HatSizeType:
value: Optional[str] = field(
default=None,
metadata={
"required": True,
}
)
system: Optional[str] = field(
default=None,
Expand Down Expand Up @@ -49,6 +52,9 @@ class ProductType:
class ShirtSizeType:
value: Optional[int] = field(
default=None,
metadata={
"required": True,
}
)
system: Optional[str] = field(
default=None,
Expand Down
3 changes: 3 additions & 0 deletions tests/fixtures/defxmlschema/chapter17.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ class ColorType:
class PriceType:
value: Optional[Decimal] = field(
default=None,
metadata={
"required": True,
}
)
currency: Optional[str] = field(
default=None,
Expand Down
2 changes: 1 addition & 1 deletion tests/models/xsd/test_attribute.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def test_get_restrictions(self):
self.assertEqual({}, obj.get_restrictions())

obj.use = UseType.REQUIRED
expected = {"max_occurs": 1, "min_occurs": 1, "required": True}
expected = {"required": True}
self.assertEqual(expected, obj.get_restrictions())

obj.use = UseType.PROHIBITED
Expand Down
8 changes: 8 additions & 0 deletions tests/utils/test_text.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from unittest import TestCase

from xsdata.utils.text import alnum
from xsdata.utils.text import camel_case
from xsdata.utils.text import capitalize
from xsdata.utils.text import mixed_case
Expand Down Expand Up @@ -99,3 +100,10 @@ def test_split_words(self):
self.assertEqual(["user"], split_words("__user"))
self.assertEqual(["TMessage", "DB"], split_words("TMessageDB"))
self.assertEqual(["GLOBAL", "REF"], split_words("GLOBAL-REF"))

def test_alnum(self):
self.assertEqual("foo1", alnum("foo 1"))
self.assertEqual("foo1", alnum(" foo_1 "))
self.assertEqual("foo1", alnum("\tfoo*1"))
self.assertEqual("foo1", alnum(" foo*1"))
self.assertEqual("βιβλίο1", alnum(" βιβλίο*1"))
2 changes: 2 additions & 0 deletions xsdata/codegen/handlers/class_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,5 +271,7 @@ def get_or_create_attribute(cls, target: Class, name: str, tag: str) -> Attr:
return attr

attr = Attr(name=name, tag=tag)
attr.restrictions.min_occurs = 1
attr.restrictions.max_occurs = 1
target.attrs.insert(0, attr)
return attr
15 changes: 9 additions & 6 deletions xsdata/codegen/mappers/definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from xsdata.codegen.models import Attr
from xsdata.codegen.models import AttrType
from xsdata.codegen.models import Class
from xsdata.codegen.models import Restrictions
from xsdata.codegen.models import Status
from xsdata.formats.dataclass.models.generics import AnyElement
from xsdata.logger import logger
Expand All @@ -26,7 +27,7 @@
from xsdata.utils import text
from xsdata.utils.collections import first
from xsdata.utils.namespaces import build_qname
from xsdata.utils.namespaces import split_qname
from xsdata.utils.namespaces import local_name


class DefinitionsMapper:
Expand Down Expand Up @@ -215,10 +216,10 @@ def build_envelope_class(

for ext in binding_message.extended_elements:
assert ext.qname is not None
local_name = split_qname(ext.qname)[1].title()
inner = cls.build_inner_class(target, local_name)
class_name = local_name(ext.qname).title()
inner = cls.build_inner_class(target, class_name)

if style == "rpc" and local_name == "Body":
if style == "rpc" and class_name == "Body":
namespace = ext.attributes.get("namespace")
attrs = cls.map_port_type_message(port_type_message, namespace)
else:
Expand Down Expand Up @@ -299,7 +300,7 @@ def map_binding_message_parts(
parts.extend(extended.attributes["parts"].split())

if "message" in extended.attributes:
message_name = split_qname(extended.attributes["message"])[1]
message_name = local_name(extended.attributes["message"])
else:
message_name = text.suffix(message)

Expand Down Expand Up @@ -351,7 +352,7 @@ def operation_namespace(cls, config: Dict) -> Optional[str]:
def attributes(cls, elements: Iterator[AnyElement]) -> Dict:
"""Return all attributes from all extended elements as a dictionary."""
return {
split_qname(qname)[1]: value
local_name(qname): value
for element in elements
if isinstance(element, AnyElement)
for qname, value in element.attributes.items()
Expand All @@ -368,10 +369,12 @@ def build_attr(
default: Optional[str] = None,
) -> Attr:
"""Builder method for attributes."""
occurs = 1 if default is not None else None
return Attr(
tag=Tag.ELEMENT,
name=name,
namespace=namespace,
default=default,
types=[AttrType(qname=qname, forward=forward, native=native)],
restrictions=Restrictions(min_occurs=occurs, max_occurs=occurs),
)
9 changes: 5 additions & 4 deletions xsdata/codegen/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
from xsdata.models.enums import Tag
from xsdata.models.mixins import ElementBase
from xsdata.utils.namespaces import build_qname
from xsdata.utils.namespaces import split_qname
from xsdata.utils.namespaces import local_name
from xsdata.utils.namespaces import target_uri

xml_type_map = {
Tag.ANY: XmlType.WILDCARD,
Expand Down Expand Up @@ -209,7 +210,7 @@ class AttrType:
@property
def name(self) -> str:
"""Shortcut for qname local name."""
return split_qname(self.qname)[1]
return local_name(self.qname)

@property
def is_dependency(self) -> bool:
Expand Down Expand Up @@ -431,11 +432,11 @@ class Class:
@property
def name(self) -> str:
"""Shortcut for qname local name."""
return split_qname(self.qname)[1]
return local_name(self.qname)

@property
def target_namespace(self) -> Optional[str]:
return split_qname(self.qname)[0]
return target_uri(self.qname)

@property
def has_suffix_attr(self) -> bool:
Expand Down
Loading