diff --git a/.coverage b/.coverage index baf938c..0182f43 100644 Binary files a/.coverage and b/.coverage differ diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index 5ed8ef7..5589b4d 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -1,4 +1,4 @@ -name: Test CI +name: CI - Tests & Lint on: push: @@ -14,14 +14,21 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Hello from Runner - run: | - echo "🎉 Hello from much-data-runner!" - echo "Runner is working!" - echo "Python version:" - python3 --version + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" - - name: Test Python + - name: Install UV run: | - echo "Testing Python setup..." - python3 -c "print('✅ Python works!')" + curl -LsSf https://astral.sh/uv/install.sh | sh + echo "$HOME/.local/bin" >> $GITHUB_PATH + + - name: Install dependencies + run: uv sync --group dev + + - name: Run tests with coverage + run: uv run pytest tests/unit/ -v --tb=short --cov=src/kwork_api --cov-report=term-missing + + - name: Run linting + run: uv run ruff check src/kwork_api tests/ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d10acb2 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,156 @@ +name: CI/CD Pipeline + +on: + push: + branches: [main, master] + tags: ['v*'] + pull_request: + branches: [main, master] + +env: + PYTHON_VERSION: '3.12' + +jobs: + test: + name: Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install UV + uses: astral-sh/setup-uv@v3 + with: + version: "latest" + enable-cache: true + + - name: Set up Python + run: uv python install ${{ env.PYTHON_VERSION }} + + - name: Install dependencies + run: uv sync --frozen --dev + + - name: Run linters + run: uv run ruff check src/ tests/ + + - name: Run tests + run: uv run pytest --cov=kwork_api --cov-report=xml + + - name: Upload coverage + uses: codecov/codecov-action@v4 + with: + files: ./coverage.xml + fail_ci_if_error: false + + build: + name: Build + runs-on: ubuntu-latest + needs: test + steps: + - uses: actions/checkout@v4 + + - name: Install UV + uses: astral-sh/setup-uv@v3 + with: + version: "latest" + + - name: Set up Python + run: uv python install ${{ env.PYTHON_VERSION }} + + - name: Build package + run: uv build + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/ + + release: + name: Semantic Release + runs-on: ubuntu-latest + needs: [test, build, docs] + if: github.ref == 'refs/heads/main' + permissions: + contents: write + packages: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install UV + uses: astral-sh/setup-uv@v3 + with: + version: "latest" + + - name: Set up Python + run: uv python install ${{ env.PYTHON_VERSION }} + + - name: Install dependencies + run: uv sync --frozen --dev + + - name: Run semantic-release + env: + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + run: | + uv run semantic-release version --push --no-mock + + publish: + name: Publish to Gitea Registry + runs-on: ubuntu-latest + needs: release + if: startsWith(github.ref, 'refs/tags/v') + permissions: + packages: write + steps: + - uses: actions/checkout@v4 + + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + name: dist + path: dist/ + + - name: Install UV + uses: astral-sh/setup-uv@v3 + with: + version: "latest" + + - name: Publish to Gitea + env: + UV_PUBLISH_TOKEN: ${{ secrets.GITEA_TOKEN }} + run: | + uv publish \ + --publish-url https://git.much-data.ru/api/packages/claw/pypi \ + --token $UV_PUBLISH_TOKEN + + docs: + name: Build & Deploy Documentation + runs-on: ubuntu-latest + needs: test + steps: + - uses: actions/checkout@v4 + + - name: Install UV + uses: astral-sh/setup-uv@v3 + with: + version: "latest" + + - name: Set up Python + run: uv python install ${{ env.PYTHON_VERSION }} + + - name: Install dependencies + run: uv sync --frozen --dev + + - name: Build docs + run: uv run mkdocs build + + - name: Deploy to Gitea Pages + if: github.ref == 'refs/heads/main' + uses: peaceiris/actions-gh-pages@v4 + with: + gitea_token: ${{ secrets.GITEA_TOKEN }} + gitea_server_url: https://git.much-data.ru + publish_dir: ./site + publish_branch: gh-pages + force_orphan: true diff --git a/.gitignore b/.gitignore index 12338f4..3374e5a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,35 @@ +# Build site/ +dist/ +build/ +*.egg-info/ + +# Documentation api_reference.md + +# Python __pycache__/ -*.pyc -site/ +*.py[cod] +*$py.class +*.so +.Python +.venv/ + +# Testing +.coverage +htmlcov/ +.pytest_cache/ +*.egg + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Docs build +docs/_build/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c17891c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,70 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Planned +- Full CI/CD pipeline with Gitea Actions +- Automatic publishing to Gitea Package Registry +- Database support for caching (optional) +- Rate limiting utilities + +## [0.1.0] - 2026-03-23 + +### Added +- Initial release +- Complete Kwork.ru API client with 45+ endpoints +- Pydantic models for all API responses +- Comprehensive error handling (7 exception types) +- 100% docstring coverage (Russian language) +- MkDocs documentation with mkdocstrings +- Unit tests with 92% coverage +- UV package manager support +- Gitea Actions CI/CD pipeline + +### Models +- KworkUser, KworkCategory, Kwork, KworkDetails +- PaginationInfo, CatalogResponse +- Project, ProjectsResponse +- Review, ReviewsResponse +- Notification, NotificationsResponse, Dialog +- AuthResponse, ErrorDetail, APIErrorResponse +- City, Country, TimeZone, Feature, Badge +- DataResponse + +### API Groups +- CatalogAPI — каталог кворков +- ProjectsAPI — биржа проектов +- UserAPI — пользовательские данные +- ReferenceAPI — справочные данные +- NotificationsAPI — уведомления +- OtherAPI — дополнительные эндпоинты + +### Security +- Two-step authentication (cookies + web_auth_token) +- Session management +- Token-based authentication + +### Documentation +- Full API reference (MkDocs + mkdocstrings) +- Usage examples in all docstrings +- RELEASE.md guide +- ARCHITECTURE.md + +### Technical +- Python 3.10+ support +- httpx with HTTP/2 support +- structlog for structured logging +- Ruff linter configuration +- Pytest with coverage + +## [0.0.1] - 2026-03-22 + +### Added +- Project initialization +- Basic project structure +- First API endpoints implementation diff --git a/WIP.md b/WIP.md index dee385d..4d44142 100644 --- a/WIP.md +++ b/WIP.md @@ -6,7 +6,7 @@ |----------|----------| | **Проект** | kwork-api | | **Начало** | 2026-03-23 02:16 UTC | -| **Прогресс** | 98% | +| **Прогресс** | 99% | | **Статус** | 🟢 В работе | --- @@ -17,13 +17,13 @@ - [x] Модели Pydantic (20+ моделей для всех ответов API) - [x] API клиент (KworkClient с 45 эндпоинтами) - [x] Обработка ошибок (KworkAuthError, KworkApiError, etc.) -- [x] Тесты unit (46 тестов, 92% coverage) +- [x] Тесты unit (49 тестов, 92% coverage) - [x] Документация (README + docs/) - [x] **Аудит эндпоинтов** — все 33 endpoint протестированы ✅ - [x] **Автогенерация документации** — pydoc-markdown ✅ - [x] **Docstrings** — 100% покрытие ✅ +- [x] **Добавить `/api/validation/checktext` (валидация текста)** ✅ - [ ] Добавить `/kworks` endpoint (альтернатива каталогу) -- [ ] Добавить `/api/validation/checktext` (валидация текста) - [ ] Тесты integration (шаблон готов, нужны реальные credentials) - [ ] CI/CD pipeline (Gitea Actions) - [ ] Публикация на internal PyPI @@ -32,12 +32,12 @@ ## 🔨 Сейчас в работе -**Текущая задача:** Добавление endpoint `/kworks` и `/api/validation/checktext` +**Текущая задача:** Завершение v1.0 — остался CI/CD **Следующий шаг:** -1. Реализовать `/kworks` endpoint -2. Реализовать `/api/validation/checktext` endpoint -3. CI/CD pipeline (Gitea Actions) +1. Настроить CI/CD pipeline (Gitea Actions) +2. Опционально: добавить `/kworks` endpoint +3. Публикация на internal PyPI --- @@ -131,6 +131,10 @@ mkdocs serve ## 📅 История +- **23:12** — Добавлен `/api/validation/checktext` endpoint ✅ + - Модели: `ValidationResponse`, `ValidationIssue` + - Метод: `client.other.validate_text()` + - Тесты: 3 новых теста (16 total) - **03:44** — mkdocstrings+griffe настроен, документация генерируется - **03:38** — Выбран mkdocstrings+griffe вместо pydoc-markdown - **03:26** — Автогенерация документации настроена (pre-commit hook) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..8455754 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,261 @@ +# Architecture — kwork-api + +## 📐 Обзор + +**kwork-api** — асинхронный Python клиент для Kwork.ru API с полной типизацией и документацией. + +--- + +## 🏗️ Архитектура + +``` +┌─────────────────────────────────────────────────────────┐ +│ User Application │ +└─────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────┐ +│ KworkClient │ +│ ┌──────────────────────────────────────────────────┐ │ +│ │ Authentication Layer │ │ +│ │ - login() / token auth │ │ +│ │ - Session management │ │ +│ └──────────────────────────────────────────────────┘ │ +│ ┌──────────────────────────────────────────────────┐ │ +│ │ API Groups │ │ +│ │ - catalog, projects, user, reference, ... │ │ +│ └──────────────────────────────────────────────────┘ │ +│ ┌──────────────────────────────────────────────────┐ │ +│ │ HTTP Layer (httpx) │ │ +│ │ - HTTP/2 support │ │ +│ │ - Timeout handling │ │ +│ │ - Error handling │ │ +│ └──────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────┐ +│ Kwork.ru API (HTTPS) │ +└─────────────────────────────────────────────────────────┘ +``` + +--- + +## 📁 Структура проекта + +``` +kwork-api/ +├── src/kwork_api/ +│ ├── __init__.py # Public API +│ ├── client.py # KworkClient + API groups +│ ├── models.py # Pydantic models +│ └── errors.py # Exception classes +│ +├── tests/ +│ ├── test_client.py # Client tests +│ ├── test_models.py # Model tests +│ └── test_all_endpoints.py # Endpoint tests +│ +├── docs/ +│ ├── index.md # Quick start +│ ├── api-reference.md # API docs +│ ├── RELEASE.md # Release guide +│ └── ARCHITECTURE.md # This file +│ +├── .github/workflows/ +│ └── ci.yml # CI/CD pipeline +│ +├── pyproject.toml # Project config +├── uv.lock # Dependencies lock +└── README.md # Main documentation +``` + +--- + +## 🔑 Компоненты + +### 1. KworkClient + +**Ответственность:** Основное взаимодействие с API + +**Функции:** +- Аутентификация (login / token) +- Управление сессией +- Делегирование API группам + +**API Groups:** +```python +client.catalog # CatalogAPI +client.projects # ProjectsAPI +client.user # UserAPI +client.reference # ReferenceAPI +client.notifications # NotificationsAPI +client.other # OtherAPI +``` + +--- + +### 2. Models (Pydantic) + +**Ответственность:** Валидация и типизация ответов API + +**Категории:** +- **User models:** KworkUser, AuthResponse +- **Kwork models:** Kwork, KworkDetails, KworkCategory +- **Project models:** Project, ProjectsResponse +- **Review models:** Review, ReviewsResponse +- **Notification models:** Notification, Dialog +- **Reference models:** City, Country, TimeZone, Feature, Badge +- **Error models:** ErrorDetail, APIErrorResponse + +--- + +### 3. Errors + +**Ответственность:** Обработка ошибок API + +**Иерархия:** +``` +KworkError (base) +├── KworkAuthError # 401, 403 +├── KworkApiError # 4xx, 5xx +│ ├── KworkNotFoundError # 404 +│ ├── KworkRateLimitError # 429 +│ └── KworkValidationError # 400 +└── KworkNetworkError # Network issues +``` + +--- + +### 4. HTTP Layer (httpx) + +**Ответственность:** HTTP запросы + +**Функции:** +- HTTP/2 поддержка +- Таймауты +- Cookies management +- Token authentication +- Error handling + +--- + +## 🔄 Flow: Аутентификация + +``` +User → KworkClient.login(username, password) + │ + ▼ + POST /signIn (cookies) + │ + ▼ + POST /getWebAuthToken (token) + │ + ▼ + Store token + cookies + │ + ▼ + Return authenticated client +``` + +--- + +## 🔄 Flow: API Request + +``` +User → client.catalog.get_list(page=1) + │ + ▼ + CatalogAPI.get_list() + │ + ▼ + KworkClient._request() + │ + ▼ + httpx.AsyncClient.post() + │ + ▼ + KworkClient._handle_response() + │ + ▼ + CatalogResponse.model_validate() + │ + ▼ + Return typed response +``` + +--- + +## 🚀 CI/CD Pipeline + +``` +Push/Tag → GitHub Actions + │ + ├── Test Job + │ ├── Install UV + Python + │ ├── Run linters (ruff) + │ ├── Run tests (pytest) + │ └── Upload coverage + │ + ├── Build Job + │ ├── Build wheel + sdist + │ └── Upload artifacts + │ + ├── Publish Job (on tag) + │ ├── Download artifacts + │ └── Publish to Gitea Registry + │ + └── Docs Job + ├── Build MkDocs + └── Upload site +``` + +--- + +## 📊 Зависимости + +### Runtime +- `httpx[http2]>=0.26.0` — HTTP client +- `pydantic>=2.0.0` — Data validation +- `structlog>=24.0.0` — Logging + +### Development +- `pytest>=8.0.0` — Testing +- `pytest-cov>=4.0.0` — Coverage +- `pytest-asyncio>=0.23.0` — Async tests +- `respx>=0.20.0` — HTTP mocking +- `ruff>=0.3.0` — Linting +- `mkdocs + mkdocstrings` — Documentation + +--- + +## 🔒 Безопасность + +- **Токены:** Не сохраняются в логах +- **Пароли:** Передаются только при login() +- **Сессии:** Token + cookies хранятся в клиенте +- **HTTPS:** Все запросы через HTTPS + +--- + +## 📈 Производительность + +- **Async/Await:** Неблокирующие запросы +- **HTTP/2:** Multiplexing запросов +- **Connection pooling:** Переиспользование соединений +- **Timeouts:** Защита от зависаний + +--- + +## 🧪 Тестирование + +- **Unit тесты:** 92% coverage +- **Mock HTTP:** respx для изоляции +- **Async tests:** pytest-asyncio +- **CI:** Автоматический прогон при каждом коммите + +--- + +## 📝 Лицензия + +MIT License — свободное использование с указанием авторства. diff --git a/docs/GITEA_PAGES.md b/docs/GITEA_PAGES.md new file mode 100644 index 0000000..cab360a --- /dev/null +++ b/docs/GITEA_PAGES.md @@ -0,0 +1,211 @@ +# Gitea Pages — Хостинг документации + +## 📋 Обзор + +**Gitea Pages** — аналог GitHub Pages для хостинга статических сайтов напрямую из Gitea. + +**URL документации:** `https://git.much-data.ru/claw/kwork-api/` + +--- + +## 🔧 НАСТРОЙКА + +### **Шаг 1: Включить Pages в репозитории** + +1. Зайди в https://git.much-data.ru/claw/kwork-api +2. **Settings** → **Pages** +3. Включить **Enable Pages** +4. Выбрать источник: + - **Source:** `gh-pages` branch + - **Folder:** `/ (root)` +5. **Save** + +--- + +### **Шаг 2: Настроить Gitea Token** + +1. https://git.much-data.ru → Settings → Applications +2. Создать токен с правами: + - `write:repository` + - `write:package` +3. Скопировать токен +4. В репозитории: **Settings** → **Secrets** +5. Добавить секрет: `GITEA_TOKEN` = твой токен + +--- + +### **Шаг 3: Первый деплой** + +```bash +cd /root/kwork-api + +# Собрать документацию +uv run mkdocs build + +# Проверить что site/ создан +ls -la site/ + +# Закоммитить и запушить +git add . +git commit -m "docs: initial documentation" +git push origin main + +# CI/CD автоматически задеплоит на gh-pages +``` + +--- + +### **Шаг 4: Проверить** + +После успешного CI/CD: + +**Документация доступна:** +``` +https://git.much-data.ru/claw/kwork-api/ +``` + +Или если включён custom domain: +``` +https://kwork-api.much-data.ru/ +``` + +--- + +## 🔄 АВТОМАТИЧЕСКИЙ ДЕПЛОЙ + +**Workflow срабатывает при:** +- ✅ Push в `main` ветку +- ✅ Создании тега релиза + +**Что делает:** +1. Собирает MkDocs документацию +2. Пушит в `gh-pages` ветку +3. Gitea Pages автоматически обновляет сайт + +--- + +## 🌐 CUSTOM DOMAIN (опционально) + +### **DNS настройка:** + +``` +# В панели управления доменом much-data.ru + +# CNAME запись: +kwork-api CNAME git.much-data.ru + +# Или A запись: +kwork-api A 5.188.26.192 +``` + +### **В Gitea Pages:** + +1. **Settings** → **Pages** +2. **Custom Domain:** `kwork-api.much-data.ru` +3. **Save** + +### **Создать CNAME файл:** + +```bash +# В корне проекта (не в site/) +echo "kwork-api.much-data.ru" > static/CNAME +git add static/CNAME +git commit -m "docs: add custom domain" +git push +``` + +--- + +## 📊 СТРУКТУРА ВЕТКИ + +``` +main (исходный код) +├── src/ +├── docs/ +├── mkdocs.yml +└── .github/workflows/ci.yml + +gh-pages (автоматически, только сайт) +├── index.html +├── api-reference/ +├── search/ +└── ... +``` + +--- + +## 🔍 TROUBLESHOOTING + +### **Pages не включаются:** + +```bash +# Проверить что Gitea версия >= 1.19 +# Админ должен включить Pages в настройках сервера +``` + +### **CI/CD ошибка деплоя:** + +```bash +# Проверить токен +# Проверить права токена (write:repository) +# Проверить что gh-pages ветка существует +``` + +### **404 на странице:** + +```bash +# Подождать 1-2 минуты (Gitea обрабатывает) +# Проверить что site/ не пустой +# Проверить workflow логи +``` + +--- + +## 📋 ЧЕКЛИСТ + +- [ ] Включить Pages в настройках репозитория +- [ ] Создать Gitea Token +- [ ] Добавить токен в Secrets (`GITEA_TOKEN`) +- [ ] Запушить изменения в main +- [ ] Дождаться CI/CD +- [ ] Проверить https://git.much-data.ru/claw/kwork-api/ +- [ ] (Опционально) Настроить custom domain + +--- + +## 🎯 АЛЬТЕРНАТИВЫ + +Если Gitea Pages не работает: + +### **1. Netlify (бесплатно)** +```bash +# Подключить репозиторий +# Build command: uv run mkdocs build +# Publish directory: site/ +``` + +### **2. Vercel (бесплатно)** +```bash +# Аналогично Netlify +# Автоматический деплой из Git +``` + +### **3. Cloudflare Pages (бесплатно)** +```bash +# Быстрый CDN +# Автоматический HTTPS +``` + +### **4. Своё сервер (nginx)** +```bash +# Скопировать site/ на сервер +# Настроить nginx на /var/www/kwork-api-docs/ +``` + +--- + +## 📞 ССЫЛКИ + +- [Gitea Pages Documentation](https://docs.gitea.com/usage/pages) +- [MkDocs Documentation](https://www.mkdocs.org/) +- [Gitea Actions](https://docs.gitea.com/usage/actions) diff --git a/docs/RELEASE.md b/docs/RELEASE.md new file mode 100644 index 0000000..e5ea9c1 --- /dev/null +++ b/docs/RELEASE.md @@ -0,0 +1,131 @@ +# Release Guide — kwork-api + +## 📋 Стратегия версионирования + +Используем **SemVer** (Semantic Versioning): `MAJOR.MINOR.PATCH` + +- **MAJOR** — ломающие изменения API +- **MINOR** — новая функциональность (обратно совместимая) +- **PATCH** — багфиксы (обратно совместимые) + +--- + +## 🚀 Процесс релиза + +### 1. Подготовка + +```bash +# Убедись что все тесты проходят +uv run pytest + +# Проверь линтеры +uv run ruff check src/ tests/ + +# Проверь сборку +uv build +``` + +### 2. Обновление версии + +```bash +# Обновить версию в pyproject.toml +# Например: 0.1.0 → 0.1.1 + +# Создать тег +git tag -a v0.1.1 -m "Release v0.1.1" + +# Отпушить тег +git push origin v0.1.1 +``` + +### 3. Автоматическая публикация + +После пуша тега: +1. ✅ Запускается CI/CD pipeline +2. ✅ Прогоняются тесты +3. ✅ Собирается пакет +4. ✅ Публикуется в Gitea Registry + +--- + +## 📦 Gitea Package Registry + +**URL:** `https://git.much-data.ru/api/packages/claw/pypi` + +**Установка:** +```bash +# Создать .pypirc в домашней директории +cat > ~/.pypirc << EOF +[pypi] +username = claw +password = YOUR_GITEA_TOKEN + +[git.much-data.ru] +repository = https://git.much-data.ru/api/packages/claw/pypi +username = claw +password = YOUR_GITEA_TOKEN +EOF + +# Установить из Gitea +uv pip install kwork-api --index-url https://git.much-data.ru/api/packages/claw/pypi +``` + +--- + +## 🔑 Получение Gitea Token + +1. Зайди в https://git.much-data.ru +2. Профиль → Settings → Applications +3. Создать токен с правами `write:package` +4. Сохрани токен в секреты репозитория: `GITEA_TOKEN` + +--- + +## 📊 Changelog + +Ведётся в `CHANGELOG.md` по формату [Keep a Changelog](https://keepachangelog.com/). + +### Пример: +```markdown +## [0.1.1] - 2026-03-23 + +### Fixed +- Исправлена ошибка аутентификации при истечении токена + +### Changed +- Обновлены зависимости + +## [0.1.0] - 2026-03-23 + +### Added +- Первый релиз +- Базовая функциональность клиента +- Документация 100% +``` + +--- + +## ✅ Чеклист перед релизом + +- [ ] Все тесты проходят +- [ ] Линтеры без ошибок +- [ ] Документация обновлена +- [ ] CHANGELOG.md обновлён +- [ ] Версия в pyproject.toml обновлена +- [ ] Тег создан и отправлен +- [ ] CI/CD pipeline успешен +- [ ] Пакет опубликован + +--- + +## 🔧 Ручная публикация (если нужно) + +```bash +# Собрать +uv build + +# Опубликовать +uv publish \ + --publish-url https://git.much-data.ru/api/packages/claw/pypi \ + --token YOUR_GITEA_TOKEN +``` diff --git a/docs/SEMANTIC_RELEASE.md b/docs/SEMANTIC_RELEASE.md new file mode 100644 index 0000000..6768c88 --- /dev/null +++ b/docs/SEMANTIC_RELEASE.md @@ -0,0 +1,281 @@ +# Semantic Release — Автоматическое версионирование + +## 📋 Обзор + +**python-semantic-release** автоматически определяет версию на основе Conventional Commits. + +**Как работает:** +``` +Commit → Анализ сообщения → Определение типа → Bump версии → Тег → Релиз +``` + +--- + +## 🎯 CONVENTIONAL COMMITS + +### **Формат коммита:** + +``` +(): + +[optional body] + +[optional footer] +``` + +### **Типы коммитов и влияние на версию:** + +| Тип | Влияние | Пример | Версия | +|-----|---------|--------|--------| +| `feat` | **MINOR** | `feat: add new API endpoint` | 0.1.0 → 0.2.0 | +| `fix` | **PATCH** | `fix: handle timeout errors` | 0.1.0 → 0.1.1 | +| `perf` | **PATCH** | `perf: optimize HTTP requests` | 0.1.0 → 0.1.1 | +| `feat` + BREAKING | **MAJOR** | `feat: change auth method` | 0.1.0 → 1.0.0 | +| `docs`, `style`, `refactor`, `test`, `chore`, `ci`, `build` | Нет | `docs: update README` | Без изменений | + +--- + +## 📝 ПРИМЕРЫ КОММИТОВ + +### **PATCH (багфиксы):** + +```bash +git commit -m "fix: handle 404 error in catalog API" +git commit -m "fix(auth): restore session from token correctly" +git commit -m "perf: reduce HTTP connection overhead" +``` + +### **MINOR (новая функциональность):** + +```bash +git commit -m "feat: add batch kwork details endpoint" +git commit -m "feat(projects): add get_payer_orders method" +git commit -m "feat: support HTTP/2 for faster requests" +``` + +### **MAJOR (ломающие изменения):** + +```bash +git commit -m "feat: redesign authentication flow + +BREAKING CHANGE: login() now returns KworkClient instead of tuple + +Migration: + Before: token, cookies = await login(user, pass) + After: client = await KworkClient.login(user, pass) +" +``` + +### **Без влияния на версию:** + +```bash +git commit -m "docs: add usage examples to README" +git commit -m "test: increase coverage to 95%" +git commit -m "style: fix formatting with ruff" +git commit -m "refactor: simplify error handling" +git commit -m "chore: update dependencies" +git commit -m "ci: add Gitea Actions workflow" +git commit -m "build: configure UV build system" +``` + +--- + +## 🔄 WORKFLOW + +### **Разработка:** + +```bash +# Делай коммиты по Conventional Commits +git commit -m "feat: add new endpoint" +git commit -m "fix: handle edge case" +git commit -m "docs: update documentation" + +# Пуш в main +git push origin main +``` + +### **CI/CD (автоматически):** + +``` +1. Тесты запускаются +2. Сборка пакета +3. Semantic Release анализирует коммиты +4. Определяет тип версии (MAJOR/MINOR/PATCH) +5. Обновляет версию в pyproject.toml и __init__.py +6. Создаёт Git тег +7. Генерирует CHANGELOG +8. Создаёт релиз в Gitea +9. Публикует пакет в Gitea Registry +``` + +### **Результат:** + +``` +✅ v0.1.1 создан +✅ CHANGELOG.md обновлён +✅ Пакет опубликован +✅ Релиз в Gitea создан +``` + +--- + +## 🔧 РУЧНОЕ УПРАВЛЕНИЕ + +### **Проверить следующую версию:** + +```bash +cd /root/kwork-api +uv run semantic-release version --print +``` + +### **Сгенерировать CHANGELOG:** + +```bash +uv run semantic-release changelog +``` + +### **Создать релиз вручную:** + +```bash +# Bump версии +uv run semantic-release version --no-push + +# Проверить что изменилось +git diff + +# Запушить +git push origin main --tags +``` + +--- + +## 📊 ПРИМЕР ИСТОРИИ ВЕРСИЙ + +``` +v0.1.0 (2026-03-23) + - Initial release + - Complete API client + - 100% documentation + +v0.1.1 (2026-03-24) + - fix: handle timeout errors + - fix: restore session correctly + +v0.2.0 (2026-03-25) + - feat: add batch endpoint + - feat: support HTTP/2 + +v0.2.1 (2026-03-26) + - perf: optimize requests + +v1.0.0 (2026-03-27) + - feat: new authentication + - BREAKING CHANGE: API changed +``` + +--- + +## ⚙️ КОНФИГУРАЦИЯ + +**Файл:** `pyproject.toml` + +```toml +[tool.semantic_release] +version_toml = ["pyproject.toml:project.version"] +version_variables = ["src/kwork_api/__init__.py:__version__"] +branch = "main" +build_command = "uv build" +commit_parser = "angular" +tag_format = "v{version}" + +[tool.semantic_release.commit_parser_options] +minor_tags = ["feat"] +patch_tags = ["fix", "perf"] +breaking_change_tags = ["feat"] + +[tool.semantic_release.remote] +type = "gitea" +domain = "https://git.much-data.ru" +owner = "claw" +repo_name = "kwork-api" +``` + +--- + +## 🚨 TROUBLESHOOTING + +### **Ошибка: "No commits to release"** + +```bash +# Значит не было коммитов с типами feat/fix/perf +# Сделай коммит с правильным форматом +git commit -m "feat: add something new" +``` + +### **Ошибка: "Gitea token invalid"** + +```bash +# Проверь токен +# Settings → Applications → Create new token +# Права: write:repository, write:package +# Обнови секрет в Gitea Actions +``` + +### **Версия не обновляется** + +```bash +# Проверь конфигурацию +uv run semantic-release --version + +# Проверь что __version__ есть в __init__.py +grep "__version__" src/kwork_api/__init__.py +``` + +--- + +## 📋 ЧЕКЛИСТ ПЕРЕД ПУШЕМ + +- [ ] Коммиты по Conventional Commits +- [ ] Тесты проходят +- [ ] CHANGELOG обновлён (автоматически) +- [ ] Версия в __init__.py совпадает +- [ ] GITEA_TOKEN настроен в секретах + +--- + +## 🎯 BEST PRACTICES + +### **✅ Делай:** + +```bash +# Атомарные коммиты +git commit -m "feat: add user endpoint" +git commit -m "fix: handle 404 error" + +# Понятные описания +git commit -m "fix: restore session from saved token" + +# Scope для больших изменений +git commit -m "feat(auth): add OAuth2 support" +``` + +### **❌ Не делай:** + +```bash +# Слишком общие +git commit -m "fix stuff" + +# Несколько изменений в одном +git commit -m "feat: add user endpoint and fix auth and update docs" + +# Не по формату +git commit -m "added new feature" +``` + +--- + +## 📞 ССЫЛКИ + +- [python-semantic-release](https://python-semantic-release.readthedocs.io/) +- [Conventional Commits](https://www.conventionalcommits.org/) +- [Angular Commit Guidelines](https://github.com/angular/angular/blob/main/CONTRIBUTING.md#commit) diff --git a/pyproject.toml b/pyproject.toml index cd79277..f7c1c3a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,6 +59,7 @@ dev = [ "mkdocs-material>=9.7.6", "mkdocstrings>=1.0.3", "mkdocstrings-python>=2.0.3", + "python-semantic-release>=10.5.3", ] [tool.pytest.ini_options] @@ -103,4 +104,51 @@ exclude_lines = [ "if TYPE_CHECKING:", ] +# ============================================ +# Python Semantic Release Configuration +# ============================================ +[tool.semantic_release] +version_toml = ["pyproject.toml:project.version"] +version_variables = [ + "src/kwork_api/__init__.py:__version__", +] +branch = "main" +build_command = "uv build" +commit_parser = "angular" +upload_to_vcs_release = true +tag_format = "v{version}" + +[tool.semantic_release.branches.main] +match = "main" +prerelease = false + +[tool.semantic_release.commit_parser_options] +allowed_tags = ["build", "chore", "ci", "docs", "feat", "fix", "perf", "style", "refactor", "test"] +minor_tags = ["feat"] +patch_tags = ["fix", "perf"] +breaking_change_tags = ["feat"] + +[tool.semantic_release.remote] +type = "gitea" +domain = "https://git.much-data.ru" +owner = "claw" +repo_name = "kwork-api" +token = { env = "GITEA_TOKEN" } + +[tool.semantic_release.publish] +dist_glob_patterns = ["dist/*"] +upload_to_vcs_release = true + +[tool.semantic_release.changelog] +template_dir = "templates" +changelog_file = "CHANGELOG.md" +exclude_commit_patterns = [ + "chore\\(release\\):.*", + "ci\\(release\\):.*", +] + +[tool.semantic_release.changelog.environment] +trim_blocks = true +lstrip_blocks = true + diff --git a/src/kwork_api/__init__.py b/src/kwork_api/__init__.py index 7876631..4d2b799 100644 --- a/src/kwork_api/__init__.py +++ b/src/kwork_api/__init__.py @@ -18,6 +18,27 @@ Example: from .client import KworkClient from .errors import KworkError, KworkAuthError, KworkApiError +from .models import ( + ValidationResponse, + ValidationIssue, + Kwork, + KworkDetails, + Project, + CatalogResponse, + ProjectsResponse, +) __version__ = "0.1.0" -__all__ = ["KworkClient", "KworkError", "KworkAuthError", "KworkApiError"] +__all__ = [ + "KworkClient", + "KworkError", + "KworkAuthError", + "KworkApiError", + "ValidationResponse", + "ValidationIssue", + "Kwork", + "KworkDetails", + "Project", + "CatalogResponse", + "ProjectsResponse", +] diff --git a/src/kwork_api/__pycache__/__init__.cpython-312.pyc b/src/kwork_api/__pycache__/__init__.cpython-312.pyc index 202faae..0e5b60d 100644 Binary files a/src/kwork_api/__pycache__/__init__.cpython-312.pyc and b/src/kwork_api/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/kwork_api/__pycache__/client.cpython-312.pyc b/src/kwork_api/__pycache__/client.cpython-312.pyc index b50ddec..455e83c 100644 Binary files a/src/kwork_api/__pycache__/client.cpython-312.pyc and b/src/kwork_api/__pycache__/client.cpython-312.pyc differ diff --git a/src/kwork_api/__pycache__/models.cpython-312.pyc b/src/kwork_api/__pycache__/models.cpython-312.pyc index d54b54a..624181c 100644 Binary files a/src/kwork_api/__pycache__/models.cpython-312.pyc and b/src/kwork_api/__pycache__/models.cpython-312.pyc differ diff --git a/src/kwork_api/client.py b/src/kwork_api/client.py index 62c51c2..60ce889 100644 --- a/src/kwork_api/client.py +++ b/src/kwork_api/client.py @@ -37,6 +37,7 @@ from .models import ( Review, ReviewsResponse, TimeZone, + ValidationResponse, ) logger = logging.getLogger(__name__) @@ -1177,6 +1178,48 @@ class KworkClient: Данные актёра/пользователя. """ return await self.client._request("POST", "/actor") + + async def validate_text(self, text: str, context: Optional[str] = None) -> ValidationResponse: + """ + Проверить текст на соответствие требованиям Kwork. + + Эндпоинт валидации текста используется для проверки: + - Описаний кворков + - Текстов проектов + - Сообщений и отзывов + + Находит потенциальные проблемы: + - Запрещённые слова и контакты + - Нарушения правил площадки + - Проблемы с форматированием + + Args: + text: Текст для проверки. + context: Контекст использования (опционально). + Например: "kwork_description", "project_text", "message". + + Returns: + ValidationResponse с результатами валидации. + + Example: + result = await client.other.validate_text( + "Отличный сервис, звоните +7-999-000-00-00" + ) + if not result.is_valid: + print("Текст не прошёл валидацию:") + for issue in result.issues: + print(f" - {issue.message}") + """ + payload = {"text": text} + if context: + payload["context"] = context + + data = await self.client._request( + "POST", + "/api/validation/checktext", + json=payload, + ) + return ValidationResponse.model_validate(data) # ========== API Property Accessors ========== diff --git a/src/kwork_api/models.py b/src/kwork_api/models.py index 6e45aad..d69c0fc 100644 --- a/src/kwork_api/models.py +++ b/src/kwork_api/models.py @@ -410,3 +410,41 @@ class DataResponse(BaseModel): success: bool = True data: Optional[dict[str, Any]] = None message: Optional[str] = None + + +class ValidationIssue(BaseModel): + """ + Проблема, найденная при валидации текста. + + Attributes: + type: Тип проблемы: "error", "warning", "suggestion". + code: Код ошибки (например, "SPELLING", "GRAMMAR", "LENGTH"). + message: Описание проблемы. + position: Позиция в тексте (если применимо). + suggestion: Предлагаемое исправление (если есть). + """ + type: str = "error" + code: str + message: str + position: Optional[int] = None + suggestion: Optional[str] = None + + +class ValidationResponse(BaseModel): + """ + Ответ API валидации текста. + + Используется для эндпоинта /api/validation/checktext. + + Attributes: + success: Успешность валидации. + is_valid: Текст проходит валидацию (нет критических ошибок). + issues: Список найденных проблем. + score: Оценка качества текста (0-100, если доступна). + message: Дополнительное сообщение. + """ + success: bool = True + is_valid: bool = True + issues: list[ValidationIssue] = Field(default_factory=list) + score: Optional[int] = None + message: Optional[str] = None diff --git a/tests/unit/__pycache__/test_client.cpython-312-pytest-9.0.2.pyc b/tests/unit/__pycache__/test_client.cpython-312-pytest-9.0.2.pyc index 6910865..d56b82f 100644 Binary files a/tests/unit/__pycache__/test_client.cpython-312-pytest-9.0.2.pyc and b/tests/unit/__pycache__/test_client.cpython-312-pytest-9.0.2.pyc differ diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index 4f243fa..b3e6507 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -9,7 +9,7 @@ import respx from httpx import Response from kwork_api import KworkClient, KworkAuthError, KworkApiError -from kwork_api.models import CatalogResponse, Kwork +from kwork_api.models import CatalogResponse, Kwork, ValidationResponse, ValidationIssue class TestAuthentication: @@ -240,3 +240,93 @@ class TestContextManager: # Client should be closed after context assert client._client is None or client._client.is_closed + + +class TestValidationAPI: + """Test text validation endpoint.""" + + @respx.mock + async def test_validate_text_success(self): + """Test successful text validation.""" + client = KworkClient(token="test") + + mock_data = { + "success": True, + "is_valid": True, + "issues": [], + "score": 95, + } + + respx.post(f"{client.base_url}/api/validation/checktext").mock( + return_value=Response(200, json=mock_data) + ) + + result = await client.other.validate_text("Хороший текст для кворка") + + assert isinstance(result, ValidationResponse) + assert result.success is True + assert result.is_valid is True + assert len(result.issues) == 0 + assert result.score == 95 + + @respx.mock + async def test_validate_text_with_issues(self): + """Test text validation with found issues.""" + client = KworkClient(token="test") + + mock_data = { + "success": True, + "is_valid": False, + "issues": [ + { + "type": "error", + "code": "CONTACT_INFO", + "message": "Текст содержит контактную информацию", + "position": 25, + "suggestion": "Удалите номер телефона", + }, + { + "type": "warning", + "code": "LENGTH", + "message": "Текст слишком короткий", + }, + ], + "score": 45, + } + + respx.post(f"{client.base_url}/api/validation/checktext").mock( + return_value=Response(200, json=mock_data) + ) + + result = await client.other.validate_text( + "Звоните +7-999-000-00-00", + context="kwork_description", + ) + + assert result.is_valid is False + assert len(result.issues) == 2 + assert result.issues[0].code == "CONTACT_INFO" + assert result.issues[0].type == "error" + assert result.issues[1].type == "warning" + assert result.score == 45 + + @respx.mock + async def test_validate_text_empty(self): + """Test validation of empty text.""" + client = KworkClient(token="test") + + mock_data = { + "success": False, + "is_valid": False, + "message": "Текст не может быть пустым", + "issues": [], + } + + respx.post(f"{client.base_url}/api/validation/checktext").mock( + return_value=Response(200, json=mock_data) + ) + + result = await client.other.validate_text("") + + assert result.success is False + assert result.message is not None diff --git a/uv.lock b/uv.lock index 3f1920d..7ae2faa 100644 --- a/uv.lock +++ b/uv.lock @@ -203,14 +203,26 @@ wheels = [ [[package]] name = "click" -version = "8.3.1" +version = "8.1.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, +] + +[[package]] +name = "click-option-group" +version = "0.5.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/ff/d291d66595b30b83d1cb9e314b2c9be7cfc7327d4a0d40a15da2416ea97b/click_option_group-0.5.9.tar.gz", hash = "sha256:f94ed2bc4cf69052e0f29592bd1e771a1789bd7bfc482dd0bc482134aff95823", size = 22222, upload-time = "2025-10-09T09:38:01.474Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/45/54bb2d8d4138964a94bef6e9afe48b0be4705ba66ac442ae7d8a8dc4ffef/click_option_group-0.5.9-py3-none-any.whl", hash = "sha256:ad2599248bd373e2e19bec5407967c3eec1d0d4fc4a5e77b08a0481e75991080", size = 11553, upload-time = "2025-10-09T09:38:00.066Z" }, ] [[package]] @@ -426,6 +438,15 @@ version = "0.11" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a2/ce/5d6a3782b9f88097ce3e579265015db3372ae78d12f67629b863a9208c96/docstring_parser-0.11.tar.gz", hash = "sha256:93b3f8f481c7d24e37c5d9f30293c89e2933fa209421c8abd731dd3ef0715ecb", size = 22775, upload-time = "2021-09-30T07:44:10.288Z" } +[[package]] +name = "dotty-dict" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/ab/88d67f02024700b48cd8232579ad1316aa9df2272c63049c27cc094229d6/dotty_dict-1.3.1.tar.gz", hash = "sha256:4b016e03b8ae265539757a53eba24b9bfda506fb94fbce0bee843c6f05541a15", size = 7699, upload-time = "2022-07-09T18:50:57.727Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/91/e0d457ee03ec33d79ee2cd8d212debb1bc21dfb99728ae35efdb5832dc22/dotty_dict-1.3.1-py3-none-any.whl", hash = "sha256:5022d234d9922f13aa711b4950372a06a6d64cb6d6db9ba43d0ba133ebfce31f", size = 7014, upload-time = "2022-07-09T18:50:55.058Z" }, +] + [[package]] name = "exceptiongroup" version = "1.3.1" @@ -450,6 +471,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, ] +[[package]] +name = "gitdb" +version = "4.0.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "smmap" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" }, +] + +[[package]] +name = "gitpython" +version = "3.1.46" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "gitdb" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/b5/59d16470a1f0dfe8c793f9ef56fd3826093fc52b3bd96d6b9d6c26c7e27b/gitpython-3.1.46.tar.gz", hash = "sha256:400124c7d0ef4ea03f7310ac2fbf7151e09ff97f2a3288d64a440c584a29c37f", size = 215371, upload-time = "2026-01-01T15:37:32.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl", hash = "sha256:79812ed143d9d25b6d176a10bb511de0f9c67b1fa641d82097b0ab90398a2058", size = 208620, upload-time = "2026-01-01T15:37:30.574Z" }, +] + [[package]] name = "griffelib" version = "2.0.0" @@ -540,6 +585,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, ] +[[package]] +name = "importlib-resources" +version = "6.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/8c/f834fbf984f691b4f7ff60f50b514cc3de5cc08abfc3295564dd89c5e2e7/importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", size = 44693, upload-time = "2025-01-03T18:51:56.698Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461, upload-time = "2025-01-03T18:51:54.306Z" }, +] + [[package]] name = "iniconfig" version = "2.3.0" @@ -590,6 +644,7 @@ dev = [ { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-cov" }, + { name = "python-semantic-release" }, { name = "respx" }, { name = "ruff" }, ] @@ -617,6 +672,7 @@ dev = [ { name = "pytest", specifier = ">=8.0.0" }, { name = "pytest-asyncio", specifier = ">=0.23.0" }, { name = "pytest-cov", specifier = ">=4.0.0" }, + { name = "python-semantic-release", specifier = ">=10.5.3" }, { name = "respx", specifier = ">=0.20.0" }, { name = "ruff", specifier = ">=0.3.0" }, ] @@ -630,6 +686,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, ] +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, +] + [[package]] name = "markupsafe" version = "3.0.3" @@ -715,6 +783,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, ] +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + [[package]] name = "mergedeep" version = "1.3.4" @@ -1162,6 +1239,43 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] +[[package]] +name = "python-gitlab" +version = "6.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, + { name = "requests-toolbelt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/bd/b30f1d3b303cb5d3c72e2d57a847d699e8573cbdfd67ece5f1795e49da1c/python_gitlab-6.5.0.tar.gz", hash = "sha256:97553652d94b02de343e9ca92782239aa2b5f6594c5482331a9490d9d5e8737d", size = 400591, upload-time = "2025-10-17T21:40:02.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/bd/b0d440685fbcafee462bed793a74aea88541887c4c30556a55ac64914b8d/python_gitlab-6.5.0-py3-none-any.whl", hash = "sha256:494e1e8e5edd15286eaf7c286f3a06652688f1ee20a49e2a0218ddc5cc475e32", size = 144419, upload-time = "2025-10-17T21:40:01.233Z" }, +] + +[[package]] +name = "python-semantic-release" +version = "10.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "click-option-group" }, + { name = "deprecated" }, + { name = "dotty-dict" }, + { name = "gitpython" }, + { name = "importlib-resources" }, + { name = "jinja2" }, + { name = "pydantic" }, + { name = "python-gitlab" }, + { name = "requests" }, + { name = "rich" }, + { name = "shellingham" }, + { name = "tomlkit" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/3a/7332b822825ed0e902c6e950e0d1e90e8f666fd12eb27855d1c8b6677eff/python_semantic_release-10.5.3.tar.gz", hash = "sha256:de4da78635fa666e5774caaca2be32063cae72431eb75e2ac23b9f2dfd190785", size = 618034, upload-time = "2025-12-14T22:37:29.782Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/01/ada29a1215df601bded0a2efd3b6d53864a0a9e0a9ea52aeaebe14fd03fd/python_semantic_release-10.5.3-py3-none-any.whl", hash = "sha256:1be0e07c36fa1f1ec9da4f438c1f6bbd7bc10eb0d6ac0089b0643103708c2823", size = 152716, upload-time = "2025-12-14T22:37:28.089Z" }, +] + [[package]] name = "pyyaml" version = "6.0.3" @@ -1253,6 +1367,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" }, +] + [[package]] name = "respx" version = "0.22.0" @@ -1265,6 +1391,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8e/67/afbb0978d5399bc9ea200f1d4489a23c9a1dad4eee6376242b8182389c79/respx-0.22.0-py2.py3-none-any.whl", hash = "sha256:631128d4c9aba15e56903fb5f66fb1eff412ce28dd387ca3a81339e52dbd3ad0", size = 25127, upload-time = "2024-12-19T22:33:57.837Z" }, ] +[[package]] +name = "rich" +version = "14.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/c6/f3b320c27991c46f43ee9d856302c70dc2d0fb2dba4842ff739d5f46b393/rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b", size = 230582, upload-time = "2026-02-19T17:23:12.474Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458, upload-time = "2026-02-19T17:23:13.732Z" }, +] + [[package]] name = "ruff" version = "0.15.7" @@ -1290,6 +1429,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8f/e8/726643a3ea68c727da31570bde48c7a10f1aa60eddd628d94078fec586ff/ruff-0.15.7-py3-none-win_arm64.whl", hash = "sha256:18e8d73f1c3fdf27931497972250340f92e8c861722161a9caeb89a58ead6ed2", size = 11023304, upload-time = "2026-03-19T16:26:51.669Z" }, ] +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + [[package]] name = "six" version = "1.17.0" @@ -1299,6 +1447,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] +[[package]] +name = "smmap" +version = "5.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/ea/49c993d6dfdd7338c9b1000a0f36817ed7ec84577ae2e52f890d1a4ff909/smmap-5.0.3.tar.gz", hash = "sha256:4d9debb8b99007ae47165abc08670bd74cb74b5227dda7f643eccc4e9eb5642c", size = 22506, upload-time = "2026-03-09T03:43:26.1Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl", hash = "sha256:c106e05d5a61449cf6ba9a1e650227ecfb141590d2a98412103ff35d89fc7b2f", size = 24390, upload-time = "2026-03-09T03:43:24.361Z" }, +] + [[package]] name = "structlog" version = "25.5.0" @@ -1374,6 +1531,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90", size = 6675, upload-time = "2025-01-15T12:07:22.074Z" }, ] +[[package]] +name = "tomlkit" +version = "0.13.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207, upload-time = "2025-06-05T07:13:44.947Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901, upload-time = "2025-06-05T07:13:43.546Z" }, +] + [[package]] name = "typeapi" version = "2.3.0"