satcfe/resposta/enviardadosvenda.py
class RespostaEnviarDadosVenda(RespostaSAT):
    """Lida com as respostas da função ``EnviarDadosVenda`` (veja o método
    :meth:`~satcfe.base.FuncoesSAT.enviar_dados_venda`). Os atributos
    esperados em caso de sucesso, são:

    .. sourcecode:: text

        numeroSessao (int)
        EEEEE (text)
        CCCC (text)
        mensagem (text)
        cod (text)
        mensagemSEFAZ (text)
        arquivoCFeSAT (text)
        timeStamp (datetime.datetime)
        chaveConsulta (text)
        valorTotalCFe (decimal.Decimal)
        CPFCNPJValue (text)
        assinaturaQRCODE (text)

    Em caso de falha, são esperados apenas os atributos:

    .. sourcecode:: text

        numeroSessao (int)
        EEEEE (text)
        CCCC (text)
        mensagem (text)
        cod (text)
        mensagemSEFAZ (text)

    Finalmente, como último recurso, a resposta poderá incluir apenas os
    atributos padrão, conforme descrito na constante
    :attr:`~satcfe.resposta.padrao.RespostaSAT.CAMPOS`.

    .. note::

        Aqui, ``text`` diz respeito à um objeto ``unicode`` (Python 2) ou
        ``str`` (Python 3). Veja ``builtins.str`` da biblioteca ``future``.

    """

    def xml(self):
        """Retorna o XML do CF-e-SAT decodificado de Base64.

        :rtype: str
        """
        if self._sucesso():
            return base64_to_str(self.arquivoCFeSAT)
        else:
            raise ExcecaoRespostaSAT(self)

    def qrcode(self):
        """Resulta nos dados que compõem o QRCode.

        :rtype: str
        """
        if self._sucesso():
            tree = ET.parse(StringIO(self.xml()))
            return dados_qrcode(tree)
        else:
            raise ExcecaoRespostaSAT(self)

    def _sucesso(self):
        return self.EEEEE == EMITIDO_COM_SUCESSO

    @staticmethod
    def analisar(retorno):
        """Constrói uma :class:`RespostaEnviarDadosVenda` a partir do
        retorno informado.

        :param str retorno: Retorno da função ``EnviarDadosVenda``.
        """
        resposta = analisar_retorno(
                retorno,
                funcao='EnviarDadosVenda',
                classe_resposta=RespostaEnviarDadosVenda,
                campos=(
                        ('numeroSessao', int),
                        ('EEEEE', text),
                        ('CCCC', text),
                        ('mensagem', text),
                        ('cod', text),
                        ('mensagemSEFAZ', text),
                        ('arquivoCFeSAT', text),
                        ('timeStamp', as_datetime),
                        ('chaveConsulta', text),
                        ('valorTotalCFe', Decimal),
                        ('CPFCNPJValue', text),
                        ('assinaturaQRCODE', text),
                    ),
                campos_alternativos=[
                        # se a venda falhar apenas os primeiros seis campos
                        # especificados na ER deverão ser retornados...
                        (
                                ('numeroSessao', int),
                                ('EEEEE', text),
                                ('CCCC', text),
                                ('mensagem', text),
                                ('cod', text),
                                ('mensagemSEFAZ', text),
                        ),
                        # por via das dúvidas, considera o padrão de campos,
                        # caso não haja nenhuma coincidência...
                        RespostaSAT.CAMPOS,
                    ]
            )
        if resposta.EEEEE not in (EMITIDO_COM_SUCESSO,):
            raise ExcecaoRespostaSAT(resposta)
        return resposta
satcfe/base.py
class FuncoesSAT(object):
    """Estabelece a interface básica para acesso às funções da biblioteca SAT.

    A intenção é que esta classe seja a base para classes mais especializadas
    capazes de trabalhar as respostas, resultando em objetos mais úteis, já que
    os métodos desta classe invocam as funções da biblioteca SAT e retornam o
    resultado *verbatim*.

    As funções implementadas estão descritas na ER SAT, item 6.1.

    +---------+-----------------------------------+-----------------------------------------+
    | Item ER | Função                            | Método                                  |
    +=========+===================================+=========================================+
    | 6.1.1   | ``AtivarSAT``                     | :meth:`ativar_sat`                      |
    +---------+-----------------------------------+-----------------------------------------+
    | 6.1.2   | ``ComunicarCertificadoICPBRASIL`` | :meth:`comunicar_certificado_icpbrasil` |
    +---------+-----------------------------------+-----------------------------------------+
    | 6.1.3   | ``EnviarDadosVenda``              | :meth:`enviar_dados_venda`              |
    +---------+-----------------------------------+-----------------------------------------+
    | 6.1.4   | ``CancelarUltimaVenda``           | :meth:`cancelar_ultima_venda`           |
    +---------+-----------------------------------+-----------------------------------------+
    | 6.1.5   | ``ConsultarSAT``                  | :meth:`consultar_sat`                   |
    +---------+-----------------------------------+-----------------------------------------+
    | 6.1.6   | ``TesteFimAFim``                  | :meth:`teste_fim_a_fim`                 |
    +---------+-----------------------------------+-----------------------------------------+
    | 6.1.7   | ``ConsultarStatusOperacional``    | :meth:`consultar_status_operacional`    |
    +---------+-----------------------------------+-----------------------------------------+
    | 6.1.8   | ``ConsultarNumeroSessao``         | :meth:`consultar_numero_sessao`         |
    +---------+-----------------------------------+-----------------------------------------+
    | 6.1.9   | ``ConfigurarInterfaceDeRede``     | :meth:`configurar_interface_de_rede`    |
    +---------+-----------------------------------+-----------------------------------------+
    | 6.1.10  | ``AssociarAssinatura``            | :meth:`associar_assinatura`             |
    +---------+-----------------------------------+-----------------------------------------+
    | 6.1.11  | ``AtualizarSoftwareSAT``          | :meth:`atualizar_software_sat`          |
    +---------+-----------------------------------+-----------------------------------------+
    | 6.1.12  | ``ExtrairLogs``                   | :meth:`extrair_logs`                    |
    +---------+-----------------------------------+-----------------------------------------+
    | 6.1.13  | ``BloquearSAT``                   | :meth:`bloquear_sat`                    |
    +---------+-----------------------------------+-----------------------------------------+
    | 6.1.14  | ``DesbloquearSAT``                | :meth:`desbloquear_sat`                 |
    +---------+-----------------------------------+-----------------------------------------+
    | 6.1.15  | ``TrocarCodigoDeAtivacao``        | :meth:`trocar_codigo_de_ativacao`       |
    +---------+-----------------------------------+-----------------------------------------+
    | 6.1.16  | ``ConsultarUltimaSessaoFiscal``   | :meth:`consultar_ultima_sessao_fiscal`  |
    +---------+-----------------------------------+-----------------------------------------+

    :param biblioteca: Uma instância de :class:`BibliotecaSAT`.

    :param string codigo_ativacao: Código de ativação. Senha definida pelo
        contribuinte no software de ativação, conforme item 2.1.1 da ER SAT.

    :param numerador_sessao: Opcional. Um ``callable`` capaz de gerar um
        número de sessão conforme descrito no item 6, alínea "a", "Funções do
        Equipamento SAT", da ER SAT. Se não for especificado, será utilizado
        um :class:`NumeroSessaoMemoria`.

    """  # noqa: E501

    def __init__(
            self,
            biblioteca,
            codigo_ativacao=None,
            numerador_sessao=None,
            encoding='utf-8',
            encoding_errors='strict'):
        """
        TODO: documentar os parâmetros aqui
        """
        self._biblioteca = biblioteca
        self._codigo_ativacao = codigo_ativacao
        self._numerador_sessao = numerador_sessao or NumeroSessaoMemoria()
        self._encoding = encoding
        self._encoding_errors = encoding_errors

    @property
    def biblioteca(self):
        return self._biblioteca

    @property
    def codigo_ativacao(self):
        return self._codigo_ativacao

    @property
    def encoding(self):
        return self._encoding

    @property
    def encoding_errors(self):
        return self._encoding_errors

    def _invocar(self, funcname, *args, **kwargs):
        if funcname not in FUNCTION_PROTOTYPES:
            raise ValueError((
                    'There is no function prototype named: {!r}'
                ).format(funcname))

        proto = FUNCTION_PROTOTYPES[funcname]
        fptr = getattr(self._biblioteca.ref, funcname)
        fptr.argtypes = proto.argtypes
        fptr.restype = proto.restype

        encoded_args = []

        for argument in args:
            if isinstance(argument, str):
                argument = argument.encode(
                        encoding=self.encoding,
                        errors=self.encoding_errors)
            encoded_args.append(argument)

        raw_response = fptr(*encoded_args, **kwargs)

        return raw_response.decode(
                encoding=self.encoding,
                errors=self.encoding_errors)

    def gerar_numero_sessao(self):
        """Gera o número de sessão para a próxima invocação de função SAT."""
        return self._numerador_sessao()

    def ativar_sat(self, tipo_certificado, cnpj, codigo_uf):
        """Função ``AtivarSAT`` conforme ER SAT, item 6.1.1.
        Ativação do equipamento SAT. Dependendo do tipo do certificado, o
        procedimento de ativação é complementado enviando-se o certificado
        emitido pela ICP-Brasil (:meth:`comunicar_certificado_icpbrasil`).

        :param int tipo_certificado: Deverá ser um dos valores
            :attr:`satcomum.constantes.CERTIFICADO_ACSAT_SEFAZ`,
            :attr:`satcomum.constantes.CERTIFICADO_ICPBRASIL` ou
            :attr:`satcomum.constantes.CERTIFICADO_ICPBRASIL_RENOVACAO`, mas
            nenhuma validação será realizada antes que a função de ativação
            seja efetivamente invocada.

        :param str cnpj: Número do CNPJ do estabelecimento contribuinte,
            contendo apenas os dígitos. Nenhuma validação do número do CNPJ
            será realizada antes que a função de ativação seja efetivamente
            invocada.

        :param int codigo_uf: Código da unidade federativa onde o equipamento
            SAT será ativado (eg. ``35`` para o Estado de São Paulo). Nenhuma
            validação do código da UF será realizada antes que a função de
            ativação seja efetivamente invocada.

        :return: Retorna *verbatim* a resposta da função SAT.
        :rtype: string
        """
        resposta = self._invocar(
                'AtivarSAT',
                self.gerar_numero_sessao(),
                tipo_certificado,
                self.codigo_ativacao,
                cnpj,
                codigo_uf)
        return resposta

    def comunicar_certificado_icpbrasil(self, certificado):
        """Função ``ComunicarCertificadoICPBRASIL`` conforme ER SAT, item 6.1.2.
        Envio do certificado criado pela ICP-Brasil.

        :param str certificado: Conteúdo do certificado digital criado pela
            autoridade certificadora ICP-Brasil.

        :return: Retorna *verbatim* a resposta da função SAT.
        :rtype: string
        """
        resposta = self._invocar(
                'ComunicarCertificadoICPBRASIL',
                self.gerar_numero_sessao(),
                self.codigo_ativacao,
                certificado)
        return resposta

    def enviar_dados_venda(self, dados_venda, *args, **kwargs):
        """Função ``EnviarDadosVenda`` conforme ER SAT, item 6.1.3. Envia o
        CF-e de venda para o equipamento SAT, que o enviará para autorização
        pela SEFAZ.

        :param dados_venda: Instância de :class:`~satcfe.entidades.CFeVenda` ou
            uma string contendo o XML do CF-e de venda.

        :return: Retorna *verbatim* a resposta da função SAT.
        :rtype: string
        """
        cfe_venda = resolver_documento(dados_venda, *args, **kwargs)
        resposta = self._invocar(
                'EnviarDadosVenda',
                self.gerar_numero_sessao(),
                self._codigo_ativacao,
                cfe_venda)
        return resposta

    def cancelar_ultima_venda(
            self,
            chave_cfe,
            dados_cancelamento,
            *args,
            **kwargs):
        """Função ``CancelarUltimaVenda`` conforme ER SAT, item 6.1.4. Envia o
        CF-e de cancelamento para o equipamento SAT, que o enviará para
        autorização e cancelamento do CF-e pela SEFAZ.

        :param chave_cfe: String contendo a chave do CF-e de venda que será
            cancelado (deve possuir o prefixo ``"CFe"`` seguido dos dígitos
            da chave do CF-e-SAT autorizado anteriormente).

        :param dados_cancelamento: Uma instância
            de :class:`~satcfe.entidades.CFeCancelamento` ou uma string
            contendo o XML do CF-e de cancelamento.

        :return: Retorna *verbatim* a resposta da função SAT.
        :rtype: string
        """
        cfe_canc = resolver_documento(dados_cancelamento, *args, **kwargs)
        resposta = self._invocar(
                'CancelarUltimaVenda',
                self.gerar_numero_sessao(),
                self._codigo_ativacao,
                chave_cfe,
                cfe_canc)
        return resposta

    def consultar_sat(self):
        """Função ``ConsultarSAT`` conforme ER SAT, item 6.1.5. Usada para
        testes de comunicação entre a AC e o equipamento SAT.

        :return: Retorna *verbatim* a resposta da função SAT.
        :rtype: str
        """
        resposta = self._invocar('ConsultarSAT', self.gerar_numero_sessao())
        return resposta

    def teste_fim_a_fim(self, dados_venda, *args, **kwargs):
        """Função ``TesteFimAFim`` conforme ER SAT, item 6.1.6. Teste de
        comunicação entre a AC, o equipamento SAT e a SEFAZ.

        :param dados_venda: Instância de :class:`~satcfe.entidades.CFeVenda` ou
            uma string contendo o XML do CF-e de venda de teste.

        :return: Retorna *verbatim* a resposta da função SAT.
        :rtype: string
        """
        cfe_venda = resolver_documento(dados_venda, *args, **kwargs)
        resposta = self._invocar(
                'TesteFimAFim',
                self.gerar_numero_sessao(),
                self._codigo_ativacao,
                cfe_venda)
        return resposta

    def consultar_status_operacional(self):
        """Função ``ConsultarStatusOperacional`` conforme ER SAT, item 6.1.7.
        Consulta do status operacional do equipamento SAT.

        :return: Retorna *verbatim* a resposta da função SAT.
        :rtype: string
        """
        resposta = self._invocar(
                'ConsultarStatusOperacional',
                self.gerar_numero_sessao(),
                self._codigo_ativacao)
        return resposta

    def consultar_numero_sessao(self, numero_sessao):
        """Função ``ConsultarNumeroSessao`` conforme ER SAT, item 6.1.8.
        Consulta o equipamento SAT por um número de sessão específico.

        :param int numero_sessao: Número da sessão que se quer consultar.

        :return: Retorna *verbatim* a resposta da função SAT.
        :rtype: string
        """
        resposta = self._invocar(
                'ConsultarNumeroSessao',
                self.gerar_numero_sessao(),
                self._codigo_ativacao,
                numero_sessao)
        return resposta

    def configurar_interface_de_rede(self, configuracao, *args, **kwargs):
        """Função ``ConfigurarInterfaceDeRede`` conforme ER SAT, item 6.1.9.
        Configuração da interface de comunicação do equipamento SAT.

        :param configuracao: Um objeto :class:`~satcfe.rede.ConfiguracaoRede`
            ou uma string contendo o XML com as configurações de rede.

        :return: Retorna *verbatim* a resposta da função SAT.
        :rtype: string
        """
        conf_xml = resolver_documento(configuracao, *args, **kwargs)
        resposta = self._invocar(
                'ConfigurarInterfaceDeRede',
                self.gerar_numero_sessao(),
                self._codigo_ativacao,
                conf_xml)
        return resposta

    def associar_assinatura(self, sequencia_cnpj, assinatura_ac):
        """Função ``AssociarAssinatura`` conforme ER SAT, item 6.1.10.
        Associação da assinatura do aplicativo comercial.

        :param sequencia_cnpj: Sequência string de 28 dígitos composta do CNPJ
            do desenvolvedor da AC e do CNPJ do estabelecimento comercial
            contribuinte, conforme ER SAT, item 2.1.3.

        :param assinatura_ac: Sequência string contendo a assinatura digital do
            parâmetro ``sequencia_cnpj`` codificada em base64.

        :return: Retorna *verbatim* a resposta da função SAT.
        :rtype: string
        """
        resposta = self._invocar(
                'AssociarAssinatura',
                self.gerar_numero_sessao(),
                self._codigo_ativacao,
                sequencia_cnpj,
                assinatura_ac)
        return resposta

    def atualizar_software_sat(self):
        """Função ``AtualizarSoftwareSAT`` conforme ER SAT, item 6.1.11,
        Atualização do software do equipamento SAT.

        :return: Retorna *verbatim* a resposta da função SAT.
        :rtype: string
        """
        resposta = self._invocar(
                'AtualizarSoftwareSAT',
                self.gerar_numero_sessao(),
                self._codigo_ativacao)
        return resposta

    def extrair_logs(self):
        """Função ``ExtrairLogs`` conforme ER SAT, item 6.1.12. Extração dos
        registros de log do equipamento SAT.

        :return: Retorna *verbatim* a resposta da função SAT.
        :rtype: string
        """
        resposta = self._invocar(
                'ExtrairLogs',
                self.gerar_numero_sessao(),
                self._codigo_ativacao)
        return resposta

    def bloquear_sat(self):
        """Função ``BloquearSAT`` conforme ER SAT, item 6.1.13. Bloqueio
        operacional do equipamento SAT.

        :return: Retorna *verbatim* a resposta da função SAT.
        :rtype: string
        """
        resposta = self._invocar(
                'BloquearSAT',
                self.gerar_numero_sessao(),
                self._codigo_ativacao)
        return resposta

    def desbloquear_sat(self):
        """Função ``DesbloquearSAT`` conforme ER SAT, item 6.1.14. Desbloqueio
        operacional do equipamento SAT.

        :return: Retorna *verbatim* a resposta da função SAT.
        :rtype: string
        """
        resposta = self._invocar(
                'DesbloquearSAT',
                self.gerar_numero_sessao(),
                self._codigo_ativacao)
        return resposta

    def trocar_codigo_de_ativacao(
            self, novo_codigo_ativacao,
            opcao=constantes.CODIGO_ATIVACAO_REGULAR,
            codigo_emergencia=None):
        """Função ``TrocarCodigoDeAtivacao`` conforme ER SAT, item 6.1.15.
        Troca do código de ativação do equipamento SAT. YouX.

        :param str novo_codigo_ativacao: O novo código de ativação escolhido
            pelo contribuinte.

        :param int opcao: Indica se deverá ser utilizado o código de ativação
            atualmente configurado, que é um código de ativação regular,
            definido pelo contribuinte, ou se deverá ser usado um código de
            emergência. Deverá ser o valor de uma das constantes
            :attr:`satcomum.constantes.CODIGO_ATIVACAO_REGULAR` (padrão) ou
            :attr:`satcomum.constantes.CODIGO_ATIVACAO_EMERGENCIA`.
            Nenhuma validação será realizada antes que a função seja
            efetivamente invocada. Entretanto, se opção de código de ativação
            indicada for ``CODIGO_ATIVACAO_EMERGENCIA``, então o argumento que
            informa o ``codigo_emergencia`` será checado e deverá avaliar como
            verdadeiro.

        :param str codigo_emergencia: O código de ativação de emergência, que
            é definido pelo fabricante do equipamento SAT. Este código deverá
            ser usado quando o usuário perder o código de ativação regular, e
            precisar definir um novo código de ativação. Note que, o argumento
            ``opcao`` deverá ser informado com o valor
            :attr:`satcomum.constantes.CODIGO_ATIVACAO_EMERGENCIA` para que
            este código de emergência seja considerado.

        :return: Retorna *verbatim* a resposta da função SAT.
        :rtype: string

        :raises ValueError: Se o novo código de ativação avaliar como falso
            (possuir uma string nula por exemplo) ou se o código de emergencia
            avaliar como falso quando a opção for pelo código de ativação de
            emergência.

        .. warning::

            De acordo com a :term:`ER SAT`, a função ``TrocarCodigoDeAtivacao``
            requer que o novo código de ativação seja especificado duas vezes.

            Este método ignora isso e apenas informa o mesmo código de ativação
            duas vezes na chamada da função. O entendimento é de que a
            confirmação do código de ativação é responsabilidade de outras
            camadas da aplicação (eg. interface com o usuário) e, portanto,
            fora do escopo desta biblioteca.

        """
        if not novo_codigo_ativacao:
            raise ValueError((
                    'Novo codigo de ativacao invalido: {!r}'
                ).format(novo_codigo_ativacao))

        codigo_ativacao = self._codigo_ativacao

        if opcao not in (v for v, s in constantes.TIPOS_CODIGOS_ATIVACAO):
            raise ValueError((
                    'Opcao de tipo de codigo de ativacao inesperado: '
                    'opcao={!r}'
                ).format(opcao))

        if opcao == constantes.CODIGO_ATIVACAO_EMERGENCIA:
            if codigo_emergencia:
                codigo_ativacao = codigo_emergencia
            else:
                raise ValueError((
                        'Codigo de ativacao de emergencia invalido: '
                        '{!r} (opcao={!r})'
                    ).format(codigo_emergencia, opcao))

        resposta = self._invocar(
                'TrocarCodigoDeAtivacao',
                self.gerar_numero_sessao(),
                codigo_ativacao,
                opcao,
                novo_codigo_ativacao,
                novo_codigo_ativacao)

        # NOTA: Em tese o novo código de ativação deverá passar a ser utilizado
        # pela implementação cliente. Este método em si não tem condições de
        # avaliar o sucesso ou fracasso do resultado sem provocar uma exceção
        # prematura, já que apenas retorna os dados brutos.
        #
        # A implementação especializada deverá alternar o código de ativação
        # atual para o novo código de ativação em caso de sucesso ao analisar o
        # retorno da função, para que as chamadas às funções SAT subsequentes
        # tenham sucesso.
        return resposta

    def consultar_ultima_sessao_fiscal(self):
        """Função ``ConsultarUltimaSessaoFiscal`` conforme ER SAT, item 6.1.16.
        Retorna a resposta da última sessão fiscal, isto é, do último comando
        fiscal (``EnviarDadosVenda`` ou ``CancelarUltimaVenda``) executado pelo
        equipamento SAT.

        :return: Retorna *verbatim* a resposta da função SAT.
        :rtype: string
        """
        resposta = self._invocar(
                'ConsultarUltimaSessaoFiscal',
                self.gerar_numero_sessao(),
                self._codigo_ativacao)
        return resposta
