Skip to content

20. Uygulama alıştırması: sürüm 5

Image

Üç uygulama geliştireceğiz:

  • Uygulama 1, sürüm 4'teki [admindata.json] dosyasının yerini alacak veritabanını başlatacak;
  • uygulama 2, vergi hesaplamasını toplu iş modunda gerçekleştirecek;
  • Uygulama 3, vergileri etkileşimli modda hesaplayacaktır;

20.1. Uygulama 1: veritabanının başlatılması

Uygulama 1 şu mimariye sahip olacaktır:

Image

Bu, sürüm 4'ün mimarisinin (|Version 4| paragrafı) bir geliştirilmesidir: vergi verileri, jSON dosyasında değil, bir veritabanında bulunacaktır. [dao] katmanı, bu değişikliği uygulamak üzere güncellenecektir.

20.1.1. [admindata.json] dosyası

Image

[admindata.json] dosyası, sürüm 4'teki haliyle şöyledir:


{
    "limites": [9964, 27519, 73779, 156244, 0],
    "coeffr": [0, 0.14, 0.3, 0.41, 0.45],
    "coeffn": [0, 1394.96, 5798, 13913.69, 20163.45],
    "plafond_qf_demi_part": 1551,
    "plafond_revenus_celibataire_pour_reduction": 21037,
    "plafond_revenus_couple_pour_reduction": 42074,
    "valeur_reduc_demi_part": 3797,
    "plafond_decote_celibataire": 1196,
    "plafond_decote_couple": 1970,
    "plafond_impot_couple_pour_decote": 2627,
    "plafond_impot_celibataire_pour_decote": 1595,
    "abattement_dixpourcent_max": 12502,
    "abattement_dixpourcent_min": 437
}

Veritabanının sütunları olarak bu sözlüğün anahtarlarını kullanacağız.

20.1.2. Veritabanlarının oluşturulması

|création d’une base de données MySQL| paragrafında gösterildiği gibi, MySQL adlı bir veritabanı oluşturuyoruz; bu veritabanının adı [dbimpots-2019], sahibi [admimpots] ve şifresi [mdpimpots]'tir. [phpMyAdmin]'te bu, şu sonucu verir:

Image

Aynı şekilde, |création d’une base de données PostgreSQL| paragrafında gösterildiği gibi, PostgreSQL adlı bir veritabanı oluşturuyoruz; bu veritabanının adı [dbimpots-2019], sahibi [admimpots] ve şifresi [mdpimpots]'tir. [pgAdmin]'te bu, aşağıdaki gibi görünür:

Image

Veritabanları oluşturuldu ancak şimdilik hiçbir tablo içermiyorlar. Bu tablolar ORM ve [sqlalchemy] tarafından oluşturulacak.

20.1.3. [sqlalchemy] tarafından eşlenen varlıklar

[admindata.json] verilerini barındırmak üzere iki tablo oluşturacağız:

[sqlalchemy] tarafından tanımlanan [tbtranches] tablosu, [admindata.json] sözlüğündeki [limites, coeffr, coeffn] tablolarının verilerini bir araya getirecektir:


    # vergi dilimleri tablosu
    tranches_table = Table("tbtranches", metadata,
                           Column('id', Integer, primary_key=True),
                           Column('limite', Float, nullable=False),
                           Column('coeffr', Float, nullable=False),
                           Column('coeffn', Float, nullable=False)
                           )

[sqlalchemy] tarafından tanımlanan [tbconstantes] tablosu, [admindata.json] sözlüğündeki sabitleri bir araya getirecektir:


    # sabitler tablosu
    constantes_table = Table("tbconstantes", metadata,
                             Column('id', Integer, primary_key=True),
                             Column('plafond_qf_demi_part', Float, nullable=False),
                             Column('plafond_revenus_celibataire_pour_reduction', Float, nullable=False),
                             Column('plafond_revenus_couple_pour_reduction', Float, nullable=False),
                             Column('valeur_reduc_demi_part', Float, nullable=False),
                             Column('plafond_decote_celibataire', Float, nullable=False),
                             Column('plafond_decote_couple', Float, nullable=False),
                             Column('plafond_impot_celibataire_pour_decote', Float, nullable=False),
                             Column('plafond_impot_couple_pour_decote', Float, nullable=False),
                             Column('abattement_dixpourcent_max', Float, nullable=False),
                             Column('abattement_dixpourcent_min', Float, nullable=False)
                             )

Bu iki tablo ile eşleştirilecek varlıklar şunlardır:

Image

[Constantes] varlığı, [admindata.json] sözlüğündeki sabitleri kapsar:


from BaseEntity import BaseEntity


# vergi idaresi verilerinin konteyner sınıfı
class Constantes(BaseEntity):
    # sınıf durumundan hariç tutulan anahtarlar
    excluded_keys = ["_sa_instance_state"]

    # izin verilen anahtarlar
    @staticmethod
    def get_allowed_keys() -> list:
        return ["id",
                "plafond_qf_demi_part",
                "plafond_revenus_celibataire_pour_reduction",
                "plafond_revenus_couple_pour_reduction",
                "valeur_reduc_demi_part",
                "plafond_decote_celibataire",
                "plafond_decote_couple",
                "plafond_decote_couple",
                "plafond_impot_celibataire_pour_decote",
                "plafond_impot_couple_pour_decote",
                "abattement_dixpourcent_max",
                "abattement_dixpourcent_min"]
    1. satır: [Constantes] sınıfı, [BaseEntity] sınıfını genişletir;
    1. satır: [sqlalchemy] eşlemesi sayesinde, [Constante] sınıfı [_sa_instance_state] özelliğini alacaktır. Bunu, varlığın [asdict] sözlüğünden hariç tutuyoruz;
  • satır 11-23: varlığın özellikleri. Kod yazımını kolaylaştırmak için [admindata.json] sözlüğünde kullanılan isimler kullanılmıştır;

[Tranche] varlığı, [admindata.json] sözlüğündeki üç [limites, coeffr, coeffn] tablosundan bir satırı kapsular:


from BaseEntity import BaseEntity


# vergi idaresi verilerini içeren sınıf
class Tranche(BaseEntity):
    # sınıf durumundan hariç tutulan anahtarlar
    excluded_keys = ["_sa_instance_state"]

    # izin verilen anahtarlar
    @staticmethod
    def get_allowed_keys() -> list:
        return ["id", "limite", "coeffr", "coeffn"]
    1. satır: [Tranche] sınıfı, [BaseEntity] sınıfını genişletir;
    1. satır: [sqlalchemy] tarafından eklenen [_sa_instance_state] özelliği, varlığın [asdict] sözlüğündeki özelliklerinden çıkarılır;
  • 10-12. satırlar: sınıfın özellikleri;

[Constantes, Tranche] varlıkları ile [constantes, tranches] tabloları arasındaki eşleme şu şekilde olacaktır:

Image



    # sabitler tablosu
    constantes_table = Table("tbconstantes", metadata,
                             Column('id', Integer, primary_key=True),
                             Column('plafond_qf_demi_part', Float, nullable=False),
                             Column('plafond_revenus_celibataire_pour_reduction', Float, nullable=False),
                             Column('plafond_revenus_couple_pour_reduction', Float, nullable=False),
                             Column('valeur_reduc_demi_part', Float, nullable=False),
                             Column('plafond_decote_celibataire', Float, nullable=False),
                             Column('plafond_decote_couple', Float, nullable=False),
                             Column('plafond_impot_celibataire_pour_decote', Float, nullable=False),
                             Column('plafond_impot_couple_pour_decote', Float, nullable=False),
                             Column('abattement_dixpourcent_max', Float, nullable=False),
                             Column('abattement_dixpourcent_min', Float, nullable=False)
                             )

    # vergi dilimleri tablosu
    tranches_table = Table("tbtranches", metadata,
                           Column('id', Integer, primary_key=True),
                           Column('limite', Float, nullable=False),
                           Column('coeffr', Float, nullable=False),
                           Column('coeffn', Float, nullable=False)
                           )
    # eşlemeler
    from Tranche import Tranche
    mapper(Tranche, tranches_table)

    from Constantes import Constantes
    mapper(Constantes, constantes_table)
  • eşlemeler 24-29. satırlarda yapılmıştır. Burada, eşlenen varlıkların özellikleri ile veritabanı tabloları arasındaki eşleştirmeler yapılmamıştır. Bu, tablo sütunlarının adları, eşleştirilmeleri gereken özelliklerin adlarıyla aynı olduğunda mümkündür. Bu nedenle, eşlenen varlıkların özellik adlarını tablolara da ekledik. Bu, kodun yazılmasını ve anlaşılmasını kolaylaştırır;

20.1.4. [sqlalchemy] yapılandırma dosyası

Image

Az önce [sqlalchemy] yapılandırmasının bir kısmını ayrıntılı olarak açıkladık. [config_database] dosyasının tamamı şu şekildedir:


def configure(config: dict) -> dict:
    # SQLAlchemy yapılandırması
    from sqlalchemy import create_engine, Table, Column, Integer, MetaData, Float
    from sqlalchemy.orm import mapper, sessionmaker

    # kullanılan veritabanlarına bağlantı dizeleri
    connection_strings = {
        'mysql': "mysql+mysqlconnector://admimpots:mdpimpots@localhost/dbimpots-2019",
        'pgres': "postgresql+psycopg2://admimpots:mdpimpots@localhost/dbimpots-2019"
    }
    # kullanılan veritabanına bağlantı dizesi
    engine = create_engine(connection_strings[config['sgbd']])

    # meta veriler
    metadata = MetaData()

    # sabitler tablosu
    constantes_table = Table("tbconstantes", metadata,
                             Column('id', Integer, primary_key=True),
                             Column('plafond_qf_demi_part', Float, nullable=False),
                             Column('plafond_revenus_celibataire_pour_reduction', Float, nullable=False),
                             Column('plafond_revenus_couple_pour_reduction', Float, nullable=False),
                             Column('valeur_reduc_demi_part', Float, nullable=False),
                             Column('plafond_decote_celibataire', Float, nullable=False),
                             Column('plafond_decote_couple', Float, nullable=False),
                             Column('plafond_impot_celibataire_pour_decote', Float, nullable=False),
                             Column('plafond_impot_couple_pour_decote', Float, nullable=False),
                             Column('abattement_dixpourcent_max', Float, nullable=False),
                             Column('abattement_dixpourcent_min', Float, nullable=False)
                             )

    # vergi dilimleri tablosu
    tranches_table = Table("tbtranches", metadata,
                           Column('id', Integer, primary_key=True),
                           Column('limite', Float, nullable=False),
                           Column('coeffr', Float, nullable=False),
                           Column('coeffn', Float, nullable=False)
                           )
    # eşlemeler
    from Tranche import Tranche
    mapper(Tranche, tranches_table)

    from Constantes import Constantes
    mapper(Constantes, constantes_table)

    # oturum fabrikası
    session_factory = sessionmaker()
    session_factory.configure(bind=engine)

    # bir oturum
    session = session_factory()

    # bazı bilgiler kaydedilir
    config['database'] = {"engine": engine, "metadata": metadata, "tranches_table": tranches_table,
                          "constantes_table": constantes_table, "session": session}

    # sonuç
    return config
    1. satır: [configure] işlevi, parametre olarak bir sözlük alır; bu sözlüğün [sgbd] anahtarı, hangi SGBD işlevinin kullanılacağını belirtir: MySQL (mysql) veya PostgreSQL (pgres);
  • 6-12. satırlar: yapılandırmada belirtilen veritabanı seçilir;
  • 14-44. satırlar: varlıklar / tablolar arası eşlemeler. Bu eşlemeler basittir çünkü [tranches] ve [constantes] tabloları arasında hiçbir bağlantı yoktur. Bu tablolar birbirinden bağımsızdır. Dolayısıyla, birinin diğerine ait yabancı anahtarları yönetmek gerekmez;
  • satır 46-51: Uygulamanın çalışma oturumu olan [session] oluşturulur;
  • satır 53-58: gerekli bilgiler yapılandırma sözlüğüne eklenir ve sözlük döndürülür;

20.1.5. [dao] katmanı

Oluşturulacak Uygulama 1’in mimarisine geri dönelim:

Image

[dao] [1] katmanı, [admindata.json] [2] dosyasını okumalı ve içeriğini [3, 4] veritabanlarından birine aktarmalıdır;

Image

[dao] katmanı, [1] arayüzünü sunar ve [2] sınıfı tarafından uygulanır.

[InterfaceDao4TransferAdminData2Database] arayüzü şu şekildedir:


# ithalatlar
from abc import ABC, abstractmethod


# arayüz InterfaceImpôtsUI
class InterfaceDao4TransferAdminData2Database(ABC):
    # vergi verilerinin bir veritabanına aktarılması
    @abstractmethod
    def transfer_admindata_in_database(self:object):
        pass
  • 8-10. satırlar: Arayüz, parametresiz tek bir [transfer_admindata_in_database] yöntemi sunar. Bu yöntemin parametrelere ihtiyacı olduğundan (hangi dosya?, hangi veritabanı?), bu parametrelerin bu arayüzü uygulayan sınıfların oluşturucusuna aktarılacağı anlamına gelir;

[DaoTransferAdminDataFromJsonFile2Database] sınıfı, [InterfaceDao4TransferAdminData2Database] arayüzünü şu şekilde uygular:


# ithalat
import codecs
import json

from sqlalchemy.exc import DatabaseError, IntegrityError, InterfaceError

from Constantes import Constantes
from ImpôtsError import ImpôtsError
from InterfaceDao4TransferAdminData2Database import InterfaceDao4TransferAdminData2Database
from Tranche import Tranche


class DaoTransferAdminDataFromJsonFile2Database(InterfaceDao4TransferAdminData2Database):

    # üretici
    def __init__(self, config: dict):
        self.config = config

    # aktarım
    def transfer_admindata_in_database(self) -> None:
        # başlatma işlemleri
        session = None
        config = self.config

        try:
            # vergi idaresinden verilerin alınması
            with codecs.open(config["admindataFilename"], "r", "utf8") as fd:
                # içeriğin bir sözlüğe aktarılması
                admindata = json.load(fd)

            # veritabanı yapılandırmasının alınması
            database = config["database"]

            # veritabanındaki iki tablonun silinmesi
            # checkfirst=True: önce tablonun varlığını kontrol eder
            database["tranches_table"].drop(database["engine"], checkfirst=True)
            database["constantes_table"].drop(database["engine"], checkfirst=True)

            # eşlemelerden tabloların yeniden oluşturulması
            database["metadata"].create_all(database["engine"])

            # geçerli [sqlalchemy] oturumu
            session = database["session"]

            # vergi dilimleri tablosu doldurulur
            limites = admindata["limites"]
            coeffr = admindata["coeffr"]
            coeffn = admindata["coeffn"]
            for i in range(len(limites)):
                session.add(Tranche().fromdict(
                    {"limite": limites[i], "coeffr": coeffr[i], "coeffn": coeffn[i]}))
            # sabitler tablosu dolduruluyor
            session.add(Constantes().fromdict({
                'plafond_qf_demi_part': admindata["plafond_qf_demi_part"],
                'plafond_revenus_celibataire_pour_reduction': admindata["plafond_revenus_celibataire_pour_reduction"],
                'plafond_revenus_couple_pour_reduction': admindata["plafond_revenus_couple_pour_reduction"],
                'valeur_reduc_demi_part': admindata["valeur_reduc_demi_part"],
                'plafond_decote_celibataire': admindata["plafond_decote_celibataire"],
                'plafond_decote_couple': admindata["plafond_decote_couple"],
                'plafond_impot_celibataire_pour_decote': admindata["plafond_impot_celibataire_pour_decote"],
                'plafond_impot_couple_pour_decote': admindata["plafond_impot_couple_pour_decote"],
                'abattement_dixpourcent_max': admindata["abattement_dixpourcent_max"],
                'abattement_dixpourcent_min': admindata["abattement_dixpourcent_min"]
            }))

            # oturum doğrulama [sqlalchemy]
            session.commit()
        except (IntegrityError, DatabaseError, InterfaceError) as erreur:
            # istisna başka bir biçimde yeniden tetiklenir
            raise ImpôtsError(17, f"{erreur}")
        finally:
            # oturum kaynakları serbest bırakılıyor
            if session:
                session.close()
    1. satır: [DaoTransferAdminDataFromJsonFile2Database] sınıfı, [InterfaceDao4TransferAdminData2Database] arayüzünü uygular;
  • 15-17. satırlar: Sınıfın oluşturucusu, yapılandırma sözlüğünü parametre olarak alır. Aşağıdaki anahtarlar kullanılacaktır:
    • [admindataFilename] (satır 27): veritabanına aktarılacak vergi idaresi verilerini içeren jSON dosyasının adı;
    • [database] satır 32: uygulamanın [sqlalchemy] yapılandırması;
  • satır 34-37: [constantes] ve [tranches] tablolarının, varsa silinmesi;
  • satır 39-40: iki tablonun yeniden oluşturulması;
    1. satır: yapılandırmada bulunan [sqlalchemy] oturumu alınır;
  • satır 45-51: [admindata] sözlüğündeki [limites, coeffr, coeffn] tabloları oturuma eklenir. Bunun için [Tranche] varlığının örnekleri oturuma eklenir;
  • satır 52-64: [Constantes] varlığının bir örneği oturuma eklenir;
  • 66-67. satırlar: oturum onaylanır. Oturum verileri henüz veritabanında değilse, bu aşamada veritabanına eklenir;
  • satır 68-70: Olası bir hatanın yönetimi;
  • satır 71-74: oturum kapatılır. [dao] katmanı yalnızca bir kez kullanıldığından bu işlem yapılabilir;

20.1.6. Uygulamanın yapılandırılması

Image

Uygulama, üç [1] dosyası ile yapılandırılır:

  • [config] genel yapılandırma dosyasıdır. [main] uygulamasını yapılandıran bu dosyadır. Bu dosya, incelediğimiz ve ORM dosyasını yapılandıran diğer iki dosyanın da yardımını alır:
    • Daha önce incelediğimiz ve ORM ile [sqlalchemy] dosyalarını yapılandıran [config_database];
    • uygulamanın katmanlarını yapılandıran [config_layers];

[config] dosyası şu şekildedir:


def configure(config: dict) -> dict:
    # [config], değeri şu olan [sgbd] anahtarına sahiptir:
    # [mysql], MySQL veritabanını yönetmek için
    # [pgres], PostgreSQL veritabanını yönetmek için

    import os

    # 1. adım ---
    # uygulamanın Python yolunu ayarlıyoruz

    # bu komut dosyasının klasörünün mutlak yolu
    script_dir = os.path.dirname(os.path.abspath(__file__))

    # root_dir (gerekirse değiştirin)
    root_dir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020"

    # bağımlılıkların mutlak yolları
    absolute_dependencies = [
        # 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",
        # AdminData, ImpôtsError, TaxPayer
        f"{root_dir}/impots/v04/entities",
        # BaseEntity, MyException
        f"{root_dir}/classes/02/entities",
        # yerel klasörler
        f"{script_dir}",
        f"{script_dir}/../../interfaces",
        f"{script_dir}/../../services",
        f"{script_dir}/../../entities",
    ]

    # syspath ayarlanıyor
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    # 2. adım ------
    # uygulama yapılandırması tamamlanıyor
    config.update({
        # veri dosyalarının mutlak yolları
        "admindataFilename"f"{script_dir}/../../data/input/admindata.json"
    })

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

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

    # yapılandırmayı geri yükleme
    return config
  • 8-36. satırlar: Uygulamanın Python Yolu oluşturulur;
  • 38-43. satırlar: [admindata.json] dosyasının yolu yapılandırmaya eklenir;
  • 45-48. satırlar: [sqlalchemy] yapılandırması;
  • 50-53. satırlar: Uygulamanın katmanları oluşturulur;
  • satır 56: genel yapılandırma geri verilir;

[config_layers] dosyası şöyledir:


def configure(config: dict) -> dict:
    # [dao] katmanının örneklenmesi
    from DaoTransferAdminDataFromJsonFile2Database import DaoTransferAdminDataFromJsonFile2Database
    config['dao'] = DaoTransferAdminDataFromJsonFile2Database(config)

    # yapılandırma döndürülüyor
    return config
  • satır 3-4: [dao] katmanının örneklenmesi. [DaoTransferAdminDataFromJsonFile2Database] sınıfının oluşturucusunun, parametre olarak uygulamanın genel yapılandırma sözlüğünü beklediğini görmüştük;
    1. satır: [dao] katmanına ilişkin referans yapılandırmaya eklenir;
    1. satır: yapılandırma geri verilir;

20.1.7. Uygulamanın [main] komut dosyası

Image

Image

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


# mysql veya pgres parametresi bekleniyor
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})

# syspath belirlendi - içe aktarmalar yapılabilir
from ImpôtsError import ImpôtsError

# katman alınır [dao]
dao = config["dao"]

# kod
try:
    # verilerin veritabanına aktarılması
    dao.transfer_admindata_in_database()
except ImpôtsError as ex1:
    # hata mesajı görüntüleniyor
    print(f"L'erreur 1 suivante s'est produite : {ex1}")
except BaseException as ex2:
    # hata görüntüleniyor
    print(f"L'erreur 2 suivante s'est produite : {ex2}")
finally:
    # son
    print("Terminé...")
  • 1-10. satırlar: bir parametre beklenir. Parametrenin mevcut ve doğru olup olmadığı kontrol edilir;
  • 12-14. satırlar: Seçilen SGBD türünü parametre olarak geçirerek uygulamayı (genel, SQLAlchemy, katmanlar) yapılandırıyoruz;
  • 19-20. satırlar: [dao] katmanına ihtiyacımız olacak. Bu katmanı alıyoruz;
    1. satır: Veritabanına aktarım yapılır. [transfer_admindata_in_database] yöntemine gerekli tüm bilgiler, 20. satırdaki [dao] katmanının özelliklerinde mevcuttur. Yöntem, bu bilgileri buradan alacaktır;

MySQL veritabanıyla çalıştırıldıktan sonra, veritabanı aşağıdaki öğeleri içerir (phpMyAdmin):

Image

Image

Image

[3] sütununda, MySQL tarafından birincil anahtar [id]'e atanan değerler görülmektedir. Numaralandırma 1'den başlar. Yukarıdaki ekran görüntüsü, komut dosyasının birkaç kez çalıştırılmasından sonra elde edilmiştir.

Image

Image

PostgreSQL veritabanında sonuçlar şu şekildedir:

Image

  • [1] üzerine sağ tıklayıp ardından [2-3]'e tıklayın;
  • [4]'te vergi dilimlerine ait veriler açıkça görülüyor;

Aynı işlemi [tbconstantes] sabit tablosu için de tekrarlayalım:

Image

Image

Image

20.2. Uygulama 2: Toplu iş modunda vergi hesaplaması

Image

20.2.1. Mimari

Sürüm 4'teki vergi hesaplama uygulaması aşağıdaki mimariyi kullanıyordu:

Image

[dao] katmanı, [InterfaceImpôtsDao] arayüzünü uygular. Bu arayüzü uygulayan bir sınıf oluşturduk:

  • [ImpôtsDaoWithAdminDataInJsonFile]; bu sınıf, vergi verilerini jSON dosyasından alıyordu. Bu, sürüm 3’tü;

[InterfaceImpôtsDao] arayüzünü, vergi idaresine ait verileri bir veritabanından alacak olan yeni bir [ImpotsDaoWithTaxAdminDataInDatabase] sınıfı aracılığıyla uygulayacağız. [dao] katmanı, daha önce olduğu gibi, sonuçları bir jSON dosyasına yazacak ve vergi mükelleflerinin verilerini bir metin dosyasından alacaktır. [InterfaceImpôtsDao] arayüzüne uymaya devam edersek, [métier] katmanının değiştirilmesine gerek kalmayacağını biliyoruz.

Yeni mimari şu şekilde olacaktır:

Image

20.2.2. Uygulama yapılandırması

Image

[config_database] yapılandırma dosyası, Uygulama 1’deki haliyle kalır. [config] yapılandırması ise yeni öğeler içerir:


    # 2. adım ------
    # uygulama yapılandırması tamamlanıyor
    config.update({
        # veri dosyalarının mutlak yolları
        "admindataFilename": f"{script_dir}/../../data/input/admindata.json",
        "taxpayersFilename": f"{script_dir}/../../data/input/taxpayersdata.txt",
        "errorsFilename": f"{script_dir}/../../data/output/errors.txt",
        "resultsFilename": f"{script_dir}/../../data/output/résultats.json"
    })
  • 6-8. satırlar: Uygulama 2 tarafından kullanılan metin dosyalarının mutlak yolları;

[config_layers] katman yapılandırması şu şekilde değişmektedir:


def configure(config: dict) -> dict:
    # DAO katmanının örneklenmesi
    from ImpotsDaoWithAdminDataInDatabase import ImpotsDaoWithAdminDataInDatabase
    config["dao"] = ImpotsDaoWithAdminDataInDatabase(config)

    # [métier] katmanının örneklenmesi
    from ImpôtsMétier import ImpôtsMétier
    config['métier'] = ImpôtsMétier()

    # yapılandırma geri yükleniyor
    return config
  • 3-4. satırlar: [dao] katmanı artık [ImpotsDaoWithAdminDataInDatabase] sınıfı tarafından uygulanmaktadır. Bu sınıf yenidir ancak uygulama alıştırmasının 4. sürümünde olduğu gibi aynı [InterfaceDao] arayüzünü uygulamaktadır;
  • 7-8. satırlar: [métier] katmanı, [ImpôtsMétier] sınıfı tarafından uygulanmaktadır. Bu, uygulama alıştırmasının 4. sürümünde kullanılan sınıftır;

20.2.3. [dao] katmanı

Image

[InterfaceImpôtsDao] arayüzünün [ImpotsDaoWithAdminDataInDatabase] uygulama sınıfı şu şekilde olacaktır:


# ithalatlar
from sqlalchemy.exc import DatabaseError, IntegrityError, InterfaceError

from AbstractImpôtsDao import AbstractImpôtsDao
from AdminData import AdminData
from Constantes import Constantes
from ImpôtsError import ImpôtsError
from Tranche import Tranche


class ImpotsDaoWithAdminDataInDatabase(AbstractImpôtsDao):
    # yapıcı
    def __init__(self, config: dict):
        # config["taxPayersFilename"] : vergi mükelleflerinin bulunduğu metin dosyasının adı
        # yapılandırma["taxPayersResultsFilename"] : sonuçların jSON dosya adı
        # config["errorsFilename"] : taxPayersFilename dosyasında bulunan hataları kaydeder
        # config["database"] : veritabanı yapılandırması

        # Parent sınıfının başlatılması
        AbstractImpôtsDao.__init__(self, config)
        # parametre kaydetme
        self.__config = config
        # admindata
        self.__admindata = None

    # arayüzün uygulanması
    def get_admindata(self):
        # admindata kaydedildi mi?
        if self.__admindata:
            return self.__admindata
        # BD'te bir sorgu yapılıyor
        session = None
        config = self.__config
        try:
            # bir oturum
            database_config = config["database"]
            session = database_config["session"]

            # vergi dilimleri tablosu okunuyor
            tranches = session.query(Tranche).all()

            # sabitler tablosu okunuyor (tek satır)
            constantes = session.query(Constantes).first()

            # admindata örneği oluşturuluyor
            admindata = AdminData()
            # burada sınır tabloları oluşturulur: coeffR, coeffN
            limites = admindata.limites = []
            coeffr = admindata.coeffr = []
            coeffn = admindata.coeffn = []
            for tranche in tranches:
                limites.append(float(tranche.limite))
                coeffr.append(float(tranche.coeffr))
                coeffn.append(float(tranche.coeffn))
            # buraya sabitler eklenir
            admindata.fromdict(constantes.asdict())
            # admindata'yı kaydediyoruz
            self.__admindata = admindata
            # değeri döndürülür
            return self.__admindata
        except (IntegrityError, DatabaseError, InterfaceError) as erreur:
            # istisnayı başka bir biçimde yeniden tetiklenir
            raise ImpôtsError(27, f"{erreur}")
        finally:
            # oturumu kapatıyoruz
            if session:
                session.close()

Notlar

    1. satır: [ImpotsDaoWithAdminDataInDatabase] sınıfı, sürüm 4'te sunulan [AbstractImpôtsDao] sınıfından miras almaktadır. Bu sınıfın, aynı sürümde sunulan [InterfaceDao] arayüzünü uyguladığı bilinmektedir. Bu arayüze uyum sağlanması sayesinde [métier] katmanını değiştirmemize gerek kalmaz;
    1. satır: Sınıfın oluşturucusu, parametre olarak uygulama yapılandırma sözlüğünü alır;
    1. satır: üst sınıf [] başlatılır. Bu sınıf, [InterfaceDao] arayüzünü kısmen uygular:
    2. [get_taxpayers_data], vergi mükelleflerinin verilerini içeren [taxpayersdata.txt] dosyasını okur;
    3. [write_taxpayers_results], sonuçları jSON ve [résultats.json] dosyalarına yazar;
    4. [get_admindata] dosyası uygulanmamıştır;
    1. satır: parametre olarak geçirilen yapılandırma kaydedilir;
    1. satır: [InterfaceDao] arayüzünün [get_admindata] yönteminin uygulanması:
  • 28-30. satırlar: [get_admindata] yöntemi, vergi idaresinden gelen verileri [AdminData] türünde bir nesneye alır ve bu nesneyi [self.__admindata]'te saklar. [get_admindata] yöntemi birden fazla kez çağrılırsa, veritabanına birden fazla kez sorgu yapılmaz. Veritabanına yalnızca ilk seferde sorgu yapılır. Sonraki seferlerde [self.__admindata] nesnesi döndürülür;
  • 36-37. satırlar: [config_database] tarafından uygulamanın yapılandırılması sırasında oluşturulan [sqlalchemy] oturumu alınır;
  • satır 40: vergi dilimlerini bir listeye alınır;
    1. satır: Vergi hesaplaması sabitleri alınır;
  • satır 46: [AdminData] sınıfının bir örneği oluşturulur. Bu sınıfın [BaseEntity] sınıfından türediği hatırlatılmalıdır;
  • 48-54. satırlar: [AdminData] örneğinin [limites, coeffr, coeffn] dizileri başlatılır;
  • 55-56. satırlar: [AdminData] sınıfının diğer özellikleri, vergi hesaplamasına ait sabitlerle başlatılır. [AdminData] ve [Constantes] sınıflarının özelliklerine aynı isimler verilmesine özen gösterilmişti; bu da kodu basitleştirir;
  • satır 57-58: [AdminData] örneği, [dao] katmanında saklanır; böylece [get_admindata] yöntemine yapılacak sonraki çağrılarda geri döndürülebilir;
    1. satır: çağıran kod tarafından istenen değer döndürülür;
  • satır 61-63: Olası bir hatanın yönetimi;
  • 64-67. satırlar: Veritabanına yalnızca tek bir sorgu gönderilir. Dolayısıyla [sqlalchemy] oturumu kapatılabilir;

20.2.4. [dao] katmanının testi

Bu uygulamanın 4. sürümünde, [métier] katmanını test eden bir sınıf oluşturmuştuk. Daha doğrusu, bu sınıf hem [métier] hem de [dao] katmanlarını test ediyordu. [dao] katmanının beklendiği gibi çalıştığını doğrulamak için bu testi tekrar kullanıyoruz. Nitekim, [métier] katmanında herhangi bir değişiklik yapılmamıştır.

Image

Image

[TestDaoMétier] testi şu şekildedir:


import unittest


class TestDaoMétier(unittest.TestCase):

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

        # {'evli': 'evet', 'çocuk': 2, 'maaş': 55555,
        # 'vergi': 2814, 'ek vergi': 0, 'indirim': 0, 'azaltma': 0, 'oran': 0,14}
        taxpayer = TaxPayer().fromdict({"marié": "oui", "enfants": 2, "salaire": 55555})
        métier.calculate_tax(taxpayer, admindata)
        # 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)

    

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

        # {'evli': 'evet', 'çocuk': 3, 'maaş': 200000,
        # 'vergi': 42842, 'ek ücret': 17283, 'indirim': 0, 'azaltma': 0, 'oran': 0.41}
        taxpayer = TaxPayer().fromdict({'marié': 'oui', 'enfants': 3, 'salaire': 200000})
        métier.calculate_tax(taxpayer, admindata)
        # kontroller
        self.assertAlmostEqual(taxpayer.impôt, 42842, 1)
        self.assertEqual(taxpayer.décôte, 0)
        self.assertEqual(taxpayer.réduction, 0)
        self.assertAlmostEqual(taxpayer.taux, 0.41, delta=0.01)
        self.assertAlmostEqual(taxpayer.surcôte, 17283, delta=1)


if __name__ == '__main__':
    # mysql veya pgres parametresi bekleniyor
    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})
    # iş mantığı katmanı
    métier = config['métier']
    try:
        # admindata
        admindata = config['dao'].get_admindata()
    except BaseException as ex:
        # görüntüleme
        print((f"L'erreur suivante s'est produite : {ex}"))
        # son
        sys.exit()
    # komut dosyası tarafından alınan parametreyi silinir
    sys.argv.pop()
    # test yöntemleri çalıştırılır
    print("tests en cours...")
   unittest.main()
  • |[métier] katmanı, sürüm 4 testi| başlıklı paragrafta açıklanan 11 teste tekrar değinmeyeceğiz;
  • 37-66. satırlar: Test komut dosyasını bir UnitTest testi olarak değil, normal bir uygulama gibi çalıştıracağız. UnitTest çerçevesini devreye sokacak olan 66. satırdır. Önceki testlerde, her bir testin yürütülmesini yapılandırmak için [setUp] yöntemini kullanıyorduk. [setUp] işlevi her testten önce çalıştırıldığı için aynı yapılandırmayı 11 kez tekrarlıyorduk. Burada ise yapılandırmayı sadece 1 kez yapıyoruz. Bu yapılandırma, 53. satırdaki [métier] ve 56. satırdaki [admindata] küresel değişkenlerini tanımlamaktan ibarettir; bu değişkenler daha sonra örneğin 12. satırdaki [TestDaoMétier] yöntemi tarafından kullanılacaktır;
  • satır 39-47: Test komut dosyası, [mysql / pgres] parametresini bekler; bu parametre, MySQL veya PostgreSQL veritabanının kullanılıp kullanılmadığını belirtir;
  • 50-51. satırlar: test yapılandırılır;
    1. satır: Yapılandırmadan [métier] katmanı alınır;
  • satır 56: Aynı işlem [dao] katmanı için de yapılır. Ardından, vergi hesaplaması için gerekli verileri içeren [admindata] örneği alınır;
  • testler, 66. satırdaki [unittest.main()] yönteminin, komut dosyası tarafından alınan [mysql / pgres] parametresini yok saymadığını, ancak ona farklı bir anlam yüklediğini göstermiştir. 63. satır, bu yöntemin artık hiçbir parametreye sahip olmamasını sağlar;

İki farklı yürütme yapılandırması oluşturuyoruz:

Image

Image

Bu iki yapılandırmadan birini çalıştırdığımızda şu sonuçları elde ediyoruz:


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/v05/tests/TestDaoMétier.py mysql
tests en cours...
...........
----------------------------------------------------------------------
Ran 11 tests in 0.001s

OK

Process finished with exit code 0
    1. ve 7. satırlar: 11 testin tümü başarılı oldu;

Bu testlerin yalnızca 11 vergi hesaplama durumunu kontrol ettiğini hatırlayalım. Yine de bu testlerin başarılı olması, [dao] katmanına güvenmemiz için yeterli olabilir.

20.2.5. Ana komut dosyası

Image

Image

Ana komut dosyası [main], sürüm 4'tekiyle aynıdır:


# mysql veya pgres parametresi bekleniyor
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})

# syspath belirlenir - içe aktarmalar yapılabilir
from ImpôtsError import ImpôtsError

# uygulamanın katmanları alınır (zaten örneklenmişlerdir)
dao = config["dao"]
métier = config["métier"]

try:
    # vergi dilimlerinin alınması
    admindata = dao.get_admindata()
    # Vergi mükelleflerinin verileri okunuyor
    taxpayers = dao.get_taxpayers_data()["taxpayers"]
    # Vergi mükellefleri mi?
    if not taxpayers:
        raise ImpôtsError(57, f"Pas de contribuables valides dans le fichier {config['taxpayersFilename']}")
    # Vergi mükelleflerinin vergisinin hesaplanması
    for taxPayer in taxpayers:
        # taxPayer hem giriş hem de çıkış parametresidir
        # taxPayer değiştirilecek
        métier.calculate_tax(taxPayer, admindata)
    # sonuçların bir metin dosyasına yazılması
    dao.write_taxpayers_results(taxpayers)
except ImpôtsError as erreur:
    # hatanın görüntülenmesi
    print(f"L'erreur suivante s'est produite : {erreur}")
finally:
    # işlem tamamlandı
    print("Travail terminé...")

Notlar

  • 1-10. satırlar: Kullanılacak SGBD değerini belirten [mysql / pgres] parametresi alınır;
  • 12-14. satırlar: uygulama yapılandırılır;
  • 16-17. satırlar: [ImpôtsError] sınıfı içe aktarılır. Bu sınıfa 38. satırda ihtiyaç duyulur;
  • 19-21. satırlar: Uygulamanın katmanlarına ilişkin referanslar alınır;
    1. satır: [dao] katmanından vergi idaresi verileri istenir. [métier] katmanı, vergi hesaplaması için bu verilere ihtiyaç duyar;
    1. satır: vergi mükelleflerinin verilerini (kimlik numarası, evli olup olmadığı, çocuk sayısı, maaş) bir listeye alıyoruz;
  • 29-30. satırlar: Bu liste boşsa, bir istisna oluşturulur;
  • satır 32-35: [taxpayers] listesindeki öğelerin vergisi hesaplanır;
  • satır 37: sonuçların jSON[résultats.json] dosyasına yazılması;
  • satır 38-40: Olası hataların yönetimi;

Komut dosyasının çalıştırılması için iki adet |configurations d’exécution| dosyası oluşturulur:

Image

[résultats.json] dosyasında elde edilen sonuçlar, sürüm 4'e aittir.

Image

20.3. Uygulama 3: Etkileşimli modda vergi hesaplaması

Şimdi, vergiyi etkileşimli bir şekilde hesaplamaya olanak tanıyan uygulamayı tanıtacağız. Bu, sürüm 4'teki Uygulama 2'nin uyarlanmış halidir.

Image

Image

  • [main] komut dosyası, [ui] katmanındaki [ui.run] yöntemi ile kullanıcıyla diyalog başlatır;
  • [ui] katmanı:
    • vergi hesaplaması için gerekli verileri elde etmek üzere [dao] katmanını kullanır;
    • kullanıcıdan, vergisi hesaplanacak vergi mükellefi ile ilgili bilgileri ister;
    • bu hesaplamayı yapmak için [métier] katmanını kullanır;

[config_layers] dosyası ek bir katmanı oluşturur:


def configure(config: dict) -> dict:
    # DAO katmanının örneklenmesi
    from ImpotsDaoWithAdminDataInDatabase import ImpotsDaoWithAdminDataInDatabase
    config["dao"] = ImpotsDaoWithAdminDataInDatabase(config)

    # katmanın örneklenmesi [métier]
    from ImpôtsMétier import ImpôtsMétier
    config['métier'] = ImpôtsMétier()

    # kullanıcı arayüzü
    from ImpôtsConsole import ImpôtsConsole
    config['ui'] = ImpôtsConsole(config)

    # yapılandırma döndürülüyor
    return config

[ImpôtsConsole] sınıfının 11-12. satırları, |version 4|'teki ile aynıdır.

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


# mysql veya pgres parametresi bekleniyor
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()

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

# syspath yapılandırıldı - içe aktarmalar yapılabilir
from ImpôtsError import ImpôtsError

# Q katmanı alınıyor [ui]
ui = config["ui"]

# kod
try:
    # [ui] katmanının çalıştırılması
    ui.run()
except ImpôtsError as ex1:
    # hata mesajı görüntülenir
    print(f"L'erreur 1 suivante s'est produite : {ex1}")
except BaseException as ex2:
    # hata mesajı görüntülenir
    print(f"L'erreur 2 suivante s'est produite : {ex2}")
finally:
    # her durumda yürütülür
    print("Travail terminé...")
  • 1-10. satırlar: komut dosyası, kullanılacak SGBD'i belirten bir [mysql / pgres] parametresini bekler;
  • 12-14. satırlar: uygulama yapılandırılır;
  • 19-20. satırlar: yapılandırmadan [ui] katmanı alınır;
    1. satır: Katman çalıştırılır;

Sonuçlar, |version 4| ile aynıdır. Sürüm 4'teki tüm arayüzler sürüm 5'te de korunmuş olduğundan, başka türlü olması mümkün değildi.