بلاگز / Python میں Ruff: اپنی پوری Lint اور Format Stack کو بدلنے کا مکمل گائیڈ

Python میں Ruff: اپنی پوری Lint اور Format Stack کو بدلنے کا مکمل گائیڈ

شائع ہوا
31 جولائی، 2026
مصنف
Faizan Nadeem
ٹیگز
Python Tooling Backend Development Ruff
تیز code linting اور formatting pass دکھاتا ہوا terminal output
Unsplash پر Emile Perron کی تصویر

زیادہ تر Python projects میں code-quality کے لیے ایک tool نہیں ہوتا — بلکہ پانچ ہوتے ہیں۔ style کے لیے Flake8، formatting کے لیے Black، imports کے لیے isort، modern syntax کے لیے pyupgrade، dead code کے لیے autoflake، اور عموماً ایک pre-commit config جو ان سب کو آپس میں جوڑتا ہے تاکہ push کرنے سے پہلے کوئی بھی انہیں چلانا نہ بھولے۔ ہر tool اپنی جگہ ٹھیک ہے۔ مسئلہ وہاں پیدا ہوتا ہے جہاں آپ کو ان سب کو، ہر save پر، ہر commit پر، ہر CI run پر، اور کسی بھی حقیقی سائز کے codebase پر چلانا پڑتا ہے — اصل friction وہیں رہتی ہے۔

زیادہ تر developers اسے “Python کو درست طریقے سے استعمال کرنے کی یہی قیمت ہے” سمجھ کر قبول کر لیتے ہیں اور کبھی اس پر سوال نہیں اٹھاتے۔ اصل حل ان tools میں سے کسی ایک کا تیز تر ورژن نہیں — بلکہ پوری stack کو کسی ایسی چیز سے بدل دینا ہے جو ایک ساتھ ان سب کے کام کر دے، اور ایسی رفتار سے کرے کہ “ہر save پر اسے چلاؤ” کوئی compromise نہیں بلکہ معمولی بات بن جائے۔ یہی Ruff ہے۔ یہ ایک واحد، Rust-based linter اور formatter ہے جو اس پانچ-tool stack کے زیادہ تر کام سنبھال لیتا ہے، تقریباً 10 سے 100 گنا زیادہ رفتار کے ساتھ، اور پانچ کی بجائے ایک configuration file کے ذریعے۔

آپ یہ سیکھیں گے:

  • Ruff اصل میں کن tools کی جگہ لیتا ہے، اور ایک Rust binary کیسے پانچ الگ Python tools کا کام کر سکتی ہے
  • Ruff کے بنیادی کام: linting، formatting، import sorting، autofixing، اور syntax upgrades
  • Ruff کے rule codes کو کیسے پڑھیں تاکہ warnings کسی اجنبی زبان جیسی محسوس نہ ہوں
  • pyproject.toml میں Ruff کو صحیح طریقے سے کیسے configure کریں، بجائے اس کے کہ blindly defaults قبول کر لیں
  • Ruff کو اپنے editor، pre-commit hooks، اور CI میں کیسے شامل کریں تاکہ یہ واقعی enforce ہو
  • Ruff اپنانے کے دوران teams کون سی عام غلطیاں کرتی ہیں، اور ان سے کیسے بچیں

فہرستِ مضامین

  1. بنیادی باتیں
  2. مکمل Architecture
  3. بنیادی Layers کی وضاحت
  4. شروع سے آخر تک Walkthrough
  5. خاص صورتیں
  6. Scaling اور Production Challenges
  7. Code Examples
  8. عام غلطیاں
  9. Production Best Practices

بنیادی باتیں

Ruff اصل میں کیا کور کرتا ہے

Ruff ایک linter اور formatter ہے جو Rust میں لکھا گیا ہے، اور یہ ان چیزوں کو یکجا کرتا ہے جن کے لیے پہلے کئی الگ Python tools درکار ہوتے تھے — Flake8 اور اس کا plugin ecosystem، Black، isort، pyupgrade، اور autoflake وغیرہ۔ اسے Charlie Marsh نے بنایا، جنہوں نے 2022 میں Astral خاص طور پر اس لیے قائم کی کہ Python ecosystem کے لیے تیز تر developer tooling بنائی جا سکے، کیونکہ وہ کئی دوسری language ecosystems میں کام کرنے کے بعد یہ محسوس کر چکے تھے کہ Python کی tooling ان کے مقابلے میں کہیں زیادہ سست محسوس ہوتی ہے۔ بعد میں Astral نے اسی تصور کے تحت uv، تیز Python package manager، بھی بنایا، اور 2026 کے اوائل میں OpenAI نے Astral کو acquire کر لیا تاکہ یہ tooling اندرونِ خانہ لائی جا سکے — Ruff خود open source ہے اور اس پر فعال development جاری ہے۔

یہ کوئی جادو نہیں کہ ایک tool پانچ کی جگہ لے سکتا ہے — اصل بات یہ ہے کہ linting، import sorting، dead-code detection، اور syntax upgrades بنیادی طور پر ایک ہی نوعیت کا عمل ہیں: code کو ایک ساخت میں parse کرنا، اس ساخت میں patterns تلاش کرنا، اور پھر جو ملے اسے report یا fix کرنا۔ ایک compiled language میں یہ سب ایک ہی pass میں کرنا ہی وہ وجہ ہے جس سے Ruff پرانی stack کے مقابلے میں صرف “کچھ زیادہ تیز” نہیں ہے — بلکہ حقیقی codebases پر معمول کے مطابق 10 سے 100 گنا زیادہ تیز ہوتا ہے۔

یہ حل کرنے کے لیے اتنا اہم مسئلہ کیوں ہے

  • Tool fatigue واقعی productivity کی قیمت وصول کرتی ہے۔ پانچ configs، پانچ commands، اور پانچ جگہیں جہاں کوئی rule خاموشی سے ویسے apply نہ ہو جیسے آپ نے توقع کی تھی — یہ overhead ہر commit، ہر reviewer، اور ہر نئے team member کی onboarding کے ساتھ بڑھتا جاتا ہے۔
  • سست tooling کو لوگ چھوڑ دیتے ہیں۔ اگر آپ کی lint suite کو 20 seconds لگتے ہیں، تو developers اسے locally چلانا چھوڑ دیتے ہیں اور CI پر چھوڑ دیتے ہیں، جس کا مطلب یہ ہے کہ feedback فوراً آنے کے بجائے منٹوں بعد آتا ہے۔
  • غیر یکساں formatting ہر diff میں noise پیدا کرتی ہے۔ enforced formatter کے بغیر، code review کا وقت اصل logic کے بجائے whitespace اور quote-style کی آراء پر خرچ ہوتا ہے۔

مکمل Architecture

Source Code ─▶ Parse ─▶ Rule Engine (lint checks) ─▶ Report / Autofix
                              │
                              ▼
                     Formatter (style only, no logic changes)
                              │
                              ▼
                     Clean, consistently-formatted code

بنیادی اصول یہ ہے: linting اور formatting الگ concerns ہیں، اگرچہ دونوں ایک ہی binary میں موجود ہوتے ہیں۔ Linting آپ کے code کے کام میں تبدیلی لاتی ہے (مثلاً unused import ہٹانا، پرانے syntax کو upgrade کرنا)؛ formatting صرف اس کی شکل بدلتی ہے (spacing، quote style، line length)۔ جب آپ rule sets configure کرنا شروع کرتے ہیں تو اس فرق کو واضح رکھنا بہت اہم ہے — آپ نہیں چاہیں گے کہ کوئی “style” tool خاموشی سے logic بدل دے، یا اس کے برعکس۔

بنیادی Layers کی وضاحت

1. Linting

یہ کیا ہے: Static analysis جو آپ کے code میں حقیقی مسائل پکڑتی ہے — unused imports، unused variables، undefined names، اور درجنوں دوسرے patterns — وہ بھی code کو execute کیے بغیر۔

یہ کیوں اہم ہے: یہی وہ مسائل ہیں جو بعد میں خاموشی سے قیمت وصول کرتے ہیں: ایک unused import جو missing dependency removal کو چھپا دے، یا ایک variable جسے assign تو کیا گیا ہو مگر کبھی پڑھا نہ گیا ہو، جو صرف style nitpick نہیں بلکہ logic bug کی نشاندہی کرتا ہے۔

import os
import sys

print("Hello")

Ruff اسے فوراً flag کرتا ہے:

F401  'sys' imported but unused

Production tip: Rule codes من مانے نہیں ہوتے — حرفی prefix بتاتا ہے کہ rule set کس tool سے آیا ہے (F Pyflakes ہے، E pycodestyle ہے، B flake8-bugbear ہے، I isort ہے)۔ یہ جاننا آپ کو اندازہ لگانے کے بجائے بہت تیزی سے سمجھنے میں مدد دیتا ہے کہ کسی code کا اصل مطلب کیا ہے۔

2. Formatting

یہ کیا ہے: Black-compatible formatter جو whitespace، quote style، اور line breaks کو normalize کرتا ہے — logic کو چھیڑے بغیر۔

# before
x=1+2
print( x )

# after
x = 1 + 2
print(x)

یہ کیوں اہم ہے: ایک consistent formatter code-review comments اور diff noise کی ایک پوری category ختم کر دیتا ہے۔ جب tool خودکار طور پر فیصلہ کرتا ہے تو spacing پر کوئی بحث نہیں کرتا۔

Production tip: CI میں ruff format اور ruff check کو دو الگ steps کے طور پر چلائیں، چاہے دونوں ایک ہی tool سے چلتے ہوں — formatting failure اور lint failure الگ معنی رکھتے ہیں، اور انہیں ایک ہی CI step میں ملا دینے سے failures کی triage مشکل ہو جاتی ہے۔

3. Import Sorting

یہ کیا ہے: imports کی automatic اور deterministic ترتیب — پہلے standard library، پھر third-party، پھر local — یعنی وہ کام جسے پہلے isort الگ سے سنبھالتا تھا۔

# before
import requests
import os
import json

# after
import json
import os

import requests

یہ کیوں اہم ہے: import order معمولی لگتی ہے، جب تک دو developers کے editors اسے مختلف انداز میں auto-sort نہ کریں اور ہر PR میں اصل تبدیلی کے ساتھ غیر متعلقہ import-reordering diff شامل نہ ہو جائے۔

4. Autofixing

یہ کیا ہے: Ruff صرف مسائل report نہیں کرتا — rules کے ایک بڑے حصے کے لیے یہ code کو دوبارہ لکھ کر انہیں براہِ راست ٹھیک بھی کر سکتا ہے۔

ruff check --fix .
# before
import os
import sys

print("Hello")

# after (sys automatically removed)
import os

print("Hello")

Production tip: زیادہ تر rule categories کے لیے autofix محفوظ ہے، مگر سب کے لیے نہیں — کچھ fixes unsafe نشان زد ہوتی ہیں کیونکہ edge cases میں وہ behavior بدل سکتی ہیں۔ اس سے پہلے کہ آپ کسی بڑے legacy codebase پر --fix کو بلا نگرانی چلنے دیں، دیکھ لیں کہ آپ نے جو rules enable کیے ہیں ان میں کون سے autofix-safe ہیں۔

5. Syntax Upgrades

یہ کیا ہے: پرانے Python idioms کو ان کے modern equivalents میں rewrite کرنا — یعنی وہ کام جو pyupgrade پہلے اکیلے کرتا تھا۔

# before
name = "{}".format(user)

# after
name = f"{user}"

یہ کیوں اہم ہے: codebases میں وہ patterns جمع ہوتے جاتے ہیں جو اس وقت کی موجودہ Python version کے مطابق لکھے گئے تھے۔ syntax-upgrade pass code کو اس interpreter version کے مطابق رکھتا ہے جسے آپ واقعی target کرتے ہیں، بجائے اس کے کہ برسوں کا stylistic debt مستقل طور پر ساتھ چلتا رہے۔

6. pyproject.toml کے ذریعے Configuration

یہ کیا ہے: ایک single configuration file جو یہ کنٹرول کرتی ہے کہ کون سے rule sets active ہوں، line length کیا ہو، target Python version کون سی ہو، اور formatting style کیا ہو — یعنی Flake8، isort، اور Black کی الگ config files کی جگہ۔

[tool.ruff]
line-length = 88
target-version = "py312"

[tool.ruff.lint]
select = ["E", "F", "B", "UP", "I"]

[tool.ruff.format]
quote-style = "double"
indent-style = "space"

Production tip: شروع میں مختصر select list رکھیں (E اور کم از کم F) اور پھر سوچ سمجھ کر اسے بڑھائیں۔ موجودہ codebase پر پہلے ہی دن تمام available rule families enable کر دینے سے عموماً warnings کی اتنی بڑی دیوار کھڑی ہو جاتی ہے کہ adoption حقیقت سے زیادہ خراب محسوس ہونے لگتی ہے۔

شروع سے آخر تک Walkthrough

ایک file کو مکمل check-and-fix cycle میں follow کریں:

  1. ابتدائی حالت۔ ایک file میں unused import، unused variable، اور inconsistent spacing موجود ہے:
    import os
    import sys
    
    def hello():
        print( "Hello" )
    
  2. linter چلائیں۔ ruff check . rule code F401 کے ساتھ unused sys import report کرتا ہے۔
  3. جو محفوظ طریقے سے fix ہو سکتا ہے اسے autofix کریں۔ ruff check --fix . unused import کو خودکار طور پر ہٹا دیتا ہے۔
  4. formatter چلائیں۔ ruff format . print(...) کے اندر spacing کو normalize کرتا ہے۔
  5. نتیجہ:
    import os
    
    
    def hello():
        print("Hello")
    
  6. Commit کریں۔ اگر pre-commit hook configure ہو، تو commit منظور ہونے سے پہلے دونوں steps خودکار طور پر چلتے ہیں — یعنی وہ developer جو ان میں سے کوئی command manually چلانا بھول بھی جائے، پھر بھی ایسا code push نہیں کر سکتا جو کسی بھی check میں fail ہو۔
  7. CI بطور آخری gate۔ pre-commit موجود ہونے کے باوجود، CI دوبارہ ruff check . اور ruff format --check . دونوں چلاتا ہے تاکہ اگر local hook bypass بھی ہو گیا ہو تو merge سے پہلے مسئلہ پکڑا جا سکے۔

خاص صورتیں

کسی ایسے rule کو suppress کرنا جس سے آپ متفق نہ ہوں۔ کبھی کبھی کوئی rule کسی خاص line یا file کے لیے موزوں نہیں ہوتا۔ ایسی صورت میں ایک مخصوص line کے لیے targeted # noqa: F401 comment استعمال کریں، بجائے config میں blanket ignore کے جو project بھر میں اس rule کو خاموش کر دے۔

Monorepos جہاں ہر package کے standards مختلف ہوں۔ Ruff کی configuration hierarchical ہوتی ہے — subdirectory میں موجود pyproject.toml root configuration کو صرف اسی package کے لیے override کر سکتا ہے، اور یہ اس وقت اہم ہوتا ہے جب monorepo کا کوئی حصہ (مثلاً کوئی legacy service) حقیقتاً ابھی نئے code کے برابر معیار پورا نہ کر سکتا ہو۔

ایک بڑے موجودہ codebase کی migration۔ برسوں پرانے موجودہ code پر ایک ساتھ ہر rule چالو کر دینے سے ہزاروں warnings پیدا ہوتی ہیں اور adoption رک جاتی ہے۔ کم سے کم rule set سے شروع کریں، اسے pass کروائیں، پھر select list کو سوچے سمجھے، reviewable increments میں بڑھائیں۔

Scaling اور Production Challenges

بڑے codebases اور CI time۔ Ruff کی رفتار ہی وہ چیز ہے جو اسے ہر push پر چلانا عملی بناتی ہے، nightly کے بجائے — جو check Flake8 کے ساتھ کسی بڑے repo پر بیس seconds لیتا تھا، وہ اکثر Ruff کے ساتھ ایک second سے بھی کم وقت میں مکمل ہو جاتا ہے، اور یہی چیز بدل دیتی ہے کہ teams اسے کتنی بار چلانے پر آمادہ ہوتی ہیں۔

legacy code جہاں پہلے سے ہزاروں violations موجود ہوں۔ سب کچھ ایک ساتھ ٹھیک کرنے کے بجائے، پہلے autofixable مسائل کے لیے ruff check --fix استعمال کریں، پھر باقی ماندہ مسائل کو rule code کے مطابق triage کریں — پورے codebase میں ایک مخصوص rule کی تمام مثالوں کو ایک ہی reviewable PR میں ٹھیک کرنا ایک بہت بڑے cleanup commit کے مقابلے میں کہیں زیادہ قابلِ انتظام ہے۔

local، pre-commit، اور CI configs کو sync میں رکھنا۔ تینوں کو ایک ہی pyproject.toml سے پڑھنا چاہیے، بجائے اس کے کہ rule selections الگ الگ جگہوں پر دہرائی جائیں — developer کے editor میں enforce ہونے والی configuration اور CI کی configuration کے درمیان drift “یہ تو locally pass ہو گیا تھا” جیسے surprises کی عام وجہ ہے۔

Code Examples

Editor integration، pre-commit، اور CI وہ تین جگہیں ہیں جہاں Ruff کو شامل کرنا ضروری ہے تاکہ یہ optional طور پر چلنے کے بجائے واقعی enforce ہو:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.14.0
    hooks:
      - id: ruff-check
        args: [--fix]
      - id: ruff-format
# .github/workflows/ruff.yml
name: Ruff
on: [push, pull_request]
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.13"
      - run: pip install ruff
      - run: ruff check .
      - run: ruff format --check .

غور کریں کہ CI job میں format کے بجائے format --check استعمال ہوا ہے — اسے unformatted code پر build fail کرنی چاہیے، نہ کہ CI میں خاموشی سے اسے reformat کر دینا چاہیے۔

عام غلطیاں

غلطی: check اور format کو ایک ہی command سمجھنا۔ اگر linting pass ہونے پر formatter چھوڑ دیا جائے تو codebase میں whitespace اور quote style غیر یکساں رہ جاتی ہے۔ حل: دونوں چلائیں، اور CI میں دونوں کو الگ الگ enforce کریں۔

غلطی: موجودہ codebase پر فوراً ہر rule family enable کر دینا۔ اس سے warnings کی تعداد اتنی بڑھ جاتی ہے کہ کوئی ان پر سنجیدگی سے کام ہی نہیں کرتا۔ حل: محدود آغاز کریں (E, F)، صاف حالت تک پہنچیں، پھر سوچ سمجھ کر دائرہ بڑھائیں۔

غلطی: targeted noqa کے بجائے blanket ignore کی طرف جانا۔ ایک file میں exception درکار ہونے کی وجہ سے پورے project میں rule خاموش کر دینا باقی ہر جگہ کے حقیقی مسائل بھی چھپا دیتا ہے۔ حل: suppressions کو صرف اسی مخصوص line یا file تک محدود رکھیں جسے ان کی ضرورت ہو۔

غلطی: unsafe نشان زد rules پر --fix کو بلا نگرانی چلانا۔ ہر autofix ہر edge case میں behavior محفوظ نہیں رکھتی۔ حل: CI میں انسانی نگرانی کے بغیر اسے automate کرنے سے پہلے دیکھ لیں کہ enable کیے گئے کون سے rules autofix کے لیے محفوظ ہیں۔

غلطی: Ruff کو صرف CI میں چلانا، locally کبھی نہیں۔ اس سے ہر lint failure editor میں فوری feedback بننے کے بجائے CI run کے ذریعے ایک سست feedback loop میں بدل جاتی ہے۔ حل: پہلے اسے اپنے editor اور pre-commit hooks میں شامل کریں — CI کو بنیادی feedback mechanism نہیں بلکہ backstop ہونا چاہیے۔

Production Best Practices

  • Configuration ایک ہی جگہ رکھیں۔ ایک واحد pyproject.toml entry، جسے آپ کا editor، pre-commit، اور CI سب پڑھیں، اس بات کو یقینی بناتی ہے کہ enforcement مختلف جگہوں پر drift نہ کرے۔
  • Rule sets کو بتدریج اپنائیں۔ خاص طور پر موجودہ codebase میں، select کو ایک ہی بار میں بڑھانے کے بجائے چھوٹے، reviewable steps میں expand کریں۔
  • CI میں lint اور format concerns کو الگ رکھیں۔ ruff check . اور ruff format --check . کو الگ steps کے طور پر چلائیں تاکہ failure واضح بتا سکے کہ آپ کس نوعیت کے مسئلے کو دیکھ رہے ہیں۔
  • Suppressions کو محدود دائرے میں رکھیں۔ جب کوئی rule کسی خاص case پر fit نہ بیٹھے تو project-wide ignore کے بجائے line-level noqa کو ترجیح دیں۔
  • رفتار کو صرف انتظار کے وقت نہیں، workflow کو بدلنے دیں۔ چونکہ Ruff اتنا تیز ہے کہ ہر save پر چل سکتا ہے، اسے صرف pre-commit یا CI gate نہیں بلکہ editor میں live feedback loop کے طور پر استعمال کریں۔

خلاصہ

Ruff محض تیز تر Flake8 نہیں ہے — یہ وہ نتیجہ ہے جو اس وقت سامنے آتا ہے جب پانچ الگ، سست tools کو ایک ایسے تیز tool سے بدل دیا جائے جو ان سب کے کام ایک ساتھ کر دے۔ رفتار headline ضرور ہے، مگر اصل فائدہ یہ ہے کہ جو tool ہر keystroke پر چلنے کے قابل ہو، وہ واقعی اسی طرح استعمال بھی ہوتا ہے، بجائے اس کے کہ developers اسے صرف commit سے پہلے ایک بار چلانے کی زحمت برداشت کریں۔ اگر آپ اب بھی Flake8، Black، اور isort کو الگ الگ configs کے ساتھ سنبھال رہے ہیں، تو migration اتنی بڑی نہیں جتنی بظاہر لگتی ہے۔

آپ کی team کو اپنی lint اور format stack یکجا کرنے سے اب بھی کیا چیز روکے ہوئے ہے — tooling، legacy config، یا محض inertia؟

مزید مضامین