robotcode-robot 0.92.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.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,,