def gera_arquivo(lista_final, prefixo, dir_arquivo, labels): ''' Recebe lista final e lista de argumentos da execução (nome do arquivo original e colunas a serem manipuladas) e grava novo arquivo .csv ''' try: diretorio_saida, arquivo = _identifica_diretorio(dir_arquivo) diretorio = os.path.join(diretorio_saida, 'files_%s' % arquivo.replace('.csv', '')) if not os.path.exists(diretorio): os.makedirs(diretorio) novo_arquivo = os.path.join(diretorio, '%s_%s' % (prefixo, arquivo)) with open(novo_arquivo, 'w', encoding=_encoding) as saida: if label_key not in labels: labels.insert(0, label_key) dict_writer = csv.DictWriter(saida, fieldnames=labels, delimiter=';') # dict_writer = csv.DictWriter(saida, fieldnames=labels, delimiter=';', dialect='excel') dict_writer.writeheader() for l in lista_final: dict_writer.writerow(l) Logger.debug('prep_files: Arquivo %s_%s criado.' % (prefixo, arquivo)) except (IOError, TypeError, csv.Error) as e: Logger.error('prep_files: Erro ao criar arquivo: %s' % e)
def _identifica_diretorio(caminho_completo): try: arquivo = os.path.basename(caminho_completo) diretorio = os.path.dirname(caminho_completo) Logger.debug("prep_geocode: %s, %s" % (diretorio, arquivo)) return diretorio, arquivo except Exception as e: Logger.error('Erro ao manipular o diretório: %s' % e)
def manipula_arquivo(self, arquivo, delimitador): path_arquivo = prep_files.formata_nome_arquivo(arquivo) if path_arquivo != '' and os.path.exists(path_arquivo): self._arquivo = path_arquivo self._delimitador = delimitador self._dados_arquivo_original, self._colunas_disponiveis = prep_files.lista_colunas_e_dados( self._arquivo, self._delimitador) else: Logger.error('"%s": arquivo não encontrado' % arquivo)
def _processa(self): status, http_code, message = prep_geocode.testa_conexao(self._url) Logger.debug('_processa: %i, %s, %s' % (status, http_code, message)) if status == OK and http_code != 200: Logger.debug('status_operacao_changed: FAIL, %s, %s' % (http_code, message)) self.status_operacao_changed.emit(FAIL, http_code, message) return elif status == FAIL: Logger.debug('status_operacao_changed: FAIL, 0, "%s"' % message) self.status_operacao_changed.emit(FAIL, 0, message) return colunas = self._colunas_escolhidas[:] dados_preparados = prep_files.prepara_dados( self._dados_arquivo_original, colunas) dados_padronizados = prep_files.padroniza_dados(dados_preparados) prep_files.gera_arquivo(self._dados_arquivo_original, 'original', self._arquivo, self._colunas_disponiveis) labels_arquivo_geocode = [ 'KEY', 'COLUNA_PESQ', 'DADO_COMPL_PESQ', 'DADO_PESQ', 'LOCAL_ENCONTRADO', 'SIMILARIDADE', 'LAT', 'LONG' ] fatias = list( prep_geocode.fatia_lista(dados_padronizados, self._registros_por_arquivo)) val = 0 try: for v in fatias: val += 1 dct_pesquisa = { 'prioridade': colunas, 'dados': v, 'geocode_service': self._url } lista_final = prep_geocode.gera_lista_final( dct_pesquisa, self.atualiza_progresso) prep_files.gera_arquivo(lista_final, 'geocode' + str(val), self._arquivo, labels_arquivo_geocode) except ThreadInterrompidaError: Logger.debug('** interrompido pelo usuário') self.status_operacao_changed.emit(INTERRUPTED, 0, '') return self.arquivos_gerados.emit(self.lista_arquivos_gerados()) # Fecha pop up de progresso Logger.debug('status_operacao_changed: OK, 0, ""') self.status_operacao_changed.emit(OK, 0, '')
def testa_conexao(geocode_service): ''' Recebe a descrição de um local e faz a pesquisa do mesmo na API de geocodificação ''' try: consulta = geocode_service + '/?{0}' if geocode_service[ -1] != '/' else geocode_service + '?{0}' parametros = urllib.parse.urlencode({'localidade': '', 'limite': '1'}) result = urllib.request.urlopen(consulta.format(parametros)) http_code = result.code return OK, http_code, result.read() except Exception as e: Logger.error( 'prep_geocode: Erro ao consultar API de geocodificação: %s' % e) return FAIL, 0, '%s' % e
def padroniza_dados(dados): ''' Recebe lista de dicionários com chave e colunas a serem geocodificadas, aplica a função 'troca_verbetes' para cada coluna e retorna nova lista. ''' try: for d in dados: c = d.keys() chaves = list(c) chaves.remove(label_key) for k in chaves: valor = d[k] d[k] = _troca_verbetes(valor) return dados except Exception as e: Logger.error( 'prep_files: Erro ao padronizar dados para geocodificação: %s' % e)
def prepara_dados(dados, campos_selecionados): ''' Recebe lista dos dados do arquivo original e as colunas a serem geocodificadas. Retorna lista de dicionários com a chave e com as colunas recebidas. ''' try: dados_pesquisa = [] for d in dados: dct = {} dct[label_key] = d[label_key] for c in campos_selecionados: if c in list(d.keys()): dct[c] = d[c] dados_pesquisa.append(dct) return dados_pesquisa except Exception as e: Logger.error( 'prep_files: Erro ao preparar dados para geocodificação: %s' % e)
def _avalia_resultado(dado_busca, str_json): ''' Recebe a string resultante da consulta à API e a string consultada. Retorna dicionário com local encontrado, similaridade e coordenadas (latitude e longitude) ''' try: geojson = loads(str_json) features = geojson['features'] if features: dados = [] for f in features: # nome_local = f['properties']['nome'].encode('utf8') nome_local = f['properties']['nome'] local = { 'LOCAL_ENCONTRADO': nome_local, 'COORDENADAS': f['geometry']['coordinates'] } dados.append(local) for d in dados: # similaridade = round(SequenceMatcher(None, d['LOCAL_ENCONTRADO'].ljust(maior).upper(), dado_busca.upper()).ratio(), 5) similaridade = round( SequenceMatcher(None, d['LOCAL_ENCONTRADO'].upper(), dado_busca.upper()).ratio(), 5) d.update({'SIMILARIDADE': similaridade}) dados_ordenados = sorted(dados, key=itemgetter('SIMILARIDADE')) melhor_resultado = dados_ordenados[-1] melhor_resultado.update({ 'LONG': melhor_resultado['COORDENADAS'][0], 'LAT': melhor_resultado['COORDENADAS'][1] }) del melhor_resultado['COORDENADAS'] return melhor_resultado else: resultado = {} return resultado except Exception as e: Logger.error('prep_geocode: Erro ao gerar lista final: %s' % e)
def filtra_dados(self, filtro_coluna, filtro_valor): Logger.debug("%s, %s" % (filtro_coluna, filtro_valor)) if filtro_coluna in self.colunas_disponiveis: dados = self._dados_arquivo_original[:] for linha in dados: if linha[filtro_coluna] != filtro_valor: self._dados_arquivo_original.remove(linha) if len(self._dados_arquivo_original) == 0: self._dados_arquivo_original = dados[:] self._filtro_ignorado = True else: self._filtro_ignorado = False else: self._filtro_ignorado = False Logger.debug( 'Filtro: %i registro(s) para {%s: %s}' % (len(self._dados_arquivo_original), filtro_coluna, filtro_valor))
def _consulta_api(local, geocode_service): ''' Recebe a descrição de um local e faz a pesquisa do mesmo na API de geocodificação ''' try: consulta = geocode_service + '/?{0}' if geocode_service[ -1] != '/' else geocode_service + '?{0}' parametros = urllib.parse.urlencode({ 'localidade': local, 'limite': '33' }) url = consulta.format(parametros) Logger.debug('prep_geocode: %s' % url) result = urllib.request.urlopen(url) result_set = result.read() str_json = result_set.decode('utf-8') return str_json except Exception as e: Logger.error( 'prep_geocode: Erro ao consultar API de geocodificação: %s' % e)
def lista_colunas_e_dados(arquivo_original, delimitador): ''' Abre arquivo csv original e adiciona uma chave única a cada registro. Retorna lista de registros (cada registro é um dicionário) e lista com elementos do cabeçalho do arquivo original. ''' try: with open(arquivo_original, encoding=_encoding) as arquivo: dict_reader = csv.DictReader(arquivo, delimiter=str(delimitador)) # dict_reader = csv.DictReader(arquivo, delimiter=str(delimitador), dialect='excel') headers_arquivo = dict_reader.fieldnames linhas = [] for l in dict_reader: if None not in l.keys() and None not in l.values(): key = uuid.uuid4() l[label_key] = str(key) linhas.append(l) Logger.debug('prep_files: linhas = %i.' % len(linhas)) return linhas, headers_arquivo except (IOError, TypeError, csv.Error) as e: Logger.error('prep_files: Erro ao manipular arquivo original: %s' % e) raise e except Exception as e: Logger.error('prep_files: Verifique o arquivo csv de entrada: %s' % e) raise e
def _consulta_geocode(string_pesquisa, coluna, geocode_service): ''' Recebe a string a ser geocodificada e sua coluna correspondente. Consulta a API de geocodificação e avalia o resultado, retornando o dicionário que será inserido na lista final. ''' try: dct = {} encontrou = False str_pesquisa = _remove_acentos(string_pesquisa) dado_reduzido = _reduz_dado(str_pesquisa) for d in dado_reduzido[::-1]: str_json = _consulta_api(d, geocode_service) resultado_consulta = _avalia_resultado(d, str_json) if resultado_consulta: dct.update({ 'COLUNA_PESQ': coluna, 'DADO_COMPL_PESQ': dado_reduzido[-1], 'DADO_PESQ': d }) dct.update(resultado_consulta) encontrou = True break if encontrou == False: dct.update({ 'COLUNA_PESQ': 'ALL', 'DADO_COMPL_PESQ': 'ALL', 'DADO_PESQ': 'ALL', 'LONG': 'NOT FOUND', 'LAT': 'NOT FOUND', 'LOCAL_ENCONTRADO': 'NOT FOUND', 'SIMILARIDADE': 0.0 }) return dct except Exception as e: Logger.error('prep_geocode: Erro ao montar novo registro: %s' % e)
def lista_arquivos_gerados(self): try: diretorio, arquivo_saida = prep_files._identifica_diretorio( self._arquivo) diretorio_saida = os.path.join( diretorio, 'files_%s' % arquivo_saida.replace('.csv', '')) if os.path.exists(diretorio_saida): lista_arquivos = os.listdir(diretorio_saida) lista_arquivos_ordenada = sorted(lista_arquivos) text_label = 'Arquivos gerados:\n\t' + diretorio_saida for i in lista_arquivos_ordenada: text_label += ('\n\t\t' + i) else: text_label = 'Houve um problema ao acessar o diretório de saída. Verifique se novos arquivos foram gerados no diretório do arquivo csv original.' except Exception as e: Logger.error('%s' % e) raise e text_label = 'Verifique se novos arquivos foram gerados no diretório do arquivo csv original.' Logger.debug(text_label) return text_label
def gera_lista_final(dct_pesquisa, atualiza_progresso=lambda: True): ''' Recebe dicionário com dados do arquivo CSV (lista de dicionários com a chave e com as colunas a serem geocodificadas) e lista de colunas na ordem em que deve ser feita a geocodificação. Monta a lista final que será gravada em novo arquivo csv aplicando a função _consulta_geocode para cada registro. ''' try: dados = dct_pesquisa['dados'] colunas = dct_pesquisa['prioridade'] geocode_service = dct_pesquisa['geocode_service'] dados_finais = [] total = len(dados) ct = 1 for d in dados: lb_key = 'KEY' key = d[lb_key] dct = {lb_key: key} for c in colunas: if c in list(d.keys()): result_set = _consulta_geocode(d[c], c, geocode_service) dct.update(result_set) if dct['SIMILARIDADE'] != 0.0: break dados_finais.append(dct) # Atualiza contador de progresso if not atualiza_progresso(ct): raise ThreadInterrompidaError() else: Logger.debug('prep_geocode: {0} de {1}'.format(ct, total)) ct += 1 return dados_finais except ThreadInterrompidaError as e: Logger.error( 'prep_geocode: interrupção por usuário foi detectada: %s' % e) raise e except Exception as e: Logger.error('prep_geocode: Erro ao gerar lista final: %s' % e)
def filtra_coluna(self, coluna): Logger.debug('coluna = "%s"' % coluna) self._valores_filtrados = [] self._coluna_filtrada = coluna if coluna in self._colunas_disponiveis: conj_valores = set() for dado in self._dados_arquivo_original: conj_valores.add(dado[coluna]) if len(conj_valores) >= 100: break valores_unicos = list(conj_valores) muitos_valores = '%d valores possíveis.' % len(valores_unicos) self._valores_filtrados.extend(valores_unicos if len( valores_unicos) <= 100 else [muitos_valores]) Logger.debug('%d valores encontrados para a coluna "%s".' % (len(valores_unicos), coluna)) else: self._valores_filtrados = [] Logger.debug('0 valor encontrados para a coluna "%s".' % coluna) self.valores_filtrados_changed.emit(self._valores_filtrados)
def adiciona_coluna(self, coluna): Logger.debug("adiciona '%s'" % coluna) self._colunas_escolhidas.append(coluna)
def operacao_terminada(self, status, http_code, mensagem): """Faz limpeza de thread depois que ela termina.""" Logger.debug('operacao_terminada: %i, %s, "%s"' % (status, http_code, mensagem)) self._thread_operacao.join()
def cancela_operacao(self): """Deve ser chamado de fora de thread.""" Logger.debug('cancela_operacao() chamado.') self._thread_operacao.stop() self._thread_operacao.join()
def remove_coluna(self, coluna): Logger.debug("removendo '%s'" % coluna) self._colunas_escolhidas.remove(coluna)
def define_quantidade_registros(self, qtd_registros: int): tam = len(self._dados_arquivo_original) qtd_registros = int(qtd_registros) Logger.debug('Contador de %s elementos selecionado' % qtd_registros) self._registros_por_arquivo = tam if (qtd_registros == -1) else qtd_registros
import uuid from config import API_USER_NAME, API_USER_PASS, LOG_LEVEL from flask import request, Response from logger import StdoutLogger basic_logger = StdoutLogger(LOG_LEVEL) class LoggingMiddleware(object): def __init__(self, logger: StdoutLogger, api_method, api_path, **kwargs): self.logger = logger self.extra = { 'api_id': uuid.uuid4(), 'api_method': api_method, 'api_path': api_path } if kwargs: self.extra.update(kwargs) def debug(self, msg): self.logger.debug(msg, extra=self.extra) def info(self, msg): self.logger.info(msg, extra=self.extra) def warning(self, msg): self.logger.warning(msg, extra=self.extra) def error(self, msg): self.logger.error(msg, extra=self.extra)
def inicia_operacao(self): """Deve ser chamado de fora de thread.""" Logger.debug('inicia_operacao() chamado.') self.status_operacao_changed.connect(self.operacao_terminada) self._thread_operacao = ThreadCancelavel(target=self._processa) self._thread_operacao.start()