robotcode-robot 0.92.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.92.0"
1
+ __version__ = "0.93.0"
@@ -28,6 +28,7 @@ class DiscoverdBy(str, Enum):
28
28
  LOCAL_ROBOT_TOML = ".robot.toml (local file)"
29
29
  USER_DEFAULT_CONFIG_TOML = "robot.toml (user default config)"
30
30
  NOT_FOUND = "not found"
31
+ COMMAND_LINE = "command line"
31
32
 
32
33
 
33
34
  class ConfigType(str, Enum):
@@ -191,7 +192,13 @@ def load_robot_config_from_path(
191
192
 
192
193
  def find_project_root(
193
194
  *sources: Union[str, Path],
195
+ root_folder: Optional[Path] = None,
196
+ no_vcs: bool = False,
194
197
  ) -> Tuple[Optional[Path], DiscoverdBy]:
198
+
199
+ if root_folder:
200
+ return root_folder.absolute(), DiscoverdBy.COMMAND_LINE
201
+
195
202
  if not sources:
196
203
  sources = (str(Path.cwd().absolute()),)
197
204
 
@@ -214,11 +221,12 @@ def find_project_root(
214
221
  if (directory / PYPROJECT_TOML).is_file():
215
222
  return directory, DiscoverdBy.PYPROJECT_TOML
216
223
 
217
- if (directory / ".git").exists():
218
- return directory, DiscoverdBy.GIT
224
+ if not no_vcs:
225
+ if (directory / ".git").exists():
226
+ return directory, DiscoverdBy.GIT
219
227
 
220
- if (directory / ".hg").is_dir():
221
- return directory, DiscoverdBy.HG
228
+ if (directory / ".hg").is_dir():
229
+ return directory, DiscoverdBy.HG
222
230
 
223
231
  return None, DiscoverdBy.NOT_FOUND
224
232
 
@@ -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:
@@ -160,7 +160,7 @@ class VariableMatcher:
160
160
  return False
161
161
 
162
162
  def __hash__(self) -> int:
163
- return hash(self.name)
163
+ return hash(self.normalized_name)
164
164
 
165
165
  def __str__(self) -> str:
166
166
  return self.name
@@ -252,7 +252,7 @@ class BuiltInVariableDefinition(VariableDefinition):
252
252
 
253
253
  @single_call
254
254
  def __hash__(self) -> int:
255
- return hash((type(self), self.name, self.type))
255
+ return hash((type(self), self.name, self.type, None, None))
256
256
 
257
257
 
258
258
  @dataclass
@@ -64,6 +64,7 @@ from .library_doc import (
64
64
  get_variables_doc,
65
65
  is_library_by_path,
66
66
  is_variables_by_path,
67
+ replace_variables_scalar,
67
68
  resolve_args,
68
69
  resolve_variable,
69
70
  )
@@ -905,7 +906,8 @@ class ImportsManager:
905
906
  ):
906
907
  self._logger.debug(
907
908
  lambda: f"Ignore library {result.name or '' if result is not None else ''}"
908
- f" {result.origin or '' if result is not None else ''} for caching."
909
+ f" {result.origin or '' if result is not None else ''} for caching.",
910
+ context_name="import",
909
911
  )
910
912
  return None, import_name, ignore_arguments
911
913
 
@@ -1143,9 +1145,7 @@ class ImportsManager:
1143
1145
  base_dir: str,
1144
1146
  variables: Optional[Dict[str, Any]] = None,
1145
1147
  ) -> LibraryDoc:
1146
- meta, source, ignore_arguments = self.get_library_meta(name, base_dir, variables)
1147
-
1148
- self._logger.debug(lambda: f"Load Library {source}{args!r}")
1148
+ meta, _source, ignore_arguments = self.get_library_meta(name, base_dir, variables)
1149
1149
 
1150
1150
  if meta is not None and not meta.has_errors:
1151
1151
  meta_file = Path(self.lib_doc_cache_path, meta.filepath_base + ".meta.json")
@@ -1219,7 +1219,7 @@ class ImportsManager:
1219
1219
 
1220
1220
  meta_file.write_text(as_json(meta), "utf-8")
1221
1221
  else:
1222
- self._logger.debug(lambda: f"Skip caching library {name}{args!r}")
1222
+ self._logger.debug(lambda: f"Skip caching library {name}{args!r}", context_name="import")
1223
1223
  except (SystemExit, KeyboardInterrupt):
1224
1224
  raise
1225
1225
  except BaseException as e:
@@ -1236,37 +1236,37 @@ class ImportsManager:
1236
1236
  sentinel: Any = None,
1237
1237
  variables: Optional[Dict[str, Any]] = None,
1238
1238
  ) -> LibraryDoc:
1239
+ with self._logger.measure_time(lambda: f"loading library {name}{args!r}", context_name="import"):
1240
+ source = self.find_library(name, base_dir, variables)
1239
1241
 
