Skip to content

25. Uygulama alıştırması: sürüm 8

25.1. Giriş

Yeni bir istemci/sunucu uygulaması yazacağız. Sunucudaki yenilik, bir oturumu yönetecek olmasıdır. Vergi idaresi verilerini [application] kapsamlı bir nesneye koymak yerine, [session] kapsamlı bir nesneye koyacağız. Bunu yaparak, kodun performansında bir gerileme yaşanır. Bir nesne tüm kullanıcılar tarafından salt okunur olarak paylaşılabiliyorsa, onu [session] kapsamlı bir nesne yerine [application] kapsamlı bir nesne haline getirmek daha tercih edilebilir. Böylece oturum çerezinin boyutu küçüldüğü için en azından bant genişliğinden tasarruf sağlanır. Ancak biz, istemci ile sunucunun birbirlerine oturum çerezi gönderdiği bir istemci/sunucu uygulamasını göstermek istiyoruz.

Uygulamanın mimarisi değişmez:

Image

25.2. Web sunucusu

Sunucudaki komut dosyalarının dizin yapısı şu şekildedir:

Image

[http-servers/03] klasörü, başlangıçta [http-servers/02] klasörünün kopyalanmasıyla oluşturulur. Ardından gerekli değişiklikler yapılır.

25.2.1. Yapılandırma

|version précédente|'teki ile aynıdır, ancak [config] betiğinde birkaç değişiklik yapılmıştır:


# 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",
        # index_controller
        f"{script_dir}/../controllers",
        # komut dosyaları [config_database, config_layers]
        script_dir,
        # Logger, SendAdminMail
        f"{root_dir}/impots/http-servers/02/utilities",
    ]
  • 17. satır: URL dosyasını işleyen [index] işlevi için bir denetleyici yeniden yazılacaktır;
  • 21. satır: |version précédente|'teki yardımcı programları kullanıyoruz;

25.2.2. Ana komut dosyası [main]

Yeni [main] betiği, önceki sürümdeki ana betik [main]’te bazı değişiklikler getiriyor:


# Flask uygulaması başlatılabilir
app = Flask(__name__)
# oturumun gizli anahtarı
app.secret_key = os.urandom(12).hex()
  • 4. satır: Uygulama için bir gizli anahtar oluşturulur. Bu anahtarın oturumları yönetmek için gerekli olduğu bilinmektedir;

Ardından, [main] kodunda vergi bilgileri artık istenmemektedir. Aşağıdaki satırlar kaldırılmıştır:


# Vergi idaresinden veri alınması
erreur = False
try:
    # admindata, uygulama kapsamında salt okunur bir veri olacaktır
    config["admindata"] = config["layers"]["dao"].get_admindata()
    # başarı günlüğü
    logger.write("[serveur] connexion à la base de données réussie\n")
except ImpôtsError as ex:
    # hata kaydedildi
    erreur = True
    # hata günlüğü
    log = f"L'erreur suivante s'est produite : {ex}"
    # konsol
    print(log)
    # günlük dosyası
    logger.write(f"{log}\n")
    # yöneticiye e-posta
    send_adminmail(config, log)

Ayrıca, [index_controller] denetleyicisi, Flask oturumu adında ek bir parametre kabul eder:


from flask import request, Flask, session
….        
         # isteği bir denetleyici tarafından yürütülür
        résultat, status_code = index_controller.execute(request, session, config)

25.2.3. [index_controller] denetleyicisi

[index_controller] denetleyicisi artık bir oturumu yönetmektedir:


# bağımlılıkların içe aktarılması
import os
import re
import threading

from flask_api import status
from werkzeug.local import LocalProxy

# URL yapılandırıldı: /?evli=xx&çocuk=yy&maaş=zz
from AdminData import AdminData
from ImpôtsError import ImpôtsError


def execute(request: LocalProxy, session: LocalProxy, config: dict) -> tuple:
    # bağımlılıklar
    from TaxPayer import TaxPayer

    # başlangıçta hata yok
    erreurs = []
    

    # hatalar mı var?
    if erreurs:
        # müşteriye hata yanıtı veriliyor
        return {"réponse": {"erreurs": erreurs}}, status.HTTP_400_BAD_REQUEST

    # hata yok, çalışmaya devam edilebilir
    # iş parçacığıyla ilişkili yapılandırma alınır
    thread_name = threading.current_thread().name
    logger = config[thread_name]["config"]["logger"]
    # isteği yürütülüyor
    réponse = None
    try:
        # en basit durum - admindata zaten oturumda
        if session.get('client_id') is not None:
            # oturum bilgilerini alıyoruz
            client_id = session.get('client_id')
            admindata = AdminData().fromdict(session.get('admindata'))
            # günlük
            logger.write(f"[index_controller] client [{client_id}], données fiscales prises en session\n")
        else:
            # vergi idaresi verilerinin alınması
            admindata = config["layers"]["dao"].get_admindata()
            # admindata oturumu açılıyor
            session['admindata'] = admindata.asdict()
            # müşteriye bir numara veriliyor ve bu numara oturuma kaydediliyor
            # bu sayede onu sunucu günlüklerinde takip edebileceğiz
            client_id = os.urandom(12).hex()
            session['client_id'] = client_id
            # günlük
            logger.write(f"[index_controller] client [{client_id}], données fiscales prises dans la couche dao\n")
        # vergi hesaplaması
        taxpayer = TaxPayer().fromdict({'marié': marié, 'enfants': enfants, 'salaire': salaire})
        config["layers"]["métier"].calculate_tax(taxpayer, admindata)
        # cevabı müşteriye göndeririz
        return {"réponse": {"result": taxpayer.asdict()}}, status.HTTP_200_OK
    except (BaseException, ImpôtsError) as erreur:
        # yanıtı müşteriye gönderiyoruz
        return {"réponse": {"erreurs": [f"{erreur}"]}}, status.HTTP_500_INTERNAL_SERVER_ERROR
  • 14. satır: denetleyici, web istemcisinden geçerli oturumu alır;
  • 35-38. satırlar: İstemcinin bir oturumu varsa, bu oturum iki anahtar içerir:
    • [client_id]: bir müşteri numarası (satır 37);
    • [admindata]: bir sözlük biçiminde vergi idaresi verileri (satır 38);
  • satır 35: oturumda beklenen iki anahtardan herhangi birinin olup olmadığına bakılır;
  • 42-51. satırlar: müşterinin oturumu henüz başlatılmamışsa;
    • satır 43: [dao] katmanından vergi dairesi verileri alınır;
    • satır 45: bu veriler bir sözlük biçiminde oturuma eklenir;
    • satır 48: müşteriye rastgele bir numara atanır. Bu numara her müşteri için farklı olacaktır;
    • 49. satır: Bu numara oturuma eklenir;
    • satır 51: Vergi idaresi verilerinin [dao] katmanından alındığı bilgisi günlüğe kaydedilir. [dao] katmanına erişim genellikle maliyetlidir. Bu nedenle bu erişimler sınırlandırılmalıdır. Buradaki amaç, vergi verilerini [dao] katmanından bir kez alıp oturuma kaydetmek ve aynı müşterinin sonraki isteklerinde buradan almaktır. Bunun en iyi çözüm olmadığı unutulmamalıdır. Vergi idaresinin verileri tüm müşteriler için aynı olduğundan, bunların yeri uygulama kapsamındaki bir nesnedir;
  • 35-40. satırlar: müşterinin oturumu önceki bir istek sırasında başlatılmışsa;
    • satır 37: oturumdan müşteri numarası alınır;
    • satır 38: oturumdan idarenin vergi verileri alınır;
    • satır 40: müşterinin oturumda idarenin vergi verilerini aldığı bilgisi günlüğe kaydedilir;

25.3. Web istemcisi

Image

25.3.1. [dao] katmanı

25.3.1.1. [ImpôtsDaoWithHttpSession] sınıfı

[dao] katmanı, aşağıdaki [ImpôtsDaoWithHttpSession] sınıfı tarafından uygulanır:


# ithalatlar

import requests
from flask_api import status
from myutils import decode_flask_session

from AbstractImpôtsDao import AbstractImpôtsDao
from AdminData import AdminData
from ImpôtsError import ImpôtsError
from InterfaceImpôtsMétier import InterfaceImpôtsMétier
from TaxPayer import TaxPayer


class ImpôtsDaoWithHttpSession(AbstractImpôtsDao, InterfaceImpôtsMétier):

    # oluşturucu
    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"]
        # hata ayıklama modu
        self.__debug = config["debug"]
        # günlük kaydı
        self.__logger = None
        # çerezler
        self.__cookies = None

    # kullanılmayan yöntem
    def get_admindata(self) -> AdminData:
        pass

    # vergi hesaplaması
    def calculate_tax(self, taxpayer: TaxPayer, admindata: AdminData = None):
        # istisnaların yukarıya iletilmesine izin verilir
        # GET parametreleri
        params = {"marié": taxpayer.marié, "enfants": taxpayer.enfants, "salaire": taxpayer.salaire}
        # Auth Basic kimlik doğrulamalı bağlantı mı?
        if self.__config_server['authBasic']:
            response = requests.get(
                # sorgulanan sunucunun URL'i
                self.__config_server['urlServer'],
                # URL parametreleri
                params=params,
                # Basic kimlik doğrulaması
                auth=(
                    self.__config_server["user"]["login"],
                    self.__config_server["user"]["password"]),
                cookies=self.__cookies)

        else:
            # Auth Basic kimlik doğrulaması olmadan bağlantı
            response = requests.get(self.__config_server['urlServer'], params=params, cookies=self.__cookies)
        # varsa yanıtta bulunan çerezler alınır
        if response.cookies:
            self.__cookies = response.cookies
            # oturum çerezi alınır
            session_cookie = response.cookies.get('session')
            # oturum açmak için çerezi çözümlenir
            if session_cookie:
                # oturum açma işlevi
                if not self.__logger:
                    self.__logger = self.__config['logger']
                # giriş yapılır
                self.__logger.write(f"cookie de session={decode_flask_session(session_cookie)}\n")

        # hata ayıklama modu mu?
        if self.__debug:
            # günlük kaydı
            if not self.__logger:
                self.__logger = self.__config['logger']
            # günlüğe kaydediliyor
            self.__logger.write(f"{response.text}\n")
        # yanıt durum kodu HTTP
        status_code = response.status_code
        # jSON yanıtını bir sözlüğe ekleniyor
        résultat = response.json()
        # durum kodu 200'den farklıysa hata OK
        if status_code != status.HTTP_200_OK:
            # hataların yanıtın [erreurs] anahtarıyla ilişkilendirildiği biliniyor
            raise ImpôtsError(87, résultat['réponse']['erreurs'])
        # sonucun yanıtın [result] anahtarıyla ilişkilendirildiği biliniyor
        # giriş parametresi bu sonuçla değiştiriliyor
        taxpayer.fromdict(résultat["réponse"]["result"])
  • 30. satır: [dao] katmanı bir çerez sözlüğünü yönetecektir;
  • satır 58: [response.cookies] özelliği, sunucu tarafından HTTP ve [Set-Cookie] başlıklarında gönderilen çerezleri bir araya getiren bir sözlüktür;
  • 59. satır: Bu çerezler [dao] katmanında saklanır. Aynı istemciden gelecek sonraki isteklerde sunucuya geri gönderilecektir;
  • 60-68. satırlar: Her ne kadar zorunlu olmasa da oturum çerezini alıyoruz. Sunucu tarafından gönderilen çerezler sözlüğünde, oturum çerezi [session] anahtarıyla ilişkilendirilmiştir;
  • 62-68. satırlar: oturum çerezini çözerek kullanıcıyı oturum açarız;
  • 68. satır: Oturum çerezini deşifre eden [decode_flask_session] işlevine daha sonra tekrar değineceğiz;
  • 52. ve 57. satırlar: Aynı istemciden gelen her istekte, sunucu tarafından gönderilen çerezler istemciye geri gönderilir. Flask oturumu, istemci ile sunucu arasında bu şekilde sürdürülür;

Şimdi, [dao] katmanının birden fazla iş parçacığı tarafından eşzamanlı olarak çalıştırılacağını unutmamak gerekir. Bu nedenle, sınıf örneğinin tüm özelliklerini incelemek ve bu özelliklere eşzamanlı erişimin sorun yaratıp yaratmadığını kontrol etmek gerekir. Burada, 30. satırda [self.__cookies] özelliğini ekledik. Bu özellik 59. satırda değiştiriliyor. Dolayısıyla, tüm iş parçacıkları tarafından paylaşılan bir veriye yazma erişimi söz konusudur. Ancak bu erişim bir sorun yaratır: belirli bir müşteriyi temsil eden her iş parçacığının kendine ait bir oturum çerezi vardır. Aslında, bu çerezlerin içinde her müşteri için benzersiz bir müşteri numarası (=iş parçacığı) bulunmaktadır. Herhangi bir önlem alınmazsa, T2 iş parçacığı, T1 iş parçacığının çerezlerini üzerine yazabilir.

Bu sorunu yönetmek için daha önce bir yöntem görmüştük: oluşturucuya parametre olarak geçirilen [config] dosyasında (17. satır), her iş parçacığı için farklı anahtarlar oluşturabiliriz. Örneğin, anahtar olarak iş parçacığının adını kullanabiliriz:

  • 59. satırda şöyle yazılabilir:

config[thread_name][‘cookies’]=cookies
  • 52. satırda ise şunu yazabiliriz:

cookies=config[thread_name][‘cookies’]

Burada farklı bir teknik kullanacağız: her iş parçacığı (=istemci) kendi [dao] katmanına sahip olacaktır. Böylece 59. satır artık sorun teşkil etmez, çünkü kullanılan çerezler tek bir istemciye aittir.

Bunun için yeni bir [ImpôtsDaoWithHttpSessionFactory] sınıfı oluşturacağız.

25.3.1.2. Flask oturumunun kod çözme işlevi

[decode_flask_session] işlevi, [myutils] betiğinde tanımlanmıştır:

Image

|myutils| betiğini daha önce incelemiştik. Bu betik, bu dersteki çeşitli betiklerin şu komutla içe aktarabileceği, makine kapsamındaki bir modüldür:

import myutils

Bu modülde [decode_flask_session] işlevi şu şekilde tanımlanmıştır:


def decode_flask_session(cookie: str) -> str:
    # kaynak: https://www.kirsle.net/wizards/flask-session.cgi
    compressed = False
    payload = cookie

    if payload.startswith('.'):
        compressed = True
        payload = payload[1:]

    data = payload.split(".")[0]

    data = base64_decode(data)
    if compressed:
        data = zlib.decompress(data)

    return data.decode("utf-8")
  • 2. satır: Bu fonksiyonu bulduğum URL;
  • satır 1: [cookie] parametresi, bir web istemcisi tarafından alınan çerezler sözlüğünde [session] anahtarıyla ilişkili karakter dizisidir;
  • satır 3-16: Tam olarak anlamadığım bu kod hakkında yorum yapmayacağım;

[__init__.py] dosyasına yeni bir içe aktarma ekleniyor:


from .myutils import set_syspath, json_response, decode_flask_session

[myutils] dosyasının yeni sürümü, bir Pycharm terminalinde [pip install .] komutuyla makine kapsamındaki modüller arasına yüklenir:


(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\packages>pip install .
Processing c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\packages
Using legacy setup.py install for myutils, since package 'wheel' is not installed.
Installing collected packages: myutils
  Attempting uninstall: myutils
    Found existing installation: myutils 0.1
    Uninstalling myutils-0.1:
      Successfully uninstalled myutils-0.1
    Running setup.py install for myutils ... done
Successfully installed myutils-0.1
  • 1. satır: Bu komutu girmek için [packages] klasöründe olmanız gerekir;

25.3.1.3. [ImpôtsDaoWithHttpSessionFactory] sınıfı

[ImpôtsDaoWithHttpSessionFactory] sınıfı şu şekildedir:


from ImpôtsDaoWithHttpSession import ImpôtsDaoWithHttpSession


class ImpôtsDaoWithHttpSessionFactory:

    def __init__(self, config: dict):
        # parametreyi belleğe kaydediyoruz
        self.__config = config

    def new_instance(self):
        # [dao] katmanının bir örneği oluşturulur
        return ImpôtsDaoWithHttpSession(self.__config)
  • [ImpôtsDaoWithHttpSessionFactory] sınıfı, 10-12. satırlardaki [new_instance] yöntemi ile [dao] katmanının yeni bir uygulamasını oluşturmaya olanak tanır;

25.3.2. Yapılandırma

Web istemcisinin katmanlarını yapılandıran [config_layers] komut dosyası şu şekilde değiştirilir:


def configure(config: dict) -> dict:
    # uygulama katmanlarının örneklendirilmesi

    # DAO katmanı
    from ImpôtsDaoWithHttpSessionFactory import ImpôtsDaoWithHttpSessionFactory
    dao_factory = ImpôtsDaoWithHttpSessionFactory(config)

    # katmanların yapılandırılması
    return {
        "dao_factory": dao_factory
    }
  • 5-6. satırlar: Daha önce yapıldığı gibi tek bir [dao] katmanını oluşturmak yerine, bu katmanın bir "fabrikasını" (fabrika = nesne üretim tesisi, burada [dao] katmanı) oluşturuyoruz;
  • 9-11. satırlar: katmanların yapılandırması geri yüklenir;

25.3.3. İstemcinin ana komut dosyası

[main] betiği, önceki sürüme kıyasla şu şekilde değişmiştir:


# uygulamayı yapılandırıyoruz

import config
config = config.configure({})

# 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(thread_dao, logger, taxpayers: list):
    


# istemcinin iş parçacıkları listesi
threads = []
logger = None
# kod
try:
    
    l_taxpayers = len(taxpayers)
    while i < len(taxpayers):
        
        # her iş parçacığının oturum çerezini doğru şekilde yönetebilmesi için kendi [dao] katmanına sahip olması gerekir
        thread_dao = dao_factory.new_instance()
        # iş parçacığı oluşturulur
        thread = threading.Thread(target=thread_function, args=(thread_dao, logger, thread_taxpayers))
        # ana betiğin 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 bitmesini bekler
    
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:
        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()
  • 29-30. satırlar: her iş parçacığının kendi [dao] katmanı vardır;

25.3.4. İstemcinin çalıştırılması

Web sunucusu başlatılır, SGBD başlatılır, [hMailServer] e-posta sunucusu başlatılır. Ardından, web istemcisinin [main] komut dosyası başlatılır. [data/logs/logs.txt]'teki yürütme günlükleri şu şekildedir:


2020-07-25 10:21:46.478511, Thread-1 : début du thread [Thread-1] avec 1 contribuable(s)
2020-07-25 10:21:46.479111, Thread-1 : début du calcul de l'impôt de {"id": 1, "marié": "oui", "enfants": 2, "salaire": 55555}
2020-07-25 10:21:46.479111, Thread-2 : début du thread [Thread-2] avec 1 contribuable(s)
2020-07-25 10:21:46.480195, Thread-3 : début du thread [Thread-3] avec 2 contribuable(s)
2020-07-25 10:21:46.480195, Thread-2 : début du calcul de l'impôt de {"id": 2, "marié": "oui", "enfants": 2, "salaire": 50000}
2020-07-25 10:21:46.481137, Thread-4 : début du thread [Thread-4] avec 3 contribuable(s)
2020-07-25 10:21:46.481137, Thread-3 : début du calcul de l'impôt de {"id": 3, "marié": "oui", "enfants": 3, "salaire": 50000}
2020-07-25 10:21:46.482279, Thread-5 : début du thread [Thread-5] avec 3 contribuable(s)
2020-07-25 10:21:46.482622, Thread-6 : début du thread [Thread-6] avec 1 contribuable(s)
2020-07-25 10:21:46.482622, Thread-4 : début du calcul de l'impôt de {"id": 5, "marié": "non", "enfants": 3, "salaire": 100000}
2020-07-25 10:21:46.485923, Thread-5 : début du calcul de l'impôt de {"id": 8, "marié": "non", "enfants": 0, "salaire": 100000}
2020-07-25 10:21:46.485923, Thread-6 : début du calcul de l'impôt de {"id": 11, "marié": "oui", "enfants": 3, "salaire": 200000}
2020-07-25 10:21:46.725910, Thread-4 : cookie de session={"admindata":{"abattement_dixpourcent_max":12502.0,"abattement_dixpourcent_min":437.0,"coeffn":[0.0,1394.96,5798.0,13913.7,20163.4],"coeffr":[0.0,0.14,0.3,0.41,0.45],"id":1,"limites":[9964.0,27519.0,73779.0,156244.0,90000.0],"plafond_decote_celibataire":1196.0,"plafond_decote_couple":1970.0,"plafond_impot_celibataire_pour_decote":1595.0,"plafond_impot_couple_pour_decote":2627.0,"plafond_qf_demi_part":1551.0,"plafond_revenus_celibataire_pour_reduction":21037.0,"plafond_revenus_couple_pour_reduction":42074.0,"valeur_reduc_demi_part":3797.0},"client_id":"fa3c83b82761c83e13217967"}
2020-07-25 10:21:46.725910, Thread-4 : {"réponse": {"result": {"marié": "non", "enfants": 3, "salaire": 100000, "impôt": 16782, "surcôte": 7176, "taux": 0.41, "décôte": 0, "réduction": 0}}}
2020-07-25 10:21:46.725910, Thread-4 : fin du calcul de l'impôt de {"id": 5, "marié": "non", "enfants": 3, "salaire": 100000, "impôt": 16782, "surcôte": 7176, "taux": 0.41, "décôte": 0, "réduction": 0}
2020-07-25 10:21:46.726960, Thread-4 : début du calcul de l'impôt de {"id": 6, "marié": "oui", "enfants": 3, "salaire": 100000}
2020-07-25 10:21:47.514108, Thread-3 : cookie de session={"admindata":{"abattement_dixpourcent_max":12502.0,"abattement_dixpourcent_min":437.0,"coeffn":[0.0,1394.96,5798.0,13913.7,20163.4],"coeffr":[0.0,0.14,0.3,0.41,0.45],"id":1,"limites":[9964.0,27519.0,73779.0,156244.0,24999.5],"plafond_decote_celibataire":1196.0,"plafond_decote_couple":1970.0,"plafond_impot_celibataire_pour_decote":1595.0,"plafond_impot_couple_pour_decote":2627.0,"plafond_qf_demi_part":1551.0,"plafond_revenus_celibataire_pour_reduction":21037.0,"plafond_revenus_couple_pour_reduction":42074.0,"valeur_reduc_demi_part":3797.0},"client_id":"700e3f5dc808c7c48f0c9007"}
2020-07-25 10:21:47.514610, Thread-3 : {"réponse": {"result": {"marié": "oui", "enfants": 3, "salaire": 50000, "impôt": 0, "surcôte": 0, "taux": 0.14, "décôte": 720, "réduction": 0}}}
2020-07-25 10:21:47.514939, Thread-3 : fin du calcul de l'impôt de {"id": 3, "marié": "oui", "enfants": 3, "salaire": 50000, "impôt": 0, "surcôte": 0, "taux": 0.14, "décôte": 720, "réduction": 0}
2020-07-25 10:21:47.514939, Thread-3 : début du calcul de l'impôt de {"id": 4, "marié": "non", "enfants": 2, "salaire": 100000}
2020-07-25 10:21:47.527211, Thread-5 : cookie de session={"admindata":{"abattement_dixpourcent_max":12502.0,"abattement_dixpourcent_min":437.0,"coeffn":[0.0,1394.96,5798.0,13913.7,20163.4],"coeffr":[0.0,0.14,0.3,0.41,0.45],"id":1,"limites":[9964.0,27519.0,73779.0,156244.0,90000.0],"plafond_decote_celibataire":1196.0,"plafond_decote_couple":1970.0,"plafond_impot_celibataire_pour_decote":1595.0,"plafond_impot_couple_pour_decote":2627.0,"plafond_qf_demi_part":1551.0,"plafond_revenus_celibataire_pour_reduction":21037.0,"plafond_revenus_couple_pour_reduction":42074.0,"valeur_reduc_demi_part":3797.0},"client_id":"9e14a5d4a3057f69ab95ab2d"}
2020-07-25 10:21:47.527211, Thread-2 : cookie de session={"admindata":{"abattement_dixpourcent_max":12502.0,"abattement_dixpourcent_min":437.0,"coeffn":[0.0,1394.96,5798.0,13913.7,20163.4],"coeffr":[0.0,0.14,0.3,0.41,0.45],"id":1,"limites":[9964.0,27519.0,73779.0,156244.0,22500.0],"plafond_decote_celibataire":1196.0,"plafond_decote_couple":1970.0,"plafond_impot_celibataire_pour_decote":1595.0,"plafond_impot_couple_pour_decote":2627.0,"plafond_qf_demi_part":1551.0,"plafond_revenus_celibataire_pour_reduction":21037.0,"plafond_revenus_couple_pour_reduction":42074.0,"valeur_reduc_demi_part":3797.0},"client_id":"a06e8fd70a44c9e311f4dce0"}
2020-07-25 10:21:47.527211, Thread-5 : {"réponse": {"result": {"marié": "non", "enfants": 0, "salaire": 100000, "impôt": 22986, "surcôte": 0, "taux": 0.41, "décôte": 0, "réduction": 0}}}
2020-07-25 10:21:47.527211, Thread-1 : cookie de session={"admindata":{"abattement_dixpourcent_max":12502.0,"abattement_dixpourcent_min":437.0,"coeffn":[0.0,1394.96,5798.0,13913.7,20163.4],"coeffr":[0.0,0.14,0.3,0.41,0.45],"id":1,"limites":[9964.0,27519.0,73779.0,156244.0,90000.0],"plafond_decote_celibataire":1196.0,"plafond_decote_couple":1970.0,"plafond_impot_celibataire_pour_decote":1595.0,"plafond_impot_couple_pour_decote":2627.0,"plafond_qf_demi_part":1551.0,"plafond_revenus_celibataire_pour_reduction":21037.0,"plafond_revenus_couple_pour_reduction":42074.0,"valeur_reduc_demi_part":3797.0},"client_id":"28c38df998f67685b3a482b8"}
2020-07-25 10:21:47.527211, Thread-2 : {"réponse": {"result": {"marié": "oui", "enfants": 2, "salaire": 50000, "impôt": 1384, "surcôte": 0, "taux": 0.14, "décôte": 384, "réduction": 347}}}
2020-07-25 10:21:47.528341, Thread-5 : fin du calcul de l'impôt de {"id": 8, "marié": "non", "enfants": 0, "salaire": 100000, "impôt": 22986, "surcôte": 0, "taux": 0.41, "décôte": 0, "réduction": 0}
2020-07-25 10:21:47.528341, Thread-1 : {"réponse": {"result": {"marié": "oui", "enfants": 2, "salaire": 55555, "impôt": 2814, "surcôte": 0, "taux": 0.14, "décôte": 0, "réduction": 0}}}
2020-07-25 10:21:47.528842, Thread-2 : fin du calcul de l'impôt de {"id": 2, "marié": "oui", "enfants": 2, "salaire": 50000, "impôt": 1384, "surcôte": 0, "taux": 0.14, "décôte": 384, "réduction": 347}
2020-07-25 10:21:47.529349, Thread-5 : début du calcul de l'impôt de {"id": 9, "marié": "oui", "enfants": 2, "salaire": 30000}
2020-07-25 10:21:47.529699, Thread-1 : fin du calcul de l'impôt de {"id": 1, "marié": "oui", "enfants": 2, "salaire": 55555, "impôt": 2814, "surcôte": 0, "taux": 0.14, "décôte": 0, "réduction": 0}
2020-07-25 10:21:47.529699, Thread-2 : fin du thread [Thread-2]
2020-07-25 10:21:47.531905, Thread-1 : fin du thread [Thread-1]
2020-07-25 10:21:47.536121, Thread-6 : cookie de session={"admindata":{"abattement_dixpourcent_max":12502.0,"abattement_dixpourcent_min":437.0,"coeffn":[0.0,1394.96,5798.0,13913.7,20163.4],"coeffr":[0.0,0.14,0.3,0.41,0.45],"id":1,"limites":[9964.0,27519.0,73779.0,156244.0,93749.0],"plafond_decote_celibataire":1196.0,"plafond_decote_couple":1970.0,"plafond_impot_celibataire_pour_decote":1595.0,"plafond_impot_couple_pour_decote":2627.0,"plafond_qf_demi_part":1551.0,"plafond_revenus_celibataire_pour_reduction":21037.0,"plafond_revenus_couple_pour_reduction":42074.0,"valeur_reduc_demi_part":3797.0},"client_id":"38499b63076516c02f2770ec"}
2020-07-25 10:21:47.537161, Thread-3 : {"réponse": {"result": {"marié": "non", "enfants": 2, "salaire": 100000, "impôt": 19884, "surcôte": 4480, "taux": 0.41, "décôte": 0, "réduction": 0}}}
2020-07-25 10:21:47.537161, Thread-6 : {"réponse": {"result": {"marié": "oui", "enfants": 3, "salaire": 200000, "impôt": 42842, "surcôte": 17283, "taux": 0.41, "décôte": 0, "réduction": 0}}}
2020-07-25 10:21:47.538156, Thread-3 : fin du calcul de l'impôt de {"id": 4, "marié": "non", "enfants": 2, "salaire": 100000, "impôt": 19884, "surcôte": 4480, "taux": 0.41, "décôte": 0, "réduction": 0}
2020-07-25 10:21:47.538557, Thread-6 : fin du calcul de l'impôt de {"id": 11, "marié": "oui", "enfants": 3, "salaire": 200000, "impôt": 42842, "surcôte": 17283, "taux": 0.41, "décôte": 0, "réduction": 0}
2020-07-25 10:21:47.538828, Thread-3 : fin du thread [Thread-3]
2020-07-25 10:21:47.538828, Thread-6 : fin du thread [Thread-6]
2020-07-25 10:21:47.546198, Thread-5 : {"réponse": {"result": {"marié": "oui", "enfants": 2, "salaire": 30000, "impôt": 0, "surcôte": 0, "taux": 0.0, "décôte": 0, "réduction": 0}}}
2020-07-25 10:21:47.546198, Thread-5 : fin du calcul de l'impôt de {"id": 9, "marié": "oui", "enfants": 2, "salaire": 30000, "impôt": 0, "surcôte": 0, "taux": 0.0, "décôte": 0, "réduction": 0}
2020-07-25 10:21:47.546198, Thread-5 : début du calcul de l'impôt de {"id": 10, "marié": "non", "enfants": 0, "salaire": 200000}
2020-07-25 10:21:47.739643, Thread-4 : {"réponse": {"result": {"marié": "oui", "enfants": 3, "salaire": 100000, "impôt": 9200, "surcôte": 2180, "taux": 0.3, "décôte": 0, "réduction": 0}}}
2020-07-25 10:21:47.739643, Thread-4 : fin du calcul de l'impôt de {"id": 6, "marié": "oui", "enfants": 3, "salaire": 100000, "impôt": 9200, "surcôte": 2180, "taux": 0.3, "décôte": 0, "réduction": 0}
2020-07-25 10:21:47.740668, Thread-4 : début du calcul de l'impôt de {"id": 7, "marié": "oui", "enfants": 5, "salaire": 100000}
2020-07-25 10:21:48.557469, Thread-5 : {"réponse": {"result": {"marié": "non", "enfants": 0, "salaire": 200000, "impôt": 64210, "surcôte": 7498, "taux": 0.45, "décôte": 0, "réduction": 0}}}
2020-07-25 10:21:48.558715, Thread-5 : fin du calcul de l'impôt de {"id": 10, "marié": "non", "enfants": 0, "salaire": 200000, "impôt": 64210, "surcôte": 7498, "taux": 0.45, "décôte": 0, "réduction": 0}
2020-07-25 10:21:48.558715, Thread-5 : fin du thread [Thread-5]
2020-07-25 10:21:48.753025, Thread-4 : {"réponse": {"result": {"marié": "oui", "enfants": 5, "salaire": 100000, "impôt": 4230, "surcôte": 0, "taux": 0.14, "décôte": 0, "réduction": 0}}}
2020-07-25 10:21:48.753318, Thread-4 : fin du calcul de l'impôt de {"id": 7, "marié": "oui", "enfants": 5, "salaire": 100000, "impôt": 4230, "surcôte": 0, "taux": 0.14, "décôte": 0, "réduction": 0}
2020-07-25 10:21:48.753540, Thread-4 : fin du thread [Thread-4]
  • Toplamda 6 iş parçacığı, yani vergi hesaplama sunucusuna eşzamanlı olarak sorgu gönderen 6 istemci (1., 3., 4., 6., 8. ve 9. satırlar) vardır;
  • 3 vergi mükellefini yöneten [Thread-4] iş parçacığını takip edeceğiz (6. satır). Bu iş parçacığı, vergi hesaplama sunucusuna sırayla üç istek gönderecektir;
  • 10. satır: [Thread-4]'in ilk isteği;
  • 13. satır: [Thread-4], ilk isteğine yanıt aldı. Yanıtta, sunucu tarafından kendisine atanan [fa3c83b82761c83e13217967] numarasını içeren bir oturum çerezi bulur;
  • 14. satır: 1. vergi mükellefinin vergisi;
  • 16. satır: [Thread-4], 2. vergi mükellefi için bir istek gönderir;
  • 43. satır: [Thread-4], ikinci vergi mükellefinin vergisini alır;
  • satır 45: [Thread-4], 3. vergi mükellefi için bir sorgu yapar;
  • satır 49: [Thread-4], 3. vergi mükellefinin vergisini alır;
  • satır 51: [Thread-4] işini tamamladı;

Şimdi, [Thread-4]'in 3 isteğinin sunucu tarafında nasıl işlendiğine bakalım. [fa3c83b82761c83e13217967] müşteri numarası sayesinde bunu sunucu günlüklerinde takip edebileceğiz.

Sunucu tarafındaki [data/logs/logs.txt] günlükleri şunlardır:


2020-07-25 10:21:39.187366, MainThread : [serveur] démarrage du serveur
2020-07-25 10:21:40.439093, MainThread : [serveur] démarrage du serveur
2020-07-25 10:21:46.502011, Thread-2 : [index] requête : <Request 'http://127.0.0.1:5000/?evli=evet&çocuk=3&maaş=50000' [GET]>
2020-07-25 10:21:46.504049, Thread-2 : [index] mis en pause du thread pendant 1 seconde(s)
2020-07-25 10:21:46.505452, Thread-3 : [index] requête : <Request 'http://127.0.0.1:5000/?evli=evet&çocuk=2&maaş=55555' [GET]>
2020-07-25 10:21:46.506257, Thread-3 : [index] mis en pause du thread pendant 1 seconde(s)
2020-07-25 10:21:46.507292, Thread-4 : [index] requête : <Request 'http://127.0.0.1:5000/?evli=hayır&çocuk=0&maaş=100000' [GET]>
2020-07-25 10:21:46.507292, Thread-4 : [index] mis en pause du thread pendant 1 seconde(s)
2020-07-25 10:21:46.508301, Thread-5 : [index] requête : <Request 'http://127.0.0.1:5000/?evli=evet&çocuk=2&maaş=50000' [GET]>
2020-07-25 10:21:46.509293, Thread-5 : [index] mis en pause du thread pendant 1 seconde(s)
2020-07-25 10:21:46.511808, Thread-6 : [index] requête : <Request 'http://127.0.0.1:5000/?evli=hayır&çocuk=3&maaş=100000' [GET]>
2020-07-25 10:21:46.517604, Thread-7 : [index] requête : <Request 'http://127.0.0.1:5000/?evli=evet&çocuk=3&maaş=200000' [GET]>
2020-07-25 10:21:46.517604, Thread-7 : [index] mis en pause du thread pendant 1 seconde(s)
2020-07-25 10:21:46.719504, Thread-6 : [index_controller] client [fa3c83b82761c83e13217967], données fiscales prises dans la couche dao
2020-07-25 10:21:46.720003, Thread-6 : [index] {'réponse': {'result': {'marié': 'non', 'enfants': 3, 'salaire': 100000, 'impôt': 16782, 'surcôte': 7176, 'taux': 0.41, 'décôte': 0, 'réduction': 0}}}
2020-07-25 10:21:46.736108, Thread-8 : [index] requête : <Request 'http://127.0.0.1:5000/?evli=evet&çocuk=3&maaş=100000' [GET]>
2020-07-25 10:21:46.736108, Thread-8 : [index] mis en pause du thread pendant 1 seconde(s)
2020-07-25 10:21:47.506709, Thread-2 : [index_controller] client [700e3f5dc808c7c48f0c9007], données fiscales prises dans la couche dao
2020-07-25 10:21:47.507216, Thread-2 : [index] {'réponse': {'result': {'marié': 'oui', 'enfants': 3, 'salaire': 50000, 'impôt': 0, 'surcôte': 0, 'taux': 0.14, 'décôte': 720, 'réduction': 0}}}
2020-07-25 10:21:47.507216, Thread-3 : [index_controller] client [28c38df998f67685b3a482b8], données fiscales prises dans la couche dao
2020-07-25 10:21:47.508442, Thread-4 : [index_controller] client [9e14a5d4a3057f69ab95ab2d], données fiscales prises dans la couche dao
2020-07-25 10:21:47.508940, Thread-3 : [index] {'réponse': {'result': {'marié': 'oui', 'enfants': 2, 'salaire': 55555, 'impôt': 2814, 'surcôte': 0, 'taux': 0.14, 'décôte': 0, 'réduction': 0}}}
2020-07-25 10:21:47.510506, Thread-4 : [index] {'réponse': {'result': {'marié': 'non', 'enfants': 0, 'salaire': 100000, 'impôt': 22986, 'surcôte': 0, 'taux': 0.41, 'décôte': 0, 'réduction': 0}}}
2020-07-25 10:21:47.511513, Thread-5 : [index_controller] client [a06e8fd70a44c9e311f4dce0], données fiscales prises dans la couche dao
2020-07-25 10:21:47.514939, Thread-5 : [index] {'réponse': {'result': {'marié': 'oui', 'enfants': 2, 'salaire': 50000, 'impôt': 1384, 'surcôte': 0, 'taux': 0.14, 'décôte': 384, 'réduction': 347}}}
2020-07-25 10:21:47.520727, Thread-7 : [index_controller] client [38499b63076516c02f2770ec], données fiscales prises dans la couche dao
2020-07-25 10:21:47.523162, Thread-7 : [index] {'réponse': {'result': {'marié': 'oui', 'enfants': 3, 'salaire': 200000, 'impôt': 42842, 'surcôte': 17283, 'taux': 0.41, 'décôte': 0, 'réduction': 0}}}
2020-07-25 10:21:47.530835, Thread-9 : [index] requête : <Request 'http://127.0.0.1:5000/?evli=hayır&çocuk=2&maaş=100000' [GET]>
2020-07-25 10:21:47.531736, Thread-9 : [index_controller] client [700e3f5dc808c7c48f0c9007], données fiscales prises en session
2020-07-25 10:21:47.531905, Thread-9 : [index] {'réponse': {'result': {'marié': 'non', 'enfants': 2, 'salaire': 100000, 'impôt': 19884, 'surcôte': 4480, 'taux': 0.41, 'décôte': 0, 'réduction': 0}}}
2020-07-25 10:21:47.541899, Thread-10 : [index] requête : <Request 'http://127.0.0.1:5000/?evli=evet&çocuk=2&maaş=30000' [GET]>
2020-07-25 10:21:47.542488, Thread-10 : [index_controller] client [9e14a5d4a3057f69ab95ab2d], données fiscales prises en session
2020-07-25 10:21:47.542488, Thread-10 : [index] {'réponse': {'result': {'marié': 'oui', 'enfants': 2, 'salaire': 30000, 'impôt': 0, 'surcôte': 0, 'taux': 0.0, 'décôte': 0, 'réduction': 0}}}
2020-07-25 10:21:47.553628, Thread-11 : [index] requête : <Request 'http://127.0.0.1:5000/?evli=hayır&çocuk=0&maaş=200000' [GET]>
2020-07-25 10:21:47.553628, Thread-11 : [index] mis en pause du thread pendant 1 seconde(s)
2020-07-25 10:21:47.736910, Thread-8 : [index_controller] client [fa3c83b82761c83e13217967], données fiscales prises en session
2020-07-25 10:21:47.737191, Thread-8 : [index] {'réponse': {'result': {'marié': 'oui', 'enfants': 3, 'salaire': 100000, 'impôt': 9200, 'surcôte': 2180, 'taux': 0.3, 'décôte': 0, 'réduction': 0}}}
2020-07-25 10:21:47.748226, Thread-12 : [index] requête : <Request 'http://127.0.0.1:5000/?evli=evet&çocuk=5&maaş=100000' [GET]>
2020-07-25 10:21:47.748226, Thread-12 : [index] mis en pause du thread pendant 1 seconde(s)
2020-07-25 10:21:48.554695, Thread-11 : [index_controller] client [9e14a5d4a3057f69ab95ab2d], données fiscales prises en session
2020-07-25 10:21:48.555070, Thread-11 : [index] {'réponse': {'result': {'marié': 'non', 'enfants': 0, 'salaire': 200000, 'impôt': 64210, 'surcôte': 7498, 'taux': 0.45, 'décôte': 0, 'réduction': 0}}}
2020-07-25 10:21:48.748753, Thread-12 : [index_controller] client [fa3c83b82761c83e13217967], données fiscales prises en session
2020-07-25 10:21:48.748753, Thread-12 : [index] {'réponse': {'result': {'marié': 'oui', 'enfants': 5, 'salaire': 100000, 'impôt': 4230, 'surcôte': 0, 'taux': 0.14, 'décôte': 0, 'réduction': 0}}}
  • 14. satırda [fa3c83b82761c83e13217967] müşteri numarasını ilk kez görüyoruz: vergiyi hesaplamak için sunucu, vergi dairesinin veritabanından verileri almak zorunda kalmıştır;
  • ardından 36. satırda [fa3c83b82761c83e13217967] istemcisi görülmektedir. Bu sefer sunucu, vergi idaresine ait verileri oturum içinde bulmaktadır; bu da, muhtemelen maliyetli olabilecek [dao] katmanına erişim ihtiyacını ortadan kaldırmaktadır;
  • 42. satırda [fa3c83b82761c83e13217967] istemcisine üçüncü kez rastlıyoruz; burada da sunucu yine istemcinin oturumunu kullanıyor;

Bu örnek, oturumun bir istemci için ne kadar yararlı olduğunu açıkça göstermektedir: bu oturuma, söz konusu istemcinin tüm istekleri tarafından paylaşılan ve elde edilmesi maliyetli veriler yerleştirilir.

İstemci tarafında, [data/output/résultats.json] dosyasındaki sonuçlar önceki sürümlerle aynıdır.

25.4. [dao] katmanının testleri

|versions précédentes|'te yaptığımız gibi, istemcinin [dao] katmanını test ediyoruz:

Image

Test sınıfı aşağıdaki ortamda çalıştırılacaktır:

Image

  • [2] yapılandırması, az önce incelediğimiz [1] yapılandırmasıyla aynıdır;

[TestHttpClientDao] test sınıfı şöyledir:


import unittest

from Logger import Logger


class TestHttpClientDao(unittest.TestCase):

    def test_1(self) -> None:
        from TaxPayer import TaxPayer

        # {'evli': 'evet', 'çocuk': 2, 'maaş': 55555,
        # 'vergi': 2814, 'ek ücret': 0, 'indirim': 0, 'azaltma': 0, 'oran': 0.14}
        taxpayer = TaxPayer().fromdict({"marié": "oui", "enfants": 2, "salaire": 55555})
        dao.calculate_tax(taxpayer)
        # doğrulama
        self.assertAlmostEqual(taxpayer.impôt, 2815, delta=1)
        self.assertEqual(taxpayer.décôte, 0)
        self.assertEqual(taxpayer.réduction, 0)
        self.assertAlmostEqual(taxpayer.taux, 0.14, delta=0.01)
        self.assertEqual(taxpayer.surcôte, 0)



if __name__ == '__main__':
    # uygulamayı yapılandırıyoruz
    import config
    config = config.configure({})

    # günlük kaydı
    logger = Logger(config["logsFilename"])
    # yapılandırmada kaydediliyor
    config["logger"] = logger
    # [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()

    # test yöntemlerini çalıştırıyoruz
    print("tests en cours...")
    unittest.main()
  • Bu test için bir |configuration d’exécution| oluşturulur;
  • web sunucusunu tüm ortamıyla birlikte başlatıyoruz;
  • testi çalıştırıyoruz;

Sonuç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/03/tests/TestHttpClientDao.py
tests en cours...
...........
----------------------------------------------------------------------
Ran 11 tests in 3.392s

OK

Process finished with exit code 0