Skip to content

24. Uygulama alıştırması: sürüm 7

24.1. Giriş

Vergi hesaplama uygulamasının 7. sürümü, aşağıdaki ayrıntılar dışında 6. sürümle aynıdır:

  • web istemcisi, HTTP isteklerini aynı anda başlatacaktır. Önceki sürümde bu istekler sırayla başlatılıyordu. O zamanlar sunucu, herhangi bir anda yalnızca tek bir isteği işliyordu;
  • sunucu çok iş parçacıklı olacaktır: aynı anda birden fazla isteği işleyebilecektir;
  • bu isteklerin yürütülmesini takip etmek için, web sunucusuna bir günlük kaydedici eklenecek ve bu sayede isteklerin işlenmesindeki önemli anlar bir metin dosyasına kaydedilecektir;
  • sunucu, başlatılmasını engelleyen bir sorunla karşılaştığında (genellikle web sunucusuyla ilişkili veritabanında bir sorun olması durumunda) uygulama yöneticisine bir e-posta gönderecektir;

Uygulamanın mimarisi değişmez:

Komut dosyalarının dizin yapısı şu şekildedir:

Image

[http-servers/02] klasörü, öncelikle [http-servers/01] klasörünün kopyalanmasıyla oluşturulur. Ardından bu klasörde değişiklikler yapılır.

24.2. Yardımcı programlar

Image

24.2.1. [Logger] sınıfı

[Logger] sınıfı, web sunucusundaki belirli eylemlerin bir metin dosyasına kaydedilmesini sağlar:


import codecs
import threading
from datetime import date, datetime
from threading import current_thread

from ImpôtsError import ImpôtsError


class Logger:
    # sınıf özniteliği
    verrou = threading.RLock()

    # yapıcı
    def __init__(self, logs_filename: str):
        try:
            # dosyayı ekleme modunda (a) açma
            self.__resource = codecs.open(logs_filename, "a", "utf-8")
        except BaseException as erreur:
            raise ImpôtsError(18, f"{erreur}")

    # günlük kaydı yazma
    def write(self, message: str):
        # anlık tarih / saat
        today = date.today()
        now = datetime.time(datetime.now())
        # iş parçacığının adı
        thread_name = current_thread().name
        # günlük dosyasına yazarken rahatsız edilmek istemiyoruz
        # sınıfın senkronizasyon nesnesini (= kilidi) talep ediyoruz - yalnızca bir iş parçacığı bunu alacaktır
        Logger.verrou.acquire()
        try:
            # günlüğe yazma
            self.__resource.write(f"{today} {now}, {thread_name} : {message}")
            # hemen yazma - aksi takdirde metin ancak yazma akışı kapatıldığında yazılır
            # ya da günlükleri zaman içinde takip etmek istiyoruz
            self.__resource.flush()
        finally:
            # başka bir iş parçacığının onu alabilmesi için senkronizasyon nesnesini (= kilidi) serbest bırakıyoruz
            Logger.verrou.release()

    # kaynakların serbest bırakılması
    def close(self):
        # dosyanın kapatılması
        if self.__resource:
            self.__resource.close()
  • 10-11. satırlar: bir sınıf özniteliği tanımlanır. Sınıf özniteliği, sınıfın tüm örnekleri tarafından paylaşılan bir özelliktir. Buna [Classe.attribut_de_classe] notasyonu ile atıfta bulunulur (30, 39. satırlar). [verrou] sınıf özniteliği, 31-36. satırlardaki kodu çalıştıran tüm iş parçacıkları için bir senkronizasyon nesnesi olacaktır;
  • 14-19. satırlar: Oluşturucu, günlük dosyasının mutlak adını alır. Ardından bu dosya açılır ve alınan dosya tanımlayıcısı sınıfta saklanır;
  • satır 17: günlük dosyası ‘append’ (a) modunda açılır. Yazılan her satır, dosyanın sonuna eklenecektir;
  • 22-39. satırlar: [write] yöntemi, parametre olarak geçirilen bir mesajın günlük dosyasına yazılmasını sağlar. Bu mesaja iki bilgi eklenir:
    • 24. satır: günün tarihi;
    • satır 25: o anki saat;
    • satır 27: günlüğü yazan iş parçacığının adı. Burada, bir web uygulamasının aynı anda birden fazla kullanıcıya hizmet verdiğini unutmamak gerekir. Her isteğe, yürütülmesi için bir iş parçacığı atanır. Bu iş parçacığı, genellikle bir giriş/çıkış işlemi (ağ, dosyalar, veritabanı) nedeniyle duraklatılırsa, işlemci başka bir iş parçacığına verilir. Bu olası kesintiler nedeniyle, bir iş parçacığının kesintiye uğramadan günlük dosyasına bir satır yazabileceğinden emin olamayız. Bu durumda, iki farklı iş parçacığının günlüklerinin birbirine karışması riski ortaya çıkar. Risk düşüktür, hatta belki de sıfırdır, ancak yine de iki iş parçacığının ortak bir kaynağa (burada günlük dosyası) erişimini nasıl senkronize edeceğimizi göstermeye karar verdik;
  • 30. satır: Yazmaya başlamadan önce iş parçacığı giriş kapısının anahtarını ister. İstenen anahtar, 11. satırda oluşturulan anahtardır. Bu anahtar gerçekten benzersizdir: bir sınıf özniteliği, sınıfın tüm örnekleri için benzersizdir;
    • T1 zamanında, Thread1 iş parçacığı anahtarı alır. Ardından 33. satırı çalıştırabilir;
    • T2 zamanında, Thread1 iş parçacığı günlüğü yazmayı bitirmeden önce duraklatılır;
    • T3 zamanında, işlemciyi ele geçiren Thread2 iş parçacığı da bir günlük kaydı yazmalıdır. Böylece 30. satıra gelir ve giriş kapısının anahtarını ister. Başka bir iş parçacığının anahtarı zaten elinde olduğu yanıtını alır. Bunun üzerine otomatik olarak askıya alınır. Bu anahtarı isteyen tüm iş parçacıkları için durum aynı olacaktır;
    • T4 zamanında, askıya alınmış olan Thread1 iş parçacığı işlemciye yeniden kavuşur. Ardından günlük yazma işlemini tamamlar;
  • 32-36. satırlar: Günlük dosyasına yazma işlemi iki aşamada gerçekleşir:
  • 33. satır: 17. satırda alınan dosya tanımlayıcısı bir tamponla çalışır. 33. satırdaki [write] işlemi bu tampona yazar, ancak doğrudan dosyaya yazmaz. Tampon daha sonra belirli koşullar altında dosyaya boşaltılır:
        • tampon dolduğunda;
        • dosya tanımlayıcısı bir [close] veya [flush] işlemi geçirirse;
  • 36. satır: Günlük satırının dosyaya yazılması zorlanır. Bunu yapmamızın nedeni, farklı iş parçacıklarının günlüklerinin birbirlerinin arasına serpiştirildiğini görmek istememizdir. Bunu yapmazsak, bir iş parçacığının günlükleri, dosya tanımlayıcısı kapatıldığında (45. satır) hepsi aynı anda yazılır. Bu durumda, bazı iş parçacıklarının durdurulduğunu görmek çok daha zor olur: günlüklerdeki saatlere bakmak gerekir;
  • 39. satır: Thread1 iş parçacığı kendisine verilen anahtarı iade eder. Bu anahtar başka bir iş parçacığına verilebilir;
  • 22. satır: Dolayısıyla [write] yöntemi senkronize edilmiştir: log dosyasına her seferinde yalnızca bir iş parçacığı yazabilir. Aygıtın anahtarı 30. satırdır: Ne olursa olsun, yalnızca bir iş parçacığı bir sonraki satıra geçmek için gerekli anahtarı alır. Anahtarı, iade edene kadar (39. satır) elinde tutar;
  • 41-45. satırlar: [close] yöntemi, günlük dosyası tanımlayıcısına ayrılan kaynakların serbest bırakılmasını sağlar;

