Browse Source

Implementando a fonte de dados para manter o repositorio dos objetos em memoria e para permitir a implementacao de persistencia para o desenvolvedor que for utilizar a biblioteca.

tags/0.1
Marinho Brandão 16 years ago
parent
commit
916ab9e17d
  1. 1
      pynfe/entidades/__init__.py
  2. 8
      pynfe/entidades/base.py
  3. 3
      pynfe/entidades/emitente.py
  4. 126
      pynfe/entidades/fontes_dados.py
  5. 6
      pynfe/excecoes.py
  6. 54
      pynfe/processamento/serializacao.py
  7. 40
      tests/01-basico.txt
  8. 102
      tests/02-modelo-00-definicoes-gerais.txt
  9. 24
      tests/03-processamento-00-definicoes-gerais.txt
  10. 16
      tests/03-processamento-01-serializacao-xml.txt

1
pynfe/entidades/__init__.py

@ -4,4 +4,5 @@ from cliente import Cliente
from transportadora import Transportadora
from notafiscal import NotaFiscal
from lotes import LoteNotaFiscal
from fontes_dados import FonteDados

8
pynfe/entidades/base.py

@ -1,12 +1,18 @@
# -*- coding: utf-8 -*-
class Entidade(object):
_fonte_dados = None
def __init__(self, **kwargs):
# Codigo para dinamizar a criacao de instancias de entidade,
# aplicando os valores dos atributos na instanciacao
for k, v in kwargs:
for k, v in kwargs.items():
setattr(self, k, v)
# Adiciona o objeto à fonte de dados informada
if self._fonte_dados:
self._fonte_dados.adicionar_objeto(self)
class Lote(object):
pass

3
pynfe/entidades/emitente.py

@ -54,3 +54,6 @@ class Emitente(Entidade):
# Logotipo
logotipo = None
def __repr__(self):
return '<Emitente cnpj="%s" razao_social="%s"/>'%(self.cnpj, self.razao_social)

126
pynfe/entidades/fontes_dados.py

@ -0,0 +1,126 @@
# -*- coding: utf-8 -*-
from pynfe.excecoes import NenhumObjetoEncontrado, MuitosObjetosEncontrados
class FonteDados(object):
u"""Classe responsável por ser o repositório dos objetos em memória e que
pode ser extendida para persistir esses objetos. Também tem a função de
memorizar os objetos redundantes como um e assim otimizar o desempenho."""
_objetos = None
def __init__(self, objetos=None):
# Inicializa variável que armazena os objetos contidos na Fonte de Dados
if objetos:
self._objetos = objetos
else:
self._objetos = []
def carregar_objetos(self, **kwargs):
u"""Método responsavel por retornar os objetos que casem com os atributos
informados no argumento **kwargs (argumentos nomeados).
Um argumento especial é o '_classe', que representa a classe da entidade
desejada.
FIXME: Este algoritimo pode ser melhorado pra fazer pesquisas melhores,
mas por enquanto vamos nos focar no processo em geral para depois nos
preocupar com otimizações e desempenho."""
# Função de filtro
def filtrar(obj):
ret = True
for k,v in kwargs.items():
# Filtra pela classe e pelos atributos
ret = (k == '_classe' and isinstance(obj, v)) or\
(k != '_classe' and getattr(obj, k, None) == v)
if not ret:
break
return ret
# Filtra a lista de objetos
lista = filter(filtrar, self._objetos)
return lista
def adicionar_objeto(self, _objeto):
u"""Método responsável por adicionar o(s) objeto(s) informado(s) ao
repositorio de objetos da fonte de dados."""
from base import Entidade
# Adiciona _objeto como objeto
if isinstance(_objeto, Entidade):
self._objetos.append(_objeto)
# Adiciona _objeto como lista
elif isinstance(_objeto, (list, tuple)):
self._objetos += _objeto
else:
raise Exception('Objeto informado e invalido!')
def remover_objeto(self, _objeto=None, **kwargs):
u"""Método responsavel por remover os objetos que casem com os atributos
informados no argumento **kwargs (argumentos nomeados).
Um argumento especial é o '_classe', que representa a classe da entidade
desejada.
Outro argumetno especial é o '_objeto', que representa o objeto a ser
removido. Caso o argumento _objeto seja uma lista de objetos, eles serão
removidos também."""
from base import Entidade
lista = None
# Remove objetos
if not _objeto:
lista = self.carregar_objetos(**kwargs)
# Remove _objeto como objeto
elif isinstance(_objeto, Entidade):
lista = [_objeto]
# Remove _objeto como objeto
elif isinstance(_objeto, (list, tuple)):
lista = _objeto
else:
raise Exception('Objeto informado e invalido!')
# Efetiva a remoção
for obj in lista:
self._objetos.remove(obj)
def obter_objeto(self, **kwargs):
u"""Faz a ponte para o método 'carregar_objetos' mas obriga o retorno de
apenas um objeto, levantando exceção se nenhum for encontrado ou se forem
encontrados mais de um."""
lista = self.carregar_objetos(**kwargs)
if len(lista) == 0:
raise NenhumObjetoEncontrado('Nenhum objeto foi encontrado!')
elif len(lista) > 1:
raise MuitosObjetosEncontrados('Muitos objetos foram encontrados!')
return lista[0]
def obter_lista(self, **kwargs):
u"""Método de proxy, que somente repassa a chamada ao metodo 'carregar_objetos'"""
return self.carregar_objetos(**kwargs)
def contar_objetos(self, **kwargs):
u"""Método que repassa a chamada ao metodo 'carregar_objetos' mas retorna
somente a quantidade de objetos encontrados."""
if kwargs:
return len(self.carregar_objetos(**kwargs))
else:
return len(self._objetos)

6
pynfe/excecoes.py

@ -0,0 +1,6 @@
class NenhumObjetoEncontrado(Exception):
pass
class MuitosObjetosEncontrados(Exception):
pass

54
pynfe/processamento/interfaces.py → pynfe/processamento/serializacao.py

@ -19,7 +19,7 @@ except ImportError:
except ImportError:
raise Exception('Falhou ao importar lxml/ElementTree')
class Interface(object):
class Serializacao(object):
"""Classe abstrata responsavel por fornecer as funcionalidades basicas para
exportacao e importacao de Notas Fiscais eletronicas para formatos serializados
de arquivos. Como XML, JSON, binario, etc.
@ -29,7 +29,7 @@ class Interface(object):
lista_de_nfs = None
def __new__(cls, *args, **kwargs):
if cls == Interface:
if cls == Serializacao:
raise Exception('Esta classe nao pode ser instanciada diretamente!')
else:
return cls(*args, **kwargs)
@ -49,6 +49,52 @@ class Interface(object):
raise Exception('Metodo nao implementado')
class InterfaceXML(Interface):
pass
class SerializacaoXML(Serializacao):
def exportar(self, objetos, destino):
"""Gera o(s) arquivo(s) de Nofa Fiscal eletronica no padrao oficial da SEFAZ
e Receita Federal, para ser(em) enviado(s) para o webservice ou para ser(em)
armazenado(s) em cache local."""
saida = []
# Dados do emitente
saida.append(self._serializar_emitente(objetos))
# Certificado Digital? XXX
# Clientes
saida.append(self._serializar_clientes(objetos))
# Transportadoras
saida.append(self._serializar_transportadoras(objetos))
# Produtos
saida.append(self._serializar_produtos(objetos))
# Lote de Notas Fiscais
saida.append(self._serializar_notas_fiscais(objetos))
# FIXME
return '\n'.join(saida)
def importar(self, objetos, origem):
"""Cria as instancias do PyNFe a partir de arquivos XML no formato padrao da
SEFAZ e Receita Federal."""
raise Exception('Metodo nao implementado')
def _serializar_emitente(self, objetos):
return ''
def _serializar_clientes(self, objetos):
return ''
def _serializar_transportadoras(self, objetos):
return ''
def _serializar_produtos(self, objetos):
return ''
def _serializar_notas_fiscais(self, objetos):
return ''

40
tests/01-basico.txt

@ -1,7 +1,11 @@
TESTES BASICOS
==============
>>> import sets
>>> try:
... set
... except:
... from sets import Set as set
<type 'set'>
A biblioteca deve fornecer uma colecao de utilitarios para consumir
o webservice da NF-e.
@ -16,9 +20,9 @@ modelo:
| MODELO DE ENTIDADES |
---------------------------------------------------------------------
| |
| ------------ |
| | Entidade | |
| ------------ |
| ------------ -------------- |
| | Entidade |-------<>| FonteDados | |
| ------------ -------------- |
| A |
| | |
| ----especializacao-------------------------- |
@ -41,16 +45,16 @@ modelo:
| PROCESSAMENTO |
--------------------------------------------------------------------------
| |
| ---------------- -------------- -------------------------------- |
| | InterfaceXML | | Assinatura | | Comunicacao | |
| ---------------- -------------- -------------------------------- |
| | exportar() | | assinar() | | transmitir() | |
| | importar() | -------------- | cancelar() | |
| ---------------- | situacao_nfe() | |
| ------------- | status_servico() | |
| -------------- | Validacao | | consultar_cadastro() | |
| | DANFE | ------------- | inutilizar_faixa_numeracao() | |
| -------------- | validar() | -------------------------------- |
| ------------------- -------------- -------------------------------- |
| | SerializacaoXML | | Assinatura | | Comunicacao | |
| ------------------- -------------- -------------------------------- |
| | exportar() | | assinar() | | transmitir() | |
| | importar() | -------------- | cancelar() | |
| ------------------- | situacao_nfe() | |
| ------------- | status_servico() | |
| -------------- | Validacao | | consultar_cadastro() | |
| | DANFE | ------------- | inutilizar_faixa_numeracao() | |
| -------------- | validar() | -------------------------------- |
| | imprimir() | ------------- |
| -------------- |
| |
@ -62,23 +66,23 @@ Os pacotes da biblioteca sao:
biblioteca, incluindo flags e funcoes genericas)
>>> from pynfe import utils
>>> sets.Set([attr for attr in dir(utils) if not attr.startswith('__')]) == sets.Set(['flags'])
>>> set([attr for attr in dir(utils) if not attr.startswith('__')]) == set(['flags'])
True
- entidades (contem todas as entidades da biblioteca)
>>> from pynfe import entidades
>>> sets.Set([attr for attr in dir(entidades) if not attr.startswith('__')]) == sets.Set([
>>> set([attr for attr in dir(entidades) if not attr.startswith('__')]) == set([
... 'Cliente', 'Emitente', 'LoteNotaFiscal', 'NotaFiscal', 'Produto',
... 'Transportadora', 'base', 'cliente', 'emitente', 'lotes', 'notafiscal',
... 'produto', 'transportadora'])
... 'produto', 'transportadora', 'fontes_dados', 'FonteDados'])
True
- processamento (contem todas as funcionalidades de processamento da
biblioteca
>>> from pynfe import processamento
>>> sets.Set([attr for attr in dir(processamento) if not attr.startswith('__')]) == sets.Set([
>>> set([attr for attr in dir(processamento) if not attr.startswith('__')]) == set([
... 'Assinatura', 'Comunicacao', 'DANFE', 'InterfaceXML', 'Validacao',
... 'assinatura', 'comunicacao', 'danfe', 'interfaces', 'validacao'])
True

102
tests/02-modelo-00-definicoes-gerais.txt

@ -5,3 +5,105 @@ Modelo das entidades e como elas se relacionam.
Nenhum dos campos deve permitir acentos e/ou cedilhas.
Todas as entidades devem referenciar uma Fonte de Dados, de forma a evitar
redundancia de dados (com o objetivo de melhorar o desempenho e possibilitar
o uso de cache de persistencia de dados serializados).
>>> from pynfe.entidades import FonteDados
>>> fonte_dados = FonteDados()
Nao eh da funcao do PyNFe efetuar a persistencia dos objetos, mas a classe
FonteDados deve facilitar esse processo ao software que for implementa-la.
>>> hasattr(FonteDados, 'carregar_objetos')
True
>>> from pynfe.entidades import Emitente
Populando fonte de dados com objetos
>>> bool(Emitente(cnpj='12.345.678/0001-90', _fonte_dados=fonte_dados))
True
>>> bool(Emitente(razao_social='JKL Calcados Ltda.', _fonte_dados=fonte_dados))
True
>>> bool(Emitente(razao_social='JKL Calcados Ltda.', _fonte_dados=fonte_dados))
True
O metodo carregar_objetos pode ser sobrecarregado para alterar o carregamento de
objetos da memoria para forcar mocking ou para carregar de camada persistente.
Ele sempre retorna uma lista de objetos, independente se vazia ou com qualquer
quantidade de objetos.
>>> def carregar_objetos(self, **kwargs):
... if kwargs.get('cnpj', None) == 'xxx':
... return ['encontrado!']
...
... return self.antigo_carregar_objetos(**kwargs)
Substituindo metodo 'carregar_objetos'
>>> fonte_dados.antigo_carregar_objetos = fonte_dados.carregar_objetos
>>> import new
>>> fonte_dados.carregar_objetos = new.instancemethod(carregar_objetos, fonte_dados, FonteDados)
>>> fonte_dados.obter_objeto(cnpj='xxx')
'encontrado!'
O metodo 'obter_objeto' retorna um unico objeto que atende aos atributos informados.
O argumento especial '_classe' eh utilizado para indicar que a classe da entidade eh
a atribuida a esse argumento.
>>> emitente = fonte_dados.obter_objeto(cnpj='12.345.678/0001-90', _classe=Emitente)
>>> isinstance(emitente, Emitente)
True
Caso nenhum objeto seja encontrado, uma excecao deve ser levantada.
>>> from pynfe.excecoes import NenhumObjetoEncontrado
>>> try:
... fonte_dados.obter_objeto(cnpj='98.765.432/0001-10', _classe=Emitente)
... except NenhumObjetoEncontrado, e:
... print e.message
Nenhum objeto foi encontrado!
Caso mais de um objeto sejam encontrados, uma excecao deve ser levantada tambem.
>>> from pynfe.excecoes import MuitosObjetosEncontrados
>>> try:
... fonte_dados.obter_objeto(razao_social='JKL Calcados Ltda.', _classe=Emitente)
... except MuitosObjetosEncontrados, e:
... print e.message
Muitos objetos foram encontrados!
O metodo 'obter_lista' retorna uma lista de objetos, mesmo que vazia.
>>> len(fonte_dados.obter_lista(razao_social='JKL Calcados Ltda.'))
2
>>> len(fonte_dados.obter_lista(razao_social='Inexistente S/A'))
0
Qualquer entidade que for instanciada deve ser acrescentada automaticamente a lista de
objetos da Fonte de Dados, atraves de um metodo especifico pra isso
>>> conta_antes = fonte_dados.contar_objetos()
>>> emitente = Emitente(razao_social='Emitente Novo', _fonte_dados=fonte_dados)
>>> fonte_dados.contar_objetos() - conta_antes
1
O contador de objetos retorna a quantidade de instancias que casem com os argumentos passados
>>> fonte_dados.contar_objetos(_classe=Emitente, razao_social='Emitente Novo')
1
Permitir tambem remover objetos (que por padrao remove apenas da lista da memoria e nao
eh persistente.
>>> fonte_dados.remover_objeto(emitente)

24
tests/03-processamento.txt → tests/03-processamento-00-definicoes-gerais.txt

@ -1,16 +1,6 @@
PROCESSAMENTO
=============
Validar NF-e
------------
- Efetuar validacoes dos XSD no(s) XML(s) gerado(s)
Assinar NF-e
------------
- Na hora de assinar, selecionar um Certificado Digital
Gerar arquivos XML
------------------
@ -24,6 +14,16 @@ Gerar arquivos XML
própria tag <Signature>, conforme exemplo abaixo.
- Cada documento XML deverá ter o seu namespace individual em seu elemento raiz.
Validar NF-e
------------
- Efetuar validacoes dos XSD no(s) XML(s) gerado(s)
Assinar NF-e
------------
- Na hora de assinar, selecionar um Certificado Digital
Transmitir NF-e (ou lote de NF-e`s)
-----------------------------------
@ -49,8 +49,8 @@ Consulta da situação atual da NF-e Sincrona
Consulta do status do serviço Sincrona
Consulta cadastro Sincrona
Imprimir NF-e
-------------
Imprimir DANF-e
---------------
- Geracao baseada no Geraldo Reports
- Gerar codigo de barras (padrao CODE-128C)

16
tests/03-processamento-01-serializacao-xml.txt

@ -0,0 +1,16 @@
PROCESSAMENTO - SERIALIZACAO PARA XML
=====================================
Gerar arquivos XML
------------------
- Gera os arquivos XML a partir dos dados das instancias da NF-e
- Quando gerados me lote, apenas o primeiro arquivo deve ter o cabecalho
padrao do XML 1.0
- <?xml version="1.0" encoding="UTF-8"?>
- Namespace
- <NFe xmlns=”http://www.portalfiscal.inf.br/nfe” >
- A declaração do namespace da assinatura digital deverá ser realizada na
própria tag <Signature>, conforme exemplo abaixo.
- Cada documento XML deverá ter o seu namespace individual em seu elemento raiz.
Loading…
Cancel
Save