robotcode-robot 1.0.2__tar.gz → 2.0.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. {robotcode_robot-1.0.2 → robotcode_robot-2.0.3}/.gitignore +4 -1
  2. {robotcode_robot-1.0.2 → robotcode_robot-2.0.3}/PKG-INFO +24 -8
  3. robotcode_robot-2.0.3/README.md +37 -0
  4. {robotcode_robot-1.0.2 → robotcode_robot-2.0.3}/pyproject.toml +8 -7
  5. robotcode_robot-2.0.3/src/robotcode/robot/__version__.py +1 -0
  6. {robotcode_robot-1.0.2 → robotcode_robot-2.0.3}/src/robotcode/robot/config/model.py +9 -12
  7. {robotcode_robot-1.0.2 → robotcode_robot-2.0.3}/src/robotcode/robot/diagnostics/data_cache.py +0 -1
  8. {robotcode_robot-1.0.2 → robotcode_robot-2.0.3}/src/robotcode/robot/diagnostics/document_cache_helper.py +5 -1
  9. {robotcode_robot-1.0.2 → robotcode_robot-2.0.3}/src/robotcode/robot/diagnostics/entities.py +20 -46
  10. {robotcode_robot-1.0.2 → robotcode_robot-2.0.3}/src/robotcode/robot/diagnostics/errors.py +2 -0
  11. {robotcode_robot-1.0.2 → robotcode_robot-2.0.3}/src/robotcode/robot/diagnostics/imports_manager.py +70 -15
  12. {robotcode_robot-1.0.2 → robotcode_robot-2.0.3}/src/robotcode/robot/diagnostics/keyword_finder.py +43 -13
  13. {robotcode_robot-1.0.2 → robotcode_robot-2.0.3}/src/robotcode/robot/diagnostics/library_doc.py +24 -10
  14. {robotcode_robot-1.0.2 → robotcode_robot-2.0.3}/src/robotcode/robot/diagnostics/model_helper.py +4 -4
  15. {robotcode_robot-1.0.2 → robotcode_robot-2.0.3}/src/robotcode/robot/diagnostics/namespace.py +246 -203
  16. {robotcode_robot-1.0.2 → robotcode_robot-2.0.3}/src/robotcode/robot/diagnostics/namespace_analyzer.py +222 -135
  17. {robotcode_robot-1.0.2 → robotcode_robot-2.0.3}/src/robotcode/robot/diagnostics/workspace_config.py +3 -0
  18. robotcode_robot-2.0.3/src/robotcode/robot/utils/__init__.py +14 -0
  19. {robotcode_robot-1.0.2 → robotcode_robot-2.0.3}/src/robotcode/robot/utils/ast.py +21 -19
  20. {robotcode_robot-1.0.2 → robotcode_robot-2.0.3}/src/robotcode/robot/utils/markdownformatter.py +3 -3
  21. robotcode_robot-2.0.3/src/robotcode/robot/utils/variables.py +227 -0
  22. robotcode_robot-1.0.2/LICENSE.txt +0 -73
  23. robotcode_robot-1.0.2/README.md +0 -21
  24. robotcode_robot-1.0.2/src/robotcode/robot/__version__.py +0 -1
  25. robotcode_robot-1.0.2/src/robotcode/robot/utils/__init__.py +0 -17
  26. robotcode_robot-1.0.2/src/robotcode/robot/utils/variables.py +0 -72
  27. {robotcode_robot-1.0.2 → robotcode_robot-2.0.3}/src/robotcode/robot/__init__.py +0 -0
  28. {robotcode_robot-1.0.2 → robotcode_robot-2.0.3}/src/robotcode/robot/config/__init__.py +0 -0
  29. {robotcode_robot-1.0.2 → robotcode_robot-2.0.3}/src/robotcode/robot/config/loader.py +0 -0
  30. {robotcode_robot-1.0.2 → robotcode_robot-2.0.3}/src/robotcode/robot/config/utils.py +0 -0
  31. {robotcode_robot-1.0.2 → robotcode_robot-2.0.3}/src/robotcode/robot/diagnostics/__init__.py +0 -0
  32. {robotcode_robot-1.0.2 → robotcode_robot-2.0.3}/src/robotcode/robot/diagnostics/diagnostics_modifier.py +0 -0
  33. {robotcode_robot-1.0.2 → robotcode_robot-2.0.3}/src/robotcode/robot/py.typed +0 -0
  34. {robotcode_robot-1.0.2 → robotcode_robot-2.0.3}/src/robotcode/robot/utils/match.py +0 -0
  35. {robotcode_robot-1.0.2 → robotcode_robot-2.0.3}/src/robotcode/robot/utils/robot_path.py +0 -0
  36. {robotcode_robot-1.0.2 → robotcode_robot-2.0.3}/src/robotcode/robot/utils/stubs.py +0 -0
  37. {robotcode_robot-1.0.2 → robotcode_robot-2.0.3}/src/robotcode/robot/utils/visitor.py +0 -0
