23. تمرين تدريبي: الإصدار 6
23.1. مقدمة
نعود الآن إلى تطبيق حساب الضرائب الخاص بنا. سنقوم بإنشاء العديد من تطبيقات الويب حوله.
في الإصدار 5 من تمرين التطبيق الخاص بنا، تم تخزين بيانات مصلحة الضرائب في قاعدة بيانات. كان هذا الإصدار 5 يتألف من تطبيقين منفصلين يشتركان في طبقات مشتركة:
- تطبيق يحسب الضرائب في الوضع |الدفعي| للمكلفين المخزنين في ملف نصي؛
- تطبيق يحسب الضرائب في الوضع |التفاعلي| للمكلفين الذين تم إدخال معلوماتهم عبر لوحة المفاتيح؛
كان الإصدار 5 من تطبيق حساب الضرائب الدفعي يتمتع بالبنية التالية:

في النهاية، سيكون للإصدار الويب من هذا التطبيق البنية التالية:

- يتواصل عميل الويب [1] مع خادم الويب [2]، الذي يتواصل بدوره مع نظام إدارة قواعد البيانات [3]؛
- يحتفظ خادم الويب [2] بطبقتي [الأعمال] [8] و[DAO] [9] من التطبيق الأصلي؛
- يحتفظ التطبيق الأصلي بنصه البرمجي الرئيسي [4] وطبقة [الأعمال] الخاصة به [15]. طبقتا [الأعمال] [8] و[15] متطابقتان؛
- يتطلب الاتصال بين العميل والخادم طبقتين إضافيتين:
- طبقة [الويب] [7]، التي تنفذ تطبيق الويب؛
- طبقة [DAO] [5]، التي تعمل كعميل لتطبيق الويب [7]؛
في الإصدار النهائي، يمكن إجراء حساب الضرائب الدفعي بطريقتين:
- تتولى طبقة [الأعمال] في الخادم معالجة منطق الأعمال الخاص بحساب الضرائب. سيستخدم البرنامج النصي [الرئيسي] هذه الطريقة؛
- يتم التعامل مع منطق الأعمال لحساب الضرائب بواسطة طبقة [الأعمال] الخاصة بالعميل. سيستخدم البرنامج النصي [main2] هذه الطريقة؛
من الآن فصاعدًا، سنقوم بتطوير العديد من تطبيقات العميل/الخادم من النوع الموصوف أعلاه، كل منها يوضح تقنية أو أكثر من تقنيات تطوير الويب الجديدة.
23.2. خادم الويب لحساب الضرائب
23.2.1. الإصدار 1

النص البرمجي [server_01] هو تطبيق الويب التالي:

- في [1]، نستخدم عنوان URL معلماتي نمرر فيه ثلاث قيم:
- [married] (نعم/لا) للإشارة إلى ما إذا كان دافع الضرائب متزوجًا أم لا؛
- [children]: عدد أطفال المكلف؛
- [salary]: الراتب السنوي للمكلف؛
- في [2]، يعرض خادم الويب سلسلة JSON تحدد مبلغ الضريبة المستحقة مع مكوناتها المختلفة؛
بنية التطبيق هي كما يلي:

- يقوم المتصفح [1] بالاستعلام عن الخادم [2]. يقوم البرنامج النصي [server_01] بتنفيذ الطبقة [الويب] [2] للخادم؛
- الطبقات [3-8] هي تلك المستخدمة بالفعل في |الإصدار 5| من تطبيق حساب الضرائب. نعيد استخدامها كما هي؛
- يتم تعريف طبقة [الأعمال] [3] |هنا|؛
- يتم تعريف طبقة [DAO] [4] |هنا|؛
يتم تكوين تطبيق الويب [server_01] باستخدام ثلاثة نصوص برمجية:
- [config]، الذي يقوم بتكوين التطبيق بأكمله؛
- [config_database]، الذي يقوم بتكوين الوصول إلى قاعدة البيانات. سنعمل مع أنظمة إدارة قواعد البيانات MySQL و PostgreSQL؛
- [config_layers]، الذي يقوم بتكوين طبقات التطبيق؛
النص البرمجي [config] هو كما يلي:
- تأخذ الدالة [configure] قاموس [config] كحجة (السطر 1) وتعيده كنتيجة (السطر 54) بعد إثراء محتوياته. كان من الممكن الإشارة منذ وقت طويل إلى أنه لم يكن من الضروري إعادة النتيجة [config]. في الواقع، [config] هو مرجع قاموس يشترك فيه الكود المستدعي مع الكود المستدعى. وبالتالي، فإن الكود المستدعي يمتلك هذا المرجع بالفعل (السطر 1)، ولا داعي لإرجاعه مرة أخرى (السطر 54). وبالتالي، فإن كتابة:
config=[module].configure(config) (1)
أمر زائد عن الحاجة. يكفي كتابة:
[module].configure(config) (2)
ومع ذلك، احتفظت بأسلوب الكتابة (1) لأنني اعتقدت أنه قد يوضح بشكل أفضل أن الكود الذي تم استدعاؤه يقوم بتعديل قاموس [config].
- السطر 1: يحتوي قاموس [config] الذي تستلمه الدالة [configure] على مفتاح "sgbd" يتم أخذ قيمته من القائمة ["mysql"، "pgres"]. يعني [mysql] أن قاعدة البيانات المستخدمة تدار بواسطة MySQL، بينما يعني "pgres" أن قاعدة البيانات المستخدمة تدار بواسطة PostgreSQL؛
- الأسطر 4–27: نسرد جميع الدلائل التي تحتوي على العناصر الضرورية لتطبيق الويب. وستكون هذه الدلائل جزءًا من مسار Python الخاص بالتطبيق (الأسطر 30–31)؛
- الأسطر 33–40: سيُسمح لمستخدمين معينين فقط بالوصول إلى التطبيق. هنا، لدينا قائمة تحتوي على مستخدم واحد؛
- الأسطر 43–46: يقوم البرنامج النصي [config_database] بإنشاء التكوين لقاعدة البيانات المستخدمة؛
- السطر 46: التكوين الذي أنشأه البرنامج النصي [config_database] هو قاموس نقوم بتخزينه في التكوين العام المرتبط بمفتاح "database"؛
- الأسطر 48–51: يقوم البرنامج النصي [config_layers] بإنشاء مثيلات لطبقات تطبيق الويب. ويعيد قاموسًا يتم تخزينه في التكوين العام تحت مفتاح "layers"؛
البرنامج النصي [config_database] هو نفسه المستخدم بالفعل في |الإصدار 5|. ونورده هنا كمرجع:
يقوم البرنامج النصي [config_layers] بتكوين طبقات خادم الويب. نعيد استخدام |script| الذي رأيناه من قبل:
- السطر 6: يتم تنفيذ طبقة [dao] باستخدام قاعدة بيانات؛
- تم تعريف [ImpotsDaoWithAdminDataInDatabase] |هنا|؛
- تم تعريف [BusinessTaxes] |هنا|؛
النص البرمجي الرئيسي [server_01] هو كما يلي:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | |
- الأسطر 1–10: استرداد المعلمة التي تشير إلى نظام إدارة قواعد البيانات (DBMS) المطلوب استخدامه؛
- الأسطر 12–14: باستخدام هذه المعلومات، يمكننا تكوين التطبيق. على وجه الخصوص، يتم إنشاء مسار Python؛
- الأسطر 16–23: باستخدام مسار Python الجديد، نقوم باستيراد الوحدات النمطية الضرورية؛
- الأسطر 25–31: استرداد البيانات من مصلحة الضرائب لحساب الضريبة؛
- الأسطر 33–34: إنشاء مثيل لتطبيق Flask؛
- السطر 38: لا يخدم تطبيق Flask سوى عنوان URL [/]. ويتوقع عنوان URL بتنسيق كما يلي: [/ ?married=xx&children=yy&salary=zz]، حيث:
- xx: نعم / لا؛
- yy: عدد الأطفال؛
- zz: الراتب السنوي؛
- السطور 40-89: نتحقق من صحة معلمات عنوان URL؛
- السطر 41: سنقوم بتجميع رسائل الخطأ في قائمة [errors]؛
- السطر 43: قد تتذكر أن معلمات عنوان URL موجودة في [request.args] (انظر |هنا|):
- كائن [request] هو كائن Flask الذي تم استيراده في السطر 20؛
- يعمل الكائن [request.args] كقاموس؛
- الأسطر 43-44: نتحقق من وجود ثلاثة معلمات بالضبط (لا أقل ولا أكثر)؛
- الأسطر 46-49: نتحقق من وجود المعلمة [married] في عنوان URL؛
- الأسطر 50–54: إذا كانت موجودة، نتحقق من أن قيمتها بالخط الصغير، بعد حذف المسافات البيضاء في البداية والنهاية، هي "yes" أو "no"؛
- الأسطر 56-59: نتحقق من وجود المعلمة [children] في عنوان URL؛
- الأسطر 60–66: إذا كانت موجودة، نتحقق من أن قيمتها عدد صحيح موجب؛
- السطر 66: تذكر أن معلمات URL وقيمها هي سلاسل نصية. يتم تحويل قيمة المعلمة [children] إلى "int"؛
- الأسطر 68-78: بالنسبة لمعلمة [salary]، نقوم بإجراء نفس الفحوصات التي أجريناها لمعلمة [children]؛
- الأسطر 81–83: نتحقق من عدم وجود معلمات أخرى غير [‘married’، ‘children’، ‘salary’] في عنوان URL؛
- الأسطر 85–89: إذا لم تكن قائمة [errors] فارغة بعد كل هذه الفحوصات، نرسل قائمة الأخطاء هذه إلى العميل كسلسلة JSON مع رمز الحالة [400 Bad Request]؛
نظرًا لأننا سنحتاج غالبًا إلى إرسال سلسلة JSON ردًا على العميل لاحقًا، فقد تم تضمين الأسطر القليلة المطلوبة لذلك في الوحدة النمطية [myutils.py] التي استخدمناها بالفعل:

يصبح البرنامج النصي [myutils.py] كما يلي:
- السطر 16: تتوقع الدالة [json_response] معلمتين:
- [response]: القاموس الذي يحتوي على سلسلة JSON المراد إرسالها إلى عميل الويب؛
- [status_code]: رمز حالة HTTP الخاص بالاستجابة؛
- السطر 18: نحدد نص JSON للاستجابة؛
- السطر 20: نضيف رأس HTTP الذي يُخبر عميل الويب أنه سيتلقى JSON؛
- السطر 22: نرسل استجابة HTTP إلى الكود المستدعي. ويتعين على الكود المستدعي إرسالها إلى عميل الويب؛
يتغير ملف [__init__.py] على النحو التالي:
from .myutils import set_syspath, json_response
يتم تثبيت الإصدار الجديد من [myutils] ضمن الوحدات النمطية على مستوى الجهاز باستخدام الأمر [pip install .] في محطة PyCharm:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\packages>pip install .
Processing c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\packages
Using legacy setup.py install for myutils, since package 'wheel' is not installed.
Installing collected packages: myutils
Attempting uninstall: myutils
Found existing installation: myutils 0.1
Uninstalling myutils-0.1:
Successfully uninstalled myutils-0.1
Running setup.py install for myutils ... done
Successfully installed myutils-0.1
- السطر 1: يجب أن تكون في مجلد [packages] لإدخال هذا الأمر؛
يستمر كود البرنامج النصي [server_01] على النحو التالي:
- السطر 10: في هذه المرحلة، تكون المعلمات المتوقعة في عنوان URL موجودة وصحيحة؛
- السطر 10: نقوم بإنشاء كائن [TaxPayer] الذي يمثل نموذجًا للمكلف؛
- السطر 11: نطلب من طبقة [business] حساب الضريبة. لاحظ أن العناصر التي تحسبها طبقة [business] يتم إدراجها في كائن [taxpayer] الذي يتم تمريره كمعلمة؛
- السطر 13: يتم إرسال الاستجابة إلى عميل الويب كسلسلة JSON. هذه هي سلسلة JSON لقاموس. نربط القاموس الخاص بكائن [taxpayer] بالمفتاح [result]. لم نتمكن من وضع كائن [taxpayer] نفسه لأنه غير قابل للتسلسل في JSON؛
نقوم بإنشاء تكوينين للتنفيذ، أحدهما لـ MySQL والآخر لـ PostgreSQL:

فيما يلي بعض أمثلة التنفيذ (لقد قمت بتشغيل تطبيق [server_01] ونظام إدارة قواعد البيانات، ثم طلبت عنوان URLhttp://localhost:5000/ باستخدام متصفح):


فيما يلي مثال على الطلب في وحدة التحكم Postman:

GET /?mari%C3%A9=xx&enfants=yy&salaire=zz HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: e4c5df8c-4bd6-4250-b789-b7b164db4eff
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
HTTP/1.0 400 BAD REQUEST
Content-Type: application/json; charset=utf-8
Content-Length: 134
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Fri, 17 Jul 2020 06:15:44 GMT
{"réponse": {"erreurs": ["paramètre marié [xx] invalide", "paramètre enfants [yy] invalide", "paramètre salaire [zz] invalide"]}}
- السطر 1: تم طلب عنوان URL غير صحيح؛
- السطر 10: يستجيب الخادم بالحالة 400 BAD REQUEST؛
23.2.2. الإصدار 2

يعزل الإصدار 2 من الخادم معالجة عناوين URL في وحدة [index_controller] [5]:
- السطر 9: تتلقى الدالة [execute] معلمتين:
- [request]: طلب HTTP الخاص بالعميل؛
- [config]: قاموس تكوين التطبيق؛
النص البرمجي [server_02] هو كما يلي:
- الأسطر 36–41: معالجة المسار /؛
- السطر 39: استخدام الدالة [IndexController.execute]؛
سنستخدم الآن هذه التقنية: سيتم التعامل مع كل مسار بواسطة الوحدة النمطية الخاصة به.
نتائج التنفيذ هي نفسها كما في الإصدار 1.
23.2.3. الإصدار 3
تقدم النسخة 3 مفهوم المصادقة.
يصبح البرنامج النصي [server_03] كما يلي:
- السطر 21: استيراد معالج المصادقة. هناك أنواع مختلفة من المصادقة لخادم الويب. النوع الذي نستخدمه هنا يسمى [HTTP Basic]. يتبع كل نوع من أنواع المصادقة حوارًا محددًا بين العميل والخادم؛
- السطر 33: إنشاء مثيل لمعالج المصادقة؛
- السطر 37: تشير التعليقة التوضيحية [@auth.verify_password] إلى الدالة التي سيتم تنفيذها عندما يرغب معالج المصادقة في التحقق من اسم المستخدم وكلمة المرور المرسلة من العميل وفقًا لبروتوكول [HTTP Basic]؛
- السطر 55: تحدد التعليقة التوضيحية [@auth.login_required] مسارًا يجب أن يتم مصادقة عميل الويب عليه. إذا لم يكن عميل الويب قد أرسل بيانات اعتماده بعد، فسيطلبها خادم الويب تلقائيًا باستخدام بروتوكول HTTP Basic؛
يجب تثبيت الوحدة النمطية [flask_httpauth]:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\impots\http-servers\01\flask>pip install flask_httpauth
Collecting flask_httpauth
Downloading Flask_HTTPAuth-4.1.0-py2.py3-none-any.whl (5.8 kB)
Requirement already satisfied: Flask in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from flask_httpauth) (1.1.2)
Requirement already satisfied: itsdangerous>=0.24 in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from Flask->flask_httpauth) (1.1.0)
Requirement already satisfied: click>=5.1 in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from Flask->flask_httpauth) (7.1.2)
Requirement already satisfied: Jinja2>=2.10.1 in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from Flask->flask_httpauth) (2.11.2)
Requirement already satisfied: Werkzeug>=0.15 in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from Flask->flask_httpauth) (1.0.1)
Requirement already satisfied: MarkupSafe>=0.23 in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from Jinja2>=2.10.1->Flask->flask_httpauth) (1.1.1
)
Installing collected packages: flask-httpauth
Successfully installed flask-httpauth-4.1.0
لنرى ما يحدث في وحدة التحكم في Postman. أنت:
- قم بإنشاء تكوين تشغيل؛
- قم بتشغيل تطبيق الويب؛
- قم بتشغيل قاعدة البيانات التي تختارها؛
- اطلب عنوان URL [/] باستخدام Postman؛
يكون الحوار بين العميل والخادم في وحدة التحكم Postman كما يلي:
- السطر 10: يرد الخادم بأننا غير مخولين بالوصول إلى عنوان URL [/]؛
- السطر 13: يخبرنا ببروتوكول المصادقة الذي يجب استخدامه، وهو في هذه الحالة بروتوكول المصادقة الأساسية؛
من الممكن تكوين Postman لإرسال بيانات اعتماد المستخدم وفقًا لبروتوكول المصادقة الأساسية:

- في [6-7] ندخل بيانات الاعتماد الموجودة في البرنامج النصي [config]:
config['users'] = [
{
"login": "admin",
"password": "admin"
}
]
يصبح الحوار بين العميل والخادم في وحدة تحكم Postman كما يلي:
GET / HTTP/1.1
Authorization: Basic YWRtaW46YWRtaW4=
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: 5ce20822-e87c-4eef-a2f4-b9eaec38d881
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
HTTP/1.0 400 BAD REQUEST
Content-Type: application/json; charset=utf-8
Content-Length: 203
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Fri, 17 Jul 2020 07:20:01 GMT
{"réponse": {"erreurs": ["Méthode GET requise avec les seuls paramètres [marié, enfants, salaire]", "paramètre [marié] manquant", "paramètre [enfants] manquant", "paramètre [salaire] manquant"]}}
- السطر 2: يرسل عميل Postman بيانات اعتماد المستخدم [admin / admin] في شكل مشفر؛
- السطر 17: يستجيب الخادم بشكل صحيح. ويبلغ عن أخطاء لأن المعلمات [marié, enfants, salaire] لم يتم إرسالها (السطر 1)، لكنه لا يبلغ عن خطأ في المصادقة؛
الآن دعونا نطلب عنوان URL / باستخدام متصفح (Firefox أدناه):

- كما هو الحال مع Postman، تلقى Firefox استجابة HTTP من الخادم مع رؤوس HTTP التالية:
لا يوقف Firefox، مثل المتصفحات الأخرى، نافذة الحوار عند تلقيه لهذه الرؤوس. بل يطلب من المستخدم بيانات الاعتماد التي يطلبها الخادم. في المثال أعلاه، سيؤدي مجرد كتابة admin / admin إلى تلقي استجابة الخادم:

23.3. عميل الويب لخادم حساب الضرائب
23.3.1. مقدمة
في القسم السابق، كان عميل الويب لخادم حساب الضرائب عبارة عن متصفح. في هذا القسم، سيكون عميل الويب عبارة عن برنامج نصي للوحدة الطرفية. تصبح البنية كما يلي:

- يتكون عميل الويب من الطبقات [1-2]؛
- يتكون خادم الويب من الطبقات [3-9]. كما ذكرنا في القسم السابق؛
لذلك نحتاج إلى كتابة الطبقات [1-2].
يجب أن تكون الطبقة [dao] [2] قادرة على التواصل مع خادم الويب [3]. نحن الآن نفهم بروتوكول HTTP ويمكننا كتابة، باستخدام الوحدة النمطية [pycurl] التي درسناها بالفعل على سبيل المثال، برنامج نصي يتواصل مع خادم الويب [3]. ومع ذلك، هناك وحدات نمطية متخصصة في اتصال عميل/خادم HTTP. سنستخدم إحداها، وهي الوحدة النمطية [requests]:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\impots\http-servers\01\flask>pip install requests
Collecting requests
Downloading requests-2.24.0-py2.py3-none-any.whl (61 kB)
|| 61 kB 137 kB/s
Collecting idna<3,>=2.5
Downloading idna-2.10-py2.py3-none-any.whl (58 kB)
|| 58 kB 692 kB/s
Collecting chardet<4,>=3.0.2
Downloading chardet-3.0.4-py2.py3-none-any.whl (133 kB)
|| 133 kB 1.3 MB/s
Collecting urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1
Downloading urllib3-1.25.9-py2.py3-none-any.whl (126 kB)
|| 126 kB 1.1 MB/s
Collecting certifi>=2017.4.17
Downloading certifi-2020.6.20-py2.py3-none-any.whl (156 kB)
|| 156 kB 1.1 MB/s
Installing collected packages: idna, chardet, urllib3, certifi, requests
Successfully installed certifi-2020.6.20 chardet-3.0.4 idna-2.10 requests-2.24.0 urllib3-1.25.9
هيكل الدليل لبرامج نصية عميل الويب هو كما يلي:

ستقوم البرنامج النصي بتنفيذ تطبيق حساب الضرائب في الوضع الدفعي الموصوف في |الإصدار 1|. أحدث إصدار من هذا التطبيق هو |الإصدار 5|. فيما يلي تذكير بكيفية عمله:
- يتم سرد دافعي الضرائب الذين سيتم حساب الضريبة لهم في الملف النصي [taxpayersdata.txt]:
- يتم حفظ النتائج في ملفين:
- يُدرج الملف النصي [errors.txt] الأخطاء التي تم اكتشافها في ملف دافع الضرائب:
Analyse du fichier C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\impots\http-clients\01\main/../data/input/taxpayersdata.txt
Ligne 15, not enough values to unpack (expected 4, got 2)
Ligne 17, MyException[1, L'identifiant d'une entité <class 'TaxPayer.TaxPayer'> doit être un entier >=0]
- (تابع)
- يحتوي ملف JSON [results.json] على نتائج حساب الضرائب لمختلف دافعي الضرائب:
[
{
"id": 0,
"marié": "oui",
"enfants": 2,
"salaire": 55555,
"impôt": 2814,
"surcôte": 0,
"taux": 0.14,
"décôte": 0,
"réduction": 0
},
{
"id": 1,
"marié": "oui",
"enfants": 2,
"salaire": 50000,
"impôt": 1384,
"surcôte": 0,
"taux": 0.14,
"décôte": 384,
"réduction": 347
},
…
]
23.3.2. تكوين عميل الويب

يتم إجراء التكوين باستخدام نصين برمجيين:
- [config]، الذي يتولى جميع عمليات التكوين خارج طبقات البنية؛
- [config_layers]، الذي يتولى تكوين طبقات البنية؛
النص البرمجي [config] هو كما يلي:
- السطر 1: تأخذ الدالة [configure] كمعلمة القاموس الذي سيتم ملؤه بمعلومات التكوين. قد يكون هذا القاموس مملوءًا مسبقًا أو فارغًا. هنا، سيكون فارغًا؛
- الأسطر 40–42: المسارات المطلقة للملفات النصية الثلاثة التي تديرها طبقة [dao]؛
- الأسطر 43-50: مرتبطة بمفتاح [server]، المعلومات التي تحتاج طبقة [dao] إلى معرفتها عن خادم الويب الذي يجب أن تتواصل معه:
- السطر 44: عنوان URL لخدمة الويب؛
- السطر 45: يتم تعيين المفتاح [authBasic] على True إذا كان الوصول إلى عنوان URL يتطلب مصادقة أساسية؛
- الأسطر 46–49: بيانات اعتماد المستخدم الذي سيقوم بالمصادقة إذا كانت المصادقة مطلوبة؛
- السطور 56-57: نقوم بإنشاء مثيلات للطبقات — في هذه الحالة، طبقة [dao] الواحدة — ونضع مراجع الطبقات في [config] تحت مفتاح [layers]؛
النص البرمجي [config_layers] هو كما يلي:
- السطر 1: تستقبل الدالة [configure] القاموس الذي يقوم بتكوين التطبيق؛
- الأسطر 4–6: يتم إنشاء مثيل لطبقة [dao]. في السطر 6، نمرر إليها تكوين التطبيق، حيث ستجد المعلومات التي تحتاجها؛
- الأسطر 8-11: يتم إرجاع قاموس يحتوي على مرجع إلى طبقة [dao]؛
23.3.3. البرنامج النصي الرئيسي [main]
البرنامج النصي الرئيسي [main] هو نسخة معدلة من البرنامج النصي الموجود في |الإصدار 5|:
- السطران 2-3: تم تكوين التطبيق؛
- السطر 13: توفر طبقة [dao] قائمة بالمكلفين الذين يجب حساب الضرائب عليهم؛
- السطر 21: تحسب طبقة [dao] الضريبة لكل منهم؛
- السطر 23: يتم حفظ النتائج في ملف JSON؛
23.3.4. تنفيذ طبقة [dao]

دعونا نعيد النظر في بنية العميل/الخادم المستخدمة:

- في [2، 6]، نرى أن طبقة [dao] لها دوران:
- تقوم بالوصول إلى نظام الملفات لقراءة بيانات دافعي الضرائب وكتابة نتائج حسابات الضرائب. لدينا بالفعل فئة |AbstractImpôtsDao| قادرة على القيام بذلك. وهي مستخدمة منذ |الإصدار 4|؛
- تتواصل مع خادم الويب [3]؛
في |الإصدار 5|، كان البرنامج النصي الرئيسي [main] [1] يتواصل مباشرة مع طبقة [business] [4]. ونفضل عدم تغيير هذا البرنامج النصي. ولتحقيق ذلك، سنضمن أن طبقة [DAO] [2] تنفذ واجهة طبقة [business] [4]. وبهذه الطريقة، سيبدو أن البرنامج النصي الرئيسي [main] يتواصل مباشرة مع طبقة [business] [4] ويمكنه تجاهل حقيقة أنه موجود على جهاز آخر تمامًا.
يمكن أن يكون تعريف الفئة التي تنفذ طبقة [DAO] [2] كما يلي:
class ImpôtsDaoWithHttpClient(AbstractImpôtsDao, InterfaceImpôtsMétier):
- فئة [TaxDaoWithHttpClient]:
- ترث من فئة [AbstractTaxDao]، مما يسمح لها بالتعامل مع نظام الملفات [6]؛
- تنفذ واجهة [InterfaceImpôtsMétier] حتى لا تضطر إلى تغيير البرنامج النصي الرئيسي [main] في |الإصدار 5|؛
فيما يلي الكود الكامل لفئة [TaxDaoWithHttpClient]:
- الأسطر 21–23: تحتوي فئة [AbstractTaxDao] (السطر 12) على طريقة مجردة [get_admindata]. يتعين علينا تنفيذها حتى لو لم نستخدمها (يتم إدارة admindata بواسطة الخادم، وليس بواسطة العميل)؛
- السطر 26: تنتمي الطريقة [calculate_tax] إلى الواجهة [InterfaceImpôtsMétier] (السطر 12). يجب علينا تنفيذها؛
- السطر 15: يتلقى المنشئ قاموس تكوين التطبيق كمعلمة وحيدة له؛
- السطران 16-17: يتم تهيئة الفئة الأم [AbstractTaxDao] عن طريق تمرير تكوين التطبيق إليها، هنا أيضًا. وستجد هناك أسماء الملفات النصية الثلاثة التي تحتاج إلى إدارتها؛
- السطران 18-19: يتم تخزين المعلومات المتعلقة بخادم الويب لحساب الضرائب محليًا داخل الفئة؛
- السطر 26: تتلقى طريقة [calculate_tax] كائنًا من النوع |Taxpayer| كمعلمة. للامتثال لتوقيع طريقة [InterfaceImpôtsMétier.calculate_tax]، تتلقى أيضًا معلمة [admindata]، والتي من المفترض أن تغلف بيانات إدارة الضرائب. على جانب العميل، لا تتوفر لدينا هذه البيانات. ستظل هذه المعلمة دائمًا [None]. يشير هذا الحل البديل إلى أن فئة [ImpôtsMétier] كانت في البداية سيئة التصميم:
- كان من المفترض أن تكون توقيع [calculate_tax] ببساطة:
def calculate_tax(self, taxpayer: TaxPayer)
وكان يجب تمرير المعلمة [admindata: AdminData] إلى منشئ الفئة؛
- السطر 27: لم يتم تغليف كود طريقة [calculate_tax] في كتلة try / catch / finally. وهذا يعني أن أي استثناءات لن يتم معالجتها وستنتقل إلى الكود المستدعي، وهو في هذه الحالة البرنامج النصي [main]. ويقوم هذا البرنامج النصي بالفعل بالتقاط جميع الاستثناءات المنقولة من طبقة [dao]؛
- السطر 28: يتم حساب الضريبة على جانب الخادم. لذلك سنحتاج إلى التواصل معه. نقوم بذلك باستخدام وحدة [requests] المستوردة في السطر 2؛
- الأسطر 31–43: لإرسال طلب GET إلى خادم الويب، نستخدم طريقة [requests.get]:
- السطور 33–34: المعلمة الأولى للطريقة هي عنوان URL المراد الاتصال به؛
- الأسطر 35-40: المعلمتان الأخريان هما معلمتان مسمايتان لا يهم ترتيبهما؛
- السطور 35-36: يجب أن تكون قيمة المعلمة المسماة [params] عبارة عن قاموس يحتوي على المعلومات التي سيتم تضمينها في عنوان URL بالشكل [/url?param1=value1¶m2=value2&…]؛
- السطر 29: القاموس الذي يحتوي على المعلمات الثلاثة [married, children, salary] التي يتوقعها خادم الويب. لا داعي للقلق بشأن الترميز (المسمى urlencoded) الذي يجب أن تخضع له هذه المعلمات. تتولى [requests] هذه المهمة؛
- الأسطر 37–40: المعلمة المسماة [auth] هي مجموعة مكونة من عنصرين (login، password). وهي تمثل بيانات الاعتماد للمصادقة الأساسية؛
- السطران 44–45: هذان السطران مخصصان للأغراض التعليمية فقط (سنقوم بتعليقهما بمجرد اكتمال عملية تصحيح الأخطاء):
- [response] تمثل استجابة HTTP للخادم؛
- [response.text] يمثل نص المستند الموجود في هذا الرد. أثناء تصحيح الأخطاء، من المفيد التحقق مما أرسله لنا الخادم؛
- السطر 47: [response.status_code] هو رمز حالة HTTP للاستجابة المستلمة. يرسل خادمنا ثلاثة رموز فقط:
- 200 OK
- 400 طلب غير صحيح
- 500 خطأ داخلي في الخادم
- السطر 49: يرسل خادمنا دائمًا JSON، حتى في حالة حدوث خطأ. تقوم الدالة [response.json()] بإنشاء قاموس من سلسلة JSON المستلمة. دعونا نستعرض الشكلين المحتملين لسلسلة JSON:
{"réponse": {"erreurs": ["Méthode GET requise avec les seuls paramètres [marié, enfants, salaire]", "paramètre [marié] manquant", "paramètre [enfants] manquant", "paramètre [salaire] manquant"]}}
{"réponse": {"result": {"id": 0, "marié": "oui", "enfants": 3, "salaire": 200000, "impôt": 42842, "surcôte": 17283, "taux": 0.41, "décôte": 0, "réduction": 0}}}
- الأسطر 51–53: إذا لم يكن رمز الحالة 200، يتم إلقاء استثناء مع رسائل الخطأ المضمنة في الرد؛
- السطر 56: استرجاع القاموس الناتج عن حساب الضريبة واستخدامه لتحديث معلمة الإدخال [taxpayer]؛
23.3.5. التنفيذ
لتشغيل العميل:
- ابدأ تشغيل الخادم [server_03] باستخدام نظام إدارة قواعد البيانات (DBMS) الذي تختاره؛
- قم بتشغيل البرنامج النصي [main] الخاص بالعميل؛
ستجد النتائج في المجلد [data/output]. وهي نفس النتائج الخاصة بالإصدار 5.
23.4. اختبارات طبقة [dao]
لنعد إلى بنية تطبيق العميل/الخادم:

- في كود العميل، تأكدنا من أن طبقة [dao] [1] توفر نفس واجهة طبقة [business] [3]. لذلك سنستخدم فئة الاختبار |TestDaoMétier|، التي درسناها سابقًا، لاختبار طبقة [business] [3]؛
سيتم تنفيذ فئة الاختبار في البيئة التالية:

- التكوين [2] مطابق للتكوين [1] الذي استعرضناه للتو؛
فئة الاختبار [TestHttpClientDao] هي كما يلي:
هذه الفئة مشابهة لتلك التي تمت دراستها بالفعل في الإصدار 4 من التطبيق.
- السطران 40-41: تكوين بيئة الاختبار؛
- السطر 44: نسترد مرجعًا إلى طبقة [DAO]؛
- السطران 47-48: نجري الاختبارات؛
لتشغيل الاختبارات، نقوم بإنشاء |تكوين تشغيل|:

- نقوم بإنشاء تكوين تشغيل لبرنامج نصي وحدة التحكم، وليس لاختبار الوحدة؛
عند تشغيل هذا التكوين، يتم الحصول على النتائج التالية:
تم اجتياز جميع الاختبارات الـ 11.