From 0a5992b447531befb1bf78e6a9ad92d8cc8eeb95 Mon Sep 17 00:00:00 2001 From: Robert Stewart Date: Mon, 24 Apr 2023 17:56:18 -0600 Subject: [PATCH 01/65] Use built in cryptography pkcs7 signature. Signed-off-by: Robert Stewart --- requirements.txt | 2 +- wsaa.py | 47 +++++++++++++++++------------------------------ 2 files changed, 18 insertions(+), 31 deletions(-) diff --git a/requirements.txt b/requirements.txt index 58c97dbc4..94189e16f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ httplib2==0.20.4; python_version > '3' pysimplesoap==1.08.14; python_version <= '2.7' git+https://github.com/pysimplesoap/pysimplesoap.git@py311#pysimplesoap; python_version > '3' cryptography==3.3.2; python_version <= '2.7' -cryptography==3.4.7; python_version > '3' +cryptography==39.0.2; python_version > '3' fpdf>=1.7.2 dbf>=0.88.019 Pillow>=2.0.0 diff --git a/wsaa.py b/wsaa.py index 30bf49d95..4fc470420 100644 --- a/wsaa.py +++ b/wsaa.py @@ -54,6 +54,8 @@ from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.bindings.openssl.binding import Binding + from cryptography.hazmat.primitives.serialization import pkcs7 + except ImportError: ex = exception_info() @@ -116,7 +118,6 @@ def sign_tra(tra, cert=CERT, privatekey=PRIVATEKEY, passphrase=""): _lib = Binding.lib _ffi = Binding.ffi # Crear un buffer desde el texto - bio_in = _lib.BIO_new_mem_buf(tra, len(tra)) # Leer privatekey y cert if not privatekey.startswith(b"-----BEGIN RSA PRIVATE KEY-----"): @@ -136,42 +137,28 @@ def sign_tra(tra, cert=CERT, privatekey=PRIVATEKEY, passphrase=""): cert = open(cert).read() if isinstance(cert, str): cert = cert.encode("utf-8") - cert = x509.load_pem_x509_certificate(cert, default_backend()) + cert = x509.load_pem_x509_certificate(cert) - try: - # Firmar el texto (tra) usando cryptography (openssl bindings para python) - p7 = _lib.PKCS7_sign( - cert._x509, private_key._evp_pkey, _ffi.NULL, bio_in, 0 - ) - finally: - # Liberar memoria asignada - _lib.BIO_free(bio_in) - # Se crea un buffer nuevo porque la firma lo consume - bio_in = _lib.BIO_new_mem_buf(tra, len(tra)) - try: - # Crear buffer de salida - bio_out = _lib.BIO_new(_lib.BIO_s_mem()) - try: - # Instanciar un SMIME - _lib.SMIME_write_PKCS7(bio_out, p7, bio_in, 0) - - # Tomar datos para la salida - result_buffer = _ffi.new("char**") - buffer_length = _lib.BIO_get_mem_data(bio_out, result_buffer) - output = _ffi.buffer(result_buffer[0], buffer_length)[:] - finally: - _lib.BIO_free(bio_out) - finally: - _lib.BIO_free(bio_in) + + p7 = pkcs7.PKCS7SignatureBuilder().set_data( + tra + ).add_signer( + cert, private_key, hashes.SHA256() + ).sign( + serialization.Encoding.SMIME, [pkcs7.PKCS7Options.DetachedSignature] + ) # Generar p7 en formato mail y recortar headers - msg = email.message_from_string(output.decode("utf8")) + msg = email.message_from_string(p7.decode("utf8")) for part in msg.walk(): filename = part.get_filename() - if filename == "smime.p7m": + if filename == "smime.p7s": # Es la parte firmada? # Devolver CMS return part.get_payload(decode=False) + else: + raise RuntimeError("Part not found") + else: # Firmar el texto (tra) usando OPENSSL directamente try: @@ -642,4 +629,4 @@ def main(): print("Expiro?", wsaa.Expirado()) if __name__=="__main__": - main() \ No newline at end of file + main() From 6bb7fe8380f5a12d9abd5c00b0b569dfc20334b1 Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Wed, 21 Jun 2023 12:58:42 +0100 Subject: [PATCH 02/65] feat: get all dependencies from the requirements.txt file automatically for the setup.py build process Signed-off-by: HanslettTheDev --- get_dep.py | 22 ++++++++++++++++++++++ setup.py | 2 ++ 2 files changed, 24 insertions(+) create mode 100644 get_dep.py diff --git a/get_dep.py b/get_dep.py new file mode 100644 index 000000000..942ae29b5 --- /dev/null +++ b/get_dep.py @@ -0,0 +1,22 @@ +import os +def get_dependecies(): + blob = "git+https://github.com" + requirements_path = os.path.join( + os.path.abspath(os.getcwd()), + "requirements.txt" + ) + if os.path.isfile(requirements_path): + with open(requirements_path) as f: + dependencies = [ + ">=".join(x.split("==")) for x in f.read().splitlines() + ] + for x in dependencies: + if x.startswith(blob): + # split the text and join them with the @ command + # index 3 holds the name of the module + chunks = x.split("/") + dependencies[dependencies.index(x)] = x.replace( + blob, chunks[3] + " @ " + blob + ) + break + return dependencies \ No newline at end of file diff --git a/setup.py b/setup.py index c730591dd..e3620253b 100644 --- a/setup.py +++ b/setup.py @@ -13,6 +13,7 @@ __copyright__ = "Copyright (C) 2008-2021 Mariano Reingart" from distutils.core import setup +from get_dep import get_dependecies import glob import os import subprocess @@ -70,6 +71,7 @@ author_email="reingart@gmail.com", url="https://github.com/reingart/pyafipws", license="LGPL-3.0-or-later", + install_requires=get_dependecies() options=opts, data_files=data_files, classifiers=[ From a44d25869eaae898c5ae1d2a205497631c079b27 Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Wed, 21 Jun 2023 13:05:31 +0100 Subject: [PATCH 03/65] fix: added a missing comma in the setup function Signed-off-by: HanslettTheDev --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e3620253b..cd83642cd 100644 --- a/setup.py +++ b/setup.py @@ -71,7 +71,7 @@ author_email="reingart@gmail.com", url="https://github.com/reingart/pyafipws", license="LGPL-3.0-or-later", - install_requires=get_dependecies() + install_requires=get_dependecies(), options=opts, data_files=data_files, classifiers=[ From a882d5b7425dd751fb132bcd15076d6ac28371a5 Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Wed, 21 Jun 2023 13:08:22 +0100 Subject: [PATCH 04/65] fix: cleared a glitch that caused setup.py to fail Signed-off-by: HanslettTheDev --- get_dep.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/get_dep.py b/get_dep.py index 942ae29b5..ca40af5e0 100644 --- a/get_dep.py +++ b/get_dep.py @@ -1,4 +1,6 @@ import os + + def get_dependecies(): blob = "git+https://github.com" requirements_path = os.path.join( From 53c1fd27cb39819961695167f3489c0781eb356f Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Wed, 21 Jun 2023 13:14:41 +0100 Subject: [PATCH 05/65] fix: removed the version number Signed-off-by: HanslettTheDev --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 58c97dbc4..fe879d6d9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ httplib2==0.9.2; python_version <= '2.7' httplib2==0.20.4; python_version > '3' pysimplesoap==1.08.14; python_version <= '2.7' -git+https://github.com/pysimplesoap/pysimplesoap.git@py311#pysimplesoap; python_version > '3' +git+https://github.com/pysimplesoap/pysimplesoap.git@py311#pysimplesoap cryptography==3.3.2; python_version <= '2.7' cryptography==3.4.7; python_version > '3' fpdf>=1.7.2 From 8f1b31d1eb97c0ca9d272ddd9f98f5cdf5029537 Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Fri, 23 Jun 2023 22:57:55 +0100 Subject: [PATCH 06/65] restart github actions Signed-off-by: HanslettTheDev --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index fe879d6d9..77932b737 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ httplib2==0.9.2; python_version <= '2.7' httplib2==0.20.4; python_version > '3' pysimplesoap==1.08.14; python_version <= '2.7' -git+https://github.com/pysimplesoap/pysimplesoap.git@py311#pysimplesoap +git+https://github.com/pysimplesoap/pysimplesoap.git@py311#pysimplesoap cryptography==3.3.2; python_version <= '2.7' cryptography==3.4.7; python_version > '3' fpdf>=1.7.2 From f0edb223b75773a72408e49f11b01772737c7ead Mon Sep 17 00:00:00 2001 From: Mariano Reingart Date: Sun, 19 Mar 2023 20:44:22 -0300 Subject: [PATCH 07/65] Only run release on original repository, not forks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This avoids errors with dependabot PR: ``` Run marvinpinto/action-automatic-releases@latest Initializing the Automatic Releases action Determining release tags Retrieving commit history Generating changelog Generating release tag Attempting to create or update release tag "beta" Could not create new tag "refs/tags/beta" (Resource not accessible by integration) therefore updating existing tag "tags/beta" Error: Resource not accessible by integration ``` Signed-off-by: Nicolás Sandoval --- .github/workflows/windows-installer.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/windows-installer.yml b/.github/workflows/windows-installer.yml index b16b5dcb1..fb15ab1c0 100644 --- a/.github/workflows/windows-installer.yml +++ b/.github/workflows/windows-installer.yml @@ -156,6 +156,7 @@ jobs: run: | cat dist-64/.env >> $GITHUB_ENV - uses: "marvinpinto/action-automatic-releases@latest" + if: github.event.pull_request.head.repo.full_name == github.repository with: repo_token: "${{ secrets.GITHUB_TOKEN }}" automatic_release_tag: ${{ (github.ref_name != 'main') && 'beta' || 'latest' }} From 9dc23811d67dfb38eb9cf0abeb17dc862d315345 Mon Sep 17 00:00:00 2001 From: Mariano Reingart Date: Sun, 19 Mar 2023 21:27:54 -0300 Subject: [PATCH 08/65] Properly fix dependabot read-only token MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/dependabot/dependabot-core/issues/3253 Signed-off-by: Nicolás Sandoval --- .github/workflows/windows-installer.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows-installer.yml b/.github/workflows/windows-installer.yml index fb15ab1c0..dffb9fc7a 100644 --- a/.github/workflows/windows-installer.yml +++ b/.github/workflows/windows-installer.yml @@ -156,7 +156,7 @@ jobs: run: | cat dist-64/.env >> $GITHUB_ENV - uses: "marvinpinto/action-automatic-releases@latest" - if: github.event.pull_request.head.repo.full_name == github.repository + if: github.actor != 'dependabot[bot]' with: repo_token: "${{ secrets.GITHUB_TOKEN }}" automatic_release_tag: ${{ (github.ref_name != 'main') && 'beta' || 'latest' }} From 84ee6c9e5609005c40ebcdec9c3c2de61be877d6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 25 Feb 2023 20:46:55 +0000 Subject: [PATCH 09/65] Bump future from 0.18.2 to 0.18.3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [future](https://github.com/PythonCharmers/python-future) from 0.18.2 to 0.18.3. - [Release notes](https://github.com/PythonCharmers/python-future/releases) - [Changelog](https://github.com/PythonCharmers/python-future/blob/master/docs/changelog.rst) - [Commits](https://github.com/PythonCharmers/python-future/compare/v0.18.2...v0.18.3) --- updated-dependencies: - dependency-name: future dependency-type: direct:production ... Signed-off-by: dependabot[bot] Signed-off-by: Nicolás Sandoval --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 58c97dbc4..59cb59a04 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,4 @@ Pillow>=2.0.0 tabulate==0.8.5 certifi>=2020.4.5.1 qrcode==6.1 -future==0.18.2 +future==0.18.3 From c8bd0f7b8cbf282713664369a7ac0bedcd594f14 Mon Sep 17 00:00:00 2001 From: Mariano Reingart Date: Wed, 28 Jun 2023 00:16:24 -0300 Subject: [PATCH 10/65] Cumulative updates f/ develop (python3 conversion) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 2to3 -w file.py * python -m black file.py * iconv -f iso-8859-1 -t utf-8 < file_new.py > file.py Signed-off-by: Nicolás Sandoval --- formatos/formato_xml.py | 7 +++ ws_sr_padron.py | 11 +++- wsfexv1.py | 74 +++++++++++++++++++++--- wslpg.py | 50 ++++++++-------- wsmtx.py | 41 ++++++++----- wsremharina.py | 124 ++++++++++++++++++++++++++++++---------- 6 files changed, 231 insertions(+), 76 deletions(-) diff --git a/formatos/formato_xml.py b/formatos/formato_xml.py index ae0abc332..4b8b74882 100644 --- a/formatos/formato_xml.py +++ b/formatos/formato_xml.py @@ -49,6 +49,7 @@ "numero_cotizacion": str, "numero_remito": str, "ape": str, + "permiso_existente": str, "incoterms": str, "detalleincoterms": str, "destinocmp": int, @@ -188,6 +189,12 @@ "fecha_serv_desde": "fechaservdesde", "fecha_serv_hasta": "fechaservhasta", "fecha_venc_pago": "fechavencpago", + "tipo_expo": "concepto", + "incoterms": "incoterms", + "incoterms_ds": "detalleincoterms", + "pais_dst_cmp": "destinocmp", + "idioma_cbte": "idioma", + "permiso_existente": "permiso_existente", "obs_generales": "otrosdatosgenerales", "obs_comerciales": "otrosdatoscomerciales", "resultado": "resultado", diff --git a/ws_sr_padron.py b/ws_sr_padron.py index d5f305461..ef222f1ed 100644 --- a/ws_sr_padron.py +++ b/ws_sr_padron.py @@ -23,9 +23,9 @@ from builtins import next __author__ = "Mariano Reingart " -__copyright__ = "Copyright (C) 2017 Mariano Reingart" +__copyright__ = "Copyright (C) 2017-2022 Mariano Reingart" __license__ = "GPL 3.0" -__version__ = "3.04e" +__version__ = "3.05a" import csv import datetime @@ -42,6 +42,7 @@ abrir_conf, norm, SoapFault, + safe_console, ) from configparser import SafeConfigParser from pyafipws.padron import TIPO_CLAVE, PROVINCIAS @@ -54,6 +55,7 @@ class WSSrPadronA4(BaseWS): +<<<<<<< HEAD "Interfaz para el WebService de Consulta Padrón Contribuyentes Alcance 4" _public_methods_ = [ "Consultar", @@ -97,6 +99,7 @@ class WSSrPadronA4(BaseWS): "nro_doc", "tipo_persona", "estado", + "es_sucesion", "impuestos", "actividades", "direccion", @@ -125,6 +128,7 @@ def inicializar(self): self.tipo_persona = "" # FISICA o JURIDICA self.tipo_doc = self.nro_doc = 0 self.estado = "" # ACTIVO + self.es_sucesion = "" self.denominacion = "" self.direccion = self.localidad = self.provincia = self.cod_postal = "" self.domicilios = [] @@ -274,6 +278,7 @@ def Consultar(self, id_persona): self.nro_doc = data.get("idPersona") self.cuit = self.nro_doc self.estado = data.get("estadoClave") + self.es_sucesion = data.get("esSucesion") if not "razonSocial" in data: self.denominacion = ", ".join( [data.get("apellido", ""), data.get("nombre", "")] @@ -320,6 +325,7 @@ def main(): global CONFIG_FILE DEBUG = "--debug" in sys.argv + safe_console() if "--constancia" in sys.argv: padron = WSSrPadronA5() @@ -427,6 +433,7 @@ def main(): print("Denominacion:", padron.denominacion) print("Tipo:", padron.tipo_persona, padron.tipo_doc, padron.nro_doc) print("Estado:", padron.estado) + print("Es Sucesion:", padron.es_sucesion) print("Direccion:", padron.direccion) print("Localidad:", padron.localidad) print("Provincia:", padron.provincia) diff --git a/wsfexv1.py b/wsfexv1.py index d91862ebc..8b5fa6085 100644 --- a/wsfexv1.py +++ b/wsfexv1.py @@ -21,9 +21,9 @@ from builtins import str __author__ = "Mariano Reingart (reingart@gmail.com)" -__copyright__ = "Copyright (C) 2011-2021 Mariano Reingart" +__copyright__ = "Copyright (C) 2011-2023 Mariano Reingart" __license__ = "LGPL-3.0-or-later" -__version__ = "3.10a" +__version__ = "3.11a" import datetime import decimal @@ -44,6 +44,7 @@ class WSFEXv1(BaseWS): "GetCMP", "AgregarPermiso", "AgregarCmpAsoc", + "AgregarActividad", "GetParamMon", "GetParamTipoCbte", "GetParamTipoExpo", @@ -67,6 +68,7 @@ class WSFEXv1(BaseWS): "GetParametro", "GetLastCMP", "GetLastID", + "GetParamActividades", "Dummy", "Conectar", "SetTicketAcceso", @@ -192,6 +194,7 @@ def CrearFactura( "cbtes_asoc": [], "permisos": [], "detalles": [], + "actividades": [], } self.factura = fact @@ -237,6 +240,12 @@ def AgregarCmpAsoc( ) return True + def AgregarActividad(self, actividad_id=0, **kwarg): + "Agrego actividad a una factura (interna)" + act = {"actividad_id": actividad_id} + self.factura["actividades"].append(act) + return True + @inicializar_y_capturar_excepciones def Authorize(self, id): "Autoriza la factura cargada en memoria" @@ -304,6 +313,16 @@ def Authorize(self, id): } for d in f["detalles"] ], + "Actividades": f["actividades"] + and [ + { + "Actividad": { + "Id": a["actividad_id"], + } + } + for a in f["actividades"] + ] + or None, }, ) @@ -748,23 +767,57 @@ def GetParamPtosVenta(self, sep="|"): res = ret["FEXGetPARAM_PtoVentaResult"].get("FEXResultGet") ret = [] for pu in res: - u = pu["ClsFEXResponse_PtoVenta"] + p = pu["ClsFEXResponse_PtoVenta"] try: r = { - "nro": u.get("Pve_Nro"), - "baja": u.get("Pve_FchBaj"), - "bloqueado": u.get("Pve_Bloqueado"), + "nro": p.get("Pve_Nro"), + "baja": p.get("Pve_FchBaj"), + "bloqueado": p.get("Pve_Bloqueado"), } except Exception as e: print(e) ret.append(r) return [ - (u"%(nro)s\tBloqueado:%(bloqueado)s\tFchBaja:%(baja)s" % r).replace( + ("%(nro)s\tBloqueado:%(bloqueado)s\tFchBaja:%(baja)s" % r).replace( "\t", sep ) for r in ret ] + @inicializar_y_capturar_excepciones + def GetParamActividades(self, sep="|"): + "Recuperar lista de valores referenciales de códigos de Idiomas" + ret = self.client.FEXGetPARAM_Actividades( + Auth={ + "Token": self.Token, + "Sign": self.Sign, + "Cuit": self.Cuit, + } + ) + result = ret["FEXGetPARAM_ActividadesResult"] + self.__analizar_errores(result) + + ret = [] + for u in result.get("FEXResultGet", []): + u = u["ClsFEXResponse_ActividadTipo"] + try: + r = { + "codigo": u.get("Id"), + "ds": u.get("Desc"), + "orden": u.get("Orden"), + } + except Exception as e: + print(e) + + ret.append(r) + if sep: + return [ + ("\t%(codigo)s\t%(ds)s\t%(orden)s\t" % it).replace("\t", sep) + for it in ret + ] + else: + return ret + class WSFEX(WSFEXv1): "Wrapper para retrocompatibilidad con WSFEX" @@ -936,6 +989,8 @@ def main(): cbteasoc_tipo, cbteasoc_pto_vta, cbteasoc_nro, cbteasoc_cuit ) + ok = wsfexv1.AgregarActividad(1234) + ##id = "99000000000100" # número propio de transacción # obtengo el último ID y le adiciono 1 # (advertencia: evitar overflow y almacenar!) @@ -1039,6 +1094,11 @@ def main(): for r in ret: print("||%(codigo)s||%(ds)s||" % r) + print("=== Actividades ===") + ret = wsfexv1.GetParamActividades(sep=False) + for r in ret: + print("||%(codigo)s||%(ds)s||" % r) + if "--ctz" in sys.argv: print(wsfexv1.GetParamCtz("DOL")) diff --git a/wslpg.py b/wslpg.py index 2537161b5..a5c40d5d9 100644 --- a/wslpg.py +++ b/wslpg.py @@ -25,9 +25,9 @@ from past.builtins import basestring __author__ = "Mariano Reingart " -__copyright__ = "Copyright (C) 2013-2021 Mariano Reingart" +__copyright__ = "Copyright (C) 2013-2022 Mariano Reingart" __license__ = "LGPL-3.0-or-later" -__version__ = "3.35b" +__version__ = "3.35e" LICENCIA = """ wslpg.py: Interfaz para generar Código de Operación Electrónica para @@ -160,11 +160,11 @@ ("nro_ing_bruto_corredor", 15, N), ("comision_corredor", 5, I, 2), # 3.2 ("fecha_precio_operacion", 10, A), # 26/02/2013 - ("precio_ref_tn", 8, I, 3), # 4.3 + ("precio_ref_tn", 17, I, 3), # 4.3 ("cod_grado_ref", 2, A), ("cod_grado_ent", 2, A), ("factor_ent", 6, I, 3), # 3.3 - ("precio_flete_tn", 7, I, 2), # 5.2 + ("precio_flete_tn", 17, I, 2), # 5.2 ("cont_proteico", 6, I, 3), # 3.3 ("alic_iva_operacion", 5, I, 2), # 3.2 ("campania_ppal", 4, N), @@ -237,7 +237,7 @@ ("tipo_reg", 1, A), # 2: Retencion ("codigo_concepto", 2, A), ("detalle_aclaratorio", 30, A), - ("base_calculo", 10, I, 2), # 8.2 + ("base_calculo", 17, I, 2), # 8.2 ("alicuota", 6, I, 2), # 3.2 ("nro_certificado_retencion", 14, N), ("fecha_certificado_retencion", 10, A), @@ -252,17 +252,17 @@ ("dias_almacenaje", 4, N), ("reservado1", 6, I, 3), ("comision_gastos_adm", 5, I, 2), # 3.2 - ("base_calculo", 10, I, 2), # 8.2 + ("base_calculo", 17, I, 2), # 8.2 ("alicuota", 6, I, 2), # 3.2 ("importe_iva", 17, I, 2), # 17.2 ("importe_deduccion", 17, I, 2), # 17.2 - ("precio_pkg_diario", 11, I, 8), # 3.8, ajustado WSLPGv1.2 + ("precio_pkg_diario", 17, I, 8), # 3.8, ajustado WSLPGv1.2 ] PERCEPCION = [ ("tipo_reg", 1, A), # P: Percepcion ("detalle_aclaratoria", 50, A), # max 50 por WSLPGv1.8 - ("base_calculo", 10, I, 2), # 8.2 + ("base_calculo", 17, I, 2), # 8.2 ("alicuota", 6, I, 2), # 3.2 ("importe_final", 19, I, 2), # 17.2 (LPG WSLPGv1.16) ] @@ -348,10 +348,10 @@ ("servicios_otros", 7, I, 3), ("servicios_forma_de_pago", 20, A), # campos para cgAutorizarRetiroTransferencia (WSLPGv1.6): - ('cuit_receptor', 11, N), - ('fecha', 10, A), # no usado WSLPGv1.8 - ('nro_carta_porte_a_utilizar', 13, N), # obligatorio para retiro - ('cee_carta_porte_a_utilizar', 10, N), # no usado WSLPGv1.8 + ("cuit_receptor", 11, N), + ("fecha", 10, A), # no usado WSLPGv1.8 + ("nro_carta_porte_a_utilizar", 13, N), # obligatorio para retiro + ("cee_carta_porte_a_utilizar", 10, N), # no usado WSLPGv1.8 # para cgAutorizarPreexistente (WSLPGv1.6): ("tipo_certificado_deposito_preexistente", 1, N), # "R": Retiro "T": Tra. ("nro_certificado_deposito_preexistente", 12, N), @@ -386,18 +386,18 @@ ("servicios_otras_percepciones", 10, I, 2), ] -CTG = [ # para cgAutorizarDeposito (WSLPGv1.6) - ('tipo_reg', 1, A), # C: CTG - ('nro_ctg', 12, A), - ('nro_carta_porte', 13, A), - ('porcentaje_secado_humedad', 5, I, 2), - ('importe_secado', 10, I, 2), - ('peso_neto_merma_secado', 10, I, 2), - ('tarifa_secado', 10, I, 2), - ('importe_zarandeo', 10, I, 2), - ('peso_neto_merma_zarandeo', 10, I, 2), - ('tarifa_zarandeo', 10, I, 2), - ('peso_neto_confirmado_definitivo', 10, I, 2), +CTG = [ # para cgAutorizarDeposito (WSLPGv1.6) + ("tipo_reg", 1, A), # C: CTG + ("nro_ctg", 12, A), + ("nro_carta_porte", 13, A), + ("porcentaje_secado_humedad", 5, I, 2), + ("importe_secado", 10, I, 2), + ("peso_neto_merma_secado", 10, I, 2), + ("tarifa_secado", 10, I, 2), + ("importe_zarandeo", 10, I, 2), + ("peso_neto_merma_zarandeo", 10, I, 2), + ("tarifa_zarandeo", 10, I, 2), + ("peso_neto_confirmado_definitivo", 10, I, 2), ] DET_MUESTRA_ANALISIS = [ # para cgAutorizarDeposito (WSLPGv1.6) @@ -4593,7 +4593,7 @@ def main(): if "--lsg" in sys.argv: print("Anulando COE LSG", coe) - ret = wslpg.AnularLiquidacionSecundaria(coe) + ret = wslpg.AnularLiquidacionSecundaria(pto_emision, nro_orden, coe) if "--cg" in sys.argv: print("Anulando COE CG", coe) ret = wslpg.AnularCertificacion(coe) diff --git a/wsmtx.py b/wsmtx.py index 3ff538685..798c66df0 100644 --- a/wsmtx.py +++ b/wsmtx.py @@ -20,9 +20,9 @@ from builtins import str __author__ = "Mariano Reingart " -__copyright__ = "Copyright (C) 2010-2021 Mariano Reingart" +__copyright__ = "Copyright (C) 2010-2023 Mariano Reingart" __license__ = "LGPL-3.0-or-later" -__version__ = "3.16a" +__version__ = "3.16b" import datetime import decimal @@ -362,11 +362,10 @@ def AgregarOpcional( self.factura["opcionales"].append(op) return True - @inicializar_y_capturar_excepciones def AgregarActividad(self, actividad_id=0, **kwarg): "Agrego actividades a una factura (interna)" - act = { 'actividad_id': actividad_id} - self.factura['actividades'].append(act) + act = {"actividad_id": actividad_id} + self.factura["actividades"].append(act) return True @inicializar_y_capturar_excepciones @@ -895,6 +894,16 @@ def InformarComprobanteCAEA(self): for dato in f["opcionales"] ] or None, + "arrayActividades": f["actividades"] + and [ + { + "actividad": { + "codigo": act["actividad_id"], + } + } + for act in f["actividades"] + ] + or None, } # fecha de vencimiento opcional (igual al último día de vigencia del CAEA) @@ -1460,10 +1469,16 @@ def ConsultarPtosVtaCAEANoInformados(self, caea): def ConsultarActividadesVigentes(self): "Este método permite consultar las actividades vigentes para el contribuyente" ret = self.client.consultarActividadesVigentes( - authRequest={'token': self.Token, 'sign': self.Sign, 'cuitRepresentada': self.Cuit}, - ) - return ["%(codigo)s: %(orden)s %(descripcion)s" % p['actividad'] - for p in ret['arrayActividades']] + authRequest={ + "token": self.Token, + "sign": self.Sign, + "cuitRepresentada": self.Cuit, + }, + ) + return [ + "%(codigo)s: %(orden)s %(descripcion)s" % p["actividad"] + for p in ret["arrayActividades"] + ] def main(): @@ -1498,7 +1513,7 @@ def main(): if "--prueba" in sys.argv: ##print wsmtxca.client.help("autorizarComprobante").encode("latin1") try: - tipo_cbte = 1 + tipo_cbte = 201 punto_vta = 4000 cbte_nro = wsmtxca.ConsultarUltimoComprobanteAutorizado( tipo_cbte, punto_vta @@ -1613,7 +1628,7 @@ def main(): if "--rg4540" in sys.argv: wsmtxca.AgregarPeriodoComprobantesAsociados("2020-01-01", "2020-01-31") - if '--rg5259' in sys.argv: + if "--rg5259" in sys.argv: wsmtxca.AgregarActividad(960990) print(wsmtxca.factura) @@ -1772,8 +1787,8 @@ def main(): print(wsmtxca.ConsultarUnidadesMedida()) print(wsmtxca.ConsultarTiposTributo()) print(wsmtxca.ConsultarTiposDatosAdicionales()) - if '--rg5259' in sys.argv: - print(wsmtxca.ConsultarActividadesVigentes()) + if "--rg5259" in sys.argv: + print("\n".join(wsmtxca.ConsultarActividadesVigentes())) if "--puntosventa" in sys.argv: print(wsmtxca.ConsultarPuntosVentaCAE()) diff --git a/wsremharina.py b/wsremharina.py index 03448e33f..abf9905e8 100644 --- a/wsremharina.py +++ b/wsremharina.py @@ -23,9 +23,9 @@ from builtins import input __author__ = "Mariano Reingart " -__copyright__ = "Copyright (C) 2018-2021 Mariano Reingart" +__copyright__ = "Copyright (C) 2018-2023 Mariano Reingart" __license__ = "LGPL-3.0-or-later" -__version__ = "3.06a" +__version__ = "3.07c" LICENCIA = """ wsremhairna.py: Interfaz para generar Remito Electrónico Harinero AFIP v2.0 @@ -104,23 +104,78 @@ class WSRemHarina(BaseWS): "Interfaz para el WebService de Remito Electronico Carnico (Version 3)" - _public_methods_ = ['Conectar', 'Dummy', 'SetTicketAcceso', 'DebugLog', - 'GenerarRemito', 'EmitirRemito', 'AutorizarRemito', 'AnularRemito', 'ConsultarRemito', - 'InformarContingencia', 'ModificarViaje', 'RegistrarRecepcion', 'ConsultarUltimoRemitoEmitido', - 'CrearRemito', 'AgregarViaje', 'AgregarVehiculo', 'AgregarMercaderia', - 'AgregarReceptor', 'AgregarDepositario', 'AgregarTransportista', - 'AgregarDatosAutorizacion', 'AgregarContingencia', - 'ConsultarTiposMercaderia', 'ConsultarTiposEmbalaje', 'ConsultarTiposUnidades', 'ConsultarTiposComprobante', - 'ConsultarPaises', 'ConsultarReceptoresValidos', - 'ConsultarTiposEstado', 'ConsultarTiposContingencia', 'ConsultarCodigosDomicilio', 'ConsultarPuntosEmision', - 'SetParametros', 'SetParametro', 'GetParametro', 'AnalizarXml', 'ObtenerTagXml', 'LoadTestXML', - ] - _public_attrs_ = ['XmlRequest', 'XmlResponse', 'Version', 'Traceback', 'Excepcion', 'LanzarExcepciones', - 'Token', 'Sign', 'Cuit', 'AppServerStatus', 'DbServerStatus', 'AuthServerStatus', - 'CodRemito', 'TipoComprobante', 'PuntoEmision', - 'NroRemito', 'CodAutorizacion', 'FechaVencimiento', 'FechaEmision', 'Estado', 'Resultado', 'QR', - 'ErrCode', 'ErrMsg', 'Errores', 'ErroresFormato', 'Observaciones', 'Obs', 'Evento', 'Eventos', - ] + _public_methods_ = [ + "Conectar", + "Dummy", + "SetTicketAcceso", + "DebugLog", + "GenerarRemito", + "EmitirRemito", + "AutorizarRemito", + "AnularRemito", + "ConsultarRemito", + "InformarContingencia", + "ModificarViaje", + "RegistrarRecepcion", + "ConsultarUltimoRemitoEmitido", + "CrearRemito", + "AgregarViaje", + "AgregarVehiculo", + "AgregarMercaderia", + "AgregarReceptor", + "AgregarDepositario", + "AgregarTransportista", + "AgregarDatosAutorizacion", + "AgregarContingencia", + "ConsultarTiposMercaderia", + "ConsultarTiposEmbalaje", + "ConsultarTiposUnidades", + "ConsultarTiposComprobante", + "ConsultarPaises", + "ConsultarReceptoresValidos", + "ConsultarTiposEstado", + "ConsultarTiposContingencia", + "ConsultarCodigosDomicilio", + "ConsultarPuntosEmision", + "SetParametros", + "SetParametro", + "GetParametro", + "AnalizarXml", + "ObtenerTagXml", + "LoadTestXML", + ] + _public_attrs_ = [ + "XmlRequest", + "XmlResponse", + "Version", + "Traceback", + "Excepcion", + "LanzarExcepciones", + "Token", + "Sign", + "Cuit", + "AppServerStatus", + "DbServerStatus", + "AuthServerStatus", + "CodRemito", + "TipoComprobante", + "PuntoEmision", + "NroRemito", + "CodAutorizacion", + "FechaVencimiento", + "FechaEmision", + "Estado", + "Resultado", + "QR", + "ErrCode", + "ErrMsg", + "Errores", + "ErroresFormato", + "Observaciones", + "Obs", + "Evento", + "Eventos", + ] _reg_progid_ = "WSRemHarina" _reg_clsid_ = "{72BFB9B9-0FD9-497C-8C62-5D41F7029377}" @@ -183,12 +238,14 @@ def CrearRemito( tipo_movimiento, cuit_titular, es_entrega_mostrador=None, + es_mercaderia_consignacion=None, importe_cot=None, tipo_emisor=None, ruca_est_emisor=None, cod_rem_redestinar=None, cod_remito=None, estado=None, + observaciones=None, **kwargs ): "Inicializa internamente los datos de un remito para autorizar" @@ -199,11 +256,13 @@ def CrearRemito( "cuitTitular": cuit_titular, "tipoMovimiento": tipo_movimiento, "esEntregaMostrador": es_entrega_mostrador, # S o N + "esMercaderiaEnConsignacion": es_mercaderia_consignacion, # S o N "importeCot": importe_cot, "estado": estado, "codRemito": cod_remito, "codRemRedestinado": cod_rem_redestinar, "rucaEstEmisor": ruca_est_emisor, + "observaciones": observaciones, "arrayMercaderia": [], "arrayContingencias": [], } @@ -326,12 +385,16 @@ def AgregarMercaderia( peso_neto_per_kg=None, peso_neto_red_kg=None, peso_neto_rei_kg=None, + cod_comer=None, + desc_comer=None, **kwargs ): "Agrega la información referente a la mercadería del remito electrónico harinero" mercaderia = dict( orden=orden, codTipo=cod_tipo, + codComer=cod_comer, + descComer=desc_comer, codTipoEmb=cod_tipo_emb, cantidadEmb=cantidad_emb, codTipoUnidad=cod_tipo_unidad, @@ -398,15 +461,16 @@ def AnalizarRemito(self, ret, archivo=None): self.CodRemito = ret.get("codRemito") self.TipoComprobante = ret.get("tipoComprobante") self.PuntoEmision = ret.get("puntoEmision") - datos_aut = ret.get("datosAutAFIP") + out = ret.get("remitoOutput") + datos_aut = out.get("datosAutAFIP") if datos_aut: self.NroRemito = datos_aut.get("nroRemito") self.CodAutorizacion = datos_aut.get("codAutorizacion") self.FechaEmision = datos_aut.get("fechaEmision") self.FechaVencimiento = datos_aut.get("fechaVencimiento") - self.Estado = ret.get("estado", ret.get("estadoRemito")) + self.Estado = out.get("estado", ret.get("estadoRemito")) self.Resultado = ret.get("resultado") - self.QR = ret.get("qr") or "" + self.QR = out.get("qr") or "" if archivo: f = open(archivo, "wb") f.write(self.QR) @@ -724,12 +788,14 @@ def ConsultarPuntosEmision(self, sep="||"): def ConsultarReceptoresValidos(self, cuit_titular, sep="||"): "Obtener el código de depositos que tiene habilitados para operar el cuit informado" res = self.client.consultarReceptoresValidos( - authRequest={ - 'token': self.Token, 'sign': self.Sign, - 'cuitRepresentada': self.Cuit, }, - arrayReceptores=[{"receptores": {"cuitReceptor": cuit_titular}}], - ) - ret = res['consultarReceptoresValidosReturn'] + authRequest={ + "token": self.Token, + "sign": self.Sign, + "cuitRepresentada": self.Cuit, + }, + arrayReceptores=[{"receptores": {"cuitReceptor": cuit_titular}}], + ) + ret = res["consultarReceptoresValidosReturn"] self.Resultado = ret["resultado"] return True @@ -1032,7 +1098,7 @@ def main(): ret = wsremharina.ConsultarPuntosEmision() print("\n".join(ret)) - if '--receptores' in sys.argv: + if "--receptores" in sys.argv: try: cuit = int(sys.argv[-1]) except: From 7b258e5e5cc90381748f50c193b45f73a23e146b Mon Sep 17 00:00:00 2001 From: Mariano Reingart Date: Wed, 28 Jun 2023 00:29:15 -0300 Subject: [PATCH 11/65] Update copyright and remove merge typo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nicolás Sandoval --- ws_sr_padron.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ws_sr_padron.py b/ws_sr_padron.py index ef222f1ed..9903e20e7 100644 --- a/ws_sr_padron.py +++ b/ws_sr_padron.py @@ -23,8 +23,8 @@ from builtins import next __author__ = "Mariano Reingart " -__copyright__ = "Copyright (C) 2017-2022 Mariano Reingart" -__license__ = "GPL 3.0" +__copyright__ = "Copyright (C) 2017-2023 Mariano Reingart" +__license__ = "LGPL-3.0-or-later" __version__ = "3.05a" import csv @@ -55,7 +55,6 @@ class WSSrPadronA4(BaseWS): -<<<<<<< HEAD "Interfaz para el WebService de Consulta Padrón Contribuyentes Alcance 4" _public_methods_ = [ "Consultar", From c1462dafc18c09e62e7b02fcb3e3c07409bbaa3c Mon Sep 17 00:00:00 2001 From: Mariano Reingart Date: Wed, 28 Jun 2023 00:38:25 -0300 Subject: [PATCH 12/65] WSFEv1: update copyright MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nicolás Sandoval --- wsfev1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wsfev1.py b/wsfev1.py index abad6385c..76d11d831 100644 --- a/wsfev1.py +++ b/wsfev1.py @@ -29,7 +29,7 @@ from past.builtins import basestring __author__ = "Mariano Reingart " -__copyright__ = "Copyright (C) 2010-2021 Mariano Reingart" +__copyright__ = "Copyright (C) 2010-2023 Mariano Reingart" __license__ = "LGPL-3.0-or-later" __version__ = "3.26a" From 1b463135eeeadd7ca9b30200584838bbe67549a8 Mon Sep 17 00:00:00 2001 From: Mariano Reingart Date: Wed, 28 Jun 2023 00:47:56 -0300 Subject: [PATCH 13/65] WSFEv1: re-sync updates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nicolás Sandoval --- wsfev1.py | 53 ++++++++++++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/wsfev1.py b/wsfev1.py index 76d11d831..034ec6665 100644 --- a/wsfev1.py +++ b/wsfev1.py @@ -31,7 +31,7 @@ __author__ = "Mariano Reingart " __copyright__ = "Copyright (C) 2010-2023 Mariano Reingart" __license__ = "LGPL-3.0-or-later" -__version__ = "3.26a" +__version__ = "3.27c" import datetime import decimal @@ -140,9 +140,10 @@ class WSFEv1(BaseWS): _reg_class_spec_ = "pyafipws.wsfev1.WSFEv1" if TYPELIB: - _typelib_guid_ = '{8AE2BD1D-A216-4E98-95DB-24A11225EF67}' + _typelib_guid_ = "{8AE2BD1D-A216-4E98-95DB-24A11225EF67}" _typelib_version_ = 1, 26 - _com_interfaces_ = ['IWSFEv1'] + _com_interfaces_ = ["IWSFEv1"] + ##_reg_class_spec_ = "wsfev1.WSFEv1" # Variables globales para BaseWS: HOMO = HOMO @@ -329,8 +330,8 @@ def AgregarComprador(self, doc_tipo=80, doc_nro=0, porcentaje=100.00, **kwarg): def AgregarActividad(self, actividad_id=0, **kwarg): "Agrego actividad a una factura (interna)" - op = { 'actividad_id': actividad_id } - self.factura['actividades'].append(op) + act = {"actividad_id": actividad_id} + self.factura["actividades"].append(act) return True def ObtenerCampoFactura(self, *campos): @@ -381,9 +382,6 @@ def CAESolicitar(self): "FchServDesde": f.get("fecha_serv_desde"), "FchServHasta": f.get("fecha_serv_hasta"), "FchVtoPago": f.get("fecha_venc_pago"), - "FchServDesde": f.get("fecha_serv_desde"), - "FchServHasta": f.get("fecha_serv_hasta"), - "FchVtoPago": f["fecha_venc_pago"], "MonId": f["moneda_id"], "MonCotiz": f["moneda_ctz"], "PeriodoAsoc": { @@ -456,7 +454,7 @@ def CAESolicitar(self): "Actividades": [ { "Actividad": { - 'Id': actividad['actividad_id'], + "Id": actividad["actividad_id"], } } for actividad in f["actividades"] @@ -638,9 +636,9 @@ def CompConsultar(self, tipo_cbte, punto_vta, cbte_nro, reproceso=False): } copia = resultget.copy() # TODO: ordenar / convertir opcionales (por ahora no se verifican) - del verificaciones['Opcionales'] - if 'Opcionales' in copia: - del copia['Opcionales'] + del verificaciones["Opcionales"] + if "Opcionales" in copia: + del copia["Opcionales"] verifica(verificaciones, copia, difs) if difs: print("Diferencias:", difs) @@ -802,9 +800,6 @@ def CAESolicitarX(self): "FchServDesde": f.get("fecha_serv_desde"), "FchServHasta": f.get("fecha_serv_hasta"), "FchVtoPago": f.get("fecha_venc_pago"), - "FchServDesde": f.get("fecha_serv_desde"), - "FchServHasta": f.get("fecha_serv_hasta"), - "FchVtoPago": f["fecha_venc_pago"], "MonId": f["moneda_id"], "MonCotiz": f["moneda_ctz"], "PeriodoAsoc": { @@ -1034,9 +1029,6 @@ def CAEARegInformativo(self): "FchServDesde": f.get("fecha_serv_desde"), "FchServHasta": f.get("fecha_serv_hasta"), "FchVtoPago": f.get("fecha_venc_pago"), - "FchServDesde": f.get("fecha_serv_desde"), - "FchServHasta": f.get("fecha_serv_hasta"), - "FchVtoPago": f["fecha_venc_pago"], "MonId": f["moneda_id"], "MonCotiz": f["moneda_ctz"], "PeriodoAsoc": { @@ -1095,6 +1087,15 @@ def CAEARegInformativo(self): for opcional in f["opcionales"] ] or None, + "Actividades": [ + { + "Actividad": { + "Id": actividad["actividad_id"], + } + } + for actividad in f["actividades"] + ] + or None, "CAEA": f["caea"], "CbteFchHsGen": f.get("fecha_hs_gen"), } @@ -1301,13 +1302,15 @@ def ParamGetPtosVenta(self, sep="|"): @inicializar_y_capturar_excepciones def ParamGetActividades(self, sep="|"): - "Recuperador de valores referenciales de códigos de Actividades" + "Recuperador de valores referenciales de c�digos de Actividades" ret = self.client.FEParamGetActividades( - Auth={'Token': self.Token, 'Sign': self.Sign, 'Cuit': self.Cuit}, - ) - res = ret['FEParamGetActividades'] - return [(u"%(Id)s\t%(Orden)s\t%(Desc)s" % p['ActividadesTipo']).replace("\t", sep) - for p in res['ResultGet']] + Auth={"Token": self.Token, "Sign": self.Sign, "Cuit": self.Cuit}, + ) + res = ret["FEParamGetActividadesResult"] + return [ + ("%(Id)s\t%(Orden)s\t%(Desc)s" % p["ActividadesTipo"]).replace("\t", sep) + for p in res["ResultGet"] + ] def p_assert_eq(a, b): @@ -1500,7 +1503,7 @@ def main(): if "--rg4540" in sys.argv: wsfev1.AgregarPeriodoComprobantesAsociados("20200101", "20200131") - if '--rg5259' in sys.argv: + if "--rg5259" in sys.argv: wsfev1.AgregarActividad(960990) # agregar la factura creada internamente para solicitud múltiple: From 686b31616c1f070b87624c5e362347d1ecf918ba Mon Sep 17 00:00:00 2001 From: Mariano Reingart Date: Wed, 28 Jun 2023 00:58:13 -0300 Subject: [PATCH 14/65] WSFEXv1: disable get parameters until UT updated MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nicolás Sandoval --- wsfexv1.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/wsfexv1.py b/wsfexv1.py index 8b5fa6085..76efd1f51 100644 --- a/wsfexv1.py +++ b/wsfexv1.py @@ -1094,10 +1094,11 @@ def main(): for r in ret: print("||%(codigo)s||%(ds)s||" % r) - print("=== Actividades ===") - ret = wsfexv1.GetParamActividades(sep=False) - for r in ret: - print("||%(codigo)s||%(ds)s||" % r) + if "--rg5259" in sys.argv: + print("=== Actividades ===") + ret = wsfexv1.GetParamActividades(sep=False) + for r in ret: + print("||%(codigo)s||%(ds)s||" % r) if "--ctz" in sys.argv: print(wsfexv1.GetParamCtz("DOL")) From 01c50efc8e74b60bd432e419207c1348305b54c7 Mon Sep 17 00:00:00 2001 From: Mariano Reingart Date: Wed, 28 Jun 2023 01:00:35 -0300 Subject: [PATCH 15/65] Remove Python 2.7 deprecated GitHub Action MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nicolás Sandoval --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 10de0b00a..e8aada329 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -16,7 +16,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [2.7, 3.9, 3.11] + python-version: [3.9, 3.11] steps: - uses: actions/checkout@v2 From c4498196fe7f2314768950d333ad3e297cca0b02 Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Tue, 4 Jul 2023 00:28:38 +0100 Subject: [PATCH 16/65] feat: added the signing of certificates and refactored the code Signed-off-by: HanslettTheDev --- wsaa.py | 76 ++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 45 insertions(+), 31 deletions(-) diff --git a/wsaa.py b/wsaa.py index 30bf49d95..894f93972 100644 --- a/wsaa.py +++ b/wsaa.py @@ -54,6 +54,7 @@ from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.bindings.openssl.binding import Binding + from cryptography.hazmat.primitives.serialization import pkcs7 except ImportError: ex = exception_info() @@ -113,11 +114,6 @@ def sign_tra(tra, cert=CERT, privatekey=PRIVATEKEY, passphrase=""): tra = tra.encode("utf8") if Binding: - _lib = Binding.lib - _ffi = Binding.ffi - # Crear un buffer desde el texto - bio_in = _lib.BIO_new_mem_buf(tra, len(tra)) - # Leer privatekey y cert if not privatekey.startswith(b"-----BEGIN RSA PRIVATE KEY-----"): privatekey = open(privatekey).read() @@ -136,42 +132,60 @@ def sign_tra(tra, cert=CERT, privatekey=PRIVATEKEY, passphrase=""): cert = open(cert).read() if isinstance(cert, str): cert = cert.encode("utf-8") - cert = x509.load_pem_x509_certificate(cert, default_backend()) + cert = x509.load_pem_x509_certificate(cert) - try: - # Firmar el texto (tra) usando cryptography (openssl bindings para python) - p7 = _lib.PKCS7_sign( - cert._x509, private_key._evp_pkey, _ffi.NULL, bio_in, 0 - ) - finally: - # Liberar memoria asignada - _lib.BIO_free(bio_in) - # Se crea un buffer nuevo porque la firma lo consume - bio_in = _lib.BIO_new_mem_buf(tra, len(tra)) - try: - # Crear buffer de salida - bio_out = _lib.BIO_new(_lib.BIO_s_mem()) - try: - # Instanciar un SMIME - _lib.SMIME_write_PKCS7(bio_out, p7, bio_in, 0) + if sys.version_info.major == 2: + _lib = Binding.lib + _ffi = Binding.ffi + # Crear un buffer desde el texto + # Se crea un buffer nuevo porque la firma lo consume + bio_in = _lib.BIO_new_mem_buf(tra, len(tra)) - # Tomar datos para la salida - result_buffer = _ffi.new("char**") - buffer_length = _lib.BIO_get_mem_data(bio_out, result_buffer) - output = _ffi.buffer(result_buffer[0], buffer_length)[:] + try: + # Firmar el texto (tra) usando cryptography (openssl bindings para python) + p7 = _lib.PKCS7_sign( + cert._x509, private_key._evp_pkey, _ffi.NULL, bio_in, 0 + ) finally: - _lib.BIO_free(bio_out) - finally: - _lib.BIO_free(bio_in) + # Liberar memoria asignada + _lib.BIO_free(bio_in) + # Se crea un buffer nuevo porque la firma lo consume + bio_in = _lib.BIO_new_mem_buf(tra, len(tra)) + try: + # Crear buffer de salida + bio_out = _lib.BIO_new(_lib.BIO_s_mem()) + try: + # Instanciar un SMIME + _lib.SMIME_write_PKCS7(bio_out, p7, bio_in, 0) + + # Tomar datos para la salida + result_buffer = _ffi.new("char**") + buffer_length = _lib.BIO_get_mem_data(bio_out, result_buffer) + p7 = _ffi.buffer(result_buffer[0], buffer_length)[:] + finally: + _lib.BIO_free(bio_out) + finally: + _lib.BIO_free(bio_in) + + else: + p7 = pkcs7.PKCS7SignatureBuilder().set_data( + tra + ).add_signer( + cert, private_key, hashes.SHA256() + ).sign( + serialization.Encoding.SMIME, [pkcs7.PKCS7Options.Binary] + ) # Generar p7 en formato mail y recortar headers - msg = email.message_from_string(output.decode("utf8")) + msg = email.message_from_string(p7.decode("utf8")) for part in msg.walk(): filename = part.get_filename() - if filename == "smime.p7m": + if filename and filename.startswith("smime.p7"): # Es la parte firmada? # Devolver CMS return part.get_payload(decode=False) + else: + raise RuntimeError("Part not found") else: # Firmar el texto (tra) usando OPENSSL directamente try: From d075fdafa18f252a00ebd1ca74ceda0fcf1d4a88 Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Tue, 4 Jul 2023 00:29:53 +0100 Subject: [PATCH 17/65] bump: upgraded cryptography from 3.3.2 -> 41.0.1 Signed-off-by: HanslettTheDev --- requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 59cb59a04..c65ebdda1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,13 @@ httplib2==0.9.2; python_version <= '2.7' httplib2==0.20.4; python_version > '3' pysimplesoap==1.08.14; python_version <= '2.7' -git+https://github.com/pysimplesoap/pysimplesoap.git@py311#pysimplesoap; python_version > '3' +git+https://github.com/pysimplesoap/pysimplesoap.git@py311#pysimplesoap ; python_version > '3' cryptography==3.3.2; python_version <= '2.7' -cryptography==3.4.7; python_version > '3' +cryptography==41.0.1; python_version > '3' fpdf>=1.7.2 dbf>=0.88.019 Pillow>=2.0.0 tabulate==0.8.5 certifi>=2020.4.5.1 qrcode==6.1 -future==0.18.3 +future==0.18.3 \ No newline at end of file From 57126c5d7ae1647fdb99d7bfdb071064c0bc1335 Mon Sep 17 00:00:00 2001 From: Mariano Reingart Date: Tue, 4 Jul 2023 22:50:17 -0300 Subject: [PATCH 18/65] Fix bad signature w/ latest cryptography version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AFIP government webservice error: ``` pysimplesoap.client.SoapFault: ns1:cms.sign.invalid: Firma inválida o algoritmo no soportado ``` Signed-off-by: Mariano Reingart --- wsaa.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wsaa.py b/wsaa.py index 4fc470420..69daff735 100644 --- a/wsaa.py +++ b/wsaa.py @@ -145,7 +145,7 @@ def sign_tra(tra, cert=CERT, privatekey=PRIVATEKEY, passphrase=""): ).add_signer( cert, private_key, hashes.SHA256() ).sign( - serialization.Encoding.SMIME, [pkcs7.PKCS7Options.DetachedSignature] + serialization.Encoding.SMIME, [pkcs7.PKCS7Options.Binary] ) # Generar p7 en formato mail y recortar headers From 752908fdf55eb8912bf57ae29d8a6dca5e1ecff5 Mon Sep 17 00:00:00 2001 From: Mariano Reingart Date: Tue, 4 Jul 2023 23:48:52 -0300 Subject: [PATCH 19/65] Fix whitespace issues due merge Signed-off-by: Mariano Reingart --- requirements.txt | 2 +- wsaa.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index c65ebdda1..3bb3036d1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ httplib2==0.9.2; python_version <= '2.7' httplib2==0.20.4; python_version > '3' pysimplesoap==1.08.14; python_version <= '2.7' -git+https://github.com/pysimplesoap/pysimplesoap.git@py311#pysimplesoap ; python_version > '3' +git+https://github.com/pysimplesoap/pysimplesoap.git@py311#pysimplesoap; python_version > '3' cryptography==3.3.2; python_version <= '2.7' cryptography==41.0.1; python_version > '3' fpdf>=1.7.2 diff --git a/wsaa.py b/wsaa.py index 5e65f1693..ca5f4c891 100644 --- a/wsaa.py +++ b/wsaa.py @@ -115,7 +115,7 @@ def sign_tra(tra, cert=CERT, privatekey=PRIVATEKEY, passphrase=""): if Binding: - # Leer privatekey y cert + # Leer privatekey y cert if not privatekey.startswith(b"-----BEGIN RSA PRIVATE KEY-----"): privatekey = open(privatekey).read() if isinstance(privatekey, str): From 4f1d7c7a88b09996d43a69fd9fb306c861fc4713 Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Tue, 11 Jul 2023 00:10:01 +0100 Subject: [PATCH 20/65] feat: splitted the sign_tra function into 3 seperate functions and called each one from the sin tra section Signed-off-by: HanslettTheDev --- wsaa.py | 156 ++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 113 insertions(+), 43 deletions(-) diff --git a/wsaa.py b/wsaa.py index ca5f4c891..9d4479b77 100644 --- a/wsaa.py +++ b/wsaa.py @@ -107,12 +107,53 @@ def create_tra(service=SERVICE, ttl=2400): return tra.as_xml() -def sign_tra(tra, cert=CERT, privatekey=PRIVATEKEY, passphrase=""): - "Firmar PKCS#7 el TRA y devolver CMS (recortando los headers SMIME)" +def sign_tra_new(tra, cert=CERT, privatekey=PRIVATEKEY, passphrase=""): + "Sign Digital transactions with cryptography versions >= 39 in python 3" + # Leer privatekey y cert + if not privatekey.startswith(b"-----BEGIN RSA PRIVATE KEY-----"): + privatekey = open(privatekey).read() + if isinstance(privatekey, str): + privatekey = privatekey.encode("utf-8") + + if not passphrase: + password = None + else: + password = passphrase + private_key = serialization.load_pem_private_key( + privatekey, password, default_backend() + ) - if isinstance(tra, str): - tra = tra.encode("utf8") + if not cert.startswith(b"-----BEGIN CERTIFICATE-----"): + cert = open(cert).read() + if isinstance(cert, str): + cert = cert.encode("utf-8") + cert = x509.load_pem_x509_certificate(cert) + + p7 = pkcs7.PKCS7SignatureBuilder().set_data( + tra + ).add_signer( + cert, private_key, hashes.SHA256() + ).sign( + serialization.Encoding.SMIME, [pkcs7.PKCS7Options.Binary] + ) + + # Generar p7 en formato mail y recortar headers + msg = email.message_from_string(p7.decode("utf8")) + for part in msg.walk(): + filename = part.get_filename() + if filename and filename.startswith("smime.p7"): + # Es la parte firmada? + # Devolver CMS + return part.get_payload(decode=False) + else: + raise RuntimeError("Part not found") + + +def sign_tra_old(tra, cert=CERT, privatekey=PRIVATEKEY, passphrase=""): + """Legacy method for signing python 2.7 digital transactions on python 2.7 + and python 3 versions that don't support cryptography version >39 + """ if Binding: # Leer privatekey y cert @@ -134,48 +175,38 @@ def sign_tra(tra, cert=CERT, privatekey=PRIVATEKEY, passphrase=""): if isinstance(cert, str): cert = cert.encode("utf-8") cert = x509.load_pem_x509_certificate(cert) + + _lib = Binding.lib + _ffi = Binding.ffi + # Crear un buffer desde el texto + # Se crea un buffer nuevo porque la firma lo consume + bio_in = _lib.BIO_new_mem_buf(tra, len(tra)) - if sys.version_info.major == 2: - _lib = Binding.lib - _ffi = Binding.ffi - # Crear un buffer desde el texto - # Se crea un buffer nuevo porque la firma lo consume - bio_in = _lib.BIO_new_mem_buf(tra, len(tra)) - - try: - # Firmar el texto (tra) usando cryptography (openssl bindings para python) - p7 = _lib.PKCS7_sign( - cert._x509, private_key._evp_pkey, _ffi.NULL, bio_in, 0 - ) - finally: - # Liberar memoria asignada - _lib.BIO_free(bio_in) - # Se crea un buffer nuevo porque la firma lo consume - bio_in = _lib.BIO_new_mem_buf(tra, len(tra)) + try: + # Firmar el texto (tra) usando cryptography (openssl bindings para python) + p7 = _lib.PKCS7_sign( + cert._x509, private_key._evp_pkey, _ffi.NULL, bio_in, 0 + ) + finally: + # Liberar memoria asignada + _lib.BIO_free(bio_in) + # Se crea un buffer nuevo porque la firma lo consume + bio_in = _lib.BIO_new_mem_buf(tra, len(tra)) + try: + # Crear buffer de salida + bio_out = _lib.BIO_new(_lib.BIO_s_mem()) try: - # Crear buffer de salida - bio_out = _lib.BIO_new(_lib.BIO_s_mem()) - try: - # Instanciar un SMIME - _lib.SMIME_write_PKCS7(bio_out, p7, bio_in, 0) - - # Tomar datos para la salida - result_buffer = _ffi.new("char**") - buffer_length = _lib.BIO_get_mem_data(bio_out, result_buffer) - p7 = _ffi.buffer(result_buffer[0], buffer_length)[:] - finally: - _lib.BIO_free(bio_out) - finally: - _lib.BIO_free(bio_in) + # Instanciar un SMIME + _lib.SMIME_write_PKCS7(bio_out, p7, bio_in, 0) - else: - p7 = pkcs7.PKCS7SignatureBuilder().set_data( - tra - ).add_signer( - cert, private_key, hashes.SHA256() - ).sign( - serialization.Encoding.SMIME, [pkcs7.PKCS7Options.Binary] - ) + # Tomar datos para la salida + result_buffer = _ffi.new("char**") + buffer_length = _lib.BIO_get_mem_data(bio_out, result_buffer) + p7 = _ffi.buffer(result_buffer[0], buffer_length)[:] + finally: + _lib.BIO_free(bio_out) + finally: + _lib.BIO_free(bio_in) # Generar p7 en formato mail y recortar headers msg = email.message_from_string(p7.decode("utf8")) @@ -188,6 +219,45 @@ def sign_tra(tra, cert=CERT, privatekey=PRIVATEKEY, passphrase=""): else: raise RuntimeError("Part not found") + +def sign_tra_openssl(tra, cert=CERT, privatekey=PRIVATEKEY, passphrase=""): + "Workaround using openssl binary directly via command-line interface to sign transactions" + try: + out = Popen( + [ + openssl_exe(), + "smime", + "-sign", + "-signer", + cert, + "-inkey", + privatekey, + "-outform", + "DER", + "-nodetach", + ], + stdin=PIPE, + stdout=PIPE, + stderr=PIPE, + ).communicate(tra)[0] + return b64encode(out) + except OSError as e: + if e.errno == 2: + warnings.warn("El ejecutable de OpenSSL no esta disponible en el PATH") + raise + + +def sign_tra(tra, cert=CERT, privatekey=PRIVATEKEY, passphrase=""): + "Firmar PKCS#7 el TRA y devolver CMS (recortando los headers SMIME)" + + if isinstance(tra, str): + tra = tra.encode("utf8") + + if sys.version_info.major == 2: + sign_tra_old(tra, cert, privatekey, passphrase) + + + else: # Firmar el texto (tra) usando OPENSSL directamente try: From 55918687bf7c2e68103b4e294a0b0c19c09f8051 Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Tue, 11 Jul 2023 00:17:12 +0100 Subject: [PATCH 21/65] feat: Added the sign_tra_openssl function Signed-off-by: HanslettTheDev --- wsaa.py | 36 ++++++------------------------------ 1 file changed, 6 insertions(+), 30 deletions(-) diff --git a/wsaa.py b/wsaa.py index 9d4479b77..8fe764203 100644 --- a/wsaa.py +++ b/wsaa.py @@ -253,37 +253,13 @@ def sign_tra(tra, cert=CERT, privatekey=PRIVATEKEY, passphrase=""): if isinstance(tra, str): tra = tra.encode("utf8") - if sys.version_info.major == 2: - sign_tra_old(tra, cert, privatekey, passphrase) - - - + if Binding: + if sys.version_info.major == 2: + sign_tra_old(tra, cert, privatekey, passphrase) + + sign_tra_new(tra, cert, privatekey, passphrase) else: - # Firmar el texto (tra) usando OPENSSL directamente - try: - out = Popen( - [ - openssl_exe(), - "smime", - "-sign", - "-signer", - cert, - "-inkey", - privatekey, - "-outform", - "DER", - "-nodetach", - ], - stdin=PIPE, - stdout=PIPE, - stderr=PIPE, - ).communicate(tra)[0] - return b64encode(out) - except OSError as e: - if e.errno == 2: - warnings.warn("El ejecutable de OpenSSL no esta disponible en el PATH") - raise - + sign_tra_openssl(tra, cert, privatekey, passphrase) def openssl_exe(): try: From 2f61fd9ef7e54c61c28b2c6058693f0f29320b4e Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Tue, 11 Jul 2023 15:41:41 +0100 Subject: [PATCH 22/65] fix: added the return statements Signed-off-by: HanslettTheDev --- wsaa.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/wsaa.py b/wsaa.py index 8fe764203..d4fffb8b3 100644 --- a/wsaa.py +++ b/wsaa.py @@ -255,11 +255,10 @@ def sign_tra(tra, cert=CERT, privatekey=PRIVATEKEY, passphrase=""): if Binding: if sys.version_info.major == 2: - sign_tra_old(tra, cert, privatekey, passphrase) - - sign_tra_new(tra, cert, privatekey, passphrase) + return sign_tra_old(tra, cert, privatekey, passphrase) + return sign_tra_new(tra, cert, privatekey, passphrase) else: - sign_tra_openssl(tra, cert, privatekey, passphrase) + return sign_tra_openssl(tra, cert, privatekey, passphrase) def openssl_exe(): try: From 29122cf9abf3fe5e723afe24c893d18934e37e61 Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Sat, 15 Jul 2023 01:36:52 +0100 Subject: [PATCH 23/65] refactored the code and used flake8 for linting Signed-off-by: HanslettTheDev --- tests/test_wsaa.py | 78 ++++++++++++++-------------- wsaa.py | 125 ++++++++++++++++++++++----------------------- 2 files changed, 102 insertions(+), 101 deletions(-) diff --git a/tests/test_wsaa.py b/tests/test_wsaa.py index 2d6a906f5..7abf4eddc 100644 --- a/tests/test_wsaa.py +++ b/tests/test_wsaa.py @@ -37,52 +37,53 @@ #fixture for key and certificate @pytest.fixture def key_and_cert(): - KEY='reingart.key' - CERT='reingart.crt' - return [KEY,CERT] + KEY = 'reingart.key' + CERT = 'reingart.crt' + return [KEY, CERT] def test_analizar_certificado(key_and_cert): """Test analizar datos en certificado.""" - wsaa=WSAA() + wsaa = WSAA() wsaa.AnalizarCertificado(key_and_cert[1]) assert wsaa.Identidad assert wsaa.Caducidad assert wsaa.Emisor + def test_crear_clave_privada(): """Test crear clave RSA.""" - wsaa=WSAA() + wsaa = WSAA() chk = wsaa.CrearClavePrivada() - assert chk==True + assert chk == True def test_crear_pedido_certificado(): """Crea CSM para solicitar certificado.""" - wsaa=WSAA() + wsaa = WSAA() chk1 = wsaa.CrearClavePrivada() chk2 = wsaa.CrearPedidoCertificado() - assert chk1==True - assert chk2==True + assert chk1 == True + assert chk2 == True def test_expirado(): """Revisar si el TA se encuentra vencido.""" - wsaa=WSAA() - #checking for expired certificate + wsaa = WSAA() + # checking for expired certificate script_dir = os.path.dirname(__file__) file_path = os.path.join(script_dir, 'xml/expired_ta.xml') - chk=wsaa.AnalizarXml(xml=open(file_path, "r").read()) - chk2=wsaa.Expirado() + chk = wsaa.AnalizarXml(xml=open(file_path, "r").read()) + chk2 = wsaa.Expirado() - #checking for a valid certificate,i.e. which will - #have expiration time 12 hrs(43200 secs) from generation - fec=str(date("c", date("U") + 43200)) - chk3=wsaa.Expirado(fecha=fec) + # checking for a valid certificate,i.e. which will + # have expiration time 12 hrs(43200 secs) from generation + fec = str(date("c", date("U") + 43200)) + chk3 = wsaa.Expirado(fecha=fec) - assert chk==True - assert chk2==True - assert chk3==False + assert chk == True + assert chk2 == True + assert chk3 == False @pytest.mark.vcr @@ -90,22 +91,21 @@ def test_login_cms(key_and_cert): """comprobando si LoginCMS está funcionando correctamente""" wsaa = WSAA() - tra=wsaa.CreateTRA(service="wsfe",ttl=DEFAULT_TTL) - cms=wsaa.SignTRA(tra,key_and_cert[1],key_and_cert[0]) - chk=wsaa.Conectar(cache=None, wsdl=WSDL,cacert=CACERT,proxy=None) + tra = wsaa.CreateTRA(service="wsfe", ttl=DEFAULT_TTL) + cms = wsaa.SignTRA(tra, key_and_cert[1], key_and_cert[0]) + chk = wsaa.Conectar(cache=None, wsdl=WSDL, cacert=CACERT, proxy=None) ta_xml = wsaa.LoginCMS(cms) ta = SimpleXMLElement(ta_xml) - if not isinstance(cms,str): + if not isinstance(cms, str): cms = cms.decode('utf-8') - assert isinstance(cms,str) - - + assert isinstance(cms, str) + assert cms.startswith('MII') - assert chk==True + assert chk == True assert ta_xml.startswith('') assert ta.credentials.token assert ta.credentials.sign @@ -156,7 +156,7 @@ def test_wsaa_sign_tra(key_and_cert): tra = wsaa.CreateTRA("wsfe") sign = wsaa.SignTRA(tra, key_and_cert[1], key_and_cert[0]) - if not isinstance(sign,str): + if not isinstance(sign, str): sign = sign.decode('utf-8') assert isinstance(sign, str) @@ -173,10 +173,10 @@ def test_wsaa_sign_tra_inline(key_and_cert): tra, open(key_and_cert[1]).read(), open(key_and_cert[0]).read() ) - if not isinstance(sign,str): + if not isinstance(sign, str): sign = sign.decode('utf-8') - if not isinstance(sign_2,str): + if not isinstance(sign_2, str): sign_2 = sign_2.decode('utf-8') assert isinstance(sign, str) @@ -185,6 +185,7 @@ def test_wsaa_sign_tra_inline(key_and_cert): assert isinstance(sign_2, str) assert sign_2.startswith("MII") + @pytest.mark.vcr def test_main(): sys.argv = [] @@ -202,22 +203,25 @@ def test_main_crear_pedido_cert(): sys.argv.append(" ") main() + @pytest.mark.vcr def test_main_analizar(): sys.argv = [] sys.argv.append("--analizar") main() + @pytest.mark.vcr def test_CallWSAA(key_and_cert): wsaa = WSAA() - tra=wsaa.CreateTRA(service="wsfe",ttl=DEFAULT_TTL) - cms=wsaa.SignTRA(tra,key_and_cert[1],key_and_cert[0]) - assert wsaa.CallWSAA(cms,WSDL) + tra = wsaa.CreateTRA(service="wsfe", ttl=DEFAULT_TTL) + cms = wsaa.SignTRA(tra, key_and_cert[1], key_and_cert[0]) + assert wsaa.CallWSAA(cms, WSDL) + @pytest.mark.vcr def test_call_wsaa(key_and_cert): wsaa = WSAA() - tra=wsaa.CreateTRA(service="wsfe",ttl=DEFAULT_TTL) - cms=wsaa.SignTRA(tra,key_and_cert[1],key_and_cert[0]) - assert call_wsaa(cms,WSDL) + tra = wsaa.CreateTRA(service="wsfe", ttl=DEFAULT_TTL) + cms = wsaa.SignTRA(tra, key_and_cert[1], key_and_cert[0]) + assert call_wsaa(cms, WSDL) diff --git a/wsaa.py b/wsaa.py index d4fffb8b3..2e12f39c9 100644 --- a/wsaa.py +++ b/wsaa.py @@ -47,7 +47,7 @@ ) try: - from cryptography import x509 + from cryptography import __version__ as cryptography_version, x509 from cryptography.x509.oid import NameOID from cryptography.hazmat.primitives import hashes from cryptography.hazmat.backends import default_backend @@ -149,75 +149,71 @@ def sign_tra_new(tra, cert=CERT, privatekey=PRIVATEKEY, passphrase=""): raise RuntimeError("Part not found") - def sign_tra_old(tra, cert=CERT, privatekey=PRIVATEKEY, passphrase=""): - """Legacy method for signing python 2.7 digital transactions on python 2.7 - and python 3 versions that don't support cryptography version >39 - """ - if Binding: + "Legacy method for signing python 2.7 digital transactions on python 2.7" - # Leer privatekey y cert - if not privatekey.startswith(b"-----BEGIN RSA PRIVATE KEY-----"): - privatekey = open(privatekey).read() - if isinstance(privatekey, str): - privatekey = privatekey.encode("utf-8") + # Leer privatekey y cert + if not privatekey.startswith(b"-----BEGIN RSA PRIVATE KEY-----"): + privatekey = open(privatekey).read() + if isinstance(privatekey, str): + privatekey = privatekey.encode("utf-8") - if not passphrase: - password = None - else: - password = passphrase - private_key = serialization.load_pem_private_key( - privatekey, password, default_backend() - ) + if not passphrase: + password = None + else: + password = passphrase + private_key = serialization.load_pem_private_key( + privatekey, password, default_backend() + ) - if not cert.startswith(b"-----BEGIN CERTIFICATE-----"): - cert = open(cert).read() - if isinstance(cert, str): - cert = cert.encode("utf-8") - cert = x509.load_pem_x509_certificate(cert) - - _lib = Binding.lib - _ffi = Binding.ffi - # Crear un buffer desde el texto - # Se crea un buffer nuevo porque la firma lo consume - bio_in = _lib.BIO_new_mem_buf(tra, len(tra)) + if not cert.startswith(b"-----BEGIN CERTIFICATE-----"): + cert = open(cert).read() + if isinstance(cert, str): + cert = cert.encode("utf-8") + cert = x509.load_pem_x509_certificate(cert) + + _lib = Binding.lib + _ffi = Binding.ffi + # Crear un buffer desde el texto + # Se crea un buffer nuevo porque la firma lo consume + bio_in = _lib.BIO_new_mem_buf(tra, len(tra)) + try: + # Firmar el texto (tra) usando cryptography (openssl bindings para python) + p7 = _lib.PKCS7_sign( + cert._x509, private_key._evp_pkey, _ffi.NULL, bio_in, 0 + ) + finally: + # Liberar memoria asignada + _lib.BIO_free(bio_in) + # Se crea un buffer nuevo porque la firma lo consume + bio_in = _lib.BIO_new_mem_buf(tra, len(tra)) + try: + # Crear buffer de salida + bio_out = _lib.BIO_new(_lib.BIO_s_mem()) try: - # Firmar el texto (tra) usando cryptography (openssl bindings para python) - p7 = _lib.PKCS7_sign( - cert._x509, private_key._evp_pkey, _ffi.NULL, bio_in, 0 - ) - finally: - # Liberar memoria asignada - _lib.BIO_free(bio_in) - # Se crea un buffer nuevo porque la firma lo consume - bio_in = _lib.BIO_new_mem_buf(tra, len(tra)) - try: - # Crear buffer de salida - bio_out = _lib.BIO_new(_lib.BIO_s_mem()) - try: - # Instanciar un SMIME - _lib.SMIME_write_PKCS7(bio_out, p7, bio_in, 0) - - # Tomar datos para la salida - result_buffer = _ffi.new("char**") - buffer_length = _lib.BIO_get_mem_data(bio_out, result_buffer) - p7 = _ffi.buffer(result_buffer[0], buffer_length)[:] - finally: - _lib.BIO_free(bio_out) + # Instanciar un SMIME + _lib.SMIME_write_PKCS7(bio_out, p7, bio_in, 0) + + # Tomar datos para la salida + result_buffer = _ffi.new("char**") + buffer_length = _lib.BIO_get_mem_data(bio_out, result_buffer) + p7 = _ffi.buffer(result_buffer[0], buffer_length)[:] finally: - _lib.BIO_free(bio_in) - - # Generar p7 en formato mail y recortar headers - msg = email.message_from_string(p7.decode("utf8")) - for part in msg.walk(): - filename = part.get_filename() - if filename and filename.startswith("smime.p7"): - # Es la parte firmada? - # Devolver CMS - return part.get_payload(decode=False) - else: - raise RuntimeError("Part not found") + _lib.BIO_free(bio_out) + finally: + _lib.BIO_free(bio_in) + + # Generar p7 en formato mail y recortar headers + msg = email.message_from_string(p7.decode("utf8")) + for part in msg.walk(): + filename = part.get_filename() + if filename and filename.startswith("smime.p7"): + # Es la parte firmada? + # Devolver CMS + return part.get_payload(decode=False) + else: + raise RuntimeError("Part not found") def sign_tra_openssl(tra, cert=CERT, privatekey=PRIVATEKEY, passphrase=""): @@ -254,12 +250,13 @@ def sign_tra(tra, cert=CERT, privatekey=PRIVATEKEY, passphrase=""): tra = tra.encode("utf8") if Binding: - if sys.version_info.major == 2: + if sys.version_info.major == 2 or int(cryptography_version[0:2]) < 39: return sign_tra_old(tra, cert, privatekey, passphrase) return sign_tra_new(tra, cert, privatekey, passphrase) else: return sign_tra_openssl(tra, cert, privatekey, passphrase) + def openssl_exe(): try: openssl = shutil.which("openssl") From 4c8d8f80e6bed0ba35144f4edc862c0e65115666 Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Sat, 15 Jul 2023 02:15:25 +0100 Subject: [PATCH 24/65] feat: use of a make command to carry out manual testing Signed-off-by: HanslettTheDev --- Makefile | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 88ddd6ec7..e7a2c4d22 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,22 @@ test: .venv/bin/py.test tests clean: - rm -Rf .venv + rm -Rf .venv -.PHONY: install test +# Works with bash and linux +load-tests: + cp conf/*.ini . + curl -o reingart.zip https://www.sistemasagiles.com.ar/soft/pyafipws/reingart.zip + python -m zipfile -e reingart.zip . + +sign-tra: + python -m pyafipws.wsaa + +sign-cert: + python -m pyafipws.wsfev1 --prueba + +# Use "git clean -n" to see the files to be cleaned +# Use only when only the config files are untracked +# Finally use "git clean -f" to remove untracked files(in this case test files) + +.PHONY: install test \ No newline at end of file From 9232e83f9801744b511e70592daf91a6fcbb85d0 Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Fri, 28 Jul 2023 03:45:21 +0100 Subject: [PATCH 25/65] feat: unit test case for the openssl signing Signed-off-by: HanslettTheDev --- tests/test_wsaa.py | 17 ++++++++++++++++- wsaa.py | 4 ++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/tests/test_wsaa.py b/tests/test_wsaa.py index 7abf4eddc..6fcafe6c2 100644 --- a/tests/test_wsaa.py +++ b/tests/test_wsaa.py @@ -20,7 +20,7 @@ import os import sys import base64 -from pyafipws.wsaa import WSAA, call_wsaa +from pyafipws.wsaa import WSAA, call_wsaa, sign_tra_openssl from pyafipws.wsaa import main from past.builtins import basestring from builtins import str @@ -162,6 +162,21 @@ def test_wsaa_sign_tra(key_and_cert): assert isinstance(sign, str) assert sign.startswith("MII") +def test_wsaa_sign_openssl(key_and_cert): + wsaa = WSAA() + + tra = wsaa.CreateTRA("wsfe").encode() + sign = sign_tra_openssl(tra, key_and_cert[1], key_and_cert[0]) + + # check if the commanmd line input is a byte data + assert isinstance(sign, bytes) + + if isinstance(sign, bytes): + sign = sign.decode("utf8") + + assert sign.startswith("MII") + + def test_wsaa_sign_tra_inline(key_and_cert): wsaa = WSAA() diff --git a/wsaa.py b/wsaa.py index 2e12f39c9..86e9b72f5 100644 --- a/wsaa.py +++ b/wsaa.py @@ -65,6 +65,10 @@ from subprocess import Popen, PIPE from base64 import b64encode +if "pytest" in sys.modules: + from subprocess import Popen, PIPE + from base64 import b64encode + # Constantes (si se usa el script de linea de comandos) WSDL = "https://wsaahomo.afip.gov.ar/ws/services/LoginCms?wsdl" # El WSDL correspondiente al WSAA CERT = "reingart.crt" # El certificado X.509 obtenido de Seg. Inf. From 3b2eb64dbe436a8e1a5c96edf53ef4d724a46b03 Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Fri, 28 Jul 2023 04:34:08 +0100 Subject: [PATCH 26/65] fix: modified make file command names for clarity Signed-off-by: HanslettTheDev --- Makefile | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index e7a2c4d22..36a751947 100644 --- a/Makefile +++ b/Makefile @@ -15,12 +15,16 @@ clean: rm -Rf .venv # Works with bash and linux -load-tests: +# This command first copies all the configuration settings from the conf folder +# to the main folder and next it downloads test key and digital certificate that +# that can be used for testing and lastly the python module is used to decompress +# the files +get-auth: cp conf/*.ini . curl -o reingart.zip https://www.sistemasagiles.com.ar/soft/pyafipws/reingart.zip python -m zipfile -e reingart.zip . -sign-tra: +sample-invoice: python -m pyafipws.wsaa sign-cert: @@ -29,5 +33,11 @@ sign-cert: # Use "git clean -n" to see the files to be cleaned # Use only when only the config files are untracked # Finally use "git clean -f" to remove untracked files(in this case test files) - -.PHONY: install test \ No newline at end of file +# This command will list all the files that are untracked. You can clean them verbosely +# using git clean -i. Else, if you are sure, you can se -f to remove all untracked files +# without a prompt +clean-test: + git clean -n + git clean -i + +.PHONY: install test get-auth sample-invoice sign-cert From 5fafb3874b0b84a42cc986f0d71771d4c9c63105 Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Wed, 9 Aug 2023 01:37:18 +0100 Subject: [PATCH 27/65] fix: changed to a split function to prevent future bug error if cryptography version > 100 Signed-off-by: HanslettTheDev --- wsaa.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wsaa.py b/wsaa.py index 86e9b72f5..f152474c1 100644 --- a/wsaa.py +++ b/wsaa.py @@ -254,7 +254,7 @@ def sign_tra(tra, cert=CERT, privatekey=PRIVATEKEY, passphrase=""): tra = tra.encode("utf8") if Binding: - if sys.version_info.major == 2 or int(cryptography_version[0:2]) < 39: + if sys.version_info.major == 2 or int(cryptography_version.split(".")[0]) < 39: return sign_tra_old(tra, cert, privatekey, passphrase) return sign_tra_new(tra, cert, privatekey, passphrase) else: From 961c4006789f0f79dd0e248138624d0b2d366b20 Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Sat, 12 Aug 2023 23:28:59 +0100 Subject: [PATCH 28/65] feat: removed the pytest code to resolve the conversation from the admin Signed-off-by: HanslettTheDev --- wsaa.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/wsaa.py b/wsaa.py index f152474c1..ea28558e9 100644 --- a/wsaa.py +++ b/wsaa.py @@ -55,6 +55,8 @@ from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.bindings.openssl.binding import Binding from cryptography.hazmat.primitives.serialization import pkcs7 + from subprocess import Popen, PIPE + from base64 import b64encode except ImportError: ex = exception_info() @@ -65,10 +67,6 @@ from subprocess import Popen, PIPE from base64 import b64encode -if "pytest" in sys.modules: - from subprocess import Popen, PIPE - from base64 import b64encode - # Constantes (si se usa el script de linea de comandos) WSDL = "https://wsaahomo.afip.gov.ar/ws/services/LoginCms?wsdl" # El WSDL correspondiente al WSAA CERT = "reingart.crt" # El certificado X.509 obtenido de Seg. Inf. From e022f8a3dcba84c5addfffe1aca0591bfff03219 Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Sat, 12 Aug 2023 23:30:58 +0100 Subject: [PATCH 29/65] fix: added the = operator for safer cryptography comparisons Signed-off-by: HanslettTheDev --- wsaa.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wsaa.py b/wsaa.py index ea28558e9..f8ff88671 100644 --- a/wsaa.py +++ b/wsaa.py @@ -252,7 +252,7 @@ def sign_tra(tra, cert=CERT, privatekey=PRIVATEKEY, passphrase=""): tra = tra.encode("utf8") if Binding: - if sys.version_info.major == 2 or int(cryptography_version.split(".")[0]) < 39: + if sys.version_info.major == 2 or int(cryptography_version.split(".")[0]) <= 39: return sign_tra_old(tra, cert, privatekey, passphrase) return sign_tra_new(tra, cert, privatekey, passphrase) else: From bfd17eaef19d34929b165cecb0aadb2c0254a161 Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Sun, 13 Aug 2023 10:36:25 +0100 Subject: [PATCH 30/65] feat: conditional in requirements.txt file to set max pillow version installation on 32bit systems Signed-off-by: HanslettTheDev --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3bb3036d1..0031f71d1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,8 @@ cryptography==3.3.2; python_version <= '2.7' cryptography==41.0.1; python_version > '3' fpdf>=1.7.2 dbf>=0.88.019 -Pillow>=2.0.0 +Pillow<=9.5.0; +Pillow>=2.0.0; platform_machine!='x86_64' tabulate==0.8.5 certifi>=2020.4.5.1 qrcode==6.1 From eefc3fcc9ed2fca516c5a60114eefd1a33774c4f Mon Sep 17 00:00:00 2001 From: Mariano Reingart Date: Sun, 13 Aug 2023 15:20:52 -0300 Subject: [PATCH 31/65] Fix pillow version for 64 bits --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 0031f71d1..5b3fbf8a4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ cryptography==3.3.2; python_version <= '2.7' cryptography==41.0.1; python_version > '3' fpdf>=1.7.2 dbf>=0.88.019 -Pillow<=9.5.0; +Pillow<=9.5.0; platform_machine!='x86_64' Pillow>=2.0.0; platform_machine!='x86_64' tabulate==0.8.5 certifi>=2020.4.5.1 From 0d4a3db866cf2e672aa0bf71f393195ecb5f36b0 Mon Sep 17 00:00:00 2001 From: Mariano Reingart Date: Sun, 13 Aug 2023 15:21:37 -0300 Subject: [PATCH 32/65] Fix pillow version for 64 bits --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5b3fbf8a4..7e3c56cc7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ cryptography==41.0.1; python_version > '3' fpdf>=1.7.2 dbf>=0.88.019 Pillow<=9.5.0; platform_machine!='x86_64' -Pillow>=2.0.0; platform_machine!='x86_64' +Pillow>=2.0.0; platform_machine=='x86_64' tabulate==0.8.5 certifi>=2020.4.5.1 qrcode==6.1 From ba62c85d72aa9dd5344303ede2b4b0578b599006 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Bernardi?= Date: Fri, 18 Aug 2023 18:38:23 -0300 Subject: [PATCH 33/65] IIBB: fix old Python2 code --- iibb.py | 23 +++++++++++------------ utils.py | 4 +--- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/iibb.py b/iibb.py index 3c6ea8aaa..7e910ba76 100644 --- a/iibb.py +++ b/iibb.py @@ -22,7 +22,8 @@ __license__ = "LGPL-3.0-or-later" __version__ = "3.01b" -import md5, os, sys, tempfile, traceback +import os, sys, tempfile, traceback +from hashlib import md5 from pysimplesoap.simplexml import SimpleXMLElement from pyafipws.utils import WebClient @@ -122,21 +123,20 @@ def ConsultarContribuyentes(self, fecha_desde, fecha_hasta, cuit_contribuyente): self.xml.contribuyentes.contribuyente.cuitContribuyente = cuit_contribuyente xml = self.xml.as_xml() - self.CodigoHash = md5.md5(xml).hexdigest() + self.CodigoHash = md5(xml.encode('utf-8')).hexdigest() nombre = "DFEServicioConsulta_%s.xml" % self.CodigoHash # guardo el xml en el archivo a enviar y luego lo re-abro: - archivo = open(os.path.join(tempfile.gettempdir(), nombre), "w") - archivo.write(xml) - archivo.close() - archivo = open(os.path.join(tempfile.gettempdir(), nombre), "r") + with open(os.path.join(tempfile.gettempdir(), nombre), "w") as archivo: + archivo.write(xml) if not self.testing: - response = self.client( - user=self.Usuario, password=self.Password, file=archivo - ) + with open(os.path.join(tempfile.gettempdir(), nombre), "r") as archivo: + response = self.client( + user=self.Usuario, password=self.Password, file=archivo) else: - response = open(self.testing).read() + with open(self.testing).read() as archivo: + response = archivo self.XmlResponse = response self.xml = SimpleXMLElement(response) if "tipoError" in self.xml: @@ -144,7 +144,6 @@ def ConsultarContribuyentes(self, fecha_desde, fecha_hasta, cuit_contribuyente): self.CodigoError = str(self.xml.codigoError) self.MensajeError = ( str(self.xml.mensajeError) - .decode("latin1") .encode("ascii", "replace") ) if "numeroComprobante" in self.xml: @@ -327,4 +326,4 @@ def main(): ) if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/utils.py b/utils.py index d9f6641b0..1e653dbeb 100644 --- a/utils.py +++ b/utils.py @@ -548,7 +548,7 @@ def multipart_encode(self, vars): boundary = choose_boundary() buf = StringIO() for key, value in list(vars.items()): - if not isinstance(value, file): + if isinstance(value, str): buf.write("--%s\r\n" % boundary) buf.write('Content-Disposition: form-data; name="%s"' % key) buf.write("\r\n\r\n" + value + "\r\n") @@ -576,8 +576,6 @@ def __call__(self, *args, **vars): "Perform a GET/POST request and return the response" location = self.location - if isinstance(location, str): - location = location.encode("utf8") # extend the base URI with additional components if args: location += "/".join(args) From 3ff2800b9d6dbe5883eb82c320b9e94efd8b0182 Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Tue, 22 Aug 2023 19:42:14 +0100 Subject: [PATCH 34/65] fixed requested changes Signed-off-by: HanslettTheDev --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 36a751947..a00ba89d5 100644 --- a/Makefile +++ b/Makefile @@ -24,10 +24,10 @@ get-auth: curl -o reingart.zip https://www.sistemasagiles.com.ar/soft/pyafipws/reingart.zip python -m zipfile -e reingart.zip . -sample-invoice: +access-ticket: python -m pyafipws.wsaa -sign-cert: +sample-invoice: python -m pyafipws.wsfev1 --prueba # Use "git clean -n" to see the files to be cleaned From 3364f92c5c5400f9efb068f9ffdc08e774787915 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Bernardi?= Date: Wed, 23 Aug 2023 09:27:29 -0300 Subject: [PATCH 35/65] IIBB: fix old Python2 code (2) --- iibb.py | 9 ++++++--- utils.py | 13 ++++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/iibb.py b/iibb.py index 7e910ba76..035cf8f27 100644 --- a/iibb.py +++ b/iibb.py @@ -23,7 +23,10 @@ __version__ = "3.01b" import os, sys, tempfile, traceback -from hashlib import md5 +try: + from hashlib import md5 +except ImportError: + from md5 import md5 from pysimplesoap.simplexml import SimpleXMLElement from pyafipws.utils import WebClient @@ -135,8 +138,8 @@ def ConsultarContribuyentes(self, fecha_desde, fecha_hasta, cuit_contribuyente): response = self.client( user=self.Usuario, password=self.Password, file=archivo) else: - with open(self.testing).read() as archivo: - response = archivo + with open(self.testing) as archivo: + response = archivo.read() self.XmlResponse = response self.xml = SimpleXMLElement(response) if "tipoError" in self.xml: diff --git a/utils.py b/utils.py index 1e653dbeb..a5503a97d 100644 --- a/utils.py +++ b/utils.py @@ -39,7 +39,7 @@ import time import traceback import warnings -from io import StringIO +from io import StringIO, BytesIO from decimal import Decimal from urllib.parse import urlencode from urllib.parse import urlparse @@ -546,9 +546,16 @@ def __init__( def multipart_encode(self, vars): "Enconde form data (vars dict)" boundary = choose_boundary() - buf = StringIO() + if sys.version_info[0] < 3: + buf = BytesIO() + def _is_string(val): + return (not isinstance(val, file)) + else: + buf = StringIO() + def _is_string(val): + return isinstance(val, str) for key, value in list(vars.items()): - if isinstance(value, str): + if _is_string(value): buf.write("--%s\r\n" % boundary) buf.write('Content-Disposition: form-data; name="%s"' % key) buf.write("\r\n\r\n" + value + "\r\n") From 318cbbd920ee784c5992c2bc82162b566978d1e0 Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Sat, 26 Aug 2023 13:33:06 +0100 Subject: [PATCH 36/65] removed the get_dep.py file Signed-off-by: HanslettTheDev --- get_dep.py | 24 ------------------------ setup.py | 3 +-- 2 files changed, 1 insertion(+), 26 deletions(-) delete mode 100644 get_dep.py diff --git a/get_dep.py b/get_dep.py deleted file mode 100644 index ca40af5e0..000000000 --- a/get_dep.py +++ /dev/null @@ -1,24 +0,0 @@ -import os - - -def get_dependecies(): - blob = "git+https://github.com" - requirements_path = os.path.join( - os.path.abspath(os.getcwd()), - "requirements.txt" - ) - if os.path.isfile(requirements_path): - with open(requirements_path) as f: - dependencies = [ - ">=".join(x.split("==")) for x in f.read().splitlines() - ] - for x in dependencies: - if x.startswith(blob): - # split the text and join them with the @ command - # index 3 holds the name of the module - chunks = x.split("/") - dependencies[dependencies.index(x)] = x.replace( - blob, chunks[3] + " @ " + blob - ) - break - return dependencies \ No newline at end of file diff --git a/setup.py b/setup.py index cd83642cd..385652a14 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,6 @@ __copyright__ = "Copyright (C) 2008-2021 Mariano Reingart" from distutils.core import setup -from get_dep import get_dependecies import glob import os import subprocess @@ -71,7 +70,7 @@ author_email="reingart@gmail.com", url="https://github.com/reingart/pyafipws", license="LGPL-3.0-or-later", - install_requires=get_dependecies(), + install_requires=open('requirements.txt').readlines(), options=opts, data_files=data_files, classifiers=[ From f03cfb8bb6682841768d0d659238d61568bc86e3 Mon Sep 17 00:00:00 2001 From: Mariano Reingart Date: Sun, 27 Aug 2023 12:32:19 -0300 Subject: [PATCH 37/65] IIBB: Fix hash md5 unicode encoding error ``` Traceback: Traceback (most recent call last): File "/src/abernardi/iibb.py", line 129, in ConsultarContribuyentes self.CodigoHash = md5(xml.decode('utf-8')).hexdigest() TypeError: Strings must be encoded before hashing ``` . --- iibb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iibb.py b/iibb.py index 035cf8f27..0ecbcef07 100644 --- a/iibb.py +++ b/iibb.py @@ -126,7 +126,7 @@ def ConsultarContribuyentes(self, fecha_desde, fecha_hasta, cuit_contribuyente): self.xml.contribuyentes.contribuyente.cuitContribuyente = cuit_contribuyente xml = self.xml.as_xml() - self.CodigoHash = md5(xml.encode('utf-8')).hexdigest() + self.CodigoHash = md5(xml).hexdigest() nombre = "DFEServicioConsulta_%s.xml" % self.CodigoHash # guardo el xml en el archivo a enviar y luego lo re-abro: From 22a6a5228c5320e3a36062d88c67b258492c2d30 Mon Sep 17 00:00:00 2001 From: Mariano Reingart Date: Sun, 27 Aug 2023 12:34:02 -0300 Subject: [PATCH 38/65] IIBB: fix temp file unicode error (binary open) ``` Traceback: Traceback (most recent call last): File "/src/abernardi/iibb.py", line 134, in ConsultarContribuyentes archivo.write(xml) TypeError: write() argument must be str, not bytes ``` --- iibb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iibb.py b/iibb.py index 0ecbcef07..0639cff6e 100644 --- a/iibb.py +++ b/iibb.py @@ -130,7 +130,7 @@ def ConsultarContribuyentes(self, fecha_desde, fecha_hasta, cuit_contribuyente): nombre = "DFEServicioConsulta_%s.xml" % self.CodigoHash # guardo el xml en el archivo a enviar y luego lo re-abro: - with open(os.path.join(tempfile.gettempdir(), nombre), "w") as archivo: + with open(os.path.join(tempfile.gettempdir(), nombre), "wb") as archivo: archivo.write(xml) if not self.testing: From 5287a6828b7cfd1645a964d4f1841fcac67326c6 Mon Sep 17 00:00:00 2001 From: Mariano Reingart Date: Sun, 27 Aug 2023 12:36:05 -0300 Subject: [PATCH 39/65] IIBB: fix error message unicode encoding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Note b prefix in the error message and wrong characters (?): ``` Numero Comprobante: Codigo HASH: f1800fba55a255c8a6317b324bf5efc7 Error General: DATO | 2 | b'' ``` Now: ``` Numero Comprobante: Codigo HASH: f1800fba55a255c8a6317b324bf5efc7 Error General: DATO | 2 | ``` --- iibb.py | 1 - 1 file changed, 1 deletion(-) diff --git a/iibb.py b/iibb.py index 0639cff6e..b97193450 100644 --- a/iibb.py +++ b/iibb.py @@ -147,7 +147,6 @@ def ConsultarContribuyentes(self, fecha_desde, fecha_hasta, cuit_contribuyente): self.CodigoError = str(self.xml.codigoError) self.MensajeError = ( str(self.xml.mensajeError) - .encode("ascii", "replace") ) if "numeroComprobante" in self.xml: self.NumeroComprobante = str(self.xml.numeroComprobante) From 99f81572ab18fa6f33ccee2fab19e98f6e466655 Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Sun, 13 Aug 2023 10:18:23 +0100 Subject: [PATCH 40/65] feat: installation of nsis and building of installer Signed-off-by: HanslettTheDev --- .github/workflows/windows-installer.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/windows-installer.yml b/.github/workflows/windows-installer.yml index dffb9fc7a..5f28d1f15 100644 --- a/.github/workflows/windows-installer.yml +++ b/.github/workflows/windows-installer.yml @@ -44,6 +44,14 @@ jobs: - name: Build executables run: | python setup_win.py py2exe + - name: Install NSIS for building Installers + run: | + curl -L https://sourceforge.net/projects/nsis/files/latest/download -o NSISInstaller.exe + Start-Process -FilePath "NSISInstaller.exe" -ArgumentList "/S" -Wait + del "NSISInstaller.exe" + - name: Build PyAfipWs Installer + run: | + makensis.exe base.nsi - name: Remove uneeded libs (TK) run: | Remove-Item .\dist\lib\tcl -recurse From 76bb99befa7772952be27f9f64f777ebd975b5fb Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Sun, 13 Aug 2023 10:21:22 +0100 Subject: [PATCH 41/65] feat: deploy the installer as a release artifact Signed-off-by: HanslettTheDev --- .github/workflows/windows-installer.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/windows-installer.yml b/.github/workflows/windows-installer.yml index 5f28d1f15..75071372c 100644 --- a/.github/workflows/windows-installer.yml +++ b/.github/workflows/windows-installer.yml @@ -73,6 +73,12 @@ jobs: name: dist-${{ matrix.targetplatform }} path: | dist/ + - name: Deploy PyAfipWs Installer + uses: actions/upload-artifact@v3 + with: + name: PyAfipWs-Installer-${{ matrix.targetplatform }} + path: | + **/PyAfipWs-*-full.exe test: name: "Full End-2-End test" From 1dfe61304ed70891aebdfc7f4998d3883519a06d Mon Sep 17 00:00:00 2001 From: Mariano Reingart Date: Sun, 27 Aug 2023 13:17:50 -0300 Subject: [PATCH 42/65] feat: download proper version of VC redist https://wiki.python.org/moin/WindowsCompilers https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170#visual-studio-2015-2017-2019-and-2022 --- .github/workflows/windows-installer.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/windows-installer.yml b/.github/workflows/windows-installer.yml index 75071372c..625349a37 100644 --- a/.github/workflows/windows-installer.yml +++ b/.github/workflows/windows-installer.yml @@ -60,6 +60,17 @@ jobs: run: | mkdir .\dist\tests copy .\tests\powershell\*.* .\dist\tests + - name: Download Visual Studio Redistributable (32bits) + if: matrix.targetplatform == 'x86' + run: | + curl -L https://aka.ms/vs/17/release/vc_redist.x86.exe -o vcredist.exe + - name: Download Visual Studio 22 Redistributable (64bits) + if: matrix.targetplatform != 'x86' + run: | + curl -L https://aka.ms/vs/17/release/vc_redist.x64.exe -o vcredist.exe + - name: Copy Visual Studio Redistributable + run: | + copy vcredist.exe .\dist\ - name: Save repository metadata for release env-vars run: | echo release_version="${{ matrix.python-version }}".$(git rev-list --count --all) > dist/.env From 90538682af5658ef5d21cd15bb78195e2f7cdb1b Mon Sep 17 00:00:00 2001 From: Mariano Reingart Date: Sun, 27 Aug 2023 13:20:16 -0300 Subject: [PATCH 43/65] feat: removed vcredist suffix for 32/64 bits --- nsis.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nsis.py b/nsis.py index 971ab3a4b..b4014b4c4 100644 --- a/nsis.py +++ b/nsis.py @@ -172,10 +172,10 @@ StrCmp $0 "Microsoft Visual C++ 2008 Redistributable - x86 9.0.21022" vcredist_ok vcredist_install vcredist_install: - File "vcredist_x86.exe" + File "vcredist.exe" DetailPrint "Installing Microsoft Visual C++ 2008 Redistributable" - ExecWait '"$INSTDIR\vcredist_x86.exe" /q' $0 - Delete $INSTDIR\vcredist_x86.exe + ExecWait '"$INSTDIR\vcredist.exe" /q' $0 + Delete $INSTDIR\vcredist.exe vcredist_ok: """ From ff4691fd163df63bbd336f90fc452f9b7a4c5e5d Mon Sep 17 00:00:00 2001 From: Mariano Reingart Date: Sun, 27 Aug 2023 13:34:58 -0300 Subject: [PATCH 44/65] fix download of vcredist (before nsis installer) --- .github/workflows/windows-installer.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/windows-installer.yml b/.github/workflows/windows-installer.yml index 625349a37..ad60b647b 100644 --- a/.github/workflows/windows-installer.yml +++ b/.github/workflows/windows-installer.yml @@ -44,6 +44,17 @@ jobs: - name: Build executables run: | python setup_win.py py2exe + - name: Download Visual Studio Redistributable (32bits) + if: matrix.targetplatform == 'x86' + run: | + curl -L https://aka.ms/vs/17/release/vc_redist.x86.exe -o vcredist.exe + - name: Download Visual Studio 22 Redistributable (64bits) + if: matrix.targetplatform != 'x86' + run: | + curl -L https://aka.ms/vs/17/release/vc_redist.x64.exe -o vcredist.exe + - name: Copy Visual Studio Redistributable + run: | + copy vcredist.exe .\dist\ - name: Install NSIS for building Installers run: | curl -L https://sourceforge.net/projects/nsis/files/latest/download -o NSISInstaller.exe @@ -60,17 +71,6 @@ jobs: run: | mkdir .\dist\tests copy .\tests\powershell\*.* .\dist\tests - - name: Download Visual Studio Redistributable (32bits) - if: matrix.targetplatform == 'x86' - run: | - curl -L https://aka.ms/vs/17/release/vc_redist.x86.exe -o vcredist.exe - - name: Download Visual Studio 22 Redistributable (64bits) - if: matrix.targetplatform != 'x86' - run: | - curl -L https://aka.ms/vs/17/release/vc_redist.x64.exe -o vcredist.exe - - name: Copy Visual Studio Redistributable - run: | - copy vcredist.exe .\dist\ - name: Save repository metadata for release env-vars run: | echo release_version="${{ matrix.python-version }}".$(git rev-list --count --all) > dist/.env From a7f9b233ecedc1f6741f4cbb6b1b9129821c2cd0 Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Mon, 28 Aug 2023 17:46:33 +0100 Subject: [PATCH 45/65] feat: added functionality to the GH actions to deploy pyafipws installers to the release section Signed-off-by: HanslettTheDev --- .github/workflows/windows-installer.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/windows-installer.yml b/.github/workflows/windows-installer.yml index ad60b647b..a05b24925 100644 --- a/.github/workflows/windows-installer.yml +++ b/.github/workflows/windows-installer.yml @@ -161,6 +161,16 @@ jobs: runs-on: "ubuntu-latest" steps: + - name: Download 64Bit Installer + uses: actions/download-artifact@v3 + with: + name: PyAfipWs-Installer-x64 + path: PyAfipWs-Installer-x64.exe + - name: Download 32bit Installer + uses: actions/download-artifact@v3 + with: + name: PyAfipWs-Installer-x86 + path: PyAfipWs-Installer-x86.exe - name: Download distribution binaries uses: actions/download-artifact@v3 with: @@ -188,5 +198,7 @@ jobs: prerelease: ${{ (github.ref != 'main') }} title: "Dev Build ${{ env.release_version }} ${{ env.git_branch }} @ ${{ env.git_short_hash }}" files: | + PyAfipWs-Installer-x64.exe + PyAfipWs-Installer-x86.exe dist-32.zip dist-64.zip \ No newline at end of file From 7d9b9a944d7913182d478ed8292dc15886eda5ab Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Wed, 6 Sep 2023 13:42:25 +0100 Subject: [PATCH 46/65] feat: added pip-tools usage and using requirements.in file instead Signed-off-by: HanslettTheDev --- requirements.in | 13 ++++++++++++ requirements.txt | 53 ++++++++++++++++++++++++++++++++++++------------ setup.py | 18 +++++++++++++++- 3 files changed, 70 insertions(+), 14 deletions(-) create mode 100644 requirements.in diff --git a/requirements.in b/requirements.in new file mode 100644 index 000000000..caa9dc0a1 --- /dev/null +++ b/requirements.in @@ -0,0 +1,13 @@ +httplib2==0.9.2; python_version <= '2.7' +httplib2>=0.20.4; python_version > '3' +pysimplesoap==1.08.14; python_version <= '2.7' +git+https://github.com/pysimplesoap/pysimplesoap.git@py311#pysimplesoap +cryptography==3.3.2; python_version <= '2.7' +cryptography>=3.4.7; python_version > '3' +fpdf>=1.7.2 +dbf>=0.88.019 +Pillow>=2.0.0 +tabulate>=0.8.5 +certifi>=2020.4.5.1 +qrcode>=6.1 +future>=0.18.2 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 77932b737..7665f563f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,40 @@ -httplib2==0.9.2; python_version <= '2.7' -httplib2==0.20.4; python_version > '3' -pysimplesoap==1.08.14; python_version <= '2.7' -git+https://github.com/pysimplesoap/pysimplesoap.git@py311#pysimplesoap -cryptography==3.3.2; python_version <= '2.7' -cryptography==3.4.7; python_version > '3' -fpdf>=1.7.2 -dbf>=0.88.019 -Pillow>=2.0.0 -tabulate==0.8.5 -certifi>=2020.4.5.1 -qrcode==6.1 -future==0.18.2 +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile --strip-extras requirements.in +# +aenum==3.1.15 + # via dbf +certifi==2023.7.22 + # via -r requirements.in +cffi==1.15.1 + # via cryptography +colorama==0.4.6 + # via qrcode +cryptography==41.0.3 ; python_version > "3" + # via -r requirements.in +dbf==0.99.3 + # via -r requirements.in +fpdf==1.7.2 + # via -r requirements.in +future==0.18.3 + # via -r requirements.in +httplib2==0.22.0 ; python_version > "3" + # via -r requirements.in +pillow==10.0.0 + # via -r requirements.in +pycparser==2.21 + # via cffi +pyparsing==3.1.1 + # via httplib2 +pypng==0.20220715.0 + # via qrcode +pysimplesoap @ git+https://github.com/pysimplesoap/pysimplesoap.git@py311 + # via -r requirements.in +qrcode==7.4.2 + # via -r requirements.in +tabulate==0.9.0 + # via -r requirements.in +typing-extensions==4.7.1 + # via qrcode diff --git a/setup.py b/setup.py index 385652a14..ec41e40a9 100644 --- a/setup.py +++ b/setup.py @@ -70,7 +70,23 @@ author_email="reingart@gmail.com", url="https://github.com/reingart/pyafipws", license="LGPL-3.0-or-later", - install_requires=open('requirements.txt').readlines(), + install_requires=[ + "httplib2==0.9.2; python_version <= '2.7'" + "httplib2>=0.20.4; python_version > '3'" + "pysimplesoap==1.08.14; python_version <= '2.7'" + "pysimplesoap @ git+https://github.com/pysimplesoap/pysimplesoap.git@py311#pysimplesoap", + "cryptography==3.3.2; python_version <= '2.7'" + "cryptography>=3.4.7; python_version > '3'" + "fpdf>=1.7.2" + "dbf>=0.88.019" + "Pillow>=2.0.0" + "tabulate>=0.8.5" + "certifi>=2020.4.5.1" + "qrcode>=6.1" + "future>=0.18.2" + "pywin32==304; sys_platform == 'win32' and python_version > '3'", + "py2exe==0.11.1.1; sys_platform == 'win32' and python_version > '3'" + ], options=opts, data_files=data_files, classifiers=[ From a8a05a1537b0e6b9ec6934f4bc8a64040dddf54c Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Wed, 6 Sep 2023 13:45:18 +0100 Subject: [PATCH 47/65] fix: removed the requirements.txt file to prevent merge conflicts Signed-off-by: HanslettTheDev --- requirements.txt | 40 ---------------------------------------- 1 file changed, 40 deletions(-) delete mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 7665f563f..000000000 --- a/requirements.txt +++ /dev/null @@ -1,40 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile --strip-extras requirements.in -# -aenum==3.1.15 - # via dbf -certifi==2023.7.22 - # via -r requirements.in -cffi==1.15.1 - # via cryptography -colorama==0.4.6 - # via qrcode -cryptography==41.0.3 ; python_version > "3" - # via -r requirements.in -dbf==0.99.3 - # via -r requirements.in -fpdf==1.7.2 - # via -r requirements.in -future==0.18.3 - # via -r requirements.in -httplib2==0.22.0 ; python_version > "3" - # via -r requirements.in -pillow==10.0.0 - # via -r requirements.in -pycparser==2.21 - # via cffi -pyparsing==3.1.1 - # via httplib2 -pypng==0.20220715.0 - # via qrcode -pysimplesoap @ git+https://github.com/pysimplesoap/pysimplesoap.git@py311 - # via -r requirements.in -qrcode==7.4.2 - # via -r requirements.in -tabulate==0.9.0 - # via -r requirements.in -typing-extensions==4.7.1 - # via qrcode From ced4d9a8c6cfd1937a52afd7cd3d7dd350365c71 Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Wed, 6 Sep 2023 13:46:54 +0100 Subject: [PATCH 48/65] fix: fixed requirements.txt file to prevent merge conflicts Signed-off-by: HanslettTheDev --- requirements.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..caa9dc0a1 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,13 @@ +httplib2==0.9.2; python_version <= '2.7' +httplib2>=0.20.4; python_version > '3' +pysimplesoap==1.08.14; python_version <= '2.7' +git+https://github.com/pysimplesoap/pysimplesoap.git@py311#pysimplesoap +cryptography==3.3.2; python_version <= '2.7' +cryptography>=3.4.7; python_version > '3' +fpdf>=1.7.2 +dbf>=0.88.019 +Pillow>=2.0.0 +tabulate>=0.8.5 +certifi>=2020.4.5.1 +qrcode>=6.1 +future>=0.18.2 \ No newline at end of file From eea2b85de56c4b405cdb1cc08eac95787a7968f9 Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Wed, 6 Sep 2023 13:48:41 +0100 Subject: [PATCH 49/65] fix: fixed requirements.txt file to prevent merge conflicts Signed-off-by: HanslettTheDev --- requirements.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/requirements.txt b/requirements.txt index caa9dc0a1..fae2ff1c8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,12 +2,12 @@ httplib2==0.9.2; python_version <= '2.7' httplib2>=0.20.4; python_version > '3' pysimplesoap==1.08.14; python_version <= '2.7' git+https://github.com/pysimplesoap/pysimplesoap.git@py311#pysimplesoap -cryptography==3.3.2; python_version <= '2.7' -cryptography>=3.4.7; python_version > '3' +cryptography==41.0.1; python_version > '3' fpdf>=1.7.2 dbf>=0.88.019 -Pillow>=2.0.0 -tabulate>=0.8.5 +Pillow<=9.5.0; platform_machine!='x86_64' +Pillow>=2.0.0; platform_machine=='x86_64' +tabulate==0.8.5 certifi>=2020.4.5.1 -qrcode>=6.1 -future>=0.18.2 \ No newline at end of file +qrcode==6.1 +future==0.18.3 \ No newline at end of file From 8f9889658ac1653b119ddb52d7f564f2c2046210 Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Wed, 6 Sep 2023 13:52:01 +0100 Subject: [PATCH 50/65] fix: fixed requirements.txt file to prevent merge conflicts Signed-off-by: HanslettTheDev --- requirements.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index fae2ff1c8..7e3c56cc7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,8 @@ httplib2==0.9.2; python_version <= '2.7' -httplib2>=0.20.4; python_version > '3' +httplib2==0.20.4; python_version > '3' pysimplesoap==1.08.14; python_version <= '2.7' -git+https://github.com/pysimplesoap/pysimplesoap.git@py311#pysimplesoap +git+https://github.com/pysimplesoap/pysimplesoap.git@py311#pysimplesoap; python_version > '3' +cryptography==3.3.2; python_version <= '2.7' cryptography==41.0.1; python_version > '3' fpdf>=1.7.2 dbf>=0.88.019 From 6d4f7999e064bc81cce2a755a6e1c22425ed8bb6 Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Sat, 9 Sep 2023 00:35:23 +0100 Subject: [PATCH 51/65] feat: removed requirements.in file and modified the setup.py file with the requirements Signed-off-by: HanslettTheDev --- requirements.in | 13 ------------- setup.py | 30 +++++++++++++++--------------- 2 files changed, 15 insertions(+), 28 deletions(-) delete mode 100644 requirements.in diff --git a/requirements.in b/requirements.in deleted file mode 100644 index caa9dc0a1..000000000 --- a/requirements.in +++ /dev/null @@ -1,13 +0,0 @@ -httplib2==0.9.2; python_version <= '2.7' -httplib2>=0.20.4; python_version > '3' -pysimplesoap==1.08.14; python_version <= '2.7' -git+https://github.com/pysimplesoap/pysimplesoap.git@py311#pysimplesoap -cryptography==3.3.2; python_version <= '2.7' -cryptography>=3.4.7; python_version > '3' -fpdf>=1.7.2 -dbf>=0.88.019 -Pillow>=2.0.0 -tabulate>=0.8.5 -certifi>=2020.4.5.1 -qrcode>=6.1 -future>=0.18.2 \ No newline at end of file diff --git a/setup.py b/setup.py index ec41e40a9..f0dfbad55 100644 --- a/setup.py +++ b/setup.py @@ -71,21 +71,21 @@ url="https://github.com/reingart/pyafipws", license="LGPL-3.0-or-later", install_requires=[ - "httplib2==0.9.2; python_version <= '2.7'" - "httplib2>=0.20.4; python_version > '3'" - "pysimplesoap==1.08.14; python_version <= '2.7'" - "pysimplesoap @ git+https://github.com/pysimplesoap/pysimplesoap.git@py311#pysimplesoap", - "cryptography==3.3.2; python_version <= '2.7'" - "cryptography>=3.4.7; python_version > '3'" - "fpdf>=1.7.2" - "dbf>=0.88.019" - "Pillow>=2.0.0" - "tabulate>=0.8.5" - "certifi>=2020.4.5.1" - "qrcode>=6.1" - "future>=0.18.2" - "pywin32==304; sys_platform == 'win32' and python_version > '3'", - "py2exe==0.11.1.1; sys_platform == 'win32' and python_version > '3'" + "httplib2==0.9.2;python_version <= '2.7'", + "httplib2>=0.20.4;python_version > '3'", + "pysimplesoap==1.08.14;python_version <= '2.7'", + "pysimplesoap @ git+https://github.com/pysimplesoap/pysimplesoap.git@py311#pysimplesoap;", + "cryptography==3.3.2;python_version <= '2.7'", + "cryptography>=3.4.7;python_version > '3'", + "fpdf>=1.7.2", + "dbf>=0.88.019", + "Pillow>=2.0.0", + "tabulate>=0.8.5", + "certifi>=2020.4.5.1", + "qrcode>=6.1", + "future>=0.18.2", + "pywin32==304;sys_platform == 'win32' and python_version > '3'", + "py2exe==0.11.1.1;sys_platform == 'win32' and python_version > '3'" ], options=opts, data_files=data_files, From 171ad52506e0531761563a3555539a8194c6ec61 Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Mon, 11 Sep 2023 23:19:24 +0100 Subject: [PATCH 52/65] feat: adding unit tests for wsfev1 based on issue #107 Signed-off-by: HanslettTheDev --- tests/test_wsfev1.py | 55 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/tests/test_wsfev1.py b/tests/test_wsfev1.py index cfe67ed27..c8dc4332f 100644 --- a/tests/test_wsfev1.py +++ b/tests/test_wsfev1.py @@ -10,6 +10,7 @@ # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. +from unittest.mock import Mock from pyafipws.wsaa import WSAA from pyafipws.wsfev1 import WSFEv1, main from builtins import str @@ -40,6 +41,35 @@ pytestmark =[pytest.mark.vcr, pytest.mark.freeze_time('2021-07-01')] +@pytest.fixture +def mock_client(): + mock = Mock() + + mock_response = { + "FEParamGetActividadesResult": { + "ResultGet": [ + { + "ActividadesTipo": { + "Id": 1, + "Orden": 10, + "Desc": "Activity 1", + } + }, + { + "ActividadesTipo": { + "Id": 2, + "Orden": 20, + "Desc": "Activity 2", + } + }, + ] + } + } + + mock.FEParamGetActividades.return_value = mock_response + + return mock + def test_dummy(auth): wsfev1 = auth wsfev1.Dummy() @@ -222,6 +252,31 @@ def test_reproceso_nota_debito(auth): test_autorizar_comprobante(auth, tipo_cbte, cbte_nro, servicios=False) assert (wsfev1.Reproceso == "S") +def test_agregar_actividad(): + """Test Agrego actividad a una factura (interna)""" + wsfev1 = WSFEv1() + wsfev1.CrearFactura() + wsfev1.AgregarActividad(960990) + assert wsfev1.factura["actividades"][0]["actividad_id"] == 960990 + + +def test_param_get_actividades(mock_client): + """Test the response values from activity code from the web service""" + wsfev1 = WSFEv1() + wsfev1.Cuit = "sdfsdf" + wsfev1.client = mock_client + + # call the ParamGetActividades where the client + # will be instantiated by the mock + items = wsfev1.ParamGetActividades() + + expected_result = [ + "1|10|Activity 1", + "2|20|Activity 2", + ] + + # Check the methods return the expected result + assert items == expected_result def test_main(auth): sys.argv = [] From 66dd1d5fc798ae0210fe6c4cc0edd7090c30e564 Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Wed, 13 Sep 2023 13:36:57 +0100 Subject: [PATCH 53/65] fix: removed development dependencies and moved optional dependencies to extras_require Signed-off-by: HanslettTheDev --- setup.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index f0dfbad55..9b07507ad 100644 --- a/setup.py +++ b/setup.py @@ -84,9 +84,10 @@ "certifi>=2020.4.5.1", "qrcode>=6.1", "future>=0.18.2", - "pywin32==304;sys_platform == 'win32' and python_version > '3'", - "py2exe==0.11.1.1;sys_platform == 'win32' and python_version > '3'" ], + extras_require={ + "opt": ["pywin32==304;sys_platform == 'win32' and python_version > '3'"] + }, options=opts, data_files=data_files, classifiers=[ From b4fec15c2004f0bf0aed7ced171d5efc3266b0bb Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Wed, 20 Sep 2023 14:29:04 +0100 Subject: [PATCH 54/65] feat: github action deploy yml file Signed-off-by: HanslettTheDev --- .github/workflows/deploy-package.yml | 41 ++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/workflows/deploy-package.yml diff --git a/.github/workflows/deploy-package.yml b/.github/workflows/deploy-package.yml new file mode 100644 index 000000000..7dd2665e4 --- /dev/null +++ b/.github/workflows/deploy-package.yml @@ -0,0 +1,41 @@ +# This workflow will build pyafipws package and deploy on pypi. +# For more information see: https://github.com/py2exe/py2exe/blob/master/.github/workflows/CI.yml + +name: Deploy to PyPi + +on: + workflow_run: + workflows: ["python-package", "windows-installer"] + types: + - completed + release: + types: [published] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.9, 3.11] + + steps: + - uses: actions/checkout@v2 + - name: Set Up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Build PyAfipWs Package + run: | + mkdir ./dist + pip install wheel + python setup.py bdist_wheel sdist + - name: Publish Package + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.TEST_PYPI_API_TOKEN }} + repository_url: https://test.pypi.org/legacy/ \ No newline at end of file From e6407f00d9e61ec3dfcdded5bb321b608c43ff78 Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Wed, 20 Sep 2023 22:40:45 +0100 Subject: [PATCH 55/65] feat: added a step to check the installation Signed-off-by: HanslettTheDev --- .github/workflows/deploy-package.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-package.yml b/.github/workflows/deploy-package.yml index 7dd2665e4..2b03fe002 100644 --- a/.github/workflows/deploy-package.yml +++ b/.github/workflows/deploy-package.yml @@ -38,4 +38,8 @@ jobs: with: user: __token__ password: ${{ secrets.TEST_PYPI_API_TOKEN }} - repository_url: https://test.pypi.org/legacy/ \ No newline at end of file + repository_url: https://test.pypi.org/legacy/ + - name: Test PyAfipWs Upload + run: | + pip install -i https://test.pypi.org/simple/ PyAfipWs + \ No newline at end of file From 49dd24652b1534631043ab73c979f61f9fe21980 Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Thu, 28 Sep 2023 06:41:54 +0100 Subject: [PATCH 56/65] ressolved the conversation made by reingart here https://github.com/PyAr/pyafipws/pull/119#discussion_r1326721063 Signed-off-by: HanslettTheDev --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9b07507ad..cb5302363 100644 --- a/setup.py +++ b/setup.py @@ -74,7 +74,7 @@ "httplib2==0.9.2;python_version <= '2.7'", "httplib2>=0.20.4;python_version > '3'", "pysimplesoap==1.08.14;python_version <= '2.7'", - "pysimplesoap @ git+https://github.com/pysimplesoap/pysimplesoap.git@py311#pysimplesoap;", + "pysimplesoap @ git+https://github.com/pysimplesoap/pysimplesoap.git@py311#pysimplesoap;python_version > '3'", "cryptography==3.3.2;python_version <= '2.7'", "cryptography>=3.4.7;python_version > '3'", "fpdf>=1.7.2", From 0ec9862edbf6dadf826bede94eb893b1f0c6f5e0 Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Mon, 2 Oct 2023 00:52:26 +0100 Subject: [PATCH 57/65] fix: removed the environment markers for pysimplesoap Signed-off-by: HanslettTheDev --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index cb5302363..164cadaa3 100644 --- a/setup.py +++ b/setup.py @@ -74,7 +74,7 @@ "httplib2==0.9.2;python_version <= '2.7'", "httplib2>=0.20.4;python_version > '3'", "pysimplesoap==1.08.14;python_version <= '2.7'", - "pysimplesoap @ git+https://github.com/pysimplesoap/pysimplesoap.git@py311#pysimplesoap;python_version > '3'", + "pysimplesoap @ git+https://github.com/pysimplesoap/pysimplesoap.git@py311#pysimplesoap", "cryptography==3.3.2;python_version <= '2.7'", "cryptography>=3.4.7;python_version > '3'", "fpdf>=1.7.2", From 54470b5683fc2b282a523299eb2b687441826e47 Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Tue, 3 Oct 2023 14:14:06 +0100 Subject: [PATCH 58/65] feat: added a comprehensive test for the module Signed-off-by: HanslettTheDev --- .github/workflows/deploy-package.yml | 34 ++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy-package.yml b/.github/workflows/deploy-package.yml index 2b03fe002..ec992e9c5 100644 --- a/.github/workflows/deploy-package.yml +++ b/.github/workflows/deploy-package.yml @@ -39,7 +39,33 @@ jobs: user: __token__ password: ${{ secrets.TEST_PYPI_API_TOKEN }} repository_url: https://test.pypi.org/legacy/ - - name: Test PyAfipWs Upload - run: | - pip install -i https://test.pypi.org/simple/ PyAfipWs - \ No newline at end of file + + + test: + name: "Test Deployed PyAfipWs Package" + needs: build + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: [3.9, 3.11] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install PyAfipWs from PyPi + run: | + pip install -i https://test.pypi.org/simple/ PyAfipWs + - name: Download certificate and private key + run: | + wget "https://www.sistemasagiles.com.ar/soft/pyafipws/reingart2021.zip" -O reingart2019.zip + unzip reingart2019.zip + - name: Test WSAA servers + run: | + python -m pyafipws.wsaa + - name: Test WSFEV1 + run: | + python -m pyafipws.wsfev1 --dummy \ No newline at end of file From dd1e9b7820c173d3da02d6849d80f8169aacfab2 Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Thu, 5 Oct 2023 08:48:27 +0100 Subject: [PATCH 59/65] feat: readme now used as the long description text Signed-off-by: HanslettTheDev --- setup.py | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/setup.py b/setup.py index c730591dd..d9a8994b3 100644 --- a/setup.py +++ b/setup.py @@ -42,30 +42,18 @@ opts = {} data_files = [("pyafipws/plantillas", glob.glob("plantillas/*"))] - -long_desc = ( - "Interfases, herramientas y aplicativos para Servicios Web" - "AFIP (Factura Electrónica, Granos, Aduana, etc.), " - "ANMAT (Trazabilidad de Medicamentos), " - "RENPRE (Trazabilidad de Precursores Químicos), " - "ARBA (Remito Electrónico)" -) - # convert the README and format in restructured text (only when registering) -if "sdist" in sys.argv and os.path.exists("README.md") and sys.platform == "linux2": - try: - cmd = ["pandoc", "--from=markdown", "--to=rst", "README.md"] - long_desc = subprocess.check_output(cmd).decode("utf8") - open("README.rst", "w").write(long_desc.encode("utf8")) - except Exception as e: - warnings.warn("Exception when converting the README format: %s" % e) - +# from docs https://packaging.python.org/en/latest/guides/making-a-pypi-friendly-readme/ +from pathlib import Path +parent_dir = Path(__file__).parent +long_desc = (parent_dir / "README.md").read_text() setup( name="PyAfipWs", version=__version__, description=desc, long_description=long_desc, + long_description_content_type="text/markdown", author="Mariano Reingart", author_email="reingart@gmail.com", url="https://github.com/reingart/pyafipws", From 017345a86e5301237cfb5b76c897d538de5a2428 Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Fri, 6 Oct 2023 18:23:03 +0100 Subject: [PATCH 60/65] fix: moved pysimplesoap to the dependency_links option Signed-off-by: HanslettTheDev --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 164cadaa3..73c66cc4e 100644 --- a/setup.py +++ b/setup.py @@ -74,7 +74,6 @@ "httplib2==0.9.2;python_version <= '2.7'", "httplib2>=0.20.4;python_version > '3'", "pysimplesoap==1.08.14;python_version <= '2.7'", - "pysimplesoap @ git+https://github.com/pysimplesoap/pysimplesoap.git@py311#pysimplesoap", "cryptography==3.3.2;python_version <= '2.7'", "cryptography>=3.4.7;python_version > '3'", "fpdf>=1.7.2", @@ -85,6 +84,9 @@ "qrcode>=6.1", "future>=0.18.2", ], + dependency_links=[ + "git+https://github.com/pysimplesoap/pysimplesoap.git@py311#pysimplesoap; python_version > '3'", + ], extras_require={ "opt": ["pywin32==304;sys_platform == 'win32' and python_version > '3'"] }, From 92908b52616809bdb06b8b31a11cd9d24ed5a887 Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Fri, 6 Oct 2023 19:08:52 +0100 Subject: [PATCH 61/65] ref: deferred back to default open() function Signed-off-by: HanslettTheDev --- setup.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index d9a8994b3..0a24bc227 100644 --- a/setup.py +++ b/setup.py @@ -44,9 +44,8 @@ # convert the README and format in restructured text (only when registering) # from docs https://packaging.python.org/en/latest/guides/making-a-pypi-friendly-readme/ -from pathlib import Path -parent_dir = Path(__file__).parent -long_desc = (parent_dir / "README.md").read_text() +parent_dir = os.getcwd() +long_desc = open(os.path.join(parent_dir, "README.md")).read() setup( name="PyAfipWs", From 3a62832684e8af64360d823f596490c8302bbb4a Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Sat, 28 Oct 2023 06:26:33 +0100 Subject: [PATCH 62/65] ref: refactored the function name, removed pytest.fixture Signed-off-by: HanslettTheDev --- tests/test_wsfev1.py | 65 ++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/tests/test_wsfev1.py b/tests/test_wsfev1.py index c8dc4332f..877eb1a62 100644 --- a/tests/test_wsfev1.py +++ b/tests/test_wsfev1.py @@ -40,36 +40,6 @@ pytestmark =[pytest.mark.vcr, pytest.mark.freeze_time('2021-07-01')] - -@pytest.fixture -def mock_client(): - mock = Mock() - - mock_response = { - "FEParamGetActividadesResult": { - "ResultGet": [ - { - "ActividadesTipo": { - "Id": 1, - "Orden": 10, - "Desc": "Activity 1", - } - }, - { - "ActividadesTipo": { - "Id": 2, - "Orden": 20, - "Desc": "Activity 2", - } - }, - ] - } - } - - mock.FEParamGetActividades.return_value = mock_response - - return mock - def test_dummy(auth): wsfev1 = auth wsfev1.Dummy() @@ -260,12 +230,41 @@ def test_agregar_actividad(): assert wsfev1.factura["actividades"][0]["actividad_id"] == 960990 -def test_param_get_actividades(mock_client): +def test_param_get_actividades(): """Test the response values from activity code from the web service""" + def simulate_wsfev1_client(): + mock = Mock() + + mock_response = { + "FEParamGetActividadesResult": { + "ResultGet": [ + { + "ActividadesTipo": { + "Id": 1, + "Orden": 10, + "Desc": "Activity 1", + } + }, + { + "ActividadesTipo": { + "Id": 2, + "Orden": 20, + "Desc": "Activity 2", + } + }, + ] + } + } + + mock.FEParamGetActividades.return_value = mock_response + + return mock + + wsfev1 = WSFEv1() wsfev1.Cuit = "sdfsdf" - wsfev1.client = mock_client - + wsfev1.client = simulate_wsfev1_client() + # call the ParamGetActividades where the client # will be instantiated by the mock items = wsfev1.ParamGetActividades() From ed284d304a6c1ec4637e00ae94bead34a53617e2 Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Sat, 28 Oct 2023 06:32:10 +0100 Subject: [PATCH 63/65] feat: removed dependency link option Signed-off-by: HanslettTheDev --- setup.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 4bcbe8ec4..a5b2a6689 100644 --- a/setup.py +++ b/setup.py @@ -61,6 +61,7 @@ "httplib2==0.9.2;python_version <= '2.7'", "httplib2>=0.20.4;python_version > '3'", "pysimplesoap==1.08.14;python_version <= '2.7'", + "pysimplesoap==1.8.22;python_version > '3'" "cryptography==3.3.2;python_version <= '2.7'", "cryptography>=3.4.7;python_version > '3'", "fpdf>=1.7.2", @@ -71,9 +72,6 @@ "qrcode>=6.1", "future>=0.18.2", ], - dependency_links=[ - "git+https://github.com/pysimplesoap/pysimplesoap.git@py311#pysimplesoap; python_version > '3'", - ], extras_require={ "opt": ["pywin32==304;sys_platform == 'win32' and python_version > '3'"] }, From b8639edba0da0ff6990011264184e55e043bc97c Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Sat, 28 Oct 2023 06:36:27 +0100 Subject: [PATCH 64/65] fix: added the comma to fix install package dependency Signed-off-by: HanslettTheDev --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a5b2a6689..f8d2a4e2d 100644 --- a/setup.py +++ b/setup.py @@ -61,7 +61,7 @@ "httplib2==0.9.2;python_version <= '2.7'", "httplib2>=0.20.4;python_version > '3'", "pysimplesoap==1.08.14;python_version <= '2.7'", - "pysimplesoap==1.8.22;python_version > '3'" + "pysimplesoap==1.8.22;python_version > '3'", "cryptography==3.3.2;python_version <= '2.7'", "cryptography>=3.4.7;python_version > '3'", "fpdf>=1.7.2", From 5f53dcbb5508a6cdd08d9bff7888f3b41f04819b Mon Sep 17 00:00:00 2001 From: HanslettTheDev Date: Sat, 28 Oct 2023 06:51:10 +0100 Subject: [PATCH 65/65] bump: pysimplesoap -> 1.8.22 Signed-off-by: HanslettTheDev --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 7e3c56cc7..e949a2416 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ httplib2==0.9.2; python_version <= '2.7' httplib2==0.20.4; python_version > '3' pysimplesoap==1.08.14; python_version <= '2.7' -git+https://github.com/pysimplesoap/pysimplesoap.git@py311#pysimplesoap; python_version > '3' +pysimplesoap==1.8.22; python_version > '3' cryptography==3.3.2; python_version <= '2.7' cryptography==41.0.1; python_version > '3' fpdf>=1.7.2 @@ -11,4 +11,4 @@ Pillow>=2.0.0; platform_machine=='x86_64' tabulate==0.8.5 certifi>=2020.4.5.1 qrcode==6.1 -future==0.18.3 \ No newline at end of file +future==0.18.3