15. Uygulama alıştırması - sürüm 4

Burada, |Version 3| paragrafında açıklanan alıştırmayı ele alıyoruz ve şimdi bunu sınıflar ve arayüzler kullanarak işliyoruz. İki uygulama yazacağız:
Uygulama 1 şu şekilde olacaktır:

[main] adlı ana komut dosyası, bir [dao] katmanını ve bir [métier] katmanını oluşturacaktır:
- [dao] katmanının görevi, metin dosyalarında ve daha sonra bir veritabanında depolanan verileri yönetmek olacaktır;
- [métier] katmanının görevi ise vergi hesaplamasını yapmak olacaktır;
Bu uygulamada kullanıcı tarafından herhangi bir işlem yapılmayacaktır: vergi mükelleflerinin verileri, adı [main] modülüne verilecek bir metin dosyasında bulunacaktır.
Uygulama 2’de ise vergi mükelleflerinin verilerini kullanıcı klavyeyle girecektir. Bu durumda mimari şu şekilde değişecektir:

- [dao] katmanı (Veri Erişim Nesnesi), harici verilere erişimden sorumludur
- [métier] katmanı iş sorunlarıyla, bu durumda vergi hesaplamasıyla ilgilenir. Verilerle ilgilenmez. Veriler iki kaynaktan gelebilir:
- kalıcı veriler için [dao] katmanı;
- kullanıcı tarafından sağlanan veriler için [ui] katmanı.
- [ui] katmanı (Kullanıcı Arayüzü) kullanıcı etkileşimlerini yönetir;
- [main] ise tüm bu süreçleri koordine eder;
Bundan sonra, [dao], [métier] ve [ui] katmanlarının her biri bir sınıf kullanılarak uygulanacaktır. [métier] ve [dao] katmanları her iki uygulama için de aynı olacaktır. Bu nedenle, bunlar uygulama alıştırmasının aynı sürümünde birleştirilmiştir.
15.1. Sürüm 4 – uygulama 1
Sürüm 4, bir metin dosyasına yerleştirilmiş vergi mükellefleri listesinin vergisini hesaplar. Aşağıdaki mimariye sahiptir:

15.1.1. Varlıklar

Varlıklar, veri sınıflarıdır. Görevleri, verileri kapsüllemek ve bu verilerin geçerliliğini doğrulamaya olanak tanıyan getter/setter yöntemleri sunmaktır. Varlıklar, katmanlar arasında aktarılır. Aynı varlık, [ui] katmanından [dao] katmanına gidebilir ve bunun tersi de geçerlidir.
15.1.1.1. [ImpôtsError] sınıfı
Özel bir istisna sınıfı kullanacağız:
# -------------------------------
# istisna sınıfı
from MyException import MyException
class ImpôtsError(MyException):
pass
[métier] ve [dao] katmanları bir sorunla karşılaştıklarında bu istisnayı tetikleyecektir. Bu sınıf, [MyException] sınıfından türetilmiştir. Dolayısıyla şu şekilde kullanılır: [raise ImpôtsError(code_erreur, msg_erreur)].
15.1.1.2. [AdminData] sınıfı
[AdminData] sınıfı, vergi hesaplamasında kullanılan sabitleri içerir:
from BaseEntity import BaseEntity
# vergi idaresi verileri
class AdminData(BaseEntity):
# sınıf durumundan hariç tutulan anahtarlar
excluded_keys = []
# izin verilen anahtarlar
@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"
]
-
- satır: [AdminData] sınıfı, |BaseEntity| paragrafında açıklanan [BaseEntity] sınıfını genişletir. [BaseEntity] sınıfını genişleten sınıfların şunları tanımlaması gerektiğini hatırlayalım:
- nesne sözlüğe dönüştürüldüğünde hariç tutulan özellikleri listeleyen bir [excluded_keys] sınıf özniteliği (7. satır);
- nesne bir sözlükle başlatıldığında kabul edilen özelliklerin listesini döndüren [get_allowed_keys] statik yöntemi (satır 10-26);
Bir [AdminData] nesnesini başlatmak için kullanılan verilerin geçerliliğini kontrol etmek amacıyla setter yöntemleri kullanılmamıştır. Zira bu nesne benzersizdir ve yapılandırma yoluyla tanımlandığından hatalı olma ihtimali yoktur.
15.1.1.3. [TaxPayer] sınıfı
[TaxPayer] sınıfı bir vergi mükellefini modelleyecektir:
# ithalatlar
from BaseEntity import BaseEntity
from ImpôtsError import ImpôtsError
# bir vergi mükellefi
class TaxPayer(BaseEntity):
# bir vergi mükellefini modelleştirir
# id: kimlik numarası
# evli: evet / hayır
# çocuklar: çocuk sayısı
# maaş: yıllık maaşı
# vergi: ödenecek vergi tutarı
# ek vergi: ödenecek ek vergi
# indirim: ödenecek vergiden yapılan indirim
# indirim: ödenecek vergiden yapılan indirim
# oran: vergi mükellefinin vergi oranı
# sınıf durumundan hariç tutulan anahtarlar
excluded_keys = []
# izin verilen anahtarlar
@staticmethod
def get_allowed_keys() -> list:
return ['id', 'marié', 'enfants', 'salaire', 'impôt', 'surcôte', 'décôte', 'réduction', 'taux']
# özellikler
@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
# ayarlama yöntemleri
@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):
# çocuk sayısı >=0 olan bir tamsayı olmalıdır
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):
# maaş, 0'dan büyük bir tamsayı olmalıdır
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):
# vergi, 0'dan büyük veya 0'a eşit bir tamsayı olmalıdır
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):
# indirim >=0 olan bir tamsayı olmalıdır
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):
# fazla ücret, 0'dan büyük veya 0'a eşit bir tamsayı olmalıdır
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):
# fazla ücret, 0'dan büyük veya 0'a eşit bir tamsayı olmalıdır
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):
# oran, >=0 olan bir gerçek sayı olmalıdır
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")
Notlar:
- [TaxPayer] sınıfı bir vergi mükellefini kapsar;
-
- satır: [TaxPayer] sınıfı, [BaseEntity] sınıfından türetilmiştir. Dolayısıyla, [id] tanımlayıcısına sahiptir;
-
- satır: [AdminData] nesnesinin durumundan hiçbir özellik hariç tutulmamıştır;
- 22-25. satırlar: Sınıfın özellikleri. Bunlar 9-17. satırlarda açıklanmıştır;
- satır 27-58: sınıf özniteliklerinin getter'ları;
- satır 60-161: sınıfın özniteliklerinin setter'ları. Verileri kapsülleyen bir sınıfın, basit bir sözlüğe kıyasla avantajı, sınıfın setter'ları sayesinde özelliklerinin geçerliliğini doğrulayabilmesidir;
15.1.2. [dao] katmanı

