Flask + حاقن التبعية - دليل حقن التبعية

مرحبًا ،



أنا مبتكر حاقن التبعية . هذا هو إطار عمل حقن التبعية لبايثون.



في هذا البرنامج التعليمي ، أريد أن أوضح كيفية استخدام حاقن التبعية لتطوير تطبيقات Flask.



يتكون الدليل من الأجزاء التالية:



  1. ماذا سنبني؟
  2. جهز البيئة
  3. هيكل المشروع
  4. مرحبا بالعالم!
  5. بما في ذلك الأنماط
  6. ربط جيثب
  7. خدمة البحث
  8. ربط البحث
  9. قليلا من إعادة بناء ديون
  10. إضافة الاختبارات
  11. خاتمة


يمكن العثور على المشروع المكتمل على Github .



للبدء ، يجب أن يكون لديك:



  • Python 3.5+
  • بيئة افتراضية


ويستحب أن يكون لديك:



  • مهارات التطوير الأولية باستخدام Flask
  • فهم مبدأ حقن التبعية


ماذا سنبني؟



سنقوم ببناء تطبيق يساعدك في البحث عن مستودعات على Github. دعنا نسميها Github Navigator.



كيف يعمل Github Navigator؟



  • يفتح المستخدم صفحة ويب حيث يُطلب منه إدخال استعلام بحث.
  • يقوم المستخدم بإدخال استعلام والضغط على Enter.
  • يبحث Github Navigator عن مستودعات مطابقة على Github.
  • عند انتهاء البحث ، يعرض Github Navigator للمستخدم صفحة ويب تحتوي على نتائج.
  • تعرض صفحة النتائج جميع المستودعات التي تم العثور عليها واستعلام البحث.
  • لكل مستودع ، يرى المستخدم:

    • اسم المستودع
    • مالك المستودع
    • الالتزام الأخير بالمستودع
  • يمكن للمستخدم النقر فوق أي عنصر لفتح صفحته على Github.






جهز البيئة



بادئ ذي بدء ، نحتاج إلى إنشاء مجلد مشروع وبيئة افتراضية:



mkdir ghnav-flask-tutorial
cd ghnav-flask-tutorial
python3 -m venv venv


لنقم الآن بتنشيط البيئة الافتراضية:



. venv/bin/activate


البيئة جاهزة ، لنبدأ الآن بهيكل المشروع.



هيكل المشروع



لنقم بإنشاء الهيكل التالي في المجلد الحالي. اترك كل الملفات فارغة الآن. هذا ليس حرجا بعد.



الهيكل الأولي:



./
├── githubnavigator/
│   ├── __init__.py
│   ├── application.py
│   ├── containers.py
│   └── views.py
├── venv/
└── requirements.txt


حان الوقت لتثبيت Flask and Dependency Injector.



دعنا نضيف الأسطر التالية إلى الملف requirements.txt:



dependency-injector
flask


الآن دعنا نثبتها:



pip install -r requirements.txt


وتحقق من نجاح التثبيت:



python -c "import dependency_injector; print(dependency_injector.__version__)"
python -c "import flask; print(flask.__version__)"


سترى شيئًا مثل:



(venv) $ python -c "import dependency_injector; print(dependency_injector.__version__)"
3.22.0
(venv) $ python -c "import flask; print(flask.__version__)"
1.1.2


مرحبا بالعالم!



لنقم بإنشاء تطبيق hello world ضئيل.



دعنا نضيف الأسطر التالية إلى الملف views.py:



"""Views module."""


def index():
    return 'Hello, World!'


الآن دعونا نضيف حاوية من التبعيات (علاوة على ذلك مجرد حاوية). ستحتوي الحاوية على جميع مكونات التطبيق. دعونا نضيف أول مكونين. هذا هو تطبيق وعرض Flask index.



دعنا نضيف ما يلي إلى الملف containers.py:



"""Application containers module."""

from dependency_injector import containers
from dependency_injector.ext import flask
from flask import Flask

from . import views


class ApplicationContainer(containers.DeclarativeContainer):
    """Application container."""

    app = flask.Application(Flask, __name__)

    index_view = flask.View(views.index)


نحتاج الآن إلى إنشاء مصنع لتطبيق Flask. عادة ما يطلق عليه create_app(). سيخلق حاوية. سيتم استخدام الحاوية لإنشاء تطبيق Flask. الخطوة الأخيرة هي إعداد التوجيه - سنقوم بتعيين عرض index_viewمن الحاوية لمعالجة الطلبات إلى جذر "/" تطبيقنا.



دعنا نحرر application.py:



"""Application module."""

from .containers import ApplicationContainer


def create_app():
    """Create and return Flask application."""
    container = ApplicationContainer()

    app = container.app()
    app.container = container

    app.add_url_rule('/', view_func=container.index_view.as_view())

    return app


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


طلبنا الآن جاهز ليقول "Hello، World!"



تشغيل في المحطة:



export FLASK_APP=githubnavigator.application
export FLASK_ENV=development
flask run


يجب أن يبدو الناتج كما يلي:



* Serving Flask app "githubnavigator.application" (lazy loading)
* Environment: development
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with fsevents reloader
* Debugger is active!
* Debugger PIN: 473-587-859


افتح المتصفح وانتقل إلى http://127.0.0.1:5000/ .



سترى "Hello، World!"



ممتاز. يبدأ تطبيقنا البسيط ويعمل بنجاح.



لنجعلها أجمل قليلاً.



بما في ذلك الأنماط



سنكون باستخدام التمهيد 4 . دعونا نستخدم ملحق Bootstrap-Flask لهذا الغرض . سيساعدنا ذلك في إضافة جميع الملفات الضرورية ببضع نقرات.



أضف bootstrap-flaskإلى requirements.txt:



dependency-injector
flask
bootstrap-flask


ونفذ في الجهاز:



pip install --upgrade -r requirements.txt


لنقم الآن بإضافة الامتداد bootstrap-flaskإلى الحاوية.



تحرير containers.py:



"""Application containers module."""

from dependency_injector import containers
from dependency_injector.ext import flask
from flask import Flask
from flask_bootstrap import Bootstrap

from . import views


class ApplicationContainer(containers.DeclarativeContainer):
    """Application container."""

    app = flask.Application(Flask, __name__)

    bootstrap = flask.Extension(Bootstrap)

    index_view = flask.View(views.index)


لنبدأ الامتداد bootstrap-flask. سنحتاج إلى التغيير create_app().



تحرير application.py:



"""Application module."""

from .containers import ApplicationContainer


def create_app():
    """Create and return Flask application."""
    container = ApplicationContainer()

    app = container.app()
    app.container = container

    bootstrap = container.bootstrap()
    bootstrap.init_app(app)

    app.add_url_rule('/', view_func=container.index_view.as_view())

    return app


الآن نحن بحاجة إلى إضافة قوالب. للقيام بذلك ، نحتاج إلى إضافة مجلد templates/إلى الحزمة githubnavigator. أضف ملفين داخل مجلد القوالب:



  • base.html - نموذج أساسي
  • index.html - قالب الصفحة الرئيسية


قم بإنشاء مجلد templatesوملفين فارغين بداخله base.htmlو index.html:



./
├── githubnavigator/
│   ├── templates/
│   │   ├── base.html
│   │   └── index.html
│   ├── __init__.py
│   ├── application.py
│   ├── containers.py
│   └── views.py
├── venv/
└── requirements.txt


الآن دعنا نملأ النموذج الأساسي.



دعنا نضيف الأسطر التالية إلى الملف base.html:



<!doctype html>
<html lang="en">
    <head>
        {% block head %}
        <!-- Required meta tags -->
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

        {% block styles %}
            <!-- Bootstrap CSS -->
            {{ bootstrap.load_css() }}
        {% endblock %}

        <title>{% block title %}{% endblock %}</title>
        {% endblock %}
    </head>
    <body>
        <!-- Your page content -->
        {% block content %}{% endblock %}

        {% block scripts %}
            <!-- Optional JavaScript -->
            {{ bootstrap.load_js() }}
        {% endblock %}
    </body>
</html>


الآن دعنا نملأ قالب الصفحة الرئيسية.



دعنا نضيف الأسطر التالية إلى الملف index.html:



{% extends "base.html" %}

{% block title %}Github Navigator{% endblock %}

{% block content %}
<div class="container">
    <h1 class="mb-4">Github Navigator</h1>

    <form>
        <div class="form-group form-row">
            <div class="col-10">
                <label for="search_query" class="col-form-label">
                    Search for:
                </label>
                <input class="form-control" type="text" id="search_query"
                       placeholder="Type something to search on the GitHub"
                       name="query"
                       value="{{ query if query }}">
            </div>
            <div class="col">
                <label for="search_limit" class="col-form-label">
                    Limit:
                </label>
                <select class="form-control" id="search_limit" name="limit">
                    {% for value in [5, 10, 20] %}
                    <option {% if value == limit %}selected{% endif %}>
                        {{ value }}
                    </option>
                    {% endfor %}
                </select>
            </div>
        </div>
    </form>

    <p><small>Results found: {{ repositories|length }}</small></p>

    <table class="table table-striped">
        <thead>
            <tr>
                <th>#</th>
                <th>Repository</th>
                <th class="text-nowrap">Repository owner</th>
                <th class="text-nowrap">Last commit</th>
            </tr>
        </thead>
        <tbody>
        {% for repository in repositories %} {{n}}
            <tr>
              <th>{{ loop.index }}</th>
              <td><a href="{{ repository.url }}">
                  {{ repository.name }}</a>
              </td>
              <td><a href="{{ repository.owner.url }}">
                  <img src="{{ repository.owner.avatar_url }}"
                       alt="avatar" height="24" width="24"/></a>
                  <a href="{{ repository.owner.url }}">
                      {{ repository.owner.login }}</a>
              </td>
              <td><a href="{{ repository.latest_commit.url }}">
                  {{ repository.latest_commit.sha }}</a>
                  {{ repository.latest_commit.message }}
                  {{ repository.latest_commit.author_name }}
              </td>
            </tr>
        {% endfor %}
        </tbody>
    </table>
</div>

{% endblock %}


رائع ، أوشك على الانتهاء. الخطوة الأخيرة هي تغيير العرض indexلاستخدام النموذج index.html.



دعنا نحرر views.py:



"""Views module."""

from flask import request, render_template


def index():
    query = request.args.get('query', 'Dependency Injector')
    limit = request.args.get('limit', 10, int)

    repositories = []

    return render_template(
        'index.html',
        query=query,
        limit=limit,
        repositories=repositories,
    )


منجز.



تأكد من تشغيل التطبيق أو تشغيله flask runوافتح http://127.0.0.1:5000/ .



يجب أن ترى:







ربط جيثب



في هذا القسم ، سنقوم بدمج تطبيقنا مع Github API. سنستخدم

مكتبة PyGithub .



دعنا نضيفه إلى requirements.txt:



dependency-injector
flask
bootstrap-flask
pygithub


ونفذ في الجهاز:



pip install --upgrade -r requirements.txt


نحتاج الآن إلى إضافة عميل Github API إلى الحاوية. للقيام بذلك ، سنحتاج إلى استخدام مزودين جديدين من الوحدة dependency_injector.providers:



  • سيقوم الموفر Factoryبإنشاء عميل Github.
  • Configurationسيمرر الموفر رمز واجهة برمجة التطبيقات ومهلة Github للعميل.


دعنا نقوم به.



دعنا نحرر containers.py:



"""Application containers module."""

from dependency_injector import containers, providers
from dependency_injector.ext import flask
from flask import Flask
from flask_bootstrap import Bootstrap
from github import Github

from . import views


class ApplicationContainer(containers.DeclarativeContainer):
    """Application container."""

    app = flask.Application(Flask, __name__)

    bootstrap = flask.Extension(Bootstrap)

    config = providers.Configuration()

    github_client = providers.Factory(
        Github,
        login_or_token=config.github.auth_token,
        timeout=config.github.request_timeout,
    )

    index_view = flask.View(views.index)


استخدمنا معلمات التكوين قبل تحديد قيمها. هذا هو المبدأ الذي يعمل به المزود Configuration.



أولا نستخدم ، ثم نضع القيم.



الآن دعنا نضيف ملف التكوين.

سوف نستخدم YAML.



قم بإنشاء ملف فارغ config.ymlفي جذر المشروع:



./
├── githubnavigator/
│   ├── templates/
│   │   ├── base.html
│   │   └── index.html
│   ├── __init__.py
│   ├── application.py
│   ├── containers.py
│   └── views.py
├── venv/
├── config.yml
└── requirements.txt


واملأه بالأسطر التالية:



github:
  request_timeout: 10


للعمل مع ملف التكوين ، سنستخدم مكتبة PyYAML . دعنا نضيفه إلى الملف مع التبعيات.



تحرير requirements.txt:



dependency-injector
flask
bootstrap-flask
pygithub
pyyaml


وقم بتثبيت التبعية:



pip install --upgrade -r requirements.txt


سنستخدم متغير بيئة لتمرير الرمز المميز لواجهة برمجة التطبيقات GITHUB_TOKEN.



نحتاج الآن إلى التعديل create_app()للقيام بإجراءين عند بدء التطبيق:



  • تكوين التحميل من config.yml
  • تحميل رمز API من متغير البيئة GITHUB_TOKEN


تحرير application.py:



"""Application module."""

from .containers import ApplicationContainer


def create_app():
    """Create and return Flask application."""
    container = ApplicationContainer()
    container.config.from_yaml('config.yml')
    container.config.github.auth_token.from_env('GITHUB_TOKEN')

    app = container.app()
    app.container = container

    bootstrap = container.bootstrap()
    bootstrap.init_app(app)

    app.add_url_rule('/', view_func=container.index_view.as_view())

    return app


الآن نحن بحاجة إلى إنشاء رمز API مميز.



لهذا تحتاج:



  • اتبع هذا البرنامج التعليمي على جيثب
  • تعيين الرمز إلى متغير البيئة:



    export GITHUB_TOKEN=<your token>


يمكن تخطي هذا العنصر مؤقتًا.



سيتم تشغيل التطبيق بدون رمز مميز ، ولكن بنطاق ترددي محدود. الحد الأقصى للعملاء غير المصدقين: 60 طلبًا في الساعة. هناك حاجة إلى الرمز المميز لزيادة هذه الحصة إلى 5000 في الساعة.


منجز.



اكتمل تثبيت Github API الخاص بالعميل.



خدمة البحث



حان الوقت لإضافة خدمة البحث SearchService. هو سوف:



  • ابحث على جيثب
  • احصل على بيانات إضافية حول الالتزامات
  • تحويل نتيجة التنسيق


SearchServiceسيستخدم عميل Github API.



قم بإنشاء ملف فارغ services.pyفي الحزمة githubnavigator:



./
├── githubnavigator/
│   ├── templates/
│   │   ├── base.html
│   │   └── index.html
│   ├── __init__.py
│   ├── application.py
│   ├── containers.py
│   ├── services.py
│   └── views.py
├── venv/
├── config.yml
└── requirements.txt


وأضف إليها الأسطر التالية:



"""Services module."""

from github import Github
from github.Repository import Repository
from github.Commit import Commit


class SearchService:
    """Search service performs search on Github."""

    def __init__(self, github_client: Github):
        self._github_client = github_client

    def search_repositories(self, query, limit):
        """Search for repositories and return formatted data."""
        repositories = self._github_client.search_repositories(
            query=query,
            **{'in': 'name'},
        )
        return [
            self._format_repo(repository)
            for repository in repositories[:limit]
        ]

    def _format_repo(self, repository: Repository):
        commits = repository.get_commits()
        return {
            'url': repository.html_url,
            'name': repository.name,
            'owner': {
                'login': repository.owner.login,
                'url': repository.owner.html_url,
                'avatar_url': repository.owner.avatar_url,
            },
            'latest_commit': self._format_commit(commits[0]) if commits else {},
        }

    def _format_commit(self, commit: Commit):
        return {
            'sha': commit.sha,
            'url': commit.html_url,
            'message': commit.commit.message,
            'author_name': commit.commit.author.name,
        }


الآن دعنا نضيف SearchServiceإلى الحاوية.



تحرير containers.py:



"""Application containers module."""

from dependency_injector import containers, providers
from dependency_injector.ext import flask
from flask import Flask
from flask_bootstrap import Bootstrap
from github import Github

from . import services, views


class ApplicationContainer(containers.DeclarativeContainer):
    """Application container."""

    app = flask.Application(Flask, __name__)

    bootstrap = flask.Extension(Bootstrap)

    config = providers.Configuration()

    github_client = providers.Factory(
        Github,
        login_or_token=config.github.auth_token,
        timeout=config.github.request_timeout,
    )

    search_service = providers.Factory(
        services.SearchService,
        github_client=github_client,
    )

    index_view = flask.View(views.index)


ربط البحث



نحن الآن جاهزون للبحث للعمل. دعونا نستخدم SearchServiceفي indexالعرض.



تحرير views.py:



"""Views module."""

from flask import request, render_template

from .services import SearchService


def index(search_service: SearchService):
    query = request.args.get('query', 'Dependency Injector')
    limit = request.args.get('limit', 10, int)

    repositories = search_service.search_repositories(query, limit)

    return render_template(
        'index.html',
        query=query,
        limit=limit,
        repositories=repositories,
    )


لنقم الآن بتغيير الحاوية لتمرير التبعية SearchServiceإلى العرض indexعندما يتم استدعاؤها.



تحرير containers.py:



"""Application containers module."""

from dependency_injector import containers, providers
from dependency_injector.ext import flask
from flask import Flask
from flask_bootstrap import Bootstrap
from github import Github

from . import services, views


class ApplicationContainer(containers.DeclarativeContainer):
    """Application container."""

    app = flask.Application(Flask, __name__)

    bootstrap = flask.Extension(Bootstrap)

    config = providers.Configuration()

    github_client = providers.Factory(
        Github,
        login_or_token=config.github.auth_token,
        timeout=config.github.request_timeout,
    )

    search_service = providers.Factory(
        services.SearchService,
        github_client=github_client,
    )

    index_view = flask.View(
        views.index,
        search_service=search_service,
    )


تأكد من تشغيل التطبيق أو تشغيله flask runوافتح http://127.0.0.1:5000/ .



سوف ترى:







قليلا من إعادة بناء ديون



indexيحتوي العرض الخاص بنا على قيمتين مشفرتين:



  • مصطلح البحث الافتراضي
  • حد لعدد النتائج


لنقم بإعادة بناء ديون صغيرة. سننقل هذه القيم إلى التكوين.



تحرير views.py:



"""Views module."""

from flask import request, render_template

from .services import SearchService


def index(
        search_service: SearchService,
        default_query: str,
        default_limit: int,
):
    query = request.args.get('query', default_query)
    limit = request.args.get('limit', default_limit, int)

    repositories = search_service.search_repositories(query, limit)

    return render_template(
        'index.html',
        query=query,
        limit=limit,
        repositories=repositories,
    )


الآن نحن بحاجة إلى تمرير هذه القيم عند الطلب. لنقم بتحديث الحاوية.



تحرير containers.py:



"""Application containers module."""

from dependency_injector import containers, providers
from dependency_injector.ext import flask
from flask import Flask
from flask_bootstrap import Bootstrap
from github import Github

from . import services, views


class ApplicationContainer(containers.DeclarativeContainer):
    """Application container."""

    app = flask.Application(Flask, __name__)

    bootstrap = flask.Extension(Bootstrap)

    config = providers.Configuration()

    github_client = providers.Factory(
        Github,
        login_or_token=config.github.auth_token,
        timeout=config.github.request_timeout,
    )

    search_service = providers.Factory(
        services.SearchService,
        github_client=github_client,
    )

    index_view = flask.View(
        views.index,
        search_service=search_service,
        default_query=config.search.default_query,
        default_limit=config.search.default_limit,
    )


لنقم الآن بتحديث ملف التكوين.



تحرير config.yml:



github:
  request_timeout: 10
search:
  default_query: "Dependency Injector"
  default_limit: 10


منجز.



تم الانتهاء من إعادة البناء. Mu جعل الكود أنظف.



إضافة الاختبارات



سيكون من الجيد إضافة بعض الاختبارات. دعنا نقوم به.



سنكون باستخدام pytest و التغطية .



تحرير requirements.txt:



dependency-injector
flask
bootstrap-flask
pygithub
pyyaml
pytest-flask
pytest-cov


وتثبيت حزم جديدة:



pip install -r requirements.txt


قم بإنشاء ملف فارغ tests.pyفي الحزمة githubnavigator:



./
├── githubnavigator/
│   ├── templates/
│   │   ├── base.html
│   │   └── index.html
│   ├── __init__.py
│   ├── application.py
│   ├── containers.py
│   ├── services.py
│   ├── tests.py
│   └── views.py
├── venv/
├── config.yml
└── requirements.txt


وأضف إليها الأسطر التالية:



"""Tests module."""

from unittest import mock

import pytest
from github import Github
from flask import url_for

from .application import create_app


@pytest.fixture
def app():
    return create_app()


def test_index(client, app):
    github_client_mock = mock.Mock(spec=Github)
    github_client_mock.search_repositories.return_value = [
        mock.Mock(
            html_url='repo1-url',
            name='repo1-name',
            owner=mock.Mock(
                login='owner1-login',
                html_url='owner1-url',
                avatar_url='owner1-avatar-url',
            ),
            get_commits=mock.Mock(return_value=[mock.Mock()]),
        ),
        mock.Mock(
            html_url='repo2-url',
            name='repo2-name',
            owner=mock.Mock(
                login='owner2-login',
                html_url='owner2-url',
                avatar_url='owner2-avatar-url',
            ),
            get_commits=mock.Mock(return_value=[mock.Mock()]),
        ),
    ]

    with app.container.github_client.override(github_client_mock):
        response = client.get(url_for('index'))

    assert response.status_code == 200
    assert b'Results found: 2' in response.data

    assert b'repo1-url' in response.data
    assert b'repo1-name' in response.data
    assert b'owner1-login' in response.data
    assert b'owner1-url' in response.data
    assert b'owner1-avatar-url' in response.data

    assert b'repo2-url' in response.data
    assert b'repo2-name' in response.data
    assert b'owner2-login' in response.data
    assert b'owner2-url' in response.data
    assert b'owner2-avatar-url' in response.data


def test_index_no_results(client, app):
    github_client_mock = mock.Mock(spec=Github)
    github_client_mock.search_repositories.return_value = []

    with app.container.github_client.override(github_client_mock):
        response = client.get(url_for('index'))

    assert response.status_code == 200
    assert b'Results found: 0' in response.data


لنبدأ الآن في الاختبار والتحقق من التغطية:



py.test githubnavigator/tests.py --cov=githubnavigator


سوف ترى:



platform darwin -- Python 3.8.3, pytest-5.4.3, py-1.9.0, pluggy-0.13.1
plugins: flask-1.0.0, cov-2.10.0
collected 2 items

githubnavigator/tests.py ..                                     [100%]

---------- coverage: platform darwin, python 3.8.3-final-0 -----------
Name                             Stmts   Miss  Cover
----------------------------------------------------
githubnavigator/__init__.py          0      0   100%
githubnavigator/application.py      11      0   100%
githubnavigator/containers.py       13      0   100%
githubnavigator/services.py         14      0   100%
githubnavigator/tests.py            32      0   100%
githubnavigator/views.py             7      0   100%
----------------------------------------------------
TOTAL                               77      0   100%


لاحظ كيف استبدلنا github_clientبـ mock باستخدام الطريقة .override(). بهذه الطريقة ، يمكنك تجاوز القيمة المرجعة لأي مزود.



خاتمة



قمنا ببناء تطبيق Flask الخاص بنا باستخدام حقن التبعية. استخدمنا حاقن التبعية كإطار لحقن التبعية.



الجزء الرئيسي من تطبيقنا هو الحاوية. يحتوي على جميع مكونات التطبيق وتبعياتها في مكان واحد. يوفر هذا التحكم في هيكل التطبيق. من السهل فهمها وتغييرها:



"""Application containers module."""

from dependency_injector import containers, providers
from dependency_injector.ext import flask
from flask import Flask
from flask_bootstrap import Bootstrap
from github import Github

from . import services, views


class ApplicationContainer(containers.DeclarativeContainer):
    """Application container."""

    app = flask.Application(Flask, __name__)

    bootstrap = flask.Extension(Bootstrap)

    config = providers.Configuration()

    github_client = providers.Factory(
        Github,
        login_or_token=config.github.auth_token,
        timeout=config.github.request_timeout,
    )

    search_service = providers.Factory(
        services.SearchService,
        github_client=github_client,
    )

    index_view = flask.View(
        views.index,
        search_service=search_service,
        default_query=config.search.default_query,
        default_limit=config.search.default_limit,
    )




الحاوية كخريطة لتطبيقك. أنت تعرف دائمًا ما يعتمد على ما.



ماذا بعد؟






All Articles