15. Exercício prático – versão 4

Retomamos aqui o exercício descrito no parágrafo |Versão 3| e agora o abordamos utilizando classes e interfaces. Escreveremos dois aplicativos:
A aplicação 1 será a seguinte:

Um script principal [main] instanciará uma camada [dao] e uma camada [métier]:
- a camada [dao] terá a função de gerenciar dados armazenados em arquivos de texto e, posteriormente, em um banco de dados;
- a camada [métier] terá a função de calcular o imposto;
Nesta aplicação, não haverá ações por parte do usuário: os dados dos contribuintes serão obtidos de um arquivo de texto cujo nome será fornecido ao módulo [main].
Na aplicação 2, será o usuário quem digitará os dados dos contribuintes. A arquitetura evoluirá, então, da seguinte forma:

- a camada [dao] (Data Access Object) é responsável pelo acesso aos dados externos
- a camada [métier] lida com as questões de negócio, neste caso, o cálculo do imposto. Ela não lida com os dados. Estes podem ter duas origens:
- a camada [dao] para os dados persistentes;
- a camada [ui] para os dados fornecidos pelo usuário.
- a camada [ui] (Interface do Usuário) lida com as interações com o usuário;
- [main] é o coordenador;
A seguir, as camadas [dao], [métier] e [ui] serão implementadas, cada uma, por meio de uma classe. As camadas [métier] e [dao] serão as mesmas para as duas aplicações. É por isso que elas foram reunidas em uma mesma versão do exercício prático.
15.1. Versão 4 – aplicação 1
A versão 4 calcula o imposto de uma lista de contribuintes contida em um arquivo de texto. Ela possui a seguinte arquitetura:

15.1.1. As entidades

As entidades são classes de dados. Sua função é encapsular dados e fornecer getters/setters que permitem verificar a validade desses dados. As entidades são trocadas entre as camadas. Uma mesma entidade pode partir da camada [ui] e chegar até a camada [dao] e vice-versa.
15.1.1.1. A classe [ImpôtsError]
Utilizaremos uma classe de exceção proprietária:
# -------------------------------
# classe de exceção
from MyException import MyException
class ImpôtsError(MyException):
pass
Assim que as camadas [métier] e [dao] encontrarem um problema, elas lançarão essa exceção. Ela deriva da classe [MyException]. Portanto, ela é utilizada da seguinte maneira: [raise ImpôtsError(code_erreur, msg_erreur)].
15.1.1.2. A classe [AdminData]
A classe [AdminData] encapsula as constantes envolvidas no cálculo do imposto:
from BaseEntity import BaseEntity
# dados da administração fiscal
class AdminData(BaseEntity):
# chaves excluídas do relatório da classe
excluded_keys = []
# chaves autorizadas
@staticmethod
def get_allowed_keys() -> list:
return [
"limites",
"coeffr",
"coeffn",
"plafond_qf_demi_part",
"plafond_revenus_celibataire_pour_reduction",
"plafond_revenus_couple_pour_reduction",
"valeur_reduc_demi_part",
"plafond_decote_celibataire",
"plafond_decote_couple",
"plafond_impot_couple_pour_decote",
"plafond_impot_celibataire_pour_decote",
"abattement_dixpourcent_max",
"abattement_dixpourcent_min"
]
- linha 5: a classe [AdminData] estende a classe [BaseEntity] descrita no parágrafo |BaseEntity|. Vale lembrar que as classes que estendem a classe [BaseEntity] devem definir:
- um atributo de classe [excluded_keys] (linha 7) que lista as propriedades do objeto excluídas quando o objeto é transformado em um dicionário;
- um método estático [get_allowed_keys] (linhas 10-26) que retorna a lista das propriedades aceitas quando o objeto é inicializado com um dicionário;
Não foram utilizados setters para verificar a validade dos dados utilizados para inicializar um objeto [AdminData]. De fato, esse objeto é único e definido por configuração e, portanto, não está sujeito a erros.
15.1.1.3. A classe [TaxPayer]
A classe [TaxPayer] modelará um contribuinte:
# importações
from BaseEntity import BaseEntity
from ImpôtsError import ImpôtsError
# um contribuinte
class TaxPayer(BaseEntity):
# modela um contribuinte
# ID: identificador
# casado: sim / não
# filhos: o número de filhos
# salário: seu salário anual
# imposto: valor do imposto a pagar
# sobretaxa: sobretaxa do imposto a pagar
# abatimento: abatimento do imposto a pagar
# redução: redução sobre o imposto a pagar
# alíquota: alíquota de imposto do contribuinte
# chaves excluídas do relatório da classe
excluded_keys = []
# chaves permitidas
@staticmethod
def get_allowed_keys() -> list:
return ['id', 'marié', 'enfants', 'salaire', 'impôt', 'surcôte', 'décôte', 'réduction', 'taux']
# propriedades
@property
def marié(self) -> str:
return self.__marié
@property
def enfants(self) -> int:
return self.__enfants
@property
def salaire(self) -> int:
return self.__salaire
@property
def impôt(self) -> int:
return self.__impôt
@property
def surcôte(self) -> int:
return self.__surcôte
@property
def décôte(self) -> int:
return self.__décôte
@property
def réduction(self) -> int:
return self.__réduction
@property
def taux(self) -> float:
return self.__taux
# setter
@marié.setter
def marié(self, marié: str):
ok = isinstance(marié, str)
if ok:
marié = marié.strip().lower()
ok = marié == "oui" or marié == "non"
if ok:
self.__marié = marié
else:
raise ImpôtsError(31, f"l'attribut marié [{marié}] doit avoir l'une des valeurs oui / non")
@enfants.setter
def enfants(self, enfants):
# filhos deve ser um inteiro >=0
try:
enfants = int(enfants)
erreur = enfants < 0
except:
erreur = True
if not erreur:
self.__enfants = enfants
else:
raise ImpôtsError(32, f"L'attribut enfants [{enfants}] doit être un entier >=0")
@salaire.setter
def salaire(self, salaire):
# salário deve ser um número inteiro >=0
try:
salaire = int(salaire)
erreur = salaire < 0
except:
erreur = True
if not erreur:
self.__salaire = salaire
else:
raise ImpôtsError(33, f"L'attribut salaire [{salaire}] doit être un entier >=0")
@impôt.setter
def impôt(self, impôt):
# imposto deve ser um número inteiro >=0
try:
impôt = int(impôt)
erreur = impôt < 0
except:
erreur = True
if not erreur:
self.__impôt = impôt
else:
raise ImpôtsError(34, f"L'attribut impôt [{impôt}] doit être un nombre >=0")
@décôte.setter
def décôte(self, décôte):
# desconto deve ser um número inteiro >=0
try:
décôte = int(décôte)
erreur = décôte < 0
except:
erreur = True
if not erreur:
self.__décôte = décôte
else:
raise ImpôtsError(35, f"L'attribut décôte [{décôte}] doit être un nombre >=0")
@surcôte.setter
def surcôte(self, surcôte):
# o acréscimo deve ser um número inteiro >= 0
try:
surcôte = int(surcôte)
erreur = surcôte < 0
except:
erreur = True
if not erreur:
self.__surcôte = surcôte
else:
raise ImpôtsError(36, f"L'attribut surcôte [{surcôte}] doit être un nombre >=0")
@réduction.setter
def réduction(self, réduction):
# o acréscimo deve ser um número inteiro >= 0
try:
réduction = int(réduction)
erreur = réduction < 0
except:
erreur = True
if not erreur:
self.__réduction = réduction
else:
raise ImpôtsError(37, f"L'attribut réduction [{réduction}] doit être un nombre >=0")
@taux.setter
def taux(self, taux):
# a taxa deve ser um número real >= 0
try:
taux = float(taux)
erreur = taux < 0
except:
erreur = True
if not erreur:
self.__taux = taux
else:
raise ImpôtsError(38, f"L'attribut taux [{taux}] doit être un nombre >=0")
Notas:
- a classe [TaxPayer] encapsula um contribuinte;
- linha 7: a classe [TaxPayer] deriva da classe [BaseEntity]. Portanto, ela possui o identificador [id];
- linha 20: nenhuma propriedade é excluída do estado de um objeto [AdminData];
- linhas 22-25: as propriedades da classe. Estas são explicadas nas linhas 9-17;
- linhas 27-58: getters dos atributos da classe;
- linhas 60-161: os setters dos atributos da classe. Vale lembrar que a vantagem de uma classe que encapsula dados em relação a um simples dicionário é que a classe pode verificar a validade de suas propriedades por meio de seus setters;
15.1.2. A camada [dao]

