Skip to content

28. Uygulama alıştırması: sürüm 10

28.1. Giriş

Vergi hesaplama sunucusunun istemci örneklerinde, iş parçacıkları N vergi mükellefini işlemek zorunda kaldıklarında N isteği sırayla gönderiyordu. Buradaki fikir, N vergi mükellefini kapsayan tek bir istek göndermektir. Her biri için [marié, enfants, salaire] bilgilerini göndermek gerekir. Bunlar, URL'in parametreleri olarak gönderilebilir:

  • URL'in parametreleri olarak. Bu durumda, anlamı pek olmayan uzun bir URL elde edilir;
  • HTTP isteğinin gövdesinde (body). Bu gövdenin, tarayıcı kullanan kullanıcıdan gizlendiği bilinmektedir;

Her iki durumda da [GET] veya [POST] isteği kullanılabilir. Biz, parametreleri HTTP isteğinin gövdesine yerleştirilmiş olan POST isteğini kullanacağız.

İstemci/sunucu mimarisi değişmemiştir:

Image

28.2. Web sunucusu

Image

[http-servers/05] dosyası, başlangıçta [http-servers/02] dosyasının kopyalanmasıyla elde edilir. İstemci ile sunucu arasındaki jSON veri alışverişine geri dönüyoruz. jSON'ten XML'e geçişin çok basit olduğunu görmüştük.

28.2.1. Yapılandırma

[config, config_database, config_layers] yapılandırması önceki sürümlerinkiyle aynıdır. Bu konuya tekrar değinmeyeceğiz.

28.2.2. Ana komut dosyası [main]

[main] komut dosyası, kopyaladığımız [http-servers/02] klasöründeki komut dosyasıyla aynıdır. Tek bir fark vardır:


# Ana Sayfa URL
@app.route('/', methods=['POST'])
@auth.login_required
def index():

  • 2. satır: Artık URL, POST aracılığıyla elde ediliyor;

28.2.3. [index_controller] denetleyicisi

[index_controller] denetleyicisi şu şekilde değişir:


# bağımlılıkların içe aktarılması

import json

