From 9e831b26061aae6cc84fd9b1654fceb58c5ed41c Mon Sep 17 00:00:00 2001 From: Arnaud Morvan Date: Thu, 2 Aug 2018 17:28:18 +0200 Subject: [PATCH] Correctly return nillable for relations in md.xsd --- c2cgeoportal/lib/dbreflection.py | 17 ++++++++++++----- .../tests/functional/test_dbreflection.py | 14 ++++++++++---- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/c2cgeoportal/lib/dbreflection.py b/c2cgeoportal/lib/dbreflection.py index 9a0591d7d8..fc7dde8b58 100644 --- a/c2cgeoportal/lib/dbreflection.py +++ b/c2cgeoportal/lib/dbreflection.py @@ -70,9 +70,10 @@ class an engine, required for the reflection. class _AssociationProxy(object): # A specific "association proxy" implementation - def __init__(self, target, value_attr): + def __init__(self, target, value_attr, nullable=True): self.target = target self.value_attr = value_attr + self.nullable = nullable def __get__(self, obj, type_=None): if obj is None: @@ -80,7 +81,7 @@ def __get__(self, obj, type_=None): # and class levels we could return an SQL expression here. # The code of hybrid_property in SQLAlchemy illustrates # how to do that. - raise AttributeError # pragma: no cover + return self target = getattr(obj, self.target) return getattr(target, self.value_attr) if target else None @@ -109,8 +110,9 @@ def xsd_sequence_callback(tb, cls): target_cls = relationship_property.argument query = DBSession.query(getattr(target_cls, p.value_attr)) attrs = {} - attrs["minOccurs"] = str(0) - attrs["nillable"] = "true" + if p.nullable: + attrs["minOccurs"] = str(0) + attrs["nillable"] = "true" attrs["name"] = k with tag(tb, "xsd:element", attrs) as tb: with tag(tb, "xsd:simpleType") as tb: @@ -247,7 +249,12 @@ def _add_association_proxy(cls, col): lazy="immediate") setattr(cls, rel, relationship_) - setattr(cls, proxy, _AssociationProxy(rel, "name")) + nullable = True + cls_column_property = getattr(cls, col.name).property + for column in cls_column_property.columns: + nullable = nullable and column.nullable + + setattr(cls, proxy, _AssociationProxy(rel, "name", nullable=nullable)) if cls.__add_properties__ is None: cls.__add_properties__ = [proxy] diff --git a/c2cgeoportal/tests/functional/test_dbreflection.py b/c2cgeoportal/tests/functional/test_dbreflection.py index c7674cee17..aad253d4ed 100644 --- a/c2cgeoportal/tests/functional/test_dbreflection.py +++ b/c2cgeoportal/tests/functional/test_dbreflection.py @@ -96,7 +96,8 @@ def _create_table(self, tablename): ), Column( "child2_id", types.Integer, - ForeignKey("public.{0!s}_child.id".format(tablename)) + ForeignKey("public.{0!s}_child.id".format(tablename)), + nullable=False ), Column("point", Geometry("POINT")), Column("linestring", Geometry("LINESTRING")), @@ -122,7 +123,7 @@ def test_get_class_nonexisting_table(self): def test_get_class(self): from geoalchemy2 import Geometry import c2cgeoportal.lib.dbreflection - from c2cgeoportal.lib.dbreflection import get_class + from c2cgeoportal.lib.dbreflection import get_class, _AssociationProxy self._create_table("table_a") modelclass = get_class("table_a") @@ -139,6 +140,11 @@ def test_get_class(self): self.assertTrue(isinstance(modelclass.multilinestring.type, Geometry)) self.assertTrue(isinstance(modelclass.multipolygon.type, Geometry)) + self.assertTrue(isinstance(modelclass.child1, _AssociationProxy)) + self.assertTrue(modelclass.child1.nullable) + self.assertTrue(isinstance(modelclass.child2, _AssociationProxy)) + self.assertFalse(modelclass.child2.nullable) + # test the Table object table = modelclass.__table__ self.assertTrue("id" in table.c) @@ -257,7 +263,7 @@ class Parent(Base): child1_ = relationship(Child, primaryjoin=(child1_id == Child.id)) child1 = _AssociationProxy("child1_", "name") child2_ = relationship(Child, primaryjoin=(child2_id == Child.id)) - child2 = _AssociationProxy("child2_", "name") + child2 = _AssociationProxy("child2_", "name", nullable=False) Child.__table__.create() Parent.__table__.create() @@ -295,7 +301,7 @@ def test_xsd_sequence_callback(self): '' '' '' - '' + '' '' '' ''