xsdata and --compound-field
Na nova branch master-xsdata que usa xsdata, temos um pequeno problema com o elemento IPI da tag Imposto. Da forma como é o xsd, ou precisa de um patch de 1 linha no xsdata, ou precisa usar a opçâo --compound-field do xsdata. Eu detalhei o problema aqui: https://github.com/tefra/xsdata/issues/666
De inicio, pode parecer uma boa usar a opção --compound-field já que resolve o problema no binding gerido como aqui. Porem tem um outro lado da moeda: quando usar essa opção, tem pelo menos 2 campos (IPI do Imposto e um outro no Transp) que viram então um campo composto com uma hierarquia mais funda:
Sem a opção --compound-field e com meu patch https://github.com/tefra/xsdata/issues/666:
@dataclass
class Imposto:
v_tot_trib: Optional[str] = field(
default=None,
metadata={
"name": "vTotTrib",
"type": "Element",
"namespace": "http://www.portalfiscal.inf.br/nfe",
"white_space": "preserve",
"pattern": r"0|0\.[0-9]{2}|[1-9]{1}[0-9]{0,12}(\.[0-9]{2})?",
}
)
icms: Optional["Tnfe.InfNfe.Det.Imposto.Icms"] = field(
default=None,
metadata={
"name": "ICMS",
"type": "Element",
"namespace": "http://www.portalfiscal.inf.br/nfe",
}
)
ipi: Optional[Tipi] = field(
default=None,
metadata={
"name": "IPI",
"type": "Element",
"namespace": "http://www.portalfiscal.inf.br/nfe",
}
)
Agora com a opção --compound-field e sem meu patch:
@dataclass
class Imposto:
v_tot_trib: Optional[str] = field(
default=None,
metadata={
"name": "vTotTrib",
"type": "Element",
"namespace": "http://www.portalfiscal.inf.br/nfe",
"white_space": "preserve",
"pattern": r"0|0\.[0-9]{2}|[1-9]{1}[0-9]{0,12}(\.[0-9]{2})?",
}
)
icms: Optional["Tnfe.InfNfe.Det.Imposto.Icms"] = field(
default=None,
metadata={
"name": "ICMS",
"type": "Element",
"namespace": "http://www.portalfiscal.inf.br/nfe",
}
)
choice: List[object] = field(
default_factory=list,
metadata={
"type": "Elements",
"choices": (
{
"name": "IPI",
"type": Tipi,
"namespace": "http://www.portalfiscal.inf.br/nfe",
},
{
"name": "II",
"type": Type["Tnfe.InfNfe.Det.Imposto.Ii"],
"namespace": "http://www.portalfiscal.inf.br/nfe",
},
{
"name": "ISSQN",
"type": Type["Tnfe.InfNfe.Det.Imposto.Issqn"],
"namespace": "http://www.portalfiscal.inf.br/nfe",
},
{
"name": "PIS",
"type": Type["Tnfe.InfNfe.Det.Imposto.Pis"],
"namespace": "http://www.portalfiscal.inf.br/nfe",
},
{
"name": "PISST",
"type": Type["Tnfe.InfNfe.Det.Imposto.Pisst"],
"namespace": "http://www.portalfiscal.inf.br/nfe",
},
{
"name": "COFINS",
"type": Type["Tnfe.InfNfe.Det.Imposto.Cofins"],
"namespace": "http://www.portalfiscal.inf.br/nfe",
},
{
"name": "COFINSST",
"type": Type["Tnfe.InfNfe.Det.Imposto.Cofinsst"],
"namespace": "http://www.portalfiscal.inf.br/nfe",
},
{
"name": "ICMSUFDest",
"type": Type["Tnfe.InfNfe.Det.Imposto.Icmsufdest"],
"namespace": "http://www.portalfiscal.inf.br/nfe",
},
),
"max_occurs": 9,
}
)
Nesse caso o xsdata tem uma espece de gestão de choice.
O grande ponto é que nos temos um interesso não apenas nos bindings Python mas tb nos modelos Odoo geridos pelo plugin https://github.com/akretion/xsdata-odoo
Esses modelos Odoo são de persistência e é interessante que seja muito mais "planos" do que os modelos super fundos do XSD que servem apenas para validação XSD. E usar a opção --compound-field iria atrapalhar nisso porque nos obrigaria a repensar o mapeamento entre um campo Odoo e um campo de um binding dessa lib, criaria mais complexidade no mapping.
Se essa opção tb lidava com as verdadeiras tags choice do xsd como fazia o generateDS (que nos interessa apenas nas visões automáticas, ou seja muito pouco hoje), eu bancaria essa complexidade. Mas não é o caso, seria uma complexidade meio inútil.
Eu fiz uma busca exaustiva e o problema acontece apenas com essa tag IPI na dezena de esquemas de NFe que temos nessa branch. Com tudo o meu fix aqui https://github.com/tefra/xsdata/issues/666 resolve isso perfeitamente (eu gerei tudo de novo e vi que o impacto era apenas o esperado aqui).
Nisso minha escolha é de usar esse patch de uma linha no xsdata ou então no código Python gerido apenas pela tag IPI.
cc @mbcosta @renatonlima @marcelsavegnago @netosjb @felipemotter
O fix ainda é necessario, mas no xsdata o nome do arquivo mudou. Eu refiz o commit xsdata aqui então: https://github.com/akretion/xsdata/commit/933ddc0b23264fec9910b535cd9199a11aff4eb4
atualização: o método process foi ré-escrito na futura versão do xsdata: https://github.com/tefra/xsdata/commit/4dc9b41f6e85616927f74bddead80ebee33a995b
agora tem que aplicar um patch no metodo: merge_duplicate_attrs
@classmethod
def merge_duplicate_attrs(self, target: Class):
result: List[Attr] = []
for attr in target.attrs:
pos = collections.find(result, attr)
existing = result[pos] if pos > -1 else None
if not existing:
result.append(attr)
elif not (attr.is_attribute or attr.is_enumeration):
existing.help = existing.help or attr.help
e_res = existing.restrictions
a_res = attr.restrictions
min_occurs = e_res.min_occurs or 0
max_occurs = e_res.max_occurs or 1
attr_min_occurs = a_res.min_occurs or 0
attr_max_occurs = a_res.max_occurs or 1
e_res.min_occurs = min(min_occurs, attr_min_occurs)
e_res.max_occurs = min(max_occurs, attr_max_occurs) # this is the patch
if a_res.sequence is not None:
e_res.sequence = a_res.sequence
existing.fixed = False
existing.types.extend(attr.types)
target.attrs = result
ClassUtils.cleanup_class(target)