Katmanların uygulamalarını [services] klasöründe bir araya getireceğiz. Bu sınıflar, [interfaces] klasöründe tanımlanan arayüzleri uygulayacaklardır.

15.1.2.1. [InterfaceImpôtsDao] arayüzü
[dao] katmanı, aşağıdaki [InterfaceImpôtsDao] arayüzünü (InterfaceImpôtsDao.py dosyası) uygulayacaktır:
# ithalatlar
from abc import ABC, abstractmethod
# IImpôtsDao arayüzü
from AdminData import AdminData
class InterfaceImpôtsDao(ABC):
# vergi dilimleri listesi
@abstractmethod
def get_admindata(self) -> AdminData:
pass
# vergi mükellefi verileri listesi
@abstractmethod
def get_taxpayers_data(self) -> dict:
pass
# vergi hesaplama sonuçlarının kaydı
@abstractmethod
def write_taxpayers_results(self, taxpayers_results: list):
pass
Arayüz üç yöntem tanımlar:
- [get_admindata]: vergi dilimlerinin dizisini alan yöntemdir. Bu verilerin nasıl elde edileceğine dair herhangi bir bilgi verilmediğine dikkat edilmelidir. İlerleyen bölümlerde, bu veriler önce bir metin dosyasında, ardından bir veritabanında bulunacaktır. Verilerin depolanma biçimine uyum sağlamak, arayüzü uygulayan sınıfların sorumluluğundadır. Dolayısıyla, vergi dilimlerini bir metin dosyasından almak için bir sınıf ve bir veritabanından almak için başka bir sınıf olacaktır. Her ikisi de [get_admindata] yöntemini uygulayacaktır;
- [get_taxpayers_data]: vergi mükelleflerinin verilerini alan yöntemdir. Burada da verilerin nerede bulunacağı belirtilmemiştir. Yalnızca verilerin bir metin dosyasında bulunduğu durumu ele alacağız;
- [write_taxpayers_results]: vergi hesaplama sonuçlarını kalıcı hale getirecek yöntemdir. Sonuçların nereye kaydedileceğini belirtmiyoruz. Yalnızca sonuçların bir metin dosyasına kaydedildiği durumu ele alacağız. [taxpayers_results] parametresi, kalıcı hale getirilecek sonuçların listesi olacaktır;
15.1.2.2. [AbstractImpôtsDao] sınıfı
[dao] katmanı iki sınıf tarafından uygulanacaktır:
- biri metin dosyalarından verileri (vergi mükellefleri, sonuçlar, vergi dilimleri) alacaktır;
- diğeri ise verileri (vergi mükellefleri, sonuçlar) metin dosyalarından ve vergi dilimlerini bir veritabanından alacaktır;
İki sınıf arasında tek fark, vergi dilimlerinin yönetimi olacaktır. Vergi mükellefi verileri ve vergi hesaplama sonuçları ise aynı şekilde yönetilecektir. Bu nedenle, bunları [AbstractImpôtsDao] adlı bir üst sınıfta yöneteceğiz. Vergi dilimlerinin yönetimine ilişkin özel durum ise iki alt sınıfta ele alınacaktır:
- [ImpôtsDaoWithAdminDataInJsonFile] sınıfı, jSON formatındaki bir metin dosyasından vergi dilimlerini alacaktır;
- [ImpôtsDaoWithAdminDataInDatabase] sınıfı, vergi dilimlerini bir veritabanından alacaktır;
Üst sınıf [AbstractImpôtsDao] şu şekilde olacaktır:
# ithalatlar
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
# [dao] katmanı için temel sınıf
class AbstractImpôtsDao(InterfaceImpôtsDao):
# Vergi mükellefleri ve vergileri metin dosyalarında yer alacaktır
# oluşturucu
def __init__(self, config: dict):
# config[taxpayersFilename] : vergi mükelleflerinin bulunduğu metin dosyasının adı
# yapılandırma[resultsFilename] : sonuçların jSON dosya adı
# config[errorsFilename]: hata dosyasının adı
# ayarlar kaydedilir
self.taxpayers_filename = config.get("taxpayersFilename")
self.taxpayers_results_filename = config.get("resultsFilename")
self.errors_filename = config.get("errorsFilename")
# ------------------
# IImpôtsDao arayüzü
# ------------------
# vergi mükellefleri verileri listesi
def get_taxpayers_data(self) -> dict:
…
# vergi mükelleflerinin vergilerinin kaydedilmesi
def write_taxpayers_results(self, taxpayers: list):
…
# vergi dilimlerinin okunması
@abstractmethod
def get_admindata(self) -> AdminData:
pass
-
- satır: [AbstractImpôtsDao] sınıfı, [InterfaceImpôtsDao] arayüzünü uygular. Dolayısıyla bu arayüzün üç yöntemi de burada bulunur:
- [get_taxpayers_data]: satır 31;
- [write_taxpayers_results]: satır 35;
- [get_admindata]: 40. satır. Bu yöntem, [AbstractImpôtsDao] sınıfı tarafından uygulanmayacağı için soyut olarak tanımlanmıştır (39. satır);
-
satır 16: oluşturucu, aşağıdaki bilgileri içeren bir [config] sözlüğü alır:
- [taxpayersFilename]: vergi mükelleflerinin verilerini içeren metin dosyasının adı;
- [resultsFilename]: sonuçların kaydedileceği metin dosyasının adı;
- [errorsFilename]: [taxpayersFilename] dosyasının işlenmesi sırasında karşılaşılan hataları listeleyen metin dosyasının adı;
[get_taxpayers_data] yöntemi şu şekildedir:
# vergi mükellefi verileri listesi
def get_taxpayers_data(self) -> dict:
# başlatma işlemleri
taxpayers_data = []
datafile = None
erreurs = []
try:
# veri dosyasının açılması
datafile = open(self.taxpayers_filename, "r")
# dosyanın geçerli satırını işleme
ligne = datafile.readline()
# satır numarası
numligne = 0
while ligne != '':
# bir satır ekleniyor
numligne += 1
# boşluklar kaldırılıyor
ligne = ligne.strip()
# boş satırları ve yorumları atlıyoruz
if ligne != "" and ligne[0] != "#":
try:
# vergi mükellefi satırını oluşturan id, evli, çocuk sayısı, maaş olmak üzere 4 alanı alınır
(id, marié, enfants, salaire) = ligne.split(",")
# yeni bir TaxPayer oluşturulur
taxpayers_data.append(
TaxPayer().fromdict({'id': id, 'marié': marié, 'enfants': enfants, 'salaire': salaire}))
except BaseException as erreur:
# hata kaydedilir
erreurs.append(f"Ligne {numligne}, {erreur}")
# yeni bir vergi mükellefi satırı okunur
ligne = datafile.readline()
# varsa hataları kaydedilir
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)
# sonuç döndürülür
return {"taxpayers": taxpayers_data, "erreurs": erreurs}
except BaseException as erreur:
# bir istisna oluşturulur ImpôtsError
raise ImpôtsError(11, f"{erreur}")
finally:
# dosya kapatılır
if datafile:
datafile.close()
-
- satır: vergi mükelleflerine ait veriler (evli olup olmadığı, çocuk sayısı, maaş) [TaxPayer] türünde bir nesne listesine yerleştirilecektir;
- 8-9. satırlar: vergi mükelleflerine ait metin dosyası okuma modunda açılır. Dosyanın içeriği şu şekildedir:
Önceki sürümlerle karşılaştırıldığında:
- [taxpayersFilename] dosyasının her satırı, vergi mükellefinin kimliği olan basit bir numara ile başlar;
- yorumlar ve boş satırlar izin verilir;
- hataları işleyeceğiz. Dolayısıyla 17, 19 ve 21. satırlar hatalı olarak işaretlenmelidir. Hatalar ayrı bir dosyaya kaydedilir;
Kodun incelemesine devam edelim:
-
- satır: metin dosyasındaki veriler [taxPayersData] listesine aktarılır;
- 14-31. satırlar: vergi mükellefleri dosyası satır satır okunur;
-
- satır: Boş bir satır okunduğunda (hiçbir şey – hatta \r\n satır sonu işareti bile yok) dosyanın sonuna ulaşılır;
-
- satır: Boş satırlar ve yorumlar göz ardı edilir. Bir satır, metnin önündeki ve arkasındaki boşluklar temizlendikten sonra ilk karakteri # ise yorum olarak kabul edilir;
- satır 24: Geçerli bir satır, virgülle ayrılmış dört alandan oluşur. Bu alanlar alınır. Atanan verilerin sayısı tam olarak dört değilse, dört elemanlı bir tuple’a veri atama işlemi başarısız olur;
- satır 25: Alınan dört alandan biri ([id, marié, enfants, salaire]) geçersizse, [BaseEntity.fromdict] yöntemi [MyException] türünde bir istisna atar;
- 25-26. satırlar: Bir [TaxPayer] nesnesi, vergi mükelleflerinin [taxpayers_data] listesine eklenir;
- 27-29. satırlar: Olası hatalar [erreurs] listesinde toplanır. Bu liste 6. satırda oluşturulmuştur;
- satır 33-36: Karşılaşılan hataların listesi, [errorsFilename] metin dosyasına kaydedilir. Hatalar iki türdür:
- bir satırda beklenen doğru alan sayısı bulunmuyordu;
- satırdaki bilgiler hatalıydı ve [TaxPayer] nesnesinin oluşturulmasında başarısızlık yaşandı;
- satır 39-41: tüm hatalar (BaseException) yakalanır ve bir [ImpôtsError] türüne kapsüllenerek üst katmana bildirilir;
- 42-45. satırlar: Başarılı ya da başarısız olsun, vergi mükelleflerinin metin dosyası açılmışsa kapatılır;
[write_taxpayers_results] yöntemi, aşağıdaki biçimde bir jSON dosyası oluşturmalıdır:
[
{
"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
},
…
]
[write_taxpayers_results] yöntemi şu şekildedir:
# mükelleflerin vergilerinin yazılması
def write_taxpayers_results(self, taxpayers: list):
# sonuçların bir dosyaya kaydedilmesi jSON
# vergi mükellefleri: TaxPayer türündeki nesnelerin listesi
# (kimlik, evli, çocuk sayısı, maaş, vergi, ek vergi, indirim, indirim oranı)
# [taxpayers] listesi, [self.taxpayers_results_filename] metin dosyasına kaydedilir
file = None
try:
# sonuç dosyasının açılması
file = codecs.open(self.taxpayers_results_filename, "w", "utf8")
# jSON formatında serileştirilecek listenin oluşturulması
mapping = map(lambda taxpayer: taxpayer.asdict(), taxpayers)
# serileştirme jSON
json.dump(list(mapping), file, ensure_ascii=False)
except BaseException as erreur:
# hatayı başka bir tür altında yeniden tetikleme
raise ImpôtsError(12, f"{erreur}")
finally:
# Dosya açıksa kapatılır
if file:
file.close()
-
- satır: Yöntem, [taxpayers] vergi mükellefleri listesini alır ve bunu jSON biçiminde [self.taxpayers_results_filename] metin dosyasına kaydetmelidir;
-
- satır: Sonuçların UTF-8 dosyasının oluşturulması;
-
- satır: Burada, sözdizimi [map (fonction, liste1)] olan [map] işlevini tanıtıyoruz. [fonction] fonksiyonu, [liste1]'in her bir elemanına uygulanır ve [liste2] listesine eklenecek yeni bir eleman üretir. Son olarak, her i için:
liste2[i]=fonction(liste1[i])
Burada, [liste1], [taxPayers] listesidir; bu da [TaxPayer] türündeki nesnelerden oluşan bir listedir. [fonction] işlevi burada, [lambda] adlı bir işlev biçiminde ifade edilmektedir; bu işlev, [taxpayers] listesindeki bir [taxpayer] elemanı üzerinde yapılan dönüşümü ifade eder: her bir [taxpayer] elemanı, kendi [taxpayer.asdict()] sözlüğüyle değiştirilir. Sonuç olarak, elde edilen [liste2] listesi, [taxpayers] listesindeki elemanların sözlüklerinin listesidir;
-
- satır: [map] işlevi tarafından döndürülen sonuç, [liste2] listesi değil, [map] türünde bir nesnedir. [liste2]'i elde etmek için [list(mapping)] ifadesini (satır 14) kullanmak gerekir;
-
- satır: [liste2] listesi, jSON biçiminde [self.taxpayers_results_filename] dosyasına kaydedilir;
- satır 15-17: Her türlü istisna yakalanır ve yeniden çalıştırılmadan önce (satır 17) [ImpôtsError] türünde bir hataya kapsüllenir;
- satır 19-21: Başarılı ya da başarısız olsun, sonuç dosyası açılmışsa her durumda kapatılır;
15.1.2.3. [ImpôtsDaoWithAdminDataInJsonFile] Sınıfı
[ImpôtsDaoWithAdminDataInJsonFile] sınıfı, [AbstractImpôtsDao] sınıfından türetilecek ve üst sınıfının uygulamadığı [getAdminData] yöntemini uygulayacaktır. Vergi idaresine ait verileri jSON dosyasından alacaktır:
{
"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
}
[ImpôtsDaoWithAdminDataInJsonFile] sınıfı şu şekildedir:
# ithalatlar
import codecs
import json
from AbstractImpôtsDao import AbstractImpôtsDao
from AdminData import AdminData
from ImpôtsError import ImpôtsError
# [dao] katmanının bir uygulaması; burada vergi idaresine ait veriler bir dosyada bulunur jSON
class ImpôtsDaoWithAdminDataInJsonFile(AbstractImpôtsDao):
# oluşturucu
def __init__(self, config: dict):
# config[admindataFilename]: vergi idaresi verilerini içeren jSON dosyasının adı
# config[taxpayersFilename] : vergi mükelleflerine ait metin dosyasının adı
# config[resultsFilename] : sonuçların bulunduğu jSON dosyasının adı
# config[errorsFilename]: hata dosyasının adı
# Parent sınıfının başlatılması
AbstractImpôtsDao.__init__(self, config)
# vergi idaresi verilerinin okunması
file = None
try:
# vergi verilerini içeren jSON dosyasının okuma modunda açılması
file = codecs.open(config["admindataFilename"], "r", "utf8")
# jSON dosyasının içeriğinin bir [AdminData] nesnesine aktarılması
self.admindata = AdminData().fromdict(json.load(file))
except BaseException as erreur:
# hata, [ImpôtsError] türü olarak yeniden gönderilir
raise ImpôtsError(21, f"{erreur}")
finally:
# Dosya açıksa kapatılır
if file:
file.close()
# -------------
# arayüz
# -------------
# vergi idaresinden verilerin alınması
# yöntem, bir [AdminData] nesnesi döndürür
def get_admindata(self) -> AdminData:
return self.admindata
-
- satır: [ImpôtsDaoWithAdminDataInJsonFile] sınıfı, [AbstractImpôtsDao] sınıfından miras alır. Bu nedenle [InterfaceImpôtsDao] arayüzünü uygular;
-
- satır: Oluşturucu, 14-17. satırlardaki bilgileri içeren bir sözlük parametresini alır;
-
- satır: üst sınıf başlatılır;
-
- satır: Vergi idaresi verilerini içeren jSON dosyası açılır;
-
- satır: Vergi idaresi verilerini içeren UTF-8 dosyası açılır;
-
- satır: Dosyanın içeriği okunur ve [AdminData] türündeki [self.admindata] nesnesine yerleştirilir. jSON dosyasının anahtarları, bir [AdminData] nesnesi için kabul edilen özelliklere uymalıdır; aksi takdirde [fromdict] yöntemi bir istisna oluşturur;
- 28-30. satırlar: istisna yönetimi. Oluşabilecek istisnalar, yeniden atılmadan önce [ImpôtsError] türünde kapsüllenir;
- satır 32-34: dosya açıksa kapatılır;
- satır 42-43: [InterfaceImpôtsDao] arayüzünün [get_admindata] yönteminin uygulanması;
15.1.3. [métier] katmanı

15.1.3.1. [InterfaceImpôtsMétier] arayüzü
[métier] katmanının arayüzü şu şekilde olacaktır:
# içe aktarmalar
from abc import ABC, abstractmethod
from AdminData import AdminData
from TaxPayer import TaxPayer
# IImpôtsMétier arayüzü
class InterfaceImpôtsMétier(ABC):
# 1 vergi mükellefi için vergi hesaplaması
@abstractmethod
def calculate_tax(self, taxpayer: TaxPayer, admindata: AdminData):
pass
- [InterfaceImpôtsMétier] arayüzü tek bir yöntem tanımlar:
-
- satır: [calculate_tax] yöntemi, tek bir vergi mükellefi olan [taxpayer]'in vergisini hesaplamaya olanak tanır. [admindata], vergi idaresinin verilerini kapsayan [AdminData] nesnesidir;
-
- satır: [calculate_tax] yöntemi herhangi bir sonuç döndürmez. Elde edilen veriler (vergi, ek vergi, indirim, muafiyet, oran) [taxpayer] parametresine dahil edilir: yöntem çağrılmadan önce bu öznitelikler boştur, çağrıldıktan sonra ise başlatılır;
15.1.3.2. [ImpôtsMétier] sınıfı
[ImpôtsMétier] sınıfı, [InterfaceImpôtsMétier] arayüzünü aşağıdaki şekilde uygular:

Sınıfın yöntemleri, |[impots.v02.modules.impôts_module_02] modülü| başlıklı bölümdeki [impôts_module_02] modülünden alınmıştır. Yöntemlerin parametre sayısı sadece ikiyle sınırlandırılmıştır:
- taxpayer(id, evli, çocuk sayısı, maaş, vergi, indirim, ek vergi, indirim, oran): bir vergi mükellefini ve vergisini temsil eden nesne;
- admindata: vergi idaresinin verilerini kapsayan nesne;
Yapılan bu değişiklikleri bir yöntem üzerinde gösteriyoruz;
# vergi hesaplaması - 1. aşama
# ----------------------------------------
def calculate_tax(self, taxpayer: TaxPayer, admindata: AdminData):
# vergi mükellefi (kimlik numarası, evli olup olmadığı, çocuk sayısı, maaş, vergi, indirim, ek vergi, indirim, vergi oranı)
# idari veriler: vergi idaresi verileri
# çocuklu vergi hesaplaması
self.calculate_tax_2(taxpayer, admindata)
# sonuçlar vergi mükellefi tablosunda yer almaktadır
taux1 = taxpayer.taux
surcôte1 = taxpayer.surcôte
impot1 = taxpayer.impôt
# çocuksuz vergi hesaplaması
if taxpayer.enfants != 0:
# çocuksuz aynı vergi mükellefi için vergi hesaplaması
taxpayer2 = TaxPayer().fromdict(
{'id': 0, 'marié': taxpayer.marié, 'enfants': 0, 'salaire': taxpayer.salaire})
self.calculate_tax_2(taxpayer2, admindata)
# sonuçlar taxpayer2'de
taux2 = taxpayer2.taux
surcôte2 = taxpayer2.surcôte
impot2 = taxpayer2.impôt
# aile katsayısı üst sınırının uygulanması
if taxpayer.enfants < 3:
# PLAFOND_QF_DEMI_PART avro, ilk 2 çocuk için
impot2 = impot2 - taxpayer.enfants * admindata.plafond_qf_demi_part
else:
# PLAFOND_QF_DEMI_PART avro ilk 2 çocuk için, sonraki çocuklar için iki katı
impot2 = impot2 - 2 * admindata.plafond_qf_demi_part - (taxpayer.enfants - 2) \
* 2 * admindata.plafond_qf_demi_part
else:
# Vergi mükellefinin çocuğu yoksa, o zaman vergi2=vergi1
impot2 = impot1
# ilgili vergi oranı ve ek vergiyle birlikte en yüksek vergi tutarı alınır
(impot, surcôte, taux) = (impot1, surcôte1, taux1) if impot1 >= impot2 else (
impot2, impot2 - impot1 + surcôte2, taux2)
# ara sonuçlar
taxpayer.impôt = impot
taxpayer.surcôte = surcôte
taxpayer.taux = taux
# Olası bir indirimin hesaplanması
self.get_décôte(taxpayer, admindata)
taxpayer.impôt -= taxpayer.décôte
# olası vergi indiriminin hesaplanması
self.get_réduction(taxpayer, admindata)
taxpayer.impôt -= taxpayer.réduction
# sonuç
taxpayer.impôt = math.floor(taxpayer.impôt)
-
- satır: [calculate_tax] yöntemi, [InterfaceImpôtsMétier] arayüzünün tek yöntemidir. Bu yöntem iki parametre kabul eder:
- [tapPayer]: Vergisi hesaplanan vergi mükellefi;
- [admindata]: vergi idaresinin verilerini kapsayan nesne;
- hesaplama sonuçları [taxpayer] parametresinde saklanır (satır 40-50). Dolayısıyla, bu nesnenin içeriği yöntem çağrılmadan önce ve sonra aynı değildir;
15.1.4. [dao] ve [métier] katmanlarının testleri

