$ sudo teach|
    $ sudo teach|
    IT school
  • Telegram
  • Партнёрам
  • Все курсы
$ sudo teach IT
OOO "SALEPROFIT"Контакты и реквизитыIT-Park Logo

Школа

  • Блог
  • Проверить сертификат

Сотрудничество

  • Стать учителем
  • Партнёрская программа
  • О проекте

Право

  • Оферта
  • Политика конфиденциальности

© 2023–2026 $ sudo teach IT™. All Rights Reserved. Public user contributions licensed under CC BY-SA 4.0 license with attribution required
TelegramGitHubYouTube
ГлавнаяБлогКак выбрать структуру проекта на Python: src-layout, нейминг модулей и где хранить конфиги

Как выбрать структуру проекта на Python: src-layout, нейминг модулей и где хранить конфиги

$ sudo teach IT
·7 августа 2026 г.·11 мин·109
Как выбрать структуру проекта на Python: src-layout, нейминг модулей и где хранить конфиги

Разберём рабочие схемы организации репозитория (src-layout vs flat), правила именования, расположение настроек/секретов и типовые ошибки, которые мешают тестированию и сборке. Поймёте, как подготовить проект под рост и CI.

Содержание
Почему структура репозитория важнее, чем кажетсяВыбор схемы: src-layout vs flatFlat layout: когда код лежит прямо в корнеsrc-layout: предсказуемость через установку пакетаКак выглядит правильный pyproject.toml для src-layoutКогда всё-таки может понадобиться flat layoutНейминг модулей и пакетов: правила, которые экономят времяИменование пакетов: строчные, без дефисов, единый стильИменование модулей: по ответственности, а не по формату файлаСтруктура подпакетов: domain, adapters, services — но с дисциплинойinit.py: минимализм и осмысленные экспортыГде хранить конфиги: от простого до воспроизводимого и безопасногоТри разные категории: default configs, env configs, secretsБазовый вариант: конфиги в репозитории, секреты — снаружиРекомендованный формат: TOML/YAML + явный loaderГде держать конфиги в дереве проекта: config/ vs src/.../configСекреты: только не в репозитории (и не в тестовых фикстурах “по умолчанию”)Циклические импорты, “магия” и почему CI может ломатьсяОшибка №1: импорты завязаны на рабочий каталогОшибка №2: конфигурация подхватывается слишком раноОшибка №3: тесты зависят от реальных файлов конфиговОшибка №4: нейминг, который конфликтуетПодготовка к росту и CI: что проверить до того, как “полетит”1) Пакет импортируется после установки2) Тесты не требуют секретов “на уровне импорта”3) Конфиги можно переопределить для тестов4) Не полагайтесь на текущую директорию (cwd)5) Структура соответствует сборкеПрактический пример “зрелого” дерева проектаПодводные камни: типовые решения, которые потом приходится переделывать“Сейчас положим всё в utils.py”“Конфиги прямо рядом с кодом”“Секреты в .env и всё локально”Вывод: выбирать структуру под предсказуемость, а не под удобство “сейчас”

Хорошая структура репозитория на Python редко бывает «видна глазом» в первых коммитах. Она проявляется позже: когда проект начинает расти, подключаются тесты, появляется CI, и внезапно выясняется, что код импортируется не так, как вы ожидали; пакеты «текут» из рабочего каталога; конфиги разъезжаются между окружениями; секреты попадают в репозиторий или, наоборот, настолько запутываются, что тесты перестают быть воспроизводимыми.

Ниже разберём практические схемы организации репозитория (включая src-layout и более «плоскую» структуру), правила именования модулей, а также рабочие способы хранения конфигураций и секретов. В конце — список проверок, который помогает подготовить проект к росту и нормальному CI.


Почему структура репозитория важнее, чем кажется

На Python многое решают импорты. В отличие от языков с жёсткой компоновкой (где проектная структура «зацементирована» билд-системой), в Python модуль можно импортировать из разных мест — по правилам sys.path, текущего рабочего каталога, окружения и того, как установлен пакет.

В начальной стадии это обычно не проблема: вы запускаете код из корня репозитория, всё «магически находится», тесты пишутся быстро, конфиги — прямо в файлах рядом со скриптами. Но как только:

  • вы добавляете pip install -e . или сборку в wheel,
  • запускаете тесты в CI из другого каталога,
  • включаете параллельный запуск,
  • добавляете линтеры и type-check,
  • делаете несколько пакетов в одном репозитории,

— начинают всплывать накопленные компромиссы.

Хорошая структура решает сразу три задачи:

  1. Предсказуемые импорты при любом способе запуска (локально, в CI, после установки).
  2. Разделение бизнес-кода и окружения (конфиги и секреты не должны мешать тестам и сборке).
  3. Управляемость роста: новые модули и настройки появляются в понятных местах.

Выбор схемы: src-layout vs flat

Flat layout: когда код лежит прямо в корне

Типичный «плоский» вариант выглядит так:

code
my_project/
  app/
    __init__.py
    main.py
  tests/
    test_main.py
  pyproject.toml

Или даже так:

code
my_project/
  src/
    ??? (нет)
  app.py
  helpers.py
  tests/

Проблема flat layout в импортах: если вы запускаете тесты из корня репозитория, Python может подхватить модули из рабочего каталога, даже если пакет «не установлен» или установлен неправильно. Это приводит к ложноположительным результатам: тесты проходят локально, а в CI ломаются (или наоборот).

Классический симптом: вы замечаете, что import app работает «само собой» и в CI, но в зависимости от настройки PYTHONPATH или команд запуска результат может меняться.

Flat layout допустим для маленьких скриптов и одноразовых утилит. Для библиотек, CLI и проектов, которые будут расти, обычно безопаснее src-layout.


src-layout: предсказуемость через установку пакета

src-layout отделяет исходники от корня репозитория:

code
my_project/
  pyproject.toml
  src/
    my_project/
      __init__.py
      cli.py
      core/
        __init__.py
        service.py
  tests/
    test_service.py

Здесь ключевой момент: пакет my_project лежит внутри src/. Это означает, что импорты, которые видит Python, завязаны на корректную установку/разрешение пакета (через настройки pyproject.toml и pip install -e .), а не на «случайное» наличие директорий в текущем рабочем каталоге.

При правильной настройке это даёт:

  • более надёжное тестирование;
  • корректную работу инструментов (pytest, mypy, ruff и т.д.);
  • предсказуемое поведение после сборки wheel.

Как выглядит правильный pyproject.toml для src-layout

Пример (минимально-репрезентативный) для пакета в src/:

code
[build-system]
requires = ["setuptools>=68", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "my-project"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = []

[tool.setuptools]
package-dir = {"" = "src"}

[tool.setuptools.packages.find]
where = ["src"]

Если вы используете hatchling или poetry, подход аналогичный: важно, чтобы билд-система знала, где исходники пакета. При src-layout это решает проблему импорта «из воздуха».


Когда всё-таки может понадобиться flat layout

Иногда flat layout оправдан:

  • репозиторий — набор утилит без единого пакета (каждая утилита автономная);
  • вы используете только один entrypoint-скрипт и импортируете из него минимум модулей;
  • проект не планирует публикацию пакетов и сложную интеграцию.

Но как только появляются тесты и CI, стоимость компромисса растёт. Практика показывает: даже если вы не планируете публиковать библиотеку, src-layout снижает риски и ускоряет дальнейшую разработку.


Нейминг модулей и пакетов: правила, которые экономят время

Именование пакетов: строчные, без дефисов, единый стиль

Python-пакеты и модули принято именовать snake_case и строчно: my_project, data_loader, http_client.

Избегайте:

  • MyProject (это будет восприниматься как класс/тип, создаст путаницу);
  • смешение стилей (myProject, my-project);
  • пробелы и дефисы в именах модулей (дефисы не являются валидными идентификаторами Python).

Также избегайте «пакетного» совпадения с именами стандартной библиотеки: модуль email.py, json.py, typing.py — частая причина странных ошибок и теневых импортов.


Именование модулей: по ответственности, а не по формату файла

Вместо:

  • utils.py, helpers.py (которые со временем превращаются в «корзину» всего подряд), лучше выделять ответственность:
  • logging.py, config_loader.py, date_time.py, http_client.py;
  • validators.py для валидаций домена;
  • transformers.py для преобразований данных.

Если модуль растёт, он должен разбиваться по функциональным границам, а не по «случайным» добавлениям.


Структура подпакетов: domain, adapters, services — но с дисциплиной

Частый вопрос: как разложить папки внутри src/my_project/?

Один из рабочих подходов — разделить код на «слои» (не строго, но концептуально):

  • core/ (бизнес-логика, доменные сущности, правила);
  • adapters/ (интеграции: БД, HTTP, файловая система);
  • services/ (оркестрация use-case’ов, если она отличается от домена);
  • api/ или cli/ (входные точки: интерфейс команд/HTTP).

Например:

code
src/
  my_project/
    core/
      __init__.py
      domain.py
      rules.py
    adapters/
      __init__.py
      db.py
      http.py
    api/
      __init__.py
      cli.py
    main.py

Ключ: не превращайте эти слова в декоративные папки. Если вы назвали adapters/db.py — там должен быть код конкретного адаптера, а не абстрактный слой без конкретики.


__init__.py: минимализм и осмысленные экспорты

Внутри пакетов нужен __init__.py, чтобы Python считал их пакетами (для «старых» контекстов) и чтобы вы могли управлять экспортом.

Но не делайте from .everything import * в каждом __init__.py. Это усложняет статический анализ и увеличивает шансы на циклические импорты.

Лучше:

  • оставлять пустым или с небольшой конфигурацией;
  • явно экспортировать ключевые объекты, если это действительно нужно.

Где хранить конфиги: от простого до воспроизводимого и безопасного

Три разные категории: default configs, env configs, secrets

Конфигурация — не один файл «на все случаи». Обычно проект имеет как минимум три типа параметров:

  1. Default config — базовые значения, которые не являются секретами и подходят «из коробки».
  2. Environment config — настройки под окружение: dev, staging, prod (часто тоже не секреты: URL сервера, уровень логирования).
  3. Secrets — токены, пароли, ключи, которые не должны попадать в репозиторий.

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


Базовый вариант: конфиги в репозитории, секреты — снаружи

Распространённая структура:

code
my_project/
  src/
    my_project/
      ...
  config/
    default.toml
    logging.toml
  pyproject.toml
  .env.example
  .gitignore

Дальше: в default.toml хранятся не секреты. Секреты задаются через переменные окружения или секрет-хранилища CI.

Файл .env.example полезен как документация:

code
APP_ENV=dev
DATABASE_URL=postgresql://user:password@localhost:5432/db
SENTRY_DSN=

Но важно: .env.example — не содержит настоящих секретов.


Рекомендованный формат: TOML/YAML + явный loader

Выбор формата часто спорный, но важно не «какой формат модный», а то, чтобы:

  • парсинг был детерминированным;
  • была внятная схема значений;
  • loader возвращал структурированные данные.

Пример: loader из TOML и переопределение через переменные окружения.

src/my_project/config_loader.py:

code
from __future__ import annotations

import os
from dataclasses import dataclass
from pathlib import Path

import tomllib  # Python 3.11+. Для 3.10 используйте tomli


@dataclass(frozen=True)
class Settings:
    app_env: str
    log_level: str
    database_url: str


def load_settings(*, config_path: str | os.PathLike | None = None) -> Settings:
    base_dir = Path(__file__).resolve().parents[2]  # .../src/my_project -> repo root
    cfg_path = Path(config_path) if config_path else base_dir / "config" / "default.toml"

    data: dict = {}
    if cfg_path.exists():
        data = tomllib.loads(cfg_path.read_text(encoding="utf-8"))

    app_env = os.getenv("APP_ENV", data.get("app_env", "dev"))
    log_level = os.getenv("LOG_LEVEL", data.get("log_level", "INFO"))
    database_url = os.getenv("DATABASE_URL", data.get("database_url", ""))

    if not database_url:
        # Для библиотеки лучше кидать понятное исключение, чтобы раннее падение было полезным.
        raise RuntimeError("DATABASE_URL is not set (env var or config).")

    return Settings(app_env=app_env, log_level=log_level, database_url=database_url)

config/default.toml:

code
app_env = "dev"
log_level = "INFO"
database_url = ""

Да, database_url пустой — намеренно. Мы вынуждаем явно задавать секрет/ресурс через окружение. Для небольших проектов это помогает не «случайно» забыть пароль в репозитории.


Где держать конфиги в дереве проекта: config/ vs src/.../config

Частая ошибка — положить конфиги внутрь src/ как обычные файлы. Это работает до тех пор, пока вы не начинаете собирать package и терять доступ к этим файлам в установке (например, внутри wheel).

Если конфиг — это часть runtime (который нужен в установленном пакете), используйте ресурсы пакета (package resources) или включайте файлы в wheel через настройки сборки.

Если конфиг — это окруженческая «документация» для разработчика, держите его в корне (config/, examples/, settings/). Тесты и dev-скрипты смогут ссылаться на него напрямую, а сборка будет проще.

Практическое правило:

  • конфиги, которые меняются от окружения → хранить снаружи src/ и подхватывать loader’ом;
  • конфиги, которые должны быть в установленном пакете (редкие случаи) → использовать механизм ресурсов пакета.

Секреты: только не в репозитории (и не в тестовых фикстурах “по умолчанию”)

Секреты нужно задавать вне кода: переменные окружения, vault, секрет-хранилища CI/CD.

Минимум:

  • добавить .env в .gitignore;
  • использовать .env.example как шаблон.

Пример .gitignore:

code
.env
*.env

Ещё одна типичная ошибка — падать на отсутствии секретов в момент импорта модуля. Это ломает тесты и инструменты, которые импортируют модуль ради определения функций/классов.

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


Циклические импорты, “магия” и почему CI может ломаться

Ошибка №1: импорты завязаны на рабочий каталог

Если структура flat, или если вы добавляете sys.path руками, тесты могут пройти локально, но сломаться в CI.

В src-layout проблема снижается, потому что импорты должны работать после корректной установки пакета. Это ещё один аргумент выбрать src-layout.


Ошибка №2: конфигурация подхватывается слишком рано

Проверка DATABASE_URL в момент импорта приведёт к ситуации:

  • тест импортирует модуль my_project.core.*;
  • модуль импортирует Settings и сразу требует DATABASE_URL;
  • тесты не могут собраться/запуститься, даже если они unit-тесты и не должны ходить в БД.

Решение:

  • вызывать load_settings() только там, где реально нужно;
  • для тестов — передавать настройки или использовать фикстуры.

Ошибка №3: тесты зависят от реальных файлов конфигов

Если тесты ожидают config/default.toml, а CI не копирует эти файлы — вы получите “File not found”.

Лучше:

  • либо хранить тестовые конфиги в репозитории;
  • либо генерировать конфиги на лету (временные файлы) и передавать в loader;
  • либо в тестах переопределять настройки через окружение.

Пример: тест с временным конфигом.

tests/test_config_loader.py:

code
from __future__ import annotations

from pathlib import Path

import pytest

from my_project.config_loader import load_settings


def test_load_settings_from_custom_path(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    cfg = tmp_path / "default.toml"
    cfg.write_text(
        'app_env = "test"\nlog_level = "DEBUG"\ndatabase_url = "postgresql://test"\n',
        encoding="utf-8",
    )

    # Секрет не должен браться из env, пусть возьмётся из конфига:
    monkeypatch.delenv("DATABASE_URL", raising=False)

    settings = load_settings(config_path=cfg)
    assert settings.app_env == "test"
    assert settings.log_level == "DEBUG"
    assert settings.database_url == "postgresql://test"

Ошибка №4: нейминг, который конфликтует

Модуль/пакет с именем config.py, tests.py, typing.py иногда начинает конфликтовать с импортами и инструментами.

Правило простое: делайте имена осмысленными и специфичными: config_loader.py, test_utils.py, typing_helpers.py.


Подготовка к росту и CI: что проверить до того, как “полетит”

Ниже — практический чеклист, который стоит прогнать перед добавлением сложной инфраструктуры.

1) Пакет импортируется после установки

Идея: тесты и CLI должны работать одинаково после pip install -e . и при запуске из репозитория.

Проверьте локально:

code
python -m pip install -e .
python -c "import my_project; print(my_project.__file__)"

Если импорт “попадает” не туда — вы не привязаны к реальному пакету.


2) Тесты не требуют секретов “на уровне импорта”

Запустите:

code
pytest -q

в среде, где переменные окружения отсутствуют (или хотя бы частично отсутствуют). Если тесты падают ещё до выполнения — значит, где-то идёт ранняя загрузка настроек.


3) Конфиги можно переопределить для тестов

Хорошая минимальная архитектура:

  • load_settings(config_path=...);
  • или отдельный слой создания settings для тестов.

4) Не полагайтесь на текущую директорию (cwd)

В Python часть проблем появляется из-за обращения к файлам по относительным путям: open("config/default.toml").

Вместо этого используйте Path(__file__) и расчёт корня репозитория (как в примере loader’а). Либо передавайте пути явно из entrypoint.


5) Структура соответствует сборке

Если вы используете src-layout, убедитесь, что билд-система корректно находит пакеты. Иначе сборка wheel может не включить то, что вы думаете, а CI при установке будет другой версией кода.


Практический пример “зрелого” дерева проекта

Один из рабочих вариантов структуры для Python-приложения/CLI:

code
my_project/
  pyproject.toml
  README.md
  src/
    my_project/
      __init__.py
      cli.py
      config_loader.py
      core/
        __init__.py
        service.py
      adapters/
        __init__.py
        db.py
  tests/
    __init__.py
    test_service.py
    test_config_loader.py
  config/
    default.toml
  .env.example
  .gitignore

Ключевые свойства:

  • код живёт в src/, импорты стабильны;
  • конфиги в config/ с понятной структурой;
  • секреты задаются через env;
  • тесты могут подменять конфиги через временные пути/монкипатчинг.

Подводные камни: типовые решения, которые потом приходится переделывать

“Сейчас положим всё в utils.py”

Потом это превращается в пакет из взаимозависимых функций, которые тянут конфиги, БД, внешние сервисы и усложняют тестирование. Разделяйте по ответственности сразу — это дешевле, чем разбирать монолит позже.


“Конфиги прямо рядом с кодом”

Если положить config/default.toml в src/my_project/config/default.toml, вы столкнётесь с вопросом: а как вы доставите этот файл при установке? Либо используете package resources, либо держите конфиги в корне репозитория и грузите относительными путями от корня — но тогда нужна согласованность с тем, как вы запускаете проект.


“Секреты в .env и всё локально”

Это работает, пока кто-то случайно не закоммитит .env. Делайте безопасные дефолты: в .env.example — пустые/заглушки, реальные секреты — в CI/локальной настройке.


Вывод: выбирать структуру под предсказуемость, а не под удобство “сейчас”

src-layout обычно даёт лучшую предсказуемость импортов и снижает риск «локально работает, в CI нет». Нейминг модулей и дисциплина разделения ответственности помогают избежать циклических зависимостей и “корзин” вроде utils.py. А конфигурации нужно рассматривать как систему: defaults и env override отдельно, секреты — снаружи, загрузка — в точке запуска, а не на этапе импорта.

Если хотите углубиться в практику организации кода и инструментальные аспекты (tests, packaging, CI-friendly структура), полезно пройти отдельную методику обучения — например, курс по этой тематике можно посмотреть здесь: [ /course/ ].

Главная мысль: структура репозитория — это часть архитектуры проекта. И именно она чаще всего определяет, как быстро вы сможете добавлять функции без постоянных правок импорта, настройки тестов и мучительного дебага в CI.

Войдите, чтобы поставить лайк и оставить комментарий.

Автор

$ sudo teach IT

Продолжите обучение

Все курсы
Python – для начинающих!

Python – для начинающих!

С нуля до профессионального уровня. Подходит для всех. Учитесь каждый день и овладейте самым популярным языком программирования.

Перейти к курсу
Solidity DLC - поймай, если сможешь

Solidity DLC - поймай, если сможешь

Аудит смарт-контрактов на реальных взломах. The DAO, Parity, Wormhole, Ronin, Euler и ещё 7 разборов: что пошло не так, сколько потеряли и что бы спасло. Атаки и защиты на Foundry, план входа в профессию аудитора.

Перейти к курсу
Ботостроение Telegram

Ботостроение Telegram

Лёгкий, быстрый и доступный способ познакомиться с миром ботостроения в Telegram. Видео, конспекты, практика и помощь – всё у нас на курсе.

Перейти к курсу
Solidity pro - всё или ничего

Solidity pro - всё или ничего

Как пишут контракты, которые работают в основной сети. Наследование и OpenZeppelin, паттерны продакшена, оптимизация газа, ABI, delegatecall и подписи. 7 модулей, 33 урока, а в финале собственное dApp: мини-DAO с токеном, голосованием и казной.

Перейти к курсу

Другие статьи

Какой первый проект выбрать новичку, чтобы не бросить обучение
первый

Какой первый проект выбрать новичку, чтобы не бросить обучение

Подберём 5–7 идей под уровень “с нуля”, объясним, что делать по шагам и как довести проект до результата без перегруза. В конце — как оформить мини-портфолио и что показать, даже если проект маленький.

23 сентября 2026 г.
2100
Python для аналитики на практике: как строить признаки и проверять их воспроизводимость
python

Python для аналитики на практике: как строить признаки и проверять их воспроизводимость

Поймём, где чаще всего ломается воспроизводимость (версии данных, фильтры, дрейф) и как оформить пайплайн признаков так, чтобы результаты совпадали между запусками и окружениями.

17 сентября 2026 г.
1820
Как выучить программирование самому и бесплатно: с чего начать и в каком порядке
выучить

Как выучить программирование самому и бесплатно: с чего начать и в каком порядке

Разберём понятный маршрут для новичка: как выбрать первый язык, что учить в первую очередь, какие мини-проекты делать и как не забросить занятия. Дам бесплатные ориентиры и план, чтобы знания складывались в портфолио.

21 сентября 2026 г.
2240
Как выбрать первый сайт-проект новичку: что сделать за вечер и расширять дальше
выбрать

Как выбрать первый сайт-проект новичку: что сделать за вечер и расширять дальше

Подберём идею под ваш уровень и объясним, как превратить её в мини-портфолио: от одной страницы/формы до простого функционала и следующего шага. В конце — список конкретных вариантов проектов и план разработки на первые дни.

4 ноября 2026 г.
250
Cargo для продакшена: workspaces, фичи и стратегия версий зависимостей
cargo

Cargo для продакшена: workspaces, фичи и стратегия версий зависимостей

Разберём, как организовать Rust workspace, управлять optional features, фиксировать версии библиотек и уменьшать “дрейф” зависимостей при командной разработке и CI.

18 сентября 2026 г.
2240
Как начать с HTML и сделать первую страницу: пошагово и без сложных слов
html

Как начать с HTML и сделать первую страницу: пошагово и без сложных слов

Разберём, что нужно новичку, чтобы за 1–2 вечера собрать свою первую веб-страницу: структура HTML, базовые теги, ссылки и картинки, плюс простой чек-лист, как понять, что страница работает правильно.

2 ноября 2026 г.
310

Комментарии

Станьте первым в обсуждении

Поделитесь мнением о статье или задайте вопрос автору

Содержание

Почему структура репозитория важнее, чем кажетсяВыбор схемы: src-layout vs flatFlat layout: когда код лежит прямо в корнеsrc-layout: предсказуемость через установку пакетаКак выглядит правильный pyproject.toml для src-layoutКогда всё-таки может понадобиться flat layoutНейминг модулей и пакетов: правила, которые экономят времяИменование пакетов: строчные, без дефисов, единый стильИменование модулей: по ответственности, а не по формату файлаСтруктура подпакетов: domain, adapters, services — но с дисциплинойinit.py: минимализм и осмысленные экспортыГде хранить конфиги: от простого до воспроизводимого и безопасногоТри разные категории: default configs, env configs, secretsБазовый вариант: конфиги в репозитории, секреты — снаружиРекомендованный формат: TOML/YAML + явный loaderГде держать конфиги в дереве проекта: config/ vs src/.../configСекреты: только не в репозитории (и не в тестовых фикстурах “по умолчанию”)Циклические импорты, “магия” и почему CI может ломатьсяОшибка №1: импорты завязаны на рабочий каталогОшибка №2: конфигурация подхватывается слишком раноОшибка №3: тесты зависят от реальных файлов конфиговОшибка №4: нейминг, который конфликтуетПодготовка к росту и CI: что проверить до того, как “полетит”1) Пакет импортируется после установки2) Тесты не требуют секретов “на уровне импорта”3) Конфиги можно переопределить для тестов4) Не полагайтесь на текущую директорию (cwd)5) Структура соответствует сборкеПрактический пример “зрелого” дерева проектаПодводные камни: типовые решения, которые потом приходится переделывать“Сейчас положим всё в utils.py”“Конфиги прямо рядом с кодом”“Секреты в .env и всё локально”Вывод: выбирать структуру под предсказуемость, а не под удобство “сейчас”