satcfe/resposta/padrao.py
class RespostaSAT(object):
    """Base para representação de respostas das funções da biblioteca SAT.
    A maior parte das funções SAT resultam em respostas que contém um conjunto
    padrão de atributos (veja o atributo :attr:`CAMPOS`), descritos na ER SAT:

    .. sourcecode:: text

        numeroSessao (int)
        EEEEE (text)
        mensagem (text)
        cod (text)
        mensagemSEFAZ (text)

    Além dos atributos padrão, a resposta deverá conter uma referência para o
    nome da função SAT a que a resposta se refere e ao conteúdo original da
    resposta, através dos atributos:

    .. sourcecode:: python

        resposta.atributos.funcao
        resposta.atributos.verbatim

    Esta classe fornece uma série de métodos construtores (*factory methods*)
    para respostas que são comuns. Para as respostas que não são comuns,
    existem especializações desta classe.

    .. note::

        Espera-se que a resposta original, devolvida pela biblioteca do
        fabricante do equipamento SAT, será sempre um dado Unicode.

    .. note::

        Aqui, ``text`` diz respeito à um objeto ``unicode`` (Python 2) ou
        ``str`` (Python 3). Veja ``builtins.str`` da biblioteca ``future``.

    """

    class Atributos(object):
        __slots__ = ('_funcao', '_verbatim')

        def __init__(self, funcao=None, verbatim=None):
            self._funcao = funcao
            self._verbatim = verbatim

        @property
        def funcao(self):
            return self._funcao

        @property
        def verbatim(self):
            return self._verbatim

    CAMPOS = (
            ('numeroSessao', int),
            ('EEEEE', text),
            ('mensagem', text),
            ('cod', text),
            ('mensagemSEFAZ', text),
        )
    """Campos padrão esperados em uma resposta e a sua função de conversão para
    o tipo Python, a partir da resposta original.
    """

    def __init__(self, **kwargs):
        super(RespostaSAT, self).__init__()
        for key, value in kwargs.items():
            setattr(self, key, value)

        # self.atributos = RespostaSAT._Atributos()
        # self.atributos.funcao = None
        # self.atributos.verbatim = None
        self.atributos = None

    @staticmethod
    def comunicar_certificado_icpbrasil(retorno):
        """Constrói uma :class:`RespostaSAT` para o retorno da função
        :meth:`~satcfe.base.FuncoesSAT.comunicar_certificado_icpbrasil`.
        """
        resposta = analisar_retorno(
                retorno,
                funcao='ComunicarCertificadoICPBRASIL')
        if resposta.EEEEE not in ('05000',):
            raise ExcecaoRespostaSAT(resposta)
        return resposta

    @staticmethod
    def consultar_sat(retorno):
        """Constrói uma :class:`RespostaSAT` para o retorno da função
        :meth:`~satcfe.base.FuncoesSAT.consultar_sat`.
        """
        resposta = analisar_retorno(retorno, funcao='ConsultarSAT')
        if resposta.EEEEE not in ('08000',):
            raise ExcecaoRespostaSAT(resposta)
        return resposta

    @staticmethod
    def configurar_interface_de_rede(retorno):
        """Constrói uma :class:`RespostaSAT` para o retorno da função
        :meth:`~satcfe.base.FuncoesSAT.configurar_interface_de_rede`.
        """
        resposta = analisar_retorno(
                retorno,
                funcao='ConfigurarInterfaceDeRede')
        if resposta.EEEEE not in ('12000',):
            raise ExcecaoRespostaSAT(resposta)
        return resposta

    @staticmethod
    def atualizar_software_sat(retorno):
        """Constrói uma :class:`RespostaSAT` para o retorno da função
        :meth:`~satcfe.base.FuncoesSAT.atualizar_software_sat`.
        """
        resposta = analisar_retorno(retorno, funcao='AtualizarSoftwareSAT')
        if resposta.EEEEE not in ('14000',):
            raise ExcecaoRespostaSAT(resposta)
        return resposta

    @staticmethod
    def bloquear_sat(retorno):
        """Constrói uma :class:`RespostaSAT` para o retorno da função
        :meth:`~satcfe.base.FuncoesSAT.bloquear_sat`.
        """
        resposta = analisar_retorno(retorno, funcao='BloquearSAT')
        if resposta.EEEEE not in ('16000',):
            raise ExcecaoRespostaSAT(resposta)
        return resposta

    @staticmethod
    def desbloquear_sat(retorno):
        """Constrói uma :class:`RespostaSAT` para o retorno da função
        :meth:`~satcfe.base.FuncoesSAT.desbloquear_sat`.
        """
        resposta = analisar_retorno(retorno, funcao='DesbloquearSAT')
        if resposta.EEEEE not in ('17000',):
            raise ExcecaoRespostaSAT(resposta)
        return resposta

    @staticmethod
    def trocar_codigo_de_ativacao(retorno):
        """Constrói uma :class:`RespostaSAT` para o retorno da função
        :meth:`~satcfe.base.FuncoesSAT.trocar_codigo_de_ativacao`.
        """
        resposta = analisar_retorno(retorno, funcao='TrocarCodigoDeAtivacao')
        if resposta.EEEEE not in ('18000',):
            raise ExcecaoRespostaSAT(resposta)
        return resposta
