FA-TOOLS — Header Component
آموزش PostgreSQL با پایتون — psycopg2

آموزش PostgreSQL با پایتون — psycopg2

رفیق برنامه‌نویس، اینجا راهنمایی کامل منتظرته!

دنبال ابزارهای خفن برای توسعه پروژه‌هات می‌گردی؟ میخوای کدهاتو بهینه کنی و از بهترین اسنیپت‌ها استفاده کنی؟
تیم FA-Tools
همیشه کنارت بوده و هست. همین حالا یه سر به بخش اسنیپت‌های آماده ما بزن و
با کدهای آماده پایتون،
HTML،
CSS،
JavaScript
و وردپرس
پروژه‌هات رو متحول کن. هر سوالی داشتی، مستقیم با ما تماس بگیر: 09202232789.

🗺️ نقشه‌ی راه: PostgreSQL با پایتون در یک نگاه 🗺️

آموزش PostgreSQL با پایتون — psycopg2 — تصویر 2
╔═══════════════════════════════════════════════════════════════════════════════╗
║ [مرحله ۱: آماده‌سازی]                                                            ║
║   •  نصب PostgreSQL (سرور پایگاه داده)                                             ║
║   •  نصب psycopg2 (کتابخانه پایتون)                                             ║
║   •  ساخت دیتابیس و کاربر (تنظیمات اولیه)                                          ║
╠═══════════════════════════════════════════════════════════════════════════════╣
║ [مرحله ۲: اتصال و ارتباط]                                                         ║
║   •  import psycopg2                                                          ║
║   •  psycopg2.connect() ⇾ برقراری ارتباط                                      ║
║   •  conn.cursor() ⇾ ایجاد اشاره‌گر برای اجرای کوئری                              ║
╠═══════════════════════════════════════════════════════════════════════════════╣
║ [مرحله ۳: اجرای کوئری‌ها]                                                          ║
║   •  cursor.execute("SQL Query;") ⇾ اجرای دستورات SQL                         ║
║   •  cursor.fetchone() / fetchall() ⇾ دریافت نتایج                                ║
║   •  conn.commit() ⇾ ذخیره تغییرات (INSERT, UPDATE, DELETE)                    ║
╠═══════════════════════════════════════════════════════════════════════════════╣
║ [مرحله ۴: امنیت و بهینه‌سازی]                                                       ║
║   •  پارامترها (%s) ⇾ جلوگیری از SQL Injection                                ║
║   •  مدیریت تراکنش (try...except...finally)                                  ║
║   •  Row Factory ⇾ دریافت نتایج به شکل دیکشنری یا کلاس                            ║
╠═══════════════════════════════════════════════════════════════════════════════╣
║ [مرحله ۵: مدیریت خطا و عیب‌یابی]                                                     ║
║   •  بررسی خطاهای رایج اتصال، کوئری و تراکنش                                   ║
║   •  راه‌حل‌های عملی برای مشکلات متداول                                         ║
╚═══════════════════════════════════════════════════════════════════════════════╝
    

فهرست مطالب

آموزش PostgreSQL با پایتون — psycopg2 — تصویر 3

PostgreSQL چیست و چرا پایتون؟

PostgreSQL، که بهش می‌گن “پُستگرس”، یه سیستم مدیریت پایگاه داده رابطه‌ای (RDBMS) قدرتمند، اوپن‌سورس و پیشرفته‌س. شاید مثل MySQL یا SQL Server به اندازه اونها شناخته شده نباشه، اما قابلیت‌هاش واقعاً عالیه و توی پروژه‌های بزرگ و پیچیده خیلی به کار میاد. از قابلیت‌های پیشرفته مثل پشتیبانی از JSON و XML گرفته تا ترانزکشن‌های ACID و امکانات گسترش‌پذیری فوق‌العاده، PostgreSQL یه انتخاب حرفه‌ای برای توسعه‌دهنده‌هاست.

حالا چرا پایتون؟ پایتون به خاطر سادگی، خوانایی بالا و اکوسیستم وسیعی از کتابخانه‌ها، یکی از محبوب‌ترین زبان‌ها برای توسعه وب، علم داده و اتوماسیون شده. وقتی صحبت از تعامل با دیتابیس‌ها میشه، پایتون با کتابخانه‌های قوی مثل `psycopg2` که مختص PostgreSQL هست، این کار رو مثل آب خوردن می‌کنه. `psycopg2` یه ماژول قدرتمند و استاندارد برای پایتونه که امکان ارتباط روان و امن با دیتابیس‌های PostgreSQL رو فراهم می‌کنه. ترکیب این دو، یه راه حل بی‌نظیر برای مدیریت و دستکاری داده‌ها بهت میده.

نصب و راه‌اندازی: آماده‌سازی محیط کار

قبل از اینکه وارد دنیای کدنویسی بشیم، باید مطمئن بشیم که همه ابزارهای لازم رو روی سیستممون داریم. این بخش مربوط به نصب PostgreSQL و کتابخانه `psycopg2` و همچنین آماده‌سازی دیتابیس برای شروع کار.

نصب PostgreSQL

اولین قدم، نصب خود سرور PostgreSQL هست.

  • ویندوز: بهترین راه، دانلود نصاب گرافیکی از وب‌سایت رسمی PostgreSQL هست. این نصاب شامل PostgreSQL Server، pgAdmin (رابط کاربری گرافیکی برای مدیریت دیتابیس)، و ابزارهای خط فرمان دیگه میشه.
  • macOS: می‌تونی از نصاب گرافیکی وب‌سایت رسمی استفاده کنی یا اگه Homebrew رو داری، با دستور `brew install postgresql` خیلی راحت نصبش کنی.
  • لینوکس (دبیان/اوبونتو):
    sudo apt update
    sudo apt install postgresql postgresql-contrib

بعد از نصب، یه یوزر (معمولاً `postgres`) و پسورد بهت میده که باید یادت باشه.

ساخت دیتابیس و کاربر جدید

برای پروژه‌مون، بهتره یه دیتابیس و یه کاربر جدید بسازیم تا با کاربر پیش‌فرض `postgres` کاری نداشته باشیم.

sudo -u postgres psql

-- حالا داخل محیط psql هستیم
CREATE DATABASE my_app_db;
CREATE USER my_app_user WITH PASSWORD 'my_strong_password';
GRANT ALL PRIVILEGES ON DATABASE my_app_db TO my_app_user;
q

(یادت باشه `my_strong_password` رو با یه پسورد قوی و واقعی عوض کنی!)

نصب psycopg2

حالا که PostgreSQL رو داریم، وقتشه `psycopg2` رو نصب کنیم.

pip install psycopg2-binary
چرا `psycopg2-binary`؟
برای راحتی کار، معمولاً `psycopg2-binary` رو نصب می‌کنیم. این نسخه شامل فایل‌های باینری از پیش کامپایل شده‌س و دیگه نیازی نیست روی سیستممون کامپایلر C داشته باشیم. اگه نیاز به امکانات پیشرفته‌تر یا کامپایل از سورس داری، می‌تونی فقط `psycopg2` رو نصب کنی، اما ممکنه نیاز به پکیج‌های توسعه‌دهنده PostgreSQL و کامپایلر روی سیستمت باشه.

معرفی psycopg2: پل ارتباطی شما

`psycopg2` (با تلفظ “سایکوپی‌جی‌تو”) یه آداپتور دیتابیس پایتون برای PostgreSQL هست که از PEP 249 (Python DB API 2.0) پیروی می‌کنه. یعنی استاندارده و اگه قبلاً با دیتابیس‌های دیگه توی پایتون کار کرده باشی، حس آشنایی بهت میده. این کتابخانه بهت اجازه میده از داخل کد پایتونت، به دیتابیس PostgreSQL متصل بشی، کوئری‌ها رو اجرا کنی و نتایج رو دریافت کنی. از مهم‌ترین ویژگی‌هاش میشه به پشتیبانی کامل از تراکنش‌ها، امنیت بالا در برابر SQL Injection با استفاده از پارامترها و سرعت خوب اشاره کرد.

اتصال به پایگاه داده

برای شروع هر کاری با دیتابیس، اول باید بهش وصل بشیم. این کار با تابع `connect()` از ماژول `psycopg2` انجام میشه.

import psycopg2

def connect_to_db():
    conn = None
    try:
        conn = psycopg2.connect(
            host="localhost",
            database="my_app_db",
            user="my_app_user",
            password="my_strong_password"
        )
        print("اتصال به پایگاه داده با موفقیت برقرار شد!")
        return conn
    except psycopg2.Error as e:
        print(f"خطا در اتصال به پایگاه داده: {e}")
        return None

