Skip to content

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:

Image

Üç 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.

Image

  • [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ı

Image

Image

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ı

Image

İ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

Image

[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(36f"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(14))
        # 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

Image

[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(36f"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:

Image


# 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(36f"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ı

Image

[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