satcfe/resposta/consultarnumerosessao.py
class RespostaConsultarNumeroSessao(RespostaSAT):
    """Lida com as respostas da função ``ConsultarNumeroSessao`` (veja o método
    :meth:`~satcfe.base.FuncoesSAT.consultar_numero_sessao`). Como as respostas
    dependem do número da sessão consultado, o método de construção
    :meth:`analisar` deverá resultar na resposta apropriada para cada retorno.
    """

    @staticmethod
    def analisar(retorno):
        """Constrói uma :class:`RespostaSAT` ou especialização dependendo da
        função SAT encontrada na sessão consultada.

        :param str retorno: Retorno da função ``ConsultarNumeroSessao``.
        """
        if '|' not in retorno:
            raise ErroRespostaSATInvalida(
                    'Resposta não possui pipes '
                    'separando os campos: {!r}'.format(retorno))

        resposta = _RespostaParcial(*(retorno.split('|')[:2]))

        try:
            eeeee = int(resposta.EEEEE)
        except ValueError:
            # não é possível converter 'EEEEE' para inteiro;
            # deixa seguir com a pós-análise do retorno
            pass
        else:
            if eeeee not in range(11001, 11999):
                # não está na faixa de códigos específicos da própria
                # função ConsultarNumeroSessao; testa por uma faixa de códigos
                # entre as respostas possíveis...
                for faixa, construtor in _RESPOSTAS_POSSIVEIS:
                    if eeeee in range(faixa, faixa+1000):
                        return construtor(retorno)

        return RespostaConsultarNumeroSessao._pos_analise(retorno)

    @staticmethod
    def _pos_analise(retorno):
        resposta = analisar_retorno(
                retorno,
                funcao='ConsultarNumeroSessao',
                classe_resposta=RespostaConsultarNumeroSessao)
        if resposta.EEEEE not in ('11000',):
            raise ExcecaoRespostaSAT(resposta)
        return resposta