from flask_api import status
from werkzeug.local import LocalProxy


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

    # gönderinin gövdesini alıyoruz - bir sözlük listesi bekleniyor
    msg_erreur = None
    list_dict_taxpayers = None
    # POST'in gövdesi jSON
    request_text = request.data
    try:
        # bunu bir sözlük listesine dönüştürüyoruz
        list_dict_taxpayers = json.loads(request_text)
    except BaseException as erreur:
        # hata kaydedilir
        msg_erreur = f"le corps du POST n'est pas une chaîne jSON valide : {erreur}"
    # boş olmayan bir liste var mı?
    if not msg_erreur and (not isinstance(list_dict_taxpayers, list) or len(list_dict_taxpayers) == 0):
        # hatayı not ediyoruz
        msg_erreur = "le corps du POST n'est pas une liste ou alors cette liste est vide"
    # bir sözlük listesi var mı?
    if not msg_erreur:
        erreur = False
        i = 0
        while not erreur and i < len(list_dict_taxpayers):
            erreur = not isinstance(list_dict_taxpayers[i], dict)
            i += 1
        # hata mı?
        if erreur:
            msg_erreur = "le corps du POST doit être une liste de dictionnaires"
    # hata mı?
    if msg_erreur:
        # istemciye bir hata yanıtı gönderilir
        résultats = {"réponse": {"erreurs": [msg_erreur]}}
        return résultats, status.HTTP_400_BAD_REQUEST

    # TaxPayers değerleri tek tek kontrol ediliyor
    # başlangıçta hata yok
    list_erreurs = []
    for dict_taxpayer in list_dict_taxpayers:
        # dict_taxpayer'ten bir TaxPayer oluşturuluyor
        msg_erreur = None
        try:
            # bir sonraki işlem, parametrelerin
            # TaxPayer sınıfının özellikleri ve bunların değerlerinin
            # değeri yanlış olan durumları
            TaxPayer().fromdict(dict_taxpayer)
        except BaseException as erreur:
            msg_erreur = f"{erreur}"
        # sözlükte belirli anahtarların bulunması gerekir
        if not msg_erreur:
            # [marié, enfants, salaire] anahtarları sözlükte bulunmalıdır
            keys = dict_taxpayer.keys()
            if 'marié' not in keys or 'enfants' not in keys or 'salaire' not in keys:
                msg_erreur = "le dictionnaire doit inclure les clés [marié, enfants, salaire]"
        # hatalar mı var?
        if msg_erreur:
            # TaxPayer'in kendisinde bir hata tespit edildi
            dict_taxpayer['erreur'] = msg_erreur
            # TaxPayer hatalar listesine ekleniyor
            list_erreurs.append(dict_taxpayer)

    # Tüm vergi mükellefleri işlendi - hata var mı?
    if list_erreurs:
        # müşteriye bir hata yanıtı gönderilir
        résultats = {"réponse": {"erreurs": list_erreurs}}
        return résultats, status.HTTP_400_BAD_REQUEST

    # hata yok, işleme devam edilebilir
    # Vergi dairesinden veriler alınmaktadır
    admindata = config["admindata"]
    métier = config["layers"]["métier"]
    try:
        # TaxPayer'leri tek tek işliyoruz
        list_taxpayers = []
        for dict_taxpayer in list_dict_taxpayers:
            # vergi hesaplaması
            taxpayer = TaxPayer().fromdict(
                {'marié': dict_taxpayer['marié'], 'enfants': dict_taxpayer['enfants'],
                 'maaş': dict_taxpayer['salaire']})
            métier.calculate_tax(taxpayer, admindata)
            # sonuç bir sözlük olarak saklanıyor
            list_taxpayers.append(taxpayer.asdict())
        # yanıtı müşteriye gönderilir
        return {"réponse": {"results": list_taxpayers}}, status.HTTP_200_OK
    except ImpôtsError as erreur:
        # müşteriye bir hata yanıtı gönderilir
        return {"réponse": {"erreurs": f"[{erreur}]"}}, status.HTTP_500_INTERNAL_SERVER_ERROR
  • 9. satır: denetleyici şunları alır:
      • istemciden gelen [request] isteğini;
      • sunucudan gelen [config] yapılandırmasını;
  • 14-18. satırlar: POST isteğinin gövdesi alınır. HTTP isteğinin gövdesinde kapsüllenmiş parametreler farklı şekillerde kodlanabilir. Bunlardan biriyle daha önce karşılaşmıştık: [x-www-form-urlencoded]. Burada başka bir kodlama kullanacağız: jSON;
  • 18. satır: [request.data], HTTP isteğinin gövdesini (body) almamızı sağlar. Burada bir metin alıyoruz ve bu metnin, bir sözlük listesini temsil eden jSON olduğunu biliyoruz;
  • 19-24. satırlar: Bu sözlük listesi alınır;
  • 22-24. satırlar: jSON'in alınması başarısız olursa, hata kaydedilir;
  • 26-28. satırlar: Alınan nesnenin bir liste olmadığı veya boş bir liste olduğu tespit edilirse, hata kaydedilir;
  • satır 29-38: bir liste başarıyla alınmışsa, bunun gerçekten bir sözlük listesi olup olmadığı kontrol edilir;
  • 40-43. satırlar: Hata oluşmuşsa, burada durulur ve müşteriye bir hata yanıtı gönderilir;
  • 45-69. satırlar: Şimdi her bir sözlüğü kontrol ediyoruz:
    • bunlar [marié, enfants, salaire] anahtarlarını içermelidir;
    • geçerli bir [TaxPayer] nesnesi oluşturulabilmelidir;
  • 65-69. satırlar: Bir sözlükte hata tespit edilirse, bu hata aynı sözlükte ‘hata’ anahtarıyla ilişkilendirilerek kaydedilir;
  • 72-75. satırlar: hatalı sözlükler [list_erreurs] listesinde biriktirilir. Bu liste boş değilse, müşteriye gönderilen bir hata yanıtında bu liste iletilir;
  • 77. satır: Bu noktaya gelindiğinde, müşteri tarafından gönderilen isteğin gövdesinden [TaxPayer] türünde bir nesne listesi oluşturulabileceği bilinmektedir;
  • satır 84-91: Alınan sözlükler listesi kullanılır;
  • 86. satır: Bir sözlükten [TaxPayer] nesnesi oluşturulur;
  • 89. satır: Bu [TaxPayer] nesnesinin vergisi hesaplanır;
  • 91. satır: [taxpayer] nesnesinin vergi hesaplaması sonucunda değiştiği anlaşılır. Bu nesne bir sözlüğe dönüştürülür ve sonuç listesine eklenir;
  • 93. satır: Bu sonuç listesi müşteriye gönderilir;

28.2.4. Sunucu Testleri

Sunucuyu bir Postman istemcisiyle test edeceğiz:

  • web sunucusunu, SGBD'i ve e-posta sunucusunu [hMailServer]'i başlatıyoruz;
  • Postman istemcisini ve konsolunu (Ctrl-Alt-C) başlatıyoruz;

Image

  • [1]'te: [POST]'e bir istek gönderiyoruz;
  • [2]'e: sunucunun URL'i;
  • [3]'e: HTTP isteğinin gövdesi;
  • [5]'e: bu gövdenin jSON biçiminde bir dize olarak gönderilmesi gerektiği belirtilir;
  • [4]'te: bir jSON dizisini kopyalayıp yapıştırabilmek için [raw] moduna geçilir;
  • [6] dosyasında: Farklı sürümlerden birindeki [résultats.json] dosyalarından alınan jSON dizesini yapıştırıyoruz. Ardından, her vergi mükellefi için yalnızca [marié, salaire, enfants] özelliklerini saklıyoruz;

Image

  • [7] dosyasında, Postman istemcisinin sunucuya göndereceği HTTP başlıklarına bakılır;
  • [8]'te, isteğin jSON ile kodlanmış bir gövde içerdiğini belirten [Content-Type] başlığını sunucuya göndereceği görülür. Bu, daha önce yapılan [5] seçiminden kaynaklanmaktadır;

Image

  • [9-12]'te: isteğe sunucunun beklediği kimlik bilgilerini ekliyoruz;

Bu istek gönderilir. Sunucudan gelen yanıt şöyledir:

Image

  • [3]'te, jSON'ten yanıt alındı;
  • [4]'te, vergi mükelleflerinin vergisi;

Postman konsolunda (Ctrl-Alt-C) gerçekleşen istemci/sunucu diyaloğunu inceleyelim:

Postman istemcisi şu metni gönderdi:

POST / HTTP/1.1
Authorization: Basic YWRtaW46YWRtaW4=
Content-Type: application/json
User-Agent: PostmanRuntime/7.26.2
Accept: */*
Cache-Control: no-cache
Postman-Token: 03c4aa28-5a5d-4bb5-ac51-7ad51968c71d
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 824

[
  {
    "marié": "oui",
    "enfants": 2,
    "salaire": 55555
  },
  {
    "marié": "oui",
    "enfants": 2,
    "salaire": 50000
  },
  {
    "marié": "oui",
    "enfants": 3,
    "salaire": 50000
  },
  {
    "marié": "non",
    "enfants": 2,
    "salaire": 100000
  },
  {
    "marié": "non",
    "enfants": 3,
    "salaire": 100000
  },
  {
    "marié": "oui",
    "enfants": 3,
    "salaire": 100000
  },
  {
    "marié": "oui",
    "enfants": 5,
    "salaire": 100000
  },
  {
    "marié": "non",
    "enfants": 0,
    "salaire": 100000
  },
  {
    "marié": "oui",
    "enfants": 2,
    "salaire": 30000
  },
  {
    "marié": "non",
    "enfants": 0,
    "salaire": 200000
  },
  {
    "marié": "oui",
    "enfants": 3,
    "salaire": 200000
  }
]
  • 1. satır: POST sunucuya gönderildi;
  • 2. satır: HTTP kimlik doğrulama başlığı;
  • 3. satır: İstemci, sunucuya jSON dizesini gönderdiğini ve bu dizenin 824 bayt olduğunu bildirir (11. satır);
  • satır 13-69: isteğin jSON gövdesi;

Sunucu, müşteriye şu metni yanıtladı:


HTTP/1.0 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 1461
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Tue, 28 Jul 2020 07:16:34 GMT

{"réponse": {"results": [{"marié": "oui", "enfants": 2, "salaire": 55555, "impôt": 2814, "surcôte": 0, "taux": 0.14, "décôte": 0, "réduction": 0}, {"marié": "oui", "enfants": 2, "salaire": 50000, "impôt": 1384, "surcôte": 0, "taux": 0.14, "décôte": 384, "réduction": 347}, {"marié": "oui", "enfants": 3, "salaire": 50000, "impôt": 0, "surcôte": 0, "taux": 0.14, "décôte": 720, "réduction": 0}, {"marié": "non", "enfants": 2, "salaire": 100000, "impôt": 19884, "surcôte": 4480, "taux": 0.41, "décôte": 0, "réduction": 0}, {"marié": "non", "enfants": 3, "salaire": 100000, "impôt": 16782, "surcôte": 7176, "taux": 0.41, "décôte": 0, "réduction": 0}, {"marié": "oui", "enfants": 3, "salaire": 100000, "impôt": 9200, "surcôte": 2180, "taux": 0.3, "décôte": 0, "réduction": 0}, {"marié": "oui", "enfants": 5, "salaire": 100000, "impôt": 4230, "surcôte": 0, "taux": 0.14, "décôte": 0, "réduction": 0}, {"marié": "non", "enfants": 0, "salaire": 100000, "impôt": 22986, "surcôte": 0, "taux": 0.41, "décôte": 0, "réduction": 0}, {"marié": "oui", "enfants": 2, "salaire": 30000, "impôt": 0, "surcôte": 0, "taux": 0.0, "décôte": 0, "réduction": 0}, {"marié": "non", "enfants": 0, "salaire": 200000, "impôt": 64210, "surcôte": 7498, "taux": 0.45, "décôte": 0, "réduction": 0}, {"marié": "oui", "enfants": 3, "salaire": 200000, "impôt": 42842, "surcôte": 17283, "taux": 0.41, "décôte": 0, "réduction": 0}]}}
  • 1. satır: istek başarılı oldu;
  • 2. satır: Sunucunun yanıt gövdesi, jSON dizesidir. Bu dize 1461 bayt uzunluğundadır (3. satır);
  • 7. satır: sunucunun jSON yanıtı;

Şimdi hata durumlarını test edelim.

Durum 1: rastgele bir veri gönderilir


POST / HTTP/1.1
Authorization: Basic YWRtaW46YWRtaW4=
Content-Type: application/json
User-Agent: PostmanRuntime/7.26.2
Accept: */*
Cache-Control: no-cache
Postman-Token: 47652706-9744-46a0-a682-de010e5406c0
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 3

abc

HTTP/1.0 400 BAD REQUEST
Content-Type: application/json; charset=utf-8
Content-Length: 125
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Tue, 28 Jul 2020 07:43:27 GMT

{"réponse": {"erreurs": ["le corps du POST n'est pas une chaîne jSON valide : Expecting value: line 1 column 1 (char 0)"]}}
  • 13. satır: [abc] dizesi gönderildi; bu, geçerli bir jSON dizesi değildir (3. satır);
  • 15. satır: sunucu 400 hata koduyla yanıt veriyor;
  • 21. satır: sunucudan gelen jSON yanıtı;

Durum 2: Liste olmayan geçerli bir jSON dizesi gönderelim


POST / HTTP/1.1
Authorization: Basic YWRtaW46YWRtaW4=
Content-Type: application/json
User-Agent: PostmanRuntime/7.26.2
Accept: */*
Cache-Control: no-cache
Postman-Token: 03b64735-9239-47b3-b92d-be7c9ebc7559
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 17

{"att1":"value1"}

HTTP/1.0 400 BAD REQUEST
Content-Type: application/json; charset=utf-8
Content-Length: 97
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Tue, 28 Jul 2020 07:50:11 GMT

{"réponse": {"erreurs": ["le corps du POST n'est pas une liste ou alors cette liste est vide"]}}

Durum 3: Elemanlarının tamamı sözlük olmayan bir liste olan jSON dizesini gönderelim


POST / HTTP/1.1
Authorization: Basic YWRtaW46YWRtaW4=
Content-Type: application/json
User-Agent: PostmanRuntime/7.26.2
Accept: */*
Cache-Control: no-cache
Postman-Token: a1528a5f-777c-413f-b3be-7d4e9955b12a
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 7

[0,1,2]

HTTP/1.0 400 BAD REQUEST
Content-Type: application/json; charset=utf-8
Content-Length: 85
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Tue, 28 Jul 2020 07:52:10 GMT

{"réponse": {"erreurs": ["le corps du POST doit être une liste de dictionnaires"]}}

Durum 4: Doğru anahtarları içermeyen bir sözlük içeren sözlükler listesi gönderelim


POST / HTTP/1.1
Authorization: Basic YWRtaW46YWRtaW4=
Content-Type: application/json
User-Agent: PostmanRuntime/7.26.2
Accept: */*
Cache-Control: no-cache
Postman-Token: ba964d81-c9d9-46ff-a521-b4c4e5639484
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 19

[{"att1":"value1"}]

HTTP/1.0 400 BAD REQUEST
Content-Type: application/json; charset=utf-8
Content-Length: 112
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Tue, 28 Jul 2020 07:54:33 GMT

{"réponse": {"erreurs": [{"att1": "value1", "erreur": "MyException[2, la clé [att1] n'est pas autorisée]"}]}}

Durum 5: Eksik anahtarları olan bir sözlük içeren bir sözlük listesi gönderelim:


POST / HTTP/1.1
Authorization: Basic YWRtaW46YWRtaW4=
Content-Type: application/json
User-Agent: PostmanRuntime/7.26.2
Accept: */*
Cache-Control: no-cache
Postman-Token: 98aec51d-f37d-4c14-81cd-c7ffcbbcdc65
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 18

[{"marié":"oui"}]

HTTP/1.0 400 BAD REQUEST
Content-Type: application/json; charset=utf-8
Content-Length: 125
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Tue, 28 Jul 2020 07:56:40 GMT

{"réponse": {"erreurs": [{"marié": "oui", "erreur": "le dictionnaire doit inclure les clés [marié, enfants, salaire]"}]}}

Durum 6: Doğru anahtarlara sahip ancak bazı anahtarların değerleri hatalı olan bir sözlük içeren sözlük listesi gönderelim:


POST / HTTP/1.1
Authorization: Basic YWRtaW46YWRtaW4=
Content-Type: application/json
User-Agent: PostmanRuntime/7.26.2
Accept: */*
Cache-Control: no-cache
Postman-Token: 3083e601-dee4-4e15-9ea4-fc0328d0fcf0
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 46

[{"marié":"x", "enfants":"x", "salaire":"x"}]

HTTP/1.0 400 BAD REQUEST
Content-Type: application/json; charset=utf-8
Content-Length: 167
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Tue, 28 Jul 2020 07:59:32 GMT

{"réponse": {"erreurs": [{"marié": "x", "enfants": "x", "salaire": "x", "erreur": "MyException[31, l'attribut marié [x] doit avoir l'une des valeurs oui / non]"}]}}

28.3. Web istemcisi

Image

[http-clients/05] (sürüm 10) dosyası, başlangıçta [http-clients/02] (sürüm 7) dosyasının kopyalanmasıyla elde edilir. Daha sonra bu dosya değiştirilir.

28.3.1. [dao] katmanı

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


# ithalatlar

import requests
from flask_api import status

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ôtsDaoWithHttpClient(AbstractImpôtsDao, InterfaceImpôtsMétier):

    # yapıcı
    def __init__(self, config: dict):
        

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

    # vergi hesaplaması
    def calculate_tax(self, taxpayer: TaxPayer, admindata: AdminData = None):
        

    # toplu modda vergi hesaplaması
    def calculate_tax_in_bulk_mode(self, taxpayers: list) -> list:
        # istisnaların yukarıya iletilmesine izin verilir

        # vergi mükelleflerini sözlük listesine dönüştür
        # sadece özellikleri saklanır [marié, enfants, salaire]
        list_dict_taxpayers = list(
            map(lambda taxpayer:
                taxpayer.asdict(included_keys=[
                    '_TaxPayer__marié',
                    '_TaxPayer__enfants',
                    '_TaxPayer__salaire']),
                taxpayers))

        # sunucuya bağlanılıyor
        config_server = self.__config_server
        if config_server['authBasic']:
            response = requests.post(config_server['urlServer'], json=list_dict_taxpayers,
                                     auth=(config_server["user"]["login"],
                                           config_server["user"]["password"]))
        else:
            response = requests.post(config_server['urlServer'], json=list_dict_taxpayers)
        # hata ayıklama modu mu?
        if self.__debug:
            # günlük kaydedici
            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(93, résultat['réponse']['erreurs'])
        # sonucun, yanıtta yer alan [results] anahtarıyla ilişkilendirildiği bilinmektedir
        list_dict_taxpayers2 = résultat['réponse']['results']
        # Alınan sonuçlarla vergi mükelleflerinin ilk listesi güncellenir
        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
  • 1-26. satırlar: kod, sürüm 7 ve diğer sürümlerdeki haliyle kalır;
  • 27-70. satırlar: Bir vergi mükellefi listesinin vergisini hesaplamakla görevli yeni bir yöntem olan [calculate_tax_in_bulk_mode] eklenmiştir;
  • 28. satır: [taxpayers], bu vergi mükellefleri listesidir;
  • satır 31-39: [TaxPayer] türündeki nesnelerden oluşan bir listeden, [map] işlevi sayesinde sözlüklerden oluşan bir listeye geçilir;
  • 34-38. satırlar: Kullanılan lambda işlevi, [TaxPayer] türündeki bir nesneyi, yalnızca [marié, enfants, salaire] anahtarlarını içeren [dict] türünde bir sözlüğe dönüştürür. Bunun için [BaseEntity.asdict] yönteminin [included_keys] adlı parametresi kullanılır. [excluded_keys, included_keys] parametrelerine eklenecek özelliklerin tam adlarını öğrenmek için önceden tanımlanmış [taxpayer.__dict__] sözlüğünün kullanılması gerektiğini hatırlatırız;
  • satır 41-48: sunucuya bağlanma ve ardından HTTP yanıtının alınması;
  • satır 44, 48:
    • sunucuya bir POST göndermek için [requests.post] statik yöntemi kullanılır;
    • [json] adlı parametre kullanılarak, POST'in gövdesinin bir jSON dizesi olduğu belirtilir. Bunun iki sonucu olacaktır:
      • [json] adlı parametreye atanan nesne, bu örnekte bir sözlük listesi, jSON dizesine dönüştürülecektir;
      • başlık
Content-Type: application/json

başlığı, HTTP başlıklarına dahil edilecektir;

  • 59. satır: Sunucudan gelen jSON yanıtı, [résultat] sözlüğüne deserialize edilir;
  • 61-63. satırlar: sunucu tarafından gönderilen olası hata işlenir;
  • 65. satır: Vergi hesaplama sonuçları bir sözlük listesinde yer alır;
  • 67-69. satırlar: Bu sonuçlar, 28. satırdaki yöntemle başlangıçta alınan [taxpayers] vergi mükellefleri listesini güncellemek için kullanılır;
  • satır 70: burada, vergi hesaplama sonuçlarıyla birlikte vergi mükelleflerinin ilk listesi güncellenmiştir;

28.3.2. Ana komut dosyası [main]

Ana komut dosyası [main] şu şekilde değiştirilmiştir: Yalnızca istemci tarafından oluşturulan iş parçacıkları tarafından yürütülen [thread_function] işlevi değiştirilmiştir. Kodun geri kalanı aynı kalmıştır.


# [dao] katmanının bir iş parçacığında çalıştırılması
# "taxpayers", vergi mükelleflerinin listesidir
def thread_function(dao: ImpôtsDaoWithHttpClient, logger: Logger, taxpayers: list):
    # iş parçacığının başlangıç günlüğü
    thread_name = threading.current_thread().name
    nb_taxpayers = len(taxpayers)
    # günlük
    logger.write(f"début du calcul de l'impôt des {nb_taxpayers} contribuables\n")
    # vergi mükelleflerinin vergisi hesaplanıyor
    dao.calculate_tax_in_bulk_mode(taxpayers)
    # günlük
    logger.write(f"fin du calcul de l'impôt des {nb_taxpayers} contribuables\n")
  • 9-10. satırlar: Daha önce, her bir vergi mükellefini sırayla [dao.calculate_tax] yöntemine aktaran bir döngü varken, burada tüm vergi mükelleflerini [dao.calculate_tax_in_bulk_mode] yöntemine aktaran tek bir çağrı yapılır;

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

Aşağıdaki sürümlerin yürütme sürelerini karşılaştıracağız:

  • 7, burada her vergi mükellefi için ayrı bir HTTP sorgusu gerçekleştirilir;
  • 10 (bu sürüm), burada vergi mükellefleri tek bir HTTP isteğinde birleştirilir;

Öncelikle sürüm 6. İki sürümü karşılaştırmak için, iş parçacıklarında zorla bekleme olmaması amacıyla sunucudaki [sleep_time] özelliğini sıfıra ayarlıyoruz. İstemci günlükleri şu şekildedir:


2020-07-28 14:20:45.811347, Thread-1 : début du thread [Thread-1] avec 4 contribuable(s)
2020-07-28 14:20:45.811347, Thread-1 : début du calcul de l'impôt de {"id": 1, "marié": "oui", "enfants": 2, "salaire": 55555}

2020-07-28 14:20:45.913065, Thread-3 : 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-28 14:20:45.913065, Thread-3 : fin du thread [Thread-3]

Dolayısıyla, 11 vergi mükellefinin vergisini hesaplamak için istemcinin yürütme süresi [913065-811347= 101718], yani yaklaşık 102 milisaniyedir.

Aynı işlemi sürüm 10 ile de yapalım (sıfır sunucusundan gelen sleep_time). Bu durumda istemcinin günlükleri şu şekildedir:


2020-07-28 14:25:31.871428, Thread-1 : début du calcul de l'impôt des 4 contribuables
2020-07-28 14:25:31.873594, Thread-2 : début du calcul de l'impôt des 3 contribuables
2020-07-28 14:25:31.877429, Thread-3 : début du calcul de l'impôt des 3 contribuables
2020-07-28 14:25:31.882855, Thread-4 : début du calcul de l'impôt des 1 contribuables
2020-07-28 14:25:31.930723, Thread-2 : {"réponse": {"results": [{"marié": "non", "enfants": 3, "salaire": 100000, "impôt": 16782, "surcôte": 7176, "taux": 0.41, "décôte": 0, "réduction": 0}, {"marié": "oui", "enfants": 3, "salaire": 100000, "impôt": 9200, "surcôte": 2180, "taux": 0.3, "décôte": 0, "réduction": 0}, {"marié": "oui", "enfants": 5, "salaire": 100000, "impôt": 4230, "surcôte": 0, "taux": 0.14, "décôte": 0, "réduction": 0}]}}
….
2020-07-28 14:25:31.935958, Thread-4 : fin du calcul de l'impôt des 1 contribuables
2020-07-28 14:25:31.935958, Thread-1 : fin du calcul de l'impôt des 4 contribuables

Dolayısıyla, 11 vergi mükellefinin vergisini hesaplamak için istemcinin yürütme süresi [935958-871428= 64530 ns]’tir (8. satır – 1. satır), yani yaklaşık 65 milisaniye. Bu yeni sürüm 10, sürüm 7’ye kıyasla yaklaşık %57’lik bir kazanç sağlıyor.

28.3.4. İstemcinin [dao] katmanının testleri

Image

Sürüm 10 istemcisinin [TestHttpClientDao] testi, sürüm 7'ninkine çok yakındır:


import unittest

from Logger import Logger


class TestHttpClientDao(unittest.TestCase):

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

        # {'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_in_bulk_mode([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ı alıyoruz
    dao = config["layers"]["dao"]

    # test yöntemleri çalıştırılıyor
    print("tests en cours...")
    unittest.main()
  • 14. satır: [dao.calculate_tax] yöntemini çağırmak yerine, bir vergi mükellefinin listesini (köşeli parantezlerin varlığı) aktaran [dao.calculate_tax_in_bulk_mode] yöntemi çağrılır;

Tüm testler başarılı.