diff --git a/padron.py b/padron.py index 043e286a7..f754f1743 100644 --- a/padron.py +++ b/padron.py @@ -12,7 +12,7 @@ "Herramienta para procesar y consultar el Padrón Unico de Contribuyentes AFIP" -# Documentación e información adicional: +# Documentación e información adicional: # http://www.sistemasagiles.com.ar/trac/wiki/PadronContribuyentesAFIP __author__ = "Mariano Reingart " @@ -42,7 +42,7 @@ FORMATO = [ ("nro_doc", 11, N, ""), ("denominacion", 30, A, ""), - ("imp_ganancias", 2, A, "'NI', 'AC','EX', 'NC'"), + ("imp_ganancias", 2, A, "'NI', 'AC','EX', 'NC'"), ("imp_iva", 2, A, "'NI' , 'AC','EX','NA','XN','AN'"), ("monotributo", 2, A, "'NI', 'Codigo categoria tributaria'"), ("integrante_soc", 1, A, "'N' , 'S'"), @@ -50,15 +50,15 @@ ("actividad_monotributo", 2, A, ""), ("tipo_doc", 2, N, "80: CUIT, 96: DNI, etc."), ("cat_iva", 2, N, "1: RI, 4: EX, 5: CF, 6: MT, etc"), - ("email", 250, A, ""), - ] + ("email", 250, A, ""), + ] # Mapeos constantes: -PROVINCIAS = {0: 'CIUDAD AUTONOMA BUENOS AIRES', 1: 'BUENOS AIRES', +PROVINCIAS = {0: 'CIUDAD AUTONOMA BUENOS AIRES', 1: 'BUENOS AIRES', 2: 'CATAMARCA', 3: 'CORDOBA', 4: 'CORRIENTES', 5: 'ENTRE RIOS', 6: 'JUJUY', - 7: 'MENDOZA', 8: 'LA RIOJA', 9: 'SALTA', 10: 'SAN JUAN', 11: 'SAN LUIS', - 12: 'SANTA FE', 13: 'SANTIAGO DEL ESTERO', 14: 'TUCUMAN', 16: 'CHACO', + 7: 'MENDOZA', 8: 'LA RIOJA', 9: 'SALTA', 10: 'SAN JUAN', 11: 'SAN LUIS', + 12: 'SANTA FE', 13: 'SANTIAGO DEL ESTERO', 14: 'TUCUMAN', 16: 'CHACO', 17: 'CHUBUT', 18: 'FORMOSA', 19: 'MISIONES', 20: 'NEUQUEN', 21: 'LA PAMPA', 22: 'RIO NEGRO', 23: 'SANTA CRUZ', 24: 'TIERRA DEL FUEGO'} @@ -75,12 +75,12 @@ class PadronAFIP(): _public_methods_ = ['Buscar', 'Descargar', 'Procesar', 'Guardar', 'ConsultarDomicilios', 'Consultar', 'Conectar', - 'DescargarConstancia', 'MostrarPDF', + 'DescargarConstancia', 'MostrarPDF', "ObtenerTablaParametros", ] _public_attrs_ = ['InstallDir', 'Traceback', 'Excepcion', 'Version', - 'cuit', 'dni', 'denominacion', 'imp_ganancias', 'imp_iva', - 'monotributo', 'integrante_soc', 'empleador', + 'cuit', 'dni', 'denominacion', 'imp_ganancias', 'imp_iva', + 'monotributo', 'integrante_soc', 'empleador', 'actividad_monotributo', 'cat_iva', 'domicilios', 'tipo_doc', 'nro_doc', 'LanzarExcepciones', 'tipo_persona', 'estado', 'impuestos', 'actividades', @@ -101,7 +101,7 @@ def __init__(self): self.LanzarExcepciones = False self.inicializar() self.client = None - + def inicializar(self): self.Excepcion = self.Traceback = "" self.cuit = self.dni = 0 @@ -114,14 +114,14 @@ def inicializar(self): self.impuestos = [] self.actividades = [] self.imp_iva = self.empleador = self.integrante_soc = self.cat_iva = "" - self.monotributo = self.actividad_monotributo = "" + self.monotributo = self.actividad_monotributo = "" self.data = {} self.response = "" @inicializar_y_capturar_excepciones_simple def Conectar(self, url=URL_API, proxy="", wrapper=None, cacert=None, trace=False): self.client = WebClient(location=url, trace=trace, cacert=cacert) - self.client.method = "GET" # metodo RESTful predeterminado + self.client.method = "GET" # metodo RESTful predeterminado self.client.enctype = None # no enviar body return True @@ -136,8 +136,8 @@ def Descargar(self, url=URL, filename="padron.txt", proxy=None): print("Abriendo URL %s ..." % url) req = urllib.request.Request(url) if os.path.exists(filename): - http_date = formatdate(timeval=os.path.getmtime(filename), - localtime=False, usegmt=True) + http_date = formatdate(timeval=os.path.getmtime(filename), + localtime=False, usegmt=True) req.add_header('If-Modified-Since', http_date) try: web = urllib.request.urlopen(req) @@ -177,10 +177,10 @@ def Descargar(self, url=URL, filename="padron.txt", proxy=None): tf.write(zf.read(fn)) tf.close() return 200 - + @inicializar_y_capturar_excepciones_simple def Procesar(self, filename="padron.txt", borrar=False): - "Analiza y crea la base de datos interna sqlite para consultas" + "Analiza y crea la base de datos interna sqlite para consultas" f = open(filename, "r") keys = [k for k, l, t, d in FORMATO] # conversion a planilla csv (no usado) @@ -188,9 +188,9 @@ def Procesar(self, filename="padron.txt", borrar=False): csvfile = open('padron.csv', 'wb') import csv wr = csv.writer(csvfile, delimiter=',', - quotechar='"', quoting=csv.QUOTE_MINIMAL) + quotechar='"', quoting=csv.QUOTE_MINIMAL) for i, l in enumerate(f): - if i % 100000 == 0: + if i % 100000 == 0: print("Progreso: %d registros" % i) r = leer(l, FORMATO) row = [r[k] for k in keys] @@ -237,7 +237,7 @@ def Procesar(self, filename="padron.txt", borrar=False): db.commit() c.close() db.close() - + @inicializar_y_capturar_excepciones_simple def Buscar(self, nro_doc, tipo_doc=80): "Devuelve True si fue encontrado y establece atributos con datos" @@ -279,7 +279,7 @@ def Buscar(self, nro_doc, tipo_doc=80): def ConsultarDomicilios(self, nro_doc, tipo_doc=80, cat_iva=None): "Busca los domicilios, devuelve la cantidad y establece la lista" self.cursor.execute("SELECT direccion FROM domicilio WHERE " - " tipo_doc=? AND nro_doc=? ORDER BY id ", + " tipo_doc=? AND nro_doc=? ORDER BY id ", [tipo_doc, nro_doc]) filas = self.cursor.fetchall() self.domicilios = [fila['direccion'] for fila in filas] @@ -300,7 +300,7 @@ def Guardar(self, tipo_doc, nro_doc, denominacion, cat_iva, direccion, email): # agregar el domicilio solo si no existe: if direccion: self.cursor.execute("SELECT * FROM domicilio WHERE direccion=? " - "AND tipo_doc=? AND nro_doc=?", + "AND tipo_doc=? AND nro_doc=?", [direccion, tipo_doc, nro_doc]) if self.cursor.rowcount < 0: sql = ("INSERT INTO domicilio (nro_doc, tipo_doc, direccion)" @@ -357,7 +357,7 @@ def Consultar(self, nro_doc): self.cod_postal = "" # retrocompatibilidad: self.domicilios = ["%s - %s (%s) - %s" % ( - self.direccion, self.localidad, + self.direccion, self.localidad, self.cod_postal, self.provincia,) ] # analizo impuestos: self.impuestos = data.get("impuestos", []) @@ -410,11 +410,11 @@ def MostrarPDF(self, archivo, imprimir=False): @inicializar_y_capturar_excepciones_simple def ObtenerTablaParametros(self, tipo_recurso, sep="||"): - "Devuelve un array de elementos que tienen id y descripción" + "Devuelve un array de elementos que tienen id y descripción" if not self.client: self.Conectar() self.response = self.client("parametros", "v1", tipo_recurso) - result = json.loads(self.response) + result = json.loads(self.response.decode('utf8')) ret = {} if result['success']: data = result['data'] @@ -431,7 +431,7 @@ def ObtenerTablaParametros(self, tipo_recurso, sep="||"): return ["%s%%s%s%%s%s" % (sep, sep, sep) % it for it in sorted(ret.items())] else: return ret - + # busco el directorio de instalación (global para que no cambie si usan otra dll) @@ -472,18 +472,18 @@ def ObtenerTablaParametros(self, tipo_recurso, sep="||"): print('\n'.join(padron.ObtenerTablaParametros("categoriasAutonomo"))) if '--csv' in sys.argv: - csv_reader = csv.reader(open("entrada.csv", "rU"), + csv_reader = csv.reader(open("entrada.csv", "rU"), dialect='excel', delimiter=",") - csv_writer = csv.writer(open("salida.csv", "w"), + csv_writer = csv.writer(open("salida.csv", "w"), dialect='excel', delimiter=",") encabezado = next(csv_reader) columnas = ["cuit", "denominacion", "estado", "direccion", "localidad", "provincia", "cod_postal", - "impuestos", "actividades", "imp_iva", - "monotributo", "actividad_monotributo", + "impuestos", "actividades", "imp_iva", + "monotributo", "actividad_monotributo", "empleador", "imp_ganancias", "integrante_soc"] csv_writer.writerow(columnas) - + for fila in csv_reader: cuit = (fila[0] if fila else "").replace("-", "") if cuit.isdigit(): @@ -515,7 +515,7 @@ def ObtenerTablaParametros(self, tipo_recurso, sep="||"): ok = padron.Consultar(cuit) print('ok' if ok else "error", padron.Excepcion) print("Denominacion:", padron.denominacion) - print("CUIT:", padron.cuit) + print("CUIT:", padron.cuit) print("Tipo:", padron.tipo_persona, padron.tipo_doc, padron.dni) print("Estado:", padron.estado) print("Direccion:", padron.direccion) diff --git a/utils.py b/utils.py index 7677f41bb..960c107b0 100644 --- a/utils.py +++ b/utils.py @@ -44,7 +44,7 @@ import json except ImportError: try: - import simplejson as json + import simplejson as json except: print("para soporte de JSON debe instalar simplejson") json = None @@ -62,7 +62,7 @@ elif release: needs_patch = (release in 'XP') else: - needs_patch = False + needs_patch = False if needs_patch and not monkey_patch: import ssl def _ssl_wrap_socket(sock, key_file, cert_file, @@ -92,8 +92,8 @@ def exception_info(current_filename=None, index=-1): info = sys.exc_info() # exc_type, exc_value, exc_traceback # importante: no usar unpacking porque puede causar memory leak if not current_filename: - # genero un call stack para ver quien me llamó y limitar la traza: - # advertencia: esto es necesario ya que en py2exe no tengo __file__ + # genero un call stack para ver quien me llamó y limitar la traza: + # advertencia: esto es necesario ya que en py2exe no tengo __file__ try: raise ZeroDivisionError except ZeroDivisionError: @@ -106,7 +106,7 @@ def exception_info(current_filename=None, index=-1): try: for (filename, lineno, fn, text) in traceback.extract_tb(info[2]): if os.path.normpath(os.path.abspath(filename)) == current_filename: - ret = {'filename': filename, 'lineno': lineno, + ret = {'filename': filename, 'lineno': lineno, 'function_name': fn, 'code': text} except Exception as e: pass @@ -114,7 +114,7 @@ def exception_info(current_filename=None, index=-1): # (para evitar errores de encoding) try: ret['msg'] = traceback.format_exception_only(*info[0:2])[0] - except: + except: ret['msg'] = '' # obtener el nombre de la excepcion (ej. "NameError") try: @@ -220,7 +220,7 @@ def __init__(self, reintentos=1): self.inicializar() self.Token = self.Sign = "" self.LanzarExcepciones = True - + def inicializar(self): self.Excepcion = self.Traceback = "" self.XmlRequest = self.XmlResponse = "" @@ -243,7 +243,7 @@ def Conectar(self, cache=None, wsdl=None, proxy="", wrapper=None, cacert=None, t if not wsdl.endswith(self.WSDL[-5:]) and wsdl.startswith("http"): wsdl += self.WSDL[-5:] if not cache or self.HOMO: - # use 'cache' from installation base directory + # use 'cache' from installation base directory cache = os.path.join(self.InstallDir, 'cache') # deshabilitar verificación cert. servidor si es nulo falso vacio if not cacert: @@ -254,7 +254,7 @@ def Conectar(self, cache=None, wsdl=None, proxy="", wrapper=None, cacert=None, t elif cacert.startswith("-----BEGIN CERTIFICATE-----"): pass else: - if not os.path.exists(cacert): + if not os.path.exists(cacert): self.log("Buscando CACERT en conf...") cacert = os.path.join(self.InstallDir, "conf", os.path.basename(cacert)) if cacert and not os.path.exists(cacert): @@ -263,17 +263,17 @@ def Conectar(self, cache=None, wsdl=None, proxy="", wrapper=None, cacert=None, t cacert = None # wrong version, certificates not found... raise RuntimeError("Error de configuracion CACERT ver DebugLog") return False - + self.log("Conectando a wsdl=%s cache=%s proxy=%s" % (wsdl, cache, proxy_dict)) # analizar espacio de nombres (axis vs .net): ns = 'ser' if self.WSDL[-5:] == "?wsdl" else None self.client = SoapClient( - wsdl = wsdl, + wsdl = wsdl, cache = cache, proxy = proxy_dict, cacert = cacert, timeout = timeout, - ns = ns, soap_server = soap_server, + ns = ns, soap_server = soap_server, trace = "--trace" in sys.argv) self.cache = cache # utilizado por WSLPG y WSAA (Ticket de Acceso) self.wsdl = wsdl # utilizado por TrazaMed (para corregir el location) @@ -323,7 +323,7 @@ def DebugLog(self): self.Log = None else: msg = '' - return msg + return msg def LoadTestXML(self, xml): "Cargar un archivo de pruebas con la respuesta simulada (depuración)" @@ -349,9 +349,9 @@ def AnalizarXml(self, xml=""): "Analiza un mensaje XML (por defecto el ticket de acceso)" try: if not xml or xml=='XmlResponse': - xml = self.XmlResponse + xml = self.XmlResponse elif xml=='XmlRequest': - xml = self.XmlRequest + xml = self.XmlRequest self.xml = SimpleXMLElement(xml) return True except Exception as e: @@ -419,7 +419,7 @@ def GetParametro(self, clave, clave1=None, clave2=None, clave3=None, clave4=None def LeerError(self): "Recorro los errores devueltos y devuelvo el primero si existe" - + if self.Errores: # extraigo el primer item er = self.Errores.pop(0) @@ -475,14 +475,14 @@ 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") + # if isinstance(location, str): + # location = location.encode("utf8") # extend the base URI with additional components if args: location += "/".join(args) if self.method == "GET": location += "?%s" % urlencode(vars) - + # prepare the request content suitable to be sent to the server: if self.enctype == "multipart/form-data": boundary, body = self.multipart_encode(vars) @@ -492,9 +492,9 @@ def __call__(self, *args, **vars): content_type = self.enctype else: body = None - + # add headers according method, cookies, etc.: - headers={} + headers={} if self.method == "POST": headers.update({ 'Content-type': content_type, @@ -510,15 +510,15 @@ def __call__(self, *args, **vars): print("%s %s" % (self.method, location)) print('\n'.join(["%s: %s" % (k,v) for k,v in list(headers.items())])) print("\n%s" % body) - + # send the request to the server and store the result: response, content = self.http.request( location, self.method, body=body, headers=headers ) self.response = response self.content = content - if self.trace: - print() + if self.trace: + print() print('\n'.join(["%s: %s" % (k,v) for k,v in list(response.items())])) print(content) print("="*80) @@ -538,11 +538,11 @@ class AttrDict(dict): class HTMLFormParser(HTMLParser): "Convert HTML form into custom named-tuple dicts" - + def __init__(self, *args, **kwargs): HTMLParser.__init__(self, *args, **kwargs) self.forms = {} - + def handle_starttag(self, tag, attrs): attrs = dict(attrs) if 'name' in attrs: @@ -567,7 +567,7 @@ def leer(linea, formato, expandir_fechas=False): "Analiza una linea de texto dado un formato, devuelve un diccionario" dic = {} comienzo = 1 - for fmt in formato: + for fmt in formato: clave, longitud, tipo = fmt[0:3] dec = (len(fmt)>3 and isinstance(fmt[3], int)) and fmt[3] or 2 valor = linea[comienzo-1:comienzo-1+longitud].strip() @@ -588,7 +588,7 @@ def leer(linea, formato, expandir_fechas=False): valor = valor.strip(" ") if valor[0] == "-": sign = -1 - valor = valor[1:] + valor = valor[1:] else: sign = +1 valor = sign * float(("%%s.%%0%sd" % dec) % (int(valor[:-dec] or '0'), int(valor[-dec:] or '0'))) @@ -675,11 +675,11 @@ def guardar_dbf(formatos, agrega=False, conf_dbf=None): if longitud>250: tipo = "M" # memo! elif tipo == A: - tipo = "C(%s)" % longitud + tipo = "C(%s)" % longitud elif tipo == N: if longitud >= 18: longitud = 17 - tipo = "N(%s,0)" % longitud + tipo = "N(%s,0)" % longitud elif tipo == I: if not dec: dec = 0 @@ -688,7 +688,7 @@ def guardar_dbf(formatos, agrega=False, conf_dbf=None): if longitud >= 18: longitud = 17 if longitud - 2 <= dec: - longitud += longitud - dec + 1 # ajusto long. decimales + longitud += longitud - dec + 1 # ajusto long. decimales tipo = "N(%s,%s)" % (longitud, dec) clave_dbf = dar_nombre_campo_dbf(clave, claves) campo = "%s %s" % (clave_dbf, tipo) @@ -702,7 +702,7 @@ def guardar_dbf(formatos, agrega=False, conf_dbf=None): tabla = dbf.Table(filename) for d in l: - # si no es un diccionario, ignorar ya que seguramente va en otra + # si no es un diccionario, ignorar ya que seguramente va en otra # tabla (por ej. retenciones tiene su propio formato) if isinstance(d, str): continue @@ -751,7 +751,7 @@ def guardar_dbf(formatos, agrega=False, conf_dbf=None): def leer_dbf(formatos, conf_dbf): import dbf if DEBUG: print("Leyendo DBF...") - + for nombre, formato, ld in formatos: filename = conf_dbf.get(nombre.lower(), "%s.dbf" % nombre[:8]) if DEBUG: print("leyendo tabla", nombre, filename) @@ -760,7 +760,7 @@ def leer_dbf(formatos, conf_dbf): tabla = dbf.Table(filename) for reg in tabla: r = {} - d = reg.scatter_fields() + d = reg.scatter_fields() claves = [] for fmt in formato: clave, longitud, tipo = fmt[0:3] @@ -772,7 +772,7 @@ def leer_dbf(formatos, conf_dbf): if isinstance(ld, dict): ld.update(r) else: - ld.append(r) + ld.append(r) def dar_nombre_campo_dbf(clave, claves): @@ -782,7 +782,7 @@ def dar_nombre_campo_dbf(clave, claves): # si el campo esta repetido, le agrego un número i = 0 while nombre in claves: - i += 1 + i += 1 nombre = nombre[:9] + str(i) return nombre.lower() @@ -828,7 +828,7 @@ def verifica(ver_list, res_dict, difs): if float(res_dict.get(k)) != float(v): difs.append("%s: %s!=%s" % (k, repr(v), repr(res_dict.get(k)))) elif str(res_dict.get(k)) != str(v): - # tipos diferentes, comparo la representación + # tipos diferentes, comparo la representación difs.append("%s: str %s!=%s" % (k, repr(v), repr(res_dict.get(k)))) else: pass @@ -844,7 +844,7 @@ def __init__(self, target): self.errors = 'replace' self.encode_to = 'latin-1' def write(self, s): - self.target.write(self.intercept(s)) + self.target.write(self.intercept(s)) def flush(self): self.target.flush() def intercept(self, s): @@ -854,7 +854,7 @@ def intercept(self, s): sys.stdout = SafeWriter(sys.stdout) #sys.stderr = SafeWriter(sys.stderr) - print("Encodign in %s" % locale.getpreferredencoding()) + print("Encoding in %s" % locale.getpreferredencoding()) def norm(x, encoding="latin1"): @@ -871,7 +871,7 @@ def date(fmt=None,timestamp=None): if fmt=='U': # return timestamp t = datetime.datetime.now() return int(time.mktime(t.timetuple())) - if fmt=='c': # return isoformat + if fmt=='c': # return isoformat d = datetime.datetime.fromtimestamp(timestamp) return d.isoformat() if fmt=='Ymd': @@ -880,7 +880,7 @@ def date(fmt=None,timestamp=None): def get_install_dir(): - if not hasattr(sys, "frozen"): + if not hasattr(sys, "frozen"): basepath = __file__ elif sys.frozen=='dll': import win32api @@ -888,7 +888,7 @@ def get_install_dir(): else: basepath = sys.executable - if hasattr(sys, "frozen"): + if hasattr(sys, "frozen"): # we are running as py2exe-packed executable import pythoncom pythoncom.frozen = 1 @@ -896,7 +896,7 @@ def get_install_dir(): return os.path.dirname(os.path.abspath(basepath)) - + def abrir_conf(config_file, debug=False): "Abrir el archivo de configuración (usar primer parámetro como ruta)" # en principio, usar el nombre de archivo predeterminado @@ -909,7 +909,7 @@ def abrir_conf(config_file, debug=False): warnings.warn("Archivo de configuracion %s invalido" % config_file) if debug: print("CONFIG_FILE:", config_file) - + config = SafeConfigParser() config.read(config_file, encoding="latin1") diff --git a/wsaa.py b/wsaa.py index 98892bc51..5de2a047c 100644 --- a/wsaa.py +++ b/wsaa.py @@ -1,5 +1,5 @@ #!/usr/bin/python -# -*- coding: latin-1 -*- +# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by the # Free Software Foundation; either version 3, or (at your option) any later @@ -10,11 +10,11 @@ # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. -"Módulo para obtener un ticket de autorización del web service WSAA de AFIP" +"Módulo para obtener un ticket de autorización del web service WSAA de AFIP" # Basado en wsaa-client.php de Gerardo Fisanotti - DvSHyS/DiOPIN/AFIP - 13-apr-07 # Definir WSDL, CERT, PRIVATEKEY, PASSPHRASE, SERVICE, WSAAURL -# Devuelve TA.xml (ticket de autorización de WSAA) +# Devuelve TA.xml (ticket de autorización de WSAA) __author__ = "Mariano Reingart (reingart@gmail.com)" __copyright__ = "Copyright (C) 2008-2011 Mariano Reingart" @@ -31,26 +31,26 @@ except ImportError: ex = exception_info() warnings.warn("No es posible importar M2Crypto (OpenSSL)") - warnings.warn(ex['msg']) # revisar instalación y DLLs de OpenSSL + warnings.warn(ex['msg']) # revisar instalación y DLLs de OpenSSL BIO = Rand = SMIME = SSL = None - # utilizar alternativa (ejecutar proceso por separado) + # utilizar alternativa (ejecutar proceso por separado) 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 +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. PRIVATEKEY = "reingart.key" # La clave privada del certificado CERT -PASSPHRASE = "xxxxxxx" # La contraseña para firmar (si hay) +PASSPHRASE = "xxxxxxx" # La contraseña para firmar (si hay) SERVICE = "wsfe" # El nombre del web service al que se le pide el TA # WSAAURL: la URL para acceder al WSAA, verificar http/https y wsaa/wsaahomo #WSAAURL = "https://wsaa.afip.gov.ar/ws/services/LoginCms" # PRODUCCION!!! WSAAURL = "https://wsaahomo.afip.gov.ar/ws/services/LoginCms" # homologacion (pruebas) SOAP_ACTION = 'http://ar.gov.afip.dif.facturaelectronica/' # Revisar WSDL -SOAP_NS = "http://wsaa.view.sua.dvadac.desein.afip.gov" # Revisar WSDL +SOAP_NS = "http://wsaa.view.sua.dvadac.desein.afip.gov" # Revisar WSDL -# Verificación del web server remoto, necesario para verificar canal seguro +# Verificación del web server remoto, necesario para verificar canal seguro CACERT = "conf/afip_ca_info.crt" # WSAA CA Cert (Autoridades de Confiaza) HOMO = False @@ -58,7 +58,7 @@ DEFAULT_TTL = 60*60*5 # five hours DEBUG = False -# No debería ser necesario modificar nada despues de esta linea +# No debería ser necesario modificar nada despues de esta linea def create_tra(service=SERVICE,ttl=2400): "Crear un Ticket de Requerimiento de Acceso (TRA)" @@ -81,10 +81,10 @@ def sign_tra(tra,cert=CERT,privatekey=PRIVATEKEY,passphrase=""): if BIO: # Firmar el texto (tra) usando m2crypto (openssl bindings para python) - buf = BIO.MemoryBuffer(tra) # Crear un buffer desde el texto + buf = BIO.MemoryBuffer(tra.encode('utf8')) # Crear un buffer desde el texto #Rand.load_file('randpool.dat', -1) # Alimentar el PRNG s = SMIME.SMIME() # Instanciar un SMIME - # soporte de contraseña de encriptación (clave privada, opcional) + # soporte de contraseña de encriptación (clave privada, opcional) callback = lambda *args, **kwarg: passphrase # Cargar clave privada y certificado if not privatekey.startswith("-----BEGIN RSA PRIVATE KEY-----"): @@ -95,16 +95,16 @@ def sign_tra(tra,cert=CERT,privatekey=PRIVATEKEY,passphrase=""): else: raise RuntimeError("Archivos no encontrados: %s, %s" % (privatekey, cert)) # crear buffers en memoria de la clave privada y certificado: - key_bio = BIO.MemoryBuffer(privatekey) - crt_bio = BIO.MemoryBuffer(cert) + key_bio = BIO.MemoryBuffer(privatekey.encode('utf8')) + crt_bio = BIO.MemoryBuffer(cert.encode('utf8')) s.load_key_bio(key_bio, crt_bio, callback) # (desde buffer) p7 = s.sign(buf,0) # Firmar el buffer - out = BIO.MemoryBuffer() # Crear un buffer para la salida + out = BIO.MemoryBuffer() # Crear un buffer para la salida s.write(out, p7) # Generar p7 en formato mail #Rand.save_file('randpool.dat') # Guardar el estado del PRNG's # extraer el cuerpo del mensaje (parte firmada) - msg = email.message_from_string(out.read()) + msg = email.message_from_string(out.read().decode('utf8')) for part in msg.walk(): filename = part.get_filename() if filename == "smime.p7m": # es la parte firmada? @@ -119,9 +119,9 @@ def sign_tra(tra,cert=CERT,privatekey=PRIVATEKEY,passphrase=""): openssl = r"c:\OpenSSL-Win32\bin\openssl.exe" else: openssl = r"c:\OpenSSL-Win64\bin\openssl.exe" - out = Popen([openssl, "smime", "-sign", + out = Popen([openssl, "smime", "-sign", "-signer", cert, "-inkey", privatekey, - "-outform","DER", "-nodetach"], + "-outform","DER", "-nodetach"], stdin=PIPE, stdout=PIPE, stderr=PIPE).communicate(tra.encode("utf8"))[0] return b64encode(out).decode("utf8") except OSError as e: @@ -131,7 +131,7 @@ def sign_tra(tra,cert=CERT,privatekey=PRIVATEKEY,passphrase=""): def call_wsaa(cms, location = WSAAURL, proxy=None, trace=False, cacert=None): - "Llamar web service con CMS para obtener ticket de autorización (TA)" + "Llamar web service con CMS para obtener ticket de autorización (TA)" # creo la nueva clase wsaa = WSAA() @@ -147,14 +147,14 @@ def call_wsaa(cms, location = WSAAURL, proxy=None, trace=False, cacert=None): class WSAA(BaseWS): - "Interfaz para el WebService de Autenticación y Autorización" + "Interfaz para el WebService de Autenticación y Autorización" _public_methods_ = ['CreateTRA', 'SignTRA', 'CallWSAA', 'LoginCMS', 'Conectar', 'AnalizarXml', 'ObtenerTagXml', 'Expirado', 'Autenticar', 'DebugLog', 'AnalizarCertificado', 'CrearClavePrivada', 'CrearPedidoCertificado', ] - _public_attrs_ = ['Token', 'Sign', 'ExpirationTime', 'Version', - 'XmlRequest', 'XmlResponse', + _public_attrs_ = ['Token', 'Sign', 'ExpirationTime', 'Version', + 'XmlRequest', 'XmlResponse', 'InstallDir', 'Traceback', 'Excepcion', 'Identidad', 'Caducidad', 'Emisor', 'CertX509', 'SoapFault', 'LanzarExcepciones', @@ -171,8 +171,8 @@ class WSAA(BaseWS): # Variables globales para BaseWS: HOMO = HOMO WSDL = WSDL - Version = "%s %s" % (__version__, HOMO and 'Homologación' or '') - + Version = "%s %s" % (__version__, HOMO and 'Homologación' or '') + @inicializar_y_capturar_excepciones def CreateTRA(self, service="wsfe", ttl=2400): "Crear un Ticket de Requerimiento de Acceso (TRA)" @@ -180,15 +180,17 @@ def CreateTRA(self, service="wsfe", ttl=2400): @inicializar_y_capturar_excepciones def AnalizarCertificado(self, crt, binary=False): - "Carga un certificado digital y extrae los campos más importantes" + "Carga un certificado digital y extrae los campos más importantes" from M2Crypto import BIO, EVP, RSA, X509 if binary: - bio = BIO.MemoryBuffer(cert) - x509 = X509.load_cert_bio(bio, X509.FORMAT_DER) + bio = BIO.MemoryBuffer(cert.encode('utf8')) + x509 = X511.load_cert_bio(bio, X509.FORMAT_DER) else: if not crt.startswith("-----BEGIN CERTIFICATE-----"): crt = open(crt).read() - bio = BIO.MemoryBuffer(crt) + if isinstance(crt, str): + crt = crt.encode('utf-8') + bio = BIO.MemoryBuffer(crt.encode('utf8')) x509 = X509.load_cert_bio(bio, X509.FORMAT_PEM) if x509: self.Identidad = x509.get_subject().as_text() @@ -196,13 +198,13 @@ def AnalizarCertificado(self, crt, binary=False): self.Emisor = x509.get_issuer().as_text() self.CertX509 = x509.as_text() return True - + @inicializar_y_capturar_excepciones - def CrearClavePrivada(self, filename="privada.key", key_length=4096, + def CrearClavePrivada(self, filename="privada.key", key_length=4096, pub_exponent=0x10001, passphrase=""): "Crea una clave privada (private key)" from M2Crypto import RSA, EVP - + # only protect if passphrase was given (it will fail otherwise) callback = lambda *args, **kwarg: passphrase chiper = None if not passphrase else "aes_128_cbc" @@ -227,7 +229,7 @@ def CrearPedidoCertificado(self, cuit="", empresa="", nombre="pyafipws", # create the certificate signing request (CSR): self.x509_req = X509.Request () - # normalizar encoding (reemplazar acentos, eñe, etc.) + # normalizar encoding (reemplazar acentos, eñe, etc.) if isinstance(empresa, str): empresa = unicodedata.normalize('NFKD', empresa).encode('ASCII', 'ignore') if isinstance(nombre, str): @@ -240,7 +242,7 @@ def CrearPedidoCertificado(self, cuit="", empresa="", nombre="pyafipws", x509name.add_entry_by_txt(field='C', entry='AR', **kwargs) x509name.add_entry_by_txt(field='O', entry=empresa, **kwargs) x509name.add_entry_by_txt(field='CN', entry=nombre, **kwargs) - x509name.add_entry_by_txt(field='serialNumber', entry="CUIT %s" % str(cuit), **kwargs) + x509name.add_entry_by_txt(field='serialNumber', entry="CUIT %s" % str(cuit), **kwargs) self.x509_req.set_subject_name(x509name) # sign the request with the previously created key (CrearClavePrivada) @@ -251,15 +253,15 @@ def CrearPedidoCertificado(self, cuit="", empresa="", nombre="pyafipws", f.write(self.x509_req.as_pem()) f.close() return True - + @inicializar_y_capturar_excepciones def SignTRA(self, tra, cert, privatekey, passphrase=""): "Firmar el TRA y devolver CMS" - return sign_tra(str(tra),cert.encode('latin1'),privatekey.encode('latin1'),passphrase.encode("utf8")) + return sign_tra(tra,cert,privatekey,passphrase) @inicializar_y_capturar_excepciones def LoginCMS(self, cms): - "Obtener ticket de autorización (TA)" + "Obtener ticket de autorización (TA)" results = self.client.loginCms(in0=str(cms)) ta_xml = results['loginCmsReturn'] #.encode("utf-8") self.xml = ta = SimpleXMLElement(ta_xml) @@ -267,9 +269,9 @@ def LoginCMS(self, cms): self.Sign = str(ta.credentials.sign) self.ExpirationTime = str(ta.header.expirationTime) return ta_xml - + def CallWSAA(self, cms, url="", proxy=None): - "Obtener ticket de autorización (TA) -version retrocompatible-" + "Obtener ticket de autorización (TA) -version retrocompatible-" self.Conectar("", url, proxy) ta_xml = self.LoginCMS(cms) if not ta_xml: @@ -278,16 +280,16 @@ def CallWSAA(self, cms, url="", proxy=None): @inicializar_y_capturar_excepciones def Expirado(self, fecha=None): - "Comprueba la fecha de expiración, devuelve si ha expirado" + "Comprueba la fecha de expiración, devuelve si ha expirado" if not fecha: fecha = self.ObtenerTagXml('expirationTime') now = datetime.datetime.now() d = datetime.datetime.strptime(fecha[:19], '%Y-%m-%dT%H:%M:%S') return now > d - + def Autenticar(self, service, crt, key, wsdl=None, proxy=None, wrapper=None, cacert=None, cache=None, debug=False): - "Método unificado para obtener el ticket de acceso (cacheado)" + "Método unificado para obtener el ticket de acceso (cacheado)" self.LanzarExcepciones = True try: @@ -295,7 +297,7 @@ def Autenticar(self, service, crt, key, wsdl=None, proxy=None, wrapper=None, cac for filename in (crt, key): if not os.access(filename,os.R_OK): raise RuntimeError("Imposible abrir %s\n" % filename) - # creo el nombre para el archivo del TA (según credenciales y ws) + # creo el nombre para el archivo del TA (según credenciales y ws) ta_src = (service + crt + key).encode("utf8") fn = "TA-%s.xml" % hashlib.md5(ta_src).hexdigest() if cache: @@ -305,19 +307,19 @@ def Autenticar(self, service, crt, key, wsdl=None, proxy=None, wrapper=None, cac # leer el ticket de acceso (si fue previamente solicitado) if not os.path.exists(fn) or os.path.getsize(fn) == 0 or \ - os.path.getmtime(fn) + (DEFAULT_TTL) < time.time(): - # ticket de acceso (TA) vencido, crear un nuevo req. (TRA) + os.path.getmtime(fn) + (DEFAULT_TTL) < time.time(): + # ticket de acceso (TA) vencido, crear un nuevo req. (TRA) if DEBUG: print("Creando TRA...") tra = self.CreateTRA(service=service, ttl=DEFAULT_TTL) - # firmarlo criptográficamente + # firmarlo criptográficamente if DEBUG: print("Frimando TRA...") cms = self.SignTRA(tra, crt, key) # concectar con el servicio web: if DEBUG: print("Conectando a WSAA...") ok = self.Conectar(cache, wsdl, proxy, wrapper, cacert) if not ok or self.Excepcion: - raise RuntimeError("Fallo la conexión: %s" % self.Excepcion) - # llamar al método remoto para solicitar el TA + raise RuntimeError("Fallo la conexión: %s" % self.Excepcion) + # llamar al método remoto para solicitar el TA if DEBUG: print("Llamando WSAA...") ta = self.LoginCMS(cms) if not ta: @@ -327,12 +329,12 @@ def Autenticar(self, service, crt, key, wsdl=None, proxy=None, wrapper=None, cac try: open(fn, "w").write(ta) except IOError as e: - self.Excepcion = "Imposible grabar ticket de accesso: %s" % fn + self.Excepcion = "Imposible grabar ticket de accesso: %s" % fn else: # leer el ticket de acceso del archivo en cache if DEBUG: print("Leyendo TA de %s..." % fn) ta = open(fn, "r").read() - # analizar el ticket de acceso y extraer los datos relevantes + # analizar el ticket de acceso y extraer los datos relevantes self.AnalizarXml(xml=ta) self.Token = self.ObtenerTagXml("token") self.Sign = self.ObtenerTagXml("sign") @@ -340,25 +342,25 @@ def Autenticar(self, service, crt, key, wsdl=None, proxy=None, wrapper=None, cac ta = "" if not self.Excepcion: # avoid encoding problem when reporting exceptions to the user: - self.Excepcion = traceback.format_exception_only(sys.exc_info()[0], + self.Excepcion = traceback.format_exception_only(sys.exc_info()[0], sys.exc_info()[1])[0] self.Traceback = "" if DEBUG or debug: raise return ta - - -# busco el directorio de instalación (global para que no cambie si usan otra dll) + + +# busco el directorio de instalación (global para que no cambie si usan otra dll) INSTALL_DIR = WSAA.InstallDir = get_install_dir() if __name__=="__main__": safe_console() - + if '--register' in sys.argv or '--unregister' in sys.argv: import pythoncom - if TYPELIB: + if TYPELIB: if '--register' in sys.argv: tlb = os.path.abspath(os.path.join(INSTALL_DIR, "typelib", "wsaa.tlb")) print("Registering %s" % (tlb,)) @@ -366,10 +368,10 @@ def Autenticar(self, service, crt, key, wsdl=None, proxy=None, wrapper=None, cac pythoncom.RegisterTypeLib(tli, tlb) elif '--unregister' in sys.argv: k = WSAA - pythoncom.UnRegisterTypeLib(k._typelib_guid_, - k._typelib_version_[0], - k._typelib_version_[1], - 0, + pythoncom.UnRegisterTypeLib(k._typelib_guid_, + k._typelib_version_[0], + k._typelib_version_[1], + 0, pythoncom.SYS_WIN32) print("Unregistered typelib") import win32com.server.register @@ -381,21 +383,21 @@ def Autenticar(self, service, crt, key, wsdl=None, proxy=None, wrapper=None, cac # start the server. win32com.server.localserver.serve([WSAA._reg_clsid_]) elif "--crear_pedido_cert" in sys.argv: - # instanciar el helper y revisar los parámetros + # instanciar el helper y revisar los parámetros wsaa = WSAA() args = [arg for arg in sys.argv if not arg.startswith("--")] # obtengo el CUIT y lo normalizo: cuit = len(args)>1 and args[1] or input("Ingrese un CUIT: ") cuit = ''.join([c for c in cuit if c.isdigit()]) nombre = len(args)>2 and args[2] or "PyAfipWs" - # consultar el padrón online de AFIP si no se especificó razón social: + # consultar el padrón online de AFIP si no se especificó razón social: empresa = len(args)>3 and args[3] or "" if not empresa: from .padron import PadronAFIP padron = PadronAFIP() ok = padron.Consultar(cuit) if ok and padron.denominacion: - print("Denominación según AFIP:", padron.denominacion) + print("Denominación según AFIP:", padron.denominacion) empresa = padron.denominacion else: print("CUIT %s no encontrado: %s..." % (cuit, padron.Excepcion)) @@ -416,7 +418,7 @@ def Autenticar(self, service, crt, key, wsdl=None, proxy=None, wrapper=None, cac print("Se crearon los archivos:") print(clave_privada) print(pedido_cert) - # convertir a terminación de linea windows y abrir con bloc de notas + # convertir a terminación de linea windows y abrir con bloc de notas if sys.platform == "win32": txt = open(pedido_cert + ".txt", "wb") for linea in open(pedido_cert, "r"): @@ -424,7 +426,7 @@ def Autenticar(self, service, crt, key, wsdl=None, proxy=None, wrapper=None, cac txt.close() os.startfile(pedido_cert + ".txt") else: - + # Leer argumentos desde la linea de comando (si no viene tomar default) args = [arg for arg in sys.argv if arg.startswith("--")] argv = [arg for arg in sys.argv if not arg.startswith("--")] @@ -435,7 +437,7 @@ def Autenticar(self, service, crt, key, wsdl=None, proxy=None, wrapper=None, cac url = len(argv)>5 and argv[5] or WSAAURL wrapper = len(argv)>6 and argv[6] or None cacert = len(argv)>7 and argv[7] or CACERT - DEBUG = "--debug" in args + DEBUG = "--debug" in args or DEBUG print("Usando CRT=%s KEY=%s URL=%s SERVICE=%s TTL=%s" % (crt, key, url, service, ttl), file=sys.stderr) @@ -444,7 +446,7 @@ def Autenticar(self, service, crt, key, wsdl=None, proxy=None, wrapper=None, cac wsaa.LanzarExcepciones = True print("WSAA Version %s %s" % (WSAA.Version, HOMO), file=sys.stderr) - + if '--proxy' in args: proxy = sys.argv[sys.argv.index("--proxy")+1] print("Usando PROXY:", proxy, file=sys.stderr) @@ -463,10 +465,10 @@ def Autenticar(self, service, crt, key, wsdl=None, proxy=None, wrapper=None, cac if DEBUG: print(wsaa.Traceback, file=sys.stderr) sys.exit("Excepcion: %s" % wsaa.Excepcion) - + else: print(ta) - + if wsaa.Excepcion: print(wsaa.Excepcion, file=sys.stderr)