1240
- source = self.find_library(name, base_dir, variables)
1241
-
1242
- resolved_args = resolve_args(
1243
- args,
1244
- str(self.root_folder),
1245
- base_dir,
1246
- self.get_resolvable_command_line_variables(),
1247
- variables,
1248
- )
1249
- entry_key = _LibrariesEntryKey(source, resolved_args)
1250
-
1251
- with self._libaries_lock:
1252
- if entry_key not in self._libaries:
1253
- self._libaries[entry_key] = _LibrariesEntry(
1254
- self,
1255
- name,
1256
- args,
1257
- str(self.root_folder),
1258
- base_dir,
1259
- self._get_library_libdoc_handler(variables),
1260
- ignore_reference=sentinel is None,
1261
- )
1242
+ resolved_args = resolve_args(
1243
+ args,
1244
+ str(self.root_folder),
1245
+ base_dir,
1246
+ self.get_resolvable_command_line_variables(),
1247
+ variables,
1248
+ )
1249
+ entry_key = _LibrariesEntryKey(source, resolved_args)
1250
+
1251
+ with self._libaries_lock:
1252
+ if entry_key not in self._libaries:
1253
+ self._libaries[entry_key] = _LibrariesEntry(
1254
+ self,
1255
+ name,
1256
+ args,
1257
+ str(self.root_folder),
1258
+ base_dir,
1259
+ self._get_library_libdoc_handler(variables),
1260
+ ignore_reference=sentinel is None,
1261
+ )
1262
1262
 
1263
- entry = self._libaries[entry_key]
1263
+ entry = self._libaries[entry_key]
1264
1264
 
1265
- if not entry.ignore_reference and sentinel is not None and sentinel not in entry.references:
1266
- weakref.finalize(sentinel, self.__remove_library_entry, entry_key, entry)
1267
- entry.references.add(sentinel)
1265
+ if not entry.ignore_reference and sentinel is not None and sentinel not in entry.references:
1266
+ weakref.finalize(sentinel, self.__remove_library_entry, entry_key, entry)
1267
+ entry.references.add(sentinel)
1268
1268
 
1269
- return entry.get_libdoc()
1269
+ return entry.get_libdoc()
1270
1270
 
1271
1271
  @_logger.call
1272
1272
  def get_libdoc_from_model(
@@ -1313,7 +1313,7 @@ class ImportsManager:
1313
1313
  resolve_variables: bool = True,
1314
1314
  resolve_command_line_vars: bool = True,
1315
1315
  ) -> VariablesDoc:
1316
- meta, source = self.get_variables_meta(
1316
+ meta, _source = self.get_variables_meta(
1317
1317
  name,
1318
1318
  base_dir,
1319
1319
  variables,
@@ -1321,7 +1321,6 @@ class ImportsManager:
1321
1321
  resolve_command_line_vars=resolve_command_line_vars,
1322
1322
  )
1323
1323
 
1324
- self._logger.debug(lambda: f"Load variables {source}{args!r}")
1325
1324
  if meta is not None:
1326
1325
  meta_file = Path(
1327
1326
  self.variables_doc_cache_path,
@@ -1391,7 +1390,7 @@ class ImportsManager:
1391
1390
  raise RuntimeError(f"Cannot write spec file for variables '{name}' to '{spec_file}'") from e
1392
1391
  meta_file.write_text(as_json(meta), "utf-8")
1393
1392
  else:
1394
- self._logger.debug(lambda: f"Skip caching variables {name}{args!r}")
1393
+ self._logger.debug(lambda: f"Skip caching variables {name}{args!r}", context_name="import")
1395
1394
  except (SystemExit, KeyboardInterrupt):
1396
1395
  raise
1397
1396
  except BaseException as e:
@@ -1410,39 +1409,40 @@ class ImportsManager:
1410
1409
  resolve_variables: bool = True,
1411
1410
  resolve_command_line_vars: bool = True,
1412
1411
  ) -> VariablesDoc:
1413
- source = self.find_variables(name, base_dir, variables, resolve_variables, resolve_command_line_vars)
1414
-
1415
- if args:
1416
- resolved_args = resolve_args(
1417
- args,
1418
- str(self.root_folder),
1419
- base_dir,
1420
- self.get_resolvable_command_line_variables() if resolve_command_line_vars else None,
1421
- variables,
1422
- )
1423
- else:
1424
- resolved_args = ()
1412
+ with self._logger.measure_time(lambda: f"getting libdoc for variables import {name}", context_name="import"):
1413
+ source = self.find_variables(name, base_dir, variables, resolve_variables, resolve_command_line_vars)
1425
1414
 
1426
- entry_key = _VariablesEntryKey(source, resolved_args)
1427
-
1428
- with self._variables_lock:
1429
- if entry_key not in self._variables:
1430
- self._variables[entry_key] = _VariablesEntry(
1431
- name,
1432
- resolved_args,
1415
+ if args:
1416
+ resolved_args = resolve_args(
1417
+ args,
1433
1418
  str(self.root_folder),
1434
1419
  base_dir,
1435
- self,
1436
- self._get_variables_libdoc_handler(variables, resolve_variables, resolve_command_line_vars),
1420
+ self.get_resolvable_command_line_variables() if resolve_command_line_vars else None,
1421
+ variables,
1437
1422
  )
1423
+ else:
1424
+ resolved_args = ()
1425
+
1426
+ entry_key = _VariablesEntryKey(source, resolved_args)
1427
+
1428
+ with self._variables_lock:
1429
+ if entry_key not in self._variables:
1430
+ self._variables[entry_key] = _VariablesEntry(
1431
+ name,
1432
+ resolved_args,
1433
+ str(self.root_folder),
1434
+ base_dir,
1435
+ self,
1436
+ self._get_variables_libdoc_handler(variables, resolve_variables, resolve_command_line_vars),
1437
+ )
1438
1438
 
1439
- entry = self._variables[entry_key]
1439
+ entry = self._variables[entry_key]
1440
1440
 
1441
- if sentinel is not None and sentinel not in entry.references:
1442
- entry.references.add(sentinel)
1443
- weakref.finalize(sentinel, self.__remove_variables_entry, entry_key, entry)
1441
+ if sentinel is not None and sentinel not in entry.references:
1442
+ entry.references.add(sentinel)
1443
+ weakref.finalize(sentinel, self.__remove_variables_entry, entry_key, entry)
1444
1444
 
1445
- return entry.get_libdoc()
1445
+ return entry.get_libdoc()
1446
1446
 
1447
1447
  @_logger.call
1448
1448
  def _get_entry_for_resource_import(
@@ -1455,7 +1455,7 @@ class ImportsManager:
1455
1455
  source = self.find_resource(name, base_dir, variables=variables)
1456
1456
 
1457
1457
  def _get_document() -> TextDocument:
1458
- self._logger.debug(lambda: f"Load resource {name} from source {source}")
1458
+ self._logger.debug(lambda: f"Load resource {name} from source {source}", context_name="import")
1459
1459
 
1460
1460
  source_path = normalized_path(Path(source))
1461
1461
  extension = source_path.suffix
@@ -1488,9 +1488,10 @@ class ImportsManager:
1488
1488
  sentinel: Any = None,
1489
1489
  variables: Optional[Dict[str, Any]] = None,
1490
1490
  ) -> Tuple["Namespace", LibraryDoc]:
1491
- entry = self._get_entry_for_resource_import(name, base_dir, sentinel, variables)
1491
+ with self._logger.measure_time(lambda: f"getting namespace and libdoc for {name}", context_name="import"):
1492
+ entry = self._get_entry_for_resource_import(name, base_dir, sentinel, variables)
1492
1493
 
1493
- return entry.get_namespace(), entry.get_libdoc()
1494
+ return entry.get_namespace(), entry.get_libdoc()
1494
1495
 
1495
1496
  def get_namespace_for_resource_import(
1496
1497
  self,
@@ -1572,3 +1573,17 @@ class ImportsManager:
1572
1573
  self.get_resolvable_command_line_variables(),
1573
1574
  variables,
1574
1575
  )
1576
+
1577
+ def replace_variables_scalar(
1578
+ self,
1579
+ scalar: str,
1580
+ base_dir: str = ".",
1581
+ variables: Optional[Dict[str, Any]] = None,
1582
+ ) -> Any:
1583
+ return replace_variables_scalar(
1584
+ scalar,
1585
+ str(self.root_folder),
1586
+ base_dir,
1587
+ self.get_resolvable_command_line_variables(),
1588
+ variables,
1589
+ )
@@ -45,12 +45,8 @@ from robot.parsing.lexer.tokens import Token
45
45
  from robot.parsing.lexer.tokens import Token as RobotToken
46
46
  from robot.parsing.model.blocks import Keyword
47
47
  from robot.parsing.model.statements import Arguments, KeywordName
48
- from robot.running.arguments.argumentresolver import (
49
- ArgumentResolver,
50
- DictToKwargs,
51
- NamedArgumentResolver,
52
- VariableReplacer,
53
- )
48
+ from robot.running.arguments.argumentresolver import ArgumentResolver, DictToKwargs, NamedArgumentResolver
49
+ from robot.running.arguments.argumentresolver import VariableReplacer as ArgumentsVariableReplacer
54
50
  from robot.running.arguments.argumentspec import ArgInfo
55
51
  from robot.running.arguments.argumentspec import (
56
52
  ArgumentSpec as RobotArgumentSpec,
@@ -64,6 +60,7 @@ from robot.utils.robotpath import find_file as robot_find_file
64
60
  from robot.variables import Variables
65
61
  from robot.variables.filesetter import PythonImporter, YamlImporter
66
62
  from robot.variables.finders import VariableFinder
63
+ from robot.variables.replacer import VariableReplacer
67
64
  from robot.variables.search import contains_variable
68
65
  from robotcode.core.lsp.types import Position, Range
69
66
  from robotcode.core.utils.path import normalized_path
@@ -578,9 +575,9 @@ class ArgumentSpec:
578
575
 
579
576
  positional, named = MyNamedArgumentResolver(self.__robot_arguments).resolve(arguments, variables)
580
577
  if get_robot_version() < (7, 0):
581
- positional, named = VariableReplacer(resolve_variables_until).replace(positional, named, variables)
578
+ positional, named = ArgumentsVariableReplacer(resolve_variables_until).replace(positional, named, variables)
582
579
  else:
583
- positional, named = VariableReplacer(self.__robot_arguments, resolve_variables_until).replace(
580
+ positional, named = ArgumentsVariableReplacer(self.__robot_arguments, resolve_variables_until).replace(
584
581
  positional, named, variables
585
582
  )
586
583
  positional, named = DictToKwargs(self.__robot_arguments, dict_to_kwargs).handle(positional, named)
@@ -1585,6 +1582,26 @@ def resolve_variable(
1585
1582
  return name.replace("\\", "\\\\")
1586
1583
 
1587
1584
 
1585
+ def replace_variables_scalar(
1586
+ scalar: str,
1587
+ working_dir: str = ".",
1588
+ base_dir: str = ".",
1589
+ command_line_variables: Optional[Dict[str, Optional[Any]]] = None,
1590
+ variables: Optional[Dict[str, Optional[Any]]] = None,
1591
+ ) -> Any:
1592
+
1593
+ _update_env(working_dir)
1594
+
1595
+ if contains_variable(scalar, "$@&%"):
1596
+ robot_variables = resolve_robot_variables(working_dir, base_dir, command_line_variables, variables)
1597
+ if get_robot_version() >= (6, 1):
1598
+ return VariableReplacer(robot_variables).replace_scalar(scalar.replace("\\", "\\\\"))
1599
+
1600
+ return VariableReplacer(robot_variables.store).replace_scalar(scalar.replace("\\", "\\\\"))
1601
+
1602
+ return scalar.replace("\\", "\\\\")
1603
+
1604
+
1588
1605
  @contextmanager
1589
1606
  def _std_capture() -> Iterator[io.StringIO]:
1590
1607
  old__stdout__ = sys.__stdout__
@@ -694,7 +694,14 @@ class Namespace:
694
694
  self._own_variables: Optional[List[VariableDefinition]] = None
695
695
  self._own_variables_lock = RLock(default_timeout=120, name="Namespace.own_variables")
696
696
  self._global_variables: Optional[List[VariableDefinition]] = None
697
+ self._global_variables_dict: Optional[Dict[VariableMatcher, VariableDefinition]] = None
697
698
  self._global_variables_lock = RLock(default_timeout=120, name="Namespace.global_variables")
699
+ self._global_variables_dict_lock = RLock(default_timeout=120, name="Namespace.global_variables_dict")
700
+
701
+ self._global_resolvable_variables: Optional[Dict[str, Any]] = None
702
+ self._global_resolvable_variables_lock = RLock(
703
+ default_timeout=120, name="Namespace._global_resolvable_variables_lock"
704
+ )
698
705
 
699
706
  self._suite_variables: Optional[Dict[str, Any]] = None
700
707
  self._suite_variables_lock = RLock(default_timeout=120, name="Namespace.global_variables")
@@ -993,9 +1000,12 @@ class Namespace:
993
1000
  return self.imports_manager.get_command_line_variables()
994
1001
 
995
1002
  def _reset_global_variables(self) -> None:
996
- with self._global_variables_lock, self._suite_variables_lock:
997
- self._global_variables = None
998
- self._suite_variables = None
1003
+ with self._global_variables_lock, self._global_variables_dict_lock, self._suite_variables_lock:
1004
+ with self._global_resolvable_variables_lock:
1005
+ self._global_variables = None
1006
+ self._global_variables_dict = None
1007
+ self._suite_variables = None
1008
+ self._global_resolvable_variables = None
999
1009
 
1000
1010
  def get_global_variables(self) -> List[VariableDefinition]:
1001
1011
  with self._global_variables_lock:
@@ -1012,12 +1022,20 @@ class Namespace:
1012
1022
 
1013
1023
  return self._global_variables
1014
1024
 
1025
+ def get_global_variables_dict(self) -> Dict[VariableMatcher, VariableDefinition]:
1026
+ with self._global_variables_dict_lock:
1027
+ if self._global_variables_dict is None:
1028
+ self._global_variables_dict = {m: v for m, v in self.yield_variables()}
1029
+
1030
+ return self._global_variables_dict
1031
+
1015
1032
  def yield_variables(
1016
1033
  self,
1017
1034
  nodes: Optional[List[ast.AST]] = None,
1018
1035
  position: Optional[Position] = None,
1019
1036
  skip_commandline_variables: bool = False,
1020
1037
  skip_local_variables: bool = False,
1038
+ skip_global_variables: bool = False,
1021
1039
  ) -> Iterator[Tuple[VariableMatcher, VariableDefinition]]:
1022
1040
  yielded: Dict[VariableMatcher, VariableDefinition] = {}
1023
1041
 
@@ -1053,7 +1071,7 @@ class Namespace:
1053
1071
  else []
1054
1072
  )
1055
1073
  ],
1056
- self.get_global_variables(),
1074
+ self.get_global_variables() if not skip_global_variables else [],
1057
1075
  ):
1058
1076
  if var.matcher not in yielded:
1059
1077
  if skip_commandline_variables and isinstance(var, CommandLineVariableDefinition):
@@ -1063,17 +1081,7 @@ class Namespace:
1063
1081
 
1064
1082
  yield var.matcher, var
1065
1083
 
1066
- def get_suite_variables(
1067
- self,
1068
- nodes: Optional[List[ast.AST]] = None,
1069
- position: Optional[Position] = None,
1070
- ) -> Dict[str, Any]:
1071
- if nodes:
1072
- return {
1073
- v.name: v.value
1074
- for k, v in self.yield_variables(nodes, position, skip_commandline_variables=True)
1075
- if v.has_value
1076
- }
1084
+ def get_suite_variables(self) -> Dict[str, Any]:
1077
1085
  with self._suite_variables_lock:
1078
1086
  vars = {}
1079
1087
 
@@ -1093,11 +1101,21 @@ class Namespace:
1093
1101
  nodes: Optional[List[ast.AST]] = None,
1094
1102
  position: Optional[Position] = None,
1095
1103
  ) -> Dict[str, Any]:
1096
- return {
1097
- v.name: v.value
1098
- for k, v in self.yield_variables(nodes, position, skip_commandline_variables=True)
1099
- if v.has_value
1100
- }
1104
+ if nodes:
1105
+ return {
1106
+ v.name: v.value
1107
+ for k, v in self.yield_variables(nodes, position, skip_commandline_variables=True)
1108
+ if v.has_value
1109
+ }
1110
+
1111
+ with self._global_resolvable_variables_lock:
1112
+ if self._global_resolvable_variables is None:
1113
+ self._global_resolvable_variables = {
1114
+ v.name: v.value
1115
+ for k, v in self.yield_variables(nodes, position, skip_commandline_variables=True)
1116
+ if v.has_value
1117
+ }
1118
+ return self._global_resolvable_variables
1101
1119
 
1102
1120
  def get_variable_matchers(
1103
1121
  self,
@@ -1141,9 +1159,15 @@ class Namespace:
1141
1159
  position,
1142
1160
  skip_commandline_variables=skip_commandline_variables,
1143
1161
  skip_local_variables=skip_local_variables,
1162
+ skip_global_variables=True,
1144
1163
  ):
1145
1164
  if matcher == m:
1146
1165
  return v
1166
+
1167
+ result = self.get_global_variables_dict().get(matcher, None)
1168
+ if matcher is not None:
1169
+ return result
1170
+
1147
1171
  except InvalidVariableError:
1148
1172
  if not ignore_error:
1149
1173
  raise
@@ -1376,12 +1400,12 @@ class Namespace:
1376
1400
  source: Optional[str] = None,
1377
1401
  parent_import: Optional[Import] = None,
1378
1402
  parent_source: Optional[str] = None,
1379
- depth: int = 0,
1380
1403
  ) -> Optional[Dict[str, Any]]:
1381
1404
 
1382
- current_time = time.monotonic()
1383
- self._logger.debug(lambda: f"{' '*depth}start imports for {self.document if top_level else source}")
1384
- try:
1405
+ with self._logger.measure_time(
1406
+ lambda: f"loading imports for {self.source if top_level else source}",
1407
+ context_name="import",
1408
+ ):
1385
1409
  for imp in imports:
1386
1410
  if variables is None:
1387
1411
  variables = self.get_suite_variables()
@@ -1418,7 +1442,6 @@ class Namespace:
1418
1442
  source=entry.library_doc.source,
1419
1443
  parent_import=imp if top_level else parent_import,
1420
1444
  parent_source=parent_source if top_level else source,
1421
- depth=depth + 1,
1422
1445
  )
1423
1446
  except (SystemExit, KeyboardInterrupt):
1424
1447
  raise
@@ -1568,48 +1591,40 @@ class Namespace:
1568
1591
  code=Error.LIBRARY_ALREADY_IMPORTED,
1569
1592
  )
1570
1593
 
1571
- finally:
1572
- self._logger.debug(
1573
- lambda: f"{' '*depth}end imports for "
1574
- f"{self.document if top_level else source} in {time.monotonic() - current_time}s"
1575
- )
1576
-
1577
1594
  return variables
1578
1595
 
1596
+ def _import_lib(self, library: str, variables: Optional[Dict[str, Any]] = None) -> Optional[LibraryEntry]:
1597
+ try:
1598
+ return self._get_library_entry(
1599
+ library,
1600
+ (),
1601
+ None,
1602
+ str(Path(self.source).parent),
1603
+ is_default_library=True,
1604
+ variables=variables,
1605
+ )
1606
+ except (SystemExit, KeyboardInterrupt):
1607
+ raise
1608
+ except BaseException as e:
1609
+ self.append_diagnostics(
1610
+ range=Range.zero(),
1611
+ message=f"Can't import default library '{library}': {str(e) or type(e).__name__}",
1612
+ severity=DiagnosticSeverity.ERROR,
1613
+ source="Robot",
1614
+ code=type(e).__qualname__,
1615
+ )
1616
+ return None
1617
+
1579
1618
  def _import_default_libraries(self, variables: Optional[Dict[str, Any]] = None) -> None:
1580
- def _import_lib(library: str, variables: Optional[Dict[str, Any]] = None) -> Optional[LibraryEntry]:
1581
- try:
1582
- return self._get_library_entry(
1583
- library,
1584
- (),
1585
- None,
1586
- str(Path(self.source).parent),
1587
- is_default_library=True,
1588
- variables=variables,
1589
- )
1590
- except (SystemExit, KeyboardInterrupt):
1591
- raise
1592
- except BaseException as e:
1593
- self.append_diagnostics(
1594
- range=Range.zero(),
1595
- message=f"Can't import default library '{library}': {str(e) or type(e).__name__}",
1596
- severity=DiagnosticSeverity.ERROR,
1597
- source="Robot",
1598
- code=type(e).__qualname__,
1599
- )
1600
- return None
1601
1619
 
1602
- self._logger.debug(lambda: f"start import default libraries for document {self.document}")
1603
- try:
1620
+ with self._logger.measure_time(lambda: f"importing default libraries for {self.source}", context_name="import"):
1604
1621
  if variables is None:
1605
1622
  variables = self.get_suite_variables()
1606
1623
 
1607
1624
  for library in DEFAULT_LIBRARIES:
1608
- e = _import_lib(library, variables)
1625
+ e = self._import_lib(library, variables)
1609
1626
  if e is not None:
1610
1627
  self._libraries[e.alias or e.name or e.import_name] = e
1611
- finally:
1612
- self._logger.debug(lambda: f"end import default libraries for document {self.document}")
1613
1628
 
1614
1629
  @_logger.call
1615
1630
  def _get_library_entry(
@@ -1820,8 +1835,6 @@ class Namespace:
1820
1835
 
1821
1836
  @_logger.call(condition=lambda self: not self._analyzed)
1822
1837
  def analyze(self) -> None:
1823
- import time
1824
-
1825
1838
  from .namespace_analyzer import NamespaceAnalyzer
1826
1839
 
1827
1840
  with self._analyze_lock:
@@ -1830,53 +1843,43 @@ class Namespace:
1830
1843
 
1831
1844
  self.ensure_initialized()
1832
1845
 
1833
- self._logger.debug(lambda: f"start analyze {self.document}")
1834
- start_time = time.monotonic()
1835
-
1836
- try:
1837
- result = NamespaceAnalyzer(self.model, self, self.create_finder()).run()
1846
+ with self._logger.measure_time(lambda: f"analyzing document {self.source}", context_name="analyze"):
1847
+ try:
1848
+ result = NamespaceAnalyzer(self.model, self, self.create_finder()).run()
1838
1849
 
1839
- self._diagnostics += result.diagnostics
1840
- self._keyword_references = result.keyword_references
1841
- self._variable_references = result.variable_references
1842
- self._local_variable_assignments = result.local_variable_assignments
1843
- self._namespace_references = result.namespace_references
1850
+ self._diagnostics += result.diagnostics
1851
+ self._keyword_references = result.keyword_references
1852
+ self._variable_references = result.variable_references
1853
+ self._local_variable_assignments = result.local_variable_assignments
1854
+ self._namespace_references = result.namespace_references
1844
1855
 
1845
- lib_doc = self.get_library_doc()
1856
+ lib_doc = self.get_library_doc()
1846
1857
 
1847
- if lib_doc.errors is not None:
1848
- for err in lib_doc.errors:
1849
- self.append_diagnostics(
1850
- range=Range(
1851
- start=Position(
1852
- line=((err.line_no - 1) if err.line_no is not None else 0),
1853
- character=0,
1854
- ),
1855
- end=Position(
1856
- line=((err.line_no - 1) if err.line_no is not None else 0),
1857
- character=0,
1858
+ if lib_doc.errors is not None:
1859
+ for err in lib_doc.errors:
1860
+ self.append_diagnostics(
1861
+ range=Range(
1862
+ start=Position(
1863
+ line=((err.line_no - 1) if err.line_no is not None else 0),
1864
+ character=0,
1865
+ ),
1866
+ end=Position(
1867
+ line=((err.line_no - 1) if err.line_no is not None else 0),
1868
+ character=0,
1869
+ ),
1858
1870
  ),
1859
- ),
1860
- message=err.message,
1861
- severity=DiagnosticSeverity.ERROR,
1862
- source=DIAGNOSTICS_SOURCE_NAME,
1863
- code=err.type_name,
1864
- )
1865
- # TODO: implement CancelationToken
1866
- except CancelledError:
1867
- canceled = True
1868
- self._logger.debug("analyzing canceled")
1869
- raise
1870
- finally:
1871
- self._analyzed = not canceled
1872
-
1873
- self._logger.debug(
1874
- lambda: (
1875
- f"end analyzed {self.document} succeed in {time.monotonic() - start_time}s"
1876
- if self._analyzed
1877
- else f"end analyzed {self.document} failed in {time.monotonic() - start_time}s"
1878
- )
1879
- )
1871
+ message=err.message,
1872
+ severity=DiagnosticSeverity.ERROR,
1873
+ source=DIAGNOSTICS_SOURCE_NAME,
1874
+ code=err.type_name,
1875
+ )
1876
+ # TODO: implement CancelationToken
1877
+ except CancelledError:
1878
+ canceled = True
1879
+ self._logger.debug("analyzing canceled")
1880
+ raise
1881
+ finally:
1882
+ self._analyzed = not canceled
1880
1883
 
1881
1884
  self.has_analysed(self)
1882
1885
 
@@ -2,7 +2,9 @@ from __future__ import annotations
2
2
 
3
3
  import ast
4
4
  import itertools
5
- from typing import Any, Iterator, List, Optional, Sequence, Set, Tuple
5
+ from typing import Any, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Type, TypeVar, Union
6
+
7
+ from typing_extensions import TypeGuard
6
8
 
7
9
  from robot.errors import VariableError
8
10
  from robot.parsing.lexer.tokens import Token
@@ -17,17 +19,42 @@ if get_robot_version() < (7, 0):
17
19
  else:
18
20
  from robot.variables.search import VariableMatches as VariableIterator
19
21
 
22
+ _cached_isinstance_cache: Dict[Tuple[type, Tuple[type, ...]], bool] = {}
23
+
24
+ _T = TypeVar("_T")
25
+
26
+
27
+ def cached_isinstance(obj: Any, *expected_types: Type[_T]) -> TypeGuard[Union[_T]]:
28
+ try:
29
+ t = type(obj)
30
+ if (t, expected_types) in _cached_isinstance_cache:
31
+ return _cached_isinstance_cache[(t, expected_types)]
32
+
33
+ _cached_isinstance_cache[(t, expected_types)] = result = isinstance(obj, expected_types)
34
+
35
+ return result
36
+
37
+ except TypeError:
38
+ return False
39
+
40
+
41
+ # def cached_isinstance(obj: Any, *expected_types: type) -> bool:
42
+ # try:
43
+ # return isinstance(obj, expected_types)
44
+ # except TypeError:
45
+ # return False
46
+
20
47
 
21
48
  def iter_nodes(node: ast.AST, descendants: bool = True) -> Iterator[ast.AST]:
22
49
  for _field, value in ast.iter_fields(node):
23
- if isinstance(value, list):
50
+ if cached_isinstance(value, list):
24
51
  for item in value:
25
- if isinstance(item, ast.AST):
52
+ if cached_isinstance(item, ast.AST):
26
53
  yield item
27
54
  if descendants:
28
55
  yield from iter_nodes(item)
29
56
 
30
- elif isinstance(value, ast.AST):
57
+ elif cached_isinstance(value, ast.AST):
31
58
  yield value
32
59
  if descendants:
33
60
  yield from iter_nodes(value)
@@ -53,7 +80,7 @@ class FirstAndLastRealStatementFinder(Visitor):
53
80
  return finder.first_statement, finder.last_statement
54
81
 
55
82
  def visit_Statement(self, statement: ast.AST) -> None: # noqa: N802
56
- if not isinstance(statement, EmptyLine):
83
+ if not cached_isinstance(statement, EmptyLine):
57
84
  if self.first_statement is None:
58
85
  self.first_statement = statement
59
86
 
@@ -63,7 +90,7 @@ class FirstAndLastRealStatementFinder(Visitor):
63
90
  def _get_non_data_range_from_node(
64
91
  node: ast.AST, only_start: bool = False, allow_comments: bool = False
65
92
  ) -> Optional[Range]:
66
- if isinstance(node, Statement) and node.tokens:
93
+ if cached_isinstance(node, Statement) and node.tokens:
67
94
  start_token = next(
68
95
  (
69
96
  v
@@ -115,7 +142,7 @@ def range_from_node(
115
142
  allow_comments: bool = False,
116
143
  ) -> Range:
117
144
  if skip_non_data:
118
- if isinstance(node, Statement) and node.tokens:
145
+ if cached_isinstance(node, Statement) and node.tokens:
119
146
  result = _get_non_data_range_from_node(node, only_start, allow_comments)
120
147
  if result is not None:
121
148
  return result
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: robotcode-robot
3
- Version: 0.92.0
3
+ Version: 0.93.0
4
4
  Summary: Support classes for RobotCode for handling Robot Framework projects.
5
5
  Project-URL: Homepage, https://robotcode.io
6
6
  Project-URL: Donate, https://opencollective.com/robotcode
@@ -26,7 +26,7 @@ Classifier: Topic :: Utilities
26
26
  Classifier: Typing :: Typed
27
27
  Requires-Python: >=3.8
28
28
  Requires-Dist: platformdirs<4.2.0,>=3.2.0
29
- Requires-Dist: robotcode-core==0.92.0
29
+ Requires-Dist: robotcode-core==0.93.0
30
30
  Requires-Dist: robotframework>=4.1.0
31
31
  Requires-Dist: tomli>=1.1.0; python_version < '3.11'
32
32
  Description-Content-Type: text/markdown
@@ -1,30 +1,30 @@
1
1
  robotcode/robot/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- robotcode/robot/__version__.py,sha256=4oQqdZ-jW338RGQfLxI7hoQNb8tTR6y4hzWoJRPdI3o,23
2
+ robotcode/robot/__version__.py,sha256=W-KEJT0wpRrG8XJ2aDG4NSyzaUucgZR9y6mwfCTlku4,23
3
3
  robotcode/robot/py.typed,sha256=bWew9mHgMy8LqMu7RuqQXFXLBxh2CRx0dUbSx-3wE48,27
4
4
  robotcode/robot/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- robotcode/robot/config/loader.py,sha256=5PQTWDn1vYFN0VmLVhEYdu12Foq1AwRtDFvjU44QQvw,7878
5
+ robotcode/robot/config/loader.py,sha256=bNJwr_XdCoUzpG2ag0BH33PIfiCwn0GMxn7q_Sw3zOk,8103
6
6
  robotcode/robot/config/model.py,sha256=sgr6-4_E06g-yIXW41Z-NtIXZ_7JMmR5WvUD7kTUqu4,89106
7
- robotcode/robot/config/utils.py,sha256=c_WZg39DJgM6kXcAH_h-v68qhf1eStJ0TslTawaJoZw,2827
7
+ robotcode/robot/config/utils.py,sha256=xY-LH31BidWzonpvSrle-4HvKrp02I7IRqU2JwlL4Ls,2931
8
8
  robotcode/robot/diagnostics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  robotcode/robot/diagnostics/diagnostics_modifier.py,sha256=3dDsu8-ET6weIvv7Sk3IQaPYFNxnXUs8Y7gpGTjfOBs,9796
10
10
  robotcode/robot/diagnostics/document_cache_helper.py,sha256=MlOPXC3mUVjNB2rHn5UJK7NnUPa8S4mE3qojILOi_Mk,23756
11
- robotcode/robot/diagnostics/entities.py,sha256=AtrqPAS3XUC-8VpFmqMXfMKjQHmfxXZlyGWWFaEBpQA,10979
11
+ robotcode/robot/diagnostics/entities.py,sha256=dWZFSrAuW9wzW9AEDSr-stseexLbJIYfdScfAbBj4FY,11002
12
12
  robotcode/robot/diagnostics/errors.py,sha256=HCE0h0_ui1tx-6pJHs0r4s09ZHmArYwKUo-_lBl9K-4,1600
13
- robotcode/robot/diagnostics/imports_manager.py,sha256=qE__lm0Hsj3R0gUauXRmbWJPYihNjk3O-c5hcICGQjc,56251
14
- robotcode/robot/diagnostics/library_doc.py,sha256=CHgyC9GMRQFP3fGTxWa139rIcGIk8Ys6Q_ZIDSgse7k,97324
13
+ robotcode/robot/diagnostics/imports_manager.py,sha256=GvcIEqghVjtliTWUwQo4N-xier00CGnEh_eO7l05cJg,57187
14
+ robotcode/robot/diagnostics/library_doc.py,sha256=7m29COvY_YlK_n9dj57q0c7sFRThoR_X4bgdZapWwaY,98160
15
15
  robotcode/robot/diagnostics/model_helper.py,sha256=_5ixKKMrb-nY-uvV8_WjJ1rlNlz7gT7kHM5NYi_hjVg,30232
16
- robotcode/robot/diagnostics/namespace.py,sha256=jCUb5GmMDA6KTr2BnwHD_0Mp_gTg1XGskUvRYPL7XQ0,89543
16
+ robotcode/robot/diagnostics/namespace.py,sha256=lH5RReu-fW0g8CZBh9LKWHItCZh14D-XFvFdYUvBoDg,90151
17
17
  robotcode/robot/diagnostics/namespace_analyzer.py,sha256=3eC1xEBm_foCnqoHk8Cj6O11UXOHcHxWnfuKP8M5KIQ,51369
18
18
  robotcode/robot/diagnostics/workspace_config.py,sha256=3SoewUj_LZB1Ki5hXM8oxQpJr6vyiog66SUw-ibODSA,2478
19
19
  robotcode/robot/utils/__init__.py,sha256=OjNPMn_XSnfaMCyKd8Kmq6vlRt6mIGlzW4qiiD3ykUg,447
20
- robotcode/robot/utils/ast.py,sha256=ynxvXv1SHkAcF07m76ey9_zaGCYXhlPOnny7km-XWSQ,10479
20
+ robotcode/robot/utils/ast.py,sha256=_ob36KHFY776n9dhljn0xAWVoUDb7pV86fPW40vIirY,11266
21
21
  robotcode/robot/utils/markdownformatter.py,sha256=0XZZ5wDU2yfzIuShjG7h79PgzMaQJ501WH4YRFcG1VM,11615
22
22
  robotcode/robot/utils/match.py,sha256=ofyfXgrvVddl7a064Dk5Kiqp3a-n_6gTIgqDbL3E80Q,632
23
23
  robotcode/robot/utils/robot_path.py,sha256=qKBh1cEnReBBLKkWu4gB9EzM-scAwE4xJc1m6v2LRN0,1786
24
24
  robotcode/robot/utils/stubs.py,sha256=6-DMI_CQVJHDgG13t-zINKGCRb_Q7MQPm0_AkfhAEvE,748
25
25
  robotcode/robot/utils/variables.py,sha256=fEl8S37lb_mD4hn2MZRAlkiuLGBjAOeZVK0r2o2CfPw,742
26
26
  robotcode/robot/utils/visitor.py,sha256=uYLqEhGPmzWKWI3SSrmCaYMwtKvNShvbiPZ4b3FavX8,3241
27
- robotcode_robot-0.92.0.dist-info/METADATA,sha256=H_aQghUcVbx_6vUwIw0DTqVSUhN1ewvzGjinWv-MEX0,2240
28
- robotcode_robot-0.92.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
29
- robotcode_robot-0.92.0.dist-info/licenses/LICENSE.txt,sha256=B05uMshqTA74s-0ltyHKI6yoPfJ3zYgQbvcXfDVGFf8,10280
30
- robotcode_robot-0.92.0.dist-info/RECORD,,
27
+ robotcode_robot-0.93.0.dist-info/METADATA,sha256=64V1dXSzlmT6KArsIP0YqCm79s_oPXVm3MXBlenwx2E,2240
28
+ robotcode_robot-0.93.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
29
+ robotcode_robot-0.93.0.dist-info/licenses/LICENSE.txt,sha256=B05uMshqTA74s-0ltyHKI6yoPfJ3zYgQbvcXfDVGFf8,10280
30
+ robotcode_robot-0.93.0.dist-info/RECORD,,