satcfe/resposta/consultarultimasessaofiscal.py
class RespostaConsultarUltimaSessaoFiscal(RespostaSAT):
    """Lida com as respostas da função ``ConsultarUltimaSessaoFiscal`` (veja o
    método :meth:`~satcfe.base.FuncoesSAT.consultar_numero_sessao`). Como as
    respostas dependem do última comando "fiscal" executado pelo equipamento
    SAT, o método de construção :meth:`analisar` deverá resultar na resposta
    apropriada para cada retorno.
    """

    @staticmethod
    def analisar(retorno):
        """Constrói uma :class:`RespostaSAT` ou especialização dependendo da
        função SAT encontrada na última sessão fiscal.

        :param str retorno: Retorno da função ``ConsultarUltimaSessaoFiscal``.
        """
        if '|' not in retorno:
            raise ErroRespostaSATInvalida(
                    'Resposta não possui pipes '
                    'separando os campos: {!r}'.format(retorno))

        resposta = _RespostaParcial(*(retorno.split('|')[:2]))

        try:
            eeeee = int(resposta.EEEEE)
        except ValueError:
            # não é possível converter 'EEEEE' para inteiro;
            # deixa seguir com a pós-análise do retorno
            pass
        else:
            if eeeee not in range(19001, 19999):
                # não está na faixa de códigos específicos da própria
                # função ConsultarUltimaSessaoFiscal; testa por uma faixa de
                # códigos entre as respostas possíveis...
                for faixa, construtor in _RESPOSTAS_POSSIVEIS:
                    if eeeee in range(faixa, faixa+1000):
                        return construtor(retorno)

        return RespostaConsultarUltimaSessaoFiscal._pos_analise(retorno)

    @staticmethod
    def _pos_analise(retorno):
        resposta = analisar_retorno(
                retorno,
                funcao='ConsultarUltimaSessaoFiscal',
                classe_resposta=RespostaConsultarUltimaSessaoFiscal)
        if resposta.EEEEE not in ('19000',):
            raise ExcecaoRespostaSAT(resposta)
        return resposta
satcfe/resposta/testefimafim.py
class RespostaTesteFimAFim(RespostaSAT):
    """Lida com as respostas da função ``TesteFimAFim`` (veja o método
    :meth:`~satcfe.base.FuncoesSAT.teste_fim_a_fim`). Os atributos
    esperados em caso de sucesso, são:

    .. sourcecode:: text

        numeroSessao (int)
        EEEEE (text)
        mensagem (text)
        cod (text)
        mensagemSEFAZ (text)
        arquivoCFeBase64 (text)
        timeStamp (datetime.datetime)
        numDocFiscal (int)
        chaveConsulta (text)

    Em caso de falha, são esperados apenas os atributos padrão, conforme
    descrito na constante :attr:`~satcfe.resposta.padrao.RespostaSAT.CAMPOS`.

    .. note::

        Aqui, ``text`` diz respeito à um objeto ``unicode`` (Python 2) ou
        ``str`` (Python 3). Veja ``builtins.str`` da biblioteca ``future``.

    """

    def xml(self):
        """Retorna o XML do CF-e-SAT decodificado.

        :rtype: str
        """
        if self._sucesso():
            return base64_to_str(self.arquivoCFeBase64)
        else:
            raise ExcecaoRespostaSAT(self)

    def qrcode(self):
        """Resulta nos dados que compõem o QRCode.

        :rtype: str
        """
        if self._sucesso():
            tree = ET.parse(StringIO(self.xml()))
            return dados_qrcode(tree)
        else:
            raise ExcecaoRespostaSAT(self)

    def _sucesso(self):
        return self.EEEEE == EMITIDO_COM_SUCESSO

    @staticmethod
    def analisar(retorno):
        """Constrói uma :class:`RespostaTesteFimAFim` a partir do retorno
        informado.

        :param str retorno: Retorno da função ``TesteFimAFim``.

        :raises ExcecaoRespostaSAT: Se o atributo ``EEEEE`` não indicar o
            código de sucesso ``09000`` para ``TesteFimAFim``.
        """
        resposta = analisar_retorno(
                retorno,
                funcao='TesteFimAFim',
                classe_resposta=RespostaTesteFimAFim,
                campos=(
                        ('numeroSessao', int),
                        ('EEEEE', text),
                        ('mensagem', text),
                        ('cod', text),
                        ('mensagemSEFAZ', text),
                        ('arquivoCFeBase64', text),
                        ('timeStamp', as_datetime),
                        ('numDocFiscal', int),
                        ('chaveConsulta', text),
                    ),
                campos_alternativos=[
                        RespostaSAT.CAMPOS,
                    ]
            )
        if resposta.EEEEE not in (EMITIDO_COM_SUCESSO,):
            raise ExcecaoRespostaSAT(resposta)
        return resposta
satcfe/resposta/ativarsat.py
class RespostaAtivarSAT(RespostaSAT):
    """Lida com as respostas da função ``AtivarSAT`` (veja o método
    :meth:`~satcfe.base.FuncoesSAT.ativar_sat`). Os atributos esperados em
    caso de sucesso, são:

    .. sourcecode:: text

        numeroSessao (int)
        EEEEE (text)
        mensagem (text)
        cod (text)
        mensagemSEFAZ (text)
        CSR (text)

    Em caso de falha, são esperados apenas os atributos padrão, conforme
    descrito na constante :attr:`~satcfe.resposta.padrao.RespostaSAT.CAMPOS`.

    .. note::

        Aqui, ``text`` diz respeito à um objeto ``unicode`` (Python 2) ou
        ``str`` (Python 3). Veja ``builtins.str`` da biblioteca ``future``.

    """

    def csr(self):
        """Retorna o CSR (**Certificate Signing Request**) decodificado."""
        return base64_to_str(self.CSR)

    @staticmethod
    def analisar(retorno):
        """Constrói uma :class:`RespostaAtivarSAT` a partir do retorno
        informado.

        :param str retorno: Retorno da função ``AtivarSAT``.
        """
        resposta = analisar_retorno(
                retorno,
                funcao='AtivarSAT',
                classe_resposta=RespostaAtivarSAT,
                campos=(
                        ('numeroSessao', int),
                        ('EEEEE', text),
                        ('mensagem', text),
                        ('cod', text),
                        ('mensagemSEFAZ', text),
                        ('CSR', text),
                    ),
                campos_alternativos=[
                        # se a ativação falhar espera-se o padrão de campos
                        # no retorno...
                        RespostaSAT.CAMPOS,
                    ]
            )
        if resposta.EEEEE not in (
                ATIVADO_CORRETAMENTE,
                CSR_ICPBRASIL_CRIADO_SUCESSO,):
            raise ExcecaoRespostaSAT(resposta)
        return resposta