@@ -331,4 +331,7 @@ output.xml
331
331
  bundled/libs
332
332
 
333
333
  # robotframework
334
- results/
334
+ results/
335
+
336
+ # kilocode
337
+ .kilocode/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: robotcode-robot
3
- Version: 1.0.2
3
+ Version: 2.0.3
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
@@ -10,23 +10,23 @@ Project-URL: Issues, https://github.com/robotcodedev/robotcode/issues
10
10
  Project-URL: Source, https://github.com/robotcodedev/robotcode
11
11
  Author-email: Daniel Biehl <dbiehl@live.de>
12
12
  License: Apache-2.0
13
- License-File: LICENSE.txt
14
13
  Classifier: Development Status :: 5 - Production/Stable
15
14
  Classifier: Framework :: Robot Framework
16
15
  Classifier: Framework :: Robot Framework :: Tool
17
16
  Classifier: Operating System :: OS Independent
18
17
  Classifier: Programming Language :: Python
19
- Classifier: Programming Language :: Python :: 3.8
20
- Classifier: Programming Language :: Python :: 3.9
21
18
  Classifier: Programming Language :: Python :: 3.10
22
19
  Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
23
  Classifier: Programming Language :: Python :: Implementation :: CPython
24
24
  Classifier: Programming Language :: Python :: Implementation :: PyPy
25
25
  Classifier: Topic :: Utilities
26
26
  Classifier: Typing :: Typed
27
- Requires-Python: >=3.8
28
- Requires-Dist: platformdirs<4.4.0,>=3.2.0
29
- Requires-Dist: robotcode-core==1.0.2
27
+ Requires-Python: >=3.10
28
+ Requires-Dist: platformdirs>=4.3
29
+ Requires-Dist: robotcode-core==2.0.3
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
@@ -41,7 +41,9 @@ Description-Content-Type: text/markdown
41
41
 
42
42
  ## Introduction
43
43
 
44
- Support classes for [RobotCode](https://robotcode.io) for handling Robot Framework projects.
44
+ Robot Framework project handling and integration utilities for [RobotCode](https://robotcode.io).
45
+
46
+ This package is part of the [RobotCode](https://robotcode.io) project. The complete source code is available at [github.com/robotcodedev/robotcode](https://github.com/robotcodedev/robotcode).
45
47
 
46
48
  ## Installation
47
49
 
@@ -49,6 +51,20 @@ Support classes for [RobotCode](https://robotcode.io) for handling Robot Framewo
49
51
  pip install robotcode-robot
50
52
  ```
51
53
 
54
+ ## Documentation
55
+
56
+ For comprehensive documentation, please visit [robotcode.io](https://robotcode.io).
57
+
58
+ ## Security
59
+
60
+ For security concerns, please refer to our [Security Policy](https://github.com/robotcodedev/robotcode/security/policy).
61
+
52
62
  ## License
53
63
 
54
64
  `robotcode-robot` is distributed under the terms of the [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html) license.
65
+
66
+ ## Support
67
+
68
+ - 📖 [Documentation](https://robotcode.io)
69
+ - 💬 [Discussions](https://github.com/robotcodedev/robotcode/discussions)
70
+ - 🐛 [Issues](https://github.com/robotcodedev/robotcode/issues)
@@ -0,0 +1,37 @@
1
+ # robotcode-robot
2
+
3
+ [![PyPI - Version](https://img.shields.io/pypi/v/robotcode-robot.svg)](https://pypi.org/project/robotcode-robot)
4
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/robotcode-robot.svg)](https://pypi.org/project/robotcode-robot)
5
+ [![License](https://img.shields.io/github/license/robotcodedev/robotcode?style=flat&logo=apache)](https://github.com/robotcodedev/robotcode/blob/master/LICENSE.txt)
6
+
7
+ -----
8
+
9
+ ## Introduction
10
+
11
+ Robot Framework project handling and integration utilities for [RobotCode](https://robotcode.io).
12
+
13
+ This package is part of the [RobotCode](https://robotcode.io) project. The complete source code is available at [github.com/robotcodedev/robotcode](https://github.com/robotcodedev/robotcode).
14
+
15
+ ## Installation
16
+
17
+ ```console
18
+ pip install robotcode-robot
19
+ ```
20
+
21
+ ## Documentation
22
+
23
+ For comprehensive documentation, please visit [robotcode.io](https://robotcode.io).
24
+
25
+ ## Security
26
+
27
+ For security concerns, please refer to our [Security Policy](https://github.com/robotcodedev/robotcode/security/policy).
28
+
29
+ ## License
30
+
31
+ `robotcode-robot` is distributed under the terms of the [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html) license.
32
+
33
+ ## Support
34
+
35
+ - 📖 [Documentation](https://robotcode.io)
36
+ - 💬 [Discussions](https://github.com/robotcodedev/robotcode/discussions)
37
+ - 🐛 [Issues](https://github.com/robotcodedev/robotcode/issues)
@@ -7,17 +7,18 @@ build-backend = "hatchling.build"
7
7
  name = "robotcode-robot"
8
8
  description = 'Support classes for RobotCode for handling Robot Framework projects.'
9
9
  readme = { "file" = "README.md", "content-type" = "text/markdown" }
10
- requires-python = ">=3.8"
11
- license = {text = "Apache-2.0"}
10
+ requires-python = ">=3.10"
11
+ license = { text = "Apache-2.0" }
12
12
  keywords = []
13
13
  authors = [{ name = "Daniel Biehl", email = "dbiehl@live.de" }]
14
14
  classifiers = [
15
15
  "Development Status :: 5 - Production/Stable",
16
16
  "Programming Language :: Python",
17
- "Programming Language :: Python :: 3.8",
18
- "Programming Language :: Python :: 3.9",
19
17
  "Programming Language :: Python :: 3.10",
20
18
  "Programming Language :: Python :: 3.11",
19
+ "Programming Language :: Python :: 3.12",
20
+ "Programming Language :: Python :: 3.13",
21
+ "Programming Language :: Python :: 3.14",
21
22
  "Programming Language :: Python :: Implementation :: CPython",
22
23
  "Programming Language :: Python :: Implementation :: PyPy",
23
24
  "Operating System :: OS Independent",
@@ -29,8 +30,8 @@ classifiers = [
29
30
  dependencies = [
30
31
  "robotframework>=4.1.0",
31
32
  "tomli>=1.1.0; python_version < '3.11'",
32
- "platformdirs>=3.2.0,<4.4.0",
33
- "robotcode-core==1.0.2",
33
+ "platformdirs>=4.3",
34
+ "robotcode-core==2.0.3",
34
35
  ]
35
36
  dynamic = ["version"]
36
37
 
@@ -57,4 +58,4 @@ only-include = ["src"]
57
58
 
58
59
  [tool.hatch.envs.build]
59
60
  detached = true
60
- python = "38"
61
+ python = "310"
@@ -0,0 +1 @@
1
+ __version__ = "2.0.3"
@@ -366,7 +366,7 @@ class RobotBaseOptions(BaseOptions):
366
366
  )
367
367
 
368
368
  def append_name(field: "dataclasses.Field[Any]", add_flag: Optional[str] = None) -> None:
369
- if "robot_short_name" in field.metadata:
369
+ if "robot_short_name" in field.metadata and not add_flag:
370
370
  result.append(f"-{field.metadata['robot_short_name']}")
371
371
  elif "robot_name" in field.metadata:
372
372
  result.append(f"--{'no' if add_flag else ''}{field.metadata['robot_name']}")
@@ -697,9 +697,9 @@ class CommonOptions(RobotBaseOptions):
697
697
  **passed:** remove data only from keywords in passed
698
698
  test cases and suites
699
699
 
700
- **for:** remove passed iterations from for loops
700
+ **for:** remove passed iterations from FOR loops
701
701
 
702
- **while:** remove passed iterations from while loops
702
+ **while:** remove passed iterations from WHILE loops
703
703
 
704
704
  **wuks:** remove all but the last failing keyword
705
705
  inside `BuiltIn.Wait Until Keyword Succeeds`
@@ -847,7 +847,6 @@ class CommonOptions(RobotBaseOptions):
847
847
  pattern. Documentation is shown in `Test Details` and
848
848
  also as a tooltip in `Statistics by Tag`. Pattern can
849
849
  use `*`, `?` and `[]` as wildcards like --test.
850
- Documentation can contain formatting like --doc.
851
850
 
852
851
  Examples:
853
852
 
@@ -1130,9 +1129,9 @@ class CommonExtendOptions(RobotBaseOptions):
1130
1129
  **passed:** remove data only from keywords in passed
1131
1130
  test cases and suites
1132
1131
 
1133
- **for:** remove passed iterations from for loops
1132
+ **for:** remove passed iterations from FOR loops
1134
1133
 
1135
- **while:** remove passed iterations from while loops
1134
+ **while:** remove passed iterations from WHILE loops
1136
1135
 
1137
1136
  **wuks:** remove all but the last failing keyword
1138
1137
  inside `BuiltIn.Wait Until Keyword Succeeds`
@@ -1202,7 +1201,6 @@ class CommonExtendOptions(RobotBaseOptions):
1202
1201
  pattern. Documentation is shown in `Test Details` and
1203
1202
  also as a tooltip in `Statistics by Tag`. Pattern can
1204
1203
  use `*`, `?` and `[]` as wildcards like --test.
1205
- Documentation can contain formatting like --doc.
1206
1204
 
1207
1205
  Examples:
1208
1206
 
@@ -1862,7 +1860,6 @@ class RebotOptions(RobotBaseOptions):
1862
1860
  Processes output also if the top level suite is
1863
1861
  empty. Useful e.g. with --include/--exclude when it
1864
1862
  is not an error that there are no matches.
1865
- Use --skiponfailure when starting execution instead.
1866
1863
 
1867
1864
  corresponds to the `--processemptysuite` option of _rebot_
1868
1865
  """,
@@ -1944,7 +1941,7 @@ class LibDocOptions(RobotBaseOptions):
1944
1941
  quiet: Union[bool, Flag, None] = field(
1945
1942
  description="""\
1946
1943
  Do not print the path of the generated output file
1947
- to the console. New in RF 4.0.
1944
+ to the console.
1948
1945
 
1949
1946
  corresponds to the `--quiet` option of _libdoc_
1950
1947
  """,
@@ -1959,7 +1956,7 @@ class LibDocOptions(RobotBaseOptions):
1959
1956
  documentation format and HTML means converting
1960
1957
  documentation to HTML. The default is RAW with XML
1961
1958
  spec files and HTML with JSON specs and when using
1962
- the special LIBSPEC format. New in RF 4.0.
1959
+ the special LIBSPEC format.
1963
1960
 
1964
1961
  corresponds to the `-s --specdocformat RAW|HTML` option of _libdoc_
1965
1962
  """,
@@ -2371,11 +2368,11 @@ class RobotConfig(RobotExtendBaseProfile):
2371
2368
 
2372
2369
  Examples:
2373
2370
  ```toml
2374
- default_profiles = "default"
2371
+ default-profiles = "default"
2375
2372
  ```
2376
2373
 
2377
2374
  ```toml
2378
- default_profiles = ["default", "Firefox"]
2375
+ default-profiles = ["default", "Firefox"]
2379
2376
  ```
2380
2377
  """
2381
2378
  )
@@ -40,7 +40,6 @@ class FileCacheDataBase(DataCache, ABC):
40
40
 
41
41
 
42
42
  class JsonDataCache(FileCacheDataBase):
43
-
44
43
  def build_cache_data_filename(self, section: CacheSection, entry_name: str) -> Path:
45
44
  return self.cache_dir / section.value / (entry_name + ".json")
46
45
 
@@ -532,6 +532,11 @@ class DocumentsCacheHelper:
532
532
  self.analysis_config.cache.ignore_arguments_for_library + cache_config.ignore_arguments_for_library,
533
533
  self.analysis_config.robot.global_library_search_order + analysis_config.global_library_search_order,
534
534
  cache_base_path,
535
+ load_library_timeout=(
536
+ analysis_config.load_library_timeout
537
+ if analysis_config.load_library_timeout is not None
538
+ else self.analysis_config.robot.load_library_timeout
539
+ ),
535
540
  )
536
541
 
537
542
  result.libraries_changed.add(self._on_libraries_changed)
@@ -589,7 +594,6 @@ class DocumentsCacheHelper:
589
594
  return self._imports_managers[folder]
590
595
 
591
596
  def calc_cache_path(self, folder_uri: Uri) -> Path:
592
- # TODO: cache path should be configurable, save cache in vscode workspace folder or in robotcode cache folder
593
597
  return folder_uri.to_path()
594
598
 
595
599
  def get_diagnostic_modifier(self, document: TextDocument) -> DiagnosticsModifier:
@@ -17,10 +17,9 @@ from typing_extensions import Concatenate, ParamSpec
17
17
 
18
18
  from robot.parsing.lexer.tokens import Token
19
19
  from robotcode.core.lsp.types import Position, Range
20
- from robotcode.robot.utils.match import normalize
21
20
 
22
21
  from ..utils.ast import range_from_token
23
- from ..utils.variables import search_variable
22
+ from ..utils.variables import VariableMatcher, search_variable
24
23
 
25
24
  if TYPE_CHECKING:
26
25
  from robotcode.robot.diagnostics.library_doc import KeywordDoc, LibraryDoc
@@ -164,48 +163,6 @@ class VariablesImport(Import):
164
163
  return hash((type(self), self.name, self.args))
165
164
 
166
165
 
167
- class InvalidVariableError(Exception):
168
- pass
169
-
170
-
171
- class VariableMatcher:
172
- def __init__(self, name: str) -> None:
173
- self.name = name
174
-
175
- match = search_variable(name, "$@&%", ignore_errors=True)
176
-
177
- if match.base is None:
178
- raise InvalidVariableError(f"Invalid variable '{name}'")
179
-
180
- self.base = match.base
181
-
182
- self.normalized_name = normalize(self.base)
183
-
184
- def __eq__(self, o: object) -> bool:
185
- if type(o) is VariableMatcher:
186
- return o.normalized_name == self.normalized_name
187
-
188
- if type(o) is str:
189
- match = search_variable(o, "$@&%", ignore_errors=True)
190
- base = match.base
191
- if base is None:
192
- return False
193
-
194
- normalized = normalize(base)
195
- return self.normalized_name == normalized
196
-
197
- return False
198
-
199
- def __hash__(self) -> int:
200
- return hash(self.normalized_name)
201
-
202
- def __str__(self) -> str:
203
- return self.name
204
-
205
- def __repr__(self) -> str:
206
- return f"{type(self).__name__}(name={self.name!r})"
207
-
208
-
209
166
  class VariableDefinitionType(Enum):
210
167
  VARIABLE = "suite variable"
211
168
  LOCAL_VARIABLE = "local variable"
@@ -222,7 +179,8 @@ class VariableDefinitionType(Enum):
222
179
  @dataclass
223
180
  class VariableDefinition(SourceEntity):
224
181
  name: str
225
- name_token: Optional[Token]
182
+ name_token: Optional[Token] # TODO: this is not needed anymore, but kept for compatibility
183
+
226
184
  type: VariableDefinitionType = VariableDefinitionType.VARIABLE
227
185
 
228
186
  has_value: bool = field(default=False, compare=False)
@@ -230,10 +188,17 @@ class VariableDefinition(SourceEntity):
230
188
 
231
189
  value: Any = field(default=None, compare=False)
232
190
  value_is_native: bool = field(default=False, compare=False)
191
+ value_type: Optional[str] = field(default=None, compare=False)
233
192
 
234
193
  @functools.cached_property
235
194
  def matcher(self) -> VariableMatcher:
236
- return VariableMatcher(self.name)
195
+ return search_variable(self.name)
196
+
197
+ @functools.cached_property
198
+ def convertable_name(self) -> str:
199
+ m = self.matcher
200
+ value_type = f": {self.value_type}" if self.value_type else ""
201
+ return f"{m.identifier}{{{m.base.strip()}{value_type}}}"
237
202
 
238
203
  @single_call
239
204
  def __hash__(self) -> int:
@@ -311,6 +276,15 @@ class ArgumentDefinition(LocalVariableDefinition):
311
276
  return hash((type(self), self.name, self.type, self.range, self.source))
312
277
 
313
278
 
279
+ @dataclass
280
+ class EmbeddedArgumentDefinition(ArgumentDefinition):
281
+ pattern: Optional[str] = field(default=None, compare=False)
282
+
283
+ @single_call
284
+ def __hash__(self) -> int:
285
+ return hash((type(self), self.name, self.type, self.range, self.source))
286
+
287
+
314
288
  @dataclass
315
289
  class LibraryArgumentDefinition(ArgumentDefinition):
316
290
  @single_call
@@ -39,7 +39,9 @@ class Error:
39
39
  INVALID_HEADER = "InvalidHeader"
40
40
  DEPRECATED_HEADER = "DeprecatedHeader"
41
41
  OVERRIDDEN_BY_COMMANDLINE = "OverriddenByCommandLine"
42
+ OVERRIDES_IMPORTED_VARIABLE = "OverridesImportedVariable"
42
43
  VARIABLE_ALREADY_DEFINED = "VariableAlreadyDefined"
43
44
  VARIABLE_OVERRIDDEN = "VariableOverridden"
44
45
  MODEL_ERROR = "ModelError"
45
46
  TOKEN_ERROR = "TokenError"
47
+ ASSIGN_MARK_ALLOWED_ONLY_ON_LAST_VAR = "AssignmentMarkAllowedOnlyOnLastVariable"
@@ -96,8 +96,9 @@ RESOURCE_EXTENSIONS = (
96
96
  REST_EXTENSIONS = (".rst", ".rest")
97
97
 
98
98
 
99
- LOAD_LIBRARY_TIME_OUT = 10
100
- COMPLETE_LIBRARY_IMPORT_TIME_OUT = COMPLETE_RESOURCE_IMPORT_TIME_OUT = COMPLETE_VARIABLES_IMPORT_TIME_OUT = 5
99
+ DEFAULT_LOAD_LIBRARY_TIMEOUT: int = 10
100
+ ENV_LOAD_LIBRARY_TIMEOUT_VAR = "ROBOTCODE_LOAD_LIBRARY_TIMEOUT"
101
+ COMPLETE_LIBRARY_IMPORT_TIMEOUT = COMPLETE_RESOURCE_IMPORT_TIMEOUT = COMPLETE_VARIABLES_IMPORT_TIMEOUT = 5
101
102
 
102
103
 
103
104
  class _EntryKey:
@@ -226,7 +227,9 @@ class _LibrariesEntry(_ImportEntry):
226
227
  source_or_origin = (
227
228
  self._lib_doc.source
228
229
  if self._lib_doc.source is not None
229
- else self._lib_doc.module_spec.origin if self._lib_doc.module_spec is not None else None
230
+ else self._lib_doc.module_spec.origin
231
+ if self._lib_doc.module_spec is not None
232
+ else None
230
233
  )
231
234
 
232
235
  # we are a module, so add the module path into file watchers
@@ -512,6 +515,7 @@ class ImportsManager:
512
515
  ignore_arguments_for_library: List[str],
513
516
  global_library_search_order: List[str],
514
517
  cache_base_path: Optional[Path],
518
+ load_library_timeout: Optional[int] = None,
515
519
  ) -> None:
516
520
  super().__init__()
517
521
 
@@ -588,6 +592,45 @@ class ImportsManager:
588
592
 
589
593
  self._diagnostics: List[Diagnostic] = []
590
594
 
595
+ # precedence: explicit config (arg) > environment variable > default
596
+ if load_library_timeout is None:
597
+ env_value = os.environ.get(ENV_LOAD_LIBRARY_TIMEOUT_VAR)
598
+ if env_value is not None:
599
+ try:
600
+ load_library_timeout = int(env_value)
601
+ except ValueError:
602
+ self._logger.warning(
603
+ lambda: (
604
+ "Invalid value for "
605
+ f"{ENV_LOAD_LIBRARY_TIMEOUT_VAR}={env_value!r}, using default "
606
+ f"{DEFAULT_LOAD_LIBRARY_TIMEOUT}"
607
+ ),
608
+ context_name="imports",
609
+ )
610
+ load_library_timeout = DEFAULT_LOAD_LIBRARY_TIMEOUT
611
+ else:
612
+ load_library_timeout = DEFAULT_LOAD_LIBRARY_TIMEOUT
613
+
614
+ # enforce sane lower bound
615
+ if load_library_timeout <= 0:
616
+ self._logger.warning(
617
+ lambda: (
618
+ "Configured load_library_timeout "
619
+ f"{load_library_timeout} is not > 0, fallback to {DEFAULT_LOAD_LIBRARY_TIMEOUT}"
620
+ ),
621
+ context_name="imports",
622
+ )
623
+ load_library_timeout = DEFAULT_LOAD_LIBRARY_TIMEOUT
624
+
625
+ self.load_library_timeout = load_library_timeout
626
+
627
+ self._logger.trace(lambda: f"Using LoadLibrary timeout of {self.load_library_timeout} seconds")
628
+
629
+ self._logger.trace(
630
+ lambda: f"Using load_library_timeout={self.load_library_timeout} (config/env/default)",
631
+ context_name="imports",
632
+ )
633
+
591
634
  def __del__(self) -> None:
592
635
  try:
593
636
  if self._executor is not None:
@@ -1226,8 +1269,7 @@ class ImportsManager:
1226
1269
  saved_meta = self.data_cache.read_cache_data(CacheSection.LIBRARY, meta_file, LibraryMetaData)
1227
1270
  if saved_meta.has_errors:
1228
1271
  self._logger.debug(
1229
- lambda: f"Saved library spec for {name}{args!r} is not used "
1230
- "due to errors in meta data",
1272
+ lambda: f"Saved library spec for {name}{args!r} is not used due to errors in meta data",
1231
1273
  context_name="import",
1232
1274
  )
1233
1275
 
@@ -1265,10 +1307,16 @@ class ImportsManager:
1265
1307
  base_dir,
1266
1308
  self.get_resolvable_command_line_variables(),
1267
1309
  variables,
1268
- ).result(LOAD_LIBRARY_TIME_OUT)
1310
+ ).result(self.load_library_timeout)
1269
1311
 
1270
1312
  except TimeoutError as e:
1271
- raise RuntimeError(f"Timeout loading library {name}({args!r})") from e
1313
+ raise RuntimeError(
1314
+ f"Loading library {name!r} with args {args!r} (working_dir={working_dir!r}, base_dir={base_dir!r}) "
1315
+ f"timed out after {self.load_library_timeout} seconds. "
1316
+ "The library may be slow or blocked during import. "
1317
+ "If required, increase the timeout by setting the ROBOTCODE_LOAD_LIBRARY_TIMEOUT "
1318
+ "environment variable."
1319
+ ) from e
1272
1320
 
1273
1321
  except (SystemExit, KeyboardInterrupt):
1274
1322
  raise
@@ -1437,10 +1485,17 @@ class ImportsManager:
1437
1485
  base_dir,
1438
1486
  self.get_resolvable_command_line_variables() if resolve_command_line_vars else None,
1439
1487
  variables,
1440
- ).result(LOAD_LIBRARY_TIME_OUT)
1488
+ ).result(self.load_library_timeout)
1441
1489
 
1442
1490
  except TimeoutError as e:
1443
- raise RuntimeError(f"Timeout loading library {name}({args!r})") from e
1491
+ raise RuntimeError(
1492
+ f"Loading variables {name!r} with args {args!r} (working_dir={working_dir!r}, "
1493
+ f"base_dir={base_dir!r}) "
1494
+ f"timed out after {self.load_library_timeout} seconds. "
1495
+ "The variables may be slow or blocked during import. "
1496
+ "If required, increase the timeout by setting the ROBOTCODE_LOAD_LIBRARY_TIMEOUT "
1497
+ "environment variable."
1498
+ ) from e
1444
1499
 
1445
1500
  except (SystemExit, KeyboardInterrupt):
1446
1501
  raise
@@ -1526,11 +1581,11 @@ class ImportsManager:
1526
1581
  variables: Optional[Dict[str, Any]] = None,
1527
1582
  ) -> _ResourcesEntry:
1528
1583
  source = self.find_resource(name, base_dir, variables=variables)
1584
+ source_path = normalized_path(Path(source))
1529
1585
 
1530
1586
  def _get_document() -> TextDocument:
1531
- self._logger.debug(lambda: f"Load resource {name} from source {source}", context_name="import")
1587
+ self._logger.debug(lambda: f"Load resource {name} from source {source_path}", context_name="import")
1532
1588
 
1533
- source_path = normalized_path(Path(source))
1534
1589
  extension = source_path.suffix
1535
1590
  if extension.lower() not in RESOURCE_EXTENSIONS:
1536
1591
  raise ImportError(
@@ -1540,7 +1595,7 @@ class ImportsManager:
1540
1595
 
1541
1596
  return self.documents_manager.get_or_open_document(source_path)
1542
1597
 
1543
- entry_key = _ResourcesEntryKey(source)
1598
+ entry_key = _ResourcesEntryKey(str(source_path))
1544
1599
 
1545
1600
  with self._resources_lock:
1546
1601
  if entry_key not in self._resources:
@@ -1607,7 +1662,7 @@ class ImportsManager:
1607
1662
  base_dir,
1608
1663
  self.get_resolvable_command_line_variables(),
1609
1664
  variables,
1610
- ).result(COMPLETE_LIBRARY_IMPORT_TIME_OUT)
1665
+ ).result(COMPLETE_LIBRARY_IMPORT_TIMEOUT)
1611
1666
 
1612
1667
  def complete_resource_import(
1613
1668
  self,
@@ -1622,7 +1677,7 @@ class ImportsManager:
1622
1677
  base_dir,
1623
1678
  self.get_resolvable_command_line_variables(),
1624
1679
  variables,
1625
- ).result(COMPLETE_RESOURCE_IMPORT_TIME_OUT)
1680
+ ).result(COMPLETE_RESOURCE_IMPORT_TIMEOUT)
1626
1681
 
1627
1682
  def complete_variables_import(
1628
1683
  self,
@@ -1637,7 +1692,7 @@ class ImportsManager:
1637
1692
  base_dir,
1638
1693
  self.get_resolvable_command_line_variables(),
1639
1694
  variables,
1640
- ).result(COMPLETE_VARIABLES_IMPORT_TIME_OUT)
1695
+ ).result(COMPLETE_VARIABLES_IMPORT_TIMEOUT)
1641
1696
 
1642
1697
  def resolve_variable(
1643
1698
  self,
@@ -65,7 +65,6 @@ class KeywordFinder:
65
65
  self.multiple_keywords_result = None
66
66
  self.result_bdd_prefix = None
67
67
 
68
- # TODO: make this threadsafe
69
68
  def find_keyword(
70
69
  self,
71
70
  name: Optional[str],
@@ -87,9 +86,25 @@ class KeywordFinder:
87
86
  try:
88
87
  result = self._find_keyword(name, handle_bdd_style)
89
88
  if result is None:
89
+ error_message = "No keyword with found."
90
+
91
+ if name and name.strip(": ").upper() == "FOR":
92
+ error_message = (
93
+ f"Support for the old FOR loop syntax has been removed. "
94
+ f"Replace '{name}' with 'FOR', end the loop with 'END', and "
95
+ f"remove escaping backslashes."
96
+ )
97
+ elif name and name == "\\":
98
+ error_message = (
99
+ "No keyword with name '\\' found. If it is used inside a for "
100
+ "loop, remove escaping backslashes and end the loop with 'END'."
101
+ )
102
+ else:
103
+ error_message = f"No keyword with name '{name}' found."
104
+
90
105
  self.diagnostics.append(
91
106
  DiagnosticsEntry(
92
- f"No keyword with name '{name}' found.",
107
+ error_message,
93
108
  DiagnosticSeverity.ERROR,
94
109
  Error.KEYWORD_NOT_FOUND,
95
110
  )
@@ -306,17 +321,32 @@ class KeywordFinder:
306
321
  return True
307
322
  return False
308
323
 
309
- def _is_better_match(
310
- self,
311
- candidate: Tuple[Optional[LibraryEntry], KeywordDoc],
312
- other: Tuple[Optional[LibraryEntry], KeywordDoc],
313
- ) -> bool:
314
- return (
315
- other[1].matcher.embedded_arguments is not None
316
- and candidate[1].matcher.embedded_arguments is not None
317
- and other[1].matcher.embedded_arguments.match(candidate[1].name) is not None
318
- and candidate[1].matcher.embedded_arguments.match(other[1].name) is None
319
- )
324
+ if get_robot_version() >= (7, 3):
325
+
326
+ def _is_better_match(
327
+ self,
328
+ candidate: Tuple[Optional[LibraryEntry], KeywordDoc],
329
+ other: Tuple[Optional[LibraryEntry], KeywordDoc],
330
+ ) -> bool:
331
+ return (
332
+ other[1].matcher.embedded_arguments is not None
333
+ and candidate[1].matcher.embedded_arguments is not None
334
+ and other[1].matcher.embedded_arguments.matches(candidate[1].name)
335
+ and not candidate[1].matcher.embedded_arguments.matches(other[1].name)
336
+ )
337
+ else:
338
+
339
+ def _is_better_match(
340
+ self,
341
+ candidate: Tuple[Optional[LibraryEntry], KeywordDoc],
342
+ other: Tuple[Optional[LibraryEntry], KeywordDoc],
343
+ ) -> bool:
344
+ return (
345
+ other[1].matcher.embedded_arguments is not None
346
+ and candidate[1].matcher.embedded_arguments is not None
347
+ and other[1].matcher.embedded_arguments.match(candidate[1].name) is not None
348
+ and candidate[1].matcher.embedded_arguments.match(other[1].name) is None
349
+ )
320
350
 
321
351
  @functools.cached_property
322
352
  def _resource_imports(self) -> List[ResourceEntry]: