#!/usr/bin/env python3 from decimal import Decimal, getcontext # ~ getcontext().prec = 6 import lxml.etree as ET from requests.structures import CaseInsensitiveDict as CIDict NS_CFDI = { 'cfdi': 'http://www.sat.gob.mx/cfd/3', 'tfd': 'http://www.sat.gob.mx/TimbreFiscalDigital', 'nomina12': 'http://www.sat.gob.mx/nomina12', } PRE = '/cfdi:Comprobante' class CfdiRead(object): def __init__(self, source): self._source = source self._data = {} self._error = '' self._rfc_emisor = '' self._rfc_receptor = '' self._parse() @property def source(self): return self._source @property def data(self): return self._data @property def rfc_emisor(self): return self._rfc_emisor @property def rfc_receptor(self): return self._rfc_receptor @property def error(self): return self._error def _parse(self): self._tree = ET.fromstring(self.source) self._data['cfdi'] = dict(self._tree.attrib) node_name = f'{PRE}/cfdi:Emisor' self._data['emisor'] = self._get_attr(node_name) self._rfc_emisor = self._data['emisor']['Rfc'] node_name = f'{PRE}/cfdi:Receptor' self._data['receptor'] = self._get_attr(node_name) self._rfc_receptor = self._data['receptor']['Rfc'] node_name = f'{PRE}/cfdi:Complemento/tfd:TimbreFiscalDigital' self._data['timbre'] = self._get_attr(node_name) self._parse_details() return def _get_attr(self, node_name): node = self._tree.xpath(node_name, namespaces=NS_CFDI)[0] attr = dict(node.attrib) return attr def _parse_details(self): node_name = f'{PRE}/cfdi:Conceptos/cfdi:Concepto' details = self._tree.xpath(node_name, namespaces=NS_CFDI) rows = [] for detail in details: row = dict(detail.attrib) for k, v in row.items(): if k in ('Cantidad', 'ValorUnitario', 'Descuento', 'Importe'): row[k] = Decimal(v) # ~ row['taxes'] = self._get_taxes(detail) rows.append(row) self._data['conceptos'] = rows return class CfdiWrite(object): pass