persona-dsl 26.1.20.8__py3-none-any.whl

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 (86) hide show
  1. persona_dsl/__init__.py +35 -0
  2. persona_dsl/components/action.py +10 -0
  3. persona_dsl/components/base_step.py +251 -0
  4. persona_dsl/components/combined_step.py +68 -0
  5. persona_dsl/components/expectation.py +10 -0
  6. persona_dsl/components/fact.py +10 -0
  7. persona_dsl/components/goal.py +10 -0
  8. persona_dsl/components/ops.py +7 -0
  9. persona_dsl/components/step.py +75 -0
  10. persona_dsl/expectations/generic/__init__.py +15 -0
  11. persona_dsl/expectations/generic/contains_item.py +19 -0
  12. persona_dsl/expectations/generic/contains_the_text.py +15 -0
  13. persona_dsl/expectations/generic/has_entries.py +21 -0
  14. persona_dsl/expectations/generic/is_equal.py +24 -0
  15. persona_dsl/expectations/generic/is_greater_than.py +18 -0
  16. persona_dsl/expectations/generic/path_equal.py +27 -0
  17. persona_dsl/expectations/web/__init__.py +5 -0
  18. persona_dsl/expectations/web/is_displayed.py +13 -0
  19. persona_dsl/expectations/web/matches_aria_snapshot.py +221 -0
  20. persona_dsl/expectations/web/matches_screenshot.py +160 -0
  21. persona_dsl/generators/__init__.py +5 -0
  22. persona_dsl/generators/api_generator.py +423 -0
  23. persona_dsl/generators/cli.py +431 -0
  24. persona_dsl/generators/page_generator.py +1140 -0
  25. persona_dsl/ops/api/__init__.py +5 -0
  26. persona_dsl/ops/api/json_as.py +104 -0
  27. persona_dsl/ops/api/json_response.py +48 -0
  28. persona_dsl/ops/api/send_request.py +41 -0
  29. persona_dsl/ops/db/__init__.py +5 -0
  30. persona_dsl/ops/db/execute_sql.py +22 -0
  31. persona_dsl/ops/db/fetch_all.py +29 -0
  32. persona_dsl/ops/db/fetch_one.py +22 -0
  33. persona_dsl/ops/kafka/__init__.py +4 -0
  34. persona_dsl/ops/kafka/message_in_topic.py +89 -0
  35. persona_dsl/ops/kafka/send_message.py +35 -0
  36. persona_dsl/ops/soap/__init__.py +4 -0
  37. persona_dsl/ops/soap/call_operation.py +24 -0
  38. persona_dsl/ops/soap/operation_result.py +24 -0
  39. persona_dsl/ops/web/__init__.py +37 -0
  40. persona_dsl/ops/web/aria_snapshot.py +87 -0
  41. persona_dsl/ops/web/click.py +30 -0
  42. persona_dsl/ops/web/current_path.py +17 -0
  43. persona_dsl/ops/web/element_attribute.py +24 -0
  44. persona_dsl/ops/web/element_is_visible.py +27 -0
  45. persona_dsl/ops/web/element_text.py +28 -0
  46. persona_dsl/ops/web/elements_count.py +42 -0
  47. persona_dsl/ops/web/fill.py +41 -0
  48. persona_dsl/ops/web/generate_page_object.py +118 -0
  49. persona_dsl/ops/web/input_value.py +23 -0
  50. persona_dsl/ops/web/navigate.py +52 -0
  51. persona_dsl/ops/web/press_key.py +37 -0
  52. persona_dsl/ops/web/rich_aria_snapshot.py +146 -0
  53. persona_dsl/ops/web/screenshot.py +68 -0
  54. persona_dsl/ops/web/table_data.py +43 -0
  55. persona_dsl/ops/web/wait_for_navigation.py +23 -0
  56. persona_dsl/pages/__init__.py +133 -0
  57. persona_dsl/pages/elements.py +998 -0
  58. persona_dsl/pages/page.py +44 -0
  59. persona_dsl/pages/virtual_page.py +94 -0
  60. persona_dsl/persona.py +125 -0
  61. persona_dsl/pytest_plugin.py +1064 -0
  62. persona_dsl/runtime/dist/persona_bundle.js +1077 -0
  63. persona_dsl/skills/__init__.py +7 -0
  64. persona_dsl/skills/core/base.py +41 -0
  65. persona_dsl/skills/core/skill_definition.py +30 -0
  66. persona_dsl/skills/use_api.py +251 -0
  67. persona_dsl/skills/use_browser.py +78 -0
  68. persona_dsl/skills/use_database.py +129 -0
  69. persona_dsl/skills/use_kafka.py +135 -0
  70. persona_dsl/skills/use_soap.py +66 -0
  71. persona_dsl/utils/__init__.py +0 -0
  72. persona_dsl/utils/artifacts.py +22 -0
  73. persona_dsl/utils/config.py +54 -0
  74. persona_dsl/utils/data_providers.py +159 -0
  75. persona_dsl/utils/decorators.py +80 -0
  76. persona_dsl/utils/metrics.py +69 -0
  77. persona_dsl/utils/naming.py +14 -0
  78. persona_dsl/utils/path.py +202 -0
  79. persona_dsl/utils/retry.py +51 -0
  80. persona_dsl/utils/taas_integration.py +124 -0
  81. persona_dsl/utils/waits.py +112 -0
  82. persona_dsl-26.1.20.8.dist-info/METADATA +35 -0
  83. persona_dsl-26.1.20.8.dist-info/RECORD +86 -0
  84. persona_dsl-26.1.20.8.dist-info/WHEEL +5 -0
  85. persona_dsl-26.1.20.8.dist-info/entry_points.txt +6 -0
  86. persona_dsl-26.1.20.8.dist-info/top_level.txt +1 -0
@@ -0,0 +1,44 @@
1
+ from typing import Optional, Dict
2
+
3
+ from .elements import Element
4
+
5
+
6
+ class Page(Element):
7
+ """
8
+ Представляет страницу как корневой контейнер для элементов.
9
+ """
10
+
11
+ # LCL-39: Default query params
12
+ default_query_params: Dict[str, str] = {}
13
+ expected_path: Optional[str] = None
14
+
15
+ def __init__(self, **url_params: str) -> None:
16
+ super().__init__(name="page", role="document", accessible_name="page")
17
+ self._url_params = url_params
18
+
19
+ def build_url(self, base_url: str | None = None) -> str:
20
+ """
21
+ Собирает полный URL: expected_path + query.
22
+ """
23
+ if not self.expected_path:
24
+ raise ValueError(f"Page {self.__class__.__name__} не имеет expected_path.")
25
+
26
+ from urllib.parse import urlencode, urljoin
27
+
28
+ # Merge params: default < explicit
29
+ params = self.default_query_params.copy()
30
+ params.update(self._url_params)
31
+
32
+ path = self.expected_path
33
+ if params:
34
+ query = urlencode(params)
35
+ path = f"{path}?{query}"
36
+
37
+ if base_url:
38
+ return urljoin(base_url, path)
39
+
40
+ return path
41
+
42
+ def __repr__(self) -> str:
43
+ elements_list = list(self._elements.keys()) if self._elements else []
44
+ return f"<Page elements={elements_list}>"
@@ -0,0 +1,94 @@
1
+ from typing import Any, Dict
2
+
3
+ from persona_dsl.pages.elements import Element
4
+
5
+ # NOTE: PageGenerator is only needed for type checking to avoid circular imports
6
+ from typing import TYPE_CHECKING
7
+
8
+ if TYPE_CHECKING:
9
+ pass
10
+
11
+
12
+ class VirtualPage:
13
+ """
14
+ Динамическая страница, построенная на основе ARIA-снепшота (dict) в памяти.
15
+ Позволяет обращаться к элементам по структуре (page.banner.button) без генерации файла.
16
+ """
17
+
18
+ def __init__(self, snapshot: Dict[str, Any]):
19
+ from persona_dsl.generators.page_generator import PageGenerator
20
+
21
+ self._snapshot = snapshot
22
+ # Используем генератор для получения маппинга ролей на классы,
23
+ # но логику построения реализуем упрощенно для runtime-объектов
24
+ self._generator = PageGenerator()
25
+ self._root = self._build_tree(snapshot)
26
+
27
+ @property
28
+ def root(self) -> Element:
29
+ return self._root
30
+
31
+ def _sanitize_name(self, name: str) -> str:
32
+ return self._generator._sanitize_name(name)
33
+
34
+ def _build_tree(self, node: Dict[str, Any]) -> Element:
35
+ role = node.get("role", "generic")
36
+ name = node.get("name", "")
37
+ ref = node.get("ref")
38
+
39
+ # Определяем класс элемента
40
+ cls = self._generator._ROLE_TO_CLASS.get(role, Element)
41
+
42
+ # Создаем инстанс
43
+ # Мы не знаем точное имя переменной здесь, так как оно зависит от контекста родителя.
44
+ # Используем роль/имя как временное имя.
45
+ base_name = self._sanitize_name(name) or role
46
+
47
+ kwargs: Dict[str, Any] = {
48
+ "name": base_name,
49
+ "accessible_name": name,
50
+ "aria_ref": ref,
51
+ }
52
+ # Если это базовый класс Element, ему нужно передать role явно.
53
+ # Если это подкласс (Button, Link и т.д.), у них role обычно init=False и задан дефолтом.
54
+ if cls is Element:
55
+ kwargs["role"] = role
56
+
57
+ el = cls(**kwargs)
58
+
59
+ # Обрабатываем детей
60
+ children = node.get("children", [])
61
+
62
+ # Для именования детей нужно следить за уникальностью в рамках этого родителя
63
+ used_names = set()
64
+
65
+ for child_node in children:
66
+ child_el = self._build_tree(child_node)
67
+
68
+ # Придумываем имя для ребенка
69
+ child_role = child_node.get("role", "element")
70
+ child_aria_name = child_node.get("name", "")
71
+
72
+ var_name_base = self._sanitize_name(child_aria_name) or child_role
73
+
74
+ # Уникализация
75
+ final_name = var_name_base
76
+ i = 1
77
+ while final_name in used_names or hasattr(el, final_name):
78
+ final_name = f"{var_name_base}_{i}"
79
+ i += 1
80
+
81
+ used_names.add(final_name)
82
+
83
+ # Обновляем имя элемента
84
+ child_el.name = final_name
85
+
86
+ # Добавляем в родителя
87
+ # Используем add_element, чтобы прописались parent ссылки и _elements
88
+ el.add_element(child_el, alias=final_name)
89
+
90
+ return el
91
+
92
+ def __getattr__(self, name: str) -> Element:
93
+ # Делегируем доступ к корневому элементу
94
+ return getattr(self._root, name)
persona_dsl/persona.py ADDED
@@ -0,0 +1,125 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Callable, Dict, Optional
4
+ from enum import Enum
5
+
6
+
7
+ # from .skills.core.skill_definition import SkillId # Removed for lazy loading
8
+ from .utils.path import extract_by_path
9
+
10
+
11
+ class BaseRole(str, Enum):
12
+ """Базовый класс для Enum'а с ролями, специфичными для проекта."""
13
+
14
+ pass
15
+
16
+
17
+ class Persona:
18
+ def __init__(self, name: str = "Персона"):
19
+ self.name = name
20
+ self.skills: Dict[str, Dict[str, Any]] = {}
21
+ self.memory: Dict[str, Any] = {}
22
+ self._skill_provider: Optional[Callable[["Persona", Any, str], Any]] = None
23
+ self.role_id: str = "default"
24
+ self.params: Dict[str, Any] = {}
25
+ self.secrets: Dict[str, Any] = {}
26
+ self.retries_config: Dict[str, Any] = {}
27
+
28
+ def __str__(self) -> str:
29
+ return self.name
30
+
31
+ @classmethod
32
+ def who_can(cls, *skills: Any) -> "Persona":
33
+ persona = cls()
34
+ persona.learn(*skills)
35
+ return persona
36
+
37
+ def learn(self, *skills_to_learn: Any) -> "Persona":
38
+ """Наделяет Персону одним или несколькими навыками."""
39
+ for skill_type, name, skill_instance in skills_to_learn:
40
+ if skill_type not in self.skills:
41
+ self.skills[skill_type] = {}
42
+ self.skills[skill_type][name] = skill_instance
43
+ return self
44
+
45
+ def make(self, *goals: Any) -> "Persona":
46
+ if not goals:
47
+ raise ValueError("Persona.make requires at least one goal.")
48
+ for goal in goals:
49
+ goal.make(self)
50
+ return self
51
+
52
+ def set_skill_provider(
53
+ self, provider: Callable[["Persona", Any, str], Any]
54
+ ) -> None:
55
+ """Регистрирует фабрику для ленивого создания навыков."""
56
+ self._skill_provider = provider
57
+
58
+ def param(self, path: str) -> Any:
59
+ """Извлекает параметр роли по dot/bracket-пути."""
60
+ return extract_by_path(self.params, path, allow_attr=False)
61
+
62
+ def secret(self, path: str) -> Any:
63
+ """Извлекает секрет роли по dot/bracket-пути."""
64
+ return extract_by_path(self.secrets, path, allow_attr=False)
65
+
66
+ def ctx(self, path: str) -> Any:
67
+ """
68
+ Универсальный метод для извлечения данных из контекста персоны (params, secrets, memory).
69
+ Приоритет: params -> secrets -> memory.
70
+ """
71
+ # Попробуем извлечь из params
72
+ try:
73
+ return extract_by_path(self.params, path, allow_attr=False)
74
+ except (KeyError, IndexError, TypeError):
75
+ pass
76
+
77
+ # Попробуем извлечь из secrets
78
+ try:
79
+ return extract_by_path(self.secrets, path, allow_attr=False)
80
+ except (KeyError, IndexError, TypeError):
81
+ pass
82
+
83
+ # Если нигде не нашли, извлекаем из memory (с жёсткой ошибкой, если там тоже нет)
84
+ try:
85
+ return extract_by_path(self.memory, path, allow_attr=False)
86
+ except (KeyError, IndexError, TypeError) as e:
87
+ raise KeyError(
88
+ f"Путь '{path}' не найден в контексте персоны (params, secrets, memory)"
89
+ ) from e
90
+
91
+ def skill(self, skill_type: Any, name: str = "default") -> Any:
92
+ """
93
+ Возвращает экземпляр навыка по его типу и, опционально, имени.
94
+ Если навык не был изучен ранее, пытается создать его с помощью skill_provider (если он есть).
95
+
96
+ Args:
97
+ skill_type: Тип навыка (член SkillId).
98
+ name: Имя навыка. Если не указано, используется 'default'.
99
+ """
100
+ # skill_type might be SkillId enum or a string-like object
101
+ skill_type_str = getattr(skill_type, "value", str(skill_type))
102
+ skill_name_str = name
103
+
104
+ # 1. Проверяем, изучен ли уже навык
105
+ if (
106
+ skill_type_str in self.skills
107
+ and skill_name_str in self.skills[skill_type_str]
108
+ ):
109
+ return self.skills[skill_type_str][skill_name_str]
110
+
111
+ # 2. Если нет — пытаемся создать через провайдер
112
+ if self._skill_provider:
113
+ skill_instance = self._skill_provider(self, skill_type, name)
114
+ self.learn((skill_type_str, name, skill_instance))
115
+ return skill_instance
116
+
117
+ # 3. Если провайдера нет или он не смог — стандартная ошибка
118
+ if skill_type_str not in self.skills:
119
+ raise NameError(
120
+ f"У Персоны нет навыков типа '{skill_type_str}'. Доступные типы: {list(self.skills.keys())}"
121
+ )
122
+
123
+ raise NameError(
124
+ f"У Персоны нет навыка '{skill_name_str}' для типа '{skill_type_str}'. Доступные: {list(self.skills[skill_type_str].keys())}"
125
+ )