satcfe/resposta/cancelarultimavenda.py
class RespostaCancelarUltimaVenda(RespostaSAT):
    """Lida com as respostas da função ``CancelarUltimaVenda`` (veja o método
    :meth:`~satcfe.base.FuncoesSAT.cancelar_ultima_venda`). Os atributos
    esperados em caso de sucesso, são:

    .. sourcecode:: text

        numeroSessao (int)
        EEEEE (text)
        CCCC (text)
        mensagem (text)
        cod (text)
        mensagemSEFAZ (text)
        arquivoCFeBase64 (text)
        timeStamp (datetime.datetime)
        chaveConsulta (text)
        valorTotalCFe (decimal.Decimal)
        CPFCNPJValue (text)
        assinaturaQRCODE (text)

    Em caso de falha, são esperados apenas os atributos:

    .. sourcecode:: text

        numeroSessao (int)
        EEEEE (text)
        CCCC (text)
        mensagem (text)
        cod (text)
        mensagemSEFAZ (text)

    Finalmente, como último recurso, a resposta poderá incluir apenas os
    atributos padrão, conforme descrito na constante
    :attr:`~satcfe.resposta.padrao.RespostaSAT.CAMPOS`.

    .. note::

        Aqui, ``text`` diz respeito à um objeto ``unicode`` (Python 2) ou
        ``str`` (Python 3). Veja ``builtins.str`` da biblioteca ``future``.

    """

    def xml(self):
        """Retorna o XML do CF-e-SAT de cancelamento decodificado.

        :rtype: str
        """
        if self._sucesso():
            return base64_to_str(self.arquivoCFeBase64)
        else:
            raise ExcecaoRespostaSAT(self)

    def qrcode(self):
        """Resulta nos dados que compõem o QRCode.

        :rtype: str
        """
        if self._sucesso():
            tree = ET.parse(StringIO(self.xml()))
            return dados_qrcode(tree)
        else:
            raise ExcecaoRespostaSAT(self)

    def _sucesso(self):
        return self.EEEEE == CANCELADO_COM_SUCESSO

    @staticmethod
    def analisar(retorno):
        """Constrói uma :class:`RespostaCancelarUltimaVenda` a partir do
        retorno informado.

        :param str retorno: Retorno da função ``CancelarUltimaVenda``.
        """
        resposta = analisar_retorno(
                retorno,
                funcao='CancelarUltimaVenda',
                classe_resposta=RespostaCancelarUltimaVenda,
                campos=(
                        ('numeroSessao', int),
                        ('EEEEE', text),
                        ('CCCC', text),
                        ('mensagem', text),
                        ('cod', text),
                        ('mensagemSEFAZ', text),
                        ('arquivoCFeBase64', text),
                        ('timeStamp', as_datetime),
                        ('chaveConsulta', text),
                        ('valorTotalCFe', Decimal),
                        ('CPFCNPJValue', text),
                        ('assinaturaQRCODE', text),
                    ),
                campos_alternativos=[
                        # se a venda falhar apenas os primeiros seis campos
                        # especificados na ER deverão ser retornados...
                        (
                                ('numeroSessao', int),
                                ('EEEEE', text),
                                ('CCCC', text),
                                ('mensagem', text),
                                ('cod', text),
                                ('mensagemSEFAZ', text),
                        ),
                        # por via das dúvidas, considera o padrão de campos,
                        # caso não haja nenhuma coincidência...
                        RespostaSAT.CAMPOS,
                    ]
            )
        if resposta.EEEEE not in (CANCELADO_COM_SUCESSO,):
            raise ExcecaoRespostaSAT(resposta)
        return resposta
satcfe/resposta/associarassinatura.py
class RespostaAssociarAssinatura(RespostaSAT):
    """Lida com as respostas da função ``AssociarAssinatura`` (veja o método
    :meth:`~satcfe.base.FuncoesSAT.associar_assinatura`). Os atributos
    esperados na resposta são:

    .. sourcecode:: text

        numeroSessao (int)
        EEEEE (text)
        CCCC (text)
        mensagem (text)
        cod (text)
        mensagemSEFAZ (text)

    .. note::

        Aqui, ``text`` diz respeito à um objeto ``unicode`` (Python 2) ou
        ``str`` (Python 3). Veja ``builtins.str`` da biblioteca ``future``.

    """

    @classmethod
    def analisar(cls, retorno):
        """Constrói uma instância da resposta a partir do retorno informado.

        :param str retorno: Retorno da função ``AssociarAssinatura``.
        """
        resposta = analisar_retorno(
                retorno,
                funcao='AssociarAssinatura',
                classe_resposta=RespostaAssociarAssinatura,
                campos=(
                        ('numeroSessao', int),
                        ('EEEEE', text),
                        ('CCCC', text),
                        ('mensagem', text),
                        ('cod', text),
                        ('mensagemSEFAZ', text),
                    ),
                campos_alternativos=[
                        # se a ativação falhar espera-se o padrão de campos
                        # no retorno (embora isto não esteja explícito na ER
                        # SAT, usa os campos padrão como um fallback razoável)
                        RespostaSAT.CAMPOS,
                    ]
            )
        if resposta.EEEEE not in ('13000',):
            raise ExcecaoRespostaSAT(resposta)
        return resposta
satcfe/base.py
def resolver_documento(dados, *args, **kwargs):
    if isinstance(dados, six.string_types):
        conteudo = dados
        if args or kwargs:
            warnings.warn(
                    (
                        'O documento foi informado como um valor str, que '
                        'sera enviado literalmente para o equipamento; '
                        'isso implica em ignorar os argumentos extras '
                        'informados (para efeito de construcao do conteudo '
                        'do documento): args={!r}; kwargs={!r}'
                    ).format(args, kwargs),
                    category=UserWarning,
                    stacklevel=3)
    else:
        if hasattr(dados, 'documento') \
                and callable(dados.documento):
            conteudo = dados.documento(*args, **kwargs)
        else:
            raise ValueError((
                    'O argumento deve ser informado como um valor str (com o '
                    'conteudo do documento pronto para ser enviado ao '
                    'equipamento SAT) ou um objeto que possua um metodo '
                    '\'documento()\' capaz de gerar o conteudo do documento; '
                    'informado: {!r}'
                ).format(dados))
    return conteudo
