diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 0000000..886484b --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,32 @@ +version: 1.3.{build} + +environment: + matrix: + - python: 35 + - python: 35-x64 + - python: 36 + - python: 36-x64 + +install: + - SET PATH=C:\\Python%PYTHON%;c:\\Python%PYTHON%\\scripts;%PATH% + - python -m pip.__main__ install -U pip wheel setuptools + - pip install -r requirements.txt + +build: off +build_script: + # configure version + - ps: >- + If ($env:APPVEYOR_REPO_TAG -Eq "true" ) { + $version = "$env:APPVEYOR_REPO_TAG_NAME" + } Else { + $version = "$env:APPVEYOR_BUILD_VERSION.dev0" + } + $version | Set-Content version.txt + - python setup.py build bdist_wheel + - ps: Get-ChildItem dist\*.whl | % { pip install $_.FullName } + +test: off +test_script: + - pip list + - py.test -v tests + - ps: Get-ChildItem dist\*.whl | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name } diff --git a/.gitignore b/.gitignore index f171cd6..eff572b 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,4 @@ dist/ *egg*/ docs/_build .vscode/tags -.cache/ +.cache diff --git a/.travis.yml b/.travis.yml index c8bec07..73a0c70 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,15 @@ -dist: precise language: python python: -- '2.7' +- "3.4" +- "3.5" +- "3.6" virtual_env: system_site_packages: true install: - pip install --upgrade pip - pip install -r requirements.txt -script: coverage run --source=pytrustnfe setup.py nosetests +script: + pytest --cov=pytrustnfe before_install: - sudo apt-get update -qq - sudo apt-get install -qq python-dev libffi-dev libxml2-dev libxslt1-dev libssl-dev @@ -19,5 +21,5 @@ deploy: password: secure: wV+DH+WVji4qyCRXxugOsu8/u9MgUN9YggIBozh2Si1z6OlONZVr/oCaJDW8UD+Qg0EF87RbHuEmmlpAZVERAZv5uGsxjSO/NyvAsr99sOlTy9TSLi6TLp4aPnOCgjBTFDWkwkNyDTGYGNfendS7KO2jaHUsr/eDZcpTz42lOfDgpmccz822wwI6Uu1hNC61qlskPkKVzFhHT61/XAgmjHvw1wAMWVmv9/E6J8VAlZoI9/v3K0RTRisB/+0+sSvY86crYyuW/zIEhQJnMu/gfFWDSxNdY+0S3VyFgERn5S7IYlpBPUUlukX5aPXy+OQD2ygeu7w9f6aOSaJZsoyhe4pPXDjA9XNyfiazuZrz51fzhricMvdsMPAcukK/sJzGICAFgOutAjy+nGBkNqA2genKL8gMtJGUrPW5Yq5MGMC7FEgEQi5SgEj+01FgSY5mHlR3qo9bEgXWcxhNL/uZ3C1ElnGNLbyn5hjWzCnMEe70JwfWNQxGgtNm73vrrsZJ7M5wGjrEKVAvTERQegRQm2ObX7YsPmTY+tF15Hxs8GiZ0T/MzpxGe6yAkIutKI0CxpoUMXBnrmcMbn74GT8KWQjS724AA3K5ePO5ogLECxIq3huyB9USeeXmYBhUtcLpKSSH7gA/8vT/tvXK0+/YNTKzIIrOjuZ9IOVrwq2PyUY= on: - branch: master + branch: master3 distributions: "bdist_wheel" diff --git a/README.md b/README.md index d16772c..98ddd7b 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,17 @@ # PyTrustNFe Biblioteca Python que tem por objetivo enviar NFe, NFCe e NFSe no Brasil -[![Coverage Status](https://coveralls.io/repos/danimaribeiro/PyTrustNFe/badge.svg?branch=master)](https://coveralls.io/r/danimaribeiro/PyTrustNFe?branch=master) -[![Code Health](https://landscape.io/github/danimaribeiro/PyTrustNFe/master/landscape.svg?style=flat)](https://landscape.io/github/danimaribeiro/PyTrustNFe/master) -[![Build Status](https://travis-ci.org/danimaribeiro/PyTrustNFe.svg?branch=master)](https://travis-ci.org/danimaribeiro/PyTrustNFe) -[![PyPI version](https://badge.fury.io/py/PyTrustNFe.svg)](https://badge.fury.io/py/PyTrustNFe) +[![Coverage Status](https://coveralls.io/repos/danimaribeiro/PyTrustNFe/badge.svg?branch=master3)](https://coveralls.io/r/danimaribeiro/PyTrustNFe?branch=master3) +[![Code Health](https://landscape.io/github/danimaribeiro/PyTrustNFe/master3/landscape.svg?style=flat)](https://landscape.io/github/danimaribeiro/PyTrustNFe/master3) +[![Build Status](https://travis-ci.org/danimaribeiro/PyTrustNFe.svg?branch=master3)](https://travis-ci.org/danimaribeiro/PyTrustNFe) +[![PyPI version](https://badge.fury.io/py/PyTrustNFe3.svg)](https://badge.fury.io/py/PyTrustNFe3) Dependências: * PyXmlSec * lxml * signxml -* suds -* suds_requests +* suds-jurko +* suds-jurko-requests * reportlab * Jinja2 @@ -19,7 +19,7 @@ NFSe - Cidades atendidas -------------- * [Ariss](cidades/ariss.md) - 4 cidades atendidas * [Simpliss](cidades/simpliss.md) - 18 cidade atendidas - +* [GINFES](cidades/ginfes.md) - 79 cidades atendidas Roadmap -------------- @@ -31,7 +31,6 @@ Compatibilidade [python 2 e 3](https://github.com/danimaribeiro/PyTrustNFe/pull/ Implementar novos provedores de NFSe * [Betha](cidades/betha.md) - 81 cidades atendidas WIP -* [GINFES](cidades/ginfes.md) - 79 cidades atendidas * [WebISS](cidades/webiss.md) - 51 cidades atendidas * [ISSIntel](cidades/issintel.md) - 32 cidades atendidas * [ISSNET](cidades/issnet.md) - 32 cidades atendidas diff --git a/pytrustnfe/Servidores.py b/pytrustnfe/Servidores.py index 8ff98cd..0b3e28d 100644 --- a/pytrustnfe/Servidores.py +++ b/pytrustnfe/Servidores.py @@ -37,8 +37,8 @@ NFE_AMBIENTE_HOMOLOGACAO = 2 NFCE_AMBIENTE_PRODUCAO = 1 NFCE_AMBIENTE_HOMOLOGACAO = 2 -NFE_MODELO = u'55' -NFCE_MODELO = u'65' +NFE_MODELO = '55' +NFCE_MODELO = '65' SIGLA_ESTADO = { '12': 'AC', diff --git a/pytrustnfe/__init__.py b/pytrustnfe/__init__.py index 252e121..093c5c3 100644 --- a/pytrustnfe/__init__.py +++ b/pytrustnfe/__init__.py @@ -12,10 +12,10 @@ class HttpClient(object): def _headers(self, action): return { - u'Content-type': - u'text/xml; charset=utf-8;', - u'Accept': u'application/soap+xml; charset=utf-8', - u'SOAPAction': action + 'Content-type': + 'text/xml; charset=utf-8;', + 'Accept': 'application/soap+xml; charset=utf-8', + 'SOAPAction': action } def post_soap(self, xml_soap, action): diff --git a/pytrustnfe/certificado.py b/pytrustnfe/certificado.py index 4e44ae9..d24732c 100644 --- a/pytrustnfe/certificado.py +++ b/pytrustnfe/certificado.py @@ -2,8 +2,7 @@ # © 2016 Danimar Ribeiro, Trustcode # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - -from uuid import uuid4 +import tempfile from OpenSSL import crypto @@ -13,8 +12,8 @@ class Certificado(object): self.password = password def save_pfx(self): - pfx_temp = '/tmp/' + uuid4().hex - arq_temp = open(pfx_temp, 'w') + pfx_temp = tempfile.mkstemp()[1] + arq_temp = open(pfx_temp, 'wb') arq_temp.write(self.pfx) arq_temp.close() return pfx_temp @@ -28,12 +27,12 @@ def extract_cert_and_key_from_pfx(pfx, password): # PEM formatted certificate cert = crypto.dump_certificate(crypto.FILETYPE_PEM, pfx.get_certificate()) - return cert, key + return cert.decode(), key.decode() def save_cert_key(cert, key): - cert_temp = '/tmp/' + uuid4().hex - key_temp = '/tmp/' + uuid4().hex + cert_temp = tempfile.mkstemp()[1] + key_temp = tempfile.mkstemp()[1] arq_temp = open(cert_temp, 'w') arq_temp.write(cert) diff --git a/pytrustnfe/client.py b/pytrustnfe/client.py index bf76c7b..03adbd2 100644 --- a/pytrustnfe/client.py +++ b/pytrustnfe/client.py @@ -42,14 +42,20 @@ class HttpClient(object): self.cert_path = cert_path self.key_path = key_path - def _headers(self, action): + def _headers(self, action, send_raw): + if send_raw: + return { + 'Content-type': 'text/xml; charset=utf-8; action="http://www.portalfiscal.inf.br/nfe/wsdl/%s"' % action, + 'Accept': 'application/soap+xml; charset=utf-8', + } + return { - u'Content-type': u'application/soap+xml; charset=utf-8; action="http://www.portalfiscal.inf.br/nfe/wsdl/%s"' % action, - u'Accept': u'application/soap+xml; charset=utf-8', + 'Content-type': 'application/soap+xml; charset=utf-8; action="http://www.portalfiscal.inf.br/nfe/wsdl/%s"' % action, + 'Accept': 'application/soap+xml; charset=utf-8', } - def post_soap(self, xml_soap, cabecalho): - header = self._headers(cabecalho.soap_action) + def post_soap(self, xml_soap, cabecalho, send_raw): + header = self._headers(cabecalho.soap_action, send_raw) urllib3.disable_warnings(category=InsecureRequestWarning) res = requests.post(self.url, data=xml_soap, cert=(self.cert_path, self.key_path), diff --git a/pytrustnfe/nfe/__init__.py b/pytrustnfe/nfe/__init__.py index f00ee9f..62b6932 100644 --- a/pytrustnfe/nfe/__init__.py +++ b/pytrustnfe/nfe/__init__.py @@ -5,6 +5,7 @@ import os import hashlib +import binascii from lxml import etree from .comunicacao import executar_consulta from .assinatura import Assinatura @@ -80,7 +81,7 @@ def _add_qrCode(xml, **kwargs): infnfesupl = etree.Element('infNFeSupl') qrcode = etree.Element('qrCode') chave_nfe = inf_nfe['Id'][3:] - dh_emissao = inf_nfe['ide']['dhEmi'].encode('hex') + dh_emissao = binascii.hexlify(inf_nfe['ide']['dhEmi'].encode()).decode() versao = '100' ambiente = kwargs['ambiente'] valor_total = inf_nfe['total']['vNF'] @@ -98,9 +99,8 @@ def _add_qrCode(xml, **kwargs): dest.append(cpf) dest_parent.append(dest) icms_total = inf_nfe['total']['vICMS'] - dig_val = xml.find( - ".//{http://www.w3.org/2000/09/xmldsig#}DigestValue")\ - .text.encode('hex') + dig_val = binascii.hexlify(xml.find( + ".//{http://www.w3.org/2000/09/xmldsig#}DigestValue").text.encode()).decode() cid_token = kwargs['NFes'][0]['infNFe']['codigo_seguranca']['cid_token'] csc = kwargs['NFes'][0]['infNFe']['codigo_seguranca']['csc'] @@ -108,7 +108,7 @@ def _add_qrCode(xml, **kwargs): ={5}&vICMS={6}&digVal={7}&cIdToken={8}{9}".\ format(chave_nfe, versao, ambiente, dest_cpf, dh_emissao, valor_total, icms_total, dig_val, cid_token, csc) - c_hash_QR_code = hashlib.sha1(c_hash_QR_code).hexdigest() + c_hash_QR_code = hashlib.sha1(c_hash_QR_code.encode()).hexdigest() QR_code_url = "?chNFe={0}&nVersao={1}&tpAmb={2}&{3}dhEmi={4}&vNF={5}&vICMS\ ={6}&digVal={7}&cIdToken={8}&cHashQRCode={9}".\ @@ -121,12 +121,13 @@ def _add_qrCode(xml, **kwargs): qrcode.text = etree.CDATA(qrcode_text) infnfesupl.append(qrcode) nfe.insert(1, infnfesupl) - return etree.tostring(xml) + return etree.tostring(xml, encoding=str) -def _send(certificado, method, sign, **kwargs): +def _render(certificado, method, sign, **kwargs): path = os.path.join(os.path.dirname(__file__), 'templates') xmlElem_send = render_xml(path, '%s.xml' % method, True, **kwargs) + modelo = xmlElem_send.find(".//{http://www.portalfiscal.inf.br/nfe}mod") modelo = modelo.text if modelo is not None else '55' if modelo == '65': @@ -175,9 +176,13 @@ def _send(certificado, method, sign, **kwargs): xml_send = _add_qrCode(xml_send, **kwargs) else: - xml_send = etree.tostring(xmlElem_send) + xml_send = etree.tostring(xmlElem_send, encoding=str) + return xml_send + - url = localizar_url(method, kwargs['estado'], modelo, +def _send(certificado, method, **kwargs): + xml_send = kwargs["xml"] + url = localizar_url(method, kwargs['estado'], '55', kwargs['ambiente']) cabecalho = _build_header(method, **kwargs) @@ -189,55 +194,128 @@ def _send(certificado, method, sign, **kwargs): send_raw=send_raw) return { 'sent_xml': xml_send, - 'received_xml': response, + 'received_xml': response.decode(), 'object': obj } -def autorizar_nfe(certificado, **kwargs): # Assinar +def xml_autorizar_nfe(certificado, **kwargs): _generate_nfe_id(**kwargs) - return _send(certificado, 'NfeAutorizacao', True, **kwargs) + return _render(certificado, 'NfeAutorizacao', True, **kwargs) + + +def autorizar_nfe(certificado, **kwargs): # Assinar + if "xml" not in kwargs: + kwargs['xml'] = xml_autorizar_nfe(certificado, **kwargs) + return _send(certificado, 'NfeAutorizacao', **kwargs) + + +def xml_retorno_autorizar_nfe(certificado, **kwargs): + return _render(certificado, 'NfeRetAutorizacao', False, **kwargs) def retorno_autorizar_nfe(certificado, **kwargs): - return _send(certificado, 'NfeRetAutorizacao', False, **kwargs) + if "xml" not in kwargs: + kwargs['xml'] = xml_retorno_autorizar_nfe(certificado, **kwargs) + return _send(certificado, 'NfeRetAutorizacao', **kwargs) + + +def xml_recepcao_evento_cancelamento(certificado, **kwargs): # Assinar + return _render(certificado, 'RecepcaoEventoCancelamento', True, **kwargs) def recepcao_evento_cancelamento(certificado, **kwargs): # Assinar - return _send(certificado, 'RecepcaoEventoCancelamento', True, **kwargs) + if "xml" not in kwargs: + kwargs['xml'] = xml_recepcao_evento_cancelamento(certificado, **kwargs) + return _send(certificado, 'RecepcaoEventoCancelamento', **kwargs) + + +def xml_inutilizar_nfe(certificado, **kwargs): + return _render(certificado, 'NfeInutilizacao', True, **kwargs) + + +def inutilizar_nfe(certificado, **kwargs): + if "xml" not in kwargs: + kwargs['xml'] = xml_inutilizar_nfe(certificado, **kwargs) + return _send(certificado, 'NfeInutilizacao', **kwargs) -def inutilizar_nfe(certificado, **kwargs): # Assinar - return _send(certificado, 'NfeInutilizacao', True, **kwargs) +def xml_consultar_protocolo_nfe(certificado, **kwargs): + return _render(certificado, 'NfeConsultaProtocolo', True, **kwargs) def consultar_protocolo_nfe(certificado, **kwargs): - return _send(certificado, 'NfeConsultaProtocolo', True, **kwargs) + if "xml" not in kwargs: + kwargs['xml'] = xml_consultar_protocolo_nfe(certificado, **kwargs) + return _send(certificado, 'NfeConsultaProtocolo', **kwargs) + + +def xml_nfe_status_servico(certificado, **kwargs): + return _render(certificado, 'NfeStatusServico', False, **kwargs) def nfe_status_servico(certificado, **kwargs): - return _send(certificado, 'NfeStatusServico', False, **kwargs) + if "xml" not in kwargs: + kwargs['xml'] = xml_nfe_status_servico(certificado, **kwargs) + return _send(certificado, 'NfeStatusServico', **kwargs) + + +def xml_consulta_cadastro(certificado, **kwargs): + return _render(certificado, 'NfeConsultaCadastro', False, **kwargs) def consulta_cadastro(certificado, **kwargs): - return _send(certificado, 'NfeConsultaCadastro', False, **kwargs) + if "xml" not in kwargs: + kwargs['xml'] = xml_consulta_cadastro(certificado, **kwargs) + return _send(certificado, 'NfeConsultaCadastro', **kwargs) + + +def xml_recepcao_evento_carta_correcao(certificado, **kwargs): # Assinar + return _render(certificado, 'RecepcaoEventoCarta', True, **kwargs) def recepcao_evento_carta_correcao(certificado, **kwargs): # Assinar - return _send(certificado, 'RecepcaoEventoCarta', True, **kwargs) + if "xml" not in kwargs: + kwargs['xml'] = xml_recepcao_evento_carta_correcao( + certificado, **kwargs) + return _send(certificado, 'RecepcaoEventoCarta', **kwargs) + + +def xml_recepcao_evento_manifesto(certificado, **kwargs): # Assinar + return _render(certificado, 'RecepcaoEventoManifesto', **kwargs) def recepcao_evento_manifesto(certificado, **kwargs): # Assinar - return _send(certificado, 'RecepcaoEventoManifesto', True, **kwargs) + if "xml" not in kwargs: + kwargs['xml'] = xml_recepcao_evento_manifesto(certificado, **kwargs) + return _send(certificado, 'RecepcaoEventoManifesto', **kwargs) + + +def xml_recepcao_evento_epec(certificado, **kwargs): # Assinar + return _render(certificado, 'RecepcaoEventoEPEC', True, **kwargs) def recepcao_evento_epec(certificado, **kwargs): # Assinar - return _send(certificado, 'RecepcaoEventoEPEC', True, **kwargs) + if "xml" not in kwargs: + kwargs['xml'] = xml_recepcao_evento_epec(certificado, **kwargs) + return _send(certificado, 'RecepcaoEventoEPEC', **kwargs) + + +def xml_consulta_distribuicao_nfe(certificado, **kwargs): # Assinar + return _render(certificado, 'NFeDistribuicaoDFe', False, **kwargs) def consulta_distribuicao_nfe(certificado, **kwargs): - return _send(certificado, 'NFeDistribuicaoDFe', False, **kwargs) + if "xml" not in kwargs: + kwargs['xml'] = xml_consulta_distribuicao_nfe(certificado, **kwargs) + return _send(certificado, 'NFeDistribuicaoDFe', **kwargs) + + +def xml_download_nfe(certificado, **kwargs): # Assinar + return _render(certificado, 'NFeDistribuicaoDFe', False, **kwargs) def download_nfe(certificado, **kwargs): - return _send(certificado, 'NFeDistribuicaoDFe', False, **kwargs) + if "xml" not in kwargs: + kwargs['xml'] = xml_download_nfe(certificado, **kwargs) + return _send(certificado, 'NFeDistribuicaoDFe', **kwargs) diff --git a/pytrustnfe/nfe/assinatura.py b/pytrustnfe/nfe/assinatura.py index e14ec5d..34df54a 100644 --- a/pytrustnfe/nfe/assinatura.py +++ b/pytrustnfe/nfe/assinatura.py @@ -32,7 +32,7 @@ class Assinatura(object): ref_uri = ('#%s' % reference) if reference else None signed_root = signer.sign( - xml_element, key=key, cert=cert, + xml_element, key=key.encode(), cert=cert.encode(), reference_uri=ref_uri) if reference: element_signed = signed_root.find(".//*[@Id='%s']" % reference) @@ -42,4 +42,4 @@ class Assinatura(object): if element_signed is not None and signature is not None: parent = element_signed.getparent() parent.append(signature) - return etree.tostring(signed_root) + return etree.tostring(signed_root, encoding=str) diff --git a/pytrustnfe/nfe/comunicacao.py b/pytrustnfe/nfe/comunicacao.py index 2e3f4d9..df6ba53 100644 --- a/pytrustnfe/nfe/comunicacao.py +++ b/pytrustnfe/nfe/comunicacao.py @@ -30,5 +30,5 @@ def executar_consulta(certificado, url, cabecalho, xmlEnviar, send_raw=False): if send_raw: xml = '' + xmlEnviar.rstrip('\n') xml_enviar = xml - xml_retorno = client.post_soap(xml_enviar, cabecalho) - return sanitize_response(xml_retorno) + xml_retorno = client.post_soap(xml_enviar, cabecalho, send_raw) + return sanitize_response(xml_retorno.encode()) diff --git a/pytrustnfe/nfe/danfe.py b/pytrustnfe/nfe/danfe.py index c1fb83c..001810a 100644 --- a/pytrustnfe/nfe/danfe.py +++ b/pytrustnfe/nfe/danfe.py @@ -3,8 +3,8 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # Classe para geração de PDF da DANFE a partir de xml etree.fromstring - -from cStringIO import StringIO as IO +import os +from io import BytesIO from textwrap import wrap from reportlab.lib import utils @@ -17,6 +17,8 @@ from reportlab.lib.styles import getSampleStyleSheet from reportlab.lib.enums import TA_CENTER from reportlab.platypus import Paragraph, Image from reportlab.lib.styles import ParagraphStyle +from reportlab.pdfbase import pdfmetrics +from reportlab.pdfbase.ttfonts import TTFont def chunks(cString, nLen): @@ -73,6 +75,14 @@ class danfe(object): def __init__(self, sizepage=A4, list_xml=None, recibo=True, orientation='portrait', logo=None, cce_xml=None): + + path = os.path.join(os.path.dirname(__file__), 'fonts') + pdfmetrics.registerFont( + TTFont('NimbusSanL-Regu', + os.path.join(path, 'NimbusSanL Regular.ttf'))) + pdfmetrics.registerFont( + TTFont('NimbusSanL-Bold', + os.path.join(path, 'NimbusSanL Bold.ttf'))) self.width = 210 # 21 x 29,7cm self.height = 297 self.nLeft = 10 @@ -86,7 +96,7 @@ class danfe(object): '2': '2 - Terceiros', '9': '9 - Sem Frete'} - self.oPDF_IO = IO() + self.oPDF_IO = BytesIO() if orientation == 'landscape': raise NameError('Rotina não implementada') else: @@ -212,7 +222,7 @@ class danfe(object): cNF = '{0:011,}'.format(int(cNF)).replace(",", ".") self.stringcenter(self.nLeft + 100, self.nlin + 25, "Nº %s" % (cNF)) - self.stringcenter(self.nLeft + 100, self.nlin + 29, u"SÉRIE %s" % ( + self.stringcenter(self.nLeft + 100, self.nlin + 29, "SÉRIE %s" % ( tagtext(oNode=elem_ide, cTag='serie'))) cPag = "Página %s de %s" % (str(self.Page), str(self.NrPages)) self.stringcenter(self.nLeft + 100, self.nlin + 32, cPag) @@ -289,7 +299,7 @@ class danfe(object): oNode=elem_emit, cTag='CEP') regime = tagtext(oNode=elem_emit, cTag='CRT') - cEnd += u'
Regime Tributário: %s' % (REGIME_TRIBUTACAO[regime]) + cEnd += '
Regime Tributário: %s' % (REGIME_TRIBUTACAO[regime]) styleN.fontName = 'NimbusSanL-Regu' styleN.fontSize = 7 @@ -646,7 +656,7 @@ obsCont[@xCampo='NomeVendedor']") self.canvas.setFont('NimbusSanL-Regu', 5) nLin = self.nlin + 10.5 - for id in xrange(oPaginator[0], oPaginator[1]): + for id in range(oPaginator[0], oPaginator[1]): item = el_det[id] el_prod = item.find(".//{http://www.portalfiscal.inf.br/nfe}prod") el_imp = item.find( @@ -767,7 +777,7 @@ obsCont[@xCampo='NomeVendedor']") self.string(self.width - self.nRight - nW + 2, self.nlin + 8, "Nº %s" % (cNF)) self.string(self.width - self.nRight - nW + 2, self.nlin + 14, - u"SÉRIE %s" % (tagtext(oNode=el_ide, cTag='serie'))) + "SÉRIE %s" % (tagtext(oNode=el_ide, cTag='serie'))) cDt, cHr = getdateUTC(tagtext(oNode=el_ide, cTag='dhEmi')) cTotal = format_number(tagtext(oNode=el_total, cTag='vNF')) @@ -779,7 +789,7 @@ obsCont[@xCampo='NomeVendedor']") oNode=el_dest, cTag='xMun') + ' - ' cEnd += tagtext(oNode=el_dest, cTag='UF') - cString = u""" + cString = """ RECEBEMOS DE %s OS PRODUTOS/SERVIÇOS CONSTANTES DA NOTA FISCAL INDICADA ABAIXO. EMISSÃO: %s VALOR TOTAL: %s DESTINATARIO: %s""" % (tagtext(oNode=el_emit, cTag='xNome'), diff --git a/pytrustnfe/nfe/fonts/NimbusSanL Bold.ttf b/pytrustnfe/nfe/fonts/NimbusSanL Bold.ttf new file mode 100644 index 0000000..0e29c13 Binary files /dev/null and b/pytrustnfe/nfe/fonts/NimbusSanL Bold.ttf differ diff --git a/pytrustnfe/nfe/fonts/NimbusSanL Regular.ttf b/pytrustnfe/nfe/fonts/NimbusSanL Regular.ttf new file mode 100644 index 0000000..ac601af Binary files /dev/null and b/pytrustnfe/nfe/fonts/NimbusSanL Regular.ttf differ diff --git a/pytrustnfe/nfse/assinatura.py b/pytrustnfe/nfse/assinatura.py index b2a8d28..0c65cee 100644 --- a/pytrustnfe/nfse/assinatura.py +++ b/pytrustnfe/nfse/assinatura.py @@ -2,86 +2,52 @@ # © 2016 Danimar Ribeiro, Trustcode # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from lxml import etree import xmlsec -import libxml2 import os.path +consts = xmlsec.constants + NAMESPACE_SIG = 'http://www.w3.org/2000/09/xmldsig#' class Assinatura(object): - def __init__(self, arquivo, senha): - self.arquivo = arquivo - self.senha = senha + def __init__(self, cert_pem, private_key, password): + self.cert_pem = cert_pem + self.private_key = private_key + self.password = password def _checar_certificado(self): - if not os.path.isfile(self.arquivo): + if not os.path.isfile(self.private_key): raise Exception('Caminho do certificado não existe.') - def _inicializar_cripto(self): - libxml2.initParser() - libxml2.substituteEntitiesDefault(1) - - xmlsec.init() - xmlsec.cryptoAppInit(None) - xmlsec.cryptoInit() - - def _finalizar_cripto(self): - xmlsec.cryptoShutdown() - xmlsec.cryptoAppShutdown() - xmlsec.shutdown() - - libxml2.cleanupParser() - def assina_xml(self, xml, reference): self._checar_certificado() - self._inicializar_cripto() - try: - doc_xml = libxml2.parseMemory( - xml, len(xml)) - - signNode = xmlsec.TmplSignature(doc_xml, - xmlsec.transformInclC14NId(), - xmlsec.transformRsaSha1Id(), None) - - doc_xml.getRootElement().addChild(signNode) - refNode = signNode.addReference(xmlsec.transformSha1Id(), - None, reference, None) + template = etree.fromstring(xml) - refNode.addTransform(xmlsec.transformEnvelopedId()) - refNode.addTransform(xmlsec.transformInclC14NId()) - keyInfoNode = signNode.ensureKeyInfo() - keyInfoNode.addX509Data() + key = xmlsec.Key.from_file( + self.private_key, format=xmlsec.constants.KeyDataFormatPem, + password=self.password) - dsig_ctx = xmlsec.DSigCtx() - chave = xmlsec.cryptoAppKeyLoad(filename=str(self.arquivo), - format=xmlsec.KeyDataFormatPkcs12, - pwd=str(self.senha), - pwdCallback=None, - pwdCallbackCtx=None) + signature_node = xmlsec.template.create( + template, c14n_method=consts.TransformInclC14N, + sign_method=consts.TransformRsaSha1) + template.append(signature_node) + ref = xmlsec.template.add_reference( + signature_node, consts.TransformSha1, uri='') - dsig_ctx.signKey = chave - dsig_ctx.sign(signNode) + xmlsec.template.add_transform(ref, consts.TransformEnveloped) + xmlsec.template.add_transform(ref, consts.TransformInclC14N) - status = dsig_ctx.status - dsig_ctx.destroy() + ki = xmlsec.template.ensure_key_info(signature_node) + xmlsec.template.add_x509_data(ki) - if status != xmlsec.DSigStatusSucceeded: - raise RuntimeError( - 'Erro ao realizar a assinatura do arquivo; status: "' + - str(status) + - '"') + ctx = xmlsec.SignatureContext() + ctx.key = key - xpath = doc_xml.xpathNewContext() - xpath.xpathRegisterNs('sig', NAMESPACE_SIG) - certificados = xpath.xpathEval( - '//sig:X509Data/sig:X509Certificate') - for i in range(len(certificados) - 1): - certificados[i].unlinkNode() - certificados[i].freeNode() + ctx.key.load_cert_from_file( + self.cert_pem, consts.KeyDataFormatPem) - xml = doc_xml.serialize() - return xml - finally: - doc_xml.freeDoc() + ctx.sign(signature_node) + return etree.tostring(template, encoding=str) diff --git a/pytrustnfe/nfse/betha/__init__.py b/pytrustnfe/nfse/betha/__init__.py index f2517fa..67ae42d 100644 --- a/pytrustnfe/nfse/betha/__init__.py +++ b/pytrustnfe/nfse/betha/__init__.py @@ -50,7 +50,7 @@ def _send(certificado, method, **kwargs): try: response = getattr(client.service, method)(1, xml_send) - except suds.WebFault, e: + except suds.WebFault as e: return { 'sent_xml': xml_send, 'received_xml': e.fault.faultstring, diff --git a/pytrustnfe/nfse/dsf/__init__.py b/pytrustnfe/nfse/dsf/__init__.py new file mode 100644 index 0000000..df4ce4a --- /dev/null +++ b/pytrustnfe/nfse/dsf/__init__.py @@ -0,0 +1,131 @@ +# -*- encoding: utf-8 -*- +# © 2017 Fábio Luna, Trustcode +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import os +import suds +from lxml import etree +from pytrustnfe.xml import render_xml, sanitize_response +from pytrustnfe.certificado import extract_cert_and_key_from_pfx, save_cert_key +from pytrustnfe.nfse.assinatura import Assinatura +from pytrustnfe.client import get_client + + +def _render(certificado, method, **kwargs): + path = os.path.join(os.path.dirname(__file__), 'templates') + if method == "testeEnviar": + xml_send = render_xml(path, 'enviar.xml', True, **kwargs) + else: + xml_send = render_xml(path, '%s.xml' % method, False, **kwargs) + + if type(xml_send) != str: + xml_send = etree.tostring(xml_send) + + return xml_send + + +def _get_url(**kwargs): + + try: + cod_cidade = kwargs['nfse']['cidade'] + except (KeyError, TypeError): + raise KeyError("Código de cidade inválido!") + + urls = { + # Belém - PA + '2715': 'http://www.issdigitalbel.com.br/WsNFe2/LoteRps.jws', + # Sorocaba - SP + '7145': 'http://issdigital.sorocaba.sp.gov.br/WsNFe2/LoteRps.jws', + # Teresina - PI + '1219': 'http://www.issdigitalthe.com.br/WsNFe2/LoteRps.jws', + # Campinas - SP + '6291': 'http://issdigital.campinas.sp.gov.br/WsNFe2/LoteRps.jws?wsdl', + # Uberlandia - MG + '5403': 'http://udigital.uberlandia.mg.gov.br/WsNFe2/LoteRps.jws', + # São Luis - MA + '0921': 'https://stm.semfaz.saoluis.ma.gov.br/WsNFe2/LoteRps?wsdl', + # Campo Grande - MS + '2729': 'http://issdigital.pmcg.ms.gov.br/WsNFe2/LoteRps.jws?wsdl', + } + + try: + return urls[str(cod_cidade)] + except KeyError: + raise KeyError("DSF não emite notas da cidade {}!".format( + cod_cidade)) + + +def _send(certificado, method, **kwargs): + url = _get_url(**kwargs) + + path = os.path.join(os.path.dirname(__file__), 'templates') + + xml_send = _render(path, method, **kwargs) + client = get_client(url) + response = False + + if certificado: + cert, key = extract_cert_and_key_from_pfx( + certificado.pfx, certificado.password) + cert, key = save_cert_key(cert, key) + signer = Assinatura(cert, key, certificado.password) + xml_send = signer.assina_xml(xml_send, '') + + try: + response = getattr(client.service, method)(xml_send) + response, obj = sanitize_response(response.encode()) + except suds.WebFault as e: + return { + 'sent_xml': xml_send, + 'received_xml': e.fault.faultstring, + 'object': None + } + except Exception as e: + if response: + raise Exception(response) + else: + raise e + + return { + 'sent_xml': xml_send, + 'received_xml': response, + 'object': obj + } + + +def xml_enviar(certificado, **kwargs): + return _render(certificado, 'enviar', **kwargs) + + +def enviar(certificado, **kwargs): + if "xml" not in kwargs: + kwargs['xml'] = xml_enviar(certificado, **kwargs) + return _send(certificado, 'enviar', **kwargs) + + +def xml_teste_enviar(certificado, **kwargs): + return _render(certificado, 'testeEnviar', **kwargs) + + +def teste_enviar(certificado, **kwargs): + if "xml" not in kwargs: + kwargs['xml'] = xml_teste_enviar(certificado, **kwargs) + return _send(certificado, 'testeEnviar', **kwargs) + + +def cancelar(certificado, ** kwargs): + return _send(certificado, 'cancelar', **kwargs) + + +def consulta_lote(**kwargs): + return _send(False, 'consultarLote', **kwargs) + + +def xml_consultar_nfse_rps(certificado, **kwargs): + return _render(certificado, 'consultarNFSeRps', **kwargs) + + +def consultar_nfse_rps(certificado, **kwargs): + if "xml" not in kwargs: + kwargs['xml'] = xml_consultar_nfse_rps(certificado, **kwargs) + return _send(certificado, 'consultarNFSeRps', **kwargs) diff --git a/pytrustnfe/nfse/dsf/templates/cancelar.xml b/pytrustnfe/nfse/dsf/templates/cancelar.xml new file mode 100644 index 0000000..d72086b --- /dev/null +++ b/pytrustnfe/nfse/dsf/templates/cancelar.xml @@ -0,0 +1,18 @@ + + + {{ cancelamento.cidade }} + {{ cancelamento.cpf_cnpj }} + true + 1 + + + + {{ cancelamento.inscricao_municipal }} + {{ cancelamento.nota_id }} + {{ cancelamento.assinatura }} + {{ cancelamento.motivo }} + + + diff --git a/pytrustnfe/nfse/dsf/templates/consulta_notas.xml b/pytrustnfe/nfse/dsf/templates/consulta_notas.xml new file mode 100644 index 0000000..4a666d0 --- /dev/null +++ b/pytrustnfe/nfse/dsf/templates/consulta_notas.xml @@ -0,0 +1,11 @@ + + +{{ consulta.cidade }} +{{ consulta.cpf_cnpj }} +{{ consulta.inscricao_municipal }} +{{ consulta.data_inicio }} +{{ consulta.data_final }} +{{ consulta.nota_inicial }} +1 + + \ No newline at end of file diff --git a/pytrustnfe/nfse/dsf/templates/consultarLote.xml b/pytrustnfe/nfse/dsf/templates/consultarLote.xml new file mode 100644 index 0000000..24afc5d --- /dev/null +++ b/pytrustnfe/nfse/dsf/templates/consultarLote.xml @@ -0,0 +1,10 @@ + + + {{ consulta.cidade }} + {{ consulta.cpf_cnpj }} + 1 + {{ consulta.lote }} + + \ No newline at end of file diff --git a/pytrustnfe/nfse/dsf/templates/consultarNFSeRps.xml b/pytrustnfe/nfse/dsf/templates/consultarNFSeRps.xml new file mode 100644 index 0000000..a6a51bc --- /dev/null +++ b/pytrustnfe/nfse/dsf/templates/consultarNFSeRps.xml @@ -0,0 +1,22 @@ + + + {{ nfse.cidade }} + {{ nfse.cpf_cnpj }} + true + 1 + + + {% for rps in nfse.lista_rps -%} + + + {{ rps.prestador.inscricao_municipal }} + {{ rps.numero }} + {{ rps.serie_prestacao }} + + + {% endfor %} + + \ No newline at end of file diff --git a/pytrustnfe/nfse/dsf/templates/enviar.xml b/pytrustnfe/nfse/dsf/templates/enviar.xml new file mode 100644 index 0000000..7e4b178 --- /dev/null +++ b/pytrustnfe/nfse/dsf/templates/enviar.xml @@ -0,0 +1,108 @@ + + + {{ nfse.cidade }} + {{ nfse.cpf_cnpj }} + {{ nfse.remetente }} + {{ nfse.transacao }} + {{ nfse.data_inicio|format_date }} + {{ nfse.data_fim|format_date }} + {{ nfse.total_rps }} + {{ nfse.total_servicos }} + {{ nfse.total_deducoes }} + 1 + WS + + + {% for rps in nfse.lista_rps -%} + + {{ rps.assinatura }} + {{ rps.prestador.inscricao_municipal }} + + {{ rps.prestador.razao_social }} + RPS + {{ rps.serie }} + {{ rps.numero }} + {{ rps.data_emissao|format_datetime }} + + {{ rps.situacao }} + + 0 + 0 + 1900-01-01 + {{ rps.serie_prestacao }} + {{ rps.tomador.inscricao_municipal }} + {{ rps.tomador.cpf_cnpj }} + {{ rps.tomador.razao_social }} + + {{ rps.tomador.tipo_logradouro }} + + {{ rps.tomador.logradouro }} + {{ rps.tomador.numero }} + + {{ rps.tomador.tipo_bairro }} + {{ rps.tomador.bairro }} + {{ rps.tomador.cidade }} + {{ rps.tomador.cidade_descricao }} + + {{ rps.tomador.cep }} + {{ rps.tomador.email }} + {{ rps.codigo_atividade }} + {{ rps.aliquota_atividade }} + {{ rps.tipo_recolhimento }} + {{ rps.municipio_prestacao }} + + {{ rps.municipio_descricao_prestacao }} + + {{ rps.operacao }} + {{ rps.tributacao }} + {{ rps.valor_pis }} + {{ rps.valor_cofins }} + {{ rps.valor_inss }} + {{ rps.valor_ir }} + {{ rps.valor_csll }} + {{ rps.aliquota_pis }} + {{ rps.aliquota_cofins }} + {{ rps.aliquota_inss }} + {{ rps.aliquota_ir }} + {{ rps.aliquota_csll }} + {{ rps.descricao }} + {{ rps.prestador.ddd }} + {{ rps.prestador.telefone }} + {{ rps.tomador.ddd }} + {{ rps.tomador.telefone }} + {{ rps.motivo_cancelamento }} + {% if rps.deducoes|count > 0 %} + + {% for deducao in rps.deducoes -%} + + {{ deducao.por }} + {{ deducao.tipo }} + {{ deducao.cnpj_referencia }} + {{ deducao.nf_referencia }} + {{ deducao.valor_referencia }} + {{ deducao.percentual_deduzir }} + {{ deducao.valor_deduzir }} + + {% endfor %} + + {% endif %} + {% if rps.deducoes|count == 0 %} + + {% endif %} + + {% for item in rps.itens -%} + + {{ item.descricao }} + {{ item.quantidade }} + {{ item.valor_unitario }} + {{ item.valor_total }} + S + + {% endfor %} + + + {% endfor %} + + diff --git a/pytrustnfe/nfse/dsf/templates/soap_header.xml b/pytrustnfe/nfse/dsf/templates/soap_header.xml new file mode 100644 index 0000000..e9d1dd2 --- /dev/null +++ b/pytrustnfe/nfse/dsf/templates/soap_header.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/pytrustnfe/nfse/floripa/__init__.py b/pytrustnfe/nfse/floripa/__init__.py new file mode 100644 index 0000000..a8282d3 --- /dev/null +++ b/pytrustnfe/nfse/floripa/__init__.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- +# © 2017 Danimar Ribeiro, Trustcode +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import os +import hashlib +import base64 +import requests +from pytrustnfe.xml import render_xml, sanitize_response +from pytrustnfe.certificado import extract_cert_and_key_from_pfx, save_cert_key +from pytrustnfe.nfse.assinatura import Assinatura + +URLS = { + 'producao': { + 'processar_nota': 'https://nfps-e.pmf.sc.gov.br/api/v1/processamento/notas/processa', + 'cancelar_nota': 'https://nfps-e.pmf.sc.gov.br/api/v1/cancelamento/notas/cancela' + }, + 'homologacao': { + 'processar_nota': 'https://nfps-e-hml.pmf.sc.gov.br/api/v1/processamento/notas/processa', + 'cancelar_nota': 'https://nfps-e-hml.pmf.sc.gov.br/api/v1/cancelamento/notas/cancela' + } +} + + +def _render(certificado, method, **kwargs): + path = os.path.join(os.path.dirname(__file__), 'templates') + xml_send = render_xml(path, '%s.xml' % method, False, **kwargs) + + cert, key = extract_cert_and_key_from_pfx( + certificado.pfx, certificado.password) + cert, key = save_cert_key(cert, key) + signer = Assinatura(cert, key, certificado.password) + xml_send = signer.assina_xml(xml_send, '') + return xml_send + + +def _get_oauth_token(**kwargs): + if kwargs['ambiente'] == 'producao': + url = 'https://nfps-e.pmf.sc.gov.br/api/v1/autenticacao/oauth/token' + else: + url = 'https://nfps-e-hml.pmf.sc.gov.br/api/v1/autenticacao/oauth/token' + + m = hashlib.md5() + secret = "%s:%s" % (kwargs["client_id"], kwargs["secret_id"]) + auth = base64.b64encode(secret.encode('utf-8')) + headers = { + "Content-Type": "application/x-www-form-urlencoded", + "Authorization": "Basic %s" % auth.decode('utf-8').replace('\n', '') + } + m.update(kwargs["password"].encode('utf-8')) + password = m.hexdigest().upper() + + dados = "grant_type=password&username=%s&password=%s&client_id=%s&client_secret=%s" % ( + kwargs["username"], password, kwargs["client_id"], kwargs["secret_id"]) + r = requests.post(url, data=dados, headers=headers) + if r.status_code == 200: + return r.json() + else: + return r.json() + + +def _send(certificado, method, **kwargs): + url = URLS[kwargs['ambiente']][method] + xml_send = kwargs['xml'] + + token = _get_oauth_token(**kwargs) + if "access_token" not in token: + raise Exception("%s - %s: %s" % (token["status"], token["error"], + token["message"])) + kwargs.update({"numero": 1, 'access_token': token["access_token"]}) + + headers = {"Accept": "application/xml;charset=UTF-8", + "Content-Type": "application/xml", + "Authorization": "Bearer %s" % kwargs['access_token']} + r = requests.post(url, headers=headers, data=xml_send) + + response, obj = sanitize_response(r.text.strip().encode('utf-8')) + return { + 'sent_xml': xml_send, + 'received_xml': response, + 'object': obj, + 'status_code': r.status_code, + } + + +def xml_processar_nota(certificado, **kwargs): + return _render(certificado, 'processar_nota', **kwargs) + + +def processar_nota(certificado, **kwargs): + if "xml" not in kwargs: + kwargs['xml'] = xml_processar_nota(certificado, **kwargs) + return _send(certificado, 'processar_nota', **kwargs) + + +def xml_cancelar_nota(certificado, **kwargs): + return _render(certificado, 'cancelar_nota', **kwargs) + + +def cancelar_nota(certificado, **kwargs): + if "xml" not in kwargs: + kwargs['xml'] = xml_cancelar_nota(certificado, **kwargs) + return _send(certificado, 'cancelar_nota', **kwargs) + + +def consultar_nota(certificado, **kwargs): + if kwargs['ambiente'] == 'producao': + url = "https://nfps-e.pmf.sc.gov.br/api/v1/consultas/notas/numero/%s" % (kwargs["numero"]) + else: + url = "https://nfps-e-hml.pmf.sc.gov.br/api/v1/consultas/notas/numero/%s" % (kwargs["numero"]) + + headers = {"Accept": "application/json", + "Authorization": "Bearer %s" % kwargs['access_token']} + r = requests.get(url, headers=headers) + print(r.status_code) + if r.status_code == 200: + return r.text + else: + return r.text diff --git a/pytrustnfe/nfse/floripa/templates/cancelar_nota.xml b/pytrustnfe/nfse/floripa/templates/cancelar_nota.xml new file mode 100644 index 0000000..324d779 --- /dev/null +++ b/pytrustnfe/nfse/floripa/templates/cancelar_nota.xml @@ -0,0 +1,7 @@ + + + {{ cancelamento.motivo }} + {{ cancelamento.aedf }} + {{ cancelamento.numero }} + {{ cancelamento.codigo_verificacao }} + diff --git a/pytrustnfe/nfse/floripa/templates/processar_nota.xml b/pytrustnfe/nfse/floripa/templates/processar_nota.xml new file mode 100644 index 0000000..08a7a2e --- /dev/null +++ b/pytrustnfe/nfse/floripa/templates/processar_nota.xml @@ -0,0 +1,40 @@ + + + {{ rps.tomador.bairro }} + {{ rps.base_calculo }} + 0.0 + {{ rps.cfps }} + {{ rps.tomador.cidade }} + {{ rps.tomador.cep }} + {{ rps.tomador.complemento }} + {{ rps.observacoes }} + {{ rps.data_emissao }} + {{ rps.tomador.email }} + {{ rps.numero }} + {{ rps.tomador.cnpj_cpf }} + {{ rps.tomador.inscricao_municipal }} + + {% for item in rps.itens_servico -%} + + {{ item.aliquota }} + {{ item.cst_servico }} + {{ item.descricao }} + {{ item.cnae }} + {{ item.quantidade }} + {{ item.valor_total }} + {{ item.valor_unitario }} + + {% endfor %} + + {{ rps.tomador.logradouro }} + + {{ rps.aedf }} + {{ rps.tomador.numero }} + 1058 + {{ rps.tomador.razao_social }} + {{ rps.tomador.telefone }} + {{ rps.tomador.uf }} + {{rps.valor_issqn }} + 0.0 + {{ rps.valor_total }} + diff --git a/pytrustnfe/nfse/ginfes/__init__.py b/pytrustnfe/nfse/ginfes/__init__.py index f69ffe1..80e969b 100644 --- a/pytrustnfe/nfse/ginfes/__init__.py +++ b/pytrustnfe/nfse/ginfes/__init__.py @@ -38,7 +38,7 @@ def _send(certificado, method, **kwargs): xml_send = kwargs['xml'] header = '3' #noqa response = getattr(client.service, method)(header, xml_send) - except suds.WebFault, e: + except suds.WebFault as e: return { 'sent_xml': xml_send, 'received_xml': e.fault.faultstring, diff --git a/pytrustnfe/nfse/paulistana/__init__.py b/pytrustnfe/nfse/paulistana/__init__.py index 48818e8..ed0611b 100644 --- a/pytrustnfe/nfse/paulistana/__init__.py +++ b/pytrustnfe/nfse/paulistana/__init__.py @@ -18,10 +18,10 @@ def sign_tag(certificado, **kwargs): if 'nfse' in kwargs: for item in kwargs['nfse']['lista_rps']: signed = crypto.sign(key, item['assinatura'], 'SHA1') - item['assinatura'] = b64encode(signed) + item['assinatura'] = b64encode(signed).decode() if 'cancelamento' in kwargs: signed = crypto.sign(key, kwargs['cancelamento']['assinatura'], 'SHA1') - kwargs['cancelamento']['assinatura'] = b64encode(signed) + kwargs['cancelamento']['assinatura'] = b64encode(signed).decode() def _send(certificado, method, **kwargs): @@ -42,13 +42,12 @@ def _send(certificado, method, **kwargs): cert, key = save_cert_key(cert, key) client = get_authenticated_client(base_url, cert, key) - pfx_path = certificado.save_pfx() - signer = Assinatura(pfx_path, certificado.password) + signer = Assinatura(cert, key, certificado.password) xml_send = signer.assina_xml(xml_send, '') try: response = getattr(client.service, method)(1, xml_send) - except suds.WebFault, e: + except suds.WebFault as e: return { 'sent_xml': xml_send, 'received_xml': e.fault.faultstring, diff --git a/pytrustnfe/nfse/susesu/__init__.py b/pytrustnfe/nfse/susesu/__init__.py index 2295bb2..557a168 100644 --- a/pytrustnfe/nfse/susesu/__init__.py +++ b/pytrustnfe/nfse/susesu/__init__.py @@ -29,7 +29,7 @@ def _send(method, **kwargs): 'sent_xml': xml_send, 'received_xml': e.fault.faultstring, } - result = unicode(result) + result = str(result) result = unicodedata.normalize('NFKD', result).encode('ascii', 'ignore') return { 'sent_xml': xml_send, diff --git a/pytrustnfe/test/XMLs/paulistana_resultado.xml b/pytrustnfe/test/XMLs/paulistana_resultado.xml deleted file mode 100644 index d4f6759..0000000 --- a/pytrustnfe/test/XMLs/paulistana_resultado.xml +++ /dev/null @@ -1 +0,0 @@ -true265436451212213329001632016-08-29T10:52:15101.3552382446APR9MJR5128216 diff --git a/pytrustnfe/xml/__init__.py b/pytrustnfe/xml/__init__.py index ff1a6f3..5cf7454 100644 --- a/pytrustnfe/xml/__init__.py +++ b/pytrustnfe/xml/__init__.py @@ -2,7 +2,6 @@ # © 2016 Danimar Ribeiro, Trustcode # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -import unicodedata from lxml import etree from lxml import objectify @@ -41,7 +40,6 @@ def render_xml(path, template_name, remove_empty, **nfe): env.filters["comma"] = filters.format_with_comma template = env.get_template(template_name) - xml = template.render(**nfe) parser = etree.XMLParser(remove_blank_text=True, remove_comments=True, strip_cdata=False) @@ -53,14 +51,13 @@ def render_xml(path, template_name, remove_empty, **nfe): if recursively_empty(elem): parent.remove(elem) return root - return etree.tostring(root) + for element in root.iter("*"): # remove espaços em branco + if element.text is not None and not element.text.strip(): + element.text = None + return etree.tostring(root, encoding=str) def sanitize_response(response): - response = unicode(response) - response = unicodedata.normalize('NFKD', response).encode('ascii', - 'ignore') - tree = etree.fromstring(response) # Remove namespaces inuteis na resposta for elem in tree.getiterator(): @@ -68,6 +65,19 @@ def sanitize_response(response): continue i = elem.tag.find('}') if i >= 0: - elem.tag = elem.tag[i+1:] + elem.tag = elem.tag[i + 1:] objectify.deannotate(tree, cleanup_namespaces=True) return response, objectify.fromstring(etree.tostring(tree)) + + +def recursively_normalize(vals): + for item in vals: + if type(vals[item]) is str: + vals[item] = vals[item].strip() + vals[item] = filters.normalize_str(vals[item]) + elif type(vals[item]) is dict: + recursively_normalize(vals[item]) + elif type(vals[item]) is list: + for a in vals[item]: + recursively_normalize(a) + return vals diff --git a/pytrustnfe/xml/filters.py b/pytrustnfe/xml/filters.py index 8a33af4..c3f7cbf 100644 --- a/pytrustnfe/xml/filters.py +++ b/pytrustnfe/xml/filters.py @@ -13,24 +13,24 @@ def normalize_str(string): Remove special characters and strip spaces """ if string: - if not isinstance(string, unicode): - string = unicode(string, 'utf-8', 'replace') + if not isinstance(string, str): + string = str(string, 'utf-8', 'replace') string = string.encode('utf-8') return normalize( - 'NFKD', string.decode('utf-8')).encode('ASCII', 'ignore') + 'NFKD', string.decode('utf-8')).encode('ASCII', 'ignore').decode() return '' def strip_line_feed(string): if string: - if not isinstance(string, unicode): - string = unicode(string, 'utf-8', 'replace') + if not isinstance(string, str): + string = str(string, 'utf-8', 'replace') remap = { - ord(u'\t'): u' ', - ord(u'\n'): u' ', - ord(u'\f'): u' ', - ord(u'\r'): None, # Delete + ord('\t'): ' ', + ord('\n'): ' ', + ord('\f'): ' ', + ord('\r'): None, # Delete } return string.translate(remap).strip() return string diff --git a/pytrustnfe/xml/schemas/enviNFe_v3.10.xsd b/pytrustnfe/xml/schemas/enviNFe_v3.10.xsd old mode 100644 new mode 100755 diff --git a/pytrustnfe/xml/schemas/leiauteNFe_v3.10.xsd b/pytrustnfe/xml/schemas/leiauteNFe_v3.10.xsd old mode 100644 new mode 100755 diff --git a/pytrustnfe/xml/schemas/nfe_v3.10.xsd b/pytrustnfe/xml/schemas/nfe_v3.10.xsd old mode 100644 new mode 100755 diff --git a/pytrustnfe/xml/schemas/tiposBasico_v3.10.xsd b/pytrustnfe/xml/schemas/tiposBasico_v3.10.xsd old mode 100644 new mode 100755 index 70c9a4b..1dfe7d0 --- a/pytrustnfe/xml/schemas/tiposBasico_v3.10.xsd +++ b/pytrustnfe/xml/schemas/tiposBasico_v3.10.xsd @@ -494,7 +494,7 @@ - + diff --git a/pytrustnfe/xml/schemas/xmldsig-core-schema_v1.01.xsd b/pytrustnfe/xml/schemas/xmldsig-core-schema_v1.01.xsd old mode 100644 new mode 100755 diff --git a/pytrustnfe/xml/validate.py b/pytrustnfe/xml/validate.py index 4314d2c..3306b57 100644 --- a/pytrustnfe/xml/validate.py +++ b/pytrustnfe/xml/validate.py @@ -3,39 +3,16 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). import os -import re from lxml import etree PATH = os.path.dirname(os.path.abspath(__file__)) -SCHEMA = os.path.join(PATH, 'schemas/nfe_v3.10.xsd') +SCHEMA = os.path.join(PATH, 'schemas/enviNFe_v3.10.xsd') -def pop_encoding(xml): - xml = xml.split('\n') - if re.match(r'<\?xml version=', xml[0]): - xml.pop(0) - return '\n'.join(xml) - - -def valida_nfe(nfe): - xml = pop_encoding(nfe).encode('utf-8') - nfe = etree.fromstring(xml) +def valida_nfe(xml_nfe): + nfe = etree.fromstring(xml_nfe) esquema = etree.XMLSchema(etree.parse(SCHEMA)) esquema.validate(nfe) erros = [x.message for x in esquema.error_log] - error_msg = '{field} inválido: {valor}.' - unexpected = '{unexpected} não é esperado. O valor esperado é {expected}' - namespace = '{http://www.portalfiscal.inf.br/nfe}' - mensagens = [] - for erro in erros: - campo = re.findall(r"'([^']*)'", erro)[0] - nome = campo[campo.find('}') + 1: ] - valor = nfe.find('.//' + campo).text - if 'Expected is' in erro: - expected_name = re.findall('\(.*?\)', erro) - valor = unexpected.format(unexpected=nome, expected=expected_name) - mensagem = error_msg.format(field=campo.replace(namespace, ''), - valor=valor) - mensagens.append(mensagem) - return "\n".join(mensagens) + return "\n".join(erros) diff --git a/requirements.txt b/requirements.txt index 9a94f0c..abb0836 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,16 +1,16 @@ -lxml >= 3.5.0, < 4 -nose -mock +lxml >= 3.5.0, < 5 coveralls -http://xmlsoft.org/sources/python/libxml2-python-2.6.21.tar.gz -https://github.com/odoo-brazil/pyxmlsec/archive/master.zip Jinja2 signxml -suds >= 0.4 -suds_requests >= 0.3 -defusedxml >= 0.4.1, < 0.6 -eight >= 0.3.0, < 0.5 -cryptography >= 1.8, < 1.10 -pyOpenSSL >= 16.0.0, < 17 +urllib3 >= 1.22 +suds-jurko >= 0.6 +suds-jurko-requests >= 1.1 +defusedxml >= 0.4.1, < 1 +eight >= 0.3.0, < 1 +cryptography >= 1.8, < 3 +pyOpenSSL >= 16.0.0, < 18 certifi >= 2015.11.20.1 +xmlsec >= 1.3.3 reportlab +pytest +pytest-cov diff --git a/setup.py b/setup.py index 2b186be..bbb800c 100644 --- a/setup.py +++ b/setup.py @@ -1,33 +1,41 @@ # coding=utf-8 from setuptools import setup, find_packages -VERSION = "0.1.44" + +VERSION = "0.9.7" + setup( - name="PyTrustNFe", + name="PyTrustNFe3", version=VERSION, author="Danimar Ribeiro", author_email='danimaribeiro@gmail.com', keywords=['nfe', 'mdf-e'], classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 4 - Beta', 'Environment :: Plugins', 'Intended Audience :: Developers', 'License :: OSI Approved :: GNU Lesser General Public License v2 or \ later (LGPLv2+)', 'Operating System :: OS Independent', 'Programming Language :: Python', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.4', 'Topic :: Software Development :: Libraries :: Python Modules', ], packages=find_packages(exclude=['*test*']), package_data={'pytrustnfe': [ 'nfe/templates/*xml', + 'nfe/fonts/*ttf', 'nfse/paulistana/templates/*xml', + 'nfse/dsf/templates/*xml', 'nfse/ginfes/templates/*xml', 'nfse/simpliss/templates/*xml', 'nfse/betha/templates/*xml', 'nfse/susesu/templates/*xml', 'nfse/imperial/templates/*xml', + 'nfse/floripa/templates/*xml', 'xml/schemas/*xsd', ]}, url='https://github.com/danimaribeiro/PyTrustNFe', @@ -37,14 +45,12 @@ later (LGPLv2+)', install_requires=[ 'Jinja2 >= 2.8', 'signxml >= 2.4.0', - 'lxml >= 3.5.0, < 4', - 'suds >= 0.4', - 'suds_requests >= 0.3', + 'lxml >= 3.5.0, < 5', + 'suds-jurko >= 0.6', + 'suds-jurko-requests >= 1.1', 'reportlab' ], - test_suite='nose.collector', tests_require=[ - 'nose', - 'mock', + 'pytest', ], ) diff --git a/pytrustnfe/test/XMLs/NFe00000857.xml b/tests/XMLs/NFe00000857.xml similarity index 100% rename from pytrustnfe/test/XMLs/NFe00000857.xml rename to tests/XMLs/NFe00000857.xml diff --git a/pytrustnfe/test/XMLs/jinja_remove_empty.xml b/tests/XMLs/jinja_remove_empty.xml similarity index 100% rename from pytrustnfe/test/XMLs/jinja_remove_empty.xml rename to tests/XMLs/jinja_remove_empty.xml diff --git a/pytrustnfe/test/XMLs/jinja_result.xml b/tests/XMLs/jinja_result.xml similarity index 100% rename from pytrustnfe/test/XMLs/jinja_result.xml rename to tests/XMLs/jinja_result.xml diff --git a/pytrustnfe/test/XMLs/jinja_template.xml b/tests/XMLs/jinja_template.xml similarity index 100% rename from pytrustnfe/test/XMLs/jinja_template.xml rename to tests/XMLs/jinja_template.xml diff --git a/pytrustnfe/test/XMLs/paulistana_canc.xml b/tests/XMLs/paulistana_canc.xml similarity index 100% rename from pytrustnfe/test/XMLs/paulistana_canc.xml rename to tests/XMLs/paulistana_canc.xml diff --git a/pytrustnfe/test/XMLs/paulistana_canc_errado.xml b/tests/XMLs/paulistana_canc_errado.xml similarity index 93% rename from pytrustnfe/test/XMLs/paulistana_canc_errado.xml rename to tests/XMLs/paulistana_canc_errado.xml index fc1aeae..4fd39b4 100644 --- a/pytrustnfe/test/XMLs/paulistana_canc_errado.xml +++ b/tests/XMLs/paulistana_canc_errado.xml @@ -1,4 +1,3 @@ - true265436451212213329001632016-08-29T10:52:15101.3552382446APR9MJR5128216 diff --git a/pytrustnfe/test/XMLs/paulistana_signature.xml b/tests/XMLs/paulistana_signature.xml similarity index 73% rename from pytrustnfe/test/XMLs/paulistana_signature.xml rename to tests/XMLs/paulistana_signature.xml index 013053e..c4f8cbc 100644 --- a/pytrustnfe/test/XMLs/paulistana_signature.xml +++ b/tests/XMLs/paulistana_signature.xml @@ -1,8 +1,4 @@ - -12345678901234false2016-08-292016-08-291E4fpHYkQa7Naxn6IKGb7NwwZu5tPk/KXJ9hCwtZgq0xvKS450aQqqBL+7Iv46lTgqrSMu7+gLrl+LC1qs/8aT2mbHE8uaVFSbzwZ+sF/BkcT6nsFHLMswEiTAEs95Jb7hN1cC91xqQGRH4buw0TzxHKmhuLJ22WwtG/scxyKtjM=12345611RPS2016-08-29NT0.000.000.000.000.00074985.00false - - - 123456Trustcode1Vinicius de Moraes, 4242CorregoFloripaSC88037240Venda de servico +12345678901234false2016-08-292016-08-291E4fpHYkQa7Naxn6IKGb7NwwZu5tPk/KXJ9hCwtZgq0xvKS450aQqqBL+7Iv46lTgqrSMu7+gLrl+LC1qs/8aT2mbHE8uaVFSbzwZ+sF/BkcT6nsFHLMswEiTAEs95Jb7hN1cC91xqQGRH4buw0TzxHKmhuLJ22WwtG/scxyKtjM=12345611RPS2016-08-29NT0.000.000.000.000.00074985.00false123456Trustcode1Vinicius de Moraes, 4242CorregoFloripaSC88037240Venda de servico @@ -12,12 +8,12 @@ -ivaOwkcrt0pfuMYsAdfyLaUAcIk= +ePJnD6hyDvlJo08PFX8h2TXk0ZM= -FjIHdfPavSEyaWYhAT0z0shPLuTsqBKyy78PUEZ8PUhTZ+iSV0MOvAIRq9MPPVK9 -jjXOw1TE903uSK8aJon52RNKPd68ORVJ3bKFSjTqQLxFRR9tiiAQFrWDETf7FF89 -EhG6dy6TGcgVbOyn0Jqm8MkqrE1XrJ44orN1X+Jt+7U= +GbaQaTEtxuKdRRaadginWPFH5K65ywqEikkwChWO3xX5Kglq8RPm4+LjnpJmuTcE +9I2BVon3GJFh+c/6RKzJPose6FXog2xnCpTOgwA/rks/gKsUAaRlXCPsLcKMKaOj +3eH21RHEyrxBAbdpEUdlEgQWaWzmGq009EiQ544sD6c= MIICMTCCAZqgAwIBAgIQfYOsIEVuAJ1FwwcTrY0t1DANBgkqhkiG9w0BAQUFADBX @@ -34,4 +30,4 @@ QtgAhuZM9rxpOJuNKc+pM29EixpAiZZiRMCSWEItNyEVdUIi+YnKBcAHd88TwO86 d126MWQ2O8cu5W1VoDp7hYBYKOnLbYi11/StO+0rzK+oPYAvIw== - + \ No newline at end of file diff --git a/pytrustnfe/test/XMLs/recibo_envio_1.xml b/tests/XMLs/recibo_envio_1.xml similarity index 100% rename from pytrustnfe/test/XMLs/recibo_envio_1.xml rename to tests/XMLs/recibo_envio_1.xml diff --git a/pytrustnfe/test/XMLs/recibo_envio_2.xml b/tests/XMLs/recibo_envio_2.xml similarity index 100% rename from pytrustnfe/test/XMLs/recibo_envio_2.xml rename to tests/XMLs/recibo_envio_2.xml diff --git a/pytrustnfe/test/XMLs/recibo_protocolo_sucesso_1.xml b/tests/XMLs/recibo_protocolo_sucesso_1.xml similarity index 100% rename from pytrustnfe/test/XMLs/recibo_protocolo_sucesso_1.xml rename to tests/XMLs/recibo_protocolo_sucesso_1.xml diff --git a/pytrustnfe/test/XMLs/recibo_protocolo_sucesso_2.xml b/tests/XMLs/recibo_protocolo_sucesso_2.xml similarity index 100% rename from pytrustnfe/test/XMLs/recibo_protocolo_sucesso_2.xml rename to tests/XMLs/recibo_protocolo_sucesso_2.xml diff --git a/pytrustnfe/test/__init__.py b/tests/__init__.py similarity index 100% rename from pytrustnfe/test/__init__.py rename to tests/__init__.py diff --git a/pytrustnfe/test/test_add_qr_code.py b/tests/test_add_qr_code.py similarity index 82% rename from pytrustnfe/test/test_add_qr_code.py rename to tests/test_add_qr_code.py index 17965c9..77a24f4 100644 --- a/pytrustnfe/test/test_add_qr_code.py +++ b/tests/test_add_qr_code.py @@ -9,10 +9,10 @@ from pytrustnfe.nfe import _add_qrCode class TestAddQRCode(unittest.TestCase): def setUp(self): - self.xml_sem_qrcode = open('pytrustnfe/test/xml_sem_qrcode.xml', 'r') - self.xml_com_qrcode = open('pytrustnfe/test/xml_com_qrcode.xml', 'r') + self.xml_sem_qrcode = open('tests/xml_sem_qrcode.xml', 'r') + self.xml_com_qrcode = open('tests/xml_com_qrcode.xml', 'r') dhEmi = '2016-11-09T16:03:25-00:00' - chave_nfe = u'NFe35161121332917000163650010000000011448875034' + chave_nfe = 'NFe35161121332917000163650010000000011448875034' ambiente = 2 valor_total = '324.00' icms_total = '61.56' diff --git a/pytrustnfe/test/test_assinatura.py b/tests/test_assinatura.py similarity index 79% rename from pytrustnfe/test/test_assinatura.py rename to tests/test_assinatura.py index 380e1fd..f13b851 100644 --- a/pytrustnfe/test/test_assinatura.py +++ b/tests/test_assinatura.py @@ -11,16 +11,14 @@ from lxml import etree from pytrustnfe.nfe.assinatura import Assinatura -XML_ASSINAR = '' \ - '' \ +XML_ASSINAR = '' \ ' '\ ' Hello, World!' \ ' ' \ '' -XML_ERRADO = '' \ - '' \ +XML_ERRADO = '' \ ' ' \ ' Hello, World!' \ ' ' \ @@ -32,21 +30,21 @@ class test_assinatura(unittest.TestCase): caminho = os.path.dirname(__file__) def test_assinar_xml_senha_invalida(self): - pfx = open(os.path.join(self.caminho, 'teste.pfx')).read() + pfx = open(os.path.join(self.caminho, 'teste.pfx'), 'rb').read() signer = Assinatura(pfx, '123') self.assertRaises(Exception, signer.assina_xml, signer, etree.fromstring(XML_ASSINAR), 'NFe43150602261542000143550010000000761792265342') def test_assinar_xml_invalido(self): - pfx = open(os.path.join(self.caminho, 'teste.pfx')).read() + pfx = open(os.path.join(self.caminho, 'teste.pfx'), 'rb').read() signer = Assinatura(pfx, '123456') self.assertRaises(Exception, signer.assina_xml, signer, etree.fromstring(XML_ERRADO), 'NFe43150602261542000143550010000000761792265342') def test_assinar_xml_valido(self): - pfx = open(os.path.join(self.caminho, 'teste.pfx')).read() + pfx = open(os.path.join(self.caminho, 'teste.pfx'), 'rb').read() signer = Assinatura(pfx, '123456') xml = signer.assina_xml( etree.fromstring(XML_ASSINAR), diff --git a/pytrustnfe/test/test_certificado.py b/tests/test_certificado.py similarity index 97% rename from pytrustnfe/test/test_certificado.py rename to tests/test_certificado.py index 2e7f248..e05a8f0 100644 --- a/pytrustnfe/test/test_certificado.py +++ b/tests/test_certificado.py @@ -49,21 +49,21 @@ class test_assinatura(unittest.TestCase): caminho = os.path.dirname(__file__) def test_preparar_pfx(self): - dir_pfx = open(os.path.join(self.caminho, 'teste.pfx'), 'r').read() + dir_pfx = open(os.path.join(self.caminho, 'teste.pfx'), 'rb').read() cert, key = extract_cert_and_key_from_pfx(dir_pfx, '123456') self.assertEqual(key, CHAVE, 'Chave gerada inválida') self.assertEqual(cert, CERTIFICADO, 'Certificado inválido') def test_save_pfx(self): - pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'r').read() + pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'rb').read() pfx = Certificado(pfx_source, '123') path = pfx.save_pfx() - saved = open(path, 'r').read() + saved = open(path, 'rb').read() self.assertEqual(pfx_source, saved, 'Arquivo pfx salvo não bate com arquivo lido') def test_save_cert_and_key(self): - dir_pfx = open(os.path.join(self.caminho, 'teste.pfx'), 'r').read() + dir_pfx = open(os.path.join(self.caminho, 'teste.pfx'), 'rb').read() cert, key = extract_cert_and_key_from_pfx(dir_pfx, '123456') cert_path, key_path = save_cert_key(cert, key) cert_saved = open(cert_path, 'r').read() diff --git a/pytrustnfe/test/test_comunicacao.py b/tests/test_comunicacao.py similarity index 100% rename from pytrustnfe/test/test_comunicacao.py rename to tests/test_comunicacao.py diff --git a/pytrustnfe/test/test_consulta_cadastro.py b/tests/test_consulta_cadastro.py similarity index 96% rename from pytrustnfe/test/test_consulta_cadastro.py rename to tests/test_consulta_cadastro.py index 9c63f03..a284b56 100644 --- a/pytrustnfe/test/test_consulta_cadastro.py +++ b/tests/test_consulta_cadastro.py @@ -12,7 +12,7 @@ class test_consulta_cadastro(unittest.TestCase): caminho = os.path.dirname(__file__) def test_conta_de_cadastro(self): - pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'r').read() + pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'rb').read() pfx = Certificado(pfx_source, '123456') obj = {'cnpj': '12345678901234', 'estado': '42'} diff --git a/pytrustnfe/test/test_danfe.py b/tests/test_danfe.py similarity index 92% rename from pytrustnfe/test/test_danfe.py rename to tests/test_danfe.py index e7385c7..a854858 100644 --- a/pytrustnfe/test/test_danfe.py +++ b/tests/test_danfe.py @@ -22,5 +22,5 @@ class test_danfe(unittest.TestCase): # Para testar localmente o Danfe # with open('/home/danimar/danfe.pdf', 'w') as oFile: - with tempfile.TemporaryFile(mode='w') as oFile: + with tempfile.TemporaryFile(mode='wb') as oFile: oDanfe.writeto_pdf(oFile) diff --git a/pytrustnfe/test/test_ginfes.py b/tests/test_ginfes.py similarity index 98% rename from pytrustnfe/test/test_ginfes.py rename to tests/test_ginfes.py index d2895f2..6d23e5e 100644 --- a/pytrustnfe/test/test_ginfes.py +++ b/tests/test_ginfes.py @@ -13,7 +13,7 @@ class test_nfse_ginfes(unittest.TestCase): @unittest.skip def test_consulta_situacao_lote(self): - pfx_source = open('/home/danimar/Downloads/machado.pfx', 'r').read() + pfx_source = open('/home/danimar/Downloads/machado.pfx', 'rb').read() pfx = Certificado(pfx_source, '123456789') dados = {'ambiente': 'homologacao'} diff --git a/pytrustnfe/test/test_nfse_paulistana.py b/tests/test_nfse_paulistana.py similarity index 98% rename from pytrustnfe/test/test_nfse_paulistana.py rename to tests/test_nfse_paulistana.py index 0ed4ded..06d4200 100644 --- a/pytrustnfe/test/test_nfse_paulistana.py +++ b/tests/test_nfse_paulistana.py @@ -54,7 +54,7 @@ class test_nfse_paulistana(unittest.TestCase): return nfse def test_envio_nfse(self): - pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'r').read() + pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'rb').read() pfx = Certificado(pfx_source, '123456') nfse = self._get_nfse() @@ -77,7 +77,7 @@ class test_nfse_paulistana(unittest.TestCase): retorno['object'].ChaveNFeRPS.ChaveRPS.NumeroRPS, 6) def test_nfse_signature(self): - pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'r').read() + pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'rb').read() pfx = Certificado(pfx_source, '123456') nfse = self._get_nfse() @@ -103,7 +103,7 @@ class test_nfse_paulistana(unittest.TestCase): } def test_cancelamento_nfse_ok(self): - pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'r').read() + pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'rb').read() pfx = Certificado(pfx_source, '123456') cancelamento = self._get_cancelamento() @@ -122,7 +122,7 @@ class test_nfse_paulistana(unittest.TestCase): self.assertEqual(retorno['object'].Cabecalho.Sucesso, True) def test_cancelamento_nfse_com_erro(self): - pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'r').read() + pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'rb').read() pfx = Certificado(pfx_source, '123456') cancelamento = self._get_cancelamento() diff --git a/pytrustnfe/test/test_servidores.py b/tests/test_servidores.py similarity index 100% rename from pytrustnfe/test/test_servidores.py rename to tests/test_servidores.py diff --git a/pytrustnfe/test/test_utils.py b/tests/test_utils.py similarity index 91% rename from pytrustnfe/test/test_utils.py rename to tests/test_utils.py index 64cf1fa..247327d 100644 --- a/pytrustnfe/test/test_utils.py +++ b/tests/test_utils.py @@ -60,7 +60,7 @@ class test_utils(unittest.TestCase): chave.validar() chave.cnpj = '1234567891011' self.assertEqual('CNPJ necessário para criar chave NF-e', - cm.exception.message, + str(cm.exception), 'Validação da chave nf-e incorreta') with self.assertRaises(AssertionError) as cm: @@ -68,7 +68,7 @@ class test_utils(unittest.TestCase): chave.validar() chave.estado = '42' self.assertEqual('Estado necessário para criar chave NF-e', - cm.exception.message, + str(cm.exception), 'Validação da chave nf-e incorreta') with self.assertRaises(AssertionError) as cm: @@ -76,7 +76,7 @@ class test_utils(unittest.TestCase): chave.validar() chave.emissao = '0' self.assertEqual('Emissão necessário para criar chave NF-e', - cm.exception.message, + str(cm.exception), 'Validação da chave nf-e incorreta') with self.assertRaises(AssertionError) as cm: @@ -84,7 +84,7 @@ class test_utils(unittest.TestCase): chave.validar() chave.modelo = '55' self.assertEqual('Modelo necessário para criar chave NF-e', - cm.exception.message, + str(cm.exception), 'Validação da chave nf-e incorreta') with self.assertRaises(AssertionError) as cm: @@ -92,7 +92,7 @@ class test_utils(unittest.TestCase): chave.validar() chave.serie = '012' self.assertEqual('Série necessária para criar chave NF-e', - cm.exception.message, + str(cm.exception), 'Validação da chave nf-e incorreta') with self.assertRaises(AssertionError) as cm: @@ -100,7 +100,7 @@ class test_utils(unittest.TestCase): chave.validar() chave.numero = '000000780' self.assertEqual('Número necessário para criar chave NF-e', - cm.exception.message, + str(cm.exception), 'Validação da chave nf-e incorreta') with self.assertRaises(AssertionError) as cm: @@ -108,12 +108,12 @@ class test_utils(unittest.TestCase): chave.validar() chave.tipo = '42' self.assertEqual('Tipo necessário para criar chave NF-e', - cm.exception.message, + str(cm.exception), 'Validação da chave nf-e incorreta') with self.assertRaises(AssertionError) as cm: chave.codigo = '' chave.validar() self.assertEqual('Código necessário para criar chave NF-e', - cm.exception.message, + str(cm.exception), 'Validação da chave nf-e incorreta') diff --git a/pytrustnfe/test/test_xml.py b/tests/test_xml.py similarity index 88% rename from pytrustnfe/test/test_xml.py rename to tests/test_xml.py index ad5162f..45b70b7 100644 --- a/pytrustnfe/test/test_xml.py +++ b/tests/test_xml.py @@ -26,5 +26,5 @@ class test_xmlfilters(unittest.TestCase): self.assertEqual('2016-09-17', format_date(dt.date())) self.assertEqual('2016-09-17T12:12:12', format_datetime(dt)) - word = strip_line_feed(u"olá\ncomo vai\r senhor ") - self.assertEqual(word, u"olá como vai senhor") + word = strip_line_feed("olá\ncomo vai\r senhor ") + self.assertEqual(word, "olá como vai senhor") diff --git a/pytrustnfe/test/test_xml_serializacao.py b/tests/test_xml_serializacao.py similarity index 93% rename from pytrustnfe/test/test_xml_serializacao.py rename to tests/test_xml_serializacao.py index 64d0a2a..3f06e14 100644 --- a/pytrustnfe/test/test_xml_serializacao.py +++ b/tests/test_xml_serializacao.py @@ -15,13 +15,13 @@ class test_xml_serializacao(unittest.TestCase): tag2='ola', tag3='comovai') result = open(os.path.join(path, 'jinja_result.xml'), 'r').read() - self.assertEqual(xml + '\n', result) + self.assertEqual(xml + "\n", result) def test_serializacao_remove_empty(self): path = os.path.join(os.path.dirname(__file__), 'XMLs') xmlElem = render_xml(path, 'jinja_template.xml', True, tag1='oi', tag2='ola', tag3='comovai') - xml = etree.tostring(xmlElem) + xml = etree.tostring(xmlElem, encoding=str) result = open(os.path.join(path, 'jinja_remove_empty.xml'), 'r').read() self.assertEqual(xml + '\n', result) @@ -29,7 +29,6 @@ class test_xml_serializacao(unittest.TestCase): path = os.path.join(os.path.dirname(__file__), 'XMLs') xml_to_clear = open(os.path.join(path, 'jinja_result.xml'), 'r').read() xml, obj = sanitize_response(xml_to_clear) - self.assertEqual(xml, xml_to_clear) self.assertEqual(obj.tpAmb, 'oi') self.assertEqual(obj.CNPJ, 'ola') diff --git a/pytrustnfe/test/teste.pfx b/tests/teste.pfx similarity index 100% rename from pytrustnfe/test/teste.pfx rename to tests/teste.pfx diff --git a/pytrustnfe/test/xml_assinado.xml b/tests/xml_assinado.xml similarity index 100% rename from pytrustnfe/test/xml_assinado.xml rename to tests/xml_assinado.xml diff --git a/pytrustnfe/test/xml_com_qrcode.xml b/tests/xml_com_qrcode.xml similarity index 98% rename from pytrustnfe/test/xml_com_qrcode.xml rename to tests/xml_com_qrcode.xml index 5731c5f..22cf1ef 100644 --- a/pytrustnfe/test/xml_com_qrcode.xml +++ b/tests/xml_com_qrcode.xml @@ -30,11 +30,11 @@ LEL AMBIENTAL LTDA - EPP Zell Ambiental - Rua Padre João + Rua Padre João 444 - Penha de França + Penha de França 3550308 - São Paulo + São Paulo SP 03637000 1058 diff --git a/pytrustnfe/test/xml_sem_qrcode.xml b/tests/xml_sem_qrcode.xml similarity index 98% rename from pytrustnfe/test/xml_sem_qrcode.xml rename to tests/xml_sem_qrcode.xml index 74b8c78..3352a22 100644 --- a/pytrustnfe/test/xml_sem_qrcode.xml +++ b/tests/xml_sem_qrcode.xml @@ -31,11 +31,11 @@ LEL AMBIENTAL LTDA - EPP Zell Ambiental - Rua Padre João + Rua Padre João 444 - Penha de França + Penha de França 3550308 - São Paulo + São Paulo SP 03637000 1058 diff --git a/pytrustnfe/test/xml_valido_assinado.xml b/tests/xml_valido_assinado.xml similarity index 100% rename from pytrustnfe/test/xml_valido_assinado.xml rename to tests/xml_valido_assinado.xml