class DatabaseDumpCommand(object): def __init__(self): self._logger = Inject('logger') self._connection = Inject('database connection') self._config = Inject('app configuration') self._git = Inject('fdc git wrapper') def database_parser_created_handler(self, db_parser): db_parser.add_parser("dump", help="Dump database.db to database.dump").set_defaults( event='database_dump_command') # TODO Flag to skip specific git steps (or all of then) def database_dump_command_handler(self, args): with open(self._config['fdc.dump_full_path'], 'w') as file: for line in self._connection.iterdump(): file.write('%s\n' % line) self._git.add(self._config['fdc.dump_full_path']) self._git.commit('--message', 'Automatic database dump') self._git.push() return 'ok' def database_dump_command_ok_handler(self): print('Database contents dumped')
class AbstractDao(object): def __init__(self, entity_class, metadata): self._connection = Inject('database connection') self._logger = Inject('logger') self._entity_class = entity_class self._metadata = metadata def new_builder(self, kind): if kind == 'SELECT': return SelectBuilder(self._metadata) else: return None def get_single(self, where=None, *values): list = self.list(where, *values) return list[0] if len(list) > 0 else None def list(self, where=None, *values): cursor = self._create_select_cursor((), where, *values) entity_list = self._load_cursor(cursor) cursor.close() return entity_list def exists(self, where, *values): cursor = self._create_select_cursor(('count(*)', ), where, *values) count = cursor.fetchone()[0] return count > 0 def _create_select_cursor(self, fields=(), where=None, *values): builder = self.new_builder('SELECT') builder.where = where builder.fields(*fields) if len(fields) > 0 else None query = builder.build() return self._connection.execute(query, values) def _load_cursor(self, cursor): entity_list = [] for row in cursor: entity = self._load_row(row) entity_list.append(entity) return entity_list def _load_row(self, row): entity_values = {} for i, field_name in enumerate(self._metadata.fields): value = row[i] entity_values[field_name] = value return SimpleNamespace(**entity_values) def insert(self, entity): builder = InsertBuilder(self._metadata) sql = builder.build() # TODO Essa recuperação de valores é responsabilidade do InsertBuilder values = self._get_field_values(entity) self._logger.debug('Running "{}" with values "{}"', sql, values) self._connection.execute(sql, values) self._connection.commit() def _get_field_values(self, entity): t = () for field in self._metadata.fields: if field == 'rowid': continue value = getattr(entity, field, None) if hasattr(value, 'rowid'): t += value.rowid, else: t += value, return t
class ImportCSVCommand(object): def __init__(self): self._connection = Inject('database connection') self._conta_dao = Inject('conta dao') self._produto_dao = Inject('produto dao') self._fornecedor_dao = Inject('fornecedor dao') self._lancamento_dao = Inject('lancamento dao') self._unknown_contas = None self._unknown_produtos = None self._unknown_fornecedores = None @staticmethod def import_parser_created_handler(import_parser): import_csv_parser = import_parser.add_parser('csv', help='Importa um arquivo .csv') import_csv_parser.set_defaults(event='import_csv_command') import_csv_parser.add_argument('-c', '--confirm', action='store_true', help='Confirma a importação, caso contrário apenas mostra em tela o que será importado') import_csv_parser.add_argument("filename", help="Nome do arquivo que será importado") def import_csv_command_handler(self, args): source = open(args.filename, 'r') self._unknown_contas = set() self._unknown_produtos = set() self._unknown_fornecedores = set() ok = True lancamentos = [] try: for self._i, self._line in enumerate(source): fields_array = self._get_fields_array() if not self._validate(fields_array, lambda f: len(f) >= 4, None, 'every line must have at least 4 fields'): continue fields = self._get_fields(fields_array) ok &= self._validate_fields(*fields) if ok: lancamento = self._make_lancamento(*fields) if args.confirm: self._lancamento_dao.insert(lancamento) else: lancamentos.append(lancamento) except Exception: self._connection.rollback() raise if ok: if args.confirm: self._connection.commit() return 'imported', {'filename': args.filename} else: return 'simulated', {'filename': args.filename, 'lancamentos': lancamentos} else: if args.confirm: self._connection.rollback() return 'error', {'filename': args.filename, 'unknown_contas': self._unknown_contas, 'unknown_produtos': self._unknown_produtos, 'unknown_fornecedores': self._unknown_fornecedores} def _get_fields_array(self): if self._line.endswith('\n'): self._line = self._line[:-1] fields = self._line.split(';') return fields def _validate(self, data, validator, validation_fail_callback, message): if not validator(data): print('[ERROR] Line {}: {}'.format(self._i + 1, message.format(data))) if validation_fail_callback: validation_fail_callback() return False return True def _get_fields(self, fields_array): data = fields_array[0] origem = fields_array[1] destino = fields_array[2] valor = fields_array[3] observacoes = self._get(fields_array, 4) produto = self._get(fields_array, 5) quantidade = self._get(fields_array, 6) fornecedor = self._get(fields_array, 7) return data, origem, destino, valor, observacoes, produto, quantidade, fornecedor def _validate_fields(self, data, origem, destino, valor, observacoes, produto, quantidade, fornecedor): ok = self._validate(data, self._data_ok, None, 'date "{{}}" is not in format "{}"'.format(_CSV_DATE_FORMAT)) ok &= self._validate(origem, self._conta_dao.by_name, lambda: self._unknown_contas.add(origem), 'origem "{}" doesnt exists') ok &= self._validate(destino, self._conta_dao.by_name, lambda: self._unknown_contas.add(destino), 'destino "{}" doesnt exists') ok &= self._validate(valor, self._valor_ok, None, 'valor "{}" is not in valid format') if produto: ok &= self._validate(produto, self._produto_dao.by_name, lambda: self._unknown_produtos.add(produto), 'produto "{}" not found') if quantidade: ok &= self._validate(quantidade, self._is_float, None, 'quantidade "{}" is not a float value') if fornecedor: ok &= self._validate(fornecedor, self._fornecedor_dao.by_name, lambda: self._unknown_fornecedores.add(fornecedor), 'fornecedor "{}" not found') return ok def _make_lancamento(self, data, origem, destino, valor, observacoes, produto, quantidade, fornecedor): lancamento = Lancamento() # TODO Extract a formatter from the following two lines lancamento.data = datetime.strftime(datetime.strptime(data, _CSV_DATE_FORMAT), _SQLITE_DATE_FORMAT) lancamento.valor = int(valor.replace('.', '')) lancamento.origem = self._conta_dao.by_name(origem) lancamento.destino = self._conta_dao.by_name(destino) lancamento.observacoes = observacoes lancamento.produto = self._produto_dao.by_name(produto) if produto else None lancamento.quantidade = quantidade lancamento.fornecedor = self._fornecedor_dao.by_name(fornecedor) if fornecedor else None return lancamento @staticmethod def _get(fields, index, default=None): return fields[index] if len(fields) >= index + 1 else default @staticmethod def _data_ok(value): try: datetime.strptime(value, _CSV_DATE_FORMAT) return True except ValueError: return False @staticmethod def _valor_ok(valor): try: # TODO Internacionalize this to work with , for decimal point! Decimal(valor) return True except InvalidOperation: return False @staticmethod def _is_float(value): try: float(value) return True except ValueError: return False @staticmethod def import_csv_command_imported_handler(filename): print('Arquivo "{}" importado com sucesso!'.format(filename)) @staticmethod def import_csv_command_simulated_handler(filename, lancamentos): print('Simulated import for file "{}" (use --confirm to actually import), would create lancamentos:'.format( filename)) for lancamento in lancamentos: print(ImportCSVCommand._make_lancamento_string(lancamento)) @staticmethod def _make_lancamento_string(lancamento): values = list() values.append(str(lancamento.data)) values.append(str(lancamento.origem)) values.append(str(lancamento.destino)) values.append(str(lancamento.valor)) values.append(str(lancamento.observacoes)) if lancamento.observacoes else None values.append(str(lancamento.produto)) if lancamento.produto else None values.append(str(lancamento.quantidade)) if lancamento.quantidade else None values.append(str(lancamento.fornecedor)) if lancamento.fornecedor else None return '; '.join(values) @staticmethod def import_csv_command_error_handler(filename, unknown_contas, unknown_produtos, unknown_fornecedores): print('Erros importando arquivo "{}"!'.format(filename)) print() ImportCSVCommand._print_array('Contas desconhecidas:', unknown_contas) ImportCSVCommand._print_array('Produtos desconhecidos:', unknown_produtos) ImportCSVCommand._print_array('Fornecedores desconhecidos:', unknown_fornecedores) @staticmethod def _print_array(message, array): if len(array) == 0: return print(message) for conta in array: print(conta) print()