architect/standards/1-structure/structure-library.md

type: standard
aspect: structure
title: "Управление кодом: Library"
version: 1.0.0
date: 2026-02-19
status: active


Управление кодом: Library

Версия: 2.1.0
Дата: 2025-12-20
Статус: ОБЯЗАТЕЛЬНО


ПРИНЦИП

ОДИН РАЗ НАПИСАТЬ → МНОГО РАЗ ИСПОЛЬЗОВАТЬ

Перед написанием кода — проверь library/ и system/.


ТРИ УРОВНЯ КОДА

┌─────────────────────────────────────────────────────────────┐
  УРОВЕНЬ 1: ПЛАТФОРМА (для всех проектов)                   
  library/connectors/   API/Data/Device коннекторы           
  library/functions/    Парсеры, форматтеры, валидаторы      
  library/internal/     Утилиты (хелперы)                    
└─────────────────────────────────────────────────────────────┘
                              
┌─────────────────────────────────────────────────────────────┐
  УРОВЕНЬ 2: ПРОЕКТ (только для данного проекта)             
  project/lib/          Проектные библиотеки                 
  project/data/config/  Конфигурации для платформы           
└─────────────────────────────────────────────────────────────┘
                              
┌─────────────────────────────────────────────────────────────┐
  УРОВЕНЬ 3: ПРИЛОЖЕНИЕ (конкретный сервис)                  
  project/app/X/        Бизнес-логика приложения             
└─────────────────────────────────────────────────────────────┘

РАЗГРАНИЧЕНИЕ: library/ vs project/lib/

Вопрос library/ project/lib/
Для кого? Все проекты Один проект
Что это? Готовый код (коннекторы, функции) Проектный код
Примеры ozon-connector, xlsx-parser jf_importer
Зависит от данных проекта? Нет Да
library/                             projects/X/lib/
├── connectors/                      ├── pim/
   ├── api/                            └── ndb_client.py
      ├── ozon/                    ├── importers/
      └── telegram/                   └── jf_importer.py
   ├── data/                        └── utils/
      ├── postgres/                    └── sku_parser.py
      └── nocodb/
   └── device/
       └── printer/
├── functions/
   ├── parsers/
      └── xlsx/
   └── formatters/
└── internal/
    └── some-lib/

Правило размещения

Код использует внешний API/БД/устройство?
├── ДА  library/connectors/

└── НЕТ  Это функция обработки данных?
          ├── ДА  library/functions/
          
          └── НЕТ  Код переиспользуется внутри проекта?
                    ├── ДА  project/lib/
                    
                    └── НЕТ  project/app/X/ (inline)

СТРУКТУРА

library/
├── index.yaml           Каталог компонентов
├── sandbox/             DEV: сырой код (v0.x.x)
├── internal/            PROD: наш код (v1.0.0+)
└── external/            PROD: внешний код (vendor)
Папка Статус Версия Использование
sandbox dev 0.x.x Только разработка
internal prod ≥1.0.0 Все проекты
external prod Все проекты

ЖИЗНЕННЫЙ ЦИКЛ

┌─────────────────────────────────────────────────────────────┐
│  SANDBOX (v0.x.x)                                           │
│                                                             │
│  • Новый код                                                │
│  • Разработка и тестирование                                │
│  • Эксперименты                                             │
│  • НЕ для production                                        │
└────────────────────────┬────────────────────────────────────┘
                         │
                         │ Тесты пройдены
                         │ Документация готова
                         │ Версия → 1.0.0
                         ▼
┌─────────────────────────────────────────────────────────────┐
│  INTERNAL (v1.0.0+)                                         │
│                                                             │
│  • Проверенный стабильный код                               │
│  • Полная документация                                      │
│  • Для всех проектов                                        │
│  • Обратная совместимость обязательна                       │
└─────────────────────────────────────────────────────────────┘

Критерии перехода sandbox → internal


ПРАВИЛА ДЛЯ РАЗРАБОТЧИКА

Алгоритм

1. Получил задачу
   
2. Проверь library/
   ├── Есть в internal/external?  Используй
   ├── Есть в sandbox?  Оцени, помоги доработать
   └── Нет?  Создай в sandbox/
   
3. Используй в проекте

Импорт

