ara-cli 0.1.9.51__py3-none-any.whl → 0.1.9.53__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.

Potentially problematic release.


This version of ara-cli might be problematic. Click here for more details.

ara_cli/artefact.py DELETED
@@ -1,172 +0,0 @@
1
- from __future__ import annotations
2
- from dataclasses import dataclass, field
3
- from typing import Literal, Optional
4
- from ara_cli.classifier import Classifier
5
- import re
6
- import os
7
-
8
- @dataclass
9
- class Artefact:
10
- classifier: Literal(Classifier.ordered_classifiers())
11
- name: str
12
- _content: str | None = None
13
- _parent: Artefact | None = field(init=False, default=None)
14
- _file_path: Optional[str] = None
15
- _tags: set[str] | None = None
16
-
17
- def assemble_content(self):
18
- def replace_parent(content):
19
- parent = self.parent
20
- if parent is None:
21
- return content
22
- parent_name = parent.name
23
- parent_classifier = parent.classifier
24
- parent_title = Classifier.get_artefact_title(parent_classifier)
25
- parent_string = f"{parent_name} {parent_title}"
26
-
27
- contribute_string = "Contributes to"
28
- illustrate_string = "Illustrates"
29
-
30
- regex_pattern = f'(?m)^(.*(?:{contribute_string}|{illustrate_string}))(.*)$'
31
-
32
- def replacement_function(match):
33
- return f"{match.group(1)} {parent_string}"
34
-
35
- def create_contributes(content):
36
- own_classifier_title = Classifier.get_artefact_title(self.classifier)
37
- own_title_line = f"{own_classifier_title}: {self.name}"
38
- contribution = contribute_string
39
- if self.classifier == 'example':
40
- contribution = illustrate_string
41
- parent_line = f"{contribution} {parent_string}"
42
- lines = content.splitlines()
43
- for i, line in enumerate(lines):
44
- if line.startswith(own_title_line):
45
- lines.insert(i + 1, "")
46
- lines.insert(i + 2, parent_line)
47
- break
48
- content = '\n'.join(lines)
49
- return content
50
-
51
- if not re.search(regex_pattern, content):
52
- return create_contributes(content)
53
-
54
- return re.sub(regex_pattern, replacement_function, content, count=1)
55
-
56
- def replace_tags(content):
57
- tag_set = self.tags
58
- if tag_set is None or not tag_set:
59
- return content
60
- tags = ' '.join(sorted([f'@{tag}' for tag in tag_set]))
61
-
62
- lines = content.splitlines()
63
- if lines and lines[0].startswith('@'):
64
- lines[0] = tags
65
- return '\n'.join(lines)
66
- lines.insert(0, tags)
67
- return '\n'.join(lines)
68
-
69
- content = self.content
70
- content = replace_tags(content)
71
- content = replace_parent(content)
72
- self._content = content
73
-
74
- @property
75
- def file_name(self) -> str:
76
- basename = os.path.basename(self.file_path)
77
- return os.path.splitext(basename)[0]
78
-
79
- @property
80
- def content(self) -> str:
81
- if self._content is not None:
82
- return self._content
83
- with open(self.file_path, 'r') as file:
84
- self._content = file.read()
85
- return self._content
86
-
87
- @property
88
- def parent(self) -> Artefact | None:
89
- if self._parent is not None:
90
- return self._parent
91
-
92
- if self.content is None:
93
- with open(self.file_path, 'r') as file:
94
- self.content = file.read()
95
-
96
- artefact_titles = Classifier.artefact_titles()
97
- title_segment = '|'.join(artefact_titles)
98
-
99
- regex_pattern = rf'(?:Contributes to|Illustrates)\s*:*\s*(.*)\s+({title_segment}).*'
100
- regex = re.compile(regex_pattern)
101
- match = re.search(regex, self.content)
102
-
103
- if match:
104
- parent_name = match.group(1).strip()
105
- parent_name = parent_name.replace(' ', '_')
106
- parent_title = match.group(2).strip()
107
- parent_type = Classifier.get_artefact_classifier(parent_title)
108
- self._parent = Artefact(classifier=parent_type, name=parent_name)
109
-
110
- return self._parent
111
-
112
- @property
113
- def file_path(self) -> str:
114
- if self._file_path is None:
115
- sub_directory = Classifier.get_sub_directory(self.classifier)
116
- underscore_name = self.name.replace(' ', '_')
117
- self._file_path = f"{sub_directory}/{underscore_name}.{self.classifier}"
118
- return self._file_path
119
-
120
- @property
121
- def tags(self) -> set[str]:
122
- if self._tags is not None:
123
- return self._tags
124
-
125
- if self.content is None:
126
- return set()
127
-
128
- lines = self.content.splitlines()
129
- first_line = lines[0].strip() if lines else ""
130
-
131
- if not first_line.startswith('@'):
132
- self._tags = set()
133
- return self._tags
134
-
135
- self._tags = {tag[1:] for tag in first_line.split() if tag.startswith('@')}
136
- return self._tags
137
-
138
- @classmethod
139
- def from_content(cls, content: str, file_path: str | None = None) -> Artefact:
140
- """
141
- Create an Artefact object from the given content.
142
- """
143
- error_message = "Content does not contain valid artefact information"
144
-
145
- if content is None:
146
- raise ValueError(error_message)
147
-
148
- artefact_titles = Classifier.artefact_titles()
149
- title_segment = '|'.join(artefact_titles)
150
-
151
- regex_pattern = rf'({title_segment})\s*:*\s*(.*)\s*'
152
- regex = re.compile(regex_pattern)
153
- match = re.search(regex, content)
154
-
155
- if not match:
156
- raise ValueError(error_message)
157
-
158
- title = match.group(1).strip()
159
- classifier = Classifier.get_artefact_classifier(title)
160
- name = match.group(2).strip()
161
-
162
- return cls(classifier=classifier, name=name, _content=content, _file_path=file_path)
163
-
164
- def write_to_file(self):
165
- if self.content is None:
166
- raise ValueError("Artefact object does not contain content information")
167
- self.assemble_content()
168
- file_path = self.file_path
169
- data_directory = f"{os.path.splitext(file_path)[0]}.data"
170
- os.makedirs(data_directory, exist_ok=True)
171
- with open(self.file_path, 'w') as file:
172
- file.write(self.content)
@@ -1,304 +0,0 @@
1
- import pytest
2
- import os
3
- from unittest.mock import patch, MagicMock, mock_open
4
- from ara_cli.artefact import Artefact
5
-
6
- mock_artefact_titles = ['Document', 'Report']
7
- mock_ordered_classifiers = ['Document', 'Report']
8
- mock_sub_directories = {
9
- 'Document': 'documents',
10
- 'Report': 'reports'
11
- }
12
-
13
-
14
- @pytest.fixture
15
- def mock_classifier():
16
- with patch('ara_cli.artefact.Classifier') as MockClassifier:
17
- MockClassifier.artefact_titles.return_value = mock_artefact_titles
18
- MockClassifier.ordered_classifiers.return_value = mock_ordered_classifiers
19
- MockClassifier.get_artefact_classifier.side_effect = lambda x: 'Type1' if x == 'Document' else 'Type2'
20
- MockClassifier.get_sub_directory.side_effect = lambda x: mock_sub_directories[x]
21
- yield MockClassifier
22
-
23
-
24
- @pytest.mark.parametrize(
25
- "classifier, name, content, expected_content",
26
- [
27
- ('Document', 'Test Artefact', 'Document: Test Artefact\nSome content',
28
- 'Document: Test Artefact\n\nContributes to Parent_Artefact Document\nSome content'),
29
-
30
- ('Document', 'Test Artefact', '@a_tag\nDocument: Test Artefact\nSome content',
31
- '@a_tag\nDocument: Test Artefact\n\nContributes to Parent_Artefact Document\nSome content'),
32
-
33
- ('example', 'Test Artefact', 'Example: Test Artefact\nSome content',
34
- 'Example: Test Artefact\n\nIllustrates Parent_Artefact Document\nSome content'),
35
-
36
- ('Document', 'Test Artefact', 'Some content without title line',
37
- 'Some content without title line'),
38
- ]
39
- )
40
- def test_create_contributes(mock_classifier, classifier, name, content, expected_content):
41
- artefact = Artefact(classifier=classifier, name=name, _content=content)
42
-
43
- with patch.object(Artefact, 'parent', new_callable=MagicMock(return_value=Artefact('Document', 'Parent_Artefact'))), \
44
- patch('ara_cli.artefact.Classifier.get_artefact_title', side_effect=lambda x: 'Document' if x == 'Document' else 'Example'):
45
-
46
- artefact.assemble_content()
47
- assert artefact._content == expected_content
48
-
49
-
50
- @pytest.mark.parametrize(
51
- "initial_content, parent, expected_content",
52
- [
53
- ("Contributes to Child Artefact", Artefact('Type1', 'Parent_Artefact'), "Contributes to Parent_Artefact Document"),
54
- ("Contributes to: Child Artefact", Artefact('Type1', 'Parent_Artefact'), "Contributes to Parent_Artefact Document"),
55
- ("Illustrates Child Artefact", Artefact('Type1', 'Parent_Artefact'), "Illustrates Parent_Artefact Document"),
56
- ("Illustrates: Child Artefact", Artefact('Type1', 'Parent_Artefact'), "Illustrates Parent_Artefact Document"),
57
- ("Contributes to Child Artefact", None, "Contributes to Child Artefact"),
58
- ("Illustrates Child Artefact", None, "Illustrates Child Artefact")
59
- ]
60
- )
61
- def test_assemble_content_replace_parent(mock_classifier, initial_content, parent, expected_content):
62
- artefact = Artefact(classifier='Document', name='Test Artefact', _content=initial_content)
63
-
64
- with patch.object(Artefact, 'parent', new_callable=MagicMock(return_value=parent)), \
65
- patch('ara_cli.artefact.Classifier.get_artefact_title', return_value='Document'):
66
- artefact.assemble_content()
67
- assert artefact._content == expected_content
68
-
69
-
70
- @pytest.mark.parametrize(
71
- "initial_content, initial_tags, expected_content",
72
- [
73
- ("Some content", {'tag1', 'tag2'}, "@tag1 @tag2\nSome content"),
74
- ("@oldtag\nContent", {'newtag'}, "@newtag\nContent"),
75
- ("Content without tags", set(), "Content without tags"),
76
- ]
77
- )
78
- def test_assemble_content_replace_tags(mock_classifier, initial_content, initial_tags, expected_content):
79
- artefact = Artefact(classifier='Document', name='Test Artefact', _content=initial_content)
80
- artefact._tags = initial_tags
81
-
82
- with patch.object(Artefact, 'parent', new_callable=MagicMock(return_value=None)):
83
- artefact.assemble_content()
84
- assert artefact._content == expected_content
85
-
86
- @pytest.mark.parametrize(
87
- "initial_content, parent, initial_tags, expected_content",
88
- [
89
- ("Contributes to Child Artefact", Artefact('Type1', 'Parent_Artefact'), {'tag1'}, "@tag1\nContributes to Parent_Artefact Document"),
90
- ("@oldtag\nIllustrates Child Artefact", Artefact('Type1', 'Parent_Artefact'), {'newtag'}, "@newtag\nIllustrates Parent_Artefact Document"),
91
- ("Content without parent", None, {'tag1', 'tag2'}, "@tag1 @tag2\nContent without parent"),
92
- ]
93
- )
94
- def test_assemble_content_full(mock_classifier, initial_content, parent, initial_tags, expected_content):
95
- artefact = Artefact(classifier='Document', name='Test Artefact', _content=initial_content)
96
- artefact._tags = initial_tags
97
-
98
- with patch.object(Artefact, 'parent', new_callable=MagicMock(return_value=parent)), \
99
- patch('ara_cli.artefact.Classifier.get_artefact_title', return_value='Document'):
100
- artefact.assemble_content()
101
- assert artefact._content == expected_content
102
-
103
-
104
- @pytest.mark.parametrize(
105
- "content, expected_parent",
106
- [
107
- ("Contributes to: Parent Artefact Document", Artefact('Type1', 'Parent_Artefact')),
108
- ("Contributes to Parent Report", Artefact('Type2', 'Parent')),
109
- ("No parent defined here", None),
110
- ("", None),
111
- ]
112
- )
113
- def test_parent_property(mock_classifier, content, expected_parent):
114
- artefact = Artefact(classifier='Document', name='Test Artefact', _content=content)
115
-
116
- parent = artefact.parent
117
-
118
- if expected_parent is None:
119
- assert parent is None
120
- else:
121
- assert parent is not None
122
- assert parent.name == expected_parent.name
123
- assert parent.classifier == expected_parent.classifier
124
-
125
-
126
- @pytest.mark.parametrize(
127
- "initial_parent, expected_parent",
128
- [
129
- (Artefact('Document', 'Existing Parent'), Artefact('Document', 'Existing Parent')),
130
- ]
131
- )
132
- def test_parent_property_initial_parent(mock_classifier, initial_parent, expected_parent):
133
- artefact = Artefact(classifier='Document', name='Test Artefact', _content="Contributes to: New Parent Document")
134
- artefact._parent = initial_parent # Directly set _parent to simulate pre-existing condition
135
-
136
- parent = artefact.parent
137
-
138
- assert parent is not None
139
- assert parent.name == expected_parent.name
140
- assert parent.classifier == expected_parent.classifier
141
-
142
-
143
- @pytest.mark.parametrize(
144
- "classifier, name, expected_file_path",
145
- [
146
- ('Document', 'Test Artefact', 'documents/Test_Artefact.Document'),
147
- ('Report', 'Another Report', 'reports/Another_Report.Report'),
148
- ('Document', 'Report with spaces', 'documents/Report_with_spaces.Document'),
149
- ]
150
- )
151
- def test_file_path_property(mock_classifier, classifier, name, expected_file_path):
152
- artefact = Artefact(classifier=classifier, name=name)
153
- file_path = artefact.file_path
154
- assert file_path == expected_file_path
155
-
156
-
157
- @pytest.mark.parametrize(
158
- "content, expected_tags",
159
- [
160
- ("@tag1 @tag2 Some content here", {'tag1', 'tag2'}),
161
- ("@singleTag Content follows", {'singleTag'}),
162
- ("No tags in this content", set()),
163
- ("", set()),
164
- ("@mixedCase @AnotherTag @123numeric", {'mixedCase', 'AnotherTag', '123numeric'}),
165
- (" @leadingSpaceTag @another ", {'leadingSpaceTag', 'another'}),
166
- ("Not a tag @notatag", set()),
167
- ]
168
- )
169
- def test_tags_property(mock_classifier, content, expected_tags):
170
- artefact = Artefact(classifier='Document', name='Test Artefact', _content=content)
171
- tags = artefact.tags
172
- assert tags == expected_tags
173
-
174
-
175
- @pytest.mark.parametrize(
176
- "initial_tags, content, expected_tags",
177
- [
178
- ({'preExistingTag'}, "@tag1 @tag2", {'preExistingTag'}),
179
- (None, "@tag1 @tag2", {'tag1', 'tag2'}),
180
- ({'anotherTag'}, "", {'anotherTag'}),
181
- (set(), "No tags in this content", set()),
182
- ]
183
- )
184
- def test_tags_property_when_tags_pre_set(mock_classifier, initial_tags, content, expected_tags):
185
- artefact = Artefact(classifier='Document', name='Test Artefact', _content=content)
186
- artefact._tags = initial_tags # Directly set _tags to simulate pre-existing condition
187
-
188
- tags = artefact.tags
189
-
190
- assert tags == expected_tags
191
-
192
-
193
- def test_content_property_reads_file(mock_classifier):
194
- artefact = Artefact(classifier='Document', name='Test Artefact')
195
- mock_file_content = "This is the file content."
196
-
197
- # Mock the open function within the specific context
198
- with patch("builtins.open", mock_open(read_data=mock_file_content)):
199
- content = artefact.content
200
-
201
- assert content == mock_file_content
202
-
203
- def test_content_property_caches_content(mock_classifier):
204
- artefact = Artefact(classifier='Document', name='Test Artefact')
205
- mock_file_content = "This is the file content."
206
-
207
- # Mock the open function within the specific context
208
- with patch("builtins.open", mock_open(read_data=mock_file_content)) as mocked_open:
209
- content_first_call = artefact.content
210
- content_second_call = artefact.content
211
-
212
- # Ensure the file is only read once
213
- mocked_open.assert_called_once_with(artefact.file_path, 'r')
214
- assert content_first_call == mock_file_content
215
- assert content_second_call == mock_file_content
216
-
217
- def test_content_property_with_existing_content(mock_classifier):
218
- existing_content = "Existing content should be returned."
219
- artefact = Artefact(classifier='Document', name='Test Artefact', _content=existing_content)
220
-
221
- with patch("builtins.open", mock_open()) as mocked_open:
222
- content = artefact.content
223
-
224
- # Ensure that the file is never opened because _content is already set
225
- mocked_open.assert_not_called()
226
- assert content == existing_content
227
-
228
-
229
- @pytest.mark.parametrize(
230
- "content, expected_classifier, expected_name",
231
- [
232
- ("Document: Parent Artefact", 'Type1', 'Parent Artefact'),
233
- ("Report: Another Report", 'Type2', 'Another Report')
234
- ]
235
- )
236
- def test_from_content_valid(mock_classifier, content, expected_classifier, expected_name):
237
- artefact = Artefact.from_content(content)
238
- assert artefact.classifier == expected_classifier
239
- assert artefact.name == expected_name
240
- assert artefact.content == content
241
-
242
-
243
- @pytest.mark.parametrize(
244
- "content",
245
- [
246
- "Invalid content without proper structure",
247
- "Classifier: Name: Missing title",
248
- "",
249
- None,
250
- ]
251
- )
252
- def test_from_content_invalid(mock_classifier, content):
253
- with pytest.raises(ValueError, match="Content does not contain valid artefact information"):
254
- Artefact.from_content(content)
255
-
256
-
257
- @pytest.mark.parametrize(
258
- "content, should_raise_error",
259
- [
260
- ("Some valid content", False), # Content is valid, should not raise
261
- ]
262
- )
263
- def test_write_to_file_content_check(mock_classifier, content, should_raise_error):
264
- artefact = Artefact(classifier='Document', name='Test Artefact', _content=content)
265
- if should_raise_error:
266
- with pytest.raises(ValueError, match="Artefact object does not contain content information"):
267
- artefact.write_to_file()
268
- else:
269
- with patch('builtins.open', mock_open()) as mocked_file, \
270
- patch('os.makedirs') as mocked_makedirs, \
271
- patch.object(artefact, 'assemble_content') as mocked_assemble_content:
272
- artefact.write_to_file()
273
- mocked_assemble_content.assert_called_once()
274
- mocked_makedirs.assert_called_once_with('documents/Test_Artefact.data', exist_ok=True)
275
- mocked_file.assert_called_once_with('documents/Test_Artefact.Document', 'w')
276
- mocked_file().write.assert_called_once_with(content)
277
-
278
-
279
- @pytest.mark.parametrize(
280
- "file_path",
281
- [
282
- ('documents/Test_Artefact.Document'),
283
- ('reports/Another_Report.Report')
284
- ]
285
- )
286
- def test_write_to_file_directory_creation(mock_classifier, file_path):
287
- artefact = Artefact(classifier='Document', name='Test Artefact', _content="Some content")
288
- artefact._file_path = file_path # Directly set file path for testing
289
- with patch('os.makedirs') as mocked_makedirs, \
290
- patch('builtins.open', mock_open()):
291
- artefact.write_to_file()
292
- data_directory = f"{os.path.splitext(file_path)[0]}.data"
293
- mocked_makedirs.assert_called_once_with(data_directory, exist_ok=True)
294
-
295
-
296
- def test_write_to_file_writes_content(mock_classifier):
297
- content = "Some valid content"
298
- artefact = Artefact(classifier='Document', name='Test Artefact', _content=content)
299
- with patch('builtins.open', mock_open()) as mocked_file, \
300
- patch('os.makedirs'), \
301
- patch.object(Artefact, 'assemble_content'):
302
- artefact.write_to_file()
303
- mocked_file.assert_called_once_with('documents/Test_Artefact.Document', 'w')
304
- mocked_file().write.assert_called_once_with(content)