src layout: Правильная структура проектов
Ваш проект растёт. Тесты начинают импортировать код странными путями: sys.path.append('../src'). В production код работает, локально — нет. Что не так?
Цель: Научиться правильно структурировать проекты с src layout.
Вы точно готовы?
Убедитесь, что понимаете:
# Базовые импорты Python
from mypackage import module
import mypackage.module
# Относительные импорты
from . import module
from .. import parent_moduleЕсли импорты Python непонятны — освежите основы Python.
Проблема: flat layout
Плохая структура: flat layout
my-project/
├── mypackage/
│ ├── __init__.py
│ ├── models.py
│ └── services.py
├── tests/
│ └── test_services.py
├── setup.py
└── README.mdЧто не так?
Проблема 1: Импорты не работают
# tests/test_services.py
from mypackage.services import UserService # ❌ ModuleNotFoundError!Почему: Python не знает где искать mypackage!
Плохие решения:
# ❌ ПЛОХО #1: sys.path хак
import sys
sys.path.append('../')
from mypackage.services import UserService
# ❌ ПЛОХО #2: Относительные пути
import os
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from mypackage.services import UserService
# ❌ ПЛОХО #3: PYTHONPATH
# export PYTHONPATH=/path/to/project
from mypackage.services import UserServiceПроблемы:
- ❌ Хрупкие пути
- ❌ Не работает в CI
- ❌ Разные пути на разных машинах
Проблема 2: Тестируете неправильный код
# Запускаем тесты
pytest tests/
# ✅ Тесты проходят!НО:
# Устанавливаем пакет
pip install .
# Импортируем установленную версию
python -c "from mypackage import services"
# ❌ Получаем старый код! (кеш .pyc или неактуальная установка)Проблема: Тесты запускают код из рабочей директории, а production использует установленный пакет!
Проблема 3: Случайные импорты
# tests/test_services.py
# ❌ Может импортировать tests/models.py вместо mypackage/models.py!
from models import UserПроблема: Python ищет модули начиная с текущей директории!
Решение: src layout
Правильная структура: src layout
my-project/
├── src/
│ └── mypackage/
│ ├── __init__.py
│ ├── models.py
│ └── services.py
├── tests/
│ └── test_services.py
├── pyproject.toml
└── README.mdКлючевое отличие: Код в src/mypackage/, не в корне!
Почему это лучше?
1. Импорты всегда из установленного пакета
# Установка в editable режиме
pip install -e .
# Теперь импорты работают ВЕЗДЕ
pytest tests/
python -c "from mypackage import services" # ✅ Работает!2. Невозможно случайно импортировать неустановленный код
# tests/test_services.py
from mypackage.services import UserService # ✅ Всегда из src/mypackage/3. Одинаковое поведение локально и в production
# Локально
pip install -e .
pytest
# Production
pip install .
python app.py
# ✅ Одинаковый код в обоих случаях!Создание src layout проекта (5 минут)
Шаг 1: Создайте структуру
mkdir my-project
cd my-project
# Создайте src директорию
mkdir -p src/mypackage
touch src/mypackage/__init__.py
# Создайте tests директорию
mkdir tests
touch tests/__init__.py
# Создайте pyproject.toml
touch pyproject.tomlСтруктура:
my-project/
├── src/
│ └── mypackage/
│ └── __init__.py
├── tests/
│ └── __init__.py
└── pyproject.tomlШаг 2: pyproject.toml
# pyproject.toml
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "mypackage"
version = "0.1.0"
description = "My awesome package"
authors = [
{name = "Your Name", email = "you@example.com"}
]
requires-python = ">=3.8"
dependencies = [
"requests>=2.28.0",
"pydantic>=2.0.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.0.0",
"pytest-cov>=4.0.0",
"pytest-xdist>=3.0.0",
]
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-v --tb=short"
[tool.setuptools.packages.find]
where = ["src"]Ключевые части:
[project]— метаданные пакетаdependencies— зависимости для productiondev— зависимости для разработки (pytest, etc.)[tool.setuptools.packages.find]— где искать пакеты (src/)
Шаг 3: pip install -e .
# Установка в editable (разработческом) режиме
pip install -e .
# Или с dev зависимостями
pip install -e ".[dev]"Что делает -e (editable):
- Создаёт symlink на
src/mypackage/ - Изменения в коде сразу доступны (не нужно переустанавливать)
- Работает как установленный пакет
Проверка:
# Импортируем из любого места
python -c "from mypackage import __version__" # ✅ Работает!
# Запускаем тесты
pytest # ✅ Импорты работают!Миграция с flat на src layout (3 минуты)
До (flat layout)
my-project/
├── mypackage/
│ ├── models.py
│ └── services.py
└── tests/
└── test_services.pyМиграция
# Создайте src директорию
mkdir src
# Переместите пакет в src
mv mypackage src/
# Создайте pyproject.toml (см. выше)
# Установите в editable режиме
pip install -e ".[dev]"
# Запустите тесты
pytestПосле (src layout)
my-project/
├── src/
│ └── mypackage/
│ ├── models.py
│ └── services.py
├── tests/
│ └── test_services.py
└── pyproject.toml✅ Импорты работают без хаков!
Практический пример
Создайте код
# src/mypackage/__init__.py
__version__ = "0.1.0"
# src/mypackage/calculator.py
def add(a, b):
"""Складывает два числа"""
return a + b
def multiply(a, b):
"""Умножает два числа"""
return a * bНапишите тесты
# tests/test_calculator.py
from mypackage.calculator import add, multiply
def test_add():
assert add(2, 3) == 5
def test_multiply():
assert multiply(4, 5) == 20Запустите
# Установка (если ещё не сделали)
pip install -e ".[dev]"
# Тесты
pytest
# ✅ Работает!Импортируйте из любого места
# Python REPL
python
>>> from mypackage import __version__
>>> print(__version__)
0.1.0
>>> from mypackage.calculator import add
>>> add(10, 20)
30✅ Всё работает как настоящий пакет!
Best practices
1. Всегда используйте src layout
✅ src/mypackage/
❌ mypackage/2. pyproject.toml вместо setup.py
# ✅ СОВРЕМЕННО
# pyproject.toml
# ❌ УСТАРЕЛО
# setup.py3. Editable install для разработки
# ✅ Разработка
pip install -e ".[dev]"
# ✅ Production
pip install .4. Разделяйте зависимости
[project]
dependencies = [
"requests", # Production
]
[project.optional-dependencies]
dev = [
"pytest", # Только для dev
]5. Никогда не используйте sys.path
# ❌ ПЛОХО
import sys
sys.path.append('../')
# ✅ ХОРОШО
# pip install -e .
from mypackage import moduleЧто вы изучили
- Проблемы flat layout — импорты, тестирование неправильного кода
- src layout — правильная структура проектов
- pyproject.toml — современная конфигурация
- pip install -e . — editable install для разработки
- Миграция — как перейти с flat на src
- Best practices — src/, pyproject.toml, no sys.path
Следующий урок
Отлично! Теперь проект структурирован правильно. Но как оптимизировать фикстуры для максимальной производительности?
Переходите к уроку 2: Продвинутые фикстуры: scope и autouse
В следующем уроке вы узнаете:
scope="session"для медленных фикстурscope="module"vsscope="function"autouse=Trueдля автоматических setup- Стратегии оптимизации фикстур
Устранение неисправностей
Забыли `pip install -e .`:
cd /path/to/project
pip install -e .Перезапустите Python процесс или переустановите:
pip install -e . --force-reinstallИспользуйте `pyproject.toml` (современный стандарт PEP 517/518):
# ✅ pyproject.toml
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
`setup.py` устарел для простых проектов!
# Обычная установка (копирует файлы)
pip install .
# Editable установка (symlink, для разработки)
pip install -e .Для разработки всегда используйте `-e`!
Проверьте `pyproject.toml`:
[tool.pytest.ini_options]
testpaths = ["tests"] # ✅ Правильно
# НЕ указывайте src/ в testpaths!pytest должен искать тесты в `tests/`, а импортировать код из установленного пакета (`src/mypackage/`).