# PROD — безопасно для production
from library.internal.filemanager import FileManager
from library.external.some_github_lib import Helper

# DEV — только для разработки!
from library.sandbox.new_parser import Parser  # НЕ для prod!

# КОННЕКТОРЫ — из system/, НЕ из library/!
from system.connectors.marketplaces.ozon import OzonClient
from system.connectors.delivery.pochta import PochtaClient

ВНЕШНИЙ КОД (external/)

Когда копировать

Ситуация Действие
pip/npm пакет, используем as-is НЕ копируем, указываем в requirements
Код которого нет в pip Копируем в external/
Модифицируем внешний код Копируем + MODS.md

Обязательные файлы

library/external/some-lib/
├── README.md            Что, как использовать
├── ORIGIN.md            Откуда: URL, версия, лицензия
├── MODS.md              Наши изменения (если есть)
└── src/

ВАЖНО: API-клиенты (ozon, pochta, telegram) — это НЕ external!
Они в library/connectors/api/. External — только чужой utility-код.

ORIGIN.md (шаблон)

# Источник

- **URL:** https://github.com/example/lib
- **Версия:** 1.2.3
- **Дата копирования:** 2025-12-20
- **Лицензия:** MIT

## Зачем взяли
[причина]

MODS.md (шаблон)

# Модификации

## 2025-12-20 — Описание изменения
**Файл:** src/client.py:45
**Причина:** [зачем]
**Изменение:** [что сделали]

ДОКУМЕНТАЦИЯ КОМПОНЕНТА

sandbox/

sandbox/my-component/
├── README.md           ← Что делает, как использовать
├── tests/              ← Тесты
└── src/                ← Код

internal/

internal/my-component/
├── README.md           ← Что, как использовать, примеры
├── HISTORY.md          ← Откуда появился, история изменений
├── tests/
└── src/

HISTORY.md (шаблон)

# История

## Происхождение
- **Появился в:** projects/pirotehnika
- **Дата:** 2025-11-01
- **Перенесён в library:** 2025-12-20

## Версии
- v1.0.0 (2025-12-20) — Первый релиз

ВЕРСИОНИРОВАНИЕ

MAJOR.MINOR.PATCH

MAJOR — несовместимые изменения
MINOR — новая функциональность (совместимо)
PATCH — исправления багов
Папка Версия
sandbox 0.x.x (всё может меняться)
internal ≥1.0.0 (обратная совместимость)

ПРОЕКТНЫЕ БИБЛИОТЕКИ (project/lib/)

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

Структура

projects/pirotehnika/
├── lib/                       Проектные библиотеки
   ├── __init__.py
   
   ├── pim/                     Работа с PIM
      ├── __init__.py
      ├── ndb_client.py        Обёртка над library/connectors/data/nocodb
      └── base_importer.py     Базовый класс импортеров
   
   ├── importers/               Импортеры поставщиков
      ├── __init__.py
      ├── jf_importer.py
      ├── maxsem_importer.py
      └── piroff_importer.py
   
   └── utils/                   Проектные утилиты
       ├── __init__.py
       └── sku_parser.py

├── data/
   └── config/                  Конфигурации
       ├── nocodb.yaml
       └── suppliers.yaml

└── app/
    └── mp1/                     Использует lib/
        └── solution/
            └── scripts/

Когда использовать project/lib/

Критерий project/lib/ library/
Использует данные проекта ✅ Да ❌ Нет
Специфичная бизнес-логика ✅ Да ❌ Нет
Переиспользуется в проекте ✅ Да ✅ Да
Переиспользуется между проектами ❌ Нет ✅ Да

Пример: обёртка над платформой

# projects/pirotehnika/lib/pim/ndb_client.py
"""Проектная обёртка над library/connectors/data/nocodb"""

from library.connectors.data.nocodb import NocoDBConnector, NocoDBCredentials

# Проектные таблицы
TABLES = {
    "Product": "tbl_abc123",
    "Order": "tbl_def456",
    "Supplier": "tbl_ghi789",
}

class PIMClient:
    """NocoDB клиент для PIM pirotehnika"""

    def __init__(self, config_path: str = "data/config/nocodb.yaml"):
        # Загружаем проектный конфиг
        config = load_config(config_path)

        # Используем платформенный коннектор
        self.connector = NocoDBConnector(
            NocoDBCredentials(**config)
        )

    def get_products(self):
        return self.connector.list(TABLES["Product"])

Использование в приложении

# projects/pirotehnika/app/mp1/solution/scripts/sync.py

# Импорт из проектной библиотеки
from lib.pim.ndb_client import PIMClient
from lib.importers.jf_importer import JFImporter

# Использование
client = PIMClient()
products = client.get_products()

Жизненный цикл: project/lib/ → library/

Если код из project/lib/ становится универсальным:

1. Код работает в проекте (project/lib/)
   
2. Другой проект хочет использовать
   
3. Выделяем универсальную часть
   
4. Переносим в library/sandbox/
   
5. Тестируем в обоих проектах
   
6. Переводим в library/internal/

СТРУКТУРА КОМПОНЕНТА

{component}/
├── README.md           ← Документация
├── index.yaml          ← Метаданные (опционально)
├── tests/
│   └── test_*.py
└── src/
    ├── __init__.py
    └── *.py

index.yaml компонента (опционально)

name: filemanager
version: "1.0.0"
description: "Файловый менеджер для DATASPACE"

dependencies:
  - requests
  - pathlib

author: "@user"

КАТАЛОГ (library/index.yaml)

components:
  sandbox:
    new-parser:
      version: "0.3.0"
      description: "Новый парсер"
      path: "sandbox/new-parser/"

  internal:
    filemanager:
      version: "1.0.0"
      description: "Файловый менеджер"
      path: "internal/filemanager/"

  external:
    pochta-api:
      version: "1.0.0"
      origin: "https://..."
      path: "external/pochta-api/"

ЗАПРЕТ НА СИМЛИНКИ

ПРАВИЛО: В git НЕ должно быть симлинков (symbolic links).

Проблемы симлинков

Windows — требуют админских прав, часто превращаются в текстовые файлы
Git clone — могут стать текстовыми файлами со ссылкой вместо реального линка
Кросс-платформенность — разное поведение на Linux/Mac/Windows
CI/CD — ломаются при сборке в контейнерах

Вместо симлинков использовать

Обёртки-скрипты (wrapper scripts):

#!/usr/bin/env python3
"""
create_value_object.py - Обёртка для create_entity.py
"""
import sys, subprocess
from pathlib import Path

script_dir = Path(__file__).parent
create_entity = script_dir / "create_entity.py"

# Добавить параметры и запустить
args = [sys.executable, str(create_entity), "--type", "value-object"] + sys.argv[1:]
sys.exit(subprocess.call(args))

Копии файлов — если файл маленький (<1KB)

Import/require — если это код:

# Вместо симлинка library/connectors/api/onec → 1c
# Используем импорт или реальную папку

Проверка на симлинки

# Проверить всю платформу (кроме node_modules и venv)
find $WORKSPACE -type l \
  -not -path "*/node_modules/*" \
  -not -path "*/venv/*" \
  -not -path "*/.venv/*" \
  -ls

# Проверить что в git
git ls-files -s | grep "^120000"

Если нашли симлинк в git

# 1. Удалить из git (но оставить файл локально)
git rm --cached path/to/symlink

# 2. Заменить на обёртку или копию
# 3. Закоммитить изменения
git add path/to/wrapper.py
git commit -m "replace symlink with wrapper script"

Исключения

Можно оставить симлинки (но НЕ коммитить в git):
- Локальные удобства навигации ($WORKSPACE/pirotehnika → projects/pirotehnika)
- Создаются автоматически (node_modules/.bin/, venv/bin/)

Добавить в .gitignore:

# Symlinks (не коммитим)
/lideravto
/pirotehnika
/seller1
*.old/

ЧЕКЛИСТЫ

Создание компонента

Переход в internal

Добавление внешнего кода


СВЯЗЬ С ПРОЕКТАМИ

# projects/pirotehnika/data/index.yaml
links:
  uses:
    - path: "library/internal/filemanager"
      what: "Файловый менеджер"
    - path: "library/internal/price-parser"
      what: "Парсер прайсов"
    # Коннекторы — из library/
    - path: "library/connectors/api/pochta"
      what: "API Почты России"

Версия: 3.0.0