vedro-step-tracer 0.1.0a4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. vedro_step_tracer-0.1.0a4/PKG-INFO +6 -0
  2. vedro_step_tracer-0.1.0a4/README.md +92 -0
  3. vedro_step_tracer-0.1.0a4/pyproject.toml +15 -0
  4. vedro_step_tracer-0.1.0a4/setup.cfg +4 -0
  5. vedro_step_tracer-0.1.0a4/vedro_step_tracer/__init__.py +19 -0
  6. vedro_step_tracer-0.1.0a4/vedro_step_tracer/config.py +99 -0
  7. vedro_step_tracer-0.1.0a4/vedro_step_tracer/filenames.py +71 -0
  8. vedro_step_tracer-0.1.0a4/vedro_step_tracer/gitlab.py +124 -0
  9. vedro_step_tracer-0.1.0a4/vedro_step_tracer/plugin.py +548 -0
  10. vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/__init__.py +7 -0
  11. vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/base.py +29 -0
  12. vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/html/__init__.py +5 -0
  13. vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/html/assets/__init__.py +52 -0
  14. vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/html/escaping.py +11 -0
  15. vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/html/layout.py +101 -0
  16. vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/html/palette.py +49 -0
  17. vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/html/traceback.py +144 -0
  18. vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/html/values.py +220 -0
  19. vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/html/writer.py +223 -0
  20. vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/llm/__init__.py +6 -0
  21. vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/llm/traceback.py +101 -0
  22. vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/llm/writer.py +73 -0
  23. vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/secrets.py +20 -0
  24. vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/shorten.py +111 -0
  25. vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/source.py +74 -0
  26. vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/status.py +27 -0
  27. vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/teamcity/__init__.py +5 -0
  28. vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/teamcity/messages.py +94 -0
  29. vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/teamcity/writer.py +303 -0
  30. vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/terminal/__init__.py +5 -0
  31. vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/terminal/markup.py +225 -0
  32. vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/terminal/writer.py +259 -0
  33. vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/unwrap.py +33 -0
  34. vedro_step_tracer-0.1.0a4/vedro_step_tracer/settings.py +36 -0
  35. vedro_step_tracer-0.1.0a4/vedro_step_tracer/tracing/__init__.py +10 -0
  36. vedro_step_tracer-0.1.0a4/vedro_step_tracer/tracing/nodes.py +114 -0
  37. vedro_step_tracer-0.1.0a4/vedro_step_tracer/tracing/tracer.py +408 -0
  38. vedro_step_tracer-0.1.0a4/vedro_step_tracer/tracing/tree.py +252 -0
  39. vedro_step_tracer-0.1.0a4/vedro_step_tracer.egg-info/PKG-INFO +6 -0
  40. vedro_step_tracer-0.1.0a4/vedro_step_tracer.egg-info/SOURCES.txt +41 -0
  41. vedro_step_tracer-0.1.0a4/vedro_step_tracer.egg-info/dependency_links.txt +1 -0
  42. vedro_step_tracer-0.1.0a4/vedro_step_tracer.egg-info/requires.txt +1 -0
  43. vedro_step_tracer-0.1.0a4/vedro_step_tracer.egg-info/top_level.txt +1 -0
@@ -0,0 +1,6 @@
1
+ Metadata-Version: 2.4
2
+ Name: vedro-step-tracer
3
+ Version: 0.1.0a4
4
+ Summary: Vedro plugin that traces function calls in e2e steps and renders call-tree reports
5
+ Requires-Python: >=3.9
6
+ Requires-Dist: vedro<2,>=1.10
@@ -0,0 +1,92 @@
1
+ # VedroStepTracer
2
+
3
+ Плагин для [vedro](https://vedro.io/), который автоматически трейсит вызовы функций во время выполнения тестов и генерирует детальные HTML-отчёты с деревом вызовов, переменными и трейсбеком ошибок.
4
+
5
+ ## Для чего нужен
6
+
7
+ При падении e2e-теста сложно понять **что именно произошло** — какие функции вызывались, с какими аргументами, что вернули и где упало. Плагин перехватывает все вызовы из указанных директорий через `sys.settrace()`, строит иерархическое дерево и сохраняет его как интерактивный HTML-файл и/или выводит в stdout.
8
+
9
+ - 🌳 **Видно весь путь до ошибки** — дерево вложенных вызовов `helpers/contexts/interfaces` с аргументами, локальными переменными и возвращаемыми значениями каждого шага, поэтому не нужно расставлять `print`/`breakpoint` и перезапускать тест, чтобы понять, где и с какими данными всё пошло не так
10
+ - 📄 **Отчёт файлом рядом с артефактами сценария** — пишется в `.vedro/artifacts/...` (раскладка `vedro.plugins.artifacted`) при падении сценария, а при `--tr` — и для успешных; в результат сценария добавляется кликабельная ссылка `🔎 Trace report: …`. Блоки с деталями упавшей ветки развёрнуты сразу, остальное свёрнуто, чтобы не тонуть в шуме
11
+ - ✂️ **Длинные значения сокращаются, но не теряются** — в HTML хвост скрыт под `…` и раскрывается по клику (и находится поиском по отчёту); в stdout и дереве тестов IDE обрезка жёсткая, полная версия остаётся в артефакте
12
+ - 🔗 **Прямая ссылка на артефакты job'а GitLab CI** — не нужно вручную искать нужный запуск и скачивать файл, чтобы посмотреть трейс
13
+
14
+ ---
15
+
16
+ ## CLI-флаги
17
+
18
+ | Флаг | Тип | По умолчанию | Описание |
19
+ |------|-----|-------------|----------|
20
+ | `--tr [LEVEL]` | `int` (0–3) | `1` (если указан без значения) | Включает интерактивный трейс в stdout. Уровни детализации: |
21
+ | | | | `0` — только return из target_dirs |
22
+ | | | | `1` — return + scope |
23
+ | | | | `2` — scope + args/return всех уровней |
24
+ | | | | `3` — всё (scope + args + locals + return) |
25
+ | `--tr-no-color` | `flag` | `false` | Отключить ANSI-раскраску в выводе трейса |
26
+ | `--tr-gitlab` | `flag` | auto | Добавить маркеры схлопываемых секций GitLab CI (автоопределяется по `CI_JOB_ID`) |
27
+
28
+ ### Примеры запуска
29
+
30
+ ```bash
31
+ # Базовый трейс (return + scope)
32
+ python -m vedro run --tr
33
+
34
+ # Максимальная детализация
35
+ python -m vedro run --tr 3
36
+
37
+ # Без цветов (для логов)
38
+ python -m vedro run --tr --tr-no-color
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Параметры конфигурации
44
+
45
+ | Параметр | Тип | По умолчанию | Описание |
46
+ |----------|-----|-------------|----------|
47
+ | `enabled` | `bool` | `True` | Включить/выключить плагин |
48
+ | `target_dirs` | `list[str]` | `["helpers", "contexts", "interfaces"]` | Директории для трейсинга (относительно workdir) |
49
+ | `exclude_paths` | `list[str]` | `[]` | Glob-паттерны файлов внутри `target_dirs`, вызовы из которых не попадают в дерево |
50
+ | `exclude_functions` | `list[str]` | `[]` | Имена функций, которые не попадают в дерево |
51
+ | `attach_artifacts` | `bool` | `True` | Сохранять отчёт в директорию артефактов сценария |
52
+ | `save_to_dir` | `bool` | `False` | Дополнительно сохранять HTML в `output_dir` |
53
+ | `output_dir` | `str` | `"logs/traces"` | Папка для HTML-отчётов (нужна только при `save_to_dir`) |
54
+ | `print_to_stdout` | `bool` | `False` | Печатать полное дерево в stdout (иначе — только ссылка на отчёт) |
55
+ | `max_str_len` | `int` | `1000` | Макс. длина строкового значения; применяется к каждой физической строке |
56
+ | `max_line_len` | `int` | `1000` | Макс. длина одной физической строки в выводе нестандартного объекта (`__str__`/`repr`/rich) |
57
+ | `collapse_passthrough` | `bool` | `True` | Схлопывать транзитные цепочки вызовов в одну хлебную крошку |
58
+ | `job_id` | `str` | `""` | ID задачи CI (для ссылок на артефакты) |
59
+ | `artifact_url_pattern` | `str` | `""` | Шаблон URL артефакта: `{job_id}`, `{path}` |
60
+ | `workdir_subpath` | `str` | `""` | Подпуть рабочей директории в репозитории; задайте, если тесты запускаются из вложенной директории |
61
+
62
+ ---
63
+
64
+ ## Пример конфигурации
65
+
66
+ ```python
67
+ # vedro.cfg.py
68
+ import os
69
+ import vedro
70
+
71
+ from vedro_step_tracer import VedroStepTracer
72
+
73
+
74
+ class Config(vedro.Config):
75
+
76
+ class Plugins(vedro.Config.Plugins):
77
+
78
+ class StepTracer(VedroStepTracer):
79
+ enabled = True
80
+ target_dirs = ['helpers', 'contexts', 'interfaces']
81
+ exclude_paths = ['interfaces/generated_client.py']
82
+
83
+ # Отчёт уходит файлом в артефакты сценария; stdout остаётся читаемым.
84
+ attach_artifacts = True
85
+ print_to_stdout = False
86
+
87
+ output_dir = 'logs/traces'
88
+ save_to_dir = bool(os.environ.get('CI_JOB_ID'))
89
+
90
+ job_id = os.environ.get('CI_JOB_ID')
91
+ artifact_url_pattern = 'https://gitlab.example.com/group/SomeProject/-/jobs/{job_id}/artifacts/file/{path}'
92
+ ```
@@ -0,0 +1,15 @@
1
+ [project]
2
+ name = "vedro-step-tracer"
3
+ version = "0.1.0a4"
4
+ description = "Vedro plugin that traces function calls in e2e steps and renders call-tree reports"
5
+ requires-python = ">=3.9"
6
+ dependencies = [
7
+ "vedro>=1.10,<2",
8
+ ]
9
+
10
+ [build-system]
11
+ requires = ["setuptools>=68"]
12
+ build-backend = "setuptools.build_meta"
13
+
14
+ [tool.setuptools.packages.find]
15
+ include = ["vedro_step_tracer*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,19 @@
1
+ """
2
+ Vedro plugin: автоматический трейсинг вызовов функций из helpers/, contexts/, interfaces/.
3
+
4
+ Использует sys.settrace() для перехвата всех вызовов и возвратов из целевых
5
+ директорий, собирает локальные переменные и возвращаемые значения, выводит
6
+ иерархическое дерево по спецификации формата вывода шагов теста.
7
+
8
+ Структура пакета:
9
+ * ``settings`` — изменяемые в рантайме настройки (лимит длины строк);
10
+ * ``tracing`` — модель дерева, sys.settrace-трейсер, пост-обработка;
11
+ * ``reporting`` — рендереры отчётов: ``terminal`` (stdout) и ``html``;
12
+ * ``gitlab`` — специфика GitLab CI: секции лога, ссылки на артефакты;
13
+ * ``plugin`` / ``config`` — интеграция с vedro.
14
+ """
15
+
16
+ from .config import VedroStepTracer
17
+ from .plugin import VedroStepTracerPlugin
18
+
19
+ __all__ = ("VedroStepTracer", "VedroStepTracerPlugin",)
@@ -0,0 +1,99 @@
1
+ """Конфиг плагина (vedro PluginConfig)."""
2
+
3
+ from typing import List, Optional
4
+
5
+ from vedro.core import PluginConfig
6
+
7
+ from .plugin import VedroStepTracerPlugin
8
+
9
+ __all__ = ("VedroStepTracer",)
10
+
11
+
12
+ class VedroStepTracer(PluginConfig):
13
+ plugin = VedroStepTracerPlugin
14
+
15
+ # Директории для отслеживания (относительно рабочей папки)
16
+ target_dirs: List[str] = ["helpers", "contexts", "interfaces"]
17
+
18
+ # Файлы внутри target_dirs, вызовы из которых не попадают в дерево
19
+ # (glob-паттерны, например "interfaces/api_client.py").
20
+ # Исключение означает «не создавать узел для вызовов из этого файла»:
21
+ # вложенные целевые вызовы сохраняются и прицепляются к ближайшему
22
+ # не исключённому предку.
23
+ exclude_paths: List[str] = []
24
+
25
+ # Имена функций, которые не попадают в дерево (по тому же правилу).
26
+ exclude_functions: List[str] = []
27
+
28
+ # Куда сохранять логи (используется только при save_to_dir = True)
29
+ output_dir: str = "logs/traces"
30
+
31
+ # Сохранять отчёт в директорию артефактов сценария (.vedro/artifacts,
32
+ # раскладка plugins.artifacted) — рядом с prompt-файлами и дампами.
33
+ # Файл пишется напрямую, артефактом vedro не прикрепляется: в результате
34
+ # сценария остаётся одна строка со ссылкой на отчёт.
35
+ attach_artifacts: bool = True
36
+
37
+ # Дополнительно сохранять HTML-отчёт в output_dir. Прежнее поведение;
38
+ # нужно там, где артефакты vedro не собираются (например, часть job'ов CI).
39
+ save_to_dir: bool = False
40
+
41
+ # Печатать полное дерево трейса в stdout. По умолчанию выключено: отчёт
42
+ # уходит в артефакт, а в stdout остаётся только ссылка на него. Полное
43
+ # дерево печатается при явном --tr.
44
+ print_to_stdout: bool = False
45
+
46
+ # Максимальная длина строкового значения перед сокращением.
47
+ # Применяется к каждой физической строке отдельно.
48
+ max_str_len: int = 1000
49
+
50
+ # Максимальная длина одной физической строки в выводе нестандартного
51
+ # объекта (рендер через __str__/repr/rich, например HTTP-трейс).
52
+ max_line_len: int = 1000
53
+
54
+ # Схлопывать транзитные цепочки вызовов: обёртка без собственных
55
+ # переменных, с одним вложенным вызовом и тем же возвращаемым объектом
56
+ # печаталась отдельным уровнем с тем же значением. Схлопывание без
57
+ # потерь — имена звеньев остаются в хлебной крошке
58
+ # "SomeProject ▸ load_data → DataClient ▸ fetch".
59
+ collapse_passthrough: bool = True
60
+
61
+ # ID job'а GitLab CI (например, job_id = os.environ.get('CI_JOB_ID')).
62
+ # По умолчанию не заполнен (локальный запуск). Его наличие используется
63
+ # для автоопределения запуска в GitLab CI, если флаг --tr-gitlab не
64
+ # передан явно.
65
+ job_id: str = ""
66
+
67
+ # Паттерн ссылки на артефакт GitLab CI с плейсхолдерами {job_id} и {path},
68
+ # например:
69
+ # "https://gitlab.example.com/group/SomeProject/-/jobs/{job_id}/artifacts/file/{path}"
70
+ # Используется для ссылки на Trace report только если job_id заполнен
71
+ # (запуск в GitLab CI); при локальном запуске ссылка формируется через локальный file:// путь.
72
+ artifact_url_pattern: str = ""
73
+
74
+ # Подпуть рабочей директории относительно корня репозитория,
75
+ # используемый при формировании хостовых и артефактных ссылок.
76
+ # Пустая строка означает, что рабочая директория — корень репозитория.
77
+ workdir_subpath: str = ""
78
+
79
+ # Удалять ранее сохранённые HTML-отчёты из output_dir перед прогоном.
80
+ # Иначе директория копится от запуска к запуску, и среди старых файлов
81
+ # трудно найти отчёт текущего прогона. Удаляются только *.html в самой
82
+ # output_dir, без рекурсии (см. filenames.cleanup_reports).
83
+ cleanup_before: bool = True
84
+
85
+ # Выводить дерево шагов service-сообщениями TeamCity — тогда шаги и
86
+ # вложенные вызовы видны деревом в тест-раннере PyCharm (встроенный
87
+ # репортёр vedro сообщает IDE только о сценариях).
88
+ # None — определять автоматически: выбран репортёр `-r pycharm` либо
89
+ # прогон идёт под IDE/TeamCity (см. teamcity_env_vars).
90
+ teamcity: Optional[bool] = None
91
+
92
+ # Переменные окружения, наличие которых означает запуск под
93
+ # PyCharm/TeamCity (используются при teamcity = None).
94
+ teamcity_env_vars: List[str] = ["PYCHARM_HOSTED", "TEAMCITY_VERSION", "PYCHARM_HELPERS_DIR"]
95
+
96
+ # Рисовать глифы дерева ("║ ┃ │ ├╴ ") перед строками в режиме TeamCity.
97
+ # False — печатать только заголовки и содержимое: внутри развёрнутого
98
+ # узла вложенность видна и без них, а отступы съедают ширину под значения.
99
+ teamcity_tree_prefix: bool = False
@@ -0,0 +1,71 @@
1
+ """Имена файлов отчётов: санитизация и очистка старых отчётов.
2
+
3
+ Имя отчёта строится из subject сценария, а там встречается что угодно —
4
+ двоеточия, слэши, кавычки. Такие символы ломают путь (`/`), несовместимы с
5
+ Windows/сетевыми ФС (`:`, `?`, `*`, `"`, `<`, `>`, `|`) и требуют
6
+ экранирования в ссылках, которые печатаются в терминал и в GitLab CI.
7
+ Поэтому имя всегда приводится к безопасному виду.
8
+ """
9
+
10
+ import os
11
+ import re
12
+ from typing import List
13
+
14
+ __all__ = ("sanitize_filename", "cleanup_reports",)
15
+
16
+ # Символы, недопустимые в именах файлов хотя бы на одной из целевых ФС,
17
+ # плюс управляющие символы.
18
+ _UNSAFE_RE = re.compile(r'[<>:"/\\|?*\x00-\x1f]+')
19
+ # Пробельные последовательности схлопываются в одно подчёркивание: имена
20
+ # отчётов часто попадают в ссылки, где пробелы приходится экранировать.
21
+ _SPACE_RE = re.compile(r"\s+")
22
+ _UNDERSCORE_RE = re.compile(r"_{2,}")
23
+
24
+ _FALLBACK_NAME = "scenario"
25
+
26
+
27
+ def sanitize_filename(name: str, max_length: int = 80) -> str:
28
+ """
29
+ Привести произвольную строку к безопасному имени файла (без расширения).
30
+
31
+ Небезопасные символы и пробелы заменяются подчёркиванием, повторы
32
+ схлопываются, точки и подчёркивания по краям убираются (имя, начинающееся
33
+ с точки, скрыто в файловых менеджерах). Пустой результат заменяется на
34
+ ``scenario``, чтобы отчёт не превратился в файл без имени.
35
+ """
36
+ cleaned = _UNSAFE_RE.sub("_", name)
37
+ cleaned = _SPACE_RE.sub("_", cleaned)
38
+ cleaned = _UNDERSCORE_RE.sub("_", cleaned)
39
+ cleaned = cleaned.strip("._ ")
40
+ if max_length > 0:
41
+ cleaned = cleaned[:max_length].strip("._ ")
42
+ return cleaned or _FALLBACK_NAME
43
+
44
+
45
+ def cleanup_reports(output_dir: str, suffix: str = ".html") -> List[str]:
46
+ """
47
+ Удалить ранее сохранённые отчёты из директории вывода.
48
+
49
+ Удаляются только файлы с нужным расширением и только в самой директории
50
+ (без рекурсии): она может быть смонтирована рядом с другими артефактами,
51
+ и сносить её целиком небезопасно. Возвращает список удалённых путей;
52
+ ошибки доступа игнорируются — очистка не должна ронять прогон тестов.
53
+ """
54
+ removed: List[str] = []
55
+ try:
56
+ entries = os.listdir(output_dir)
57
+ except OSError:
58
+ return removed
59
+
60
+ for entry in entries:
61
+ if not entry.endswith(suffix):
62
+ continue
63
+ path = os.path.join(output_dir, entry)
64
+ if not os.path.isfile(path):
65
+ continue
66
+ try:
67
+ os.remove(path)
68
+ except OSError:
69
+ continue
70
+ removed.append(path)
71
+ return removed
@@ -0,0 +1,124 @@
1
+ """GitLab CI-специфика: схлопываемые секции лога и ссылки на артефакты."""
2
+
3
+ import os
4
+ import time
5
+ from typing import Optional
6
+ from urllib.parse import quote
7
+
8
+ __all__ = (
9
+ "SectionIdGenerator", "section_start", "section_end",
10
+ "to_host_path", "to_relative_path", "to_artifact_url", "make_terminal_link",
11
+ )
12
+
13
+
14
+ class SectionIdGenerator:
15
+ """Выдаёт уникальные идентификаторы секций в пределах одного отчёта."""
16
+
17
+ def __init__(self, prefix: str = "tr") -> None:
18
+ self._prefix = prefix
19
+ self._counter = 0
20
+
21
+ def next_id(self) -> str:
22
+ self._counter += 1
23
+ return f"{self._prefix}{self._counter}"
24
+
25
+
26
+ def section_start(section_id: str, collapsed: bool = True) -> str:
27
+ """Маркер начала схлопываемой секции GitLab CI (пишется перед заголовком)."""
28
+ ts = int(time.time())
29
+ opts = "[collapsed=true]" if collapsed else ""
30
+ return f"\033[0Ksection_start:{ts}:{section_id}{opts}\r\033[0K"
31
+
32
+
33
+ def section_end(section_id: str) -> str:
34
+ """Маркер конца схлопываемой секции GitLab CI."""
35
+ ts = int(time.time())
36
+ return f"\033[0Ksection_end:{ts}:{section_id}\r\033[0K\n"
37
+
38
+
39
+ def to_host_path(filepath: str, project_root: str, workdir_subpath: str) -> str:
40
+ """
41
+ Преобразовать путь в рабочей директории в путь на хостовой машине.
42
+
43
+ Если задан `HOST_PROJECT_ROOT_DIRECTORY`, к корню проекта на хосте
44
+ добавляются подпуть рабочей директории и относительный путь к файлу.
45
+ """
46
+ host_root = os.environ.get("HOST_PROJECT_ROOT_DIRECTORY")
47
+ if not host_root:
48
+ return filepath
49
+ try:
50
+ rel_path = os.path.relpath(filepath, project_root)
51
+ except ValueError:
52
+ return filepath
53
+ if rel_path.startswith(".."):
54
+ return filepath
55
+ return os.path.join(host_root, workdir_subpath, rel_path)
56
+
57
+
58
+ def to_relative_path(filepath: str, project_root: str) -> str:
59
+ """
60
+ Преобразовать путь внутри контейнера в путь относительно рабочей
61
+ директории (workdir) тестов — `project_root`.
62
+
63
+ Используется для блока с кодом из traceback: там удобнее видеть короткий
64
+ путь относительно workdir, а не абсолютный путь на хосте. Файлы вне
65
+ workdir (например, сторонние библиотеки) остаются нетронутыми
66
+ (абсолютный путь как есть).
67
+ """
68
+ try:
69
+ rel_path = os.path.relpath(filepath, project_root)
70
+ except ValueError:
71
+ return filepath
72
+ if rel_path.startswith(".."):
73
+ return filepath
74
+ return rel_path
75
+
76
+
77
+ def to_artifact_url(filepath: str, project_root: str, job_id: str,
78
+ url_pattern: str, workdir_subpath: str) -> Optional[str]:
79
+ """
80
+ Построить ссылку на прикреплённый артефакт GitLab CI (job_id известен —
81
+ сборка запущена именно в GitLab CI).
82
+
83
+ Паттерн задаётся переменной окружения PROJECT_ARTIFACT_PATTERN (см.
84
+ VedroStepTracer.artifact_url_pattern) с плейсхолдерами {job_id} и {path},
85
+ например:
86
+ "https://gitlab.example.com/group/SomeProject/-/jobs/{job_id}/artifacts/file/{path}"
87
+
88
+ Если job_id или паттерн не заданы (локальный запуск) — возвращает None,
89
+ и вызывающий код должен использовать локальную file:// ссылку.
90
+ """
91
+ if not job_id or not url_pattern:
92
+ return None
93
+ try:
94
+ rel_path = os.path.relpath(filepath, project_root)
95
+ except ValueError:
96
+ return None
97
+ if rel_path.startswith(".."):
98
+ return None
99
+ artifact_path = os.path.join(workdir_subpath, rel_path)
100
+ try:
101
+ return url_pattern.format(job_id=job_id, path=artifact_path)
102
+ except (KeyError, IndexError):
103
+ return None
104
+
105
+
106
+ def make_terminal_link(path: str) -> str:
107
+ """
108
+ Вернуть путь как file:// URI, чтобы терминал сам распознал его как
109
+ кликабельную ссылку на файл.
110
+
111
+ Изначально использовалась OSC 8 hyperlink escape-последовательность
112
+ (ESC]8;;URI ESC\\ TEXT ESC]8;;ESC\\), но репортёр vedro выводит
113
+ extra_details через rich/logging-конвейер, который не пропускает
114
+ "сырые" управляющие последовательности — в терминале виден только
115
+ обычный текст без ссылки.
116
+
117
+ Более надёжный кроссплатформенный вариант — просто вывести сам file://
118
+ URI: большинство терминалов (Terminal.app и iTerm2 на macOS; GNOME
119
+ Terminal, Konsole, kitty, VS Code на Linux) автоматически подсвечивают
120
+ и делают кликабельными строки, распознанные как URL, без необходимости
121
+ в OSC 8.
122
+ """
123
+ abs_path = os.path.abspath(path)
124
+ return "file://" + quote(abs_path, safe="/:")