microdot --- إطار عمل HTTP بسيط

Microdot هو إطار عمل HTTP صغير مستوحى من Flask لأجل MicroPython. يعمل فوق asyncio، ويتعامل مع عملاء متزامنين متعددين دون استخدام خيوط، ويوفر واجهة مألوفة قائمة على مزينات المسارات. يبدو التطبيق البسيط هكذا:

from microdot import Microdot

app = Microdot()

@app.route('/')
async def index(request):
    return 'Hello, world!'

app.run(host='0.0.0.0', port=80)

class Microdot

class microdot.Microdot

مثيل تطبيق HTTP. يُنشأ مثيل واحد قرب أعلى البرنامج النصي ويُزيَّن بمعالجات المسارات؛ ويبدأ التقديم باستدعاء run() أو انتظار start_server().

تسجيل المسارات

route(url_pattern: str, methods: list | None = None) Callable

مزيِّن يسجل معالجًا لأجل url_pattern ضمن طرق HTTP المذكورة (الافتراضي ['GET']). يُرجع المزيِّن الذي يسجل الدالة كمعالج عند تطبيقه عليها ويُرجع الدالة دون تغيير.

url_pattern

نمط مسار. يدعم المقاطع الثابتة (/users) والمقاطع الديناميكية المحاطة بـ < / >. تقبل المقاطع الديناميكية بادئة نوع اختيارية مفصولة بـ : -- <int:id> و <path:rest> و <re:[0-9a-f]+:hex>. والنوع الافتراضي هو string.

methods

قائمة بأسماء طرق HTTP. إذا حُذفت، فلن يُطابق سوى GET.

يُستدعى المعالج بكائن الطلب أولًا، ثم بأي مقاطع ديناميكية ملتقطة كوسائط بأسماء مفاتيح. تصبح القيمة المُرجعة من المعالج استجابة HTTP: إما Response، أو سلسلة نصية، أو صف (body, status_code[, headers])، أو قاموس / قائمة (تُرسل بصيغة JSON).

get(url_pattern: str) Callable

اسم بديل مريح لـ route(url_pattern, methods=['GET']) -- يسجل الدالة المزيَّنة كمعالج GET لأجل url_pattern. يُرجع المزيِّن.

post(url_pattern: str) Callable

اسم بديل مريح لـ route(url_pattern, methods=['POST']) -- يسجل الدالة المزيَّنة كمعالج POST لأجل url_pattern. يُرجع المزيِّن.

put(url_pattern: str) Callable

اسم بديل مريح لـ route(url_pattern, methods=['PUT']) -- يسجل الدالة المزيَّنة كمعالج PUT لأجل url_pattern. يُرجع المزيِّن.

patch(url_pattern: str) Callable

اسم بديل مريح لـ route(url_pattern, methods=['PATCH']) -- يسجل الدالة المزيَّنة كمعالج PATCH لأجل url_pattern. يُرجع المزيِّن.

delete(url_pattern: str) Callable

اسم بديل مريح لـ route(url_pattern, methods=['DELETE']) -- يسجل الدالة المزيَّنة كمعالج DELETE لأجل url_pattern. يُرجع المزيِّن.

خطافات دورة الحياة

before_request(f: Callable) Callable

مزيِّن يسجل f لتُنفَّذ قبل كل طلب. تأخذ f كائن الطلب؛ وتُتجاهَل قيمتها المُرجعة عادةً. ولاختصار مسار الطلب، أرجع Response (أو قيمة تتحول إليها) -- عندئذ يُتخطى باقي خط المعالجة وتُرسل تلك الاستجابة. تُرجع f دون تغيير.

after_request(f: Callable) Callable

مزيِّن يسجل f لتُنفَّذ بعد كل طلب ناجح. تأخذ f الوسيطين (request, response) ويجب أن تُرجع كائن الاستجابة (ربما بعد تعديله). تُرجع f دون تغيير.

after_error_request(f: Callable) Callable

مزيِّن يسجل f لتُنفَّذ بعد توليد Microdot لاستجابة خطأ (404، 500، استثناء مرفوع، إلخ.). تأخذ f الوسيطين (request, response) ويجب أن تُرجع كائن الاستجابة (ربما بعد تعديله). تُرجع f دون تغيير.

errorhandler(status_code_or_exception_class) Callable

مزيِّن يسجل معالجًا مخصصًا لرمز حالة HTTP أو لصنف استثناء Python. بالنسبة لرموز الحالة، يأخذ المعالج الطلب فقط؛ وبالنسبة لأصناف الاستثناءات، يأخذ (request, exception). يُرجع المزيِّن.

التركيب والإجهاض

mount(subapp: Microdot, url_prefix: str = '', local: bool = False) None

يربط مسارات مثيل Microdot آخر تحت url_prefix. عندما تكون local قيمتها False (الافتراضي)، تُربط أيضًا معالجات before / after / error الخاصة بالتطبيق الفرعي بالتطبيق الأصل. وعندما تكون True، تُنفَّذ تلك المعالجات لمسارات التطبيق الفرعي فقط. يُرجع None.

static abort(status_code: int, reason: str | None = None) None

ارفع HTTPException لإجهاض الطلب الحالي برمز الحالة المعطى. لا يعود أبدًا بصورة طبيعية -- يلتقط الإطار الاستثناء ويحوله إلى استجابة الخطأ المقابلة. مريح داخل أجسام المسارات:

from microdot import abort

@app.get('/users/<int:id>')
def get_user(request, id):
    user = lookup(id)
    if user is None:
        abort(404)
    return user.to_dict()

تشغيل الخادم

run(host: str = '0.0.0.0', port: int = 5000, debug: bool = False, ssl=None) None

يحجب الخيط المستدعي، ويُشغِّل asyncio.run() على start_server(). لا يُرجع None إلا بعد استدعاء shutdown() وخروج حلقة الإصغاء. أبسط طريقة لتشغيل تطبيق مستقل:

app.run(host='0.0.0.0', port=80)
async start_server(host: str = '0.0.0.0', port: int = 5000, debug: bool = False, ssl=None, start_serving: bool = True) None

يبدأ الخادم كروتين متزامن (coroutine). استخدم هذا عند الدمج مع حلقة أحداث asyncio قائمة إلى جانب مهام أخرى. لا يعود الروتين المتزامن حتى يُستدعى shutdown()

async def main():
    await asyncio.gather(
        app.start_server(port=80),
        capture_loop(),
    )
host

واجهة الشبكة التي يُصغى عليها. '0.0.0.0' (الافتراضي) تعني جميع الواجهات؛ و '127.0.0.1' تعني الاسترجاع المحلي (loopback) فقط.

port

منفذ TCP الذي يُصغى عليه. الافتراضي 5000 -- اختر 80 لأجل HTTP العادي، و 443 لأجل HTTPS.

debug

إذا كانت True، فسجِّل كل طلب وأخرِج تتبعات المكدس إلى stdout.

ssl

كائن ssl.SSLContext لتغليف الاتصالات الواردة بـ TLS. القيمة None (الافتراضي) تعني HTTP عاديًا.

start_serving

ذو صلة على CPython فقط؛ ويُتجاهَل على MicroPython.

shutdown() None

يطلب إيقاف تشغيل الخادم بسلاسة. يمكن استدعاؤه بأمان من معالج مسار -- يكتمل الطلب الحالي قبل خروج الحلقة. يعود فورًا؛ ويحدث الإيقاف الفعلي بمجرد انتهاء الطلب الجاري تنفيذه.

السمات

url_map: list

قائمة بالمسارات المسجلة على هيئة صفوف (methods, URLPattern, handler, url_prefix, subapp).

before_request_handlers: list

قائمة بالكائنات القابلة للاستدعاء المسجلة بـ before_request()، بترتيب التسجيل. يُنفَّذ كل منها مع كائن الطلب قبل معالج المسار. يعمل تعديل القائمة مباشرةً لكنه نادرًا ما يُحتاج إليه -- يُفضَّل استخدام المزيِّن.

after_request_handlers: list

قائمة بالكائنات القابلة للاستدعاء المسجلة بـ after_request()، بترتيب التسجيل. يُنفَّذ كل منها مع (request, response) بعد طلب ناجح ويجب أن يُرجع الاستجابة (ربما بعد تعديلها).

after_error_request_handlers: list

قائمة بالكائنات القابلة للاستدعاء المسجلة بـ after_error_request()، بترتيب التسجيل. يُنفَّذ كل منها مع (request, response) بعد توليد استجابة خطأ (بواسطة الإطار أو بواسطة معالج خطأ في التطبيق) ويجب أن يُرجع الاستجابة.

error_handlers: dict

تعيين (mapping) لمفاتيح الأخطاء إلى كائنات معالجة قابلة للاستدعاء، تُملأ بواسطة errorhandler(). المفاتيح إما رموز حالة HTTP (int) أو أصناف استثناءات Python؛ والقيم هي المعالجات المسجلة. تأخذ معالجات رموز الحالة (request)؛ وتأخذ معالجات أصناف الاستثناءات (request, exception).

debug: bool

تكون True أثناء تشغيل الخادم بـ debug=True.

class Request

class microdot.Request

طلب HTTP وارد. تُمرَّر المثيلات إلى معالجات المسارات كأول وسيط موضعي. لا تنشئ التطبيقات Request مباشرةً.

سمات الصنف

max_content_length: int

ترفض الطلبات التي يتجاوز Content-Length فيها هذا العدد من البايتات باستجابة 413. الافتراضي 16 كيلوبايت.

max_body_length: int

أكبر جسم يُخزَّن مؤقتًا في الذاكرة ويُعرَض عبر body. تبقى الأجسام الأكبر (حتى max_content_length) على المقبس (socket) ويجب قراءتها عبر stream. الافتراضي 16 كيلوبايت.

max_readline: int

أقصى طول لسطر طلب / سطر ترويسة واحد بالبايتات. الافتراضي 2 كيلوبايت.

سمات المثيل

app: Microdot

مثيل Microdot الذي يعالج الطلب.

client_addr: tuple

عنوان العميل على هيئة (host, port).

method: str

سلسلة طريقة HTTP ('GET' و 'POST' ...).

scheme: str

إما 'http' أو 'https'.

url: str

مسار عنوان URL الكامل للطلب وسلسلة الاستعلام (كل ما يأتي بعد المضيف).

path: str

جزء المسار فقط.

query_string: str | None

جزء سلسلة الاستعلام الخام، أو None.

args: MultiDict

سلسلة الاستعلام بعد تحليلها على هيئة MultiDict.

headers: NoCaseDict

ترويسات الطلب على هيئة NoCaseDict (بحث غير حساس لحالة الأحرف).

cookies: dict

ترويسة Cookie بعد تحليلها على هيئة dict.

content_length: int

قيمة Content-Length الصحيحة، أو 0 في حال غيابها.

content_type: str | None

قيمة ترويسة Content-Type، أو None.

g: object

حاوية حرة الشكل خاصة بكل طلب (كائن فارغ). أسنِد إليها سمات لتمرير القيم بين الخطافات والمعالجات (request.g.user = ...).

route: Callable

دالة المعالج التي طابقت هذا الطلب.

url_prefix: str

البادئة التي رُكِّب المسار تحتها، أو ''.

subapp: Microdot | None

مثيل التطبيق الفرعي المُركَّب، أو None.

الوصول إلى الجسم

body: bytes

جسم الطلب الكامل على هيئة bytes. يكون فارغًا عندما يُبَثُّ الجسم (انظر stream).

stream: object

كائن دفق غير متزامن يعرض read() على الجسم. استخدم هذا لأجل الأجسام الأكبر من max_body_length.

json: dict | list | str | int | float | bool | None

الجسم بعد تحليله بصيغة JSON (إما dict أو list أو str أو int أو float أو bool -- أيًا كان ما تشفره الحمولة)، أو None إذا لم يكن Content-Type هو application/json.

form: MultiDict | None

حقول النموذج المشفرة بصيغة URL على هيئة MultiDict، أو None. لأجل multipart/form-data، زيِّن المسار بـ microdot.multipart.with_form_data().

files: dict | None

الملفات المرفوعة على هيئة {name: FileUpload}، تُملأ بواسطة microdot.multipart.with_form_data(). تكون None حتى يُنفَّذ ذلك المزيِّن.

after_request(f: Callable) Callable

يسجل خطاف ما بعد الطلب محليًا للطلب -- يُنفَّذ بعد معالجات ما بعد الطلب على مستوى التطبيق، عند النجاح فقط. تأخذ f الوسيطين (request, response) ويجب أن تُرجع كائن الاستجابة (ربما بعد تعديله). تُرجع f دون تغيير.

class Response

class microdot.Response(body=b'', status_code: int = 200, headers: dict | None = None, reason: str | None = None)

استجابة HTTP. تُرجع معظم المعالجات قيمًا يحولها Microdot إلى Response تلقائيًا؛ أنشئ واحدة مباشرةً عندما تحتاج إلى ترويسات مخصصة أو رمز حالة محدد.

body

جسم الاستجابة. تُشفَّر str بترميز UTF-8؛ وتُشفَّر dict / list بصيغة JSON ويُضبَط Content-Type تبعًا لذلك؛ وتُرسَل bytes كما هي؛ ويُبَث كائن شبيه بالملف أو مولِّد غير متزامن.

status_code

حالة HTTP رقمية. الافتراضي 200.

headers

قاموس بترويسات الاستجابة (غير حساس لحالة الأحرف).

reason

عبارة سبب مخصصة. القيمة الافتراضية "OK" لأجل 200 و "N/A" فيما عدا ذلك.

سمات الصنف

default_content_type: str

نوع المحتوى المستخدم عندما لا يُضبَط أي نوع صراحةً. الافتراضي 'text/plain'.

default_send_file_max_age: int | None

قيمة Cache-Control: max-age لأجل send_file() عندما لا تُعطى قيمة max_age. القيمة None (الافتراضي) تعني عدم وجود ترويسة Cache-Control.

send_file_buffer_size: int

حجم القطعة لأجل بث send_file(). الافتراضي 1024.

already_handled: None

قيمة حارسة (sentinel) تُرجَع من المعالجات التي كتبت الاستجابة مباشرةً بالفعل (تستخدمها ترقيات WebSocket). تكون دائمًا None؛ والمهم هو هويتها.

types_map: dict

خريطة من الامتداد إلى نوع mime يستخدمها send_file() لاستنتاج نوع المحتوى. تربط css و gif و html و jpg و js و json و png و txt و svg.

الطرائق

يضيف ترويسة Set-Cookie. قد تكون expires سلسلة منسقة مسبقًا أو كائنًا شبيهًا بـ datetime له timetuple(). يُرجع None؛ وتُلحَق الترويسة بهذه الاستجابة في مكانها.

يضبط Set-Cookie تنتهي صلاحية ملف تعريف الارتباط المعطى فورًا. تقبل kwargs الخيارات نفسها التي تقبلها set_cookie() (path و domain و secure و http_only و partitioned)؛ وتُتجاهَل expires و max_age. يُرجع None؛ وتُلحَق الترويسة بهذه الاستجابة في مكانها.

classmethod redirect(location: str, status_code: int = 302) Response

يُرجع استجابة إعادة توجيه:

@app.get('/old')
def old(request):
    return Response.redirect('/new')
classmethod send_file(filename: str, status_code: int = 200, content_type: str | None = None, stream=None, max_age: int | None = None, compressed: bool | str = False, file_extension: str = '') Response

يبث ملفًا من نظام الملفات كجسم للاستجابة. يُستنتَج content_type من الامتداد عبر types_map إن لم يُعطَ. ويضبط compressed=True الترويسة Content-Encoding: gzip (يجب أن يكون الملف مضغوطًا بالفعل).

تحذير

يُفتح filename مباشرةً. لا تمرر أبدًا مسارًا مزودًا من المستخدم دون تطهير -- فالقيام بذلك يتيح كشف ملفات اعتباطية.

الاستثناءات

exception microdot.HTTPException

يُرفع بواسطة abort() لاختصار مسار طلب برمز حالة محدد. يلتقط Microdot هذا ويحوله إلى استجابة الخطأ المقابلة.

status_code: int

رمز حالة HTTP الرقمي المراد إرجاعه -- القيمة المُمرَّرة إلى abort().

reason: str

عبارة السبب المراد تضمينها في استجابة الخطأ. إذا لم تُعطَ إلى abort()، فالقيمة الافتراضية "<status_code> error" (مثل "404 error").

class URLPattern

class microdot.URLPattern(url_pattern: str)

الصيغة المُجمَّعة لنمط عنوان URL الخاص بمسار. يُنشأ تلقائيًا بواسطة Microdot.route()؛ ونادرًا ما تنشئ التطبيقات واحدًا مباشرةً.

classmethod register_type(type_name: str, pattern: str = '[^/]+', parser: Callable | None = None) None

يسجل نوع مقطع ديناميكي جديد للاستخدام في أنماط المسارات. يُرجع None؛ ويُضاف النوع إلى سجل الأنواع على مستوى الصنف. على سبيل المثال لإضافة نوع <uuid:...>

URLPattern.register_type('uuid', '[0-9a-f-]{36}')

parser هو كائن قابل للاستدعاء اختياري يحوِّل السلسلة الملتقطة قبل أن تصل إلى المعالج.

match(path: str) dict | None

يطابق path مع النمط ويُرجع قاموسًا بالمجموعات الملتقطة، أو None في حال عدم وجود تطابق.

أصناف مساعِدة

class microdot.MultiDict(initial_dict: dict | None = None)

صنف فرعي من dict يخزن قيمًا متعددة لكل مفتاح. يُستخدم لسلاسل الاستعلام وأجسام النماذج المشفرة بصيغة URL حيث يمكن أن يظهر المفتاح نفسه أكثر من مرة (?tag=a&tag=b).

get(key, default=None, type: Callable | None = None)

يُرجع القيمة الأولى لأجل key، مع تحويلها اختياريًا بـ type (كائن قابل للاستدعاء). يُرجع default إذا كان المفتاح مفقودًا أو فشل التحويل؛ وإلا فيُرجع القيمة (المحوَّلة اختياريًا).

getlist(key, type: Callable | None = None) list

يُرجع كل القيم لأجل key على هيئة قائمة، مع تحويل كل قيمة اختياريًا بـ type. يُرجع قائمة فارغة إذا لم يكن key موجودًا.

class microdot.NoCaseDict

صنف فرعي من dict بمفاتيح نصية غير حساسة لحالة الأحرف. يُستخدم لأجل Request.headers و Response.headers.

دوال على مستوى الوحدة

microdot.abort(status_code: int, reason: str | None = None) None

اختصار لـ Microdot.abort(). لا يعود أبدًا بصورة طبيعية -- يرفع HTTPException. قابل للاستيراد بصيغة from microdot import abort.

microdot.redirect(location: str, status_code: int = 302) Response

اختصار لـ Response.redirect(). قابل للاستيراد بصيغة from microdot import redirect.

microdot.send_file(filename: str, **kwargs) Response

اختصار لـ Response.send_file().

microdot.urlencode(s: str) str

يشفِّر مكوِّن عنوان URL بترميز النسبة المئوية (percent-encode). يستبدل الأحرف ذات المعنى المحجوز في عنوان URL (/ و ? و & و = و # ومسافة ...) بهرَوبها الست عشري %xx بحيث يمكن أن توضع النتيجة بأمان داخل مقطع مسار أو قيمة استعلام. يُرجع str المشفرة.

microdot.urldecode(s: str) str

يفك تشفير مكوِّن عنوان URL من ترميز النسبة المئوية -- معكوس urlencode(). تُستبدَل هرَوبات %xx بالبايت الذي تشفره، وتُحوَّل + إلى مسافة (وفق العرف التاريخي لسلسلة الاستعلام). يُرجع str المفكوكة.

الوحدات الفرعية