Günlük dosyasına yazılan günlükler şu şekilde görünecektir:

2020-07-22 20:03:52.992152, Thread-2 : …

24.2.2. [SendAdminMail] sınıfı

[SendAminMail] sınıfı, uygulama ‘çöktüğünde’ uygulama yöneticisine bir mesaj gönderilmesini sağlar.

Image

[SendAdminMail] sınıfı, [config] [2] betiğinde şu şekilde yapılandırılır:


        # SMTP sunucu yapılandırması
        "adminMail": {
            # sunucu SMTP
            "smtp-server": "localhost",
            # SMTP sunucu bağlantı noktası
            "smtp-port": "25",
            # yönetici
            "from": "guest@localhost.com",
            "to": "guest@localhost.com",
            # e-posta konusu
            "subject": "plantage du serveur de calcul d'impôts",
            # SMTP sunucusu yetkilendirme gerektiriyorsa tls değeri True, aksi takdirde False
            "tls": False
        }

[SendAdminMail] sınıfı, 2-13. satırlardaki sözlüğü ve e-posta gönderme yapılandırmasını alır. Sınıf şu şekildedir:


# ithalatlar
import smtplib
from email.mime.text import MIMEText
from email.utils import formatdate


class SendAdminMail:

    # -----------------------------------------------------------------------
    @staticmethod
    def send(config: dict, message: str, verbose: bool = False):
        # mesajı, config['smtp-server'] sunucusuna config[smtp-port] bağlantı noktası üzerinden gönderir
        # eğer config['tls'] doğruysa, TLS desteği kullanılacak
        # e-posta, config['from'] adına gönderilir
        # alıcı config['to']'e
        # mesajın konusu config['subject']
        # yapılandırmada bir logger referansı bulunur['logger']

        # yapılandırma dosyasından logger alınır - None değerine eşit olabilir
        logger = config["logger"]
        # sunucu SMTP
        server = None
        # mesaj gönderilir
        try:
            # SMTP sunucusu
            server = smtplib.SMTP(config["smtp-server"])
            # ayrıntılı mod
            server.set_debuglevel(verbose)
            # güvenli bağlantı mı?
            if config['tls']:
                # güvenlik diyaloğu başlıyor
                server.starttls()
                # kimlik doğrulama
                server.login(config["user"], config["password"])
            # Multipart mesajı oluşturuluyor - gönderilecek mesaj budur
            msg = MIMEText(message)
            msg['From'] = config["from"]
            msg['To'] = config["to"]
            msg['Date'] = formatdate(localtime=True)
            msg['Subject'] = config["subject"]
            # mesaj gönderiliyor
            server.send_message(msg)
            # günlük - günlük kaydedici mevcut olmayabilir
            if logger:
                logger.write(f"[SendAdminMail] Message envoyé à [{config['to']}] : [{message}]\n")
        except BaseException as erreur:
            # günlük - günlük kaydedici mevcut olmayabilir
            if logger:
                logger.write(
                    f"[SendAdminMail] Erreur [{erreur}] lors de l'envoi à [{config['to']}] du message [{message}] : \n")
        finally:
            # işlem tamamlandı - işlev tarafından kullanılan kaynaklar serbest bırakılıyor
            if server:
                server.quit()
  • 24-54. satırlar: |smtp/02| örneğinde daha önce incelenen kod burada da yer almaktadır;
  • 20. satır: Bir logger referansı alınır. Bu referans, 45. ve 49. satırlarda kullanılır;

24.3. Web sunucusu

Image

24.3.1. Yapılandırma

Image

Sunucunun yapılandırması, daha önce incelenen sunucunun yapılandırmasına çok benziyor. Yalnızca [config.py] dosyasında küçük bir değişiklik var:


def configure(config: dict) -> dict:
    import os

    # 1. adım ------

    # bu dosyanın bulunduğu 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, aralıklar
        f"{root_dir}/impots/v05/entities",
        # IndexController
        f"{root_dir}/impots/http-servers/01/controllers",
        # komut dosyaları [config_database, config_layers]
        script_dir,
        # Logger, SendAdminMail
        f"{script_dir}/../utilities",
    ]
    # syspath ayarlanıyor
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    # 2. adım ------
    # uygulama yapılandırması
    config.update({
        # uygulamayı kullanmaya yetkili kullanıcılar
        "users"[
            {
                "login""admin",
                "password""admin"
            }
        ],
        # günlük dosyası
        "logsFilename"f"{script_dir}/../data/logs/logs.txt",
        # sunucu yapılandırması SMTP
        "adminMail": {
            # SMTP sunucusu
            "smtp-server""localhost",
            # SMTP sunucusunun bağlantı noktası
            "smtp-port""25",
            # yönetici
            "from""guest@localhost.com",
            "to""guest@localhost.com",
            # e-posta konusu
            "subject""plantage du serveur de calcul d'impôts",
            # SMTP sunucusu yetkilendirme gerektiriyorsa TLS'yi True, aksi takdirde False olarak ayarlayın
            "tls"False
        },
        # iş parçacığı bekleme süresi (saniye cinsinden)
        "sleep_time"0
    })

    # 3. adım ------
    # veritabanı yapılandırması
    import config_database
    config["database"] = config_database.configure(config)

    # adım 4 ------
    # uygulama katmanlarının örneklenmesi
    import config_layers
    config['layers'] = config_layers.configure(config)

    # yapılandırma gönderiliyor
    return config
  • 40-66. satırlar: Sunucu yapılandırma sözlüğüne, günlük kaydediciyle ilgili öğeler (49. satır) ve uygulama yöneticisine uyarı e-postası gönderilmesiyle ilgili öğeler (51-63. satırlar) eklenir;
  • 65. satır: Çalışan iş parçacıklarını daha iyi görebilmek için bazılarının durdurulmasını sağlayacağız. [sleep_time], saniye cinsinden ifade edilen durdurma süresidir;
  • satır 27-28: önceki sürüm 6'ya ait [index_controller] denetleyicisinin kullanıldığına dikkat edelim;

24.3.2. Ana komut dosyası [main]

Ana komut dosyası [main] şu şekildedir:


# mysql veya pgres parametresini bekliyoruz
import sys
syntaxe = f"{sys.argv[0]} mysql / pgres"
erreur = len(sys.argv) != 2
if not erreur:
    sgbd = sys.argv[1].lower()
    erreur = sgbd != "mysql" and sgbd != "pgres"
if erreur:
    print(f"syntaxe : {syntaxe}")
    sys.exit()

# uygulama yapılandırılıyor
import config
config = config.configure({'sgbd': sgbd})

# bağımlılıklar
from flask import request, Flask
from flask_httpauth import HTTPBasicAuth
import json
import index_controller
from flask_api import status
from SendAdminMail import SendAdminMail
from myutils import json_response
from Logger import Logger
import threading
import time
from random import randint
from ImpôtsError import ImpôtsError

# kimlik doğrulama yöneticisi
auth = HTTPBasicAuth()


@auth.verify_password
def verify_password(login, password):
    # kullanıcı listesi
    users = config['users']
    # bu liste taranıyor
    for user in users:
        if user['login'] == login and user['password'] == password:
            return True
    # bulunamadı
    return False


# yöneticiye e-posta gönderiliyor
def send_adminmail(config: dict, message: str):
    # uygulama yöneticisine e-posta gönderiliyor
    config_mail = config["adminMail"]
    config_mail["logger"] = config['logger']
    SendAdminMail.send(config_mail, message)


# günlük dosyası kontrol ediliyor
logger = None
erreur = False
message_erreur = None
try:
    # günlük kaydedici
    logger = Logger(config["logsFilename"])
except BaseException as exception:
    # konsol günlüğü
    print(f"L'erreur suivante s'est produite : {exception}")
    # hatkın not alınması
    erreur = True
    message_erreur = f"{exception}"
# loglayıcıyı yapılandırmada kaydedin
config['logger'] = logger
# hatanın yönetimi
if erreur:
    # yöneticiye e-posta gönderiliyor
    send_adminmail(config, message_erreur)
    # uygulama sonlandırılıyor
    sys.exit(1)

# başlangıç günlüğü
log = "[serveur] démarrage du serveur"
logger.write(f"{log}\n")
print(log)

# Vergi İdaresi verilerinin alınması
erreur = False
try:
    # admindata, uygulama kapsamlı, 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)

# ana iş parçacığı artık günlüğe ihtiyaç duymuyor
logger.close()

# hata varsa durdurulur
if erreur:
    sys.exit(2)

# Flask uygulaması başlatılabilir
app = Flask(__name__)


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



# sadece ana iş parçacığı
if __name__ == '__main__':
    # sunucu başlatılıyor
    app.config.update(ENV="development", DEBUG=True)
    app.run(threaded=True)
  • 1-10. satırlar: komut dosyası, hangi SGBD'in kullanılacağını belirten [mysql / pgres] parametresini bekler;
  • 12-14. satırlar: Uygulama yapılandırılır (Python Yolu, katmanlar, veritabanı);
  • satır 16-28: uygulama için gerekli bağımlılıklar;
  • satır 30-43: kimlik doğrulama yönetimi;
  • satır 46-51: uygulama yöneticisine e-posta gönderen bir işlev;
  • fonksiyon iki parametre bekler:
      • config: [adminMail] ve [logger] anahtarlarını içeren bir sözlük;
      • gönderilecek mesaj;
    • 49-50. satırlar: gönderim yapılandırması hazırlanır;
    • e-posta gönderilir;
  • 54-74. satırlar: günlük dosyasının varlığı kontrol edilir;
  • 70-74. satırlar: günlük dosyasını açamadıysak, yöneticiye bir e-posta gönderir ve işlemi sonlandırırız;
  • 76-79. satırlar: sunucunun başlatılması günlüğe kaydedilir;
  • satır 81-98: veritabanından vergi idaresi verilerini alınır;
  • satır 88-98: bu veriler alınamazsa, hata hem konsola hem de günlük dosyasına kaydedilir;
  • 100-101. satırlar: ana iş parçacığı artık günlük kaydı yapmayacaktır (oluşturulan iş parçacıkları aynı dosya tanımlayıcısını kullanmayacaktır);
  • satır 103-105: veritabanına bağlanılamazsa, işlem durdurulur;
  • satır 122: sunucu çok iş parçacıklı modda başlatılır;

[index] işlevi (satır 114) şu şekildedir:


# Ana Sayfa URL
@app.route('/', methods=['GET'])
@auth.login_required
def index():
    logger = None
    try:
        # günlük kaydı
        logger = Logger(config["logsFilename"])
        # iş parçacığıyla ilişkili bir yapılandırmada saklanır
        thread_config = {"logger": logger}
        thread_name = threading.current_thread().name
        config[thread_name] = {"config": thread_config}
        # isteği günlüğe kaydediliyor
        logger.write(f"[index] requête : {request}\n")
        # istenirse iş parçacığı durdurulur
        sleep_time = config["sleep_time"]
        if sleep_time != 0:
            # bazı iş parçacıklarının kesintiye uğraması, diğerlerinin uğramaması için duraklama süresi rastgele belirlenir
            aléa = randint(0, 1)
            if aléa == 1:
                # duraklama öncesi günlük kaydı
                logger.write(f"[index] mis en pause du thread pendant {sleep_time} seconde(s)\n")
                # duraklama
                time.sleep(sleep_time)
        # istek bir denetleyici tarafından yürütülür
        résultat, status_code = index_controller.execute(request, config)
        # Önemli bir hata oluştu mu?
        if status_code == status.HTTP_500_INTERNAL_SERVER_ERROR:
            # uygulama yöneticisine bir e-posta gönderiliyor
            config_mail = config["adminMail"]
            config_mail["logger"] = logger
            SendAdminMail.send(config_mail, json.dumps(résultat, ensure_ascii=False))
        # yanıt günlüğe kaydedilir
        logger.write(f"[index] {résultat}\n")
        # yanıt gönderilir
        return json_response(résultat, status_code)
    except BaseException as erreur:
        # mümkünse hata günlüğe kaydedilir
        if logger:
            logger.write(f"[index] {erreur}")
        # müşteriye verilecek yanıt hazırlanır
        résultat = {"réponse": {"erreurs": [f"{erreur}"]}}
        # yanıt gönderilir
        return json_response(résultat, status.HTTP_500_INTERNAL_SERVER_ERROR)
    finally:
        # açılmışsa günlük dosyasını kapatılır
        if logger:
            logger.close()
  • 4. satır: Bir kullanıcı URL / komutunu istediğinde yürütülen işlev. Sunucu çok iş parçacıklı olduğu için (112. satır), işlevi yürütmek üzere bir iş parçacığı oluşturulur. Bu iş parçacığı herhangi bir anda kesilebilir ve daha sonra yürütülmeye devam etmek üzere duraklatılabilir. Kod, tüm iş parçacıkları tarafından paylaşılan bir kaynağa eriştiğinde bu noktayı her zaman akılda tutmak gerekir. Burada böyle bir kaynak, günlük dosyasıdır: tüm iş parçacıkları bu dosyaya yazmaktadır;
  • 8. satır: bir loglayıcı örneği oluşturulur. Dolayısıyla tüm iş parçacıkları, loglayıcının farklı birer örneğine sahip olacaktır. Bununla birlikte, tüm bu loglayıcılar aynı log dosyasına işaret eder. Yine de, bir iş parçacığı kendi loglayıcısını kapattığında, bunun diğer iş parçacıklarının loglayıcıları üzerinde herhangi bir etkisi olmadığını belirtmek önemlidir;
  • 9-12. satırlar: Günlük kaydedici, uygulamanın [config] sözlüğüne, iş parçacığının adını taşıyan bir anahtarla ilişkilendirilerek kaydedilir. Böylece, eşzamanlı olarak çalışan n adet iş parçacığı varsa, [config] sözlüğünde n adet giriş oluşturulur. [config], tüm iş parçacıkları arasında paylaşılan bir kaynaktır. Dolayısıyla senkronizasyon ihtiyacı doğabilir. Burada bir varsayımda bulundum. İki iş parçacığının [config] dosyasında eşzamanlı olarak kendi girdilerini oluştururken, bunlardan birinin diğeri tarafından kesintiye uğramasının bir etkisi olmayacağını varsaydım. Kesintiye uğrayan iş parçacığı, daha sonra girdinin oluşturulmasını tamamlayabilirdi. Deney, bu varsayımın yanlış olduğunu gösterirse, 12. satıra erişimin senkronize edilmesi gerekir;
  • 10. satır: loglayıcıyı bir sözlüğe ekliyoruz;
  • 11. satır: [threading.current_thread()], bu satırı çalıştıran iş parçacığıdır, dolayısıyla [index] işlevini çalıştıran iş parçacığıdır. Adını not ederiz. Her iş parçacığının benzersiz bir adı vardır;
  • 12. satır: iş parçacığının yapılandırmasını kaydediyoruz. Bundan sonra her zaman şu şekilde ilerleyeceğiz: iş parçacıkları arasında paylaşılamayan bilgiler varsa, bunlar yine de genel yapılandırmaya eklenecek, ancak iş parçacığının adıyla ilişkilendirilecektir;
  • 14. satır: Yürütülmekte olan istek günlüğe kaydedilir;
  • satır 15-24: işlemciyi başka bir iş parçacığına bırakmaları için bazı iş parçacıklarını rastgele duraklatıyoruz;
    • 16. satır: yapılandırmadan duraklama süresini (saniye cinsinden) alıyoruz;
    • 17. satır: Duraklama süresi 0'dan farklıysa duraklama gerçekleşir;
    • satır 19: [0, 1] aralığında rastgele bir tamsayı. Dolayısıyla yalnızca 0 ve 1 değerleri mümkündür;
    • 20. satır: Yalnızca rastgele sayı 1 ise iş parçacığı duraklatılır;
    • 22. satır: iş parçacığının durdurulacağı bilgisi günlüğe kaydedilir;
    • 24. satır: iş parçacığı [sleep_time] saniye boyunca durdurulur;
  • 26. satır: İş parçacığı uyanınca, [index_controller] modülü aracılığıyla sorguyu yürütür;
  • 28-32. satırlar: Bu işlem [500 INTERNAL SERVER ERROR] türünde bir hataya neden olursa, yöneticiye bir e-posta gönderilir;
    • satır 30-31: [config_mail] sözlüğünü yapılandırırız ve bunu [SendAdminMail] sınıfına aktarırız;
    • satır 32: yöneticiye gönderilen mesaj, müşteriye gönderilecek sonucun jSON dizesidir;
  • satır 33-34: müşteriye gönderilecek yanıt (satır 36) günlüğe kaydedilir;
  • satır 37-44: Olası bir istisnanın işlenmesi;
  • satır 39-40: Günlük kaydedici varsa, meydana gelen hata günlüğe kaydedilir;
  • 47-48. satırlar: loglayıcı varsa kapatılır. Sonuç olarak, iş parçacığı isteğin başında bir loglayıcı oluşturur ve istek işlendiğinde onu kapatır;

24.3.3. [index_controller] denetleyicisi

İstekleri yürüten [index_controller] denetleyicisi, önceki sürümünkiyle aynıdır:

Image

24.3.4. Çalıştırma

Flask sunucusunu, |hMailServer| e-posta sunucusunu ve |Thunderbird| e-posta okuyucusunu başlatıyoruz. SGBD'i başlatmıyoruz. Sunucu, aşağıdaki konsol günlükleriyle durur:


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-servers/02/flask/main.py mysql
[serveur] démarrage du serveur
L'erreur suivante s'est produite : MyException[27, (mysql.connector.errors.InterfaceError) 2003: Can't connect to MySQL server on 'localhost:3306' (10061 Aucune connexion n’a pu être établie car l’ordinateur cible l’a expressément refusée)
(Background on this error at: http://sqlalche.me/e/13/rvf5)]

Process finished with exit code 2 

[logs.txt] günlük dosyası ise şu şekildedir:


2020-07-23 11:51:38.324752, MainThread : [serveur] démarrage du serveur
2020-07-23 11:51:40.355510, MainThread : L'erreur suivante s'est produite : MyException[27, (mysql.connector.errors.InterfaceError) 2003: Can't connect to MySQL server on 'localhost:3306' (10061 Aucune connexion n’a pu être établie car l’ordinateur cible l’a expressément refusée)
(Background on this error at: http://sqlalche.me/e/13/rvf5)]
2020-07-23 11:51:42.464206, MainThread : [SendAdminMail] Message envoyé à [guest@localhost.com] : [L'erreur suivante s'est produite : MyException[27, (mysql.connector.errors.InterfaceError) 2003: Can't connect to MySQL server on 'localhost:3306' (10061 Aucune connexion n’a pu être établie car l’ordinateur cible l’a expressément refusée)
(Background on this error at: http://sqlalche.me/e/13/rvf5)]]

Thunderbird ile [guest@localhost.com] adlı yöneticinin e-postaları kontrol ediliyor:

Image

Ardından SGBD'i başlatıp URL ve [http://127.0.0.1:5000/?mari%C3%A9=oui&enfants=3&salaire=200000]'i talep ediyoruz. Günlükler şu şekilde oluyor:


2020-07-23 11:56:38.891753, MainThread : [serveur] démarrage du serveur
2020-07-23 11:56:38.987999, MainThread : [serveur] connexion à la base de données réussie
2020-07-23 11:56:40.586747, MainThread : [serveur] démarrage du serveur
2020-07-23 11:56:40.655254, MainThread : [serveur] connexion à la base de données réussie
2020-07-23 11:56:54.528360, Thread-2 : [index] requête : <Request 'http://127.0.0.1:5000/?evli=evet&çocuk=3&maaş=200000' [GET]>
2020-07-23 11:56:54.530653, Thread-2 : [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}}}
  • 1-4. satırlar: [Debug=True] modu ikinci bir başlatmaya neden olduğu için sunucunun iki kez başlatıldığını hatırlatırız;
  • 5-6. satırlar: Günlükler, bir isteğin yürütme süresi hakkında fikir verir; burada bu süre 2,293 milisaniyedir;

24.4. Web istemcisi

Image

Image

[http-clients/02] klasörü, [http-clients/01] klasörünün kopyalanmasıyla elde edilir. Ardından birkaç değişiklik yapılır.

24.4.1. Yapılandırma

[http-clients/02] uygulamasının [config] yapılandırması, birkaç küçük ayrıntı dışında [http-clients/01] uygulamasının yapılandırmasıyla aynıdır:


def configure(config: dict) -> dict:
    import os

    # 1. adım ------

    # bu dosyanın bulunduğu 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ôtsDaoWithHttpClient
        f"{script_dir}/../services",
        # yapılandırma komut dosyaları
        script_dir,
        # Günlük Kaydedici
        f"{root_dir}/impots/http-servers/02/utilities",
    ]

    # syspath'i ayarlıyoruz
    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/",
            "authBasic"True,
            "user": {
                "login""admin",
                "password""admin"
            }
        },
        # hata ayıklama modu
        "debug"True
    }
    )

    # 3. adım ------
    # katmanların oluşturulması
    import config_layers
    config['layers'] = config_layers.configure(config)

    # yapılandırmayı oluşturuyoruz
    return config
  • 31-32. satırlar: sunucu için kullanılanla aynı |Logger| günlük kaydedicisini kullanacağız;
  • 49. satır: günlük dosyasının mutlak yolu;
  • 60. satır: [debug=True] modu, web sunucusunun yanıtlarını günlük dosyasına yazmak için kullanılır;

24.4.2. [dao] katmanı

[ImpôtsDaoWithHttpClient] sınıfının kodu biraz değişir:


# ithalatlar

import requests
from flask_api import status




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

    # yapıcı
    def __init__(self, config: dict):
        # üst nesnenin başlatılması
        AbstractImpôtsDao.__init__(self, config)
        # yapılandırma öğelerinin depolanması
        # 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

    # 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
        
        # hata ayıklama modu mu?
        if self.__debug:
            # günlük kaydedici
            if not self.__logger:
                self.__logger = self.__config['logger']
            # günlük kaydı
            self.__logger.write(f"{response.text}\n")
        # yanıt durum kodu HTTP
        status_code = response.status_code
        
  • 17. satır: Genel yapılandırma kaydedilir. Daha sonra göreceğiz ki, [ImpôtsDaoWithHttpClient] sınıfının oluşturucusu çalıştırıldığında, [config] sözlüğü henüz 37. satırda kullanılan [logger] anahtarını içermemektedir. Bu nedenle, oluşturucuda [self.__logger] (satır 23) başlatılamaz;
  • 21. satır: Yapılandırmaya, 33-39. satırların günlüğünü kontrol eden [debug] anahtarı eklendi;
  • satır 34: [debug] modundaysak;
  • satır 36-37: [self.__logger] özelliğinin olası başlatılması. [calculate_tax] yöntemi kullanıldığında, [logger] anahtarı [config] sözlüğünün bir parçasıdır;
  • satır 39: sunucunun HTTP yanıtıyla ilişkili metin belgesi günlüğe kaydedilir;

[dao] katmanı, birden fazla iş parçacığı tarafından eşzamanlı olarak çalıştırılacaktır. Oysa burada bu katmanın tek bir örneği oluşturulmaktadır (bkz. config_layers). Bu nedenle, kodun paylaşılan verilere (tipik olarak [dao] katmanını uygulayan [ImpôtsDaoWithHttpClient] sınıfının özellikleri) yazma erişimi gerektirmediğinden emin olunmalıdır. Oysa yukarıda 37. satır, sınıf örneğinin bir özelliğini değiştiriyor. Burada bu durum bir sorun yaratmaz çünkü tüm iş parçacıkları aynı günlüğü paylaşmaktadır. Aksi durumda, 37. satıra erişim senkronize edilmeliydi.

24.4.3. Ana komut dosyası

Ana komut dosyası [main] şu şekilde gelişir:


# uygulama yapılandırılıyor

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


# 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
    # [dao] katmanını alıyoruz
    dao = config["layers"]["dao"]
    # vergi mükelleflerinin verilerini okuma
    taxpayers = dao.get_taxpayers_data()["taxpayers"]
    # vergi mükellefleri mi?
    if not taxpayers:
        raise ImpôtsError(36, f"Pas de contribuables valides dans le fichier {config['taxpayersFilename']}")
    # birden fazla iş parçacığıyla vergi mükelleflerinin vergisi hesaplanır
    i = 0
    l_taxpayers = len(taxpayers)
    while i < len(taxpayers):
        # her iş parçacığı 1 ila 4 vergi mükellefini işleyecek
        nb_taxpayers = min(l_taxpayers - i, random.randint(1, 4))
        # iş parçacığı tarafından işlenen vergi mükelleflerinin listesi
        thread_taxpayers = taxpayers[slice(i, i + nb_taxpayers)]
        # bir sonraki iş parçacığı için i değerini artırıyoruz
        i += nb_taxpayers
        # iş parçacığı oluşturulur
        thread = threading.Thread(target=thread_function, args=(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
    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)
except BaseException as erreur:
    # hata mesajı görüntüleniyor
    print(f"L'erreur suivante s'est produite : {erreur}")
finally:
    # günlük kaydedici kapatılıyor
    if logger:
        logger.close()
    # işlem tamamlandı
    print("Travail terminé...")
    # hata nedeniyle durdurulmuşsa, hala mevcut olabilecek iş parçacıkları sonlandırılır
    sys.exit()
  • Ana komut dosyası, sunucuya istekleri göndermek için birden fazla yürütme iş parçacığı oluşturması bakımından önceki istemcinin komut dosyasından farklıdır. Sürüm 6 istemcisi tüm isteklerini sırayla yapıyordu. i numaralı istek, ancak [i-1] numaralı isteğin yanıtı alındıktan sonra gerçekleştirilirdi. Burada, sunucunun aynı anda birden fazla istek aldığında nasıl davranacağını görmek istiyoruz. Bunun için iş parçacıklarına ihtiyacımız var;
  • 21. satır: Oluşturulan iş parçacıkları bir listeye eklenecektir. [main] komut dosyasının da [MainThread] adlı bir iş parçacığı tarafından yürütüldüğünü anlamak gerekir. Bu ana iş parçacığı, bir veya daha fazla vergi mükellefinin vergisini hesaplamakla görevli başka iş parçacıkları oluşturacaktır;
  • 26. satır: Bir günlük dosyası oluşturulur. Bu günlük dosyası tüm iş parçacıkları tarafından paylaşılacaktır;
  • 32. satır: Vergisi hesaplanacak tüm vergi mükellefleri alınır;
  • 39-51. satırlar: Bu vergi mükellefleri birkaç iş parçacığına dağıtılır;
  • satır 40-41: her iş parçacığı 1 ila 4 vergi mükellefini işleyecektir. Bu sayı rastgele belirlenir;
    • [random.randint(1, 4)], [1, 2, 3, 4] listesinden rastgele bir sayı verir;
    • bir iş parçacığı, [l-i]'ten fazla vergi mükellefi barındıramaz; burada [l-i], henüz bir iş parçacığına atanmamış vergi mükelleflerinin sayısını temsil eder;
    • bu nedenle iki değerden en küçüğü alınır;
  • 43. satır: İş parçacığı tarafından işlenen vergi mükelleflerinin sayısı olan [nb_taxpayers] bilindiğinde, bunlar vergi mükellefleri listesinden alınır:
    • [slice(10,12)], [10, 11, 12] indekslerinin kümesidir;
    • [résultat["réponse"]], [taxpayers[10], taxpayers[11], taxpayers[12]] listesidir;
  • 45. satır: 39. satırdaki döngüyü kontrol eden i değerini artırıyoruz;
  • 47. satır: bir iş parçacığı oluşturulur:
    • [target=thread_function], iş parçacığının çalıştıracağı işlevi belirler. Bu, 16-17. satırlardaki işlevdir. Üç parametre bekler;
    • [ags], [thread_function] işlevinin beklediği üç parametrenin listesidir;

Bir iş parçacığı oluşturmak, onu çalıştırmaz. Sadece bir nesne oluşturur, hepsi bu;

  • 48-49. satırlar: Az önce oluşturulan iş parçacığı, ana iş parçacığı tarafından oluşturulan iş parçacıkları listesine eklenir;
  • 51. satır: İş parçacığı başlatılır. Ardından, diğer aktif iş parçacıklarıyla paralel olarak çalıştırılacaktır. Burada, kendisine verilen argümanlarla [thread_function] işlevini çalıştıracaktır;
  • 53-54. satırlar: ana iş parçacığı, başlattığı her bir iş parçacığının tamamlanmasını bekler. Bir örnek verelim:
    • ana iş parçacığı üç adet [th1, th2, th3] iş parçacığı başlatmıştır;
    • ana iş parçacığı, for döngüsünün sırasına göre her bir iş parçacığını (satır 53-54) beklemeye alır: [th1, th2, th3];
    • iş parçacıklarının [th2, th1, th3] sırasıyla sona erdiğini varsayalım;
    • ana iş parçacığı th1'in bitmesini bekler. th2 bittiğinde hiçbir şey olmaz;
    • th1 bittiğinde, ana iş parçacığı th2'yi beklemeye geçer. Ancak th2 çoktan bitmiştir. Ana iş parçacığı bu durumda bir sonraki iş parçacığına geçer ve th3'ü bekler;
    • th3 bittiğinde, ana iş parçacığı beklemeyi tamamlar ve 57. satırı çalıştırmaya geçer;
  • 57. satır, elde edilen sonuçları sonuç dosyasına yazar. Burada nesne referanslarına ilişkin iyi bir örnek var:
    • 43. satır: Bir iş parçacığına bağlı [thread_payers] listesi, [taxpayers] listesinde bulunan nesne referanslarının kopyalarını içerir;
    • Vergi hesaplamasının, [thread_payers] listesindeki referansların işaret ettiği nesneleri değiştireceği bilinmektedir. Bu nesneler, vergi hesaplamasının sonuçlarıyla zenginleştirilecektir. Bununla birlikte, referansların kendisi değiştirilmez. Dolayısıyla, başlangıçtaki [taxpayers] listesindeki referanslar, değiştirilen nesneleri “görür” veya “onlara işaret eder”;

İş parçacıkları tarafından yürütülen [thread_function] işlevi şöyledir:


# [dao] katmanının bir iş parçacığında yürütülmesi
# taxpayers, vergi mükelleflerinin listesidir
def thread_function(dao, logger, taxpayers: list):
    # iş parçacığının başlangıcına ilişkin günlük kaydı
    thread_name = threading.current_thread().name
    logger.write(f"début du thread [{thread_name}] avec {len(taxpayers)} contribuable(s)\n")
    # vergi mükelleflerinin vergisi hesaplanıyor
    for taxpayer in taxpayers:
        # günlük
        logger.write(f"début du calcul de l'impôt de {taxpayer}\n")
        # verginin eşzamanlı hesaplanması
        dao.calculate_tax(taxpayer)
        # günlük
        logger.write(f"fin du calcul de l'impôt de {taxpayer}\n")
    # iş parçacığı sonu günlüğü
    logger.write(f"fin du thread [{thread_name}]\n")
  • Birden fazla iş parçacığı tarafından eşzamanlı olarak yürütülen işlevleri yazmak genellikle zordur: Kodun, iş parçacıkları arasında paylaşılan bir veriyi değiştirmeye çalışmadığını her zaman kontrol etmek gerekir. Böyle bir durum ortaya çıktığında, değiştirilecek olan paylaşılan veriye senkronize erişim sağlanmalıdır;
  • 3. satır: İşlev üç parametre alır:
    • [dao]: [dao] katmanına bir referans. Bu veri paylaşımlıdır;
    • [logger]: logger'a bir referans. Bu veri paylaşımlıdır;
    • [taxpayers]: vergi mükelleflerinin listesi. Bu veri paylaşılmamaktadır: her iş parçacığı farklı bir listeyi yönetir;
  • [dao, logger] referanslarını inceleyelim:
    • [dao] referansının işaret ettiği nesnenin, iş parçacıkları tarafından değiştirilen ancak tüm iş parçacıkları için ortak bir değer atamak amacıyla değiştirilen [self.__logger] referansına sahip olduğunu görmüştük;
    • [logger] referansı bir dosya tanımlayıcısına işaret eder. Dosyaya günlük yazılırken bir sorun yaşanabileceğini görmüştük. Bu nedenle dosyaya yazma işlemi senkronize edilmiştir;
  • 5-6. satırlar: iş parçacığının adı ve yönetmesi gereken vergi mükelleflerinin sayısı günlüğe kaydedilir;
  • 8-14. satırlar: vergi mükelleflerinin vergisi hesaplanır;
  • 16. satır: iş parçacığının sonu günlüğe kaydedilir;

24.4.4. Çalıştırma

Bir önceki paragrafta olduğu gibi web sunucusunu başlatalım (web sunucusu, SGBD, hMailServer, Thunderbird), ardından istemcinin [main] komut dosyasını çalıştıralım. [data/output/errors.txt, data/output/résultats.json] dosyalarında önceki sürümle aynı sonuçlar elde edilir. [data/logs/logs.txt] dosyasında ise şu günlük kayıtları bulunur:


2020-07-24 10:05:20.942404, Thread-1 : début du thread [Thread-1] avec 1 contribuable(s)
2020-07-24 10:05:20.943458, Thread-1 : début du calcul de l'impôt de {"id": 1, "marié": "oui", "enfants": 2, "salaire": 55555}
2020-07-24 10:05:20.943458, Thread-2 : début du thread [Thread-2] avec 3 contribuable(s)
2020-07-24 10:05:20.946502, Thread-3 : début du thread [Thread-3] avec 1 contribuable(s)
2020-07-24 10:05:20.946502, Thread-2 : début du calcul de l'impôt de {"id": 2, "marié": "oui", "enfants": 2, "salaire": 50000}
2020-07-24 10:05:20.947003, Thread-3 : début du calcul de l'impôt de {"id": 5, "marié": "non", "enfants": 3, "salaire": 100000}
2020-07-24 10:05:20.947003, Thread-4 : début du thread [Thread-4] avec 3 contribuable(s)
2020-07-24 10:05:20.950324, Thread-4 : début du calcul de l'impôt de {"id": 6, "marié": "oui", "enfants": 3, "salaire": 100000}
2020-07-24 10:05:20.948449, Thread-5 : début du thread [Thread-5] avec 3 contribuable(s)
2020-07-24 10:05:20.953645, Thread-5 : début du calcul de l'impôt de {"id": 9, "marié": "oui", "enfants": 2, "salaire": 30000}
2020-07-24 10:05:20.976143, 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-24 10:05:20.976695, 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-24 10:05:20.976695, Thread-1 : fin du thread [Thread-1]
2020-07-24 10:05:21.973914, 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-24 10:05:21.973914, 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-24 10:05:21.973914, Thread-2 : début du calcul de l'impôt de {"id": 3, "marié": "oui", "enfants": 3, "salaire": 50000}
2020-07-24 10:05:21.977130, 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-24 10:05:21.977130, 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-24 10:05:21.977130, Thread-4 : début du calcul de l'impôt de {"id": 7, "marié": "oui", "enfants": 5, "salaire": 100000}
2020-07-24 10:05:21.982634, Thread-3 : {"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-24 10:05:21.982634, 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-24 10:05:21.983134, Thread-3 : 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-24 10:05:21.983134, 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-24 10:05:21.983134, Thread-3 : fin du thread [Thread-3]
2020-07-24 10:05:21.983763, Thread-5 : début du calcul de l'impôt de {"id": 10, "marié": "non", "enfants": 0, "salaire": 200000}
2020-07-24 10:05:22.008562, 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-24 10:05:22.008562, 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-24 10:05:22.009062, Thread-5 : début du calcul de l'impôt de {"id": 11, "marié": "oui", "enfants": 3, "salaire": 200000}
2020-07-24 10:05:22.016848, Thread-5 : {"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-24 10:05:22.017349, Thread-5 : 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-24 10:05:22.017349, Thread-5 : fin du thread [Thread-5]
2020-07-24 10:05:23.008486, Thread-2 : {"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-24 10:05:23.008486, Thread-2 : 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-24 10:05:23.009749, Thread-2 : début du calcul de l'impôt de {"id": 4, "marié": "non", "enfants": 2, "salaire": 100000}
2020-07-24 10:05:23.011722, 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-24 10:05:23.013723, 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-24 10:05:23.013723, Thread-4 : début du calcul de l'impôt de {"id": 8, "marié": "non", "enfants": 0, "salaire": 100000}
2020-07-24 10:05:23.024135, Thread-2 : {"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-24 10:05:23.024135, Thread-2 : 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-24 10:05:23.025178, Thread-2 : fin du thread [Thread-2]
2020-07-24 10:05:23.025178, Thread-4 : {"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-24 10:05:23.026191, Thread-4 : 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-24 10:05:23.026191, Thread-4 : fin du thread [Thread-4]
  • Bu günlük kayıtları, 11 vergi mükellefinin vergisini hesaplamak için beş iş parçacığının başlatıldığını göstermektedir. Bu beş iş parçacığı, vergi hesaplama sunucusuna eşzamanlı istekler göndermiştir. Bunun nasıl çalıştığını anlamak gerekir:
    • [Thread-1] iş parçacığı ilk olarak başlatılır. İşlemciyi aldığında, kodda ilerleyerek HTTP isteğini gönderir. Bu isteğin sonucunu beklemesi gerektiğinden, otomatik olarak bekleme durumuna geçirilir. Böylece işlemciyi kaybeder ve başka bir iş parçacığı işlemciyi alır;
    • 1-10. satırlar: Aynı süreç 5 iş parçacığının her biri için tekrarlanır. Böylece, [Thread-1] iş parçacığı yanıtını almadan önce 5 iş parçacığı da başlatılır (11. satır);
  • iş parçacıkları başlatıldıkları sırayla tamamlanmaz. Böylece, ilk olarak [Thread-3] iş parçacığı tamamlanır (satır 23);

Sunucu tarafında, [data/logs/logs.txt] dosyasındaki günlükler şu şekildedir:


2020-07-24 10:05:01.692980, MainThread : [serveur] démarrage du serveur
2020-07-24 10:05:01.877251, MainThread : [serveur] connexion à la base de données réussie
2020-07-24 10:05:03.596162, MainThread : [serveur] démarrage du serveur
2020-07-24 10:05:03.661160, MainThread : [serveur] connexion à la base de données réussie
2020-07-24 10:05:20.968053, Thread-2 : [index] requête : <Request 'http://127.0.0.1:5000/?evli=evet&çocuk=2&maaş=50000' [GET]>
2020-07-24 10:05:20.969132, Thread-2 : [index] mis en pause du thread pendant 1 seconde(s)
2020-07-24 10:05:20.970316, Thread-3 : [index] requête : <Request 'http://127.0.0.1:5000/?evli=evet&çocuk=3&maaş=100000' [GET]>
2020-07-24 10:05:20.970316, Thread-3 : [index] mis en pause du thread pendant 1 seconde(s)
2020-07-24 10:05:20.971335, Thread-4 : [index] requête : <Request 'http://127.0.0.1:5000/?evli=evet&çocuk=2&maaş=55555' [GET]>
2020-07-24 10:05:20.972563, Thread-4 : [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-24 10:05:20.974796, Thread-5 : [index] requête : <Request 'http://127.0.0.1:5000/?evli=hayır&çocuk=3&maaş=100000' [GET]>
2020-07-24 10:05:20.974796, Thread-5 : [index] mis en pause du thread pendant 1 seconde(s)
2020-07-24 10:05:20.976143, Thread-6 : [index] requête : <Request 'http://127.0.0.1:5000/?evli=evet&çocuk=2&maaş=30000' [GET]>
2020-07-24 10:05:20.976143, Thread-6 : [index] mis en pause du thread pendant 1 seconde(s)
2020-07-24 10:05:21.970615, Thread-2 : [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-24 10:05:21.973914, Thread-3 : [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-24 10:05:21.977130, Thread-6 : [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-24 10:05:21.977130, Thread-5 : [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-24 10:05:22.001693, Thread-7 : [index] requête : <Request 'http://127.0.0.1:5000/?evli=evet&çocuk=3&maaş=50000' [GET]>
2020-07-24 10:05:22.003013, Thread-7 : [index] mis en pause du thread pendant 1 seconde(s)
2020-07-24 10:05:22.003013, Thread-8 : [index] requête : <Request 'http://127.0.0.1:5000/?evli=evet&çocuk=5&maaş=100000' [GET]>
2020-07-24 10:05:22.003013, Thread-8 : [index] mis en pause du thread pendant 1 seconde(s)
2020-07-24 10:05:22.005871, Thread-9 : [index] requête : <Request 'http://127.0.0.1:5000/?evli=hayır&çocuk=0&maaş=200000' [GET]>
2020-07-24 10:05:22.006370, Thread-9 : [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-24 10:05:22.014170, Thread-10 : [index] requête : <Request 'http://127.0.0.1:5000/?evli=evet&çocuk=3&maaş=200000' [GET]>
2020-07-24 10:05:22.014170, Thread-10 : [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-24 10:05:23.003533, Thread-7 : [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-24 10:05:23.006434, Thread-8 : [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}}}
2020-07-24 10:05:23.018026, Thread-11 : [index] requête : <Request 'http://127.0.0.1:5000/?evli=hayır&çocuk=2&maaş=100000' [GET]>
2020-07-24 10:05:23.019074, Thread-11 : [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-24 10:05:23.021447, Thread-12 : [index] requête : <Request 'http://127.0.0.1:5000/?evli=hayır&çocuk=0&maaş=100000' [GET]>
2020-07-24 10:05:23.022447, Thread-12 : [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}}}
  • 11 iş parçacığının 11 vergi mükellefini işlediği görülüyor;
  • bazı iş parçacıkları beklemeye alınmış (6, 8, 12, 14, 20, 22. satırlar), diğerleri ise alınmamıştır (9, 23, 25, 29, 31. satırlar);

24.5. [dao] katmanının testleri

|version précédente|'te yaptığımız gibi, müşterinin [dao] katmanını test ediyoruz. Prensip tamamen aynıdır:

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"])
    # bu yapılandırmada kaydedilir
    config["logger"] = logger
    # [dao] katmanını alıyoruz
    dao = config["layers"]["dao"]

    # 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/02/tests/TestHttpClientDao.py
tests en cours...
...........
----------------------------------------------------------------------
Ran 11 tests in 6.128s

OK

Process finished with exit code 0