robotcode-robot 0.91.0__py3-none-any.whl → 0.93.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: