30. Uygulama alıştırması: sürüm 12
Bu bölümde, MVC mimarisine (Model-Görünüm-Denetleyici) uygun bir web uygulaması yazacağız. Uygulama, yanıtlarını üç farklı formatta sunabilecektir: jSON, XML, HTML. Şu anda yapacağımız şey ile daha önce yapılanlar arasında karmaşıklık açısından önemli bir fark bulunmaktadır. Şimdiye kadar gördüğümüz kavramların çoğunu yeniden kullanacağız ve nihai uygulamaya giden tüm adımları ayrıntılı olarak ele alacağız.
30.1. MVC Mimarisi
MVC olarak bilinen mimari modelini (Model – Görünüm – Denetleyici) şu şekilde uygulayacağız:
Bir istemciden gelen isteğin işlenmesi şu şekilde gerçekleşecektir:
- 1 - istek
İstenen URL'ler, http://machine:port/action/param1/param2/… biçiminde olacaktır. [Contrôleur principal], isteği doğru denetleyiciye "yönlendirmek" için bir yapılandırma dosyası kullanacaktır. Bunun için, URL'in [action] alanını kullanacaktır. URL ve [param1/param2/…]'in geri kalanı, eyleme iletilecek isteğe bağlı parametrelerden oluşur. Burada MVC'in C'si, [Contrôleur principal, Contrôleur / Action] dizesidir. İstenen eylemi işleyebilecek hiçbir denetleyici yoksa, web sunucusu istenen URL'in bulunamadığını bildirir.
- 2 - İşleme
- Seçilen [2a] eylemi, [Contrôleur principal] tarafından kendisine iletilen parami parametrelerini kullanabilir. Bu parametreler iki kaynaktan gelebilir:
- [/param1/param2/…]'in URL yolundan,
- istemcinin isteğinin gövdesine gönderilen parametrelerden;
- kullanıcının isteğinin işlenmesi sırasında, eylem [métier] ve [2b] katmanlarına ihtiyaç duyabilir. İstemcinin isteği işlendikten sonra, bu eylem çeşitli yanıtları tetikleyebilir. Klasik bir örnek şudur:
- istek düzgün bir şekilde işlenemediğinde bir hata yanıtı;
- aksi takdirde bir onay yanıtı;
- [Contrôleur / Action], yanıtını [2c] olarak ana denetleyiciye iletecek ve bir durum kodu gönderecektir. Bu durum kodları, uygulamanın bulunduğu durumu benzersiz bir şekilde temsil edecektir. Bu, ya bir başarı kodu ya da bir hata kodu olacaktır;
- 3 - yanıt
- müşterinin jSON, XML veya HTML yanıtını talep etmiş olmasına bağlı olarak, [Contrôleur principal], uygun yanıt türünü temsil eden [3a]'i başlatacak ve ondan yanıtı müşteriye göndermesini isteyecektir. [Contrôleur principal], çalıştırılan [Contrôleur / Action] tarafından sağlanan yanıtı ve durum kodunu ona iletecektir;
- istenen yanıt jSON veya XML türündeyse, seçilen yanıt kendisine verilen [Contrôleur / Action] yanıtını biçimlendirecek ve [3c] aracılığıyla gönderecektir. Bu yanıtı işleyebilen istemci, bir Python konsol komut dosyası veya bir HTML sayfasında barındırılan bir JavaScript komut dosyası olabilir;
- istenen yanıt HTML türündeyse, seçilen yanıt kendisine verilen durum kodunu kullanarak [3b], HTML veya [Vuei] görünümlerinden birini seçecektir. Bu, MVC'in V'sidir. Her durum koduna tek bir görünüm karşılık gelir. Bu V görünümü, yürütülen [Contrôleur / Action]'in yanıtını görüntüleyecektir. Bu görünüm, HTML, CSS ve JavaScript kullanarak bu yanıtın verilerini işler. Bu verilere görünüm modeli denir. Bu, MVC'teki M harfidir. İstemci ise çoğu zaman bir tarayıcıdır;
Şimdi, web mimarisi ile katmanlı mimari arasındaki bağlantıyı açıklayalım. Modele verilen tanıma göre, bu iki kavram birbiriyle ilişkili olabilir veya olmayabilir. Tek katmanlı bir web uygulamasını ele alalım:

Yukarıda, her bir [Contrôleur / Action], [métier] ve [dao] katmanlarının bir kısmını içermektedir. [web] katmanında bir MVC mimarisi mevcuttur, ancak uygulamanın tamamı katmanlı bir mimariye sahip değildir. Burada her şeyi yapan tek bir katman, yani web katmanı vardır.
Şimdi, çok katmanlı bir web mimarisini ele alalım:

[web] katmanı, MVC modelini takip etmeden uygulanabilir. Bu durumda çok katmanlı bir mimariye sahip oluruz, ancak web katmanı MVC modelini uygulamaz.
Örneğin, .NET dünyasında yukarıdaki [web] katmanıyukarıdaki [web] katmanı, ASP.NET ve MVC ile uygulanabilir ve böylece MVC türünde bir [web] katmanına sahip katmanlı bir mimari elde edilir. Bu işlem tamamlandıktan sonra, bu ASP.NET MVC katmanını, geri kalan (iş mantığı, DAO, Sürücü) aynı şekilde korunur. Böylece, artık MVC türü olmayan bir [web] katmanına sahip katmanlı bir mimari elde ederiz.
MVC'te, M modelinin V görünümü olan c.a.d'in modeli olduğunu belirtmiştik. V görünümü tarafından görüntülenen tüm veriler. MVC'in M modeline ilişkin başka bir tanım verilmiştir:

Birçok yazar, [web] katmanının sağındaki kısmın MVC'in M modelini oluşturduğunu düşünmektedir. Belirsizlikleri önlemek için,
- [web] katmanının sağındaki her şeyi ifade ederken "alan modelinden";
- bir V görünümü tarafından görüntülenen verileri ifade ederken “görünüm modeli”nden;
Bundan sonra, modelden bahsettiğimizde, her zaman görünüm modelinden söz etmiş olacağız.
30.2. İstemci/sunucu uygulaması mimarisi
Web uygulaması aşağıdaki mimariye sahip olacaktır:
- [1]'te web sunucusu iki tür istemciye sahip olacaktır:
- [2]'te, sunucuyla jSON ve XML verilerini alışverişinde bulunacak bir konsol istemcisi;
- [3]'te, sunucudan HTML verilerini alacak ve bunları görüntüleyecek bir tarayıcı;
- [1] web sunucusu, önceki sürümlerin [métier] ve [dao] katmanlarını korur;
- [2] web istemcisi, web uygulamasının yeni URL hizmetlerini dikkate alacak şekilde geliştirilecektir;
- tarayıcı tarafından görüntülenen HTML uygulaması tamamen yeniden yazılmalıdır;
Uygulamayı birkaç aşamada geliştireceğiz:
- öncelikle sunucunun jSON sürümünü geliştireceğiz. Sunucunun URL hizmetlerini bir Postman istemcisi ile sırayla test edeceğiz. Bu yöntem, uygulamanın görünümleri (=HTML) ile uğraşmadan web sunucusunun iskeletini oluşturmamızı sağlar;
- Postman ile jSON sunucusunu test ettikten sonra, onu bir konsol istemcisiyle test edeceğiz;
- ardından sunucunun XML sürümüne geçeceğiz. jSON'ten XML'e geçişin çok basit olduğunu gördük;
- son olarak sunucunun HTML sürümüne geçeceğiz. Bir MVC mimarisi oluşturacak ve görüntülenecek görünümleri tanımlayacağız. HTML uygulaması hem Postman istemcisiyle hem de geleneksel bir tarayıcıyla test edilecek;
30.3. Sunucu kod ağacı

- [1: web sunucusunun tamamı;
- [2] içinde: Şu an için, sunucunun HTML sürümüyle ilgili [static, templates, tests_views] klasörlerini göz ardı edeceğiz. Bu klasörün dışında, ana komut dosyası [main] ve yapılandırmasını;
- [3] içinde ise web sunucusunun denetleyicileri yer alır. Bunlar sınıf örnekleridir;
![]() | ![]() |
- [4] dosyasında, sunucunun HTTP yanıtı sınıflar tarafından yönetilecektir;
- [5]'te, önceki sunucuların günlük dosyalarını saklıyoruz;
Sunucunun HTML sürümünü oluşturduğumuzda, başka klasörler de devreye girecektir:
![]() | ![]() |
- [6]'te, HTML uygulamasının statik öğeleri;
- [7]'te, HTML uygulamasının şablonları, [9] görünümlerine ve [8] görünüm parçalarına ayrılır;
- [9]'te, görünüm modellerini uygulayan sınıflar;
30.4. Uygulamanın URL hizmet dosyaları
Web sunucusunu oluşturmak için şu şekilde ilerleyeceğiz:
- HTML uygulama görünümlerinden yola çıkarak, web uygulamasının uygulaması gereken eylemleri tanımlayacağız. Burada gerçek görünümleri kullanacağız, ancak bunlar sadece kağıt üzerinde çizilmiş görünümler de olabilir;
- Bu eylemlerden yola çıkarak, HTML uygulamasının URL hizmetlerini tanımlayacağız;
- bu hizmet URL'lerini, jSON sunan bir sunucu ile uygulayacağız. Bu, sunulacak HTML sayfalarıyla ilgilenmeden web sunucusunun iskeletini tanımlamamızı sağlar. Bu URL hizmetlerini Postman ile test edeceğiz;
- ardından jSON sunucumuzu bir konsol istemcisiyle test edeceğiz;
- jSON sunucusu onaylandıktan sonra, HTML uygulamasını yazmaya geçeceğiz;
İlk ekran, kimlik doğrulama ekranı olacaktır:

- Bu ilk ekrana yönlendiren eylem [init-session] [1] olarak adlandırılacaktır;
- [Valider] düğmesine tıklandığında, iki parametre gönderilerek [authentifier-utilisateur] eylemi tetiklenecektir: [2-3];
Vergi hesaplama görünümü:

- [1]'te, bu görünüme yönlendiren [authentifier-utilisateur] eylemi;
- [2]'te, [Valider] düğmesine tıklanması, üç parametreyle ([2-5]) [calculer-impot] eyleminin yürütülmesini tetikler;
- [6] bağlantısına tıklandığında, [lister-simulations] eylemi parametre olmadan tetiklenir;
- [7] bağlantısına tıklandığında, [fin-session] eylemi parametre olmadan tetiklenir;
- görünüm, kimliği doğrulanmış kullanıcı tarafından yapılan simülasyonlara aittir:

- [3]'te, bu görünüme yönlendiren [lister-simulations] eylemi;
- [2]'te, [Supprimer] bağlantısına tıklanması, listeden silinecek simülasyon numarası parametresiyle birlikte [supprimer-simulation] eylemini tetikler;
- [3] bağlantısına tıklandığında, parametre içermeyen [afficher-calcul-impot] eylemi tetiklenir ve bu eylem, vergi hesaplama görünümünü yeniden görüntüler;
- [4] bağlantısına tıklandığında, parametre olmadan [fin-session] eylemi tetiklenir;
Bu ilk bilgilerle, sunucunun çeşitli URL hizmetlerini tanımlayabiliriz:
Eylem | Rol | Çalıştırma bağlamı |
/init-session | İstenen yanıtların türünü (json, xml, html) belirlemek için kullanılır | GET isteği Herhangi bir zamanda gönderilebilir |
/kullanıcı-doğrulama | Bir kullanıcının oturum açmasına izin verir veya vermez | POST isteği. İsteğin iki POST parametresi içermesi gerekir [user, password] Yalnızca oturum türü (json, xml, html) biliniyorsa gönderilebilir |
/vergi-hesapla | Vergi hesaplaması simülasyonu yapar | POST isteği. İstek, üç POST parametresi içermelidir: [marié, enfants, salaire] Yalnızca oturum türü (json, xml, html) biliniyorsa ve kullanıcı kimliği doğrulanmışsa gönderilebilir |
/simülasyonları-listele | Oturumun başlangıcından itibaren gerçekleştirilen simülasyonların listesini görüntüleme isteği | GET isteği. Yalnızca oturum türü (json, xml, html) biliniyor ve kullanıcı kimliği doğrulanmışsa gönderilebilir |
/simülasyonu-sil/numara | Simülasyon listesinden bir simülasyonu siler | GET isteği. Yalnızca oturum türü (json, xml, html) biliniyor ve kullanıcı kimliği doğrulanmışsa gönderilebilir |
/vergi-hesaplamasını-göster | Vergi hesaplama sayfası HTML'i görüntüler | GET isteği. Yalnızca oturum türü (json, xml, html) biliniyorsa ve kullanıcı kimliği doğrulanmışsa gönderilebilir |
/oturumu-sonlandır | Simülasyon oturumunu sonlandırır. | Teknik olarak eski web oturumu silinir ve yeni bir oturum oluşturulur Yalnızca oturum türü (json, xml, html) biliniyor ve kullanıcı kimliği doğrulanmışsa gönderilebilir |
Bu farklı URL hizmet kodları, hem HTML sunucusu hem de jSON veya XML sunucuları için kullanılacaktır. İki URL kodu yalnızca bu son iki sunucu için kullanılacaktır: Bunlar, burada yeniden kullandığımız web istemcisi/sunucusunun önceki sürümüne ait URL kodlarıdır:
Eylem | Rol | Çalışma Ortamı |
/get-admindata | Vergi hesaplamasına olanak tanıyan vergi verilerini döndürür | GET isteği. Yalnızca oturum türü json veya xml ise kullanılır. Kullanıcının kimliği doğrulanmış olması gerekir |
/calculer-impots | jSON'te gönderilen vergi mükellefleri listesi için vergi hesaplaması yapar | GET isteği. Yalnızca oturum türü json veya xml ise kullanılır. Kullanıcının kimliği doğrulanmış olması gerekir |
Bu eylemlerle ilişkili tüm denetleyiciler aynı şekilde çalışır:
- parametrelerini kontrol ederler. Bu parametreler,
- [request.path] nesnesinde;
- isteğin gövdesinde [x-www-form-urlencoded] olarak iletilenler için [request.form] nesnesinde;
- isteğin gövdesinde jSON olarak iletilenler için [request.data] nesnesinde;
- Bir denetleyici, parametrelerinin geçerliliğini kontrol eden bir işlev veya yönteme benzer. Ancak denetleyici için durum biraz daha karmaşıktır:
- beklenen parametreler eksik olabilir;
- denetleyici tarafından alınan parametreler karakter dizileridir. Beklenen parametre bir sayı ise, denetleyici parametrenin karakter dizisinin gerçekten bir sayıya ait olup olmadığını kontrol etmelidir;
- beklenen parametrelerin mevcut olduğu ve sözdizimsel olarak doğru olduğu doğrulandıktan sonra, bunların o anki yürütme bağlamında geçerli olup olmadığının kontrol edilmesi gerekir. Bu bağlam oturumda bulunur. Kimlik doğrulama örneği, bir yürütme bağlamı örneğidir. Bazı eylemler, ancak istemci kimliği doğrulandıktan sonra işlenmelidir. Genellikle, oturumdaki bir anahtar, bu kimlik doğrulamanın gerçekleşip gerçekleşmediğini gösterir;
- Önceki kontroller tamamlandıktan sonra, ikincil denetleyici çalışmaya başlayabilir. Bu parametre kontrolü çok önemlidir. Bir müşterinin, uygulamanın yaşam döngüsünün herhangi bir aşamasında bize rastgele veriler göndermesi kabul edilemez. Uygulamanın yaşam döngüsünü tamamen kontrol etmeliyiz;
- işini tamamladıktan sonra, ikincil denetleyici, kendisini çağıran ana denetleyiciye [action, état, réponse] anahtarlarını içeren bir sözlük döndürür:
- [action], az önce yürütülen eylemdir;
- [état], eylemin işlenmesinin sonucunu gösteren üç basamaklı bir sayıdır:
- [x00], işlemin başarıyla tamamlandığını bildirir;
- [x01], işlemin başarısız olduğunu bildirir;
- [réponse], {‘yanıt’:nesne} biçimindeki sonuç sözlüğüdür. Nesne, işlenen eyleme göre farklı yapılara sahip olacaktır;
Şimdi, web uygulamasının işleyişini yönlendiren ve bu denetleyiciler tarafından işlenen farklı denetleyicileri, yani farklı eylemleri gözden geçireceğiz.
30.5. Sunucu yapılandırması

[config_database] veritabanı yapılandırması ile [config_layers] sunucu katmanlarının yapılandırması, önceki sürümlerdekiyle aynıdır. [config] dosyasında yeni bilgiler yer almaktadır:
def configure(config: dict) -> dict:
import os
# adım 1 ------
# 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"
# 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",
# Günlük Kaydedici, SendAdminMail
f"{root_dir}/impots/http-servers/02/utilities",
# komut dosyaları [config_database, config_layers]
script_dir,
# denetleyiciler
f"{script_dir}/../controllers",
# yanıtlar HTTP
f"{script_dir}/../responses",
# görünüm şablonları
f"{script_dir}/../models_for_views",
]
# syspath'ı ayarlıyoruz
from myutils import set_syspath
set_syspath(absolute_dependencies)
# web sunucusunun bağımlılıkları
# denetleyiciler
from AfficherCalculImpotController import AfficherCalculImpotController
from AuthentifierUtilisateurController import AuthentifierUtilisateurController
from CalculerImpotController import CalculerImpotController
from CalculerImpotsController import CalculerImpotsController
from FinSessionController import FinSessionController
from GetAdminDataController import GetAdminDataController
from InitSessionController import InitSessionController
from ListerSimulationsController import ListerSimulationsController
from MainController import MainController
from SupprimerSimulationController import SupprimerSimulationController
# yanıtlar HTTP
from HtmlResponse import HtmlResponse
from JsonResponse import JsonResponse
from XmlResponse import XmlResponse
# görünüm şablonları
from ModelForAuthentificationView import ModelForAuthentificationView
from ModelForCalculImpotView import ModelForCalculImpotView
from ModelForErreursView import ModelForErreursView
from ModelForListeSimulationsView import ModelForListeSimulationsView
# 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,
# izin verilen eylemler ve bunların denetleyicileri
"controllers": {
# hesaplama oturumunun başlatılması
"init-session": InitSessionController(),
# kullanıcı kimlik doğrulaması
"authentifier-utilisateur": AuthentifierUtilisateurController(),
# bireysel modda vergi hesaplaması
"calculer-impot": CalculerImpotController(),
# toplu modda vergi hesaplaması
"calculer-impots": CalculerImpotsController(),
# simülasyon listesi
"lister-simulations": ListerSimulationsController(),
# simülasyon silme
"supprimer-simulation": SupprimerSimulationController(),
# hesaplama oturumunun sonlandırılması
"fin-session": FinSessionController(),
# vergi hesaplama görünümünün görüntülenmesi
"afficher-calcul-impot": AfficherCalculImpotController(),
# vergi dairesinden verilerin alınması
"get-admindata": GetAdminDataController(),
# ana denetleyici
"main-controller": MainController()
},
# farklı yanıt türleri (json, xml, html)
"responses": {
"json": JsonResponse(),
"html": HtmlResponse(),
"xml": XmlResponse()
},
# HTML görünümleri ve bunların şablonları, denetleyici tarafından döndürülen duruma bağlıdır
"views": [
{
# kimlik doğrulama görünümü
"états": [
# /init-session başarılı
700,
# /kullanıcı-kimlik-doğrulama başarısız
201
],
"view_name": "views/vue-authentification.html",
"model_for_view": ModelForAuthentificationView()
},
{
# vergi hesaplama görünümü
"états": [
# /kullanıcı-doğrulama başarılı
200,
# /vergi-hesapla başarılı
300,
# /vergi-hesapla başarısız
301,
# /vergi-hesaplamasını-göster
800
],
"view_name": "views/vue-calcul-impot.html",
"model_for_view": ModelForCalculImpotView()
},
{
# simülasyon listesini görüntüle
"états": [
# /simülasyonları-listele
500,
# /simülasyonu-sil
600
],
"view_name": "views/vue-liste-simulations.html",
"model_for_view": ModelForListeSimulationsView()
}
],
# beklenmedik hataların görünümü
"view-erreurs": {
"view_name": "views/vue-erreurs.html",
"model_for_view": ModelForErreursView()
},
# yönlendirmeler
"redirections": [
{
"états": [
400, # /oturum-sonlandırma başarılı
],
# yönlendirme
"to": "/init-session/html",
}
],
}
)
# 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 uygulanıyor
return config
- 41. satıra kadar klasik unsurlar yer almaktadır;
- 43-66. satırlar: 43. satıra gelindiğinde, sunucunun Python Yolu tanımlanır. Ardından projenin bağımlılıkları içe aktarılabilir:
- 45-55. satırlar: denetleyiciler listesi;
- 57-60. satırlar: HTTP yanıtlarının listesi;
- 62-66. satırlar: görünüm şablonlarının listesi;
- 68-189. satırlar: bir dizi sabit ile uygulamanın yapılandırması;
- 71-98. satırlar: Bu satırları önceki sürümlerde de görmüştük;
- 101-122. satırlar: denetleyiciler sözlüğü:
- anahtarlar, eylemlerin adlarıdır;
- değerler ise bu eylemi yönetmesi gereken denetleyicinin bir örneğidir. Her denetleyici yalnızca tek bir örnek olarak oluşturulur (singleton). Aynı örnek, sunucunun farklı iş parçacıkları tarafından yürütülecektir. Bu nedenle, her denetleyicinin değiştirmek isteyebileceği paylaşılan verilere dikkat etmek gerekir;
- 125-129. satırlar: Olası üç yanıt (HTTP) sözlüğü:
- anahtarlar, istemcinin istediği yanıt türüdür (jSON, xml, html);
- değerler, HTTP yanıtının bir örneğidir. Her yanıt oluşturucu yalnızca tek bir örnek olarak oluşturulur (singleton). Aynı oluşturucu, sunucunun farklı iş parçacıkları tarafından çalıştırılacaktır. Bu nedenle, her bir oluşturucunun değiştirmek isteyebileceği paylaşılan verilere dikkat etmek gerekir;
- 132-186. satırlar: HTML görünümlerinin yapılandırılması. Şu an için bu satırları göz ardı ediyoruz;
- satır 191-202: Bu satırlarla önceki sürümlerde de karşılaşmıştık;
30.6. Sunucu içinde bir istemci isteğinin izlediği yol

Sunucuya gelen bir istemci isteğinin, geri gönderilen HTTP yanıtına kadar izlediği yolu takip edeceğiz. Bu yol, MVC sunucusundaki yolu takip eder.
30.6.1. [main] komut dosyası

[main] komut dosyası, önceki sürümlerinkiyle birçok açıdan aynıdır. Yine de sağlam bir temel oluşturmak amacıyla bu komut dosyasını tam olarak veriyoruz:
# 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, session, url_for, redirect
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
import os
# 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ının kontrol edilmesi
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}")
# hatayı not ediyoruz
erreur = True
message_erreur = f"{exception}"
# günlük kaydediciyi yapılandırmada kaydedilir
config['logger'] = logger
# hatanın yönetimi
if erreur:
# yöneticiye e-posta gönder
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 idaresinden veri alma
erreur = False
try:
# admindata, uygulama kapsamında salt okunur bir veri olacaktır
config["admindata"] = config["layers"]["dao"].get_admindata().asdict()
# 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 oluşursa durdurulur
if erreur:
sys.exit(2)
# Flask uygulaması
app = Flask(__name__, template_folder="templates", static_folder="static")
# oturumun gizli anahtarı
app.secret_key = os.urandom(12).hex()
# ön denetleyici
def front_controller() -> tuple:
# isteği işleniyor
logger = None
…
@app.route('/', methods=['GET'])
def index() -> tuple:
# /init-session/html adresine yönlendirme
return redirect(url_for("init_session", type_response="html"), status.HTTP_302_FOUND)
# init-session
@app.route('/init-session/<string:type_response>', methods=['GET'])
def init_session(type_response: str) -> tuple:
# eyleme ilişkin denetleyici çalıştırılıyor
return front_controller()
# kullanıcı kimlik doğrulama
@app.route('/authentifier-utilisateur', methods=['POST'])
def authentifier_utilisateur() -> tuple:
# eylemle ilişkili denetleyici çalıştırılıyor
return front_controller()
# vergi-hesapla
@app.route('/calculer-impot', methods=['POST'])
def calculer_impot() -> tuple:
# eyleme ilişkin denetleyici çalıştırılıyor
return front_controller()
# simülasyonları listele
@app.route('/lister-simulations', methods=['GET'])
def lister_simulations() -> tuple:
# eyleme ilişkin denetleyici çalıştırılır
return front_controller()
# simülasyonu-sil
@app.route('/supprimer-simulation/<int:numero>', methods=['GET'])
def supprimer_simulation(numero: int) -> tuple:
# eyleme ilişkin denetleyici çalıştırılır
return front_controller()
# oturum sonu
@app.route('/fin-session', methods=['GET'])
def fin_session() -> tuple:
# eyleme ilişkin denetleyici çalıştırılıyor
return front_controller()
# vergi-hesaplamasını-göster
@app.route('/afficher-calcul-impot', methods=['GET'])
def afficher_calcul_impot() -> tuple:
# eyleme ilişkin denetleyici çalıştırılıyor
return front_controller()
# yönetici verilerini al
@app.route('/get-admindata/<int:numero>', methods=['GET'])
def get_admindata() -> tuple:
# eyleme ilişkin denetleyici çalıştırılıyor
return front_controller()
# sadece ana işlem
if __name__ == '__main__':
# sunucu başlatılıyor
app.config.update(ENV="development", DEBUG=True)
app.run(threaded=True)
- 1-92. satırlar: Bu satırların tümü daha önce ele alınmış ve açıklanmıştır;
- 92. satır: Sunucu bir oturumu yönetecektir. Bu nedenle gizli bir anahtara ihtiyacımız var. Her kullanıcı için oturuma iki bilgi ekleyeceğiz:
- kullanıcının kimlik doğrulamasını başarıyla tamamlayıp tamamlamadığı;
- her vergi hesaplaması yaptığında, bu hesaplamanın sonuçları “kullanıcının simülasyon listesi” adını vereceğimiz bir listeye eklenecek. Bu liste oturuma kaydedilecek;
- 100-151. satırlar: Sunucunun hizmet listesi. İlgili işlevler filtre görevi görür: Bu listede bulunmayan tüm istekler, Flask sunucusu tarafından [404 NOT FOUND] hatasıyla reddedilecektir. Bu filtrelemeyi geçtikten sonra, istek sistematik olarak 94-98. satırlarda yer alan ve birazdan tanıtacağımız [front_controller] işlevi tarafından uygulanan bir ‘Front Controller’a iletilir;
- 100-103. satırlar: [/] rotasının yönetimi. Web uygulamasının giriş noktası, 107. satırdaki URL olacaktır. Ayrıca 103. satırda, istemciyi bu URL’e yönlendiriyoruz:
- [url_for] işlevi 18. satırda içe aktarılmıştır. Bu işlevin burada iki parametresi vardır:
- 1. parametre, yönlendirme fonksiyonlarından birinin adıdır; burada 107. satırdaki fonksiyon. Bu fonksiyonun, müşterinin istediği yanıt türünü (json, xml, html) belirten [type_response] parametresini beklediği görülmektedir;
- ikinci parametre, 107. satırdaki parametrenin adını ([type_response]) alır ve ona bir değer atar. Başka parametreler olsaydı, bu işlem her biri için tekrarlanırdı;
- bu işlem, kendisine verilen iki parametre ile belirtilen fonksiyona atanan URL değerini döndürür. Burada, 106. satırdaki URL değeri elde edilir; burada parametre, [/init-session/html] değeriyle değiştirilir;
- [redirect] işlevi 18. satırda içe aktarılmıştır. Bu işlevin görevi, istemciye bir HTTP yönlendirme başlığı göndermektir:
- 1. parametre, müşterinin yönlendirilmesi gereken URL adresidir;
- 2. parametre, müşteriye verilen HTTP yanıtının durum kodudur. [status.HTTP_302_FOUND] kodu, bir yönlendirmeye karşılık gelir (HTTP);
94-98. satırlardaki [front_controller] işlevi, müşterinin isteğinin ilk işlemlerini gerçekleştirir:
# ön denetleyici
def front_controller() -> tuple:
# istek işlenir
logger = None
try:
# günlüğe kaydediliyor
logger = Logger(config["logsFilename"])
# istek, iş parçacığıyla ilişkili bir yapılandırmada saklanıyor
thread_config = {"logger": logger}
thread_name = threading.current_thread().name
config[thread_name] = {"config": thread_config}
# istek günlüğe kaydediliyor
logger.write(f"[ front_controller] 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 durdurulması, diğerlerinin ise durdurulmaması 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"[ front_controller] mis en pause du thread pendant {sleep_time} seconde(s)\n")
# duraklama
time.sleep(sleep_time)
# istek ana denetleyiciye iletilir
main_controller = config['controllers']["main-controller"]
résultat, status_code = main_controller.execute(request, session, config)
# müşteriye gönderilen sonuç günlüğe kaydediliyor
log = f"[front_controller] {résultat}\n"
logger.write(log)
# Önemli bir hata oluştu mu?
if status_code == status.HTTP_500_INTERNAL_SERVER_ERROR:
# uygulama yöneticisine bir e-posta gönderilir
send_adminmail(config, log)
# yanıt için istenen tür belirlenir
if session.get('typeResponse') is None:
# oturum türü henüz belirlenmedi - bu jSON olacak
type_response = 'json'
else:
type_response = session['typeResponse']
# gönderilecek yanıt oluşturulur
response_builder = config["responses"][type_response]
response, status_code = response_builder \
.build_http_response(request, session, config, status_code, résultat)
# yanıt gönderiliyor
return response, status_code
except BaseException as erreur:
# bu beklenmedik bir hatadır – mümkünse hata günlüğüne kaydedilir
if logger:
logger.write(f"[ front_controller] {erreur}")
# müşteriye gönderilecek yanıt hazırlanıyor
résultat = {"réponse": {"erreurs": [f"{erreur}"]}}
# jSON formatında yanıt gönderilir
return json_response(résultat, status.HTTP_500_INTERNAL_SERVER_ERROR)
finally:
# Günlük dosyası açıksa kapatılır
if logger:
logger.close()
- 1-57. satırlar: Bu kodu biliyoruz. Örneğin, önceki sürümdeki [main] betiğinde [main] adlı işlevin koduydu. Dikkat edilmesi gereken tek bir nokta, 25-26. satırlarda kullanılan denetleyicidir:
- 25. satır: Yapılandırmadan, [main-controller] adıyla ilişkili denetleyici örneğini alıyoruz. Bu, aşağıdaki satırlardır:
# web sunucusunun bağımlılıkları
# denetleyiciler
…
from MainController import MainController
# izin verilen eylemler ve bunların denetleyicileri
"controllers": {
…,
# ana denetleyici
"main-controller": MainController()
},
- (devamı)
- yukarıdaki 10. satırda, bir sınıf örneğinin alındığı görülecektir;
- 26. satır: [MainController] denetleyicisinden isteği işlemesi istenir;
- 30-45. satırlar: [MainController] denetleyicisi tarafından verilen yanıt, istemciye gönderilir. Bu satırlara biraz sonra tekrar döneceğiz;
[front_controller] işlevinin ve ardından [MainController] sınıfının görevi, tüm isteklerde ortak olan işlemleri gerçekleştirmektir:
Yukarıdaki şemada, hâlâ isteğin işlenmesinin 1. aşamasındayız. Ana denetleyici [MainController], 1. aşamaya devam edecektir.
30.6.2. Ana denetleyici [MainController]
Ana denetleyici [MainController], [front_controller] işlevi tarafından başlatılan işi sürdürür:
Tüm denetleyiciler, aşağıdaki [InterfaceController] [2] arayüzünü uygular:

from abc import ABC, abstractmethod
from werkzeug.local import LocalProxy
class InterfaceController(ABC):
@abstractmethod
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
pass
- [InterfaceController] arayüzü, yalnızca 8. satırdaki [execute] yöntemini tanımlar. Bu yöntem üç parametre alır:
- [request]: istemcinin isteği;
- [session]: istemcinin oturumu;
- [config]: uygulamanın yapılandırması;
[execute] yöntemi, iki elemanlı bir tuple döndürür:
- birincisi, {‘eylem’: eylem, ‘durum’: durum, ‘yanıt’: sonuçlar} biçiminde sonuç sözlüğüdür;
- ikincisi, müşteriye döndürülecek HTTP durum kodudur;
Ana denetleyici [MainController] [1], [InterfaceController] arayüzünü şu şekilde uygular:
# bağımlılıkların içe aktarılması
from flask_api import status
from werkzeug.local import LocalProxy
# web uygulamasının denetleyicileri
from InterfaceController import InterfaceController
class MainController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# yol öğelerini alıyoruz
params = request.path.split('/')
action = params[1]
# hatalar
erreur = False
# Bazı işlemlerden önce oturum türü bilinmelidir
type_response = session.get('typeResponse')
if type_response is None and action != "init-session":
# hatayı not ediyoruz
résultat = {"action": action, "état": 101,
"réponse": ["pas de session en cours. Commencer par action [init-session]"]}
erreur = True
# Bazı işlemler için kimlik doğrulaması yapılmalıdır
user = session.get('user')
if not erreur and user is None and action not in ["init-session", "authentifier-utilisateur"]:
# hata kaydediliyor
résultat = {"action": action, "état": 101,
"réponse": [f"action [{action}] demandée par utilisateur non authentifié"]}
erreur = True
# Hata var mı?
if erreur:
# bir hata mesajı gönderilir
return résultat, status.HTTP_400_BAD_REQUEST
else:
# eylemle ilişkili denetleyici çalıştırılır
controller = config["controllers"][action]
résultat, status_code = controller.execute(request, session, config)
return résultat, status_code
[MainController] denetleyicisi, isteğin geçerliliğine ilişkin ilk kontrolleri yapar.
- 11-13. satırlar: Denetleyici, öncelikle istemci tarafından talep edilen eylemi alır. Hizmet URL'lerinin [/action/param1/param2/…] biçiminde olduğunu ve bu URL'in [request.path] içinde yer aldığını hatırlatırız;
- 17-23. satırlar: [init-session] eylemi, istemcinin istediği yanıt türünü (json, xml, html) başlatmak için kullanılır. Bu bilgi, [typeRéponse] anahtarıyla ilişkilendirilerek oturuma kaydedilir. Dolayısıyla, eylem [init-session] değilse, oturumda [typeRéponse] anahtarı bulunmalıdır; aksi takdirde istek hatalı sayılır;
- 21-22. satırlar: Her denetleyicinin döndürdüğü sonucun yapısı; burada bir hata sonucu gösterilmektedir:
- [action]: devam eden eylemin adıdır. Bu, isteğin sonucunu günlüğe kaydederken eylemin adını elde etmemizi sağlar;
- [état]: üç basamaklı bir durum kodudur:
- [x00], başarılı bir işlem için;
- [x01], başarısızlık anlamına gelir;
- [réponse]: isteğe verilen yanıttır. Bu yanıtın niteliği her isteğe göre farklılık gösterir;
- 24-30. satırlar: [authentifier-utilisateur] eylemi, kullanıcının kimliğini doğrulamak için kullanılır. İşlem başarılı olursa, [user=True] anahtarı kullanıcının oturumuna eklenir. Bazı URL hizmet eylemlerine yalnızca kimlik doğrulaması yapılmış bir kullanıcı erişebilir. Burada bu durum kontrol edilmektedir;
- 26. satır: Henüz kimlik doğrulaması yapılmamış bir kullanıcı tarafından yalnızca [init-session] ve [authentifier-utilisateur] eylemleri gerçekleştirilebilir;
- 28-29. satırlar: hata durumunda gönderilecek sonuç;
- satır 32-34: Yukarıdaki iki hatadan biri meydana gelirse, HTTP 400 BAD REQUEST durum koduyla hata yanıtı müşteriye gönderilir;
- satır 35-39: Hata oluşmamışsa, iş devam eden eylemi işlemekle görevli denetleyiciye devredilir. Bu denetleyicinin örneği, uygulama yapılandırmasında bulunur;
[MainController] sınıfı, [front_controller] işlevinin işini devam ettirir: Bu iki sınıf birlikte, isteklerin işlenmesinde faktörlere ayrılabilecek her şeyi bir araya getirir ve isteği belirli bir denetleyiciye aktarmak için son anı bekler. [front_controller] işlevi ile [MainController] sınıfı arasındaki kod dağılımı tamamen özneldir. Burada önceki sürümün kazanımlarını korumak istedim: [front_controller] işlevi, daha önce [main] adıyla zaten mevcuttu. Uygulamada şunlar yapılabilir:
- her şeyi [front_controller] işlevine aktarıp [MainController] sınıfını ortadan kaldırabiliriz;
- her şeyi [MainController] sınıfına aktarıp [front_controller] işlevini kaldırabiliriz. Ben daha çok bu çözümü tercih ederim, çünkü bu çözüm [main] ana betiğinin kodunu hafifletme avantajı sunar;
30.7. Bir eyleme özgü işleme
Uygulamanın MVC mimarisine geri dönelim:

Hâlâ yukarıdaki 1. adımdayız. Hata olmamışsa, 2. adım başlayacaktır. İstek, istek tarafından talep edilen eyleme özgü denetleyiciye iletilmiştir. Bu eylemin, şu rota ile tanımlanan [/init-session] olduğunu varsayalım:
# oturum başlatma
@app.route('/init-session/<string:type_response>', methods=['GET'])
def init_session(type_response: str) -> tuple:
# eylemle ilişkili denetleyici çalıştırılıyor
return front_controller()
Bu eylem, [config] yapılandırmasındaki bir denetleyiciye bağlıdır:
# izin verilen eylemler ve bunlara ait denetleyiciler
"controllers": {
# hesaplama oturumunun başlatılması
"init-session": InitSessionController(),
…
},
Böylece [InitSessionController] denetleyicisi (4. satır) devreye girer. Kodu şöyledir:
from flask_api import status
from werkzeug.local import LocalProxy
from InterfaceController import InterfaceController
class InitSessionController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# yolun öğeleri alınır
dummy, action, type_response = request.path.split('/')
# başlangıçta hata yok
erreur = False
# yanıt türünün doğrulanması
if type_response not in config['responses'].keys():
erreur = True
résultat = {"action": action, "état": 701,
"réponse": [f"paramètre [type={type_response}] invalide"]}
# hata yoksa
if not erreur:
# oturum türünü Flask oturumuna eklenir
session['typeResponse'] = type_response
résultat = {"action": action, "état": 700,
"réponse": [f"session démarrée avec le type de réponse {type_response}"]}
return résultat, status.HTTP_200_OK
else:
return résultat, status.HTTP_400_BAD_REQUEST
- 6. satır: Diğer denetleyiciler gibi, [InitSessionController] denetleyicisi de [InterfaceController] arayüzünü uygular;
- 10. satır: URL, [/init-session/type_response] türündedir. [init-session] eylemi ve istenen yanıt türü alınır;
- 15. satır: İstenen yanıt türü, yalnızca yanıt yapılandırmasında bulunanlardan biri olabilir:
# farklı yanıt türleri (json, xml, html)
"responses": {
"json": JsonResponse(),
"html": HtmlResponse(),
"xml": XmlResponse()
},
- Aksi takdirde 701 hata yanıtı hazırlanır (satır 17);
- 20-25. satırlar: istenen yanıt türü geçerliyse;
- 22. satır: istenen yanıt türü oturuma kaydedilir. Zira, takip edecek istekler için bu bilginin hatırlanması gerekecektir;
- 23-24. satırlar: 700 başarı yanıtı hazırlanır;
- satır 25: başarı yanıtı çağıran koda döndürülür;
- 27. satır: Hata oluşmuşsa, hata yanıtı çağıran koda döndürülür;
30.8. Sunucunun HTTP yanıtının oluşturulması
Uygulamanın MVC mimarisine geri dönelim:

Az önce 1. ve 2. adımları inceledik. Üç durum kodu ile karşılaştık:
- 700: /init-session başarılı;
- 701: /init-session başarısız oldu;
- 101: oturum başlatılmadığı veya kullanıcı kimliği doğrulanmadığı için geçersiz istek;
Yukarıdaki 3. adımda sunucunun yanıtının istemciye nasıl gönderileceğini inceleyelim. Bu işlem, [main] betiğindeki [front_controller] işlevinde gerçekleşir:
# ön denetleyici
def front_controller() -> tuple:
# isteği işliyoruz
logger = None
try:
# günlük kaydı
logger = Logger(config["logsFilename"])
# isteği iş parçacığıyla ilişkili bir yapılandırmada saklama
thread_config = {"logger": logger}
thread_name = threading.current_thread().name
config[thread_name] = {"config": thread_config}
# istek günlüğe kaydediliyor
logger.write(f"[ front_controller] 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"[ front_controller] mis en pause du thread pendant {sleep_time} seconde(s)\n")
# duraklama
time.sleep(sleep_time)
# istek ana denetleyiciye iletilir
main_controller = config['controllers']["main-controller"]
résultat, status_code = main_controller.execute(request, session, config)
# müşteriye gönderilen sonuç günlüğe kaydediliyor
log = f"[front_controller] {résultat}\n"
logger.write(log)
# ölümcül bir hata mı oluştu?
if status_code == status.HTTP_500_INTERNAL_SERVER_ERROR:
# uygulama yöneticisine bir e-posta gönderilir
send_adminmail(config, log)
# yanıt için istenen tür belirlenir
if session.get('typeResponse') is None:
# oturum türü henüz belirlenmedi - bu jSON olacak
type_response = 'json'
else:
type_response = session['typeResponse']
# gönderilecek yanıt oluşturulur
response_builder = config["responses"][type_response]
response, status_code = response_builder \
.build_http_response(request, session, config, status_code, résultat)
# yanıt gönderiliyor
return response, status_code
except BaseException as erreur:
# bu beklenmedik bir hatadır – mümkünse hata günlüğüne kaydedilir
if logger:
logger.write(f"[ front_controller] {erreur}")
# müşteriye gönderilecek yanıt hazırlanıyor
résultat = {"réponse": {"erreurs": [f"{erreur}"]}}
# jSON formatında yanıt gönderilir
return json_response(résultat, status.HTTP_500_INTERNAL_SERVER_ERROR)
finally:
# açılmışsa günlük dosyası kapatılır
if logger:
logger.close()
- 26. satırdayız: ana denetleyici hata yanıtını verdi;
- 27-29. satırlar: Ana denetleyicinin yanıtı ne olursa olsun (başarılı veya başarısız), bu yanıt günlük dosyasına kaydedilir;
- 30-33. satırlar: önceki sürümlerde olduğu gibi, HTTP durum kodu [500 INTERNAL SERVER ERROR] ise, hata günlüğüyle birlikte uygulama yöneticisine bir e-posta gönderilir;
- satır 34-39: HTTP yanıtı gönderilecek ve denetleyici tarafından döndürülen sonuç bu yanıtın gövdesine yerleştirilecektir. İstemcinin bu yanıtı hangi formatta (json, xml, html) istediğini bilmemiz gerekir. İstenen yanıt türünü oturumda arıyoruz. Eğer yoksa, bu türü keyfi olarak jSON olarak belirliyoruz;
- 40-43. satırlar: HTTP yanıtı oluşturulur;
Yapılandırma dosyasında, her yanıt türü (json, xml, html) bir sınıf örneğiyle ilişkilendirilmiştir:
# farklı yanıt türleri (json, xml, html)
"responses": {
"json": JsonResponse(),
"html": HtmlResponse(),
"xml": XmlResponse()
},
Yanıt sınıfları, sunucu dizin yapısındaki [responses] klasöründe bulunur:

Her yanıt sınıfı, aşağıdaki [InterfaceResponse] arayüzünü uygular:
from abc import ABC, abstractmethod
from flask.wrappers import Response
from werkzeug.local import LocalProxy
class InterfaceResponse(ABC):
@abstractmethod
def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
résultat: dict) -> (Response, int):
pass
- 8-11. satırlar: [InterfaceResponse] arayüzü, aşağıdaki parametrelere sahip tek bir [build_http_response] yöntemini tanımlar:
- [request, session, config]: bunlar, eylem denetleyicisi tarafından alınan parametrelerdir;
- [résultat, status_code]: bunlar, eylem denetleyicisi tarafından üretilen sonuçlardır;
Şimdi jSON yanıtını inceleyeceğiz. Bu yanıt, aşağıdaki [JsonResponse] sınıfı tarafından üretilir:
import json
from flask import make_response
from flask.wrappers import Response
from werkzeug.local import LocalProxy
from InterfaceResponse import InterfaceResponse
class JsonResponse(InterfaceResponse):
def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
résultat: dict) -> (Response, int):
# sonuçlar: sonuç sözlüğü
# status_code: yanıtın durum kodu HTTP
# yanıt gönderiliyor HTTP
response = make_response(json.dumps(résultat, ensure_ascii=False))
response.headers['Content-Type'] = 'application/json; charset=utf-8'
return response, status_code
Bu kodu daha önce birçok kez gördük. Bu, [myutils] modülündeki [json_response] işlevinin kodudur.
30.9. İlk testler
İncelenen kodda üç durum kodu ile karşılaştık:
- 700: /init-session başarılı;
- 701: /init-session başarısız;
- 101: oturum başlatılmadığı veya kullanıcı kimliği doğrulanmadığı için geçersiz istek;
jSON oturumu ile bunları elde etmeye çalışacağız.
- Web sunucusunu, SGBD'i ve e-posta sunucusunu başlatıyoruz;
- Postman istemcisini başlatıyoruz;
Test 1
Öncelikle, oturum başlatılmadığı için geçersiz bir istek göstereceğiz:

- [1-2]: [POST http://localhost:5000/authentifier-utilisateur] isteği geçerli bir yoldur:
# kullanıcı kimlik doğrulaması
@app.route('/authentifier-utilisateur', methods=['POST'])
def authentifier_utilisateur() -> tuple:
# eylemle ilişkili denetleyici çalıştırılıyor
return front_controller()
ancak bu, oturumun önceden [/init-session] eylemiyle başlatılmış olması durumunda kabul edilir.
İsteği çalıştıralım ve sunucu tarafından gönderilen sonucu görelim:

- [1-2]: jSON yanıtı alındı. Yanıt türü istemci tarafından henüz belirlenmemişse, sunucu yanıt vermek için jSON'i kullanır;
- [3-5]: yanıtın jSON sözlüğü;
- [action]: Yürütülen eylem;
- [état]: yanıtın durum kodu. [x01] kodu bir hatayı gösterir;
- [réponse]: her eyleme özeldir. Burada bir hata mesajı içerir;
Şimdi, yanlış bir yanıt türüyle bir oturum başlatalım:

- [1-2] doğru bir yoldur:
# oturum başlatma
@app.route('/init-session/<string:type_response>', methods=['GET'])
def init_session(type_response: str) -> tuple:
# eyleme ilişkin denetleyici çalıştırılıyor
return front_controller()
Dolayısıyla, bu istek MVC sunucusunun istek işleme tüneline girecektir. Ancak, istenen oturum türü yanlış olduğu için bu işlem sırasında reddedilmesi beklenir.
Yanıt şu şekildedir:

- [4]'te, bir hata kodu [x01];
- [5]'te ise hatanın açıklaması;
Şimdi, jSON oturumunu başlatalım:

Yanıt şu şekildedir:

Şimdi, XML oturumunu başlatalım. jSON yanıtı, aşağıdaki [XmlResponse] sınıfı tarafından oluşturulan XML yanıtıyla değiştirilecektir:
import xmltodict
from flask import make_response
from flask.wrappers import Response
from werkzeug.local import LocalProxy
from InterfaceResponse import InterfaceResponse
from Logger import Logger
class XmlResponse(InterfaceResponse):
def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
résultat: dict) -> (Response, int):
# sonuçlar: sonuç sözlüğü
# status_code: yanıtın durum kodu HTTP
# sonuç: dizeye dönüştürülecek sözlük XML
xml_string = xmltodict.unparse({"root": résultat})
# yanıt HTTP olarak verilir
response = make_response(xml_string)
response.headers['Content-Type'] = 'application/xml; charset=utf-8'
return response, status_code
Bu, [myutils] paylaşımlı modülündeki [xml_response] işlevine ait, bizim bildiğimiz bir koddur.
Bir XML oturumu başlatıyoruz:

Sunucudan gelen sonuç şu şekildedir:

jSON'te olduğu gibi aynı yanıtı alıyoruz, ancak bu sefer yanıt XML biçiminde.
30.10. [authentifier-utilisateur] eylemi
[authentifier-utilisateur] eylemi, vergi hesaplama uygulamasını kullanmak isteyen bir kullanıcının kimliğini doğrulamaya olanak tanır. Bu eylemin yolu, [main] betiğinde şu şekilde tanımlanmıştır:
# kullanıcı kimlik doğrulaması
@app.route('/authentifier-utilisateur', methods=['POST'])
def authentifier_utilisateur() -> tuple:
# eylemle ilişkili denetleyici çalıştırılır
return front_controller()
Sunucu, iki adet POST parametresi bekler:
- [user]: kullanıcı kimliği;
- [password]: kullanıcının şifresi;
Yetkili kullanıcı listesi, [config] yapılandırmasında tanımlanmıştır:
# uygulamayı kullanmaya yetkili kullanıcılar
"users": [
{
"login": "admin",
"password": "admin"
}
],
Burada tek öğeli bir liste bulunmaktadır.
[authentifier-utilisateur] eylemi, aşağıdaki [AuthentifierUtilisateurController] denetleyicisi tarafından işlenir:
from flask_api import status
from werkzeug.local import LocalProxy
from InterfaceController import InterfaceController
from Logger import Logger
class AuthentifierUtilisateurController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# yolun öğeleri alınır
dummy, action = request.path.split('/')
# POST parametreleri
post_params = request.form
# yanıt durum kodu HTTP
status_code = None
# başlangıçta hata yok
erreur = False
erreurs = []
# iki parametreli bir POST gerekiyor
if len(post_params) != 2:
erreur = True
status_code = status.HTTP_400_BAD_REQUEST
erreurs.append("méthode POST requise, paramètre [action] dans l'URL, paramètres postés [user, password]")
if not erreur:
# POST'in parametreleri alınır
# [user] parametresi
user = post_params.get("user")
if user is None:
erreur = True
erreurs.append("paramètre [user] manquant")
# [password] parametresi
password = post_params.get("password")
if password is None:
erreur = True
erreurs.append("paramètre [password] manquant")
# hata mı?
if erreur:
status_code = status.HTTP_400_BAD_REQUEST
# hata mı?
if not erreur:
# kullanıcı adı ve şifre çiftinin geçerliliği kontrol ediliyor
users = config['users']
i = 0
nbusers = len(users)
trouvé = False
while not trouvé and i < nbusers:
trouvé = user == users[i]["login"] and password == users[i]["password"]
i += 1
# bulundu mu?
if not trouvé:
# hata kaydediliyor
erreur = True
status_code = status.HTTP_401_UNAUTHORIZED
erreurs.append(f"Echec de l'authentification")
else:
# kullanıcının bulunduğu oturuma not ediliyor
session["user"] = True
# işlem tamamlandı
if not erreur:
# hata olmadan geri dönülür
résultat = {"action": action, "état": 200, "réponse": f"Authentification réussie"}
return résultat, status.HTTP_200_OK
else:
# hata ile geri dönüş
return {"action": action, "état": 201, "réponse": erreurs}, status_code
- 14. satır: POST'in parametreleri alınır;
- 19. satır: istekte bulunan hata listesi;
- 20-24. satırlar: gönderilen iki parametrenin varlığı kontrol edilir;
- 27-31. satırlar: [users] parametresinin varlığı kontrol edilir;
- satır 32-36: [password] parametresinin varlığı kontrol edilir;
- satır 38-39: gönderilen parametreler hatalıysa, HTTP 400 BAD REQUEST yanıtı hazırlanır;
- satır 40-58: [user, password] kimlik bilgilerinin, uygulamayı kullanma yetkisi olan bir kullanıcıya ait olup olmadığı kontrol edilir;
- 51-55. satırlar: Kullanıcı (kullanıcı adı, şifre) uygulamayı kullanma yetkisine sahip değilse, HTTP 401 UNAUTHORIZED yanıtı hazırlanır;
- satır 56-58: yetkili ise, oturumda [user] anahtarıyla kimliği doğrulandığı kaydedilir;
Şunu unutmayalım: Kullanıcı [identifiants1] kimlik bilgileriyle kimlik doğrulaması yapmışsa ve [identifiants2] kimlik bilgileriyle kimlik doğrulamasında başarısız olursa, yine de [identifiants1] kimlik bilgileriyle kimlik doğrulaması yapılmış olarak kalır.
Postman ile testler yapalım:
- web sunucusunu, SGBD'i ve e-posta sunucusunu başlatalım;
- Postman istemcisiyle:
- jSON oturumu başlatın;
- ardından kimlik doğrulaması yapıyoruz;
İşte farklı senaryolar.
Durum 1: POST, POST parametreleri olmadan

- [3-5]'te, POST'in gövdesi yoktur;
İsteğin sonucu şöyledir:

- [2]'te, HTTP 400 BAD REQUEST yanıtı alındı;
- [5]'e gönderdiğimizde, [201] hata kodu aldık;
Durum 2: Hatalı kimlik bilgileriyle POST

- [6]'te kimlik bilgileri hatalı;
Sunucu şu yanıtı gönderiyor:

- [2]'te, yanıt HTTP 401 UNAUTHORIZED;
- [5]'te hata yanıtı;
Durum 2: Doğru kimlik bilgileriyle POST

- [6]'te kimlik bilgileri doğrudur;
Sunucunun yanıtı şöyledir:
- [2]'te, HTTP 200 OK yanıtı;
- [5]'te, başarı yanıtı;
30.11. [calculer_impot] eylemi
[calculer_impot] eylemi, bir vergi mükellefinin vergisini hesaplamaya yarar. Bu eylemin yolu, [main] komut dosyasında şu şekilde tanımlanmıştır:
# vergi hesapla
@app.route('/calculer-impot', methods=['POST'])
def calculer_impot() -> tuple:
# eylemle ilişkili denetleyici çalıştırılıyor
return front_controller()
Sunucu, üç adet POST parametresi bekler:
- [marié]: evet / hayır;
- [enfants]: vergi mükellefinin çocuk sayısı;
- [salaire]: vergi mükellefinin yıllık maaşı;
[CalculerImpotController] denetleyicisi, [calculer_impot] eylemini işler:
import re
from flask_api import status
from werkzeug.local import LocalProxy
from InterfaceController import InterfaceController
from TaxPayer import TaxPayer
class CalculerImpotController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# yolun öğeleri alınır
dummy, action = request.path.split('/')
# başlangıçta hata yok
erreur = False
erreurs = []
# POST parametreleri
post_params = request.form
# Üç parametreli bir POST gerekiyor
if len(post_params) != 3:
erreur = True
erreurs.append(
"méthode POST requise avec les paramètres postés [marié, enfants, salaire]")
# gönderilen parametreler analiz ediliyor
if not erreur:
# parametre eşleştirildi
marié = post_params.get("marié")
if marié is None:
erreurs.append("paramètre [marié] manquant")
else:
# parametre geçerli mi?
marié = marié.lower()
if marié != "oui" and marié != "non":
erreur = True
erreurs.append(f"valeur [{marié}] invalide pour le paramètre [marié (oui/non)]")
# [enfants] parametresi
enfants = post_params.get("enfants")
if enfants is None:
erreur = True
erreurs.append("paramètre [enfants] manquant")
else:
# parametre geçerli mi?
enfants = enfants.strip()
match = re.match(r"\d+", enfants)
if not match:
erreur = True
erreurs.append(f"valeur [{enfants}] invalide pour le paramètre [enfants (entier>=0)]")
# maaş parametresi
salaire = post_params.get("salaire")
if salaire is None:
erreur = True
erreurs.append("paramètre [salaire] manquant")
else:
# parametre geçerli mi?
salaire = salaire.strip()
match = re.match(r"\d+", salaire)
if not match:
erreur = True
erreurs.append(f"valeur [{salaire}] invalide pour le paramètre [salaire (entier>=0)]")
# hata mı?
if erreur:
status_code = status.HTTP_400_BAD_REQUEST
résultat = {"action": action, "état": 301, "réponse": erreurs}
# sonuç veriliyor
return résultat, status_code
# vergi hesaplaması
# [métier] katmanını ve [adminData] sözlüğünü alıyoruz
métier = config["layers"]["métier"]
admin_data = config["admindata"]
# vergi hesaplaması
taxpayer = TaxPayer().fromdict({'marié': marié, 'enfants': enfants, 'salaire': salaire})
métier.calculate_tax(taxpayer, admin_data)
# simülasyon numarası
id_simulation = session.get('id_simulation', 0)
id_simulation += 1
session['id_simulation'] = id_simulation
# sonuç, TaxPayer sözlüğü biçiminde oturuma eklenir
simulation = taxpayer.fromdict({'id': id_simulation}).asdict()
# sonuç, daha önce yapılmış simülasyonlar listesine eklenir ve bu liste oturuma kaydedilir
simulations = session.get("simulations", [])
simulations.append(simulation)
session["simulations"] = simulations
# sonuç
résultat = {"action": action, "état": 300, "réponse": simulation}
status_code = status.HTTP_200_OK
# sonuç görüntülenir
return résultat, status_code
- 13. satır: işlenmekte olan işlemin adı alınır;
- 17. satır: hataları bir listede biriktirilir;
- 19. satır: gönderilen parametreler alınır. Bunlar [x-www-form-urlencoded] biçiminde gönderilir ve bu nedenle [request.form]'te alınırlar. Eğer jSON biçiminde gönderilmiş olsalardı, bunları [request.data] olarak alırdık;
- 21-24. satırlar: Gönderilen üç parametrenin varlığı kontrol edilir;
- 27-36. satırlar: Gönderilen [marié] parametresinin varlığı ve geçerliliği kontrol edilir;
- satır 37-48: gönderilen [enfants] parametresinin varlığı ve geçerliliği kontrol edilir;
- satır 49-60: gönderilen [salaire] parametresinin varlığı ve geçerliliği kontrol edilir;
- satır 62-66: hata oluşmuşsa, BAD REQUEST hata yanıtı, [301] durum koduyla birlikte gönderilir;
- satır 69-71: Hata yoksa, vergi hesaplamasına hazırlanılır. Bunun için,
- 70. satır: [métier] katmanından bir referans alınır;
- satır 71: sunucu yapılandırmasından vergi dairesine ait veriler alınır;
- satır 72-74: vergi mükellefinin vergisi hesaplanır;
- 75-77. satırlar: kullanıcı tarafından yapılan vergi hesaplamalarının sayısı sayılır;
- 76. satır: oturumdan yapılan son hesaplamanın numarası alınır. Burada bir hesaplamanın sonucuna [simulation] adı verilir;
- 77. satır: Son simülasyon numarası artırılır;
- satır 78: bu numara oturuma kaydedilir;
- satır 79-84: Kullanıcının yaptığı hesaplamaları takip etmek için, yaptığı simülasyonların listesini oturumuna kaydedeceğiz;
- satır 80: bir simülasyon, TaxPayer nesnesinin sözlüğü olacak ve bu nesnenin [id] özelliğinin değeri simülasyon numarası olacaktır;
- satır 82-84: mevcut simülasyon, oturumda bulunan simülasyon listesine eklenir;
- satır 86-87: başarılı bir HTTP yanıtı hazırlanır;
- 90. satır: sonuç döndürülür;
Birkaç test yapalım: web sunucusu, SGBD, e-posta sunucusu ve bir Postman istemcisi başlatılır.
Durum 1: Oturum başlatılmamışken vergi hesaplaması yapmak

Yanıt şu şekildedir:

Durum 2: Kimlik doğrulaması yapılmadan vergi hesaplaması yapmak
Öncelikle [/init-session/json] ile bir oturum açıyoruz (jSON). Ardından, önceki ile aynı sorguyu yapıyoruz. Bu durumda yanıt şöyledir:

Durum 3: Eksik parametrelerle vergi hesaplaması yapmak
jSON oturumunu başlatıp kimlik doğrulamasını yaptıktan sonra şu sorguyu gerçekleştiriyoruz:

- [5]'te [marié] parametresi eksiktir;
Yanıt şu şekildedir:
Durum 4: Hatalı parametrelerle vergi hesaplaması yapmak


Sunucudan gelen yanıt şöyledir:

Durum 4: Doğru parametrelerle vergi hesaplaması yapmak

Sunucudan gelen yanıt şöyledir:

30.12. [lister-simulations] eylemi
[lister-simulations] eylemi, bir kullanıcının oturumun başlangıcından itibaren yaptığı simülasyonların listesini görmesini sağlar. Bu eylemin yolu, [main] komut dosyasında şu şekilde tanımlanmıştır:
# simülasyonları listele
@app.route('/lister-simulations', methods=['GET'])
def lister_simulations() -> tuple:
# eyleme ilişkin denetleyiciyi çalıştırıyoruz
return front_controller()
Sunucu herhangi bir parametre beklemez. [lister-simulations] eylemi, aşağıdaki [ListerSimulationsController] denetleyicisi tarafından işlenir:
from flask_api import status
from werkzeug.local import LocalProxy
from InterfaceController import InterfaceController
class ListerSimulationsController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# yolun öğelerini alınıyor
dummy, action = request.path.split('/')
# oturumdaki simülasyon listesi alınır
simulations = session.get("simulations", [])
# sonuç görüntülenir
return {"action": action, "état": 500,
"réponse": simulations}, status.HTTP_200_OK
- 13. satır: simülasyon listesi oturumdan alınır;
- 15-16. satırlar: başarılı bir yanıt döndürülür;
Aşağıdaki Postman testini yapalım:
- jSON oturumu başlatılır;
- kimlik doğrulaması yapıyoruz;
- iki vergi hesaplaması yapıyoruz;
- simülasyon listesini istiyoruz;
İstek şu şekildedir:
- [3]'te hiçbir parametre yoktur;
Sunucunun yanıtı şöyledir:

- [4]'te, kullanıcının simülasyon listesi;
30.13. [supprimer-simulation] eylemi
[supprimer-simulation] eylemi, bir kullanıcının simülasyon listesinden bir simülasyonu silmesine olanak tanır. Bu eylemin yolu, [main] komut dosyasında şu şekilde tanımlanmıştır:
# simülasyonu sil
@app.route('/supprimer-simulation/<int:numero>', methods=['GET'])
def supprimer_simulation(numero: int) -> tuple:
# eyleme ilişkin denetleyici çalıştırılır
return front_controller()
Sunucu, silinecek simülasyonun numarası olmak üzere tek bir parametre bekler. [supprimer-simulation] eylemi, aşağıdaki [SupprimerSimulationController] denetleyicisi tarafından işlenir:
from flask_api import status
from werkzeug.local import LocalProxy
from InterfaceController import InterfaceController
class SupprimerSimulationController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# yolun öğelerini alınıyor
dummy, action, numéro = request.path.split('/')
# [numéro] parametresi, yoluna göre pozitif bir tamsayı veya sıfırdır
numéro = int(numéro)
# id=numara simülasyonu simülasyonlar listesinde mevcut olmalıdır
simulations = session.get("simulations", [])
liste_simulations = list(filter(lambda simulation: simulation['id'] == numéro, simulations))
if not liste_simulations:
msg_erreur = f"la simulation n° [{numéro}] n'existe pas"
# hata mesajı verilir
return {"action": action, "état": 601, "réponse": [msg_erreur]}, status.HTTP_400_BAD_REQUEST
# id=numara simülasyonu siliniyor
simulation = liste_simulations.pop(0)
simulations.remove(simulation)
# simülasyonlar oturuma geri ekleniyor
session["simulations"] = simulations
# sonuç veriliyor
return {"action": action, "état": 600, "réponse": simulations}, status.HTTP_200_OK
- 10. satır: İsteğin yolundaki iki öğe alınır. Bunlar karakter dizisi olarak alınır;
- 13. satır: [numéro] parametresi tamsayıya dönüştürülür. Bunun mümkün olduğu, yolunun imzası sayesinde bilinmektedir,
@app.route('/supprimer-simulation/<int:numero>', methods=['GET'])
Ayrıca bunun >=0 olan bir tamsayı olduğunu biliyoruz. Nitekim URL veya [/supprimer-simulation/-4] gibi değerler olamaz. Bu tür değerler Flask sunucusu tarafından reddedilir;
- 15. satır: oturumdaki simülasyon listesi alınır;
- 16. satır: [filter] işleviyle, id==numara olan simülasyon aranır. [filter] nesnesi elde edilir ve bu nesne [list] türüne dönüştürülür;
- satır 17-20: Filtre hiçbir sonuç getirmediyse, silinecek simülasyon mevcut değildir. Bunu belirten bir hata yanıtı döndürülür;
- satır 21-23: filtre tarafından getirilen simülasyon silinir;
- 25. satır: Yeni simülasyon listesi oturuma eklenir;
- 27. satır: yanıtta yeni simülasyon listesi döndürülür;
Bir başarı testi ve bir başarısızlık testi yapıyoruz. Simülasyonlar gerçekleştirildikten sonra simülasyon listesi isteniyor:

- burada simülasyonların numaraları 2 ve 3'tür;
3 numaralı simülasyonun silinmesini istiyoruz.

Yanıt şu şekildedir:
Şimdi aynı işlemi (id=3 olan simülasyonun silinmesi) tekrar yapalım. Bu durumda yanıt şöyledir:


30.14. [fin-session] eylemi
[fin-session] eylemi, bir kullanıcının simülasyon oturumunu sonlandırmasına olanak tanır. Bu eylemin yolu, [main] komut dosyasında şu şekilde tanımlanmıştır:
# oturum sonu
@app.route('/fin-session', methods=['GET'])
def fin_session() -> tuple:
# eyleme ilişkin denetleyici çalıştırılıyor
return front_controller()
Sunucu herhangi bir parametre beklemez. Eylem, aşağıdaki [FinSessionController] denetleyicisi tarafından işlenir:
from flask_api import status
from werkzeug.local import LocalProxy
from InterfaceController import InterfaceController
class FinSessionController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# yolun öğelerini alın
dummy, action = request.path.split('/')
# geçerli oturumdaki tüm anahtarları silinir
session.clear()
# sonuç döndürülür
return {"action": action, "état": 400, "réponse": "session réinitialisée"}, status.HTTP_200_OK
- 13. satır: Oturumdaki tüm anahtarlar silinir. Bu işlem şunları siler:
- [typeResponse]: HTTP yanıtlarının türü (json, xml, html);
- [id_simulation]: yapılan son simülasyonun numarası;
- [simulations]: kullanıcının simülasyon listesi;
- [user]: kullanıcının kimlik doğrulamasının yapıldığını gösteren gösterge;
- yanıt gönderilir;
Artık yanıt türü oturumda bulunmadığına göre, 15. satırdaki HTTP yanıtının nasıl döndürüleceği merak edilebilir. Bunu öğrenmek için, ana komut dosyası [main]’teki |front_controller| işlevine geri dönüp şu şekilde değiştirmemiz gerekir:
…
# on not# bu bilgi oturumda mevcutsa istenen yanıt türünü not ediyoruz
type_response1 = session.get('typeResponse', None)
# isteği ana denetleyiciye iletilir
main_controller = config['controllers']["main-controller"]
résultat, status_code = main_controller.execute(request, session, config)
# müşteriye gönderilen sonuç günlüğe kaydedilir
log = f"[front_controller] {résultat}\n"
logger.write(log)
# Önemli bir hata oluştu mu?
if status_code == status.HTTP_500_INTERNAL_SERVER_ERROR:
# uygulama yöneticisine bir e-posta gönderilir
send_adminmail(config, log)
# yanıt için istenen tür belirlenir
type_response2=session.get('typeResponse')
if type_response2 is None and type_response1 is None:
# oturum türü henüz belirlenmedi - bu jSON olacaktır
type_response = 'json'
elif type_response2 is not None:
# yanıt türü biliniyor ve oturumda mevcut
type_response = type_response2
else:
type_response=type_response1
# gönderilecek yanıt oluşturuluyor
response_builder = config["responses"][type_response]
response, status_code = response_builder \
.build_http_response(request, session, config, status_code, résultat)
# yanıt gönderiliyor
return response, status_code
- 3. satır: Oturumda bulunan yanıt türü kaydedilir;
- 6. satır: eylem gerçekleştirilir. Eğer bu:
- [fin-session] ise, [typeResponse] anahtarı artık oturumda bulunmaz;
- [init-session] ise, oturumdaki [typeResponse] anahtarının değeri değişmiş olabilir;;
- 14-20. satırlar: HTTP yanıtı gönderilmelidir. Bunun hangi biçimde olacağını bilmemiz gerekir:
- 16-18. satırlar: Yanıt türü ne 3. satırdaki [type_response1] ne de 15. satırdaki [type_response2] ile tanımlanmışsa, yanıt türü eylemin ne öncesinde ne de sonrasında tanımlanmamıştır. Bu durumda jSON (satır 18) kullanılır;
- 19-21. satırlar: [type_response2] varsa, yani eylemden sonraki oturumdaki tür ise, bu tür kullanılmalıdır;
- 22-23. satırlar: aksi takdirde, eylemden önceki yanıt türü olan [type_response1] (bu, zorunlu olarak [fin-session]'tir) kullanılmalıdır;
30.15. [get-admindata] eylemi
Şimdi, jSON ve XML hizmetlerine ayrılmış iki URL'e geçiyoruz:
Eylem | Rol | Çalıştırma bağlamı |
/get-admindata | Vergi hesaplamasına olanak tanıyan vergi verilerini döndürür | GET isteği. Yalnızca oturum türü json veya xml ise kullanılır. Kullanıcının kimliği doğrulanmış olması gerekir |
/calculer-impots | jSON'e gönderilen vergi mükellefleri listesi için vergi hesaplaması yapar | GET isteği. Yalnızca oturum türü json veya xml ise kullanılır. Kullanıcının kimliği doğrulanmış olması gerekir |
URL ve [/get-admindata], ana komut dosyası [main]'in rotalarında şu şekilde tanımlanmıştır:
# get-admindata
@app.route('/get-admindata', methods=['GET'])
def get_admindata() -> tuple:
# eylemle ilişkili denetleyici çalıştırılır
return front_controller()
[/get-admindata] rotası, aşağıdaki [GetAdminDataController] denetleyicisi tarafından işlenir:
# bağımlılıklar içe aktarılır
from flask_api import status
from werkzeug.local import LocalProxy
from InterfaceController import InterfaceController
class GetAdminDataController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# yolun öğeleri alınır
dummy, action = request.path.split('/')
# yalnızca json ve xml oturumları kabul edilir
type_response = session.get('typeResponse')
if type_response != 'json' and type_response != 'xml':
# hata yanıtı döndürülür
return {
"action": action,
"état": 1001,
"réponse": ["cette action n'est possible que pour les sessions json ou xml"]
}, status.HTTP_400_BAD_REQUEST
else:
# başarılı bir yanıt verilir
return {"action": action, "état": 1000, "réponse": config["adminData"].asdict()}, status.HTTP_200_OK
- 13-21. satırlar: json veya xml oturumunda olup olmadığımız kontrol edilir;
- 24. satır: sunucunun başlatılmasıyla birlikte yapılandırmaya yerleştirilmiş olan vergi idaresi veri sözlüğü döndürülür:
# admindata, uygulama kapsamında salt okunur bir veri olacaktır
config["admindata"] = config["layers"]["dao"].get_admindata()
Bir Postman istemcisi açalım ve URL [/get-admindata]'i isteyelim; bunun için önce bir jSON oturumu başlatıp kimlik doğrulamasını yapmalıyız:

Sunucudan gelen yanıt şöyledir:

30.16. [calculer-impots] eylemi
[calculer-impots] eylemi, isteğin gövdesinde jSON biçiminde bir dize olarak bulunan vergi mükellefleri listesinin vergisini hesaplar. Bu eylemi zaten biliyoruz: önceki sürümde [calculate_tax_in_bulk_mode] olarak adlandırılmıştı.
İş akışı şu şekildedir:
# vergi hesaplaması toplu olarak yapılır
@app.route('/calculer-impots', methods=['POST'])
def calculer_impots():
# eylemle ilişkili denetleyici çalıştırılıyor
return front_controller()
Bu eylem, aşağıdaki [CalculerImpotsController] denetleyicisi tarafından işlenir:
import json
from flask_api import status
from werkzeug.local import LocalProxy
from ImpôtsError import ImpôtsError
from InterfaceController import InterfaceController
from TaxPayer import TaxPayer
class CalculerImpotsController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# yolun öğeleri alınır
dummy, action = request.path.split('/')
# yalnızca json ve xml oturumları kabul edilir
type_response = session.get('typeResponse')
if type_response != 'json' and type_response != 'xml':
# bir hata yanıtı döndürülür
return {
"action": action,
"état": 1501,
"réponse": ["cette action n'est possible que pour les sessions json ou xml"]
}, status.HTTP_400_BAD_REQUEST
# POST isteğinin gövdesi alınır - bir sözlük listesi beklenir
msg_erreur = None
list_dict_taxpayers = None
# POST'in gövdesi jSON
request_text = request.data
try:
# bunu bir sözlük listesine dönüştürüyoruz
list_dict_taxpayers = json.loads(request_text)
except BaseException as erreur:
# hata not edilir
msg_erreur = f"le corps du POST n'est pas une chaîne jSON valide : {erreur}"
# boş olmayan bir liste var mı?
if not msg_erreur and (not isinstance(list_dict_taxpayers, list) or len(list_dict_taxpayers) == 0):
# hatayı not ediyoruz
msg_erreur = "le corps du POST n'est pas une liste ou alors cette liste est vide"
# Sözlük listesi var mı?
if not msg_erreur:
erreur = False
i = 0
while not erreur and i < len(list_dict_taxpayers):
erreur = not isinstance(list_dict_taxpayers[i], dict)
i += 1
# hata mı?
if erreur:
msg_erreur = "le corps du POST doit être une liste de dictionnaires"
# hata mı?
if msg_erreur:
# müşteriye bir hata yanıtı gönderiliyor
résultats = {"action": action, "état": 1501, "réponse": [msg_erreur]}
return résultats, status.HTTP_400_BAD_REQUEST
# TaxPayers değerleri tek tek kontrol ediliyor
# başlangıçta hata yok
list_erreurs = []
for dict_taxpayer in list_dict_taxpayers:
# dict_taxpayer'ten bir TaxPayer oluşturulur
msg_erreur = None
try:
# Aşağıdaki işlem, parametrelerin
# TaxPayer sınıfının özellikleri ve bunların değerlerinin
# değeri yanlış olan durumları
TaxPayer().fromdict(dict_taxpayer)
except BaseException as erreur:
msg_erreur = f"{erreur}"
# sözlükte belirli anahtarların bulunması gerekir
if not msg_erreur:
# [marié, enfants, salaire] anahtarları sözlükte bulunmalıdır
keys = dict_taxpayer.keys()
if 'marié' not in keys or 'enfants' not in keys or 'salaire' not in keys:
msg_erreur = "le dictionnaire doit inclure les clés [marié, enfants, salaire]"
# hatalar mı var?
if msg_erreur:
# Hata, TaxPayer'in kendisinde görülmektedir
dict_taxpayer['erreur'] = msg_erreur
# TaxPayer hatalar listesine ekleniyor
list_erreurs.append(dict_taxpayer)
# Tüm vergi mükellefleri işlendi - hata var mı?
if list_erreurs:
# müşteriye bir hata yanıtı gönderiliyor
résultats = {"action": action, "état": 1501, "réponse": list_erreurs}
return résultats, status.HTTP_400_BAD_REQUEST
# hata yok, çalışmaya devam edilebilir
# Vergi dairesinden veriler alınmaktadır
admindata = config["admindata"]
métier = config["layers"]["métier"]
try:
# TaxPayer'leri tek tek işleniyor
list_taxpayers = []
for dict_taxpayer in list_dict_taxpayers:
# vergi hesaplaması
taxpayer = TaxPayer().fromdict(
{'marié': dict_taxpayer['marié'], 'enfants': dict_taxpayer['enfants'],
'maaş': dict_taxpayer['salaire']})
métier.calculate_tax(taxpayer, admindata)
# sonucu bir sözlük olarak saklayalım
list_taxpayers.append(taxpayer.asdict())
# list_taxpayers'i mevcut simülasyonlara eklenir ve her simülasyona bir numara verilir
simulations = session.get("simulations", [])
id_simulation = session.get("id_simulation", 0)
for simulation in list_taxpayers:
# her simülasyona bir numara verilir
id_simulation += 1
simulation['id'] = id_simulation
# mevcut simülasyon listesine eklenir
simulations.append(simulation)
# her şeyi oturuma geri yüklenir
session["simulations"] = simulations
session["id_simulation"] = id_simulation
# yanıt müşteriye gönderilir
return {"action": action, "état": 1500, "réponse": list_taxpayers}, status.HTTP_200_OK
except ImpôtsError as erreur:
# müşteriye bir hata yanıtı gönderilir
return {"action": action, "état": 1501, "réponse": [f"{erreur}"]}, status.HTTP_500_INTERNAL_SERVER_ERROR
- 16-24. satırlar: json veya xml oturumunda olup olmadığımız kontrol edilir
- 26-120. satırlar: Bu kod genel olarak bize aşinadır. Bu, uygulamanın 10. sürümündeki |index_controller| işlevine aittir ve uygulanan [InterfaceController] arayüzünün özelliklerine uyacak şekilde düzenlenmiştir;
- 104-115. satırlar: Bu denetleyicinin yeni ortamını dikkate almak için eklenen kod. Az önce vergi hesaplamaları yaptık. Sonuçları, oturumda tutulan simülasyonlar listesine kaydetmemiz gerekiyor;
- satır 105: oturumdaki simülasyon listesi alınır;
- satır 106: yapılan son simülasyonun numarası alınır;
- 107-112. satırlar: Vergi hesaplama sonuçlarına ait dizinler listesi taranır; her birine [id] numaralı bir simülasyon numarası atanır ve her dizin simülasyonlar listesine eklenir;
- 113-115. satırlar: Yeni simülasyon listesi ve yapılan son simülasyonun numarası oturuma aktarılır;
Bir jSON oturumu başlatıp kimlik doğrulamasını yaptıktan sonra aşağıdaki Postman testini gerçekleştiriyoruz:


Sunucudan gelen yanıt şöyledir:

Şimdi simülasyon listesini talep edersek:
[/calcul-impots] sonuç listesinde, vergi mükelleflerinin [id] özniteliğine sahip olmadığı görülürken, simülasyonlar listesinde her simülasyonun kendisini tanımlayan bir numarası olduğu fark edilir.