if __name__ == "__main__":
    connection = connect_to_db()
    if connection:
        connection.close()
        print("اتصال بسته شد.")

همیشه یادت باشه بعد از اتمام کار با دیتابیس، اتصال رو با `connection.close()` ببندی تا منابع سیستمت آزاد بشن.

اجرای کوئری‌ها: از SELECT تا DELETE

بعد از اتصال، نوبت به اجرای دستورات SQL میرسه. برای این کار به یه `cursor` (اشاره‌گر) نیاز داریم. `cursor` یه شیء هست که بهمون اجازه میده دستورات SQL رو اجرا کنیم و نتایج رو دریافت کنیم.

# فرض می‌کنیم connection از تابع connect_to_db برگشته
connection = connect_to_db()
if connection:
    cursor = connection.cursor()
    # حالا می‌تونیم کوئری‌ها رو اجرا کنیم
    cursor.close()
    connection.close()

ایجاد جدول

اول از همه، یه جدول برای داده‌هامون می‌سازیم. فرض کنیم می‌خوایم یه جدول `tasks` برای کارهای روزمره داشته باشیم.

import psycopg2

def create_tasks_table(conn):
    try:
        cursor = conn.cursor()
        create_table_query = """
        CREATE TABLE IF NOT EXISTS tasks (
            id SERIAL PRIMARY KEY,
            title VARCHAR(100) NOT NULL,
            description TEXT,
            is_completed BOOLEAN DEFAULT FALSE,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        );
        """
        cursor.execute(create_table_query)
        conn.commit() # مهم: برای ذخیره تغییرات DDL باید commit کنیم
        print("جدول 'tasks' با موفقیت ایجاد یا موجود بود.")
    except psycopg2.Error as e:
        print(f"خطا در ایجاد جدول: {e}")
    finally:
        if cursor:
            cursor.close()

if __name__ == "__main__":
    conn = connect_to_db()
    if conn:
        create_tasks_table(conn)
        conn.close()

کار با داده‌ها: درج، به‌روزرسانی و حذف (CRUD)

حالا که جدول رو داریم، وقتشه که عملیات اصلی (Create, Read, Update, Delete) رو روی داده‌ها انجام بدیم.

درج داده (INSERT)

برای درج داده جدید، از دستور `INSERT` استفاده می‌کنیم. اینجا اهمیت استفاده از پارامترها (جایگزین‌های `%s`) برای جلوگیری از SQL Injection رو میبینی.

def insert_task(conn, title, description):
    try:
        cursor = conn.cursor()
        insert_query = """
        INSERT INTO tasks (title, description) VALUES (%s, %s) RETURNING id;
        """
        cursor.execute(insert_query, (title, description))
        task_id = cursor.fetchone()[0] # برای دریافت ID ردیف جدید
        conn.commit()
        print(f"وظیفه با شناسه {task_id} با موفقیت اضافه شد.")
        return task_id
    except psycopg2.Error as e:
        conn.rollback() # در صورت خطا، تغییرات رو برگردون
        print(f"خطا در درج وظیفه: {e}")
        return None
    finally:
        if cursor:
            cursor.close()

if __name__ == "__main__":
    conn = connect_to_db()
    if conn:
        create_tasks_table(conn) # مطمئن می‌شیم جدول هست
        insert_task(conn, "خرید نان", "از سوپر مارکت سر کوچه")
        insert_task(conn, "تمرین پایتون", "آموزش psycopg2 و PostgreSQL")
        conn.close()

انتخاب داده (SELECT)

برای خوندن داده‌ها، از `SELECT` استفاده می‌کنیم. توابع `fetchone()`، `fetchall()` و `fetchmany(size)` برای دریافت نتایج به کار میرن.

def get_all_tasks(conn):
    try:
        cursor = conn.cursor()
        select_query = "SELECT id, title, description, is_completed, created_at FROM tasks ORDER BY created_at DESC;"
        cursor.execute(select_query)
        tasks = cursor.fetchall() # دریافت تمام ردیف‌ها
        print("nلیست وظایف:")
        for task in tasks:
            print(f"ID: {task[0]}, عنوان: {task[1]}, وضعیت: {'کامل شده' if task[3] else 'در حال انجام'}")
        return tasks
    except psycopg2.Error as e:
        print(f"خطا در دریافت وظایف: {e}")
        return []
    finally:
        if cursor:
            cursor.close()

