behave-model 1.0.0__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 (39) hide show
  1. behave_model/__init__.py +164 -0
  2. behave_model/exceptions.py +28 -0
  3. behave_model/model/__init__.py +34 -0
  4. behave_model/model/background.py +39 -0
  5. behave_model/model/comment.py +23 -0
  6. behave_model/model/docstring.py +29 -0
  7. behave_model/model/examples.py +44 -0
  8. behave_model/model/feature.py +113 -0
  9. behave_model/model/location.py +30 -0
  10. behave_model/model/metadata.py +23 -0
  11. behave_model/model/project.py +208 -0
  12. behave_model/model/rule.py +83 -0
  13. behave_model/model/scenario.py +56 -0
  14. behave_model/model/scenario_outline.py +69 -0
  15. behave_model/model/step.py +57 -0
  16. behave_model/model/table.py +93 -0
  17. behave_model/model/tag.py +31 -0
  18. behave_model/parser/__init__.py +13 -0
  19. behave_model/parser/adapter.py +240 -0
  20. behave_model/parser/loader.py +82 -0
  21. behave_model/parser/parser.py +46 -0
  22. behave_model/queries/__init__.py +25 -0
  23. behave_model/queries/query.py +155 -0
  24. behave_model/serializers/__init__.py +11 -0
  25. behave_model/serializers/dict_serializer.py +165 -0
  26. behave_model/serializers/json_serializer.py +41 -0
  27. behave_model/serializers/pretty_printer.py +191 -0
  28. behave_model/transformations/__init__.py +23 -0
  29. behave_model/transformations/transform.py +192 -0
  30. behave_model/utils/__init__.py +15 -0
  31. behave_model/utils/utils.py +29 -0
  32. behave_model/validation/__init__.py +23 -0
  33. behave_model/validation/validator.py +228 -0
  34. behave_model/visitors/__init__.py +13 -0
  35. behave_model/visitors/visitor.py +142 -0
  36. behave_model-1.0.0.dist-info/METADATA +273 -0
  37. behave_model-1.0.0.dist-info/RECORD +39 -0
  38. behave_model-1.0.0.dist-info/WHEEL +4 -0
  39. behave_model-1.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,164 @@
1
+ """behave-model — Canonical object model for Behave projects.
2
+
3
+ Public API:
4
+
5
+ from behave_model import (
6
+ load_project,
7
+ load_feature,
8
+ Project,
9
+ Feature,
10
+ Scenario,
11
+ ScenarioOutline,
12
+ Step,
13
+ Table,
14
+ Tag,
15
+ Background,
16
+ Examples,
17
+ DocString,
18
+ Comment,
19
+ Location,
20
+ Metadata,
21
+ Visitor,
22
+ Validator,
23
+ DictSerializer,
24
+ JsonSerializer,
25
+ PrettyPrinter,
26
+ )
27
+
28
+ project = load_project("features/")
29
+ print(project.statistics())
30
+ """
31
+
32
+ from behave_model.exceptions import (
33
+ BehaveModelError,
34
+ ParseError,
35
+ SerializationError,
36
+ TransformationError,
37
+ ValidationError,
38
+ )
39
+ from behave_model.model import (
40
+ Background,
41
+ Comment,
42
+ DocString,
43
+ Examples,
44
+ Feature,
45
+ Location,
46
+ Metadata,
47
+ Project,
48
+ Rule,
49
+ Scenario,
50
+ ScenarioOutline,
51
+ Step,
52
+ Table,
53
+ TableRow,
54
+ Tag,
55
+ )
56
+ from behave_model.parser import (
57
+ BehaveParserAdapter,
58
+ load_feature,
59
+ load_project,
60
+ parse_feature,
61
+ parse_project,
62
+ )
63
+ from behave_model.queries import (
64
+ find_feature,
65
+ find_features_with_tag,
66
+ find_outlines,
67
+ find_plain_scenarios,
68
+ find_scenarios,
69
+ find_scenarios_with_tag,
70
+ find_steps,
71
+ find_tag,
72
+ )
73
+ from behave_model.serializers import DictSerializer, JsonSerializer, PrettyPrinter
74
+ from behave_model.transformations import (
75
+ add_tag_to_feature,
76
+ normalize_whitespace,
77
+ remove_tag,
78
+ rename_scenario,
79
+ rename_tag,
80
+ sort_features,
81
+ sort_scenarios,
82
+ sort_tags,
83
+ )
84
+ from behave_model.validation import (
85
+ DuplicateFeatureNamesRule,
86
+ DuplicateScenarioNamesRule,
87
+ EmptyFeatureRule,
88
+ EmptyScenarioRule,
89
+ InvalidTableRule,
90
+ ValidationIssue,
91
+ ValidationRule,
92
+ Validator,
93
+ )
94
+ from behave_model.visitors import CollectingVisitor, CountingVisitor, Visitor
95
+
96
+ __version__ = "0.1.0"
97
+
98
+ __all__ = [
99
+ # model
100
+ "Background",
101
+ "Comment",
102
+ "DocString",
103
+ "Examples",
104
+ "Feature",
105
+ "Location",
106
+ "Metadata",
107
+ "Project",
108
+ "Rule",
109
+ "Scenario",
110
+ "ScenarioOutline",
111
+ "Step",
112
+ "Table",
113
+ "TableRow",
114
+ "Tag",
115
+ # parser
116
+ "BehaveParserAdapter",
117
+ "load_feature",
118
+ "load_project",
119
+ "parse_feature",
120
+ "parse_project",
121
+ # queries
122
+ "find_feature",
123
+ "find_features_with_tag",
124
+ "find_outlines",
125
+ "find_plain_scenarios",
126
+ "find_scenarios",
127
+ "find_scenarios_with_tag",
128
+ "find_steps",
129
+ "find_tag",
130
+ # visitors
131
+ "CollectingVisitor",
132
+ "CountingVisitor",
133
+ "Visitor",
134
+ # serializers
135
+ "DictSerializer",
136
+ "JsonSerializer",
137
+ "PrettyPrinter",
138
+ # transformations
139
+ "add_tag_to_feature",
140
+ "normalize_whitespace",
141
+ "remove_tag",
142
+ "rename_scenario",
143
+ "rename_tag",
144
+ "sort_features",
145
+ "sort_scenarios",
146
+ "sort_tags",
147
+ # validation
148
+ "DuplicateFeatureNamesRule",
149
+ "DuplicateScenarioNamesRule",
150
+ "EmptyFeatureRule",
151
+ "EmptyScenarioRule",
152
+ "InvalidTableRule",
153
+ "ValidationIssue",
154
+ "ValidationRule",
155
+ "Validator",
156
+ # exceptions
157
+ "BehaveModelError",
158
+ "ParseError",
159
+ "SerializationError",
160
+ "TransformationError",
161
+ "ValidationError",
162
+ # version
163
+ "__version__",
164
+ ]
@@ -0,0 +1,28 @@
1
+ """Exception hierarchy for behave-model."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ class BehaveModelError(Exception):
7
+ """Base exception for all behave-model errors."""
8
+
9
+
10
+ class ParseError(BehaveModelError):
11
+ """Raised when a feature file cannot be parsed."""
12
+
13
+
14
+ class ValidationError(BehaveModelError):
15
+ """Raised when a validation rule fails."""
16
+
17
+ def __init__(self, message: str, *, rule: str = "", location: str = "") -> None:
18
+ super().__init__(message)
19
+ self.rule = rule
20
+ self.location = location
21
+
22
+
23
+ class TransformationError(BehaveModelError):
24
+ """Raised when a transformation cannot be applied."""
25
+
26
+
27
+ class SerializationError(BehaveModelError):
28
+ """Raised when serialization fails."""
@@ -0,0 +1,34 @@
1
+ """Domain model package — re-exports all model classes."""
2
+
3
+ from behave_model.model.background import Background
4
+ from behave_model.model.comment import Comment
5
+ from behave_model.model.docstring import DocString
6
+ from behave_model.model.examples import Examples
7
+ from behave_model.model.feature import Feature
8
+ from behave_model.model.location import Location
9
+ from behave_model.model.metadata import Metadata
10
+ from behave_model.model.project import Project
11
+ from behave_model.model.rule import Rule
12
+ from behave_model.model.scenario import Scenario
13
+ from behave_model.model.scenario_outline import ScenarioOutline
14
+ from behave_model.model.step import Step
15
+ from behave_model.model.table import Table, TableRow
16
+ from behave_model.model.tag import Tag
17
+
18
+ __all__ = [
19
+ "Background",
20
+ "Comment",
21
+ "DocString",
22
+ "Examples",
23
+ "Feature",
24
+ "Location",
25
+ "Metadata",
26
+ "Project",
27
+ "Rule",
28
+ "Scenario",
29
+ "ScenarioOutline",
30
+ "Step",
31
+ "Table",
32
+ "TableRow",
33
+ "Tag",
34
+ ]
@@ -0,0 +1,39 @@
1
+ """Background model."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import TYPE_CHECKING
7
+
8
+ from behave_model.model.location import Location
9
+ from behave_model.model.step import Step
10
+
11
+ if TYPE_CHECKING:
12
+ from behave_model.visitors.visitor import Visitor
13
+
14
+
15
+ @dataclass
16
+ class Background:
17
+ """A Background block that runs before every scenario in a feature.
18
+
19
+ Attributes:
20
+ name: Optional background name.
21
+ steps: Steps that execute before each scenario.
22
+ location: Source location.
23
+ """
24
+
25
+ name: str = ""
26
+ steps: list[Step] = field(default_factory=list)
27
+ location: Location = field(default_factory=Location)
28
+
29
+ def __iter__(self):
30
+ return iter(self.steps)
31
+
32
+ def __len__(self) -> int:
33
+ return len(self.steps)
34
+
35
+ def accept(self, visitor: "Visitor") -> None:
36
+ """Accept a visitor."""
37
+ visitor.visit_background(self)
38
+ for step in self.steps:
39
+ step.accept(visitor)
@@ -0,0 +1,23 @@
1
+ """Comment model preserving source-file comments."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+ from behave_model.model.location import Location
8
+
9
+
10
+ @dataclass
11
+ class Comment:
12
+ """A comment extracted from a feature file.
13
+
14
+ Attributes:
15
+ text: The raw comment text including the ``#`` prefix.
16
+ location: Source location of the comment.
17
+ """
18
+
19
+ text: str
20
+ location: Location = Location()
21
+
22
+ def __str__(self) -> str:
23
+ return self.text
@@ -0,0 +1,29 @@
1
+ """DocString (multi-line text) model."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+
7
+ from behave_model.model.location import Location
8
+
9
+
10
+ @dataclass
11
+ class DocString:
12
+ """A docstring attached to a step.
13
+
14
+ Attributes:
15
+ content: The raw text content.
16
+ content_type: Optional content-type hint (e.g. ``json``, ``xml``).
17
+ delimiter: The delimiter used (``\"\"\"`` or `````).
18
+ location: Source location.
19
+ """
20
+
21
+ content: str = ""
22
+ content_type: str = ""
23
+ delimiter: str = '"""'
24
+ location: Location = field(default_factory=Location)
25
+
26
+ @property
27
+ def lines(self) -> list[str]:
28
+ """Return the content split into lines."""
29
+ return self.content.splitlines()
@@ -0,0 +1,44 @@
1
+ """Examples model for scenario outlines."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import TYPE_CHECKING
7
+
8
+ from behave_model.model.location import Location
9
+ from behave_model.model.table import Table
10
+ from behave_model.model.tag import Tag
11
+
12
+ if TYPE_CHECKING:
13
+ from behave_model.visitors.visitor import Visitor
14
+
15
+
16
+ @dataclass
17
+ class Examples:
18
+ """An Examples table attached to a ScenarioOutline.
19
+
20
+ Attributes:
21
+ name: Optional name for the examples block.
22
+ tags: Tags on the examples block.
23
+ table: The data table containing headers and rows.
24
+ location: Source location.
25
+ """
26
+
27
+ name: str = ""
28
+ tags: list[Tag] = field(default_factory=list)
29
+ table: Table = field(default_factory=Table)
30
+ location: Location = field(default_factory=Location)
31
+
32
+ @property
33
+ def headers(self) -> list[str]:
34
+ """Column headers from the underlying table."""
35
+ return self.table.headers
36
+
37
+ @property
38
+ def rows(self) -> list:
39
+ """Data rows from the underlying table."""
40
+ return self.table.rows
41
+
42
+ def accept(self, visitor: "Visitor") -> None:
43
+ """Accept a visitor."""
44
+ visitor.visit_examples(self)
@@ -0,0 +1,113 @@
1
+ """Feature model."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Iterator, Union
7
+ from typing import TYPE_CHECKING
8
+
9
+ from behave_model.model.background import Background
10
+ from behave_model.model.comment import Comment
11
+ from behave_model.model.location import Location
12
+ from behave_model.model.rule import Rule
13
+ from behave_model.model.scenario import Scenario
14
+ from behave_model.model.scenario_outline import ScenarioOutline
15
+ from behave_model.model.tag import Tag
16
+
17
+ if TYPE_CHECKING:
18
+ from behave_model.visitors.visitor import Visitor
19
+
20
+ # Union type for any scenario-like element
21
+ ScenarioLike = Union[Scenario, ScenarioOutline]
22
+
23
+
24
+ @dataclass
25
+ class Feature:
26
+ """A feature parsed from a ``.feature`` file.
27
+
28
+ Attributes:
29
+ name: Feature name.
30
+ description: Optional description text (multi-line).
31
+ tags: Tags applied at the feature level.
32
+ background: Optional Background block (feature-level).
33
+ scenarios: List of Scenario and ScenarioOutline objects directly
34
+ under the feature (not inside a Rule).
35
+ rules: List of Rule blocks (Gherkin v6).
36
+ comments: Comments associated with the feature.
37
+ location: Source location.
38
+ language: Feature file language (default ``en``).
39
+ """
40
+
41
+ name: str = ""
42
+ description: str = ""
43
+ tags: list[Tag] = field(default_factory=list)
44
+ background: Background | None = None
45
+ scenarios: list[ScenarioLike] = field(default_factory=list)
46
+ rules: list[Rule] = field(default_factory=list)
47
+ comments: list[Comment] = field(default_factory=list)
48
+ location: Location = field(default_factory=Location)
49
+ language: str = "en"
50
+
51
+ def __iter__(self) -> Iterator[ScenarioLike]:
52
+ return iter(self.scenarios)
53
+
54
+ def __len__(self) -> int:
55
+ return len(self.scenarios)
56
+
57
+ @property
58
+ def tag_names(self) -> list[str]:
59
+ """Return tag names as strings."""
60
+ return [t.name for t in self.tags]
61
+
62
+ def has_tag(self, name: str) -> bool:
63
+ """Check whether the feature has a given tag."""
64
+ return any(t.name == name for t in self.tags)
65
+
66
+ def outlines(self) -> list[ScenarioOutline]:
67
+ """Return only scenario outlines."""
68
+ return [s for s in self.scenarios if isinstance(s, ScenarioOutline)]
69
+
70
+ def plain_scenarios(self) -> list[Scenario]:
71
+ """Return only plain (non-outline) scenarios."""
72
+ return [s for s in self.scenarios if isinstance(s, Scenario)]
73
+
74
+ def all_scenarios(self) -> list[ScenarioLike]:
75
+ """Return all scenario-like elements (including those inside rules)."""
76
+ result = list(self.scenarios)
77
+ for rule in self.rules:
78
+ result.extend(rule.scenarios)
79
+ return result
80
+
81
+ def all_tags(self) -> list[Tag]:
82
+ """Return all tags (feature + rule + scenario tags)."""
83
+ tags = list(self.tags)
84
+ for s in self.scenarios:
85
+ tags.extend(s.tags)
86
+ for rule in self.rules:
87
+ tags.extend(rule.tags)
88
+ for s in rule.scenarios:
89
+ tags.extend(s.tags)
90
+ return tags
91
+
92
+ def all_steps(self):
93
+ """Return every step in the feature (background + scenarios + rules)."""
94
+ from behave_model.model.step import Step
95
+
96
+ steps: list[Step] = []
97
+ if self.background:
98
+ steps.extend(self.background.steps)
99
+ for s in self.scenarios:
100
+ steps.extend(s.steps)
101
+ for rule in self.rules:
102
+ steps.extend(rule.all_steps())
103
+ return steps
104
+
105
+ def accept(self, visitor: "Visitor") -> None:
106
+ """Accept a visitor."""
107
+ visitor.visit_feature(self)
108
+ if self.background:
109
+ self.background.accept(visitor)
110
+ for s in self.scenarios:
111
+ s.accept(visitor)
112
+ for rule in self.rules:
113
+ rule.accept(visitor)
@@ -0,0 +1,30 @@
1
+ """Location model representing a position in a source file."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class Location:
10
+ """Represents a position in a feature file.
11
+
12
+ Attributes:
13
+ filename: Path to the source file.
14
+ line: 1-based line number.
15
+ column: 1-based column number (0 if unknown).
16
+ """
17
+
18
+ filename: str = ""
19
+ line: int = 0
20
+ column: int = 0
21
+
22
+ def __str__(self) -> str:
23
+ parts: list[str] = []
24
+ if self.filename:
25
+ parts.append(self.filename)
26
+ if self.line:
27
+ parts.append(f"line {self.line}")
28
+ if self.column:
29
+ parts.append(f"column {self.column}")
30
+ return ", ".join(parts) if parts else "<unknown>"
@@ -0,0 +1,23 @@
1
+ """Metadata model for project-level information."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from datetime import datetime
7
+
8
+
9
+ @dataclass
10
+ class Metadata:
11
+ """Project-level metadata.
12
+
13
+ Attributes:
14
+ created_at: When the project model was created.
15
+ source_path: Root path from which the project was loaded.
16
+ tool_version: Version of behave-model that produced this metadata.
17
+ extra: Free-form key-value pairs for extensions.
18
+ """
19
+
20
+ created_at: datetime = field(default_factory=datetime.now)
21
+ source_path: str = ""
22
+ tool_version: str = "0.1.0"
23
+ extra: dict[str, str] = field(default_factory=dict)