18. نوشتن کد مستقل از SGBD
ما قبلاً دیدهایم که در برخی موارد میتوان کد پایتون نوشته شده برای SGBD و MySQL را به کدی نوشته شده برای SGBD و PostgreSQL مهاجرت داد. در این فصل نشان میدهیم چگونه این رویکرد را نظاممند کنیم. معماری پیشنهادی به شرح زیر است:

ما میخواهیم انتخاب کانکتور – و در نتیجه SGBD – از طریق پیکربندی انجام شود، بدون اینکه نیاز به بازنویسی اسکریپت باشد. توجه داشته باشید که این امر تنها در مواردی امکانپذیر است که اسکریپت از افزونههای اختصاصی SGBD استفاده نکند.
درخت اسکریپت به صورت زیر خواهد بود:

اسکریپتهای [any_xx] بر اساس اسکریپتهای بررسیشده برای SGBD، MySQL و PostgreSQL ساخته شدهاند. ما دیگر همهٔ آنها را دوباره بررسی نخواهیم کرد. تمرکز ما بر روی اسکریپت [any_04] خواهد بود که پیچیدهترین است. بهعنوان یادآوری، این اسکریپت دستورات SQL را از فایل زیر [data/commandes.sql] اجرا میکند:
# حذف جدول [personnes]
drop table if exists personnes
# ایجاد جدول «people»
create table personnes (id int primary key, prenom varchar(30) not null, nom varchar(30) not null, age integer not null, unique (nom,prenom))
# افزودن دو نفر
insert into personnes(id, prenom, nom, age) values(1, 'Paul','Langevin',48)
insert into personnes(id, prenom, nom, age) values (2, 'Sylvie','Lefur',70)
# نمایش جدول
select prenom, nom, age from personnes
# خطای عمدی
xx
# افزودن سه نفر
insert into personnes(id, prenom, nom, age) values (3, 'Pierre','Nicazou',35)
insert into personnes(id, prenom, nom, age) values (4, 'Geraldine','Colou',26)
insert into personnes(id, prenom, nom, age) values (5, 'Paulette','Girond',56)
#نمایش جدول
select prenom, nom, age from personnes
# فهرست افراد به ترتیب الفبایی نام خانوادگی؛ در صورتی که نام خانوادگی یکسان باشد، به ترتیب الفبایی نام کوچک
select nom,prenom from personnes order by nom asc, prenom desc
#فهرست افرادی که سنشان در بازه [20,40] قرار دارد، به ترتیب نزولی سن
#سپس، برای همسنوسالها، به ترتیب الفبایی نام خانوادگی و در صورت یکسان بودن نام خانوادگی، به ترتیب الفبایی نام کوچک
select nom,prenom,age from personnes where age between 20 and 40 order by age desc, nom asc, prenom asc
# افزودن خانم برونو
insert into personnes(id, prenom, nom, age) values(6, 'Josette','Bruneau',46)
# بهروزرسانی سن او
update personnes set age=47 where nom='Bruneau'
# فهرست افرادی با نام خانوادگی برونو
select nom,prenom,age from personnes where nom='Bruneau'
# برکناری خانم برونو
delete from personnes where nom='Bruneau'
# فهرست افرادی با نام خانوادگی برونو
select nom,prenom,age from personnes where nom='Bruneau'
ما خط ۲ را اصلاح کردهایم تا فرمان برای SGBD، MySQL و PostgreSQL به یک شکل عمل کند، در صورتی که جدول [personnes] وجود نداشته باشد.
اسکریپت [any_04] توسط اسکریپت زیر، [config.py]، پیکربندی شده است:
def configure():
import os
# مسیر مطلق پوشه این اسکریپت
script_dir = os.path.dirname(os.path.abspath(__file__))
#پیکربندی پوشه syspath
absolute_dependencies = [
# پوشههای محلی
f"{script_dir}/shared",
]
# تنظیم syspath
from myutils import set_syspath
set_syspath(absolute_dependencies)
# پیکربندی برنامه
config = {
# سیستمهای مدیریت پایگاه داده پشتیبانیشده
"sgbds": {
"mysql": {
# اتصالدهنده DBMS
"sgbd_connector": "mysql.connector",
# نام ماژولی که شامل توابع مدیریت DBMS است
"sgbd_fonctions": "any_module",
# اطلاعات اعتبارنامهی اتصال
"user": "admpersonnes",
"password": "nobody",
"host": "localhost",
"database": "dbpersonnes"
},
"postgresql": {
# اتصالدهنده DBMS
"sgbd_connector": "psycopg2",
# نام ماژولی که شامل توابع مدیریت DBMS است
"sgbd_fonctions": "any_module",
# اطلاعات اعتبارسنجی اتصال
"user": "admpersonnes",
"password": "nobody",
"host": "localhost",
"database": "dbpersonnes"
}
},
# فایل فرمان SQL
"commands_filename": f"{script_dir}/data/commandes.sql"
}
#مسیر سیستمی برنامه
from myutils import set_syspath
set_syspath(absolute_dependencies)
#پیکربندی اعمال شده است
return config
عناصر جدید در خطوط ۱۸–۴۳ یافت میشوند:
- خط ۲۰: [sgbds] یک دیکشنری با دو کلید است: [mysql] در خط ۲۱ و [postgresql] در خط ۳۲؛
- مقدار مرتبط با این کلیدها یک دیکشنری است که شامل عناصر لازم برای اتصال به یک SGBD میباشد:
- خطوط ۲۱–۳۲: اجزای یک اتصال به SGBD MySQL;
- خط ۲۳: کانکتور پایتون مورد استفاده؛
- خط ۲۵: ماژولی که شامل توابع مشترک است؛
- خطوط ۲۶–۳۰: اعتبارنامههای اتصال؛
- خطوط ۳۲–۴۱: همان جزئیات برای یک اتصال به SGBD PostgreSQL;
اسکریپت [any_04] که فایل دستهای SQL [data/commandes.sql] را اجرا میکند به شرح زیر است:
# راهاندازی برنامه
import config
config = config.configure()
#مسیر سیستم پیکربندی شده است – اکنون میتوان واردات را انجام داد
import importlib
import sys
# بررسی نحوی فراخوانی
#argv[0] sgbd_name true / false
#سه پارامتر لازم است
args = sys.argv
erreur = len(args) != 3
if not erreur:
# ما دو پارامتر مورد نظرمان را بازیابی میکنیم
sgbd_name = args[1].lower()
with_transaction = args[2].lower()
#بررسی دو پارامتر
erreur = (with_transaction != "true" and with_transaction != "false") \
or sgbd_name not in config["sgbds"].keys()
#خطا؟
if erreur:
print(f"syntaxe : {args[0]} (1) sgbd_name (2) true / false")
sys.exit()
# پیکربندی sgbd_name
sgbd_config = config["sgbds"][sgbd_name]
#کانکتور برای sgbd_name
sgbd_connector = importlib.import_module(sgbd_config["sgbd_connector"])
#کتابخانهٔ توابع
lib = importlib.import_module(sgbd_config["sgbd_fonctions"])
#محاسبه متن نمایشی
with_transaction = with_transaction == "true"
if with_transaction:
texte = "avec transaction"
else:
texte = "sans transaction"
#نمایش
commands_filename=config['commands_filename']
print("--------------------------------------------------------------------")
print(f"Exécution du fichier SQL {commands_filename} {texte}")
print("--------------------------------------------------------------------")
#اجرای دستورات SQL از فایل
connexion = None
try:
#ورود
connexion = sgbd_connector.connect(
host=sgbd_config['host'],
user=sgbd_config['user'],
password=sgbd_config['password'],
database=sgbd_config['database'])
#اجرای فایل فرمان SQL
erreurs = lib.execute_file_of_commands(sgbd_connector, connexion, commands_filename, suivi=True, arrêt=False,
with_transaction=with_transaction)
except (sgbd_connector.InterfaceError, sgbd_connector.DatabaseError) as erreur:
print(f"L'erreur suivante s'est produite : {erreur}")
finally:
#بستن اتصال
if connexion:
connexion.close()
#نمایش تعداد خطاها
print("--------------------------------------------------------------------")
print(f"Exécution terminée")
print("--------------------------------------------------------------------")
print(f"Il y a eu {len(erreurs)} erreur(s)")
# نمایش خطاها
for erreur in erreurs:
print(erreur)
توضیحات
- خطوط ۱–۴: پیکربندی برنامه [config] بازیابی میشود؛
- خطوط ۱۰–۲۱: اسکریپت با دو پارامتر [sgbd_name with_transaction] فراخوانی میشود:
- [sgbd_name]: نام SGBD که باید استفاده شود؛
- [with_transaction]: اگر میخواهید اسکریپت SQL را در یک تراکنش اجرا کنید، مقدار True و در غیر این صورت False را برمیگرداند؛
- خطوط ۱۰–۲۵: پارامترها بازیابی و بررسی میشوند؛
- خط ۲۸: پیکربندی SGBD انتخابشده؛
- خط ۳۰: کانکتور برای SGBD انتخابشده وارد میشود. برای این کار، از کتابخانه [importlib] (خط ۷) استفاده میشود که امکان وارد کردن ماژولی را فراهم میکند که نام آن در یک متغیر ذخیره شده است. نتیجهٔ عملیات [importlib.import_module] یک ماژول است. بنابراین، پس از خط ۳۰، همه چیز چنان پیش میرود که گویی دستور اجرا شده این بوده است:
این به ما اجازه میدهد تا در خط ۵۲، [sgbd_connector.connect] را بنویسیم، که در آن از تابع [connect] از ماژول [sgbd_connector] استفاده میکنیم. مهم است که به خاطر داشته باشیم [sgbd_connector] یا [mysql.connector] است یا [psycopg2]. هر دوی این ماژولها حاوی تابع [connect] هستند. به همین ترتیب، در خط ۶۰، میتوانید [sgbd_connector.InterfaceError, sgbd_connector.DatabaseError] را بنویسید.
- خط ۳۲: ماژولی که شامل توابع مورد استفاده در اسکریپت است، وارد میشود؛
- خط ۵۸: تابع [execute_file_of_commands] از ماژول تابع مورد استفاده توسط اسکریپت اجرا میشود. در مقایسه با نسخههای قبلی، امضای این تابع یک پارامتر اضافی، یعنی پارامتر اول، دارد. کانکتور پایتون [sgbd_connector]، که این تابع باید از آن استفاده کند، به آن پاس داده میشود؛
- به جز این موارد، اسکریپت [any_04] از نسخههای قبلی بدون تغییر باقی میماند؛
کتابخانه توابع [any_module] به شرح زیر است:
# ---------------------------------------------------------------------------------
def afficher_infos(curseur):
…
# ---------------------------------------------------------------------------------
def execute_list_of_commands(sgbd_connector, connexion, sql_commands: list,
suivi: bool = False, arrêt: bool = True, with_transaction: bool = True):
…
# ابتداییسازیها
curseur = None
connexion.autocommit = not with_transaction
erreurs = []
try:
#درخواست یک نشانگر
curseur = connexion.cursor()
#اجرای sql_commands SQL موجود در sql_commands
# آنها را یکییکی اجرا کنید
for command in sql_commands:
# ما فاصلههای خالی ابتدایی و انتهایی را از دستور فعلی حذف میکنیم
command = command.strip()
#آیا دستور خالی است یا نظر (comment)؟ اگر چنین است، به دستور بعدی برو
if command == '' or command[0] == "#":
continue
# دستور فعلی را اجرا کنید
error = None
try:
curseur.execute(command)
except (sgbd_connector.InterfaceError, sgbd_connector.DatabaseError) as erreur:
error = erreur
#آیا خطایی رخ داده است؟
…
# ---------------------------------------------------------------------------------
def execute_file_of_commands(sgbd_connector, connexion, sql_filename: str,
suivi: bool = False, arrêt: bool = True, with_transaction: bool = True):
…
# پردازش فایل SQL
try:
#باز کردن فایل برای خواندن
file = open(sql_filename, "r")
#پردازش
return execute_list_of_commands(sgbd_connector, connexion, file.readlines(), suivi, arrêt, with_transaction)
except BaseException as erreur:
# بازگرداندن جدول خطا
return [f"Le fichier {sql_filename} n'a pu être être exploité : {erreur}"]
finally:
pass
پارامتر [sgbd_connector] در خط ۳۱ برای مشخص کردن نوع استثناءهای دریافتشده استفاده شد.
اجرای اسکریپت [any_04] با پارامترهای [mysql false] نتایج زیر را تولید میکند:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/databases/anysgbd/any_04.py mysql false
--------------------------------------------------------------------
Exécution du fichier SQL C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\databases\anysgbd/data/commandes.sql sans transaction
--------------------------------------------------------------------
[drop table if exists personnes] : Exécution réussie
nombre de lignes modifiées : 0
[create table personnes (id int primary key, prenom varchar(30) not null, nom varchar(30) not null, age integer not null, unique (nom,prenom))] : Exécution réussie
nombre de lignes modifiées : 0
[insert into personnes(id, prenom, nom, age) values(1, 'Paul','Langevin',48)] : Exécution réussie
nombre de lignes modifiées : 1
[insert into personnes(id, prenom, nom, age) values (2, 'Sylvie','Lefur',70)] : Exécution réussie
nombre de lignes modifiées : 1
[select prenom, nom, age from personnes] : Exécution réussie
prenom, nom, age,
*****************
('Paul', 'Langevin', 48)
('Sylvie', 'Lefur', 70)
*****************
xx : Erreur (1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'xx' at line 1)
[insert into personnes(id, prenom, nom, age) values (3, 'Pierre','Nicazou',35)] : Exécution réussie
nombre de lignes modifiées : 1
[insert into personnes(id, prenom, nom, age) values (4, 'Geraldine','Colou',26)] : Exécution réussie
nombre de lignes modifiées : 1
[insert into personnes(id, prenom, nom, age) values (5, 'Paulette','Girond',56)] : Exécution réussie
nombre de lignes modifiées : 1
[select prenom, nom, age from personnes] : Exécution réussie
prenom, nom, age,
*****************
('Paul', 'Langevin', 48)
('Sylvie', 'Lefur', 70)
('Pierre', 'Nicazou', 35)
('Geraldine', 'Colou', 26)
('Paulette', 'Girond', 56)
*****************
[select nom,prenom from personnes order by nom asc, prenom desc] : Exécution réussie
nom, prenom,
************
('Colou', 'Geraldine')
('Girond', 'Paulette')
('Langevin', 'Paul')
('Lefur', 'Sylvie')
('Nicazou', 'Pierre')
************
[select nom,prenom,age from personnes where age between 20 and 40 order by age desc, nom asc, prenom asc] : Exécution réussie
nom, prenom, age,
*****************
('Nicazou', 'Pierre', 35)
('Colou', 'Geraldine', 26)
*****************
[insert into personnes(id, prenom, nom, age) values(6, 'Josette','Bruneau',46)] : Exécution réussie
nombre de lignes modifiées : 1
[update personnes set age=47 where nom='Bruneau'] : Exécution réussie
nombre de lignes modifiées : 1
[select nom,prenom,age from personnes where nom='Bruneau'] : Exécution réussie
nom, prenom, age,
*****************
('Bruneau', 'Josette', 47)
*****************
[delete from personnes where nom='Bruneau'] : Exécution réussie
nombre de lignes modifiées : 1
[select nom,prenom,age from personnes where nom='Bruneau'] : Exécution réussie
nom, prenom, age,
*****************
*****************
--------------------------------------------------------------------
Exécution terminée
--------------------------------------------------------------------
Il y a eu 1 erreur(s)
xx : Erreur (1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'xx' at line 1)
Process finished with exit code 0