# Большие приложения — несколько файлов { #bigger-applications-multiple-files }

При построении приложения или веб-API нам редко удается поместить всё в один файл.

**FastAPI** предоставляет удобный инструментарий, который позволяет нам структурировать приложение, сохраняя при этом всю необходимую гибкость.

/// info | Примечание

Если вы раньше использовали Flask, то это аналог шаблонов Flask (Flask's Blueprints).

///

## Пример структуры приложения { #an-example-file-structure }

Давайте предположим, что наше приложение имеет следующую структуру:

```
.
├── app
│   ├── __init__.py
│   ├── main.py
│   ├── dependencies.py
│   └── routers
│   │   ├── __init__.py
│   │   ├── items.py
│   │   └── users.py
│   └── internal
│       ├── __init__.py
│       └── admin.py
```

/// tip | Подсказка

Есть несколько файлов `__init__.py`: по одному в каждом каталоге или подкаталоге.

Это как раз то, что позволяет импортировать код из одного файла в другой.

Например, в файле `app/main.py` может быть следующая строка:

```
from app.routers import items
```

///

* Всё помещается в каталоге `app`. В нём также находится пустой файл `app/__init__.py`. Таким образом, `app` является "Python-пакетом" (коллекцией "Python-модулей"): `app`.
* Он содержит файл `app/main.py`. Данный файл является частью Python-пакета (т.е. находится внутри каталога, содержащего файл `__init__.py`), и, соответственно, он является модулем этого пакета: `app.main`.
* Он также содержит файл `app/dependencies.py`, который также, как и `app/main.py`, является модулем: `app.dependencies`.
* Здесь также находится подкаталог `app/routers/`, содержащий `__init__.py`. Он является Python-подпакетом: `app.routers`.
* Файл `app/routers/items.py` находится внутри пакета `app/routers/`. Таким образом, он является подмодулем: `app.routers.items`.
* Точно так же `app/routers/users.py` является ещё одним подмодулем: `app.routers.users`.
* Подкаталог `app/internal/`, содержащий файл `__init__.py`, является ещё одним Python-подпакетом: `app.internal`.
* А файл `app/internal/admin.py` является ещё одним подмодулем: `app.internal.admin`.

<img src="/img/tutorial/bigger-applications/package.drawio.svg">

Та же самая файловая структура приложения, но с комментариями:

```bash
.
├── app                  # "app" пакет
│   ├── __init__.py      # этот файл превращает "app" в "Python-пакет"
│   ├── main.py          # модуль "main", напр.: import app.main
│   ├── dependencies.py  # модуль "dependencies", напр.: import app.dependencies
│   └── routers          # подпакет "routers"
│   │   ├── __init__.py  # превращает "routers" в подпакет
│   │   ├── items.py     # подмодуль "items", напр.: import app.routers.items
│   │   └── users.py     # подмодуль "users", напр.: import app.routers.users
│   └── internal         # подпакет "internal"
│       ├── __init__.py  # превращает "internal" в подпакет
│       └── admin.py     # подмодуль "admin", напр.: import app.internal.admin
```

## `APIRouter` { #apirouter }

Давайте предположим, что для работы с пользователями используется отдельный файл (подмодуль) `/app/routers/users.py`.

Вы хотите отделить *операции пути*, связанные с пользователями, от остального кода, чтобы сохранить порядок.

Но это всё равно часть того же приложения/веб-API на **FastAPI** (часть того же «Python-пакета»).

С помощью `APIRouter` вы можете создать *операции пути* для этого модуля.

### Импорт `APIRouter` { #import-apirouter }

Точно так же, как и в случае с классом `FastAPI`, вам нужно импортировать и создать его «экземпляр»:

{* ../../docs_src/bigger_applications/app_an_py310/routers/users.py hl[1,3] title["app/routers/users.py"] *}

### *Операции пути* с `APIRouter` { #path-operations-with-apirouter }

И затем вы используете его, чтобы объявить ваши *операции пути*.

Используйте его так же, как вы использовали бы класс `FastAPI`:

{* ../../docs_src/bigger_applications/app_an_py310/routers/users.py hl[6,11,16] title["app/routers/users.py"] *}

Вы можете думать об `APIRouter` как об «мини-классе `FastAPI`».

Поддерживаются все те же опции.

Все те же `parameters`, `responses`, `dependencies`, `tags` и т.д.

/// tip | Подсказка

В данном примере, в качестве названия переменной используется `router`, но вы можете использовать любое другое имя.

///

Мы собираемся подключить данный `APIRouter` к нашему основному приложению на `FastAPI`, но сначала давайте проверим зависимости и ещё один `APIRouter`.

## Зависимости { #dependencies }

Мы видим, что нам понадобятся некоторые зависимости, которые будут использоваться в нескольких местах приложения.

Поэтому мы поместим их в отдельный модуль `dependencies` (`app/dependencies.py`).

Теперь мы воспользуемся простой зависимостью, чтобы прочитать кастомный HTTP-заголовок `X-Token`:

{* ../../docs_src/bigger_applications/app_an_py310/dependencies.py hl[3,6:8] title["app/dependencies.py"] *}

/// tip | Подсказка

Для простоты мы воспользовались выдуманным заголовком.

В реальных случаях для получения наилучших результатов используйте интегрированные [утилиты безопасности](security/index.md).

///

## Ещё один модуль с `APIRouter` { #another-module-with-apirouter }

Давайте также предположим, что у вас есть эндпоинты, отвечающие за обработку «items» в вашем приложении, и они находятся в модуле `app/routers/items.py`.

У вас определены *операции пути* для:

* `/items/`
* `/items/{item_id}`

Тут всё та же структура, как и в случае с `app/routers/users.py`.

Но мы хотим поступить умнее и слегка упростить код.

Мы знаем, что все *операции пути* этого модуля имеют одинаковые:

* `prefix` пути: `/items`.
* `tags`: (один единственный тег: `items`).
* Дополнительные `responses`.
* `dependencies`: всем им нужна та зависимость `X-Token`, которую мы создали.

Таким образом, вместо того чтобы добавлять всё это в каждую *операцию пути*, мы можем добавить это в `APIRouter`.

{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *}

Так как путь каждой *операции пути* должен начинаться с `/`, как здесь:

```Python hl_lines="1"
@router.get("/{item_id}")
async def read_item(item_id: str):
    ...
```

...то префикс не должен заканчиваться символом `/`.

В нашем случае префиксом является `/items`.

Мы также можем добавить список `tags` и дополнительные `responses`, которые будут применяться ко всем *операциям пути*, включённым в этот маршрутизатор.

И ещё мы можем добавить список `dependencies`, которые будут добавлены ко всем *операциям пути* в маршрутизаторе и будут выполняться/разрешаться для каждого HTTP-запроса к ним.

/// tip | Подсказка

Обратите внимание, что так же, как и в случае с [зависимостями в декораторах *операций пути*](dependencies/dependencies-in-path-operation-decorators.md), никакое значение не будет передано в вашу *функцию-обработчик пути*.

///

В результате пути для items теперь такие:

* `/items/`
* `/items/{item_id}`

...как мы и планировали.

* Они будут помечены списком тегов, содержащим одну строку `"items"`.
    * Эти «теги» особенно полезны для систем автоматической интерактивной документации (с использованием OpenAPI).
* Все они будут включать предопределённые `responses`.
* Все эти *операции пути* будут иметь список `dependencies`, вычисляемых/выполняемых перед ними.
    * Если вы также объявите зависимости в конкретной *операции пути*, **они тоже будут выполнены**.
    * Сначала выполняются зависимости маршрутизатора, затем [`dependencies` в декораторе](dependencies/dependencies-in-path-operation-decorators.md), и затем обычные параметрические зависимости.
    * Вы также можете добавить [`Security`-зависимости с `scopes`](../advanced/security/oauth2-scopes.md).

/// tip | Подсказка

Например, с помощью зависимостей в `APIRouter` мы можем потребовать аутентификации для доступа ко всей группе *операций пути*. Даже если зависимости не добавляются по отдельности к каждой из них.

///

/// check | Заметка

Параметры `prefix`, `tags`, `responses` и `dependencies` — это (как и во многих других случаях) просто возможность **FastAPI**, помогающая избежать дублирования кода.

///

### Импорт зависимостей { #import-the-dependencies }

Этот код находится в модуле `app.routers.items`, в файле `app/routers/items.py`.

И нам нужно получить функцию зависимости из модуля `app.dependencies`, файла `app/dependencies.py`.

Поэтому мы используем относительный импорт с `..` для зависимостей:

{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[3] title["app/routers/items.py"] *}

#### Как работает относительный импорт { #how-relative-imports-work }

/// tip | Подсказка

Если вы прекрасно знаете, как работает импорт, переходите к следующему разделу ниже.

///

Одна точка `.`, как здесь:

```Python
from .dependencies import get_token_header
```

означает:

* Начать в том же пакете, в котором находится этот модуль (файл `app/routers/items.py`) (каталог `app/routers/`)...
* найти модуль `dependencies` (воображаемый файл `app/routers/dependencies.py`)...
* и импортировать из него функцию `get_token_header`.

Но такого файла не существует, наши зависимости находятся в файле `app/dependencies.py`.

Вспомните, как выглядит файловая структура нашего приложения:

<img src="/img/tutorial/bigger-applications/package.drawio.svg">

---

Две точки `..`, как здесь:

```Python
from ..dependencies import get_token_header
```

означают:

* Начать в том же пакете, в котором находится этот модуль (файл `app/routers/items.py`) (каталог `app/routers/`)...
* перейти в родительский пакет (каталог `app/`)...
* и там найти модуль `dependencies` (файл `app/dependencies.py`)...
* и импортировать из него функцию `get_token_header`.

Это работает корректно! 🎉

---

Аналогично, если бы мы использовали три точки `...`, как здесь:

```Python
from ...dependencies import get_token_header
```

то это бы означало:

* Начать в том же пакете, в котором находится этот модуль (файл `app/routers/items.py`) расположен в (каталоге `app/routers/`)...
* перейти в родительский пакет (каталог `app/`)...
* затем перейти в родительский пакет этого пакета (родительского пакета нет, `app` — верхний уровень 😱)...
* и там найти модуль `dependencies` (файл `app/dependencies.py`)...
* и импортировать из него функцию `get_token_header`.

Это ссылалось бы на какой-то пакет выше `app/`, со своим файлом `__init__.py` и т.п. Но у нас такого нет. Поэтому это вызвало бы ошибку в нашем примере. 🚨

Но теперь вы знаете, как это работает, так что можете использовать относительные импорты в своих приложениях, независимо от того, насколько они сложные. 🤓

### Добавление пользовательских `tags`, `responses` и `dependencies` { #add-some-custom-tags-responses-and-dependencies }

Мы не добавляем префикс `/items` и `tags=["items"]` к каждой *операции пути*, потому что мы добавили их в `APIRouter`.

Но мы всё равно можем добавить _ещё_ `tags`, которые будут применяться к конкретной *операции пути*, а также дополнительные `responses`, специфичные для этой *операции пути*:

{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[30:31] title["app/routers/items.py"] *}

/// tip | Подсказка

Эта последняя операция пути будет иметь комбинацию тегов: `["items", "custom"]`.

И в документации у неё будут оба ответа: один для `404` и один для `403`.

///

## Модуль main в `FastAPI` { #the-main-fastapi }

Теперь давайте посмотрим на модуль `app/main.py`.

Именно сюда вы импортируете и именно здесь вы используете класс `FastAPI`.

Это основной файл вашего приложения, который связывает всё воедино.

И так как большая часть вашей логики теперь будет находиться в отдельных специфичных модулях, основной файл будет довольно простым.

### Импорт `FastAPI` { #import-fastapi }

Вы импортируете и создаёте класс `FastAPI` как обычно.

И мы даже можем объявить [глобальные зависимости](dependencies/global-dependencies.md), которые будут объединены с зависимостями для каждого `APIRouter`:

{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[1,3,7] title["app/main.py"] *}

### Импорт `APIRouter` { #import-the-apirouter }

Теперь мы импортируем другие подмодули, содержащие `APIRouter`:

{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[4:5] title["app/main.py"] *}

Так как файлы `app/routers/users.py` и `app/routers/items.py` являются подмодулями, входящими в один и тот же Python-пакет `app`, мы можем использовать одну точку `.` для импорта через «относительные импорты».

### Как работает импорт { #how-the-importing-works }

Этот фрагмент:

```Python
from .routers import items, users
```

означает:

* Начать в том же пакете, в котором находится этот модуль (файл `app/main.py`) расположен в (каталоге `app/`)...
* найти подпакет `routers` (каталог `app/routers/`)...
* и импортировать из него подмодули `items` (файл `app/routers/items.py`) и `users` (файл `app/routers/users.py`)...

В модуле `items` будет переменная `router` (`items.router`). Это та же самая, которую мы создали в файле `app/routers/items.py`, это объект `APIRouter`.

И затем мы делаем то же самое для модуля `users`.

Мы также могли бы импортировать их так:

```Python
from app.routers import items, users
```

/// info | Примечание

Первая версия — это «относительный импорт»:

```Python
from .routers import items, users
```

Вторая версия — это «абсолютный импорт»:

```Python
from app.routers import items, users
```

Чтобы узнать больше о Python-пакетах и модулях, прочитайте [официальную документацию Python о модулях](https://docs.python.org/3/tutorial/modules.html).

///

### Избегайте конфликтов имён { #avoid-name-collisions }

Мы импортируем подмодуль `items` напрямую, вместо того чтобы импортировать только его переменную `router`.

Это потому, что у нас также есть другая переменная с именем `router` в подмодуле `users`.

Если бы мы импортировали их одну за другой, как здесь:

```Python
from .routers.items import router
from .routers.users import router
```

то `router` из `users` перезаписал бы `router` из `items`, и мы не смогли бы использовать их одновременно.

Поэтому, чтобы иметь возможность использовать обе в одном файле, мы импортируем подмодули напрямую:

{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[5] title["app/main.py"] *}

### Подключение `APIRouter` для `users` и `items` { #include-the-apirouters-for-users-and-items }

Теперь давайте подключим `router` из подмодулей `users` и `items`:

{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[10:11] title["app/main.py"] *}

/// info | Примечание

`users.router` содержит `APIRouter` из файла `app/routers/users.py`.

А `items.router` содержит `APIRouter` из файла `app/routers/items.py`.

///

С помощью `app.include_router()` мы можем добавить каждый `APIRouter` в основное приложение `FastAPI`.

Он включит все маршруты этого маршрутизатора как часть приложения.

/// note | Технические детали

Фактически, внутри он создаст *операцию пути* для каждой *операции пути*, объявленной в `APIRouter`.

Так что под капотом всё будет работать так, как будто всё было одним приложением.

///

/// check | Заметка

При подключении маршрутизаторов не нужно беспокоиться о производительности.

Это займёт микросекунды и произойдёт только при старте.

Так что это не повлияет на производительность. ⚡

///

### Подключение `APIRouter` с пользовательскими `prefix`, `tags`, `responses` и `dependencies` { #include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies }

Теперь давайте представим, что ваша организация передала вам файл `app/internal/admin.py`.

Он содержит `APIRouter` с некоторыми административными *операциями пути*, которые ваша организация использует в нескольких проектах.

Для этого примера всё будет очень просто. Но допустим, что поскольку он используется совместно с другими проектами в организации, мы не можем модифицировать его и добавить `prefix`, `dependencies`, `tags` и т.д. непосредственно в `APIRouter`:

{* ../../docs_src/bigger_applications/app_an_py310/internal/admin.py hl[3] title["app/internal/admin.py"] *}

Но мы всё равно хотим задать пользовательский `prefix` при подключении `APIRouter`, чтобы все его *операции пути* начинались с `/admin`, хотим защитить его с помощью `dependencies`, которые у нас уже есть для этого проекта, и хотим включить `tags` и `responses`.

Мы можем объявить всё это, не изменяя исходный `APIRouter`, передав эти параметры в `app.include_router()`:

{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[14:17] title["app/main.py"] *}

Таким образом исходный `APIRouter` не будет модифицирован, и мы сможем использовать файл `app/internal/admin.py` сразу в нескольких проектах организации.

В результате в нашем приложении каждая из *операций пути* из модуля `admin` будет иметь:

* Префикс `/admin`.
* Тег `admin`.
* Зависимость `get_token_header`.
* Ответ `418`. 🍵

Но это повлияет только на этот `APIRouter` в нашем приложении, а не на любой другой код, который его использует.

Так что, например, другие проекты могут использовать тот же `APIRouter` с другим методом аутентификации.

### Подключение *операции пути* { #include-a-path-operation }

Мы также можем добавлять *операции пути* напрямую в приложение `FastAPI`.

Здесь мы делаем это... просто чтобы показать, что можем 🤷:

{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[21:23] title["app/main.py"] *}

и это будет работать корректно вместе со всеми другими *операциями пути*, добавленными через `app.include_router()`.

/// info | Очень технические детали

**Примечание**: это очень техническая деталь, которую, вероятно, можно **просто пропустить**.

---

`APIRouter` не «монтируются», они не изолированы от остального приложения.

Это потому, что мы хотим включить их *операции пути* в OpenAPI-схему и пользовательские интерфейсы.

Так как мы не можем просто изолировать их и «смонтировать» независимо от остального, *операции пути* «клонируются» (пересоздаются), а не включаются напрямую.

///

## Настройка `entrypoint` в `pyproject.toml` { #configure-the-entrypoint-in-pyproject-toml }

Так как ваш объект FastAPI `app` находится в `app/main.py`, вы можете настроить `entrypoint` в файле `pyproject.toml` следующим образом:

```toml
[tool.fastapi]
entrypoint = "app.main:app"
```

это эквивалентно импорту:

```python
from app.main import app
```

Таким образом, команда `fastapi` будет знать, где найти ваше приложение.

/// Note | Примечание

Вы также можете передать путь в команду, например:

```console
$ fastapi dev app/main.py
```

Но вам придётся каждый раз помнить и указывать корректный путь при вызове команды `fastapi`.

Кроме того, другие инструменты могут не суметь его найти, например [расширение VS Code](../editor-support.md) или [FastAPI Cloud](https://fastapicloud.com), поэтому рекомендуется использовать `entrypoint` в `pyproject.toml`.

///

## Проверка автоматической документации API { #check-the-automatic-api-docs }

Теперь запустите приложение:

<div class="termy">

```console
$ fastapi dev

<span style="color: green;">INFO</span>:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```

</div>

Откройте документацию по адресу [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs).

Вы увидите автоматическую документацию API, включая пути из всех подмодулей, с использованием корректных путей (и префиксов) и корректных тегов:

<img src="/img/tutorial/bigger-applications/image01.png">

## Подключение одного и того же маршрутизатора несколько раз с разными `prefix` { #include-the-same-router-multiple-times-with-different-prefix }

Вы можете использовать `.include_router()` несколько раз с *одним и тем же* маршрутизатором, используя разные префиксы.

Это может быть полезно, например, чтобы предоставить доступ к одному и тому же API с разными префиксами, например `/api/v1` и `/api/latest`.

Это продвинутое использование, которое вам может и не понадобиться, но оно есть на случай, если понадобится.

## Подключение `APIRouter` в другой `APIRouter` { #include-an-apirouter-in-another }

Точно так же, как вы можете подключить `APIRouter` к приложению `FastAPI`, вы можете подключить `APIRouter` к другому `APIRouter`, используя:

```Python
router.include_router(other_router)
```

Убедитесь, что вы сделали это до подключения `router` к приложению `FastAPI`, чтобы *операции пути* из `other_router` также были подключены.