satcfe/resposta/extrairlogs.py
class RespostaExtrairLogs(RespostaSAT):
    """Lida com as respostas da função ``ExtrairLogs`` (veja o método
    :meth:`~satcfe.base.FuncoesSAT.extrair_logs`). Os atributos esperados em
    caso de sucesso, são:

    .. sourcecode:: text

        numeroSessao (int)
        EEEEE (text)
        mensagem (text)
        cod (text)
        mensagemSEFAZ (text)
        arquivoLog (text)

    Em caso de falha, são esperados apenas os atributos padrão, conforme
    descrito na constante :attr:`~satcfe.resposta.padrao.RespostaSAT.CAMPOS`.

    .. note::

        Aqui, ``text`` diz respeito à um objeto ``unicode`` (Python 2) ou
        ``str`` (Python 3). Veja ``builtins.str`` da biblioteca ``future``.

    """

    def conteudo(self):
        """Retorna o conteúdo do log decodificado."""
        return base64_to_str(self.arquivoLog)

    def salvar(
            self,
            destino=None,
            prefix='tmp', suffix='-sat.log', dir=None,
            encoding='utf-8', encoding_errors='strict'):
        """Salva o arquivo de log decodificado.

        :param str destino: Opcional. Caminho completo para o arquivo onde os
            dados dos logs deverão ser salvos. Se não informado, será criado
            um arquivo temporário via :func:`tempfile.mkstemp`.

        :param str prefix: Opcional. Prefixo para o nome do arquivo. Se não
            informado será usado ``"tmp"``.

        :param str suffix: Opcional. Sufixo para o nome do arquivo. Se não
            informado será usado ``"-sat.log"``.

        :param dir: Opcional. Contém o caminho completo onde o arquivo
            temporário deverá ser criado. Este argumento terá efeito apenas
            quando o argumento ``destino`` não for informado.

        :param str encoding: Opcional. Codificação de caracteres a ser usada
            para codificar o conteúdo do log em bytes que serão efetivamente
            escritos no arquivo de destino. Padrão é ``"utf-8"``. Veja o
            método :meth:`str.encode` para detalhes.

        :param str encoding_errors: Opcional. Como lidar com os erros de
            codificação de caracteres. Padrão é ``"strict"``. Veja o método
            :meth:`str.encode` para detalhes.


        :return: Retorna o caminho completo para o arquivo salvo.
        :rtype: str

        :raises FileExistsError: Se o destino informado já existir.
        """
        if destino:
            if os.path.exists(destino):
                raise FileExistsError(destino)
            fd = os.open(destino, os.O_CREAT | os.O_EXCL | os.O_WRONLY)
        else:
            fd, destino = tempfile.mkstemp(
                    dir=dir,
                    prefix=prefix,
                    suffix=suffix)

        # converte conteúdo str (unicode) para bytes antes de escrever
        dados = self.conteudo().encode(encoding, errors=encoding_errors)

        os.write(fd, dados)
        os.fsync(fd)
        os.close(fd)

        return destino

    @staticmethod
    def analisar(retorno):
        """Constrói uma :class:`RespostaExtrairLogs` a partir do retorno
        informado.

        :param str retorno: Retorno da função ``ExtrairLogs``.
        """
        resposta = analisar_retorno(
                retorno,
                funcao='ExtrairLogs',
                classe_resposta=RespostaExtrairLogs,
                campos=RespostaSAT.CAMPOS + (
                        ('arquivoLog', text),
                    ),
                campos_alternativos=[
                        # se a extração dos logs falhar espera-se o padrão de
                        # campos no retorno...
                        RespostaSAT.CAMPOS,
                    ]
            )
        if resposta.EEEEE not in ('15000',):
            raise ExcecaoRespostaSAT(resposta)
        return resposta
satcfe/resposta/consultarstatusoperacional.py
class RespostaConsultarStatusOperacional(RespostaSAT):
    """Lida com as respostas da função ``ConsultarStatusOperacional`` (veja o
    método :meth:`~satcfe.base.FuncoesSAT.consultar_status_operacional`).
    Os atributos esperados em caso de sucesso, são:

    +---------------------+----------------------------------+
    | Atributo            | Tipo Python                      |
    +=====================+==================================+
    | ``numeroSessao``    | ``int``                          |
    +---------------------+----------------------------------+
    | ``EEEEE``           | ``text``                         |
    +---------------------+----------------------------------+
    | ``mensagem``        | ``text``                         |
    +---------------------+----------------------------------+
    | ``cod``             | ``text``                         |
    +---------------------+----------------------------------+
    | ``mensagemSEFAZ``   | ``text``                         |
    +---------------------+----------------------------------+
    | ``NSERIE``          | ``text``                         |
    +---------------------+----------------------------------+
    | ``TIPO_LAN``        | ``text``                         |
    +---------------------+----------------------------------+
    | ``LAN_IP``          | ``text``                         |
    +---------------------+----------------------------------+
    | ``LAN_MAC``         | ``text``                         |
    +---------------------+----------------------------------+
    | ``LAN_MASK``        | ``text``                         |
    +---------------------+----------------------------------+
    | ``LAN_GW``          | ``text``                         |
    +---------------------+----------------------------------+
    | ``LAN_DNS_1``       | ``text``                         |
    +---------------------+----------------------------------+
    | ``LAN_DNS_2``       | ``text``                         |
    +---------------------+----------------------------------+
    | ``STATUS_LAN``      | ``text``                         |
    +---------------------+----------------------------------+
    | ``NIVEL_BATERIA``   | ``text``                         |
    +---------------------+----------------------------------+
    | ``MT_TOTAL``        | ``text``                         |
    +---------------------+----------------------------------+
    | ``MT_USADA``        | ``text``                         |
    +---------------------+----------------------------------+
    | ``DH_ATUAL``        | ``datetime.datetime``            |
    +---------------------+----------------------------------+
    | ``VER_SB``          | ``text``                         |
    +---------------------+----------------------------------+
    | ``VER_LAYOUT``      | ``text``                         |
    +---------------------+----------------------------------+
    | ``ULTIMO_CF_E_SAT`` | ``text``                         |
    +---------------------+----------------------------------+
    | ``LISTA_INICIAL``   | ``text``                         |
    +---------------------+----------------------------------+
    | ``LISTA_FINAL``     | ``text``                         |
    +---------------------+----------------------------------+
    | ``DH_CFE``          | ``datetime.datetime``|``None``   |
    +---------------------+----------------------------------+
    | ``DH_ULTIMA``       | ``datetime.datetime``            |
    +---------------------+----------------------------------+
    | ``CERT_EMISSAO``    | ``datetime.date``                |
    +---------------------+----------------------------------+
    | ``CERT_VENCIMENTO`` | ``datetime.date``                |
    +---------------------+----------------------------------+
    | ``ESTADO_OPERACAO`` | ``int``                          |
    +---------------------+----------------------------------+

    Em caso de falha, são esperados apenas os atributos padrão, conforme
    descrito na constante :attr:`~satcfe.resposta.padrao.RespostaSAT.CAMPOS`.

    .. note::

        Aqui, ``text`` diz respeito à um objeto ``unicode`` (Python 2) ou
        ``str`` (Python 3). Veja ``builtins.str`` da biblioteca ``future``.

    """

    @property
    def status(self):
        """Nome amigável do campo ``ESTADO_OPERACAO``, conforme a "Tabela de
        Informações do Status do SAT".
        """
        for valor, rotulo in ESTADOS_OPERACAO:
            if self.ESTADO_OPERACAO == valor:
                return rotulo
        return u'(desconhecido: {})'.format(self.ESTADO_OPERACAO)

    @staticmethod
    def analisar(retorno):
        """Constrói uma :class:`RespostaConsultarStatusOperacional` a partir do
        retorno informado.

        :param str retorno: Retorno da função ``ConsultarStatusOperacional``.
        """
        resposta = analisar_retorno(
                retorno,
                funcao='ConsultarStatusOperacional',
                classe_resposta=RespostaConsultarStatusOperacional,
                campos=RespostaSAT.CAMPOS + (
                        ('NSERIE', _stripped_str),
                        ('TIPO_LAN', _stripped_str),
                        ('LAN_IP', normalizar_ip),
                        ('LAN_MAC', text),
                        ('LAN_MASK', normalizar_ip),
                        ('LAN_GW', normalizar_ip),
                        ('LAN_DNS_1', normalizar_ip),
                        ('LAN_DNS_2', normalizar_ip),
                        ('STATUS_LAN', _stripped_str),
                        ('NIVEL_BATERIA', _stripped_str),
                        ('MT_TOTAL', _stripped_str),
                        ('MT_USADA', _stripped_str),
                        ('DH_ATUAL', as_datetime),
                        ('VER_SB', _stripped_str),
                        ('VER_LAYOUT', _stripped_str),
                        ('ULTIMO_CF_E_SAT', _stripped_str),
                        ('LISTA_INICIAL', _stripped_str),
                        ('LISTA_FINAL', _stripped_str),
                        ('DH_CFE', as_datetime_or_none),
                        ('DH_ULTIMA', as_datetime),
                        ('CERT_EMISSAO', as_date),
                        ('CERT_VENCIMENTO', as_date),
                        ('ESTADO_OPERACAO', int),
                    ),
                campos_alternativos=[
                        # se falhar resultarão apenas os 5 campos padrão
                        RespostaSAT.CAMPOS,
                    ]
            )
        if resposta.EEEEE not in ('10000',):
            raise ExcecaoRespostaSAT(resposta)
        return resposta


Question:     import requests
import satcfe
from satcomum import constantes
from .base import FuncoesSAT
from .base import resolver_documento
from .resposta import RespostaAssociarAssinatura
from .resposta import RespostaAtivarSAT
from .resposta import RespostaCancelarUltimaVenda
from .resposta import RespostaConsultarNumeroSessao
from .resposta import RespostaConsultarStatusOperacional
from .resposta import RespostaConsultarUltimaSessaoFiscal
from .resposta import RespostaEnviarDadosVenda
from .resposta import RespostaExtrairLogs
from .resposta import RespostaSAT
from .resposta import RespostaTesteFimAFim
                conteudo.get('retorno'))

    def enviar_dados_venda(self, dados_venda, *args, **kwargs):
        """Sobrepõe :meth:`~satcfe.base.FuncoesSAT.enviar_dados_venda`.

        :return: Uma resposta SAT especializada em ``EnviarDadosVenda``.
        :rtype: satcfe.resposta.enviardadosvenda.RespostaEnviarDadosVenda
        """
        cfe = resolver_documento(dados_venda, *args, **kwargs)
        resp = self._http_post('enviardadosvenda', dados_venda=cfe)
        conteudo = resp.json()
        return RespostaEnviarDadosVenda.analisar(conteudo.get('retorno'))

    def cancelar_ultima_venda(
            self,
            chave_cfe,
            dados_cancelamento,
            *args,
            **kwargs):
        """Sobrepõe :meth:`~satcfe.base.FuncoesSAT.cancelar_ultima_venda`.

        :return: Uma resposta SAT especializada em ``CancelarUltimaVenda``.
        :rtype: satcfe.resposta.cancelarultimavenda.RespostaCancelarUltimaVenda
        """
        cfe_canc = resolver_documento(dados_cancelamento, *args, **kwargs)
        resp = self._http_post(
                'cancelarultimavenda',
                chave_cfe=chave_cfe,
                dados_cancelamento=cfe_canc)
        conteudo = resp.json()
        return RespostaCancelarUltimaVenda.analisar(conteudo.get('retorno'))

    def consultar_sat(self):
        """Sobrepõe :meth:`~satcfe.base.FuncoesSAT.consultar_sat`.

        :return: Uma resposta SAT padrão.
        :rtype: satcfe.resposta.padrao.RespostaSAT
        """
        resp = self._http_post('consultarsat')
        conteudo = resp.json()
        return RespostaSAT.consultar_sat(conteudo.get('retorno'))

    def teste_fim_a_fim(self, dados_venda, *args, **kwargs):
        """Sobrepõe :meth:`~satcfe.base.FuncoesSAT.teste_fim_a_fim`.

        :return: Uma resposta SAT especializada em ``TesteFimAFim``.
        :rtype: satcfe.resposta.testefimafim.RespostaTesteFimAFim
        """
        cfe = resolver_documento(dados_venda, *args, **kwargs)
        resp = self._http_post('testefimafim', dados_venda=cfe)
        conteudo = resp.json()
        return RespostaTesteFimAFim.analisar(conteudo.get('retorno'))

    def consultar_status_operacional(self):
        """Sobrepõe :meth:`~satcfe.base.FuncoesSAT.consultar_status_operacional`.

        :return: Uma resposta SAT especializada em
            ``ConsultarStatusOperacional``.
        :rtype: satcfe.resposta.consultarstatusoperacional.RespostaConsultarStatusOperacional
        """  # noqa: E501
        resp = self._http_post('consultarstatusoperacional')
        conteudo = resp.json()
        return RespostaConsultarStatusOperacional.analisar(
                conteudo.get('retorno'))

    def consultar_numero_sessao(self, numero_sessao):
        """Sobrepõe :meth:`~satcfe.base.FuncoesSAT.consultar_numero_sessao`.

        :return: Uma resposta SAT que irá depender da sessão consultada.
        :rtype: satcfe.resposta.padrao.RespostaSAT
        """
        resp = self._http_post(
                'consultarnumerosessao',
                numero_sessao=numero_sessao)
        conteudo = resp.json()
        return RespostaConsultarNumeroSessao.analisar(conteudo.get('retorno'))

    def configurar_interface_de_rede(self, configuracao, *args, **kwargs):
        """Sobrepõe :meth:`~satcfe.base.FuncoesSAT.configurar_interface_de_rede`.

        :return: Uma resposta SAT padrão.
        :rtype: satcfe.resposta.padrao.RespostaSAT
        """
        conf = resolver_documento(configuracao, *args, **kwargs)
        resp = self._http_post('configurarinterfacederede', configuracao=conf)
        conteudo = resp.json()
        return RespostaSAT.configurar_interface_de_rede(
                conteudo.get('retorno'))

    def associar_assinatura(self, sequencia_cnpj, assinatura_ac):
        """Sobrepõe :meth:`~satcfe.base.FuncoesSAT.associar_assinatura`.

        :return: Uma resposta SAT padrão.
        :rtype: satcfe.resposta.padrao.RespostaSAT
        """
        resp = self._http_post(
                'associarassinatura',
                sequencia_cnpj=sequencia_cnpj,
                assinatura_ac=assinatura_ac)
        conteudo = resp.json()
        return RespostaAssociarAssinatura.analisar(conteudo.get('retorno'))

    def atualizar_software_sat(self):
        """Sobrepõe :meth:`~satcfe.base.FuncoesSAT.atualizar_software_sat`.

        :return: Uma resposta SAT padrão.
        :rtype: satcfe.resposta.padrao.RespostaSAT
        """
        resp = self._http_post('atualizarsoftwaresat')
        conteudo = resp.json()
        return RespostaSAT.atualizar_software_sat(conteudo.get('retorno'))

    def extrair_logs(self):
        """Sobrepõe :meth:`~satcfe.base.FuncoesSAT.extrair_logs`.

        :return: Uma resposta SAT especializada em ``ExtrairLogs``.
        :rtype: satcfe.resposta.extrairlogs.RespostaExtrairLogs
        """
        resp = self._http_post('extrairlogs')
        conteudo = resp.json()

Answer:         return RespostaExtrairLogs.analisar(conteudo.get('retorno'))