def get_task_by_id(conn, task_id):
    try:
        cursor = conn.cursor()
        select_query = "SELECT title, description, is_completed FROM tasks WHERE id = %s;"
        cursor.execute(select_query, (task_id,)) # حتماً tuple باشه حتی برای یک پارامتر
        task = cursor.fetchone() # دریافت یک ردیف
        if task:
            print(f"nوظیفه با ID {task_id}: عنوان: {task[0]}, وضعیت: {'کامل شده' if task[2] else 'در حال انجام'}")
        else:
            print(f"nوظیفه با ID {task_id} یافت نشد.")
        return task
    except psycopg2.Error as e:
        print(f"خطا در دریافت وظیفه: {e}")
        return None
    finally:
        if cursor:
            cursor.close()

if __name__ == "__main__":
    conn = connect_to_db()
    if conn:
        # فرض می‌کنیم چند وظیفه قبلاً اضافه شده
        get_all_tasks(conn)
        get_task_by_id(conn, 1) # ID اولین وظیفه‌ای که اضافه کردیم
        conn.close()

به‌روزرسانی داده (UPDATE)

برای تغییر داده‌های موجود، از دستور `UPDATE` استفاده می‌کنیم.

def update_task_status(conn, task_id, is_completed):
    try:
        cursor = conn.cursor()
        update_query = "UPDATE tasks SET is_completed = %s WHERE id = %s;"
        cursor.execute(update_query, (is_completed, task_id))
        conn.commit()
        if cursor.rowcount > 0:
            print(f"وضعیت وظیفه با شناسه {task_id} به {'کامل شده' if is_completed else 'در حال انجام'} تغییر یافت.")
            return True
        else:
            print(f"وظیفه با شناسه {task_id} یافت نشد یا تغییری اعمال نشد.")
            return False
    except psycopg2.Error as e:
        conn.rollback()
        print(f"خطا در به‌روزرسانی وظیفه: {e}")
        return False
    finally:
        if cursor:
            cursor.close()

if __name__ == "__main__":
    conn = connect_to_db()
    if conn:
        update_task_status(conn, 2, True) # وظیفه با ID 2 رو کامل می‌کنیم
        get_all_tasks(conn)
        conn.close()

حذف داده (DELETE)

و در نهایت، برای حذف داده، از `DELETE` استفاده می‌کنیم.

def delete_task(conn, task_id):
    try:
        cursor = conn.cursor()
        delete_query = "DELETE FROM tasks WHERE id = %s;"
        cursor.execute(delete_query, (task_id,))
        conn.commit()
        if cursor.rowcount > 0:
            print(f"وظیفه با شناسه {task_id} با موفقیت حذف شد.")
            return True
        else:
            print(f"وظیفه با شناسه {task_id} یافت نشد یا حذف نشد.")
            return False
    except psycopg2.Error as e:
        conn.rollback()
        print(f"خطا در حذف وظیفه: {e}")
        return False
    finally:
        if cursor:
            cursor.close()

if __name__ == "__main__":
    conn = connect_to_db()
    if conn:
        delete_task(conn, 1) # وظیفه با ID 1 رو حذف می‌کنیم
        get_all_tasks(conn)
        conn.close()

مدیریت تراکنش‌ها: امنیت داده‌ها در اولویت

تراکنش‌ها (Transactions) یکی از مهم‌ترین ویژگی‌های هر RDBMS هستند. تراکنش یعنی یه سری عملیات که باید همگی با هم موفق بشن یا همگی با هم شکست بخورن. فکر کن داری از حساب بانکی A به حساب بانکی B پول واریز می‌کنی. این عملیات شامل کسر از A و اضافه کردن به B میشه. اگه کسر از A موفق بشه ولی اضافه کردن به B شکست بخوره، چی میشه؟ پولت گم میشه! تراکنش‌ها اینجا نجات‌دهنده‌ن.

`psycopg2` به طور پیش‌فرض در حالت “autocommit” نیست، یعنی هر `execute()` به خودی خود تغییرات رو ذخیره نمی‌کنه. شما باید به صراحت `conn.commit()` رو صدا بزنید تا تغییرات دائمی بشن. در صورت بروز خطا، می‌تونید با `conn.rollback()` همه تغییرات داخل اون تراکنش رو لغو کنید.

def transfer_money(conn, sender_id, receiver_id, amount):
    cursor = None
    try:
        cursor = conn.cursor()

        # کسر از حساب فرستنده
        cursor.execute("UPDATE accounts SET balance = balance - %s WHERE id = %s;", (amount, sender_id))
        if cursor.rowcount == 0:
            raise Exception("فرستنده یافت نشد یا موجودی کافی نیست.")

        # اضافه کردن به حساب گیرنده
        cursor.execute("UPDATE accounts SET balance = balance + %s WHERE id = %s;", (amount, receiver_id))
        if cursor.rowcount == 0:
            raise Exception("گیرنده یافت نشد.")

        conn.commit() # اگر هر دو عملیات موفق بود، تغییرات رو دائمی کن
        print(f"انتقال {amount} از حساب {sender_id} به {receiver_id} با موفقیت انجام شد.")

    except Exception as e:
        conn.rollback() # در صورت بروز هر خطایی، همه تغییرات رو برگردون
        print(f"خطا در انتقال وجه: {e}. تراکنش لغو شد.")
    finally:
        if cursor:
            cursor.close()

# فرض کنید قبلا جدول accounts ساخته شده و اطلاعاتی داخلشه
# CREATE TABLE accounts (id SERIAL PRIMARY KEY, name VARCHAR(100), balance DECIMAL(10, 2));
# INSERT INTO accounts (name, balance) VALUES ('علی', 1000), ('رضا', 500);

if __name__ == "__main__":
    conn = connect_to_db()
    if conn:
        # نمونه سازی:
        # cursor = conn.cursor()
        # cursor.execute("DROP TABLE IF EXISTS accounts;")
        # cursor.execute("CREATE TABLE accounts (id SERIAL PRIMARY KEY, name VARCHAR(100), balance DECIMAL(10, 2));")
        # cursor.execute("INSERT INTO accounts (name, balance) VALUES ('علی', 1000), ('رضا', 500);")
        # conn.commit()
        # cursor.close()

        transfer_money(conn, 1, 2, 200) # انتقال موفق
        transfer_money(conn, 1, 3, 100) # گیرنده نامعتبر
        conn.close()

استفاده از پارامترها و جلوگیری از SQL Injection

یکی از مهم‌ترین نکات امنیتی در کار با دیتابیس، جلوگیری از SQL Injection هست. این حمله زمانی اتفاق میفته که مهاجم بتونه از طریق ورودی‌های کاربر، دستورات SQL مخرب رو به کوئری شما تزریق کنه. `psycopg2` با استفاده از پارامترها، این مشکل رو به راحتی حل می‌کنه.

همیشه به جای اینکه مقادیر رو مستقیماً با f-string یا Concatenation به کوئری اضافه کنی، از `s%` در کوئری و یک tuple (یا لیست) از مقادیر به عنوان آرگومان دوم `execute()` استفاده کن. `psycopg2` خودش مقادیر رو به درستی Escape می‌کنه و از هرگونه تزریق جلوگیری می‌کنه.

هشدار امنیتی:
هرگز، هرگز و هرگز مقادیر ورودی کاربر رو مستقیماً با استفاده از f-string یا string concatenation وارد کوئری‌های SQL نکن. این کار درهای سیستمت رو به روی حملات SQL Injection باز می‌کنه. همیشه از مکان‌نماهای پارامترها (مثل %s در psycopg2) استفاده کن.

اینجا یه جدول مقایسه‌ای داریم که تفاوت استفاده از پارامتر و عدم استفاده از اون رو نشون میده:

روش ناامن (SQL Injection) روش امن (با پارامتر)
user_input = "'; DROP TABLE users;--"
query_unsafe = f"SELECT * FROM users WHERE username = '{user_input}';"
cursor.execute(query_unsafe)
user_input = "'; DROP TABLE users;--"
query_safe = "SELECT * FROM users WHERE username = %s;"
cursor.execute(query_safe, (user_input,))
خطر: این روش امکان اجرای کدهای مخرب را فراهم می‌کند. امن: `psycopg2` ورودی را به درستی Escape می‌کند و از حمله جلوگیری می‌کند.

کار با Row Factory و cursor.description

به طور پیش‌فرض، `psycopg2` نتایج رو به صورت tuple برمی‌گردونه. مثلاً `(‘خرید نان’, ‘از سوپر مارکت’)`. این کار خوبه، ولی گاهی اوقات دوست داریم نتایج رو به صورت دیکشنری (با نام ستون‌ها به عنوان کلید) یا حتی به صورت شیء (کلاس) بگیریم. اینجا `row_factory` به کمکمون میاد.

import psycopg2.extras # برای DictCursor

def get_tasks_as_dict(conn):
    try:
        # استفاده از DictCursor برای دریافت نتایج به صورت دیکشنری
        cursor = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
        select_query = "SELECT id, title, description, is_completed FROM tasks ORDER BY created_at DESC;"
        cursor.execute(select_query)
        tasks = cursor.fetchall()
        print("nلیست وظایف (به صورت دیکشنری):")
        for task in tasks:
            print(f"ID: {task['id']}, عنوان: {task['title']}, وضعیت: {'کامل شده' if task['is_completed'] else 'در حال انجام'}")
        return tasks
    except psycopg2.Error as e:
        print(f"خطا در دریافت وظایف (دیکشنری): {e}")
        return []
    finally:
        if cursor:
            cursor.close()

if __name__ == "__main__":
    conn = connect_to_db()
    if conn:
        insert_task(conn, "مرور ایمیل‌ها", "چک کردن صندوق ورودی") # اضافه کردن یک وظیفه دیگر
        get_tasks_as_dict(conn)
        conn.close()

`cursor.description` هم یه ویژگی مفید دیگه است که اطلاعاتی درباره ستون‌های نتایج کوئری (مثل نام ستون، نوع داده، و …) بهمون میده.

def describe_tasks_table(conn):
    try:
        cursor = conn.cursor()
        cursor.execute("SELECT * FROM tasks LIMIT 0;") # یک کوئری که ردیفی برنمی‌گردونه
        column_names = [desc[0] for desc in cursor.description]
        print("nنام ستون‌های جدول tasks:")
        print(column_names)
        return column_names
    except psycopg2.Error as e:
        print(f"خطا در دریافت توضیحات جدول: {e}")
        return []
    finally:
        if cursor:
            cursor.close()

if __name__ == "__main__":
    conn = connect_to_db()
    if conn:
        describe_tasks_table(conn)
        conn.close()

مثال جامع: یک اپلیکیشن ساده To-Do

خب، حالا که همه چیز رو یاد گرفتیم، بیایید یه اپلیکیشن To-Do خیلی ساده بسازیم تا تمام مفاهیم رو توی عمل ببینیم.

import psycopg2
import psycopg2.extras

# تنظیمات اتصال (همون قبلی‌ها)
DB_CONFIG = {
    "host": "localhost",
    "database": "my_app_db",
    "user": "my_app_user",
    "password": "my_strong_password"
}

def get_db_connection():
    try:
        conn = psycopg2.connect(**DB_CONFIG)
        return conn
    except psycopg2.Error as e:
        print(f"❌ خطا در اتصال به پایگاه داده: {e}")
        return None

def init_db():
    conn = get_db_connection()
    if conn:
        try:
            cursor = conn.cursor()
            create_table_query = """
            CREATE TABLE IF NOT EXISTS todos (
                id SERIAL PRIMARY KEY,
                task TEXT NOT NULL,
                completed BOOLEAN DEFAULT FALSE,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            );
            """
            cursor.execute(create_table_query)
            conn.commit()
            print("✅ جدول 'todos' آماده است.")
        except psycopg2.Error as e:
            print(f"❌ خطا در ایجاد جدول: {e}")
            conn.rollback()
        finally:
            if cursor: cursor.close()
            conn.close()

def add_todo(task_text):
    conn = get_db_connection()
    if conn:
        try:
            cursor = conn.cursor()
            insert_query = "INSERT INTO todos (task) VALUES (%s) RETURNING id;"
            cursor.execute(insert_query, (task_text,))
            todo_id = cursor.fetchone()[0]
            conn.commit()
            print(f"➕ وظیفه '{task_text}' (ID: {todo_id}) اضافه شد.")
            return todo_id
        except psycopg2.Error as e:
            print(f"❌ خطا در افزودن وظیفه: {e}")
            conn.rollback()
        finally:
            if cursor: cursor.close()
            conn.close()

def get_todos():
    conn = get_db_connection()
    if conn:
        try:
            cursor = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
            select_query = "SELECT id, task, completed, created_at FROM todos ORDER BY created_at ASC;"
            cursor.execute(select_query)
            todos = cursor.fetchall()
            print("n📋 لیست وظایف:")
            if not todos:
                print("   هیچ وظیفه‌ای ثبت نشده است.")
            for todo in todos:
                status = "✅" if todo['completed'] else "⏳"
                print(f"   {status} ID: {todo['id']}, وظیفه: {todo['task']}")
            return todos
        except psycopg2.Error as e:
            print(f"❌ خطا در دریافت وظایف: {e}")
        finally:
            if cursor: cursor.close()
            conn.close()

def complete_todo(todo_id):
    conn = get_db_connection()
    if conn:
        try:
            cursor = conn.cursor()
            update_query = "UPDATE todos SET completed = TRUE WHERE id = %s;"
            cursor.execute(update_query, (todo_id,))
            conn.commit()
            if cursor.rowcount > 0:
                print(f"🎉 وظیفه با ID {todo_id} به اتمام رسید.")
                return True
            else:
                print(f"❓ وظیفه با ID {todo_id} یافت نشد.")
                return False
        except psycopg2.Error as e:
            print(f"❌ خطا در تکمیل وظیفه: {e}")
            conn.rollback()
        finally:
            if cursor: cursor.close()
            conn.close()

def delete_todo(todo_id):
    conn = get_db_connection()
    if conn:
        try:
            cursor = conn.cursor()
            delete_query = "DELETE FROM todos WHERE id = %s;"
            cursor.execute(delete_query, (todo_id,))
            conn.commit()
            if cursor.rowcount > 0:
                print(f"🗑️ وظیفه با ID {todo_id} حذف شد.")
                return True
            else:
                print(f"❓ وظیفه با ID {todo_id} یافت نشد.")
                return False
        except psycopg2.Error as e:
            print(f"❌ خطا در حذف وظیفه: {e}")
            conn.rollback()
        finally:
            if cursor: cursor.close()
            conn.close()

if __name__ == "__main__":
    init_db() # مطمئن می‌شیم جدول هست

    add_todo("یادگیری PostgreSQL پیشرفته")
    add_todo("آماده کردن ناهار")
    add_todo("کدنویسی یه اسکریپت خفن پایتون")

    get_todos()

    complete_todo(2) # ناهار رو آماده می‌کنیم!

    get_todos()

    delete_todo(1) # وظیفه اول رو حذف می‌کنیم

    get_todos()

عیب‌یابی سریع: مشکلات رایج و راه حل‌ها

توی مسیر برنامه‌نویسی، ممکنه با خطاهای مختلفی برخورد کنی. نگران نباش، این یه بخش طبیعی از کاره. اینجا به چند تا مشکل رایج و راه‌حلشون اشاره می‌کنیم:

1. `psycopg2.OperationalError: connection to server at “localhost” (::1), port 5432 failed: Connection refused`

مشکل: سرور PostgreSQL در حال اجرا نیست یا روی پورت پیش‌فرض 5432 گوش نمیده.

راه‌حل:

  • مطمئن شو که سرویس PostgreSQL در حال اجراست.
  • ویندوز: برو به Services (services.msc) و PostgreSQL رو پیدا و استارت کن.
  • لینوکس/macOS: با دستور `sudo service postgresql start` یا `pg_ctl -D /usr/local/var/postgres start` (برای Homebrew) سرویس رو شروع کن.
  • تنظیمات پورت رو در فایل `postgresql.conf` بررسی کن و مطمئن شو که با پورت در کد پایتون یکیه.

2. `psycopg2.OperationalError: FATAL: password authentication failed for user “…”`

مشکل: نام کاربری یا رمز عبور اشتباه است، یا کاربر اجازه اتصال ندارد.

راه‌حل:

  • نام کاربری و رمز عبور در تابع `psycopg2.connect()` رو دوباره چک کن.
  • مطمئن شو که کاربر `my_app_user` (یا هر کاربری که ساختی) با پسورد صحیح ایجاد شده.
  • فایل `pg_hba.conf` در دایرکتوری PostgreSQL رو بررسی کن. ممکنه نیاز باشه روش احراز هویت رو از `ident` به `md5` یا `scram-sha-256` تغییر بدی تا امکان اتصال با پسورد فراهم بشه. (مثال: `host all all 127.0.0.1/32 md5`)

3. `psycopg2.ProgrammingError: relation “my_table” does not exist`

مشکل: جدول مورد نظر شما در دیتابیس وجود ندارد.

راه‌حل:

  • مطمئن شو که قبل از اجرای کوئری‌های CRUD، تابع `create_table_query` یا `init_db` رو اجرا کردی.
  • نام جدول رو با دقت از نظر املایی و حروف بزرگ/کوچک (PostgreSQL به نام‌ها حساسه اگه با کوتیشن ایجاد بشن) بررسی کن.
  • با استفاده از `psql` یا `pgAdmin` به دیتابیس وصل شو و با دستور `dt` لیست جداول رو ببین تا مطمئن شی جدول هست.

4. `psycopg2.errors.InFailedSqlTransaction: current transaction is aborted, commands ignored until end of transaction block`

مشکل: در یک تراکنش قبلی خطایی رخ داده و تراکنش به حالت نامعتبر رفته. تا زمانی که `rollback()` یا `commit()` نکنی، نمی‌تونی کوئری جدیدی اجرا کنی.

راه‌حل:

  • همیشه از بلوک‌های `try…except…finally` برای مدیریت تراکنش‌ها استفاده کن. در قسمت `except` حتماً `conn.rollback()` رو فراخوانی کن.
  • مطمئن شو که بعد از هر خطا (که باعث میشه تراکنش fail بشه)، یا `rollback()` انجام بدی یا اتصال رو ببندی و دوباره برقرار کنی.

سوالات متداول (FAQ)

PostgreSQL و MySQL چه تفاوتی با هم دارند؟

PostgreSQL معمولاً به خاطر قابلیت‌های پیشرفته‌تر (مثل پشتیبانی از داده‌های ساختاریافته پیچیده‌تر، تراکنش‌های ACID قوی‌تر و قابلیت‌های گسترش‌پذیری) در پروژه‌های سازمانی و پیچیده‌تر مورد استفاده قرار می‌گیرد. MySQL معمولاً سریع‌تر و ساده‌تر برای یادگیری و استقرار اولیه است و برای وب‌سایت‌های کوچک تا متوسط و اپلیکیشن‌های عمومی مناسب‌تر است. انتخاب بین این دو بستگی به نیازهای پروژه شما دارد.

آیا `psycopg2` از Asyncio پشتیبانی می‌کند؟

`psycopg2` به طور مستقیم از asyncio پشتیبانی نمی‌کند، چون یک کتابخانه بلاک‌کننده (blocking) است. اما اگر نیاز به کار با PostgreSQL در محیط‌های Asyncio را دارید، می‌توانید از کتابخانه‌هایی مانند `asyncpg` استفاده کنید که به طور خاص برای محیط‌های ناهمگام پایتون (async) طراحی شده‌اند و عملکرد بسیار خوبی دارند.

چگونه می‌توانم اتصالات دیتابیس را بهینه کنم؟

باز و بسته کردن مکرر اتصال به دیتابیس می‌تواند سربار زیادی داشته باشد. برای بهینه‌سازی، می‌توانید از Connection Pooling استفاده کنید. کتابخانه‌هایی مثل `psycopg2.pool` به شما امکان می‌دهند تا مجموعه‌ای از اتصالات آماده را نگهداری کنید و به جای ایجاد هر بار اتصال جدید، از این pool استفاده کنید. فریمورک‌های وب مثل Django یا SQLAlchemy هم معمولاً مکانیسم‌های داخلی برای مدیریت اتصال بهینه دارند.

نتیجه‌گیری و گام بعدی

خب رفیق، تا اینجا یه مسیر کامل رو از نصب PostgreSQL و `psycopg2` تا انجام عملیات CRUD و مدیریت تراکنش‌ها با پایتون طی کردیم. دیدی که چقدر راحت و قدرتمند میشه با این ترکیب، پروژه‌های دیتابیسی رو هندل کرد. این دانش پایه خیلی محکمیه برای هر برنامه‌نویسی که میخواد با داده‌ها سر و کله بزنه.

یادت نره که بهترین راه برای یادگیری عمیق، تمرین و کدنویسیه. حالا که اصول رو بلدی، شروع کن به ساختن پروژه‌های کوچیک خودت. اگه دنبال کدهای آماده و اسنیپت‌های پایتون خفن می‌گردی، حتماً یه سر به بخش اسنیپت‌های پایتون در FA-Tools بزن تا کارات سریع‌تر پیش بره. برای تمام نیازهای کدنویسیت، FA-Tools همراه همیشگی توئه. کدنویسی خوش بگذره!

Table of Contents

آخرین نوشته‌ها