ara-cli 0.1.13.3__py3-none-any.whl → 0.1.14.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 (61) hide show
  1. ara_cli/__init__.py +1 -1
  2. ara_cli/ara_command_action.py +162 -112
  3. ara_cli/ara_config.py +1 -1
  4. ara_cli/ara_subcommands/convert.py +66 -2
  5. ara_cli/ara_subcommands/prompt.py +266 -106
  6. ara_cli/artefact_autofix.py +2 -2
  7. ara_cli/artefact_converter.py +152 -53
  8. ara_cli/artefact_creator.py +41 -17
  9. ara_cli/artefact_lister.py +3 -3
  10. ara_cli/artefact_models/artefact_model.py +1 -1
  11. ara_cli/artefact_models/artefact_templates.py +0 -9
  12. ara_cli/artefact_models/feature_artefact_model.py +8 -8
  13. ara_cli/artefact_reader.py +62 -43
  14. ara_cli/artefact_scan.py +39 -17
  15. ara_cli/chat.py +23 -15
  16. ara_cli/children_contribution_updater.py +737 -0
  17. ara_cli/classifier.py +34 -0
  18. ara_cli/commands/load_command.py +4 -3
  19. ara_cli/commands/load_image_command.py +1 -1
  20. ara_cli/commands/read_command.py +23 -27
  21. ara_cli/completers.py +24 -0
  22. ara_cli/error_handler.py +26 -11
  23. ara_cli/file_loaders/document_reader.py +0 -178
  24. ara_cli/file_loaders/factories/__init__.py +0 -0
  25. ara_cli/file_loaders/factories/document_reader_factory.py +32 -0
  26. ara_cli/file_loaders/factories/file_loader_factory.py +27 -0
  27. ara_cli/file_loaders/file_loader.py +1 -30
  28. ara_cli/file_loaders/loaders/__init__.py +0 -0
  29. ara_cli/file_loaders/{document_file_loader.py → loaders/document_file_loader.py} +1 -1
  30. ara_cli/file_loaders/loaders/text_file_loader.py +47 -0
  31. ara_cli/file_loaders/readers/__init__.py +0 -0
  32. ara_cli/file_loaders/readers/docx_reader.py +49 -0
  33. ara_cli/file_loaders/readers/excel_reader.py +27 -0
  34. ara_cli/file_loaders/{markdown_reader.py → readers/markdown_reader.py} +1 -1
  35. ara_cli/file_loaders/readers/odt_reader.py +59 -0
  36. ara_cli/file_loaders/readers/pdf_reader.py +54 -0
  37. ara_cli/file_loaders/readers/pptx_reader.py +104 -0
  38. ara_cli/file_loaders/tools/__init__.py +0 -0
  39. ara_cli/output_suppressor.py +53 -0
  40. ara_cli/prompt_handler.py +123 -17
  41. ara_cli/tag_extractor.py +8 -7
  42. ara_cli/version.py +1 -1
  43. {ara_cli-0.1.13.3.dist-info → ara_cli-0.1.14.0.dist-info}/METADATA +18 -12
  44. {ara_cli-0.1.13.3.dist-info → ara_cli-0.1.14.0.dist-info}/RECORD +58 -45
  45. {ara_cli-0.1.13.3.dist-info → ara_cli-0.1.14.0.dist-info}/WHEEL +1 -1
  46. tests/test_artefact_converter.py +1 -46
  47. tests/test_artefact_lister.py +11 -8
  48. tests/test_chat.py +4 -4
  49. tests/test_chat_givens_images.py +1 -1
  50. tests/test_children_contribution_updater.py +98 -0
  51. tests/test_document_loader_office.py +267 -0
  52. tests/test_prompt_handler.py +416 -214
  53. tests/test_setup_default_chat_prompt_mode.py +198 -0
  54. tests/test_tag_extractor.py +95 -49
  55. ara_cli/file_loaders/document_readers.py +0 -233
  56. ara_cli/file_loaders/file_loaders.py +0 -123
  57. ara_cli/file_loaders/text_file_loader.py +0 -187
  58. /ara_cli/file_loaders/{binary_file_loader.py → loaders/binary_file_loader.py} +0 -0
  59. /ara_cli/file_loaders/{image_processor.py → tools/image_processor.py} +0 -0
  60. {ara_cli-0.1.13.3.dist-info → ara_cli-0.1.14.0.dist-info}/entry_points.txt +0 -0
  61. {ara_cli-0.1.13.3.dist-info → ara_cli-0.1.14.0.dist-info}/top_level.txt +0 -0
@@ -27,11 +27,21 @@ class ArtefactCreator:
27
27
  raise ValueError("classifier must not be None or empty!")
28
28
 
29
29
  # Standard prompt log artefact
30
- self._copy_template_file(dir_path, template_path, f"template.{classifier}.prompt_log.md", f"{classifier}.prompt_log.md")
30
+ self._copy_template_file(
31
+ dir_path,
32
+ template_path,
33
+ f"template.{classifier}.prompt_log.md",
34
+ f"{classifier}.prompt_log.md",
35
+ )
31
36
 
32
37
  # Additional prompt log artefact for 'feature' classifier
33
- if classifier == 'feature':
34
- self._copy_template_file(dir_path, template_path, "template.steps.prompt_log.md", "steps.prompt_log.md")
38
+ if classifier == "feature":
39
+ self._copy_template_file(
40
+ dir_path,
41
+ template_path,
42
+ "template.steps.prompt_log.md",
43
+ "steps.prompt_log.md",
44
+ )
35
45
 
36
46
  def _copy_template_file(self, dir_path, template_path, source_name, dest_name):
37
47
  source = Path(template_path) / source_name
@@ -41,7 +51,9 @@ class ArtefactCreator:
41
51
  raise FileNotFoundError(f"Source file {source} not found!")
42
52
 
43
53
  if not destination.parent.exists():
44
- raise NotADirectoryError(f"Destination directory {destination.parent} does not exist!")
54
+ raise NotADirectoryError(
55
+ f"Destination directory {destination.parent} does not exist!"
56
+ )
45
57
 
46
58
  copyfile(source, destination)
47
59
 
@@ -59,7 +71,9 @@ class ArtefactCreator:
59
71
 
60
72
  def handle_existing_files(self, file_exists):
61
73
  if file_exists:
62
- user_choice = input("File already exists. Do you want to overwrite the existing file and directory? (y/N): ")
74
+ user_choice = input(
75
+ "File already exists. Do you want to overwrite the existing file and directory? (y/N): "
76
+ )
63
77
  if user_choice.lower() != "y":
64
78
  print("No changes were made to the existing file and directory.")
65
79
  return False
@@ -68,14 +82,20 @@ class ArtefactCreator:
68
82
  def validate_template(self, template_path, classifier):
69
83
  template_name = f"template.{classifier}"
70
84
  if not self.template_exists(template_path, template_name):
71
- raise FileNotFoundError(f"Template file '{template_name}' not found in the specified template path.")
85
+ raise FileNotFoundError(
86
+ f"Template file '{template_name}' not found in the specified template path."
87
+ )
72
88
 
73
- def set_artefact_parent(self, artefact, parent_classifier, parent_file_name) -> Artefact:
74
- classified_artefacts = ArtefactReader.read_artefacts()
89
+ def set_artefact_parent(
90
+ self, artefact, parent_classifier, parent_file_name
91
+ ) -> Artefact:
92
+ classified_artefacts = ArtefactReader(self.file_system).read_artefacts()
75
93
  if parent_classifier not in classified_artefacts:
76
94
  return artefact
77
95
  artefact_list = classified_artefacts[parent_classifier]
78
- matching_artefacts = list(filter(lambda a: a.file_name == parent_file_name, artefact_list))
96
+ matching_artefacts = list(
97
+ filter(lambda a: a.file_name == parent_file_name, artefact_list)
98
+ )
79
99
  if not matching_artefacts:
80
100
  artefact_names_list = [a.file_name for a in artefact_list]
81
101
  suggest_close_name_matches(parent_file_name, artefact_names_list)
@@ -83,17 +103,23 @@ class ArtefactCreator:
83
103
  artefact._parent = matching_artefacts[0]
84
104
  return artefact
85
105
 
86
- def run(self, filename, classifier, parent_classifier=None, parent_name=None, rule=None):
106
+ def run(
107
+ self, filename, classifier, parent_classifier=None, parent_name=None, rule=None
108
+ ):
87
109
  # make sure this function is always called from the ara top level directory
88
110
  original_directory = os.getcwd()
89
111
  navigator = DirectoryNavigator()
90
112
  navigator.navigate_to_target()
91
113
 
92
114
  if not Classifier.is_valid_classifier(classifier):
93
- raise ValueError("Invalid classifier provided. Please provide a valid classifier.")
115
+ raise ValueError(
116
+ "Invalid classifier provided. Please provide a valid classifier."
117
+ )
94
118
 
95
119
  sub_directory = Classifier.get_sub_directory(classifier)
96
- file_path = self.file_system.path.join(sub_directory, f"{filename}.{classifier}")
120
+ file_path = self.file_system.path.join(
121
+ sub_directory, f"{filename}.{classifier}"
122
+ )
97
123
  dir_path = self.file_system.path.join(sub_directory, f"{filename}.data")
98
124
 
99
125
  file_exists = self.file_system.path.exists(file_path)
@@ -105,9 +131,7 @@ class ArtefactCreator:
105
131
 
106
132
  if parent_classifier and parent_name:
107
133
  artefact.set_contribution(
108
- artefact_name=parent_name,
109
- classifier=parent_classifier,
110
- rule=rule
134
+ artefact_name=parent_name, classifier=parent_classifier, rule=rule
111
135
  )
112
136
  else:
113
137
  artefact.set_contribution(None, None, None)
@@ -115,7 +139,7 @@ class ArtefactCreator:
115
139
  artefact_content = artefact.serialize()
116
140
  rmtree(dir_path, ignore_errors=True)
117
141
  os.makedirs(dir_path, exist_ok=True)
118
- with open(file_path, 'w', encoding='utf-8') as artefact_file:
142
+ with open(file_path, "w", encoding="utf-8") as artefact_file:
119
143
  artefact_file.write(artefact_content)
120
144
 
121
145
  relative_file_path = os.path.relpath(file_path, original_directory)
@@ -138,6 +162,6 @@ class ArtefactCreator:
138
162
  with open(file_path, "r", encoding="utf-8") as file:
139
163
  for line in file:
140
164
  if line.strip().startswith(title):
141
- return line.split(':')[1].strip()
165
+ return line.split(":")[1].strip()
142
166
 
143
167
  raise ValueError(f"Title not found in the parent file {file_path}")
@@ -28,7 +28,7 @@ class ArtefactLister:
28
28
  def list_files(
29
29
  self, tags=None, navigate_to_target=False, list_filter: ListFilter | None = None
30
30
  ):
31
- artefact_list = ArtefactReader.read_artefacts(tags=tags)
31
+ artefact_list = ArtefactReader(self.file_system).read_artefacts(tags=tags)
32
32
  artefact_list = self.filter_artefacts(artefact_list, list_filter)
33
33
 
34
34
  filtered_artefact_list = {
@@ -54,7 +54,7 @@ class ArtefactLister:
54
54
  )
55
55
 
56
56
  artefacts_by_classifier = {classifier: []}
57
- ArtefactReader.step_through_value_chain(
57
+ ArtefactReader(self.file_system).step_through_value_chain(
58
58
  artefact_name=artefact_name,
59
59
  classifier=classifier,
60
60
  artefacts_by_classifier=artefacts_by_classifier,
@@ -79,7 +79,7 @@ class ArtefactLister:
79
79
  artefact_name, [info["title"] for info in artefact_info]
80
80
  )
81
81
 
82
- child_artefacts = ArtefactReader.find_children(
82
+ child_artefacts = ArtefactReader(self.file_system).find_children(
83
83
  artefact_name=artefact_name, classifier=classifier
84
84
  )
85
85
 
@@ -185,7 +185,7 @@ class Artefact(BaseModel, ABC):
185
185
  description="Optional list of tags (0-many)",
186
186
  )
187
187
  author: Optional[str] = Field(
188
- default="creator_unknown",
188
+ default=None,
189
189
  description="Author of the artefact, must be a single entry of the form 'creator_<someone>'."
190
190
  )
191
191
  title: str = Field(
@@ -30,7 +30,6 @@ def _default_vision(title: str, use_default_contribution: bool) -> VisionArtefac
30
30
  )
31
31
  return VisionArtefact(
32
32
  tags=[],
33
- author="creator_unknown",
34
33
  title=title,
35
34
  description="<further optional description to understand the vision, markdown capable text formatting>",
36
35
  intent=intent,
@@ -46,7 +45,6 @@ def _default_businessgoal(title: str, use_default_contribution: bool) -> Busines
46
45
  )
47
46
  return BusinessgoalArtefact(
48
47
  tags=[],
49
- author="creator_unknown",
50
48
  title=title,
51
49
  description="<further optional description to understand the businessgoal, markdown capable text formatting>",
52
50
  intent=intent,
@@ -60,7 +58,6 @@ def _default_capability(title: str, use_default_contribution: bool) -> Capabilit
60
58
  )
61
59
  return CapabilityArtefact(
62
60
  tags=[],
63
- author="creator_unknown",
64
61
  title=title,
65
62
  description="<further optional description to understand the capability, markdown capable text formatting>",
66
63
  intent=intent,
@@ -81,7 +78,6 @@ def _default_epic(title: str, use_default_contribution: bool) -> EpicArtefact:
81
78
  ]
82
79
  return EpicArtefact(
83
80
  tags=[],
84
- author="creator_unknown",
85
81
  title=title,
86
82
  description="<further optional description to understand the epic, markdown capable text formatting>",
87
83
  intent=intent,
@@ -103,7 +99,6 @@ def _default_userstory(title: str, use_default_contribution: bool) -> UserstoryA
103
99
  ]
104
100
  return UserstoryArtefact(
105
101
  tags=[],
106
- author="creator_unknown",
107
102
  title=title,
108
103
  description="<further optional description to understand the userstory, markdown capable text formatting>",
109
104
  intent=intent,
@@ -116,7 +111,6 @@ def _default_userstory(title: str, use_default_contribution: bool) -> UserstoryA
116
111
  def _default_example(title: str, use_default_contribution: bool) -> ExampleArtefact:
117
112
  return ExampleArtefact(
118
113
  tags=[],
119
- author="creator_unknown",
120
114
  title=title,
121
115
  description="<further optional description to understand the example, markdown capable text formatting>",
122
116
  contribution=default_contribution() if use_default_contribution else None
@@ -137,7 +131,6 @@ def _default_keyfeature(title: str, use_default_contribution: bool) -> Keyfeatur
137
131
  AND some other result is to be expected>"""
138
132
  return KeyfeatureArtefact(
139
133
  tags=[],
140
- author="creator_unknown",
141
134
  title=title,
142
135
  description=description,
143
136
  intent=intent,
@@ -193,7 +186,6 @@ def _default_feature(title: str, use_default_contribution: bool) -> FeatureArtef
193
186
 
194
187
  return FeatureArtefact(
195
188
  tags=[],
196
- author="creator_unknown",
197
189
  title=title,
198
190
  description=description,
199
191
  intent=intent,
@@ -224,7 +216,6 @@ def _default_issue(title: str, use_default_contribution: bool) -> IssueArtefact:
224
216
 
225
217
  return IssueArtefact(
226
218
  tags=[],
227
- author="creator_unknown",
228
219
  title=title,
229
220
  description=description,
230
221
  additional_description=additional_description,
@@ -149,10 +149,10 @@ class Scenario(BaseModel):
149
149
  return steps
150
150
 
151
151
  @model_validator(mode='after')
152
- def check_no_placeholders(cls, values: 'Scenario') -> 'Scenario':
152
+ def check_no_placeholders(self) -> 'Scenario':
153
153
  """Ensure regular scenarios don't contain placeholders that should be in scenario outlines."""
154
154
  placeholders = set()
155
- for step in values.steps:
155
+ for step in self.steps:
156
156
  # Skip validation if step contains docstring placeholders (during parsing)
157
157
  if '__DOCSTRING_PLACEHOLDER_' in step:
158
158
  continue
@@ -170,7 +170,7 @@ class Scenario(BaseModel):
170
170
  f"Scenario Contains Placeholders ({placeholder_list}) but is not a Scenario Outline. "
171
171
  f"Use 'Scenario Outline:' instead of 'Scenario:' and provide an Examples table."
172
172
  )
173
- return values
173
+ return self
174
174
 
175
175
  @classmethod
176
176
  def from_lines(cls, lines: List[str], start_idx: int) -> Tuple['Scenario', int]:
@@ -219,18 +219,18 @@ class ScenarioOutline(BaseModel):
219
219
  return v
220
220
 
221
221
  @model_validator(mode='after')
222
- def check_placeholders(cls, values: 'ScenarioOutline') -> 'ScenarioOutline':
222
+ def check_placeholders(self) -> 'ScenarioOutline':
223
223
  """Ensure all placeholders in steps have corresponding values in examples."""
224
224
  placeholders = set()
225
- for step in values.steps:
225
+ for step in self.steps:
226
226
  found = re.findall(r'<([^>]+)>', step)
227
227
  placeholders.update(found)
228
- for example in values.examples:
228
+ for example in self.examples:
229
229
  missing = placeholders - set(example.values.keys())
230
230
  if missing:
231
231
  raise ValueError(
232
232
  f"Example is missing values for placeholders: {missing}")
233
- return values
233
+ return self
234
234
 
235
235
  @classmethod
236
236
  def from_lines(cls, lines: List[str], start_idx: int) -> Tuple['ScenarioOutline', int]:
@@ -298,7 +298,7 @@ class FeatureArtefact(Artefact):
298
298
  def validate_artefact_type(cls, v):
299
299
  if v != ArtefactType.feature:
300
300
  raise ValueError(
301
- f"FeatureArtefact must have artefact_type of '{ArtefactType.feature}', not '{v}'")
301
+ f"FeatureArtefact must have artefact_type of '{ArtefactType.feature.value}', not '{v}'")
302
302
  return v
303
303
 
304
304
  @classmethod
@@ -3,20 +3,29 @@ from ara_cli.classifier import Classifier
3
3
  from ara_cli.file_classifier import FileClassifier
4
4
  from ara_cli.artefact_models.artefact_model import Artefact
5
5
  from ara_cli.artefact_models.artefact_load import artefact_from_content
6
- from ara_cli.artefact_fuzzy_search import suggest_close_name_matches_for_parent, suggest_close_name_matches
6
+ from ara_cli.artefact_fuzzy_search import (
7
+ suggest_close_name_matches_for_parent,
8
+ suggest_close_name_matches,
9
+ )
7
10
  from typing import Dict, List
8
11
  import os
9
12
  import re
10
13
 
11
14
 
12
15
  class ArtefactReader:
13
- @staticmethod
14
- def read_artefact_data(artefact_name, classifier, classified_file_info = None) -> tuple[str, dict[str, str]]:
16
+ def __init__(self, file_system=None):
17
+ self.file_system = file_system or os
18
+
19
+ def read_artefact_data(
20
+ self, artefact_name, classifier, classified_file_info=None
21
+ ) -> tuple[str, dict[str, str]]:
15
22
  if not Classifier.is_valid_classifier(classifier):
16
- raise ValueError("Invalid classifier provided. Please provide a valid classifier.")
23
+ raise ValueError(
24
+ "Invalid classifier provided. Please provide a valid classifier."
25
+ )
17
26
 
18
27
  if not classified_file_info:
19
- file_classifier = FileClassifier(os)
28
+ file_classifier = FileClassifier(self.file_system)
20
29
  classified_file_info = file_classifier.classify_files()
21
30
  artefact_info_of_classifier = classified_file_info.get(classifier, [])
22
31
 
@@ -24,21 +33,21 @@ class ArtefactReader:
24
33
  file_path = artefact_info["file_path"]
25
34
  artefact_title = artefact_info["title"]
26
35
  if artefact_title == artefact_name:
27
- with open(file_path, 'r', encoding='utf-8') as file:
36
+ with open(file_path, "r", encoding="utf-8") as file:
28
37
  content = file.read()
29
38
  return content, artefact_info
30
39
 
31
40
  all_artefact_names = [info["title"] for info in artefact_info_of_classifier]
32
- suggest_close_name_matches(
33
- artefact_name,
34
- all_artefact_names
35
- )
41
+ suggest_close_name_matches(artefact_name, all_artefact_names)
36
42
 
37
43
  return None, None
38
44
 
39
- @staticmethod
40
- def read_artefact(artefact_name, classifier, classified_file_info=None) -> Artefact:
41
- content, artefact_info = ArtefactReader.read_artefact_data(artefact_name, classifier, classified_file_info)
45
+ def read_artefact(
46
+ self, artefact_name, classifier, classified_file_info=None
47
+ ) -> Artefact:
48
+ content, artefact_info = self.read_artefact_data(
49
+ artefact_name, classifier, classified_file_info
50
+ )
42
51
  if not content or not artefact_info:
43
52
  return None
44
53
  file_path = artefact_info["file_path"]
@@ -49,9 +58,11 @@ class ArtefactReader:
49
58
  @staticmethod
50
59
  def extract_parent_tree(artefact_content):
51
60
  artefact_titles = Classifier.artefact_titles()
52
- title_segment = '|'.join(artefact_titles)
61
+ title_segment = "|".join(artefact_titles)
53
62
 
54
- regex_pattern = rf'(?:Contributes to|Illustrates)\s*:*\s*(.*)\s+({title_segment}).*'
63
+ regex_pattern = (
64
+ rf"(?:Contributes to|Illustrates)\s*:*\s*(.*)\s+({title_segment}).*"
65
+ )
55
66
  regex = re.compile(regex_pattern)
56
67
  match = re.search(regex, artefact_content)
57
68
  if not match:
@@ -72,33 +83,40 @@ class ArtefactReader:
72
83
  merged[key].extend(artefacts)
73
84
  return dict(merged)
74
85
 
75
- @staticmethod
76
- def read_artefacts(classified_artefacts=None, file_system=os, tags=None) -> Dict[str, List[Artefact]]:
86
+ def read_artefacts(
87
+ self, classified_artefacts=None, tags=None
88
+ ) -> Dict[str, List[Artefact]]:
77
89
 
78
90
  if classified_artefacts is None:
79
- file_classifier = FileClassifier(file_system)
91
+ file_classifier = FileClassifier(self.file_system)
80
92
  classified_artefacts = file_classifier.classify_files()
81
93
 
82
- artefacts = {artefact_type: []
83
- for artefact_type in classified_artefacts.keys()}
94
+ artefacts = {artefact_type: [] for artefact_type in classified_artefacts.keys()}
84
95
  for artefact_type, artefact_info_dicts in classified_artefacts.items():
85
96
  for artefact_info in artefact_info_dicts:
86
97
  title = artefact_info["title"]
87
98
  try:
88
- artefact = ArtefactReader.read_artefact(title, artefact_type, classified_artefacts)
99
+ artefact = self.read_artefact(
100
+ title, artefact_type, classified_artefacts
101
+ )
89
102
  artefacts[artefact_type].append(artefact)
90
103
  except Exception as e:
91
104
  error_handler.report_error(e, f"reading {artefact_type} '{title}'")
92
105
  continue
93
106
  return artefacts
94
107
 
95
- @staticmethod
96
- def find_children(artefact_name, classifier, artefacts_by_classifier=None, classified_artefacts=None):
108
+ def find_children(
109
+ self,
110
+ artefact_name,
111
+ classifier,
112
+ artefacts_by_classifier=None,
113
+ classified_artefacts=None,
114
+ ):
97
115
  artefacts_by_classifier = artefacts_by_classifier or {}
98
116
  filtered_artefacts = {k: [] for k in artefacts_by_classifier.keys()}
99
117
 
100
118
  if classified_artefacts is None:
101
- classified_artefacts = ArtefactReader.read_artefacts()
119
+ classified_artefacts = self.read_artefacts()
102
120
 
103
121
  for artefact_classifier, artefacts in classified_artefacts.items():
104
122
  for artefact in artefacts:
@@ -112,38 +130,37 @@ class ArtefactReader:
112
130
  def _process_artefact(artefact, artefact_name, classifier, filtered_artefacts):
113
131
  if not isinstance(artefact, Artefact):
114
132
  return
115
- contribution = getattr(artefact, 'contribution', None)
133
+ contribution = getattr(artefact, "contribution", None)
116
134
  if not contribution:
117
135
  return
118
- if getattr(contribution, 'artefact_name', None) != artefact_name:
136
+ if getattr(contribution, "artefact_name", None) != artefact_name:
119
137
  return
120
- if getattr(contribution, 'classifier', None) != classifier:
138
+ if getattr(contribution, "classifier", None) != classifier:
121
139
  return
122
140
 
123
- file_classifier = getattr(artefact, '_file_path', '').split('.')[-1]
141
+ file_classifier = getattr(artefact, "_file_path", "").split(".")[-1]
124
142
  if file_classifier not in filtered_artefacts:
125
143
  filtered_artefacts[file_classifier] = []
126
144
  filtered_artefacts[file_classifier].append(artefact)
127
145
 
128
- @staticmethod
129
146
  def step_through_value_chain(
130
- artefact_name,
131
- classifier,
132
- artefacts_by_classifier=None,
133
- classified_artefacts: dict[str, list['Artefact']] | None = None
147
+ self,
148
+ artefact_name,
149
+ classifier,
150
+ artefacts_by_classifier=None,
151
+ classified_artefacts: dict[str, list["Artefact"]] | None = None,
134
152
  ):
135
153
  from ara_cli.artefact_models.artefact_load import artefact_from_content
136
154
 
137
155
  artefacts_by_classifier = artefacts_by_classifier or {}
138
156
 
139
157
  if classified_artefacts is None:
140
- classified_artefacts = ArtefactReader.read_artefacts()
158
+ classified_artefacts = self.read_artefacts()
141
159
 
142
160
  ArtefactReader._ensure_classifier_key(classifier, artefacts_by_classifier)
143
161
 
144
162
  artefact = ArtefactReader._find_artefact_by_name(
145
- artefact_name,
146
- classified_artefacts.get(classifier, [])
163
+ artefact_name, classified_artefacts.get(classifier, [])
147
164
  )
148
165
 
149
166
  if not artefact or artefact in artefacts_by_classifier[classifier]:
@@ -151,7 +168,7 @@ class ArtefactReader:
151
168
 
152
169
  artefacts_by_classifier[classifier].append(artefact)
153
170
 
154
- parent = getattr(artefact, 'contribution', None)
171
+ parent = getattr(artefact, "contribution", None)
155
172
  if not ArtefactReader._has_valid_parent(parent):
156
173
  return
157
174
 
@@ -168,11 +185,11 @@ class ArtefactReader:
168
185
  print()
169
186
  return
170
187
 
171
- ArtefactReader.step_through_value_chain(
188
+ self.step_through_value_chain(
172
189
  artefact_name=parent_name,
173
190
  classifier=parent_classifier,
174
191
  artefacts_by_classifier=artefacts_by_classifier,
175
- classified_artefacts=classified_artefacts
192
+ classified_artefacts=classified_artefacts,
176
193
  )
177
194
 
178
195
  @staticmethod
@@ -186,13 +203,15 @@ class ArtefactReader:
186
203
 
187
204
  @staticmethod
188
205
  def _has_valid_parent(parent):
189
- return parent and getattr(parent, 'artefact_name', None) and getattr(parent, 'classifier', None)
206
+ return (
207
+ parent
208
+ and getattr(parent, "artefact_name", None)
209
+ and getattr(parent, "classifier", None)
210
+ )
190
211
 
191
212
  @staticmethod
192
213
  def _suggest_parent_name_match(artefact_name, all_artefact_names, parent_name):
193
214
  if parent_name is not None:
194
215
  suggest_close_name_matches_for_parent(
195
- artefact_name,
196
- all_artefact_names,
197
- parent_name
216
+ artefact_name, all_artefact_names, parent_name
198
217
  )
ara_cli/artefact_scan.py CHANGED
@@ -4,12 +4,16 @@ import os
4
4
 
5
5
  def is_contribution_valid(contribution, classified_artefact_info) -> bool:
6
6
  from ara_cli.artefact_fuzzy_search import extract_artefact_names_of_classifier
7
- if not contribution or not contribution.artefact_name or not contribution.classifier:
7
+
8
+ if (
9
+ not contribution
10
+ or not contribution.artefact_name
11
+ or not contribution.classifier
12
+ ):
8
13
  return True
9
14
 
10
15
  all_artefact_names = extract_artefact_names_of_classifier(
11
- classified_files=classified_artefact_info,
12
- classifier=contribution.classifier
16
+ classified_files=classified_artefact_info, classifier=contribution.classifier
13
17
  )
14
18
  if contribution.artefact_name not in all_artefact_names:
15
19
  return False
@@ -19,12 +23,18 @@ def is_contribution_valid(contribution, classified_artefact_info) -> bool:
19
23
  def is_rule_valid(contribution, classified_artefact_info) -> bool:
20
24
  from ara_cli.artefact_reader import ArtefactReader
21
25
 
22
- if not contribution or not contribution.artefact_name or not contribution.classifier:
26
+ if (
27
+ not contribution
28
+ or not contribution.artefact_name
29
+ or not contribution.classifier
30
+ ):
23
31
  return True
24
32
  rule = contribution.rule
25
33
  if not rule:
26
34
  return True
27
- parent = ArtefactReader.read_artefact(contribution.artefact_name, contribution.classifier)
35
+ parent = ArtefactReader().read_artefact(
36
+ contribution.artefact_name, contribution.classifier
37
+ )
28
38
  if not parent:
29
39
  return True
30
40
  rules = parent.rules
@@ -33,20 +43,26 @@ def is_rule_valid(contribution, classified_artefact_info) -> bool:
33
43
  return True
34
44
 
35
45
 
36
- def check_contribution(contribution, classified_artefact_info, file_path) -> tuple[bool, str]:
46
+ def check_contribution(
47
+ contribution, classified_artefact_info, file_path
48
+ ) -> tuple[bool, str]:
37
49
  if not contribution:
38
50
  return True, None
39
51
 
40
52
  if not is_contribution_valid(contribution, classified_artefact_info):
41
- reason = (f"Invalid Contribution Reference: The contribution references "
42
- f"'{contribution.classifier}' artefact '{contribution.artefact_name}' "
43
- f"which does not exist.")
53
+ reason = (
54
+ f"Invalid Contribution Reference: The contribution references "
55
+ f"'{contribution.classifier}' artefact '{contribution.artefact_name}' "
56
+ f"which does not exist."
57
+ )
44
58
  return False, reason
45
59
 
46
60
  if not is_rule_valid(contribution, classified_artefact_info):
47
- reason = (f"Rule Mismatch: The contribution references "
48
- f"rule '{contribution.rule}' which the parent "
49
- f"{contribution.classifier} '{contribution.artefact_name}' does not have.")
61
+ reason = (
62
+ f"Rule Mismatch: The contribution references "
63
+ f"rule '{contribution.rule}' which the parent "
64
+ f"{contribution.classifier} '{contribution.artefact_name}' does not have."
65
+ )
50
66
  return False, reason
51
67
  return True, None
52
68
 
@@ -73,13 +89,17 @@ def check_file(file_path, artefact_class, classified_artefact_info=None):
73
89
 
74
90
  # Check title and file name matching
75
91
  if artefact_instance.title != file_name_without_ext:
76
- reason = (f"Filename-Title Mismatch: The file name '{file_name_without_ext}' "
77
- f"does not match the artefact title '{artefact_instance.title}'.")
92
+ reason = (
93
+ f"Filename-Title Mismatch: The file name '{file_name_without_ext}' "
94
+ f"does not match the artefact title '{artefact_instance.title}'."
95
+ )
78
96
  return False, reason
79
97
 
80
98
  contribution = artefact_instance.contribution
81
99
 
82
- contribution_valid, reason = check_contribution(contribution, classified_artefact_info, file_path)
100
+ contribution_valid, reason = check_contribution(
101
+ contribution, classified_artefact_info, file_path
102
+ )
83
103
  if not contribution_valid:
84
104
  return False, reason
85
105
 
@@ -100,7 +120,9 @@ def find_invalid_files(classified_artefact_info, classifier):
100
120
  continue
101
121
  if ".data" in artefact_info["file_path"]:
102
122
  continue
103
- is_valid, reason = check_file(artefact_info["file_path"], artefact_class, classified_artefact_info)
123
+ is_valid, reason = check_file(
124
+ artefact_info["file_path"], artefact_class, classified_artefact_info
125
+ )
104
126
  if not is_valid:
105
127
  invalid_files.append((artefact_info["file_path"], reason))
106
128
  return invalid_files
@@ -122,4 +144,4 @@ def show_results(invalid_artefacts):
122
144
  report.write("\n")
123
145
  if not has_issues:
124
146
  print("All files are good!")
125
- report.write("No problems found.\n")
147
+ report.write("No problems found.\n")