31. Sürüm 12'deki jSON ve XML hizmetleri için web istemcileri
Az önce yazdığımız web sunucusunun jSON ve XML hizmetleri için üç konsol istemci uygulaması yazacağız. Sürüm 11’deki istemci/sunucu mimarisini kullanacağız:

Üç konsol komut dosyası yazacağız:
- [main] ve [main3] komut dosyaları, sunucunun [métier] katmanını kullanacak;
- [main2] komut dosyası, istemcinin [métier] katmanını kullanacaktır;
31.1. İstemci komut dosyalarının dizin yapısı
[http-clients/07] klasörü, başlangıçta [http-clients/06] klasörünün kopyalanmasıyla oluşturulur. Daha sonra bu klasör üzerinde değişiklik yapılır.

- [1]'e: müşteri tarafından kullanılan veya oluşturulan veriler;
- [2]'e, müşterinin yapılandırması ve konsol komut dosyaları;
- [3]'e, müşterinin [dao] katmanı;
- [4]'te, müşterinin [dao] katmanına ait test klasörü;
31.2. Müşterilerin [dao] katmanı


31.2.1. Arayüz
[dao] katmanı, aşağıdaki [InterfaceImpôtsDaoWithHttpSession] arayüzünü uygulayacaktır:
from abc import abstractmethod
from AbstractImpôtsDao import AbstractImpôtsDao
from AdminData import AdminData
from TaxPayer import TaxPayer
class InterfaceImpôtsDaoWithHttpSession(AbstractImpôtsDao):
# birim başına vergi hesaplaması
@abstractmethod
def calculate_tax(self, taxpayer: TaxPayer):
pass
# toplu vergi hesaplaması
@abstractmethod
def calculate_tax_in_bulk_mode(self, taxpayers: list):
pass
# oturum başlatma
@abstractmethod
def init_session(self, type_session: str):
pass
# oturum sonlandırma
@abstractmethod
def end_session(self):
pass
# kimlik doğrulama
@abstractmethod
def authenticate_user(self, user: str, password: str):
pass
# simülasyon listesi
@abstractmethod
def get_simulations(self) -> list:
pass
# bir simülasyonu silme
@abstractmethod
def delete_simulation(self, id: int) -> list:
pass
# vergi hesaplaması için gerekli verileri alma
@abstractmethod
def get_admindata(self) -> AdminData:
pass
Arayüzdeki her yöntem, vergi hesaplama sunucusunun bir URL hizmetine karşılık gelir.
- 7. satır: Arayüz, dosya sistemine erişimi yöneten [AbstractDao] sınıfını genişletir;
Yöntemler ile hizmet URL'leri arasındaki eşleştirme, [config] yapılandırma dosyasında yapılır:
# vergi hesaplama sunucusu
"server": {
"urlServer": "http://127.0.0.1:5000",
"user": {
"login": "admin",
"password": "admin"
},
"url_services": {
"calculate-tax": "/calculer-impot",
"get-admindata": "/get-admindata",
"calculate-tax-in-bulk-mode": "/calculer-impots",
"init-session": "/init-session",
"end-session": "/fin-session",
"authenticate-user": "/authentifier-utilisateur",
"get-simulations": "/lister-simulations",
"delete-simulation": "/supprimer-simulation"
}
31.2.2. Uygulama
[InterfaceImpôtsDaoWithHttpSession] arayüzü, aşağıdaki [ImpôtsDaoWithHttpSession] sınıfı tarafından uygulanır:
# ithalatlar
import json
import requests
import xmltodict
from flask_api import status
from AbstractImpôtsDao import AbstractImpôtsDao
from AdminData import AdminData
from ImpôtsError import ImpôtsError
from InterfaceImpôtsDaoWithHttpSession import InterfaceImpôtsDaoWithHttpSession
from TaxPayer import TaxPayer
class ImpôtsDaoWithHttpSession(InterfaceImpôtsDaoWithHttpSession):
# üretici
def __init__(self, config: dict):
# üst nesnenin başlatılması
AbstractImpôtsDao.__init__(self, config)
# yapılandırma öğelerinin kaydedilmesi
# genel yapılandırma
self.__config = config
# sunucu
self.__config_server = config["server"]
# hizmetler
self.__config_services = config["server"]['url_services']
# hata ayıklama modu
self.__debug = config["debug"]
# günlük kaydı
self.__logger = None
# çerezler
self.__cookies = None
# oturum türü (json, xml)
self.__session_type = None
# istek / yanıt aşaması
def get_response(self, method: str, url_service: str, data_value: dict = None, json_value=None):
# [method] : HTTP, GET veya POST yöntemi
# [url_service] : URL hizmet
# [data] : POST'in x-www-form-urlencoded biçimindeki parametreleri
# [json] : POST parametreleri (json formatında)
# [cookies]: isteğe eklenecek çerezler
# XML veya JSON oturumu olmalıdır, aksi takdirde yanıtı işleyemeyiz
if self.__session_type not in ['json', 'xml']:
raise ImpôtsError(73, "il n'y a pas de session valide en cours")
# oturum açma
if method == "GET":
# GET
response = requests.get(url_service, cookies=self.__cookies)
else:
# POST
response = requests.post(url_service, data=data_value, json=json_value, cookies=self.__cookies)
# hata ayıklama modu mu?
if self.__debug:
# günlük kaydedici
if not self.__logger:
self.__logger = self.__config['logger']
# giriş yapılıyor
self.__logger.write(f"{response.text}\n")
# sonuç
if self.__session_type == "json":
résultat = json.loads(response.text)
else: # xml
résultat = xmltodict.parse(response.text[39:])['root']
# varsa yanıtta bulunan çerezleri alıyoruz
if response.cookies:
self.__cookies = response.cookies
# durum kodu
status_code = response.status_code
# durum kodu 200'den farklıysa OK
if status_code != status.HTTP_200_OK:
raise ImpôtsError(35, résultat['réponse'])
# sonuç döndürülür
return résultat['réponse']
def init_session(self, session_type: str):
# oturum türünü not ediyoruz
self.__session_type = session_type
# hizmet URL'si
config_server = self.__config_server
url_service = f"{config_server['urlServer']}{self.__config_services['init-session']}/{session_type}"
# isteğin yürütülmesi
self.get_response("GET", url_service)
…
- 16-34. satırlar: sınıfın oluşturucusu;
- 19. satır: üst sınıf başlatılır;
- 21-28. satırlar: yapılandırmanın belirli verileri kaydedilir;
- 29-34. satırlar: sınıfın yöntemlerinde kullanılan üç özellik oluşturulur;
- satır 36-82: [get_response] yöntemi, [dao] katmanındaki tüm yöntemlerde ortak olan unsurları ayırır: HTTP isteğinin gönderilmesi ve sunucudan HTTP yanıtının alınması;
- satır 38-42: [get_response] yönteminin 5 parametresinin tanımlanması;
- 42. satır: Sunucu bir oturum tuttuğu için, istemcinin çerezleri okuması ve geri göndermesi gerektiği unutulmamalıdır;
- satır 44-46: geçerli bir oturumun açık olup olmadığı kontrol edilir;
- 51. satır: GET durumu. Alınan çerezler geri gönderilir;
- 54. satır: POST örneği. Bu modelde iki tür parametre bulunabilir:
- [x-www-form-urlencoded] türü. URL, [/calculer-impot] ve [/authentifier-utilisateur] örnekleri bu türdür. Bu durumda, yöntem tarafından alınan [data_value] parametresi kullanılır;
- [json] türü. URL ve [/calculer-impots] için de durum böyledir. Bu durumda, yöntem tarafından alınan [json_value] parametresi kullanılır;
Burada da oturum çerezi geri gönderilir.
- 56-62. satırlar: [debug] modundaysak, sunucunun yanıtı günlüğe kaydedilir. Bu günlük kaydı, sunucunun tam olarak neyi geri gönderdiğini bilmemizi sağladığı için önemlidir;
- 64-68. satırlar: json veya xml moduna göre, sunucunun metin yanıtı bir sözlüğe dönüştürülür. URL [/init-session] örneğini ele alalım:
jSON yanıtı şöyledir:
2020-08-03 11:45:21.218116, MainThread : {"action": "init-session", "état": 700, "réponse": ["session démarrée avec le type de réponse json"]}
XML yanıtı şöyledir:
2020-08-03 11:45:54.671871, MainThread : <?xml version="1.0" encoding="utf-8"?>
<root><action>init-session</action><état>700</état><réponse>session démarrée avec le type de réponse xml</réponse></root>
64-68. satırlardaki kod, her iki durumda da [résultat]'te [action, état, réponse] anahtarlarını içeren bir sözlük elde edilmesini sağlar;
- 70-72. satırlar: Yanıtta çerezler varsa bunlar alınır. Bir sonraki istek sırasında bu çerezlerin geri gönderilmesi gerekecektir;
- 74-79. satırlar: Yanıtın HTTP durumu 200'den farklıysa, sonuç[’réponse’] içinde bulunan hata mesajıyla bir istisna atılır. Bu bir hata veya bir hata listesi olabilir;
- satır 81-82: Sunucunun yanıtı çağrı yapan koda döndürülür;
[init_session]
- 84. satır: [init_session] yöntemi, istemcinin sunucuyla başlatmak istediği json veya xml oturum türünü belirlemek için kullanılır;
- satır 86: istenen oturum türü sınıf içinde kaydedilir. Aslında, tüm yöntemler sunucunun yanıtını doğru şekilde çözmek için bu bilgiye ihtiyaç duyar;
- satır 88-90: Uygulama yapılandırması yardımıyla, sorgulanması gereken URL hizmet yöntemi belirlenir;
- 93. satır: URL hizmeti sorgulanır. [get_response] yönteminin sonucu alınmaz:
- eğer bir istisna atarsa, işlem başarısız olmuştur. İstisna burada işlenmez ve doğrudan çağıran koda iletilir; bu kod da bir hata mesajıyla istemciyi durdurur;
- istisna atmazsa, oturumun başlatılması başarılı olmuştur;
[authenticate_user]
def authenticate_user(self, user: str, password: str):
# hizmet URL'si
config_server = self.__config_server
url_service = f"{config_server['urlServer']}{self.__config_services['authenticate-user']}"
post_params = {
"user": user,
"password": password
}
# isteğin yürütülmesi
self.get_response("POST", url_service, post_params)
- [authenticate_user] yöntemi, sunucuya kimlik doğrulaması yapmak için kullanılır. Bunun için 1. satırda [user, password] bağlantı kimlik bilgilerini alır;
- 2-4. satırlar: sorgulanacak hizmetin URL değeri belirlenir;
- 5-8. satırlar: POST'in parametreleri belirlenir; çünkü URL [/authentifier-utilisateur], [user, password] parametrelerine sahip bir POST beklemektedir;
- 11. satır: istek yürütülür. Burada da sunucudan yanıt alınamaz. Başarılı olup olmadığını gösteren, [get_response] tarafından atılan istisnadır;
[calculate_tax]
def calculate_tax(self, taxpayer: TaxPayer):
# hizmet URL'si
config_server = self.__config_server
url_service = f"{config_server['urlServer']}{self.__config_services['calculate-tax']}"
# POST parametreleri
post_params = {
"marié": taxpayer.marié,
"enfants": taxpayer.enfants,
"salaire": taxpayer.salaire
}
# isteğin yürütülmesi
response = self.get_response("POST", url_service, post_params)
# TaxPayer'in yanıtla güncellenmesi
taxpayer.fromdict(response)
- [calculate_tax] yöntemi, parametre olarak geçirilen [taxpayer] vergi mükellefinin vergisini hesaplamaya olanak tanır. Bu parametre, yöntem tarafından değiştirilir (satır 15) ve dolayısıyla yöntemin sonucunu oluşturur;
- 2-4. satırlar: sorgulanacak hizmet URL tanımlanır;
- satır 6-10: gönderilecek POST parametreleri. Aslında, [/calculer-impot] hizmetinin URL'i, [marié, enfants, salaire] parametrelerine sahip bir POST beklemektedir;
- 12-13. satırlar: istek yürütülür ve sunucudan yanıt alınır. [/calculer-impot] hizmetine ait URL, [impôt, décôte, surcôte, réduction, taux] vergi anahtarlarını içeren bir sözlük döndürür;
- satır 15: Elde edilen [response] sözlüğü, [taxpayer] vergi mükellefinin bilgilerini güncellemek için kullanılır;
[calculate_tax_in_bulk_mode]
# toplu modda vergi hesaplaması
def calculate_tax_in_bulk_mode(self, taxpayers: list):
# istisnaların yukarıya aktarılmasına izin veriliyor
# vergi mükelleflerini sözlük listesine dönüştürme
# sadece özellikleri sakla [marié, enfants, salaire]
list_dict_taxpayers = list(
map(lambda taxpayer:
taxpayer.asdict(included_keys=[
'_TaxPayer__marié',
'_TaxPayer__enfants',
'_TaxPayer__salaire']),
taxpayers))
# hizmet URL'si
config_server = self.__config_server
url_service = f"{config_server['urlServer']}{self.__config_services['calculate-tax-in-bulk-mode']}"
# isteğin yürütülmesi
list_dict_taxpayers2 = self.get_response("POST", url_service, data_value=None, json_value=list_dict_taxpayers)
# tek bir vergi mükellefi varsa ve bir XML oturumundaysak, [list_dict_taxpayers2] bir liste değildir
# bu durumda onu bir liste haline getiriyoruz
if not isinstance(list_dict_taxpayers2, list):
list_dict_taxpayers2 = [list_dict_taxpayers2]
# Alınan sonuçlarla vergi mükelleflerinin ilk listesini güncelliyoruz
for i in range(len(taxpayers)):
# vergi mükellefleri listesi güncelleniyor [i]
taxpayers[i].fromdict(list_dict_taxpayers2[i])
# burada [taxpayers] parametresi sunucudan gelen sonuçlarla güncellendi
- 2. satır: Yöntem, TaxPayer türünde bir vergi mükellefi listesi alır;
- satır 7-13: [TaxPayer] türündeki bu öğe listesi, [marié, enfants, salaire] türündeki sözlük listesine dönüştürülür;
- satır 15-17: hizmet URL değeri belirlenir;
- 19-20. satırlar: POST sorgusu yürütülür; bu sorgunun gövdesi olan jSON, 7. satırda oluşturulan sözlük listesinden oluşur. Sunucudan yanıt alınır;
- 23-24. satırlar: Testler, oturum türü XML olduğunda bir sorun ortaya çıkardığını göstermiştir:
- eğer başlangıçtaki vergi mükellefleri listesi N eleman içeriyorsa (N>1), sonuç olarak [OrderedDict] türünde N adet sözlükten oluşan bir liste elde edilir;
- başlangıç listesi tek bir öğe içeriyorsa, bir liste değil, [OrderedDict] türünde bir öğe elde edilir;
- 23-24. satırlar: bu son durum söz konusuysa (1 eleman), sonuç 1 elemanlı bir listeye dönüştürülür;
- 25-28. satırlar: Alınan bu sözlük listesi, başlangıçtaki listedeki her vergi mükellefinin vergisini içerir. Ardından her birini alınan sonuçlarla güncelleriz;
[get_simulations]
def get_simulations(self) -> list:
# hizmet URL'si
config_server = self.__config_server
url_service = f"{config_server['urlServer']}{self.__config_services['get-simulations']}"
# isteğin yürütülmesi
return self.get_response("GET", url_service)
- 1. satır: Yöntem, geçerli oturumda yapılan simülasyonların listesini ister;
- satır 2: Yöntem, sunucudan gelen yanıtı döndürür;
[delete_simulation]
def delete_simulation(self, id: int) -> list:
# hizmet URL'si
config_server = self.__config_server
url_service = f"{config_server['urlServer']}{self.__config_services['delete-simulation']}/{id}"
# isteğin yürütülmesi
return self.get_response("GET", url_service)
- satır 1: Yöntem, kimliği belirtilen simülasyonu siler;
- 7. satır: Yöntem, sunucunun yanıtını, yani istenen silme işleminden sonra kalan simülasyonların listesini döndürür;
[get-admindata]
def get_admindata(self) -> AdminData:
# istisnaların yukarıya iletilmesine izin verilir
# hizmet URL'si
config_server = self.__config_server
url_service = f"{config_server['urlServer']}{self.__config_services['get-admindata']}"
# isteğin yürütülmesi
résultat = self.get_response("GET", url_service)
# sonuç, oturum XML ise str türünde bir değerler sözlüğüdür
if self.__session_type == 'xml':
# yeni sözlük
résultat2 = {}
# her şey sayısal hale getirilir
for key, value in résultat.items():
# sözlüğün bazı öğeleri listelerdir
if isinstance(value, list):
values = []
for value2 in value:
values.append(float(value2))
résultat2[key] = values
else:
# diğerleri ise basit öğelerdir
résultat2[key] = float(value)
else:
résultat2 = résultat
# AdminData türünde sonuç
return AdminData().fromdict(résultat2)
- satır 1: Yöntem, vergi hesaplamasına olanak tanıyan vergi sabitlerini sunucudan ister;
- 29. satır: [AdminData] türünde bir değer döndürür;
- satır 9: sunucudan gelen yanıt bir sözlük biçiminde alınır. Testler, oturumun XML oturumu olduğu durumlarda bir sorun olduğunu göstermektedir: sözlükteki değerler sayısal değerler yerine karakter dizileridir. Bu sorunu [xmltodict] modülünü incelerken bildirmiştik ve bunun normal bir çalışma şekli olduğunu tespit etmiştik. [xmltodict], kendisine verilen XML akışında tür bilgisi içermez. Bununla birlikte, bu özel durumda, alınan sözlüğün tüm değerleri sayısal olarak dönüştürülmelidir. Sözlük, üç adet [limites, coeffr, coeffn] listesi ve bir dizi sayısal özellik içerir;
- 13-25. satırlar: karakter dizisi türündeki değerlere sahip [résultat] sözlüğünden, sayısal değerlere sahip bir [résultat2] sözlüğü oluşturulur;
- 29. satır: [resultat2] sözlüğü, bir [AdminData] türünü başlatmak için kullanılır;
31.2.3. [dao] katmanının fabrika nesnesi
Müşterilerimiz çok iş parçacıklı olacaktır. [dao] katmanı, okuma/yazma durumuna sahip bir sınıf (= okuma/yazma özellikleri) tarafından uygulandığından, her iş parçacığının kendi [dao] katmanına sahip olması gerekir; aksi takdirde iş parçacıkları arasında paylaşılan verilere erişim senkronize edilmelidir. Burada ilk çözümü tercih ediyoruz. [dao] katmanının örneklerini oluşturabilen bir [ImpôtsDaoWithHttpSessionFactory] sınıfı kullanıyoruz:
from ImpôtsDaoWithHttpSession import ImpôtsDaoWithHttpSession
class ImpôtsDaoWithHttpSessionFactory:
def __init__(self, config: dict):
# parametre kaydedilir
self.__config = config
def new_instance(self):
# [dao] katmanından bir örnek döndürülür
return ImpôtsDaoWithHttpSession(self.__config)
31.3. İstemcilerin yapılandırılması

İstemciler, [config] ve [config_layers] dosyalarıyla yapılandırılır. [config] dosyası şöyledir:
def configure(config: dict) -> dict:
import os
# adım 1 ------
# bu dosyanın klasörü
script_dir = os.path.dirname(os.path.abspath(__file__))
# kök dizin
root_dir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020"
# mutlak bağımlılıklar
absolute_dependencies = [
# proje klasörleri
# BaseEntity, MyException
f"{root_dir}/classes/02/entities",
# InterfaceImpôtsDao, InterfaceImpôtsMétier, InterfaceImpôtsUi
f"{root_dir}/impots/v04/interfaces",
# AbstractImpôtsdao, ImpôtsConsole, ImpôtsMétier
f"{root_dir}/impots/v04/services",
# ImpotsDaoWithAdminDataInDatabase
f"{root_dir}/impots/v05/services",
# AdminData, ImpôtsError, TaxPayer
f"{root_dir}/impots/v04/entities",
# Sabitler, dilimler
f"{root_dir}/impots/v05/entities",
# ImpôtsDaoWithHttpSession, ImpôtsDaoWithHttpSessionFactory, InterfaceImpôtsDaoWithHttpSession
f"{script_dir}/../services",
# yapılandırma komut dosyaları
script_dir,
# Günlük Kaydedici
f"{root_dir}/impots/http-servers/02/utilities",
]
# syspath ayarlanıyor
from myutils import set_syspath
set_syspath(absolute_dependencies)
# 2. adım ------
# sabit değerlerle uygulama yapılandırması
config.update({
# vergi mükellefleri dosyası
"taxpayersFilename": f"{script_dir}/../data/input/taxpayersdata.txt",
# sonuç dosyası
"resultsFilename": f"{script_dir}/../data/output/résultats.json",
# hata dosyası
"errorsFilename": f"{script_dir}/../data/output/errors.txt",
# günlük dosyası
"logsFilename": f"{script_dir}/../data/logs/logs.txt",
# vergi hesaplama sunucusu
"server": {
"urlServer": "http://127.0.0.1:5000",
"user": {
"login": "admin",
"password": "admin"
},
"url_services": {
"calculate-tax": "/calculer-impot",
"get-admindata": "/get-admindata",
"calculate-tax-in-bulk-mode": "/calculer-impots",
"init-session": "/init-session",
"end-session": "/fin-session",
"authenticate-user": "/authentifier-utilisateur",
"get-simulations": "/lister-simulations",
"delete-simulation": "/supprimer-simulation"
}
},
# hata ayıklama modu
"debug": True
}
)
# 3. adım ------
# katmanların oluşturulması
import config_layers
config['layers'] = config_layers.configure(config)
# yapılandırma
return config
[config_layers] dosyası şöyledir:
def configure(config: dict) -> dict:
# uygulama katmanlarının örneklenmesi
# [métier] katmanı
from ImpôtsMétier import ImpôtsMétier
métier = ImpôtsMétier()
# DAO katmanının fabrika nesnesi
from ImpôtsDaoWithHttpSessionFactory import ImpôtsDaoWithHttpSessionFactory
dao_factory = ImpôtsDaoWithHttpSessionFactory(config)
# katman yapılandırması döndürülüyor
return {
"dao_factory": dao_factory,
"métier": métier
}
- Müşteriler, [dao] katmanına doğrudan erişemeyeceklerdir. Doğrudan erişim sağlamak için, [dao] katmanının fabrika nesnesini kullanmaları gerekecektir;
31.4. [main] istemcisi

[main] istemcisi, URL ve [/init-session, /authentifier-utilisateur, /calculer-impots, /fin-session]'i test etmeye olanak tanır:
# json veya xml formatında bir parametre bekleniyor
import sys
syntaxe = f"{sys.argv[0]} json / xml"
erreur = len(sys.argv) != 2
if not erreur:
session_type = sys.argv[1].lower()
erreur = session_type != "json" and session_type != "xml"
if erreur:
print(f"syntaxe : {syntaxe}")
sys.exit()
# uygulamayı yapılandırıyoruz
import config
config = config.configure({"session_type": session_type})
# bağımlılıklar
from ImpôtsError import ImpôtsError
import random
import sys
import threading
from Logger import Logger
# [dao] katmanının bir iş parçacığında çalıştırılması
# taxpayers, vergi mükelleflerinin listesidir
def thread_function(config: dict, taxpayers: list):
# [dao] katmanının fabrikası alınır
dao_factory = config['layers']['dao_factory']
# [dao] katmanının bir örneği oluşturulur
dao = dao_factory.new_instance()
# oturum türü
session_type = config['session_type']
# vergi mükellefi sayısı
nb_taxpayers = len(taxpayers)
# günlük
logger.write(f"début du calcul de l'impôt des {nb_taxpayers} contribuables\n")
# oturum başlatılıyor
dao.init_session(session_type)
# kimlik doğrulama yapılıyor
dao.authenticate_user(config['server']['user']['login'], config['server']['user']['password'])
# vergi mükelleflerinin vergisi hesaplanıyor
dao.calculate_tax_in_bulk_mode(taxpayers)
# oturum sonu
dao.end_session()
# günlük
logger.write(f"fin du calcul de l'impôt des {nb_taxpayers} contribuables\n")
# istemcinin iş parçacıkları listesi
threads = []
logger = None
# kod
try:
# günlük kaydedici
logger = Logger(config["logsFilename"])
# yapılandırmada saklanır
config["logger"] = logger
# başlangıç günlüğü
logger.write("début du calcul de l'impôt des contribuables\n")
# [dao] katmanının fabrikasını alıyoruz
dao_factory = config["layers"]["dao_factory"]
# [dao] katmanının bir örneği oluşturulur
dao = dao_factory.new_instance()
# vergi mükelleflerinin verilerini okuma
taxpayers = dao.get_taxpayers_data()["taxpayers"]
# vergi mükelleflerinin?
if not taxpayers:
raise ImpôtsError(36, f"Pas de contribuables valides dans le fichier {config['taxpayersFilename']}")
# birden fazla iş parçacığıyla vergi mükelleflerinin vergisi hesaplanıyor
i = 0
l_taxpayers = len(taxpayers)
while i < len(taxpayers):
# her iş parçacığı 1 ila 4 vergi mükellefini işleyecek
nb_taxpayers = min(l_taxpayers - i, random.randint(1, 4))
# iş parçacığı tarafından işlenen vergi mükelleflerinin listesi
thread_taxpayers = taxpayers[slice(i, i + nb_taxpayers)]
# bir sonraki iş parçacığı için i değerini artırıyoruz
i += nb_taxpayers
# iş parçacığı oluşturulur
thread = threading.Thread(target=thread_function, args=(config, thread_taxpayers))
# ana komut dosyasının iş parçacığı listesine eklenir
threads.append(thread)
# iş parçacığı başlatılır - bu işlem eşzamansızdır - iş parçacığının sonucu beklenmez
thread.start()
# ana iş parçacığı, başlattığı tüm iş parçacıklarının tamamlanmasını bekler
for thread in threads:
thread.join()
# burada tüm iş parçacıkları işlerini tamamladı - her biri bir veya daha fazla nesneyi değiştirdi [taxpayer]
# sonuçlar jSON dosyasına kaydediliyor
dao.write_taxpayers_results(taxpayers)
# son
except BaseException as erreur:
# hata mesajı görüntülenir
print(f"L'erreur suivante s'est produite : {erreur}")
finally:
# günlük kaydedici kapatılır
if logger:
# son kayıt
logger.write("fin du calcul de l'impôt des contribuables\n")
# günlük kaydedicinin kapatılması
logger.close()
# işlem tamamlandı
print("Travail terminé...")
# hata nedeniyle durdurulmuşsa hâlâ mevcut olabilecek iş parçacıklarının sonlandırılması
sys.exit()
- 4-11. satırlar: istemci, sunucuyla kullanılacak oturum türünü (json veya xml) belirten bir parametre bekler;
- 13-15. satırlar: istemci yapılandırılmıştır;
- satır 48-104: Bu kod biliniyor. Birçok kez kullanıldı. Vergi hesaplanması istenen vergi mükelleflerini birkaç iş parçacığına dağıtır;
- 26. satır: [thread_function] yöntemi, her iş parçacığı tarafından kendisine atanan vergi mükelleflerinin vergisini hesaplamak için yürütülen yöntemdir;
- satır 27-30: her iş parçacığının kendine ait bir [dao] katmanı vardır;
- vergi hesaplaması dört adımda gerçekleştirilir:
- 37-38. satırlar: sunucu ile bir oturumun (json veya xml) başlatılması;
- 39-40. satırlar: sunucuya kimlik doğrulama;
- 41-42. satırlar: verginin hesaplanması;
- satır 43-44: sunucuyla oturumun kapatılması;
Bu kodu [json] modunda çalıştırdığımızda, aşağıdaki günlük kayıtları elde ederiz:
2020-08-03 14:28:34.320751, MainThread : début du calcul de l'impôt des contribuables
2020-08-03 14:28:34.328749, Thread-1 : début du calcul de l'impôt des 4 contribuables
2020-08-03 14:28:34.328749, Thread-2 : début du calcul de l'impôt des 4 contribuables
2020-08-03 14:28:34.333592, Thread-3 : début du calcul de l'impôt des 3 contribuables
2020-08-03 14:28:34.368651, Thread-2 : {"action": "init-session", "état": 700, "réponse": ["session démarrée avec le type de réponse json"]}
2020-08-03 14:28:34.375699, Thread-1 : {"action": "init-session", "état": 700, "réponse": ["session démarrée avec le type de réponse json"]}
2020-08-03 14:28:34.377432, Thread-3 : {"action": "init-session", "état": 700, "réponse": ["session démarrée avec le type de réponse json"]}
2020-08-03 14:28:34.385653, Thread-2 : {"action": "authentifier-utilisateur", "état": 200, "réponse": "Authentification réussie"}
2020-08-03 14:28:34.392656, Thread-1 : {"action": "authentifier-utilisateur", "état": 200, "réponse": "Authentification réussie"}
2020-08-03 14:28:34.396377, Thread-3 : {"action": "authentifier-utilisateur", "état": 200, "réponse": "Authentification réussie"}
2020-08-03 14:28:34.406528, Thread-2 : {"action": "calculer-impots", "état": 1500, "réponse": [{"marié": "non", "enfants": 3, "salaire": 100000, "impôt": 16782, "surcôte": 7176, "taux": 0.41, "décôte": 0, "réduction": 0, "id": 1}, {"marié": "oui", "enfants": 3, "salaire": 100000, "impôt": 9200, "surcôte": 2180, "taux": 0.3, "décôte": 0, "réduction": 0, "id": 2}, {"marié": "oui", "enfants": 5, "salaire": 100000, "impôt": 4230, "surcôte": 0, "taux": 0.14, "décôte": 0, "réduction": 0, "id": 3}, {"marié": "non", "enfants": 0, "salaire": 100000, "impôt": 22986, "surcôte": 0, "taux": 0.41, "décôte": 0, "réduction": 0, "id": 4}]}
2020-08-03 14:28:34.413837, Thread-1 : {"action": "calculer-impots", "état": 1500, "réponse": [{"marié": "oui", "enfants": 2, "salaire": 55555, "impôt": 2814, "surcôte": 0, "taux": 0.14, "décôte": 0, "réduction": 0, "id": 1}, {"marié": "oui", "enfants": 2, "salaire": 50000, "impôt": 1384, "surcôte": 0, "taux": 0.14, "décôte": 384, "réduction": 347, "id": 2}, {"marié": "oui", "enfants": 3, "salaire": 50000, "impôt": 0, "surcôte": 0, "taux": 0.14, "décôte": 720, "réduction": 0, "id": 3}, {"marié": "non", "enfants": 2, "salaire": 100000, "impôt": 19884, "surcôte": 4480, "taux": 0.41, "décôte": 0, "réduction": 0, "id": 4}]}
2020-08-03 14:28:34.416695, Thread-3 : {"action": "calculer-impots", "état": 1500, "réponse": [{"marié": "oui", "enfants": 2, "salaire": 30000, "impôt": 0, "surcôte": 0, "taux": 0.0, "décôte": 0, "réduction": 0, "id": 1}, {"marié": "non", "enfants": 0, "salaire": 200000, "impôt": 64210, "surcôte": 7498, "taux": 0.45, "décôte": 0, "réduction": 0, "id": 2}, {"marié": "oui", "enfants": 3, "salaire": 200000, "impôt": 42842, "surcôte": 17283, "taux": 0.41, "décôte": 0, "réduction": 0, "id": 3}]}
2020-08-03 14:28:34.425747, Thread-2 : {"action": "fin-session", "état": 400, "réponse": "session réinitialisée"}
2020-08-03 14:28:34.425747, Thread-2 : fin du calcul de l'impôt des 4 contribuables
2020-08-03 14:28:34.428956, Thread-1 : {"action": "fin-session", "état": 400, "réponse": "session réinitialisée"}
2020-08-03 14:28:34.428956, Thread-1 : fin du calcul de l'impôt des 4 contribuables
2020-08-03 14:28:34.428956, Thread-3 : {"action": "fin-session", "état": 400, "réponse": "session réinitialisée"}
2020-08-03 14:28:34.428956, Thread-3 : fin du calcul de l'impôt des 3 contribuables
2020-08-03 14:28:34.428956, MainThread : fin du calcul de l'impôt des contribuables
Yukarıda [Thread-2] iş parçacığının akışını görüyoruz.
[main]'i XML modunda çalıştırırsak, günlükler şu şekildedir:
2020-08-03 14:32:48.495316, MainThread : début du calcul de l'impôt des contribuables
2020-08-03 14:32:48.496452, Thread-1 : début du calcul de l'impôt des 2 contribuables
2020-08-03 14:32:48.498992, Thread-2 : début du calcul de l'impôt des 2 contribuables
2020-08-03 14:32:48.498992, Thread-3 : début du calcul de l'impôt des 4 contribuables
2020-08-03 14:32:48.498992, Thread-4 : début du calcul de l'impôt des 3 contribuables
2020-08-03 14:32:48.538637, Thread-1 : <?xml version="1.0" encoding="utf-8"?>
<root><action>init-session</action><état>700</état><réponse>session démarrée avec le type de réponse xml</réponse></root>
2020-08-03 14:32:48.540783, Thread-4 : <?xml version="1.0" encoding="utf-8"?>
<root><action>init-session</action><état>700</état><réponse>session démarrée avec le type de réponse xml</réponse></root>
2020-08-03 14:32:48.547811, Thread-3 : <?xml version="1.0" encoding="utf-8"?>
<root><action>init-session</action><état>700</état><réponse>session démarrée avec le type de réponse xml</réponse></root>
2020-08-03 14:32:48.547811, Thread-2 : <?xml version="1.0" encoding="utf-8"?>
<root><action>init-session</action><état>700</état><réponse>session démarrée avec le type de réponse xml</réponse></root>
2020-08-03 14:32:48.555184, Thread-1 : <?xml version="1.0" encoding="utf-8"?>
<root><action>authentifier-utilisateur</action><état>200</état><réponse>Authentification réussie</réponse></root>
2020-08-03 14:32:48.564793, Thread-2 : <?xml version="1.0" encoding="utf-8"?>
<root><action>authentifier-utilisateur</action><état>200</état><réponse>Authentification réussie</réponse></root>
2020-08-03 14:32:48.564793, Thread-3 : <?xml version="1.0" encoding="utf-8"?>
<root><action>authentifier-utilisateur</action><état>200</état><réponse>Authentification réussie</réponse></root>
2020-08-03 14:32:48.568333, Thread-4 : <?xml version="1.0" encoding="utf-8"?>
<root><action>authentifier-utilisateur</action><état>200</état><réponse>Authentification réussie</réponse></root>
2020-08-03 14:32:48.568333, Thread-1 : <?xml version="1.0" encoding="utf-8"?>
<root><action>calculer-impots</action><état>1500</état><réponse><marié>oui</marié><enfants>2</enfants><salaire>55555</salaire><impôt>2814</impôt><surcôte>0</surcôte><taux>0.14</taux><décôte>0</décôte><réduction>0</réduction><id>1</id></réponse><réponse><marié>oui</marié><enfants>2</enfants><salaire>50000</salaire><impôt>1384</impôt><surcôte>0</surcôte><taux>0.14</taux><décôte>384</décôte><réduction>347</réduction><id>2</id></réponse></root>
2020-08-03 14:32:48.579205, Thread-2 : <?xml version="1.0" encoding="utf-8"?>
<root><action>calculer-impots</action><état>1500</état><réponse><marié>oui</marié><enfants>3</enfants><salaire>50000</salaire><impôt>0</impôt><surcôte>0</surcôte><taux>0.14</taux><décôte>720</décôte><réduction>0</réduction><id>1</id></réponse><réponse><marié>non</marié><enfants>2</enfants><salaire>100000</salaire><impôt>19884</impôt><surcôte>4480</surcôte><taux>0.41</taux><décôte>0</décôte><réduction>0</réduction><id>2</id></réponse></root>
2020-08-03 14:32:48.579205, Thread-3 : <?xml version="1.0" encoding="utf-8"?>
<root><action>calculer-impots</action><état>1500</état><réponse><marié>non</marié><enfants>3</enfants><salaire>100000</salaire><impôt>16782</impôt><surcôte>7176</surcôte><taux>0.41</taux><décôte>0</décôte><réduction>0</réduction><id>1</id></réponse><réponse><marié>oui</marié><enfants>3</enfants><salaire>100000</salaire><impôt>9200</impôt><surcôte>2180</surcôte><taux>0.3</taux><décôte>0</décôte><réduction>0</réduction><id>2</id></réponse><réponse><marié>oui</marié><enfants>5</enfants><salaire>100000</salaire><impôt>4230</impôt><surcôte>0</surcôte><taux>0.14</taux><décôte>0</décôte><réduction>0</réduction><id>3</id></réponse><réponse><marié>non</marié><enfants>0</enfants><salaire>100000</salaire><impôt>22986</impôt><surcôte>0</surcôte><taux>0.41</taux><décôte>0</décôte><réduction>0</réduction><id>4</id></réponse></root>
2020-08-03 14:32:48.588051, Thread-4 : <?xml version="1.0" encoding="utf-8"?>
<root><action>calculer-impots</action><état>1500</état><réponse><marié>oui</marié><enfants>2</enfants><salaire>30000</salaire><impôt>0</impôt><surcôte>0</surcôte><taux>0.0</taux><décôte>0</décôte><réduction>0</réduction><id>1</id></réponse><réponse><marié>non</marié><enfants>0</enfants><salaire>200000</salaire><impôt>64210</impôt><surcôte>7498</surcôte><taux>0.45</taux><décôte>0</décôte><réduction>0</réduction><id>2</id></réponse><réponse><marié>oui</marié><enfants>3</enfants><salaire>200000</salaire><impôt>42842</impôt><surcôte>17283</surcôte><taux>0.41</taux><décôte>0</décôte><réduction>0</réduction><id>3</id></réponse></root>
2020-08-03 14:32:48.594058, Thread-1 : <?xml version="1.0" encoding="utf-8"?>
<root><action>fin-session</action><état>400</état><réponse>session réinitialisée</réponse></root>
2020-08-03 14:32:48.595198, Thread-1 : fin du calcul de l'impôt des 2 contribuables
2020-08-03 14:32:48.595198, Thread-2 : <?xml version="1.0" encoding="utf-8"?>
<root><action>fin-session</action><état>400</état><réponse>session réinitialisée</réponse></root>
2020-08-03 14:32:48.595198, Thread-2 : fin du calcul de l'impôt des 2 contribuables
2020-08-03 14:32:48.595198, Thread-3 : <?xml version="1.0" encoding="utf-8"?>
<root><action>fin-session</action><état>400</état><réponse>session réinitialisée</réponse></root>
2020-08-03 14:32:48.595198, Thread-3 : fin du calcul de l'impôt des 4 contribuables
2020-08-03 14:32:48.603351, Thread-4 : <?xml version="1.0" encoding="utf-8"?>
<root><action>fin-session</action><état>400</état><réponse>session réinitialisée</réponse></root>
2020-08-03 14:32:48.603351, Thread-4 : fin du calcul de l'impôt des 3 contribuables
2020-08-03 14:32:48.603351, MainThread : fin du calcul de l'impôt des contribuables
Yukarıda, [Thread-2] iş parçacığının izlediği yol gösterilmektedir.
31.5. [main2] istemcisi

[main2] istemcisi, URL ve [/init-session, /authentifier-utilisateur, /get-admindata, /fin-session]'i test etmenizi sağlar:
# json veya xml formatında bir parametre bekleniyor
import sys
syntaxe = f"{sys.argv[0]} json / xml"
erreur = len(sys.argv) != 2
if not erreur:
session_type = sys.argv[1].lower()
erreur = session_type != "json" and session_type != "xml"
if erreur:
print(f"syntaxe : {syntaxe}")
sys.exit()
# uygulama yapılandırılıyor
import config
config = config.configure({"session_type": session_type})
# bağımlılıklar
from ImpôtsError import ImpôtsError
from Logger import Logger
logger = None
# kod
try:
# günlük kaydı
logger = Logger(config["logsFilename"])
# yapılandırmada kaydediliyor
config["logger"] = logger
# başlangıç günlüğü
logger.write("début du calcul de l'impôt des contribuables\n")
# [dao] katmanının fabrikasını alıyoruz
dao_factory = config['layers']['dao_factory']
# [dao] katmanının bir örneği oluşturulur
dao = dao_factory.new_instance()
# vergi mükellefleri alınır
taxpayers = dao.get_taxpayers_data()["taxpayers"]
# vergi mükellefleri mi?
if not taxpayers:
raise ImpôtsError(36, f"Pas de contribuables valides dans le fichier {config['taxpayersFilename']}")
# oturum türü
session_type = config['session_type']
# oturum başlatılıyor
dao.init_session(session_type)
# kimlik doğrulama yapılıyor
dao.authenticate_user(config['server']['user']['login'], config['server']['user']['password'])
# vergi idaresinden veriler alınıyor
admindata = dao.get_admindata()
# oturum sonu
dao.end_session()
# [métier] katmanı tarafından vergi mükelleflerinin vergisi hesaplanıyor
métier = config['layers']['métier']
for taxpayer in taxpayers:
métier.calculate_tax(taxpayer, admindata)
# sonuçlar jSON dosyasına kaydedilir
dao.write_taxpayers_results(taxpayers)
except BaseException as erreur:
# hatanın görüntülenmesi
print(f"L'erreur suivante s'est produite : {erreur}")
finally:
# günlük kaydedici kapatılır
if logger:
# son kayıt
logger.write("fin du calcul de l'impôt des contribuables\n")
# kayıt cihazı kapatılıyor
logger.close()
# işlem tamamlandı
print("Travail terminé...")
- 1-11. satırlar: sunucu ile kurulacak oturumun türünü belirleyen [json, xml] parametresi alınır;
- 13-15. satırlar: İstemci yapılandırılır;
- satır 30-33: [dao] katmanı oluşturulur;
- satır 34-35: bu katman kullanılarak, vergisi hesaplanacak vergi mükelleflerinin listesi alınır;
- ardından sunucuyla yapılan iletişimin dört adımı gelir;
- satır 41-42: sunucu ile bir oturum başlatılır;
- satır 43-44: sunucuya kimlik doğrulaması yapılır;
- satır 45-46: verginin hesaplanmasına olanak tanıyan vergi sabitleri sunucudan istenir;
- satır 47-48: sunucuyla oturum sonlandırılır;
- satır 49-52: bu sabitler sayesinde, istemcide bulunan [métier] katmanı yardımıyla vergi mükelleflerinin vergisi hesaplanabilir;
- satır 53-54: elde edilen sonuçlar kaydedilir;
Bir XML oturumu için sonuçlar şöyledir:
2020-08-03 14:44:43.194294, MainThread : début du calcul de l'impôt des contribuables
2020-08-03 14:44:43.231633, MainThread : <?xml version="1.0" encoding="utf-8"?>
<root><action>init-session</action><état>700</état><réponse>session démarrée avec le type de réponse xml</réponse></root>
2020-08-03 14:44:43.240872, MainThread : <?xml version="1.0" encoding="utf-8"?>
<root><action>authentifier-utilisateur</action><état>200</état><réponse>Authentification réussie</réponse></root>
2020-08-03 14:44:43.250061, MainThread : <?xml version="1.0" encoding="utf-8"?>
<root><action>get-admindata</action><état>1000</état><réponse><limites>9964.0</limites><limites>27519.0</limites><limites>73779.0</limites><limites>156244.0</limites><limites>93749.0</limites><coeffr>0.0</coeffr><coeffr>0.14</coeffr><coeffr>0.3</coeffr><coeffr>0.41</coeffr><coeffr>0.45</coeffr><coeffn>0.0</coeffn><coeffn>1394.96</coeffn><coeffn>5798.0</coeffn><coeffn>13913.7</coeffn><coeffn>20163.4</coeffn><abattement_dixpourcent_min>437.0</abattement_dixpourcent_min><plafond_impot_couple_pour_decote>2627.0</plafond_impot_couple_pour_decote><plafond_decote_couple>1970.0</plafond_decote_couple><valeur_reduc_demi_part>3797.0</valeur_reduc_demi_part><plafond_revenus_celibataire_pour_reduction>21037.0</plafond_revenus_celibataire_pour_reduction><id>1</id><abattement_dixpourcent_max>12502.0</abattement_dixpourcent_max><plafond_impot_celibataire_pour_decote>1595.0</plafond_impot_celibataire_pour_decote><plafond_decote_celibataire>1196.0</plafond_decote_celibataire><plafond_revenus_couple_pour_reduction>42074.0</plafond_revenus_couple_pour_reduction><plafond_qf_demi_part>1551.0</plafond_qf_demi_part></réponse></root>
2020-08-03 14:44:43.269850, MainThread : <?xml version="1.0" encoding="utf-8"?>
<root><action>fin-session</action><état>400</état><réponse>session réinitialisée</réponse></root>
2020-08-03 14:44:43.269850, MainThread : fin du calcul de l'impôt des contribuables
31.6. [main3] müşterisi
[main3] istemcisi, URL ve [/init-session, /calculer-impots, /get-simulations, /delete-simulation, /fin-session]'i test etmeye olanak tanır:

# json veya xml formatında bir parametre bekleniyor
import sys
syntaxe = f"{sys.argv[0]} json / xml"
erreur = len(sys.argv) != 2
if not erreur:
session_type = sys.argv[1].lower()
erreur = session_type != "json" and session_type != "xml"
if erreur:
print(f"syntaxe : {syntaxe}")
sys.exit()
# uygulama yapılandırılıyor
import config
config = config.configure({"session_type": session_type})
# bağımlılıklar
from ImpôtsError import ImpôtsError
import sys
from Logger import Logger
logger = None
# kod
try:
# günlük kaydı
logger = Logger(config["logsFilename"])
# yapılandırmada kaydediliyor
config["logger"] = logger
# başlangıç günlüğü
logger.write("début du calcul de l'impôt des contribuables\n")
# [dao] katmanının fabrikasını alıyoruz
dao_factory = config["layers"]["dao_factory"]
# [dao] katmanının bir örneği oluşturulur
dao = dao_factory.new_instance()
# vergi mükelleflerinin verilerini okuma
taxpayers = dao.get_taxpayers_data()["taxpayers"]
# vergi mükelleflerinin?
if not taxpayers:
raise ImpôtsError(36, f"Pas de contribuables valides dans le fichier {config['taxpayersFilename']}")
# Vergi mükelleflerinin vergisi hesaplanıyor
# vergi mükelleflerinin sayısı
nb_taxpayers = len(taxpayers)
# günlük
logger.write(f"début du calcul de l'impôt des {nb_taxpayers} contribuables\n")
# oturum başlatılıyor
dao.init_session(session_type)
# kimlik doğrulama
dao.authenticate_user(config['server']['user']['login'], config['server']['user']['password'])
# vergi mükelleflerinin vergisi hesaplanıyor
dao.calculate_tax_in_bulk_mode(taxpayers)
# simülasyon listesi isteniyor
simulations = dao.get_simulations()
# simülasyonlardan her ikisinden birini siliyor
for i in range(len(simulations)):
if i % 2 == 0:
# simülasyon siliniyor
dao.delete_simulation(simulations[i]['id'])
# oturum sonu
dao.end_session()
# farklı sonuçları görmek için günlük dosyalarını inceleyin (debug=True modu)
except BaseException as erreur:
# hatanın görüntülenmesi
print(f"L'erreur suivante s'est produite : {erreur}")
finally:
# günlük kaydedici kapatılıyor
if logger:
# son kayıt
logger.write("fin du calcul de l'impôt des contribuables\n")
# günlük kaydedicinin kapatılması
logger.close()
# işlem tamamlandı
print("Travail terminé...")
- 1-11. satırlar: komut dosyasının parametrelerinden oturum türü alınır;
- 13-15. satırlar: uygulama yapılandırılır;
- 25-50. satırlar: Daha önce bir ara açıklanmış olan kod parçaları burada tekrar karşımıza çıkar;
- 51-52. satırlar: mevcut oturumda yapılan simülasyonların listesi istenir;
- satır 53-57: simülasyonların yarısı silinir;
- 58-59. satırlar: oturum sonlandırılır;
jSON oturumu sırasında günlükler şu şekildedir:
2020-08-03 15:01:52.702297, MainThread : début du calcul de l'impôt des contribuables
2020-08-03 15:01:52.702297, MainThread : début du calcul de l'impôt des 11 contribuables
2020-08-03 15:01:52.734806, MainThread : {"action": "init-session", "état": 700, "réponse": ["session démarrée avec le type de réponse json"]}
2020-08-03 15:01:52.747961, MainThread : {"action": "authentifier-utilisateur", "état": 200, "réponse": "Authentification réussie"}
2020-08-03 15:01:52.765721, MainThread : {"action": "calculer-impots", "état": 1500, "réponse": [{"marié": "oui", "enfants": 2, "salaire": 55555, "impôt": 2814, "surcôte": 0, "taux": 0.14, "décôte": 0, "réduction": 0, "id": 1}, {"marié": "oui", "enfants": 2, "salaire": 50000, "impôt": 1384, "surcôte": 0, "taux": 0.14, "décôte": 384, "réduction": 347, "id": 2}, {"marié": "oui", "enfants": 3, "salaire": 50000, "impôt": 0, "surcôte": 0, "taux": 0.14, "décôte": 720, "réduction": 0, "id": 3}, {"marié": "non", "enfants": 2, "salaire": 100000, "impôt": 19884, "surcôte": 4480, "taux": 0.41, "décôte": 0, "réduction": 0, "id": 4}, {"marié": "non", "enfants": 3, "salaire": 100000, "impôt": 16782, "surcôte": 7176, "taux": 0.41, "décôte": 0, "réduction": 0, "id": 5}, {"marié": "oui", "enfants": 3, "salaire": 100000, "impôt": 9200, "surcôte": 2180, "taux": 0.3, "décôte": 0, "réduction": 0, "id": 6}, {"marié": "oui", "enfants": 5, "salaire": 100000, "impôt": 4230, "surcôte": 0, "taux": 0.14, "décôte": 0, "réduction": 0, "id": 7}, {"marié": "non", "enfants": 0, "salaire": 100000, "impôt": 22986, "surcôte": 0, "taux": 0.41, "décôte": 0, "réduction": 0, "id": 8}, {"marié": "oui", "enfants": 2, "salaire": 30000, "impôt": 0, "surcôte": 0, "taux": 0.0, "décôte": 0, "réduction": 0, "id": 9}, {"marié": "non", "enfants": 0, "salaire": 200000, "impôt": 64210, "surcôte": 7498, "taux": 0.45, "décôte": 0, "réduction": 0, "id": 10}, {"marié": "oui", "enfants": 3, "salaire": 200000, "impôt": 42842, "surcôte": 17283, "taux": 0.41, "décôte": 0, "réduction": 0, "id": 11}]}
2020-08-03 15:01:52.785505, MainThread : {"action": "lister-simulations", "état": 500, "réponse": [{"décôte": 0, "enfants": 2, "id": 1, "impôt": 2814, "marié": "oui", "réduction": 0, "salaire": 55555, "surcôte": 0, "taux": 0.14}, {"décôte": 384, "enfants": 2, "id": 2, "impôt": 1384, "marié": "oui", "réduction": 347, "salaire": 50000, "surcôte": 0, "taux": 0.14}, {"décôte": 720, "enfants": 3, "id": 3, "impôt": 0, "marié": "oui", "réduction": 0, "salaire": 50000, "surcôte": 0, "taux": 0.14}, {"décôte": 0, "enfants": 2, "id": 4, "impôt": 19884, "marié": "non", "réduction": 0, "salaire": 100000, "surcôte": 4480, "taux": 0.41}, {"décôte": 0, "enfants": 3, "id": 5, "impôt": 16782, "marié": "non", "réduction": 0, "salaire": 100000, "surcôte": 7176, "taux": 0.41}, {"décôte": 0, "enfants": 3, "id": 6, "impôt": 9200, "marié": "oui", "réduction": 0, "salaire": 100000, "surcôte": 2180, "taux": 0.3}, {"décôte": 0, "enfants": 5, "id": 7, "impôt": 4230, "marié": "oui", "réduction": 0, "salaire": 100000, "surcôte": 0, "taux": 0.14}, {"décôte": 0, "enfants": 0, "id": 8, "impôt": 22986, "marié": "non", "réduction": 0, "salaire": 100000, "surcôte": 0, "taux": 0.41}, {"décôte": 0, "enfants": 2, "id": 9, "impôt": 0, "marié": "oui", "réduction": 0, "salaire": 30000, "surcôte": 0, "taux": 0.0}, {"décôte": 0, "enfants": 0, "id": 10, "impôt": 64210, "marié": "non", "réduction": 0, "salaire": 200000, "surcôte": 7498, "taux": 0.45}, {"décôte": 0, "enfants": 3, "id": 11, "impôt": 42842, "marié": "oui", "réduction": 0, "salaire": 200000, "surcôte": 17283, "taux": 0.41}]}
2020-08-03 15:01:52.801475, MainThread : {"action": "supprimer-simulation", "état": 600, "réponse": [{"décôte": 384, "enfants": 2, "id": 2, "impôt": 1384, "marié": "oui", "réduction": 347, "salaire": 50000, "surcôte": 0, "taux": 0.14}, {"décôte": 720, "enfants": 3, "id": 3, "impôt": 0, "marié": "oui", "réduction": 0, "salaire": 50000, "surcôte": 0, "taux": 0.14}, {"décôte": 0, "enfants": 2, "id": 4, "impôt": 19884, "marié": "non", "réduction": 0, "salaire": 100000, "surcôte": 4480, "taux": 0.41}, {"décôte": 0, "enfants": 3, "id": 5, "impôt": 16782, "marié": "non", "réduction": 0, "salaire": 100000, "surcôte": 7176, "taux": 0.41}, {"décôte": 0, "enfants": 3, "id": 6, "impôt": 9200, "marié": "oui", "réduction": 0, "salaire": 100000, "surcôte": 2180, "taux": 0.3}, {"décôte": 0, "enfants": 5, "id": 7, "impôt": 4230, "marié": "oui", "réduction": 0, "salaire": 100000, "surcôte": 0, "taux": 0.14}, {"décôte": 0, "enfants": 0, "id": 8, "impôt": 22986, "marié": "non", "réduction": 0, "salaire": 100000, "surcôte": 0, "taux": 0.41}, {"décôte": 0, "enfants": 2, "id": 9, "impôt": 0, "marié": "oui", "réduction": 0, "salaire": 30000, "surcôte": 0, "taux": 0.0}, {"décôte": 0, "enfants": 0, "id": 10, "impôt": 64210, "marié": "non", "réduction": 0, "salaire": 200000, "surcôte": 7498, "taux": 0.45}, {"décôte": 0, "enfants": 3, "id": 11, "impôt": 42842, "marié": "oui", "réduction": 0, "salaire": 200000, "surcôte": 17283, "taux": 0.41}]}
2020-08-03 15:01:52.810129, MainThread : {"action": "supprimer-simulation", "état": 600, "réponse": [{"décôte": 384, "enfants": 2, "id": 2, "impôt": 1384, "marié": "oui", "réduction": 347, "salaire": 50000, "surcôte": 0, "taux": 0.14}, {"décôte": 0, "enfants": 2, "id": 4, "impôt": 19884, "marié": "non", "réduction": 0, "salaire": 100000, "surcôte": 4480, "taux": 0.41}, {"décôte": 0, "enfants": 3, "id": 5, "impôt": 16782, "marié": "non", "réduction": 0, "salaire": 100000, "surcôte": 7176, "taux": 0.41}, {"décôte": 0, "enfants": 3, "id": 6, "impôt": 9200, "marié": "oui", "réduction": 0, "salaire": 100000, "surcôte": 2180, "taux": 0.3}, {"décôte": 0, "enfants": 5, "id": 7, "impôt": 4230, "marié": "oui", "réduction": 0, "salaire": 100000, "surcôte": 0, "taux": 0.14}, {"décôte": 0, "enfants": 0, "id": 8, "impôt": 22986, "marié": "non", "réduction": 0, "salaire": 100000, "surcôte": 0, "taux": 0.41}, {"décôte": 0, "enfants": 2, "id": 9, "impôt": 0, "marié": "oui", "réduction": 0, "salaire": 30000, "surcôte": 0, "taux": 0.0}, {"décôte": 0, "enfants": 0, "id": 10, "impôt": 64210, "marié": "non", "réduction": 0, "salaire": 200000, "surcôte": 7498, "taux": 0.45}, {"décôte": 0, "enfants": 3, "id": 11, "impôt": 42842, "marié": "oui", "réduction": 0, "salaire": 200000, "surcôte": 17283, "taux": 0.41}]}
2020-08-03 15:01:52.818803, MainThread : {"action": "supprimer-simulation", "état": 600, "réponse": [{"décôte": 384, "enfants": 2, "id": 2, "impôt": 1384, "marié": "oui", "réduction": 347, "salaire": 50000, "surcôte": 0, "taux": 0.14}, {"décôte": 0, "enfants": 2, "id": 4, "impôt": 19884, "marié": "non", "réduction": 0, "salaire": 100000, "surcôte": 4480, "taux": 0.41}, {"décôte": 0, "enfants": 3, "id": 6, "impôt": 9200, "marié": "oui", "réduction": 0, "salaire": 100000, "surcôte": 2180, "taux": 0.3}, {"décôte": 0, "enfants": 5, "id": 7, "impôt": 4230, "marié": "oui", "réduction": 0, "salaire": 100000, "surcôte": 0, "taux": 0.14}, {"décôte": 0, "enfants": 0, "id": 8, "impôt": 22986, "marié": "non", "réduction": 0, "salaire": 100000, "surcôte": 0, "taux": 0.41}, {"décôte": 0, "enfants": 2, "id": 9, "impôt": 0, "marié": "oui", "réduction": 0, "salaire": 30000, "surcôte": 0, "taux": 0.0}, {"décôte": 0, "enfants": 0, "id": 10, "impôt": 64210, "marié": "non", "réduction": 0, "salaire": 200000, "surcôte": 7498, "taux": 0.45}, {"décôte": 0, "enfants": 3, "id": 11, "impôt": 42842, "marié": "oui", "réduction": 0, "salaire": 200000, "surcôte": 17283, "taux": 0.41}]}
2020-08-03 15:01:52.834604, MainThread : {"action": "supprimer-simulation", "état": 600, "réponse": [{"décôte": 384, "enfants": 2, "id": 2, "impôt": 1384, "marié": "oui", "réduction": 347, "salaire": 50000, "surcôte": 0, "taux": 0.14}, {"décôte": 0, "enfants": 2, "id": 4, "impôt": 19884, "marié": "non", "réduction": 0, "salaire": 100000, "surcôte": 4480, "taux": 0.41}, {"décôte": 0, "enfants": 3, "id": 6, "impôt": 9200, "marié": "oui", "réduction": 0, "salaire": 100000, "surcôte": 2180, "taux": 0.3}, {"décôte": 0, "enfants": 0, "id": 8, "impôt": 22986, "marié": "non", "réduction": 0, "salaire": 100000, "surcôte": 0, "taux": 0.41}, {"décôte": 0, "enfants": 2, "id": 9, "impôt": 0, "marié": "oui", "réduction": 0, "salaire": 30000, "surcôte": 0, "taux": 0.0}, {"décôte": 0, "enfants": 0, "id": 10, "impôt": 64210, "marié": "non", "réduction": 0, "salaire": 200000, "surcôte": 7498, "taux": 0.45}, {"décôte": 0, "enfants": 3, "id": 11, "impôt": 42842, "marié": "oui", "réduction": 0, "salaire": 200000, "surcôte": 17283, "taux": 0.41}]}
2020-08-03 15:01:52.843803, MainThread : {"action": "supprimer-simulation", "état": 600, "réponse": [{"décôte": 384, "enfants": 2, "id": 2, "impôt": 1384, "marié": "oui", "réduction": 347, "salaire": 50000, "surcôte": 0, "taux": 0.14}, {"décôte": 0, "enfants": 2, "id": 4, "impôt": 19884, "marié": "non", "réduction": 0, "salaire": 100000, "surcôte": 4480, "taux": 0.41}, {"décôte": 0, "enfants": 3, "id": 6, "impôt": 9200, "marié": "oui", "réduction": 0, "salaire": 100000, "surcôte": 2180, "taux": 0.3}, {"décôte": 0, "enfants": 0, "id": 8, "impôt": 22986, "marié": "non", "réduction": 0, "salaire": 100000, "surcôte": 0, "taux": 0.41}, {"décôte": 0, "enfants": 0, "id": 10, "impôt": 64210, "marié": "non", "réduction": 0, "salaire": 200000, "surcôte": 7498, "taux": 0.45}, {"décôte": 0, "enfants": 3, "id": 11, "impôt": 42842, "marié": "oui", "réduction": 0, "salaire": 200000, "surcôte": 17283, "taux": 0.41}]}
2020-08-03 15:01:52.851855, MainThread : {"action": "supprimer-simulation", "état": 600, "réponse": [{"décôte": 384, "enfants": 2, "id": 2, "impôt": 1384, "marié": "oui", "réduction": 347, "salaire": 50000, "surcôte": 0, "taux": 0.14}, {"décôte": 0, "enfants": 2, "id": 4, "impôt": 19884, "marié": "non", "réduction": 0, "salaire": 100000, "surcôte": 4480, "taux": 0.41}, {"décôte": 0, "enfants": 3, "id": 6, "impôt": 9200, "marié": "oui", "réduction": 0, "salaire": 100000, "surcôte": 2180, "taux": 0.3}, {"décôte": 0, "enfants": 0, "id": 8, "impôt": 22986, "marié": "non", "réduction": 0, "salaire": 100000, "surcôte": 0, "taux": 0.41}, {"décôte": 0, "enfants": 0, "id": 10, "impôt": 64210, "marié": "non", "réduction": 0, "salaire": 200000, "surcôte": 7498, "taux": 0.45}]}
2020-08-03 15:01:52.863165, MainThread : {"action": "fin-session", "état": 400, "réponse": "session réinitialisée"}
2020-08-03 15:01:52.863165, MainThread : fin du calcul de l'impôt des contribuables
- 6. satır: 11 simülasyon var;
- satır 12: çeşitli silme işlemlerinden sonra sadece 5 tane kalmıştır;
31.7. [Test2HttpClientDaoWithSession] test sınıfı

[Test2HttpClientDaoWithSession] sınıfı, istemcilerin [dao] katmanını şu şekilde test eder:
import unittest
from ImpôtsError import ImpôtsError
from Logger import Logger
from TaxPayer import TaxPayer
class Test2HttpClientDaoWithSession(unittest.TestCase):
def test_init_session_json(self) -> None:
print('test_init_session_json')
erreur = False
try:
dao.init_session('json')
except ImpôtsError as ex:
print(ex)
erreur = True
# hata olmamalı
self.assertFalse(erreur)
def test_init_session_xml(self) -> None:
print('test_init_session_xml')
erreur = False
try:
dao.init_session('xml')
except ImpôtsError as ex:
print(ex)
erreur = True
# hata olmamalı
self.assertFalse(erreur)
def test_init_session_xxx(self) -> None:
print('test_init_session_xxx')
erreur = False
try:
dao.init_session('xxx')
except ImpôtsError as ex:
print(ex)
erreur = True
# bir hata olmalı
self.assertTrue(erreur)
def test_authenticate_user_success(self) -> None:
print('test_authenticate_user_success')
# oturum başlat
dao.init_session('json')
# test
erreur = False
try:
dao.authenticate_user(config['server']['user']['login'], config['server']['user']['password'])
except ImpôtsError as ex:
print(ex)
erreur = True
# hata olmamalı
self.assertFalse(erreur)
def test_authenticate_user_failed(self) -> None:
print('test_authenticate_user_failed')
# oturum başlatma
dao.init_session('json')
# test
erreur = False
try:
dao.authenticate_user('x', 'y')
except ImpôtsError as ex:
print(ex)
erreur = True
# bir hata olmalı
self.assertTrue(erreur)
def test_get_simulations(self) -> None:
print('test_get_simulations')
# oturum başlatma
dao.init_session('json')
# kimlik doğrulama
dao.authenticate_user('admin', 'admin')
# vergi hesaplama
# {'evli': 'evet', 'çocuk sayısı': 2, 'maaş': 55555,
# 'vergi': 2814, 'ek vergi': 0, 'indirim': 0, 'azaltma': 0, 'oran': 0,14}
taxpayer = TaxPayer().fromdict({"marié": "oui", "enfants": 2, "salaire": 55555})
dao.calculate_tax(taxpayer)
# get_simulations
simulations = dao.get_simulations()
# kontroller
# 1 simülasyon olmalı
self.assertEqual(1, len(simulations))
simulation = simulations[0]
# hesaplanan verginin kontrolü
self.assertAlmostEqual(simulation['impôt'], 2815, delta=1)
self.assertEqual(simulation['décôte'], 0)
self.assertEqual(simulation['réduction'], 0)
self.assertAlmostEqual(simulation['taux'], 0.14, delta=0.01)
self.assertEqual(simulation['surcôte'], 0)
def test_delete_simulation(self) -> None:
print('test_delete_simulation')
# oturum başlatma
dao.init_session('json')
# kimlik doğrulama
dao.authenticate_user('admin', 'admin')
# vergi hesaplama
taxpayer = TaxPayer().fromdict({"marié": "oui", "enfants": 2, "salaire": 55555})
dao.calculate_tax(taxpayer)
# get_simulations
simulations = dao.get_simulations()
# delete_simulation
dao.delete_simulation(simulations[0]['id'])
# get_simulations
simulations = dao.get_simulations()
# doğrulama - artık simülasyon kalmamalı
self.assertEqual(0, len(simulations))
# var olmayan bir simülasyon siliniyor
erreur = False
try:
dao.delete_simulation(100)
except ImpôtsError as ex:
print(ex)
erreur = True
# bir hata olmalı
self.assertTrue(erreur)
if __name__ == '__main__':
# uygulama yapılandırılıyor
import config
config = config.configure({})
# günlüğe kaydediliyor
logger = Logger(config["logsFilename"])
# yapılandırmada kaydediliyor
config["logger"] = logger
# katman [dao]
dao_factory = config['layers']['dao_factory']
dao = dao_factory.new_instance()
# test yöntemleri çalıştırılıyor
print("tests en cours...")
unittest.main()
- [dao] katmanı sunucuya bir istek gönderir, yanıtını alır ve çağıran koda geri döndürmek üzere biçimlendirir. Sunucu, 200'den farklı bir durum koduna sahip bir yanıt gönderdiğinde, [dao] katmanı bir istisna oluşturur. Ayrıca, bir dizi test, istisna olup olmadığını belirlemeye yöneliktir;
- 9-18. satırlar: jSON oturumu başlatılır. Hata olmamalıdır;
- 20-29. satırlar: XML oturumu başlatılır. Hata olmamalıdır;
- satır 31-40: yanlış türde bir oturum başlatılır. Hata oluşmalıdır;
- satır 42-54: Doğru kimlik bilgileriyle oturum açılıyor. Hata olmamalıdır;
- satır 56-68: yanlış kimlik bilgileriyle oturum açılıyor. Bir hata oluşmalıdır;
- 70-92. satırlar: Vergi hesaplaması yapılıyor ve ardından simülasyon listesi isteniyor. Bir simülasyon bulunmalıdır. Ayrıca, bu simülasyonun istenen vergiyi içerdiği doğrulanıyor;
- 94-119. satırlar: Bir simülasyon oluşturulur ve ardından silinir. Ardından, artık simülasyon kalmamışken bir simülasyonu silmeye çalışılır. Bir hata oluşmalıdır;
- satır 121-137: test, klasik bir konsol komut dosyası olarak çalıştırılır;
- satır 122-124: uygulamayı yapılandırıyoruz;
- satır 126-129: logger yapılandırılıyor. Bu, günlükleri takip etmemizi sağlayacak;
- satır 131-133: Test edilecek olan [dao] katmanının bir örneği oluşturulur;
- 135-137. satırlar: testler başlatılıyor;
Konsol çıktıları şu şekildedir:
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/http-clients/07/tests/Test2HttpClientDaoWithSession.py
tests en cours...
test_authenticate_user_failed
..MyException[35, ["Echec de l'authentification"]]
test_authenticate_user_success
test_delete_simulation
MyException[35, ["la simulation n° [100] n'existe pas"]]
test_get_simulations
test_init_session_json
test_init_session_xml
test_init_session_xxx
MyException[73, il n'y a pas de session valide en cours]
----------------------------------------------------------------------
Ran 7 tests in 0.171s
OK
Process finished with exit code 0