- [TestDaoMétier], [dao] ve [métier] katmanlarının test sınıfı olan UnitTest'tir;
- [config], testlerin yapılandırma dosyasıdır;
[config] yapılandırması şu şekildedir:
def configure():
import os
# 1. adım ------
# Python yolunun yapılandırılması
# bu dosyanın bulunduğu klasör
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"
# uygulamanın mutlak bağımlılıkları
absolute_dependencies = [
f"{script_dir}/../entities",
f"{script_dir}/../interfaces",
f"{script_dir}/../services",
f"{root_dir}/02/entities",
]
# syspath ayarlanıyor
from myutils import set_syspath
set_syspath(absolute_dependencies)
# 2. adım ------
# uygulama yapılandırması
config = {
# uygulama dosyalarının mutlak yolları
"admindataFilename": f"{script_dir}/../data/input/admindata.json"
}
# uygulama katmanlarının örneklenmesi
from ImpôtsDaoWithAdminDataInJsonFile import ImpôtsDaoWithAdminDataInJsonFile
from ImpôtsMétier import ImpôtsMétier
dao = ImpôtsDaoWithAdminDataInJsonFile(config)
métier = ImpôtsMétier()
# katman örneklerini yapılandırmaya ekliyoruz
config["dao"] = dao
config["métier"] = métier
# yapılandırma dosyası oluşturulur
return config
- 4-23. satırlar: Testlerin Python Yolu yapılandırılır;
- 32-41. satırlar: [dao] ve [métier] katmanları oluşturulur. Referansları [config] sözlüğüne eklenir;
-
- satır: bu sözlük döndürülür;
[TestDaoMétier] test sınıfı şu şekildedir:
import unittest
def get_config() -> dict:
# uygulama yapılandırması
import config
# yapılandırma geri yüklenir
return config.configure()
class TestDaoMétier(unittest.TestCase):
# her yöntemden önce yürütülür test_
def setUp(self) -> None:
# test yapılandırmasını alıyoruz
config = get_config()
# bazı bilgileri kaydediyoruz
self.métier = config['métier']
self.admindata = config['dao'].get_admindata()
def test_1(self) -> None:
from TaxPayer import TaxPayer
# {'evli': 'evet', 'çocuk': 2, 'maaş': 55555,
# 'vergi': 2814, 'ek vergi': 0, 'indirim': 0, 'azaltma': 0, 'oran': 0,14}
taxpayer = TaxPayer().fromdict({"marié": "oui", "enfants": 2, "salaire": 55555})
self.métier.calculate_tax(taxpayer, self.admindata)
# doğrulama
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
# {'evli': 'evet', 'çocuk': 2, 'maaş': 50000,
# 'vergi': 1384, 'ek ücret': 0, 'indirim': 384, 'azaltma': 347, 'oran': 0,14}
taxpayer = TaxPayer().fromdict({'marié': 'oui', 'enfants': 2, 'salaire': 50000})
self.métier.calculate_tax(taxpayer, self.admindata)
# kontroller
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
# {'evli': 'evet', 'çocuk sayısı': 3, 'maaş': 50000,
# 'vergi': 0, 'ek ücret': 0, 'indirim': 720, 'azaltma': 0, 'oran': 0.14}
taxpayer = TaxPayer().fromdict({'marié': 'oui', 'enfants': 3, 'salaire': 50000})
self.métier.calculate_tax(taxpayer, self.admindata)
# kontroller
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
# {'evli': 'hayır', 'çocuk': 2, 'maaş': 100000,
# 'vergi': 19884, 'ek ücret': 4480, 'indirim': 0, 'azaltma': 0, 'oran': 0.41}
taxpayer = TaxPayer().fromdict({'marié': 'non', 'enfants': 2, 'salaire': 100000})
self.métier.calculate_tax(taxpayer, self.admindata)
# kontroller
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
# {'evli': 'hayır', 'çocuk sayısı': 3, 'maaş': 100000,
# 'vergi': 16782, 'ek ücret': 7176, 'indirim': 0, 'azaltma': 0, 'oran': 0.41}
taxpayer = TaxPayer().fromdict({'marié': 'non', 'enfants': 3, 'salaire': 100000})
self.métier.calculate_tax(taxpayer, self.admindata)
# kontroller
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
# {'evli': 'evet', 'çocuk sayısı': 3, 'maaş': 100000,
# 'vergi': 9200, 'ek ücret': 2180, 'indirim': 0, 'azaltma': 0, 'oran': 0.3}
taxpayer = TaxPayer().fromdict({'marié': 'oui', 'enfants': 3, 'salaire': 100000})
self.métier.calculate_tax(taxpayer, self.admindata)
# kontroller
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
# {'evli': 'evet', 'çocuk sayısı': 5, 'maaş': 100000,
# 'vergi': 4230, 'ek ücret': 0, 'indirim': 0, 'azaltma': 0, 'oran': 0.14}
taxpayer = TaxPayer().fromdict({'marié': 'oui', 'enfants': 5, 'salaire': 100000})
self.métier.calculate_tax(taxpayer, self.admindata)
# kontroller
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
# {'evli': 'hayır', 'çocuk': 0, 'maaş': 100000,
# 'vergi': 22986, 'ek ücret': 0, 'indirim': 0, 'azaltma': 0, 'oran': 0,41}
taxpayer = TaxPayer().fromdict({'marié': 'non', 'enfants': 0, 'salaire': 100000})
self.métier.calculate_tax(taxpayer, self.admindata)
# kontroller
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
# {'evli': 'evet', 'çocuk sayısı': 2, 'maaş': 30000,
# 'vergi': 0, 'ek ücret': 0, 'indirim': 0, 'azaltma': 0, 'oran': 0}
taxpayer = TaxPayer().fromdict({'marié': 'oui', 'enfants': 2, 'salaire': 30000})
self.métier.calculate_tax(taxpayer, self.admindata)
# kontroller
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
# {'evli': 'hayır', 'çocuk': 0, 'maaş': 200000,
# 'vergi': 64210, 'ek ücret': 7498, 'indirim': 0, 'azaltma': 0, 'oran': 0,45}
taxpayer = TaxPayer().fromdict({'marié': 'non', 'enfants': 0, 'salaire': 200000})
self.métier.calculate_tax(taxpayer, self.admindata)
# kontroller
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
# {'evli': 'evet', 'çocuk sayısı': 3, 'maaş': 200000,
# 'vergi': 42842, 'ek ücret': 17283, 'indirim': 0, 'azaltma': 0, 'oran': 0.41}
taxpayer = TaxPayer().fromdict({'marié': 'oui', 'enfants': 3, 'salaire': 200000})
self.métier.calculate_tax(taxpayer, self.admindata)
# kontroller
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()
Açıklamalar
-
- satır: Test sınıfı, [unittest.TestCase] sınıfını genişletir;
- 13-19. satırlar: UnitTest testinde, [setUp] yöntemi, her bir [test_] yönteminden önce yürütülür;
-
- satır: Daha önce incelenen [config] komut dosyasından elde edilen yapılandırma alınır;
-
- satır: [métier] katmanına bir referans kaydedilir;
- satır 19: [dao] katmanından, vergi idaresi verilerini içeren [AdminData] nesnesini istenir ve bu nesne saklanır;
- 21-173. satırlar: Sonuçları 2019 vergi dairesinin resmi web sitesinde |https://www3.impots.gouv.fr/simulateur/calcul_impot/2019/simplifie/index.htm| üzerinden doğrulanmış 11 test;
- 21-33. satırlar: tüm testler aynı şablona göre oluşturulmuştur;
-
- satır: [TaxPayer] sınıfı içe aktarılır;
-
- satır: test edilen vergi mükellefi;
- satır 25: beklenen sonuçlar;
- satır 26: vergi mükellefinin [TaxPayer] nesnesinin oluşturulması;
- satır 27: vergi hesaplaması. Sonuç, [taxpayer]'te yer almaktadır;
- satır 29-33: elde edilen sonuçların doğrulanması;
- satır 29: vergi tutarı euro düzeyinde doğrulanır. Testler, bu belgedeki algoritma ile elde edilen sonuçların resmi rakamlardan en fazla 1 euro farklılık gösterebileceğini ortaya koymuştur;
Testlerin yürütülmesi sonucunda aşağıdaki sonuçlar elde edilmiştir:

15.1.5. Ana komut dosyası

Ana komut dosyası, aşağıdaki [config] komut dosyası ile yapılandırılmıştır:
def configure():
import os
# adım 1 ------
# Python yolunun yapılandırılması
# bu dosyanın bulunduğu klasör
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"
# uygulamanın bağımlılıkları
absolute_dependencies = [
# yerel bağımlılıklar
f"{script_dir}/../../entities",
f"{script_dir}/../../interfaces",
f"{script_dir}/../../services",
f"{root_dir}/02/entities",
]
# syspath ayarlanıyor
from myutils import set_syspath
set_syspath(absolute_dependencies)
# 2. adım ------
# uygulama yapılandırması
config = {
# uygulama dosyalarının mutlak yolları
"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"
}
# uygulama katmanlarının örneklenmesi
from ImpôtsDaoWithAdminDataInJsonFile import ImpôtsDaoWithAdminDataInJsonFile
from ImpôtsMétier import ImpôtsMétier
dao = ImpôtsDaoWithAdminDataInJsonFile(config)
métier = ImpôtsMétier()
# katman örneklerini yapılandırmaya ekliyoruz
config["dao"] = dao
config["métier"] = métier
# yapılandırmayı oluşturuyoruz
return config
Bu komut dosyası, [métier] ve [dao] katmanlarının testinde kullanılan komut dosyasına benzerdir.
[main.py] ana komut dosyası şöyledir:
# uygulamayı yapılandırıyoruz
import config
config = config.configure()
# ithalatlar
from ImpôtsError import ImpôtsError
# uygulamanın katmanlarını alıyoruz (bunlar zaten örneklenmiş durumda)
dao = config["dao"]
métier = config["métier"]
try:
# vergi dilimlerini alıyoruz
admindata = dao.get_admindata()
# vergi mükelleflerinin verilerinin okunması
taxpayers = dao.get_taxpayers_data()["taxpayers"]
# vergi mükelleflerinin mi?
if not taxpayers:
raise ImpôtsError(51, f"Pas de contribuables valides dans le fichier {config['taxpayersFilename']}")
# Vergi mükelleflerinin vergisinin hesaplanması
for taxpayer in taxpayers:
# "vergi mükellefi" hem giriş hem de çıkış parametresidir
# "taxpayer" değiştirilecek
métier.calculate_tax(taxpayer, admindata)
# sonuçların bir metin dosyasına yazılması
dao.write_taxpayers_results(taxpayers)
except ImpôtsError as erreur:
# hatanın görüntülenmesi
print(f"L'erreur suivante s'est produite : {erreur}")
finally:
# işlem tamamlandı
print("Travail terminé...")
Notlar
- 2-4. satırlar: Uygulamanın yapılandırması alınır. Ayrıca, uygulamanın Python Yolu'nun oluşturulduğu da bilinmektedir;
- 9-11. satırlar: [métier] ve [dao] katmanlarına ilişkin referanslar alınır;
-
- satır: Vergi idaresinden veriler alınır;
-
- satır: Vergisi hesaplanacak vergi mükelleflerinin listesi alınır;
- 19-20. satırlar: Bu liste boşsa, bir istisna oluşturulur;
- satır 22-25: [métier] katmanı kullanılarak çeşitli [taxpayer] nesnelerinin vergisi hesaplanır;
- satır 27: [taxpayers] artık, özniteliklerine (vergi, indirim, ek vergi, indirim, oran) değer atanan [TaxPayer] nesnelerinden oluşan bir listedir. Bu liste, jSON dosyasına yazılır;
- 28-30. satırlar: olası bir hatanın yakalanması;
- 31-33. satırlar: her durumda yürütülür;
Komut dosyasının çalıştırılması, önceki sürümlerdekiyle aynı sonuçları verir. Vergi mükelleflerinin hata dosyası, bu sürümde yeni eklenmiştir. [main] komut dosyası çalıştırıldıktan sonra içeriği şu şekildedir:
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]
Hatalı satırlar şunlardı:
15.2. Sürüm 4 – Uygulama 2
Bu sürümde, vergi mükelleflerinin listesini kullanıcı klavye aracılığıyla girer. Uygulamanın mimarisi şu şekilde olacaktır:

Yeni bir modül ortaya çıkıyor: kullanıcıyla etkileşime girecek olan [ui] (Kullanıcı Arayüzü) katmanı. Bu katman bir arayüze sahip olacak ve bir sınıf tarafından uygulanacaktır.

15.2.1. [InterfaceImpôtsUi] arayüzü
# ithalatlar
from abc import ABC, abstractmethod
# InterfaceImpôtsUI arayüzü
class InterfaceImpôtsUi(ABC):
# arayüzü uygulayan sınıfın çalıştırılması
@abstractmethod
def run(self):
pass
[InterfaceImpôtsUi] arayüzü, 8-10. satırlarda yer alan tek bir yönteme sahip olacaktır. Arayüz burada bir konsol uygulamasıyla uygulanacaktır, ancak bir grafik kullanıcı arayüzüyle de uygulanabilir. [run] yöntemine geçirilen parametreler, her iki uygulamada da aynı olmayacaktır. Bu sorunu aşmak için genellikle şu yöntem kullanılır:
- [run] yöntemine parametre vermemek (veya en az sayıda parametre vermek);
- arayüzü uygulayan sınıfın oluşturucusuna parametreler aktarmak. Bu parametreler, uygulamadan uygulamaya farklılık gösterebilir. Bu parametreler, sınıfın öznitelikleri olarak kaydedilir;
- [run] yönteminin bu sınıf özniteliklerini (self.x) kullanmasını sağlamak;
Bu yöntem, her bir uygulama sınıfının yapıcı parametreleriyle belirlenmiş çok genel bir arayüz elde etmeyi sağlar. Bu yöntem, modüler sürüm 1 için daha önce kullanılmıştır.
15.2.2. [ImpôtsConsole] sınıfı
[ImpôtsConsole] sınıfı, [InterfaceImpôtsUi] arayüzünü şu şekilde uygular:
# ithalatlar
import re
from InterfaceImpôtsUi import InterfaceImpôtsUi
from TaxPayer import TaxPayer
# [UI] katmanı
class ImpôtsConsole(InterfaceImpôtsUi):
# yapıcı
def __init__(self, config: dict):
# parametrelerin kaydedilmesi
self.admindata = config['dao'].get_admindata()
self.métier = config['métier']
def run(self):
# kullanıcıyla etkileşimli diyalog
fini = False
while not fini:
# Vergi mükellefi evli mi?
marié = input("Le contribuable est-il marié / pacsé (oui/non) (* pour arrêter) : ").strip().lower()
# girişin geçerliliğinin kontrolü
while marié != "oui" and marié != "non" and marié != "*":
# hata mesajı
print("Tapez oui ou non ou *")
# soruyu tekrar sor
marié = input("Le contribuable est-il marié / pacsé (oui/non) (* pour arrêter) : ").strip().lower()
# Bitti mi?
if marié == "*":
# diyalog sona erdi
return
# çocuk sayısı
enfants = input("Nombre d'enfants : ").strip()
# girişin geçerliliğinin kontrolü
if not re.match(r"^\d+$", enfants):
# hata mesajı
print("Tapez un nombre entier positif ou nul")
# yeniden başla
enfants = input("Nombre d'enfants : ").strip()
# yıllık maaş
salaire = input("Salaire annuel : ").strip()
# girişin geçerliliğinin kontrolü
if not re.match(r"^\d+$", salaire):
# hata mesajı
print("Tapez un nombre entier positif ou nul")
# yeniden deneme
salaire = input("Salaire annuel : ").strip()
# vergi hesaplaması
taxpayer = TaxPayer().fromdict({'id': 0, 'marié': marié, 'enfants': int(enfants), 'salaire': int(salaire)})
self.métier.calculate_tax(taxpayer, self.admindata)
# görüntüleme
print(f"Impôt du contribuable = {taxpayer}\n\n")
# sonraki vergi mükellefi
-
- satır: [ImpôtsConsole] sınıfı, [InterfaceImpôtsUi] arayüzünü uygular;
-
- satır: Sınıfın oluşturucusu, uygulama yapılandırmasına ait [config] sözlüğünü bir parametre olarak alır;
-
- satır: Vergi hesaplamasına olanak tanıyan vergi idaresi verileri alınır;
-
- satır: [métier] katmanında bir referans kaydedilir;
-
- satır: arayüzün [run] yönteminin uygulanması;
- satır 19-53: kullanıcıyla etkileşim. Bu etkileşim,
- vergi mükellefinin üç bilgisini (evli olup olmadığı, çocuk sayısı, maaşı) istemek;
- vergisini hesaplamak;
- vergiyi ekrana yansıtmak;
- kullanıcı ilk soruya * yanıt verdiğinde diyalog sona erer;
- 20-27. satırlar: vergi mükellefinin evli olup olmadığı sorulur ve cevabın geçerliliği kontrol edilir;
- 29-31. satırlar: Kullanıcı soruya ‘*’ ile yanıt verirse diyalog sonlandırılır;
- satır 32-39: vergi mükellefinin çocuk sayısı sorulur ve cevabın geçerliliği kontrol edilir;
- satır 40-47: vergi mükellefinin yıllık maaşı sorulur ve cevabın geçerliliği kontrol edilir;
- satır 48-50: bu bilgilerle, [métier] katmanı aracılığıyla vergi mükellefinin vergisi hesaplanır;
-
- satır: vergi tutarı görüntülenir;
15.2.3. Ana komut dosyası
Ana komut dosyası [main], aşağıdaki [config] dosyası ile yapılandırılır:
def configure():
import os
# 1. adım ------
# Python yolunun yapılandırılması
# bu dosyanın bulunduğu klasör
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"
# uygulamanın bağımlılıkları
absolute_dependencies = [
# yerel bağımlılıklar
f"{script_dir}/../../entities",
f"{script_dir}/../../interfaces",
f"{script_dir}/../../services",
f"{root_dir}/02/entities",
]
# syspath ayarlanıyor
from myutils import set_syspath
set_syspath(absolute_dependencies)
# 2. adım ------
# uygulama yapılandırması
config = {
# uygulama dosyalarının mutlak yolları
"admindataFilename": f"{script_dir}/../../data/input/admindata.json",
}
# uygulama katmanlarının örneklenmesi
from ImpôtsDaoWithAdminDataInJsonFile import ImpôtsDaoWithAdminDataInJsonFile
from ImpôtsMétier import ImpôtsMétier
from ImpôtsConsole import ImpôtsConsole
# DAO katmanı
dao = ImpôtsDaoWithAdminDataInJsonFile(config)
# iş mantığı katmanı
métier = ImpôtsMétier()
# katman örneklerini yapılandırmaya ekliyoruz
config["dao"] = dao
config["métier"] = métier
# UI katmanı
ui = ImpôtsConsole(config)
config["ui"] = ui
# yapılandırmayı oluşturuyoruz
return config
Koordinatör komut dosyası şöyledir (main.py):
# uygulamayı yapılandırıyoruz
import config
config = config.configure()
# ithalatlar
from ImpôtsError import ImpôtsError
# uygulamanın katmanlarını alıyoruz (bunlar zaten örneklenmiş durumda)
ui = config["ui"]
# kod
try:
# [ui] katmanının çalıştırılması
ui.run()
except ImpôtsError as erreur:
# hata mesajı görüntülenir
print(f"L'erreur suivante s'est produite : {erreur}")
finally:
# her durumda yürütülür
print("Travail terminé...")
- 1-4. satırlar: uygulamanın yapılandırması alınır;
-
- satır: [ui] katmanına ilişkin bir referans alınır;
- 12-21. satırlar: kod yapısı önceki uygulamadakiyle aynıdır: olası istisnaları durdurmak için try / catch ile çevrelenmiş kod;
-
- satır: [ui] katmanının çalıştırılması istenir: böylece kullanıcıyla diyalog başlar;
- 16-18. satırlar: Olası bir istisnanın yakalanması;
İşte bir yürütme örneği:
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