Vamos reunir as implementações das camadas em uma pasta [services]. Essas classes implementarão interfaces definidas na pasta [interfaces].

15.1.2.1. A interface [InterfaceImpôtsDao]
A camada [dao] implementará a seguinte interface [InterfaceImpôtsDao] (arquivo InterfaceImpôtsDao.py):
# importações
from abc import ABC, abstractmethod
# interface IImpôtsDao
from AdminData import AdminData
class InterfaceImpôtsDao(ABC):
# lista das faixas de imposto
@abstractmethod
def get_admindata(self) -> AdminData:
pass
# lista de dados dos contribuintes
@abstractmethod
def get_taxpayers_data(self) -> dict:
pass
# lançamento dos resultados do cálculo do imposto
@abstractmethod
def write_taxpayers_results(self, taxpayers_results: list):
pass
A interface define três métodos:
- [get_admindata]: é o método que obtém a tabela de faixas de imposto. Observe-se que não são fornecidas informações sobre como obter esses dados. Posteriormente, eles serão encontrados primeiro em um arquivo de texto e, em seguida, em um banco de dados. Caberá às classes que implementam a interface se adaptarem ao modo de armazenamento dos dados. Teremos, portanto, uma classe para recuperar as faixas de imposto de um arquivo de texto e outra para recuperá-las de um banco de dados. Ambas implementarão o método [get_admindata];
- [get_taxpayers_data]: é o método que obtém os dados dos contribuintes. Mais uma vez, não especificamos onde eles serão encontrados. Trataremos apenas o caso em que eles estejam em um arquivo de texto;
- [write_taxpayers_results]: é o método que irá armazenar os resultados do cálculo do imposto. Não especificamos onde. Abordaremos apenas o caso em que os resultados são armazenados em um arquivo de texto. O parâmetro [taxpayers_results] será a lista dos resultados a serem armazenados;
15.1.2.2. A classe [AbstractImpôtsDao]
A camada [dao] será implementada por duas classes:
- uma irá buscar os dados (contribuintes, resultados, faixas de imposto) em arquivos de texto;
- a outra irá buscar os dados (contribuintes, resultados) em arquivos de texto e as faixas de imposto em um banco de dados;
As duas classes diferirão apenas no gerenciamento das faixas de imposto. Os dados dos contribuintes e os resultados dos cálculos do imposto serão, por sua vez, gerenciados da mesma forma. Por esse motivo, vamos gerenciá-los em uma classe pai [AbstractImpôtsDao]. A particularidade do gerenciamento das faixas de imposto será, por sua vez, tratada em duas classes filhas:
- a classe [ImpôtsDaoWithAdminDataInJsonFile] irá buscar as faixas de imposto em um arquivo de texto no formato jSON;
- a classe [ImpôtsDaoWithAdminDataInDatabase] irá buscar as faixas de imposto em um banco de dados;
A classe pai [AbstractImpôtsDao] será a seguinte:
# importações
import codecs
import json
from abc import abstractmethod
from AdminData import AdminData
from ImpôtsError import ImpôtsError
from InterfaceImpôtsDao import InterfaceImpôtsDao
from TaxPayer import TaxPayer
# classe base para a camada [dao]
class AbstractImpôtsDao(InterfaceImpôtsDao):
# os contribuintes e seus impostos estarão em arquivos de texto
# construtor
def __init__(self, config: dict):
# config[taxpayersFilename]: o nome do arquivo de texto dos contribuintes
# config[resultsFilename]: o nome do arquivo jSON dos resultados
# config[errorsFilename]: o nome do arquivo de erros
#: os parâmetros são salvos
self.taxpayers_filename = config.get("taxpayersFilename")
self.taxpayers_results_filename = config.get("resultsFilename")
self.errors_filename = config.get("errorsFilename")
# ------------------
# interface IImpôtsDao
# ------------------
# lista de dados dos contribuintes
def get_taxpayers_data(self) -> dict:
…
# lançamento do imposto dos contribuintes
def write_taxpayers_results(self, taxpayers: list):
…
# leitura das faixas de imposto
@abstractmethod
def get_admindata(self) -> AdminData:
pass
-
linha 13: a classe [AbstractImpôtsDao] implementa a interface [InterfaceImpôtsDao]. Portanto, encontramos os três métodos dessa interface:
- [get_taxpayers_data]: linha 31;
- [write_taxpayers_results]: linha 35;
- [get_admindata]: linha 40. Esse método não será implementado pela classe [AbstractImpôtsDao], por isso é declarado como abstrato (linha 39);
-
linha 16: o construtor recebe um dicionário [config] contendo as seguintes informações:
- [taxpayersFilename]: o nome do arquivo de texto que contém os dados dos contribuintes;
- [resultsFilename]: o nome do arquivo de texto no qual serão gravados os resultados;
- [errorsFilename]: o nome do arquivo de texto que lista os erros encontrados durante o processamento do arquivo [taxpayersFilename];
O método [get_taxpayers_data] é o seguinte:
# lista de dados dos contribuintes
def get_taxpayers_data(self) -> dict:
# inicializações
taxpayers_data = []
datafile = None
erreurs = []
try:
# abertura do arquivo de dados
datafile = open(self.taxpayers_filename, "r")
# processamento da linha atual do arquivo
ligne = datafile.readline()
# número da linha
numligne = 0
while ligne != '':
# uma linha de +
numligne += 1
# remoção de espaços em branco
ligne = ligne.strip()
# ignora-se as linhas vazias e os comentários
if ligne != "" and ligne[0] != "#":
try:
# recuperam-se os 4 campos id, casado, filhos, salário que compõem a linha do contribuinte
(id, marié, enfants, salaire) = ligne.split(",")
# cria-se um novo TaxPayer
taxpayers_data.append(
TaxPayer().fromdict({'id': id, 'marié': marié, 'enfants': enfants, 'salaire': salaire}))
except BaseException as erreur:
# registra-se o erro
erreurs.append(f"Ligne {numligne}, {erreur}")
# lê-se uma nova linha de contribuinte
ligne = datafile.readline()
# registram-se os erros, se houver
if erreurs:
text = f"Analyse du fichier {self.taxpayers_filename}\n\n" + "\n".join(erreurs)
with codecs.open(self.errors_filename, "w", "utf-8") as fd:
fd.write(text)
# retorna-se o resultado
return {"taxpayers": taxpayers_data, "erreurs": erreurs}
except BaseException as erreur:
# lança-se uma exceção ImpôtsError
raise ImpôtsError(11, f"{erreur}")
finally:
# fecha-se o arquivo
if datafile:
datafile.close()
- linha 4: os dados dos contribuintes (estado civil, filhos, salário) serão colocados em uma lista de objetos do tipo [TaxPayer];
- linhas 8-9: abre-se o arquivo de texto dos contribuintes em modo de leitura. Seu conteúdo tem o seguinte formato:
Em relação às versões anteriores:
- cada linha do arquivo [taxpayersFilename] começa com o identificador do contribuinte, um simples número;
- comentários e linhas em branco são permitidos;
- vamos lidar com os erros. Assim, as linhas 17, 19 e 21 devem ser declaradas como erradas. Os erros são registrados em um arquivo separado;
Vamos continuar a análise do código:
- linha 4: os dados do arquivo de texto são transferidos para a lista [taxPayersData];
- linhas 14-31: o arquivo dos contribuintes é lido linha por linha;
- linha 14: chega-se ao fim do arquivo quando se lê uma linha vazia (nada — nem mesmo o caractere de fim de linha \r\n);
- linha 20: as linhas vazias e os comentários são ignorados. Uma linha é considerada um comentário se, após a remoção dos espaços à frente e atrás do texto, o primeiro caractere for o símbolo #;
- linha 24: uma linha correta é composta por quatro campos separados por uma vírgula. Esses campos são recuperados. A atribuição de dados a uma tupla de quatro elementos falha se não houver exatamente quatro dados atribuídos;
- linha 25: se um dos quatro campos recuperados [id, marié, enfants, salaire] for inválido, o método [BaseEntity.fromdict] lançará uma exceção do tipo [MyException];
- linhas 25-26: um objeto [TaxPayer] é adicionado à lista [taxpayers_data] de contribuintes;
- linhas 27-29: os eventuais erros são acumulados em uma lista [erreurs]. Essa lista foi criada na linha 6;
- linhas 33-36: a lista de erros encontrados é registrada no arquivo de texto [errorsFilename]. Existem dois tipos de erros:
- uma linha não possuía o número correto de campos esperados;
- as informações da linha estavam incorretas e não foi possível construir um objeto [TaxPayer];
- linhas 39-41: todo erro (BaseException) é interceptado e reportado, encapsulado em um tipo [ImpôtsError];
- linhas 42-45: em todos os casos, seja de sucesso ou de falha, o arquivo de texto dos contribuintes é fechado, caso tenha sido aberto;
O método [write_taxpayers_results] deve gerar um arquivo jSON no formato:
[
{
"id": 1,
"marié": "oui",
"enfants": 2,
"salaire": 55555,
"impôt": 2814,
"surcôte": 0,
"taux": 0.14,
"décôte": 0,
"réduction": 0
},
{
"id": 2,
"marié": "oui",
"enfants": 2,
"salaire": 50000,
"impôt": 1384,
"surcôte": 0,
"taux": 0.14,
"décôte": 384,
"réduction": 347
},
{
"id": 3,
"marié": "oui",
"enfants": 3,
"salaire": 50000,
"impôt": 0,
"surcôte": 0,
"taux": 0.14,
"décôte": 720,
"réduction": 0
},
…
]
O método [write_taxpayers_results] é o seguinte:
# registro do imposto dos contribuintes
def write_taxpayers_results(self, taxpayers: list):
# gravação dos resultados em um arquivo jSON
# contribuintes: lista de objetos do tipo TaxPayer
# (ID, casado, filhos, salário, imposto, sobretaxa, abatimento, redução, alíquota)
# a lista [taxpayers] está gravada no arquivo de texto [self.taxpayers_results_filename]
file = None
try:
# abertura do arquivo de resultados
file = codecs.open(self.taxpayers_results_filename, "w", "utf8")
# criação da lista a ser serializada em jSON
mapping = map(lambda taxpayer: taxpayer.asdict(), taxpayers)
# serialização jSON
json.dump(list(mapping), file, ensure_ascii=False)
except BaseException as erreur:
# relançamento do erro com outro tipo
raise ImpôtsError(12, f"{erreur}")
finally:
# fecha-se o arquivo, caso ele tenha sido aberto
if file:
file.close()
- linha 2: o método recebe uma lista de contribuintes [taxpayers], que deve gravar no arquivo de texto [self.taxpayers_results_filename] no formato jSON;
- linha 10: criação do arquivo UTF-8 com os resultados;
- linha 12: aqui introduzimos a função [map], cuja sintaxe neste caso é [map (fonction, liste1)]. A função [fonction] é aplicada a cada elemento de [liste1] e gera um novo elemento que alimenta uma lista [liste2]. Por fim, para cada i:
liste2[i]=fonction(liste1[i])
Aqui, [liste1] é a lista [taxPayers], uma lista de objetos do tipo [TaxPayer]. A função [fonction] é expressa aqui na forma de uma função denominada [lambda], que expressa a transformação realizada em um elemento [taxpayer] da lista [taxpayers]: cada elemento [taxpayer] é substituído por seu dicionário [taxpayer.asdict()]. Por fim, a lista [liste2] obtida é a lista dos dicionários dos elementos da lista [taxpayers];
- linha 12: o resultado retornado pela função [map] não é a lista [liste2], mas um objeto do tipo [map]. Para obter [liste2], é preciso usar a expressão [list(mapping)] (linha 14);
- linha 14: a lista [liste2] é gravada no formato jSON no arquivo [self.taxpayers_results_filename];
- linhas 15-17: qualquer tipo de exceção é interceptada e encapsulada em um erro do tipo [ImpôtsError] antes de ser relançada (linha 17);
- linhas 19-21: em todos os casos, seja de sucesso ou de falha, o arquivo de resultados é fechado caso tenha sido aberto;
15.1.2.3. Classe [ImpôtsDaoWithAdminDataInJsonFile]
A classe [ImpôtsDaoWithAdminDataInJsonFile] derivará da classe [AbstractImpôtsDao] e implementará o método [getAdminData], que sua classe pai não implementou. Ela irá buscar os dados da administração fiscal em um arquivo jSON:
{
"limites": [9964, 27519, 73779, 156244, 0],
"coeffr": [0, 0.14, 0.3, 0.41, 0.45],
"coeffn": [0, 1394.96, 5798, 13913.69, 20163.45],
"plafond_qf_demi_part": 1551,
"plafond_revenus_celibataire_pour_reduction": 21037,
"plafond_revenus_couple_pour_reduction": 42074,
"valeur_reduc_demi_part": 3797,
"plafond_decote_celibataire": 1196,
"plafond_decote_couple": 1970,
"plafond_impot_couple_pour_decote": 2627,
"plafond_impot_celibataire_pour_decote": 1595,
"abattement_dixpourcent_max": 12502,
"abattement_dixpourcent_min": 437
}
A classe [ImpôtsDaoWithAdminDataInJsonFile] é a seguinte:
# importações
import codecs
import json
from AbstractImpôtsDao import AbstractImpôtsDao
from AdminData import AdminData
from ImpôtsError import ImpôtsError
# uma implementação da camada [dao], na qual os dados da administração fiscal estão em um arquivo jSON
class ImpôtsDaoWithAdminDataInJsonFile(AbstractImpôtsDao):
# construtor
def __init__(self, config: dict):
# config[admindataFilename]: o nome do arquivo jSON que contém os dados da administração fiscal
# config[taxpayersFilename]: o nome do arquivo de texto dos contribuintes
# config[resultsFilename]: o nome do arquivo jSON dos resultados
# config[errorsFilename]: o nome do arquivo de erros
# inicialização da classe Parent
AbstractImpôtsDao.__init__(self, config)
# leitura dos dados da administração fiscal
file = None
try:
# abertura do arquivo jSON com os dados fiscais em modo de leitura
file = codecs.open(config["admindataFilename"], "r", "utf8")
# transferência do conteúdo do arquivo jSON para um objeto [AdminData]
self.admindata = AdminData().fromdict(json.load(file))
except BaseException as erreur:
# o erro é reenviado na forma de um tipo [ImpôtsError]
raise ImpôtsError(21, f"{erreur}")
finally:
# fechamento do arquivo, caso ele tenha sido aberto
if file:
file.close()
# -------------
# interface
# -------------
# recuperação dos dados da administração fiscal
# o método retorna um objeto [AdminData]
def get_admindata(self) -> AdminData:
return self.admindata
- linha 11: a classe [ImpôtsDaoWithAdminDataInJsonFile] herda da classe [AbstractImpôtsDao]. Por isso, ela implementa a interface [InterfaceImpôtsDao];
- linha 13: o construtor recebe como parâmetro um dicionário contendo as informações das linhas 14 a 17;
- linha 20: a classe pai é inicializada;
- linha 24: abertura do arquivo jSON com os dados da administração fiscal;
- linha 25: o arquivo UTF-8 com os dados da administração fiscal é aberto;
- linha 27: o conteúdo do arquivo é lido e colocado no objeto [self.admindata] do tipo [AdminData]. É necessário que as chaves do arquivo jSON correspondam às propriedades aceitas para um objeto [AdminData]; caso contrário, o método [fromdict] lançará uma exceção;
- linhas 28-30: gerenciamento de exceções. As exceções que podem ocorrer são encapsuladas em um tipo [ImpôtsError] antes de serem relançadas;
- linhas 32-34: o arquivo é fechado, caso tenha sido aberto;
- linhas 42-43: implementação do método [get_admindata] da interface [InterfaceImpôtsDao];
15.1.3. A camada [métier]

15.1.3.1. A interface [InterfaceImpôtsMétier]
A interface da camada [métier] será a seguinte:
# importações
from abc import ABC, abstractmethod
from AdminData import AdminData
from TaxPayer import TaxPayer
# interface IImpôtsMétier
class InterfaceImpôtsMétier(ABC):
# cálculo do imposto para 1 contribuinte
@abstractmethod
def calculate_tax(self, taxpayer: TaxPayer, admindata: AdminData):
pass
- A interface [InterfaceImpôtsMétier] define um único método:
- linha 12: o método [calculate_tax] permite calcular o imposto de um único contribuinte [taxpayer]. [admindata] é o objeto [AdminData] que encapsula os dados da administração fiscal;
- linha 12: o método [calculate_tax] não retorna nenhum resultado. Os dados obtidos (imposto, sobretaxa, abatimento, redução, alíquota) estão incluídos no parâmetro [taxpayer]: antes da chamada, esses atributos estão vazios; após a chamada, eles foram inicializados;
15.1.3.2. A classe [ImpôtsMétier]
A classe [ImpôtsMétier] implementa a interface [InterfaceImpôtsMétier] da seguinte maneira:

Os métodos da classe são provenientes do módulo [impôts_module_02] do parágrafo |O módulo [impots.v02.modules.impôts_module_02]|. Limitamos os parâmetros dos métodos a apenas dois:
- taxpayer(id, casado, filhos, salário, imposto, abatimento, sobretaxa, redução, alíquota): o objeto que representa um contribuinte e seu imposto;
- admindata: o objeto que encapsula os dados da administração tributária;
Mostramos em um método as alterações assim realizadas;
# cálculo do imposto – fase 1
# ----------------------------------------
def calculate_tax(self, taxpayer: TaxPayer, admindata: AdminData):
# contribuinte (ID, casado, filhos, salário, imposto, abatimento, acréscimo, redução, alíquota)
# dados administrativos: dados da administração tributária
# cálculo do imposto com filhos
self.calculate_tax_2(taxpayer, admindata)
# os resultados estão em taxpayer
taux1 = taxpayer.taux
surcôte1 = taxpayer.surcôte
impot1 = taxpayer.impôt
# cálculo do imposto sem filhos
if taxpayer.enfants != 0:
# cálculo do imposto para o mesmo contribuinte sem filhos
taxpayer2 = TaxPayer().fromdict(
{'id': 0, 'marié': taxpayer.marié, 'enfants': 0, 'salaire': taxpayer.salaire})
self.calculate_tax_2(taxpayer2, admindata)
# os resultados estão em taxpayer2
taux2 = taxpayer2.taux
surcôte2 = taxpayer2.surcôte
impot2 = taxpayer2.impôt
# aplicação do limite máximo do quociente familiar
if taxpayer.enfants < 3:
# PLAFOND_QF_DEMI_PART euros para os dois primeiros filhos
impot2 = impot2 - taxpayer.enfants * admindata.plafond_qf_demi_part
else:
# PLAFOND_QF_DEMI_PART euros para os dois primeiros filhos, o dobro para os seguintes
impot2 = impot2 - 2 * admindata.plafond_qf_demi_part - (taxpayer.enfants - 2) \
* 2 * admindata.plafond_qf_demi_part
else:
# se o contribuinte não tiver filhos, então imposto2 = imposto1
impot2 = impot1
# considera-se o imposto mais alto, com a alíquota e a sobretaxa correspondentes
(impot, surcôte, taux) = (impot1, surcôte1, taux1) if impot1 >= impot2 else (
impot2, impot2 - impot1 + surcôte2, taux2)
# resultados parciais
taxpayer.impôt = impot
taxpayer.surcôte = surcôte
taxpayer.taux = taux
# cálculo de uma eventual redução
self.get_décôte(taxpayer, admindata)
taxpayer.impôt -= taxpayer.décôte
# cálculo de uma eventual redução de impostos
self.get_réduction(taxpayer, admindata)
taxpayer.impôt -= taxpayer.réduction
# resultado
taxpayer.impôt = math.floor(taxpayer.impôt)
- linha 3: o método [calculate_tax] é o único método da interface [InterfaceImpôtsMétier]. Ele aceita dois parâmetros:
- [tapPayer]: o contribuinte cujo imposto está sendo calculado;
- [admindata]: o objeto que encapsula os dados da administração tributária;
- os resultados do cálculo são encapsulados no parâmetro [taxpayer] (linhas 40-50). O conteúdo desse objeto, portanto, não é o mesmo antes e depois da chamada ao método;
15.1.4. Testes das camadas [dao] e [métier]

- [TestDaoMétier] é a classe de teste UnitTest das camadas [dao] e [métier];
- [config] é o arquivo de configuração dos testes;
A configuração [config] é a seguinte:
def configure():
import os
# etapa 1 ------
# configuração do caminho do Python
# pasta deste arquivo
script_dir = os.path.dirname(os.path.abspath(__file__))
# root_dir
root_dir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes"
# dependências essenciais do aplicativo
absolute_dependencies = [
f"{script_dir}/../entities",
f"{script_dir}/../interfaces",
f"{script_dir}/../services",
f"{root_dir}/02/entities",
]
# definimos o syspath
from myutils import set_syspath
set_syspath(absolute_dependencies)
# etapa 2 ------
# configuração do aplicativo
config = {
# caminhos absolutos dos arquivos da aplicação
"admindataFilename": f"{script_dir}/../data/input/admindata.json"
}
# instanciação das camadas do aplicativo
from ImpôtsDaoWithAdminDataInJsonFile import ImpôtsDaoWithAdminDataInJsonFile
from ImpôtsMétier import ImpôtsMétier
dao = ImpôtsDaoWithAdminDataInJsonFile(config)
métier = ImpôtsMétier()
# colocamos as instâncias das camadas na configuração
config["dao"] = dao
config["métier"] = métier
# geramos a configuração
return config
- linhas 4-23: configura-se o Python Path dos testes;
- linhas 32-41: instanciamos as camadas [dao] e [métier]. Colocamos suas referências no dicionário [config];
- linha 44: retorna-se esse dicionário;
A classe de teste [TestDaoMétier] é a seguinte:
import unittest
def get_config() -> dict:
# configuração da aplicação
import config
# a configuração é restaurada
return config.configure()
class TestDaoMétier(unittest.TestCase):
# executada antes de cada método test_
def setUp(self) -> None:
# recuperamos a configuração dos testes
config = get_config()
# armazenamos algumas informações
self.métier = config['métier']
self.admindata = config['dao'].get_admindata()
def test_1(self) -> None:
from TaxPayer import TaxPayer
# {'casado': 'sim', 'filhos': 2, 'salário': 55555,
# 'imposto': 2814, 'sobretaxa': 0, 'abatimento': 0, 'redução': 0, 'alíquota': 0,14}
taxpayer = TaxPayer().fromdict({"marié": "oui", "enfants": 2, "salaire": 55555})
self.métier.calculate_tax(taxpayer, self.admindata)
# verificação
self.assertAlmostEqual(taxpayer.impôt, 2815, delta=1)
self.assertEqual(taxpayer.décôte, 0)
self.assertEqual(taxpayer.réduction, 0)
self.assertAlmostEqual(taxpayer.taux, 0.14, delta=0.01)
self.assertEqual(taxpayer.surcôte, 0)
def test_2(self) -> None:
from TaxPayer import TaxPayer
# {'casado': 'sim', 'filhos': 2, 'salário': 50000,
# 'imposto': 1384, 'sobretaxa': 0, 'abatimento': 384, 'redução': 347, 'alíquota': 0,14}
taxpayer = TaxPayer().fromdict({'marié': 'oui', 'enfants': 2, 'salaire': 50000})
self.métier.calculate_tax(taxpayer, self.admindata)
# verificações
self.assertAlmostEqual(taxpayer.impôt, 1384, delta=1)
self.assertAlmostEqual(taxpayer.décôte, 384, delta=1)
self.assertAlmostEqual(taxpayer.réduction, 347, delta=1)
self.assertAlmostEqual(taxpayer.taux, 0.14, delta=0.01)
self.assertEqual(taxpayer.surcôte, 0)
def test_3(self) -> None:
from TaxPayer import TaxPayer
# {'casado': 'sim', 'filhos': 3, 'salário': 50000,
# 'imposto': 0, 'sobretaxa': 0, 'desconto': 720, 'redução': 0, 'alíquota': 0,14}
taxpayer = TaxPayer().fromdict({'marié': 'oui', 'enfants': 3, 'salaire': 50000})
self.métier.calculate_tax(taxpayer, self.admindata)
# verificações
self.assertEqual(taxpayer.impôt, 0)
self.assertAlmostEqual(taxpayer.décôte, 720, delta=1)
self.assertEqual(taxpayer.réduction, 0)
self.assertAlmostEqual(taxpayer.taux, 0.14, delta=0.01)
self.assertEqual(taxpayer.surcôte, 0)
def test_4(self) -> None:
from TaxPayer import TaxPayer
# {'casado': 'não', 'filhos': 2, 'salário': 100.000,
# 'imposto': 19884, 'sobretaxa': 4480, 'desconto': 0, 'redução': 0, 'alíquota': 0,41}
taxpayer = TaxPayer().fromdict({'marié': 'non', 'enfants': 2, 'salaire': 100000})
self.métier.calculate_tax(taxpayer, self.admindata)
# verificações
self.assertAlmostEqual(taxpayer.impôt, 19884, delta=1)
self.assertEqual(taxpayer.décôte, 0)
self.assertEqual(taxpayer.réduction, 0)
self.assertAlmostEqual(taxpayer.taux, 0.41, delta=0.01)
self.assertAlmostEqual(taxpayer.surcôte, 4480, delta=1)
def test_5(self) -> None:
from TaxPayer import TaxPayer
# {'casado': 'não', 'filhos': 3, 'salário': 100000,
# 'imposto': 16782, 'sobretaxa': 7176, 'desconto': 0, 'redução': 0, 'alíquota': 0,41}
taxpayer = TaxPayer().fromdict({'marié': 'non', 'enfants': 3, 'salaire': 100000})
self.métier.calculate_tax(taxpayer, self.admindata)
# verificações
self.assertAlmostEqual(taxpayer.impôt, 16782, delta=1)
self.assertEqual(taxpayer.décôte, 0)
self.assertEqual(taxpayer.réduction, 0)
self.assertAlmostEqual(taxpayer.taux, 0.41, delta=0.01)
self.assertAlmostEqual(taxpayer.surcôte, 7176, delta=1)
def test_6(self) -> None:
from TaxPayer import TaxPayer
# {'casado': 'sim', 'filhos': 3, 'salário': 100.000,
# 'imposto': 9200, 'sobretaxa': 2180, 'desconto': 0, 'redução': 0, 'alíquota': 0,3}
taxpayer = TaxPayer().fromdict({'marié': 'oui', 'enfants': 3, 'salaire': 100000})
self.métier.calculate_tax(taxpayer, self.admindata)
# verificações
self.assertAlmostEqual(taxpayer.impôt, 9200, delta=1)
self.assertEqual(taxpayer.décôte, 0)
self.assertEqual(taxpayer.réduction, 0)
self.assertAlmostEqual(taxpayer.taux, 0.3, delta=0.01)
self.assertAlmostEqual(taxpayer.surcôte, 2180, delta=1)
def test_7(self) -> None:
from TaxPayer import TaxPayer
# {'casado': 'sim', 'filhos': 5, 'salário': 100000,
# 'imposto': 4230, 'sobretaxa': 0, 'desconto': 0, 'redução': 0, 'alíquota': 0,14}
taxpayer = TaxPayer().fromdict({'marié': 'oui', 'enfants': 5, 'salaire': 100000})
self.métier.calculate_tax(taxpayer, self.admindata)
# verificações
self.assertAlmostEqual(taxpayer.impôt, 4230, delta=1)
self.assertEqual(taxpayer.décôte, 0)
self.assertEqual(taxpayer.réduction, 0)
self.assertAlmostEqual(taxpayer.taux, 0.14, delta=0.01)
self.assertEqual(taxpayer.surcôte, 0)
def test_8(self) -> None:
from TaxPayer import TaxPayer
# {'casado': 'não', 'filhos': 0, 'salário': 100000,
# 'imposto': 22986, 'sobretaxa': 0, 'desconto': 0, 'redução': 0, 'alíquota': 0,41}
taxpayer = TaxPayer().fromdict({'marié': 'non', 'enfants': 0, 'salaire': 100000})
self.métier.calculate_tax(taxpayer, self.admindata)
# verificações
self.assertAlmostEqual(taxpayer.impôt, 22986, delta=1)
self.assertEqual(taxpayer.décôte, 0)
self.assertEqual(taxpayer.réduction, 0)
self.assertAlmostEqual(taxpayer.taux, 0.41, delta=0.01)
self.assertEqual(taxpayer.surcôte, 0)
def test_9(self) -> None:
from TaxPayer import TaxPayer
# {'casado': 'sim', 'filhos': 2, 'salário': 30000,
# 'imposto': 0, 'sobretaxa': 0, 'abatimento': 0, 'redução': 0, 'alíquota': 0}
taxpayer = TaxPayer().fromdict({'marié': 'oui', 'enfants': 2, 'salaire': 30000})
self.métier.calculate_tax(taxpayer, self.admindata)
# verificações
self.assertEqual(taxpayer.impôt, 0)
self.assertEqual(taxpayer.décôte, 0)
self.assertEqual(taxpayer.réduction, 0)
self.assertAlmostEqual(taxpayer.taux, 0.0, delta=0.01)
self.assertEqual(taxpayer.surcôte, 0)
def test_10(self) -> None:
from TaxPayer import TaxPayer
# {'casado': 'não', 'filhos': 0, 'salário': 200000,
# 'imposto': 64210, 'sobretaxa': 7498, 'desconto': 0, 'redução': 0, 'alíquota': 0,45}
taxpayer = TaxPayer().fromdict({'marié': 'non', 'enfants': 0, 'salaire': 200000})
self.métier.calculate_tax(taxpayer, self.admindata)
# verificações
self.assertAlmostEqual(taxpayer.impôt, 64210, 1)
self.assertEqual(taxpayer.décôte, 0)
self.assertEqual(taxpayer.réduction, 0)
self.assertAlmostEqual(taxpayer.taux, 0.45, delta=0.01)
self.assertAlmostEqual(taxpayer.surcôte, 7498, delta=1)
def test_11(self) -> None:
from TaxPayer import TaxPayer
# {'casado': 'sim', 'filhos': 3, 'salário': 200000,
# 'imposto': 42842, 'sobretaxa': 17283, 'desconto': 0, 'redução': 0, 'alíquota': 0,41}
taxpayer = TaxPayer().fromdict({'marié': 'oui', 'enfants': 3, 'salaire': 200000})
self.métier.calculate_tax(taxpayer, self.admindata)
# verificações
self.assertAlmostEqual(taxpayer.impôt, 42842, 1)
self.assertEqual(taxpayer.décôte, 0)
self.assertEqual(taxpayer.réduction, 0)
self.assertAlmostEqual(taxpayer.taux, 0.41, delta=0.01)
self.assertAlmostEqual(taxpayer.surcôte, 17283, delta=1)
if __name__ == '__main__':
unittest.main()
Comentários
- linha 11: a classe de teste estende a classe [unittest.TestCase];
- linhas 13-19: em um teste UnitTest, o método [setUp] é executado antes de cada um dos métodos [test_];
- linha 16: recupera-se a configuração proveniente do script [config] analisado anteriormente;
- linha 18: armazena-se uma referência na camada [métier];
- linha 19: solicita-se à camada [dao] o objeto [AdminData] que encapsula os dados da administração fiscal e este é armazenado;
- linhas 21-173: 11 testes cujos resultados foram verificados no site oficial da Receita Federal de 2019 |https://www3.impots.gouv.fr/simulateur/calcul_impot/2019/simplifie/index.htm|;
- linhas 21-33: todos os testes foram elaborados seguindo o mesmo modelo;
- linha 22: importa-se a classe [TaxPayer];
- linha 24: contribuinte testado;
- linha 25: resultados esperados;
- linha 26: criação do objeto [TaxPayer] do contribuinte;
- linha 27: cálculo do imposto. O resultado está em [taxpayer];
- linhas 29-33: verificação dos resultados obtidos;
- linha 29: verifica-se o valor do imposto com precisão de um euro. Os testes demonstraram, de fato, que os resultados obtidos pelo algoritmo deste documento podem diferir dos valores oficiais em, no máximo, 1 euro;
A execução dos testes fornece os seguintes resultados:

15.1.5. Script principal

O script principal é configurado pelo seguinte script [config]:
def configure():
import os
# etapa 1 ------
# configuração do caminho do Python
# pasta deste arquivo
script_dir = os.path.dirname(os.path.abspath(__file__))
# root_dir
root_dir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes"
# dependências do aplicativo
absolute_dependencies = [
# dependências locais
f"{script_dir}/../../entities",
f"{script_dir}/../../interfaces",
f"{script_dir}/../../services",
f"{root_dir}/02/entities",
]
# definimos o syspath
from myutils import set_syspath
set_syspath(absolute_dependencies)
# etapa 2 ------
# configuração do aplicativo
config = {
# caminhos absolutos dos arquivos do aplicativo
"taxpayersFilename": f"{script_dir}/../../data/input/taxpayersdata.txt",
"resultsFilename": f"{script_dir}/../../data/output/résultats.json",
"admindataFilename": f"{script_dir}/../../data/input/admindata.json",
"errorsFilename": f"{script_dir}/../../data/output/errors.txt"
}
# instanciação das camadas da aplicação
from ImpôtsDaoWithAdminDataInJsonFile import ImpôtsDaoWithAdminDataInJsonFile
from ImpôtsMétier import ImpôtsMétier
dao = ImpôtsDaoWithAdminDataInJsonFile(config)
métier = ImpôtsMétier()
# colocamos as instâncias das camadas na configuração
config["dao"] = dao
config["métier"] = métier
# geramos a configuração
return config
Ele é semelhante ao utilizado para o teste das camadas [métier] e [dao].
O script principal [main.py] é o seguinte:
# configuramos a aplicação
import config
config = config.configure()
# importações
from ImpôtsError import ImpôtsError
# recuperam-se as camadas da aplicação (elas já estão instanciadas)
dao = config["dao"]
métier = config["métier"]
try:
# recuperação das faixas de imposto
admindata = dao.get_admindata()
# leitura dos dados dos contribuintes
taxpayers = dao.get_taxpayers_data()["taxpayers"]
# dos contribuintes?
if not taxpayers:
raise ImpôtsError(51, f"Pas de contribuables valides dans le fichier {config['taxpayersFilename']}")
# cálculo do imposto dos contribuintes
for taxpayer in taxpayers:
# O contribuinte é tanto um parâmetro de entrada quanto de saída
# o contribuinte será modificado
métier.calculate_tax(taxpayer, admindata)
# gravação dos resultados em um arquivo de texto
dao.write_taxpayers_results(taxpayers)
except ImpôtsError as erreur:
# exibição do erro
print(f"L'erreur suivante s'est produite : {erreur}")
finally:
# concluído
print("Travail terminé...")
Notas
- linhas 2-4: recupera-se a configuração do aplicativo. Sabe-se também que o Python Path do aplicativo foi construído;
- linhas 9-11: recuperam-se referências às camadas [métier] e [dao];
- linha 15: obtêm-se os dados da administração fiscal;
- linha 17: obtém-se a lista de contribuintes cujo imposto deve ser calculado;
- linhas 19-20: se essa lista estiver vazia, é gerada uma exceção;
- linhas 22-25: cálculo do imposto dos diferentes objetos [taxpayer] por meio da camada [métier];
- linha 27: [taxpayers] passa a ser uma lista de objetos [TaxPayer] em que os atributos (imposto, desconto, sobretaxa, redução, alíquota) receberam um valor. Essa lista é gravada em um arquivo jSON;
- linhas 28-30: detecção de um possível erro;
- linhas 31-33: executadas em todos os casos;
A execução do script produz os mesmos resultados que nas versões anteriores. O arquivo de erros dos contribuintes foi uma novidade nesta versão. Após a execução do script [main], seu conteúdo é o seguinte:
Analyse du fichier C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\impots\v04\main\01/../../data/input/taxpayersdata.txt
Ligne 17, not enough values to unpack (expected 4, got 2)
Ligne 19, too many values to unpack (expected 4)
Ligne 21, MyException[1, L'identifiant d'une entité <class 'TaxPayer.TaxPayer'> doit être un entier >=0]
As linhas com erros eram as seguintes:
15.2. Versão 4 – aplicação 2
Nesta versão, é o usuário que, por meio do teclado, fornece a lista de contribuintes. A arquitetura da aplicação será a seguinte:

Surge um novo módulo: a camada [ui] (Interface do Usuário), que se comunicará com o usuário. Essa camada terá uma interface e será implementada por uma classe.

15.2.1. A interface [InterfaceImpôtsUi]
# importações
from abc import ABC, abstractmethod
# interface InterfaceImpôtsUI
class InterfaceImpôtsUi(ABC):
# execução da classe que implementa a interface
@abstractmethod
def run(self):
pass
A interface [InterfaceImpôtsUi] terá apenas um método, o das linhas 8 a 10. A interface será implementada aqui com um aplicativo de console, mas também poderia ser implementada com uma interface gráfica. Os parâmetros passados ao método [run] não seriam os mesmos nas duas implementações. Para contornar esse problema, o procedimento habitual é:
- não passar parâmetros para o método [run] (ou o mínimo de parâmetros);
- passar parâmetros ao construtor da classe que implementa a interface. Eles podem variar de uma implementação para outra. Esses parâmetros são registrados como atributos da classe;
- fazer com que o método [run] utilize esses atributos de classe (self.x);
Esse método permite ter uma interface muito geral, que é especificada pelos parâmetros dos construtores de cada classe de implementação. Esse método já foi utilizado na versão modular nº 1.
15.2.2. A classe [ImpôtsConsole]
A classe [ImpôtsConsole] implementa a interface [InterfaceImpôtsUi] da seguinte maneira:
# importações
import re
from InterfaceImpôtsUi import InterfaceImpôtsUi
from TaxPayer import TaxPayer
# camada [UI]
class ImpôtsConsole(InterfaceImpôtsUi):
# construtor
def __init__(self, config: dict):
# armazenamento dos parâmetros
self.admindata = config['dao'].get_admindata()
self.métier = config['métier']
def run(self):
# diálogo interativo com o usuário
fini = False
while not fini:
# o contribuinte é casado?
marié = input("Le contribuable est-il marié / pacsé (oui/non) (* pour arrêter) : ").strip().lower()
# verificação da validade dos dados inseridos
while marié != "oui" and marié != "non" and marié != "*":
# mensagem de erro
print("Tapez oui ou non ou *")
# repete a pergunta
marié = input("Le contribuable est-il marié / pacsé (oui/non) (* pour arrêter) : ").strip().lower()
# Concluído?
if marié == "*":
# diálogo encerrado
return
# número de filhos
enfants = input("Nombre d'enfants : ").strip()
# verificação da validade da entrada
if not re.match(r"^\d+$", enfants):
# mensagem de erro
print("Tapez un nombre entier positif ou nul")
# vamos tentar novamente
enfants = input("Nombre d'enfants : ").strip()
# salário anual
salaire = input("Salaire annuel : ").strip()
# verificação da validade dos dados inseridos
if not re.match(r"^\d+$", salaire):
# mensagem de erro
print("Tapez un nombre entier positif ou nul")
# tentando novamente
salaire = input("Salaire annuel : ").strip()
# cálculo do imposto
taxpayer = TaxPayer().fromdict({'id': 0, 'marié': marié, 'enfants': int(enfants), 'salaire': int(salaire)})
self.métier.calculate_tax(taxpayer, self.admindata)
# exibição
print(f"Impôt du contribuable = {taxpayer}\n\n")
# próximo contribuinte
- linha 9: a classe [ImpôtsConsole] implementa a interface [InterfaceImpôtsUi];
- linha 11: o construtor da classe recebe um parâmetro, o dicionário [config] da configuração do aplicativo;
- linha 13: recuperam-se os dados da administração fiscal que permitem o cálculo do imposto;
- linha 14: armazena-se uma referência na camada [métier];
- linha 16: implementação do método [run] da interface;
- linhas 19-53: diálogo com o usuário. Consiste
- em solicitar as três informações (estado civil, filhos, salário) do contribuinte;
- calcular seu imposto;
- exibi-lo;
- o diálogo termina quando o usuário responde * à primeira pergunta;
- linhas 20-27: pergunta-se se o contribuinte é casado e verifica-se a validade da resposta;
- linhas 29-31: se o usuário respondeu ‘*’ à pergunta, o diálogo é encerrado;
- linhas 32-39: pergunta-se o número de filhos do contribuinte e verifica-se a validade da resposta;
- linhas 40-47: pergunta-se o salário anual do contribuinte e verifica-se a validade da resposta;
- linhas 48-50: com essas informações, calcula-se, por meio da camada [métier], o imposto do contribuinte;
- linha 52: o valor do imposto é exibido;
15.2.3. O script principal
O script principal [main] é configurado pelo seguinte arquivo [config]:
def configure():
import os
# etapa 1 ------
# configuração do caminho do Python
# pasta deste arquivo
script_dir = os.path.dirname(os.path.abspath(__file__))
# root_dir
root_dir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes"
# dependências do aplicativo
absolute_dependencies = [
# dependências locais
f"{script_dir}/../../entities",
f"{script_dir}/../../interfaces",
f"{script_dir}/../../services",
f"{root_dir}/02/entities",
]
# definimos o syspath
from myutils import set_syspath
set_syspath(absolute_dependencies)
# etapa 2 ------
# configuração do aplicativo
config = {
# caminhos absolutos dos arquivos da aplicação
"admindataFilename": f"{script_dir}/../../data/input/admindata.json",
}
# instanciação das camadas do aplicativo
from ImpôtsDaoWithAdminDataInJsonFile import ImpôtsDaoWithAdminDataInJsonFile
from ImpôtsMétier import ImpôtsMétier
from ImpôtsConsole import ImpôtsConsole
# camada DAO
dao = ImpôtsDaoWithAdminDataInJsonFile(config)
# camada de negócios
métier = ImpôtsMétier()
# colocamos as instâncias das camadas na configuração
config["dao"] = dao
config["métier"] = métier
# camada UI
ui = ImpôtsConsole(config)
config["ui"] = ui
# geramos a configuração
return config
O script coordenador é o seguinte (main.py):
# configuramos o aplicativo
import config
config = config.configure()
# importações
from ImpôtsError import ImpôtsError
# recuperamos as camadas do aplicativo (elas já estão instanciadas)
ui = config["ui"]
# código
try:
# execução da camada [ui]
ui.run()
except ImpôtsError as erreur:
# exibe-se a mensagem de erro
print(f"L'erreur suivante s'est produite : {erreur}")
finally:
# executado em todos os casos
print("Travail terminé...")
- linhas 1-4: recupera-se a configuração do aplicativo;
- linha 10: recupera-se uma referência à camada [ui];
- linhas 12-21: a estrutura do código é a mesma da aplicação anterior: código envolto por um try/catch para interceptar qualquer exceção eventual;
- linha 15: solicita-se que a camada [ui] seja executada: o diálogo com o usuário então se inicia;
- linhas 16-18: interceptação de uma possível exceção;
Aqui está um exemplo de execução:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/v04/main/02/main.py
Le contribuable est-il marié / pacsé (oui/non) (* pour arrêter) : oui
Nombre d'enfants : 3
Salaire annuel : 200000
Impôt du contribuable = {"id": 0, "marié": "oui", "enfants": 3, "salaire": 200000, "impôt": 42842, "surcôte": 17283, "taux": 0.41, "décôte": 0, "réduction": 0}
Le contribuable est-il marié / pacsé (oui/non) (* pour arrêter) : *
Travail terminé...
Process finished with exit code 0