robotcode-robot 0.91.0__py3-none-any.whl → 0.93.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.
@@ -1 +1 @@
1
- __version__ = "0.91.0"
1
+ __version__ = "0.93.0"
@@ -1,7 +1,8 @@
1
1
  import sys
2
+ from dataclasses import fields, is_dataclass
2
3
  from enum import Enum
3
4
  from pathlib import Path
4
- from typing import Any, Optional, Sequence, Tuple, Type, TypeVar, Union
5
+ from typing import Any, Callable, Dict, Optional, Sequence, Tuple, Type, TypeVar, Union
5
6
 
6
7
  from robotcode.core.utils.dataclasses import from_dict
7
8
 
@@ -27,6 +28,7 @@ class DiscoverdBy(str, Enum):
27
28
  LOCAL_ROBOT_TOML = ".robot.toml (local file)"
28
29
  USER_DEFAULT_CONFIG_TOML = "robot.toml (user default config)"
29
30
  NOT_FOUND = "not found"
31
+ COMMAND_LINE = "command line"
30
32
 
31
33
 
32
34
  class ConfigType(str, Enum):
@@ -58,9 +60,9 @@ def load_robot_config_from_robot_toml_str(__s: str) -> RobotConfig:
58
60
 
59
61
 
60
62
  def load_config_from_robot_toml_str(
61
- config_type: Type[_ConfigType], __s: str, tool_name: Optional[str] = None
63
+ config_type: Type[_ConfigType], data: Union[str, Dict[str, Any]], tool_name: Optional[str] = None
62
64
  ) -> _ConfigType:
63
- dict_data = tomllib.loads(__s)
65
+ dict_data = tomllib.loads(data) if isinstance(data, str) else data
64
66
 
65
67
  if tool_name:
66
68
  try:
@@ -73,34 +75,39 @@ def load_config_from_robot_toml_str(
73
75
  return from_dict(dict_data, config_type)
74
76
 
75
77
 
76
- def load_config_from_pyproject_toml_str(config_type: Type[_ConfigType], tool_name: str, __s: str) -> _ConfigType:
77
- dict_data = tomllib.loads(__s)
78
+ def load_config_from_pyproject_toml_str(
79
+ config_type: Type[_ConfigType], tool_name: str, data: Union[str, Dict[str, Any]]
80
+ ) -> _ConfigType:
81
+ dict_data = tomllib.loads(data) if isinstance(data, str) else data
78
82
 
79
83
  return from_dict(dict_data.get("tool", {}).get(tool_name, {}), config_type)
80
84
 
81
85
 
82
86
  def _load_config_data_from_path(
83
87
  config_type: Type[_ConfigType],
84
- tool_name: str,
88
+ pyproject_toml_tool_name: str,
85
89
  robot_toml_tool_name: Optional[str],
86
- __path: Path,
90
+ path: Path,
91
+ data: Optional[Dict[str, Any]] = None,
87
92
  ) -> _ConfigType:
88
93
  try:
89
- if __path.name == PYPROJECT_TOML:
90
- return load_config_from_pyproject_toml_str(config_type, tool_name, __path.read_text("utf-8"))
94
+ if path.name == PYPROJECT_TOML:
95
+ return load_config_from_pyproject_toml_str(
96
+ config_type, pyproject_toml_tool_name, path.read_text("utf-8") if data is None else data
97
+ )
91
98
 
92
- if __path.name == ROBOT_TOML or __path.name == LOCAL_ROBOT_TOML or __path.suffix == ".toml":
99
+ if path.name == ROBOT_TOML or path.name == LOCAL_ROBOT_TOML or path.suffix == ".toml":
93
100
  return load_config_from_robot_toml_str(
94
101
  config_type,
95
- __path.read_text("utf-8"),
102
+ path.read_text("utf-8") if data is None else data,
96
103
  tool_name=robot_toml_tool_name,
97
104
  )
98
105
  raise TypeError("Unknown config file type.")
99
106
 
100
107
  except ValueError as e:
101
- raise ConfigValueError(__path, f'Parsing "{__path}" failed: {e}') from e
108
+ raise ConfigValueError(path, f'Parsing "{path}" failed: {e}') from e
102
109
  except TypeError as e:
103
- raise ConfigTypeError(__path, f'Parsing "{__path}" failed: {e}') from e
110
+ raise ConfigTypeError(path, f'Parsing "{path}" failed: {e}') from e
104
111
 
105
112
 
106
113
  def get_default_config() -> RobotConfig:
@@ -113,39 +120,85 @@ def get_default_config() -> RobotConfig:
113
120
  def load_config_from_path(
114
121
  config_type: Type[_ConfigType],
115
122
  *__paths: Union[Path, Tuple[Path, ConfigType]],
116
- tool_name: str,
123
+ pyproject_toml_tool_name: str,
117
124
  robot_toml_tool_name: Optional[str] = None,
125
+ extra_tools: Optional[Dict[str, Type[Any]]] = None,
126
+ verbose_callback: Optional[Callable[[str], None]] = None,
118
127
  ) -> _ConfigType:
119
128
  result = config_type()
129
+ tools: Optional[Dict[str, Any]] = (
130
+ {} if extra_tools and is_dataclass(result) and any(f for f in fields(result) if f.name == "tool") else None
131
+ )
120
132
 
121
133
  for __path in __paths:
122
134
  if isinstance(__path, tuple):
123
135
  path, c_type = __path
124
136
  if path.name == "__no_user_config__.toml" and c_type == ConfigType.DEFAULT_CONFIG_TOML:
137
+ if verbose_callback:
138
+ verbose_callback("Load default configuration.")
125
139
  result.add_options(get_default_config())
126
140
  continue
127
141
 
142
+ if verbose_callback:
143
+ verbose_callback(f"Load configuration from {__path if isinstance(__path, Path) else __path[0]}")
144
+
145
+ p = __path if isinstance(__path, Path) else __path[0]
146
+ data = tomllib.loads(p.read_text("utf-8"))
147
+
128
148
  result.add_options(
129
149
  _load_config_data_from_path(
130
150
  config_type,
131
- tool_name,
151
+ pyproject_toml_tool_name,
132
152
  robot_toml_tool_name,
133
- __path if isinstance(__path, Path) else __path[0],
153
+ p,
154
+ data,
134
155
  )
135
156
  )
136
157
 
158
+ if tools is not None and extra_tools:
159
+ for tool_name, tool_config in extra_tools.items():
160
+ if tool_name not in tools:
161
+ tools[tool_name] = tool_config()
162
+
163
+ tool = tools[tool_name]
164
+ tool.add_options(
165
+ _load_config_data_from_path(
166
+ tool_config,
167
+ tool_name,
168
+ tool_name,
169
+ p,
170
+ data,
171
+ )
172
+ )
173
+ if tools is not None:
174
+ setattr(result, "tool", tools)
175
+
137
176
  return result
138
177
 
139
178
 
140
179
  def load_robot_config_from_path(
141
180
  *__paths: Union[Path, Tuple[Path, ConfigType]],
181
+ extra_tools: Optional[Dict[str, Type[Any]]] = None,
182
+ verbose_callback: Optional[Callable[[str], None]] = None,
142
183
  ) -> RobotConfig:
143
- return load_config_from_path(RobotConfig, *__paths, tool_name="robot")
184
+ return load_config_from_path(
185
+ RobotConfig,
186
+ *__paths,
187
+ pyproject_toml_tool_name="robot",
188
+ extra_tools=extra_tools,
189
+ verbose_callback=verbose_callback,
190
+ )
144
191
 
145
192
 
146
193
  def find_project_root(
147
194
  *sources: Union[str, Path],
195
+ root_folder: Optional[Path] = None,
196
+ no_vcs: bool = False,
148
197
  ) -> Tuple[Optional[Path], DiscoverdBy]:
198
+
199
+ if root_folder:
200
+ return root_folder.absolute(), DiscoverdBy.COMMAND_LINE
201
+
149
202
  if not sources:
150
203
  sources = (str(Path.cwd().absolute()),)
151
204
 
@@ -168,11 +221,12 @@ def find_project_root(
168
221
  if (directory / PYPROJECT_TOML).is_file():
169
222
  return directory, DiscoverdBy.PYPROJECT_TOML
170
223
 
171
- if (directory / ".git").exists():
172
- return directory, DiscoverdBy.GIT
224
+ if not no_vcs:
225
+ if (directory / ".git").exists():
226
+ return directory, DiscoverdBy.GIT
173
227
 
174
- if (directory / ".hg").is_dir():
175
- return directory, DiscoverdBy.HG
228
+ if (directory / ".hg").is_dir():
229
+ return directory, DiscoverdBy.HG
176
230
 
177
231
  return None, DiscoverdBy.NOT_FOUND
178
232
 
@@ -245,65 +245,6 @@ class BaseOptions(ValidateMixin):
245
245
  def _decode_case(cls, s: str) -> str:
246
246
  return s.replace("-", "_")
247
247
 
248
- """Base class for all options."""
249
-
250
- def build_command_line(self) -> List[str]:
251
- """Build the arguments to pass to Robot Framework."""
252
- result = []
253
-
254
- sorted_fields = sorted(
255
- (f for f in dataclasses.fields(self) if f.metadata.get("robot_priority", -1) > -1),
256
- key=lambda f: f.metadata.get("robot_priority", 0),
257
- )
258
-
259
- def append_name(field: "dataclasses.Field[Any]", add_flag: Optional[str] = None) -> None:
260
- if "robot_short_name" in field.metadata:
261
- result.append(f"-{field.metadata['robot_short_name']}")
262
- elif "robot_name" in field.metadata:
263
- result.append(f"--{'no' if add_flag else ''}{field.metadata['robot_name']}")
264
-
265
- for field in sorted_fields:
266
- try:
267
- value = getattr(self, field.name)
268
- if value is None:
269
- continue
270
-
271
- if field.metadata.get("robot_is_flag", False):
272
- if value is None or value == Flag.DEFAULT:
273
- continue
274
-
275
- append_name(
276
- field,
277
- bool(value) != field.metadata.get("robot_flag_default", True),
278
- )
279
-
280
- continue
281
-
282
- if isinstance(value, list):
283
- for item in value:
284
- append_name(field)
285
- if isinstance(item, dict):
286
- for k, v in item.items():
287
- result.append(f"{k}:{v}")
288
- else:
289
- result.append(str(item))
290
- elif isinstance(value, dict):
291
- for key, item in value.items():
292
- append_name(field)
293
- if isinstance(item, list):
294
- str_item = [str(i) for i in item]
295
- separator = ";" if any(True for s in str_item if ":" in s) else ":"
296
- result.append(f"{key}{separator if item else ''}{separator.join(str_item)}")
297
- else:
298
- result.append(f"{key}:{item}")
299
- else:
300
- append_name(field)
301
- result.append(str(value))
302
- except EvaluationError as e:
303
- raise ValueError(f"Evaluation of '{field.name}' failed: {e!s}") from e
304
-
305
- return result
306
-
307
248
  @staticmethod
308
249
  def _verified_value(name: str, value: Any, types: Union[type, Tuple[type, ...]], target: Any) -> Any:
309
250
  errors = validate_types(types, value)
@@ -315,6 +256,44 @@ class BaseOptions(ValidateMixin):
315
256
  )
316
257
  return value
317
258
 
259
+ def evaluated(
260
+ self,
261
+ verbose_callback: Optional[Callable[[Union[str, Callable[[], Any]]], None]] = None,
262
+ error_callback: Optional[Callable[[Union[str, Callable[[], Any]]], None]] = None,
263
+ ) -> Self:
264
+ if verbose_callback is not None:
265
+ verbose_callback("Evaluating options")
266
+
267
+ result = dataclasses.replace(self)
268
+ for f in dataclasses.fields(result):
269
+ try:
270
+ if isinstance(getattr(result, f.name), Condition):
271
+ setattr(result, f.name, getattr(result, f.name).evaluate())
272
+ elif isinstance(getattr(result, f.name), Expression):
273
+ setattr(result, f.name, getattr(result, f.name).evaluate())
274
+ elif isinstance(getattr(result, f.name), list):
275
+ setattr(
276
+ result,
277
+ f.name,
278
+ [e.evaluate() if isinstance(e, Expression) else e for e in getattr(result, f.name)],
279
+ )
280
+ elif isinstance(getattr(result, f.name), dict):
281
+ setattr(
282
+ result,
283
+ f.name,
284
+ {
285
+ k: e.evaluate() if isinstance(e, Expression) else e
286
+ for k, e in getattr(result, f.name).items()
287
+ },
288
+ )
289
+ except EvaluationError as e:
290
+ message = f"Evaluation of '{f.name}' failed: {type(e).__name__}: {e}"
291
+ if error_callback is None:
292
+ raise ValueError(message) from e
293
+ error_callback(message)
294
+
295
+ return result
296
+
318
297
  def add_options(self, config: "BaseOptions", combine_extends: bool = False) -> None:
319
298
  type_hints = get_type_hints(type(self))
320
299
  base_field_names = [f.name for f in dataclasses.fields(self)]
@@ -352,6 +331,8 @@ class BaseOptions(ValidateMixin):
352
331
  setattr(self, old_field_name, [*old, *new])
353
332
  elif isinstance(old, tuple):
354
333
  setattr(self, old_field_name, (*old, *new))
334
+ elif isinstance(old, BaseOptions):
335
+ old.add_options(new)
355
336
  else:
356
337
  setattr(self, old_field_name, new)
357
338
  continue
@@ -370,41 +351,65 @@ class BaseOptions(ValidateMixin):
370
351
  if new is not None:
371
352
  setattr(self, f.name, new)
372
353
 
373
- def evaluated(
374
- self,
375
- verbose_callback: Optional[Callable[[Union[str, Callable[[], Any]]], None]] = None,
376
- error_callback: Optional[Callable[[Union[str, Callable[[], Any]]], None]] = None,
377
- ) -> Self:
378
- if verbose_callback is not None:
379
- verbose_callback("Evaluating options")
380
354
 
381
- result = dataclasses.replace(self)
382
- for f in dataclasses.fields(result):
355
+ @dataclass
356
+ class RobotBaseOptions(BaseOptions):
357
+ """Base class for all options."""
358
+
359
+ def build_command_line(self) -> List[str]:
360
+ """Build the arguments to pass to Robot Framework."""
361
+ result = []
362
+
363
+ sorted_fields = sorted(
364
+ (f for f in dataclasses.fields(self) if f.metadata.get("robot_priority", -1) > -1),
365
+ key=lambda f: f.metadata.get("robot_priority", 0),
366
+ )
367
+
368
+ def append_name(field: "dataclasses.Field[Any]", add_flag: Optional[str] = None) -> None:
369
+ if "robot_short_name" in field.metadata:
370
+ result.append(f"-{field.metadata['robot_short_name']}")
371
+ elif "robot_name" in field.metadata:
372
+ result.append(f"--{'no' if add_flag else ''}{field.metadata['robot_name']}")
373
+
374
+ for field in sorted_fields:
383
375
  try:
384
- if isinstance(getattr(result, f.name), Condition):
385
- setattr(result, f.name, getattr(result, f.name).evaluate())
386
- elif isinstance(getattr(result, f.name), Expression):
387
- setattr(result, f.name, getattr(result, f.name).evaluate())
388
- elif isinstance(getattr(result, f.name), list):
389
- setattr(
390
- result,
391
- f.name,
392
- [e.evaluate() if isinstance(e, Expression) else e for e in getattr(result, f.name)],
393
- )
394
- elif isinstance(getattr(result, f.name), dict):
395
- setattr(
396
- result,
397
- f.name,
398
- {
399
- k: e.evaluate() if isinstance(e, Expression) else e
400
- for k, e in getattr(result, f.name).items()
401
- },
376
+ value = getattr(self, field.name)
377
+ if value is None:
378
+ continue
379
+
380
+ if field.metadata.get("robot_is_flag", False):
381
+ if value is None or value == Flag.DEFAULT:
382
+ continue
383
+
384
+ append_name(
385
+ field,
386
+ bool(value) != field.metadata.get("robot_flag_default", True),
402
387
  )
388
+
389
+ continue
390
+
391
+ if isinstance(value, list):
392
+ for item in value:
393
+ append_name(field)
394
+ if isinstance(item, dict):
395
+ for k, v in item.items():
396
+ result.append(f"{k}:{v}")
397
+ else:
398
+ result.append(str(item))
399
+ elif isinstance(value, dict):
400
+ for key, item in value.items():
401
+ append_name(field)
402
+ if isinstance(item, list):
403
+ str_item = [str(i) for i in item]
404
+ separator = ";" if any(True for s in str_item if ":" in s) else ":"
405
+ result.append(f"{key}{separator if item else ''}{separator.join(str_item)}")
406
+ else:
407
+ result.append(f"{key}:{item}")
408
+ else:
409
+ append_name(field)
410
+ result.append(str(value))
403
411
  except EvaluationError as e:
404
- message = f"Evaluation of '{f.name}' failed: {type(e).__name__}: {e}"
405
- if error_callback is None:
406
- raise ValueError(message) from e
407
- error_callback(message)
412
+ raise ValueError(f"Evaluation of '{field.name}' failed: {e!s}") from e
408
413
 
409
414
  return result
410
415
 
@@ -413,7 +418,7 @@ class BaseOptions(ValidateMixin):
413
418
 
414
419
 
415
420
  @dataclass
416
- class CommonOptions(BaseOptions):
421
+ class CommonOptions(RobotBaseOptions):
417
422
  """Common options for all _robot_ commands."""
418
423
 
419
424
  console_colors: Optional[Literal["auto", "on", "ansi", "off"]] = field(
@@ -972,7 +977,7 @@ class CommonOptions(BaseOptions):
972
977
 
973
978
 
974
979
  @dataclass
975
- class CommonExtendOptions(BaseOptions):
980
+ class CommonExtendOptions(RobotBaseOptions):
976
981
  """Extra common options for all _robot_ commands."""
977
982
 
978
983
  extend_excludes: Optional[List[Union[str, StringExpression]]] = field(
@@ -1297,7 +1302,7 @@ class CommonExtendOptions(BaseOptions):
1297
1302
 
1298
1303
 
1299
1304
  @dataclass
1300
- class RobotOptions(BaseOptions):
1305
+ class RobotOptions(RobotBaseOptions):
1301
1306
  """Options for _robot_ command."""
1302
1307
 
1303
1308
  console: Optional[Literal["verbose", "dotted", "skipped", "quiet", "none"]] = field(
@@ -1674,7 +1679,7 @@ class RobotOptions(BaseOptions):
1674
1679
 
1675
1680
 
1676
1681
  @dataclass
1677
- class RobotExtendOptions(BaseOptions):
1682
+ class RobotExtendOptions(RobotBaseOptions):
1678
1683
  """Extra options for _robot_ command."""
1679
1684
 
1680
1685
  extend_languages: Optional[List[Union[str, StringExpression]]] = field(
@@ -1789,7 +1794,7 @@ class RobotExtendOptions(BaseOptions):
1789
1794
 
1790
1795
 
1791
1796
  @dataclass
1792
- class RebotOptions(BaseOptions):
1797
+ class RebotOptions(RobotBaseOptions):
1793
1798
  """Options for _rebot_ command."""
1794
1799
 
1795
1800
  end_time: Optional[Union[str, StringExpression]] = field(
@@ -1884,7 +1889,7 @@ class RebotOptions(BaseOptions):
1884
1889
 
1885
1890
 
1886
1891
  @dataclass
1887
- class LibDocOptions(BaseOptions):
1892
+ class LibDocOptions(RobotBaseOptions):
1888
1893
  """Options for _libdoc_ command."""
1889
1894
 
1890
1895
  doc_format: Optional[Literal["ROBOT", "HTML", "TEXT", "REST"]] = field(
@@ -1976,7 +1981,7 @@ class LibDocOptions(BaseOptions):
1976
1981
 
1977
1982
 
1978
1983
  @dataclass
1979
- class LibDocExtendOptions(BaseOptions):
1984
+ class LibDocExtendOptions(RobotBaseOptions):
1980
1985
  """Extra options for _libdoc_ command."""
1981
1986
 
1982
1987
  extend_python_path: Optional[List[Union[str, StringExpression]]] = field(
@@ -1992,7 +1997,7 @@ class LibDocExtendOptions(BaseOptions):
1992
1997
 
1993
1998
 
1994
1999
  @dataclass
1995
- class TestDocOptions(BaseOptions):
2000
+ class TestDocOptions(RobotBaseOptions):
1996
2001
  """Options for _testdoc_ command."""
1997
2002
 
1998
2003
  doc: Optional[Union[str, StringExpression]] = field(
@@ -2090,7 +2095,7 @@ class TestDocOptions(BaseOptions):
2090
2095
 
2091
2096
 
2092
2097
  @dataclass
2093
- class TestDocExtendOptions(BaseOptions):
2098
+ class TestDocExtendOptions(RobotBaseOptions):
2094
2099
  """Extra options for _testdoc_ command."""
2095
2100
 
2096
2101
  extend_excludes: Optional[List[Union[str, StringExpression]]] = field(
@@ -2378,7 +2383,7 @@ class RobotConfig(RobotExtendBaseProfile):
2378
2383
 
2379
2384
  extend_profiles: Optional[Dict[str, RobotProfile]] = field(description="Extra execution profiles.")
2380
2385
 
2381
- tool: Any = field(description="Tool configurations.")
2386
+ tool: Optional[Dict[str, Any]] = field(description="Tool configurations.")
2382
2387
 
2383
2388
  def _select_profiles(
2384
2389
  self,
@@ -43,10 +43,12 @@ def get_user_config_file(
43
43
  def get_config_files(
44
44
  paths: Optional[Sequence[Union[str, Path]]] = None,
45
45
  config_files: Optional[Sequence[Path]] = None,
46
+ root_folder: Optional[Path] = None,
47
+ no_vcs: bool = False,
46
48
  *,
47
49
  verbose_callback: Optional[Callable[[str], None]] = None,
48
50
  ) -> Tuple[Sequence[Tuple[Path, ConfigType]], Optional[Path], DiscoverdBy]:
49
- root_folder, discovered_by = find_project_root(*(paths or []))
51
+ root_folder, discovered_by = find_project_root(*(paths or []), root_folder=root_folder, no_vcs=no_vcs)
50
52
 
51
53
  if root_folder is None:
52
54
  root_folder = Path.cwd()
@@ -54,7 +56,7 @@ def get_config_files(
54
56
  verbose_callback(f"Cannot detect root folder. Use current folder '{root_folder}' as root.")
55
57
 
56
58
  if verbose_callback:
57
- verbose_callback(f"Found root at:\n {root_folder} ({discovered_by.value})")
59
+ verbose_callback(f"Use root at:\n {root_folder} ({discovered_by.value})")
58
60
 
59
61
  if config_files:
60
62
  if verbose_callback: