Skip to content

Commit adae90e

Browse files
committed
Support for translations
1 parent 8e36e69 commit adae90e

15 files changed

+271
-115
lines changed

atest/SmallLibrary.py

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from pathlib import Path
2+
from typing import Optional
3+
4+
from robot.api import logger
5+
from robotlibcore import DynamicCore, keyword
6+
7+
class SmallLibrary(DynamicCore):
8+
"""Library documentation."""
9+
10+
def __init__(self, translation: Optional[Path] = None):
11+
"""__init__ documentation."""
12+
logger.warn(translation.absolute())
13+
logger.warn(type(translation))
14+
15+
DynamicCore.__init__(self, [], translation.absolute())
16+
17+
@keyword(tags=["tag1", "tag2"])
18+
def normal_keyword(self, arg: int, other: str) -> str:
19+
"""I have doc
20+
21+
Multiple lines.
22+
Other line.
23+
"""
24+
data = f"{arg} {other}"
25+
print(data)
26+
return data
27+
28+
def not_keyword(self, data: str) -> str:
29+
print(data)
30+
return data
31+
32+
@keyword(name="This Is New Name", tags=["tag1", "tag2"])
33+
def name_changed(self, some: int, other: int) -> int:
34+
"""This one too"""
35+
print(f"{some} {type(some)}, {other} {type(other)}")
36+
return some + other

atest/tests_types.robot

+6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
*** Settings ***
22
Library DynamicTypesLibrary.py
33
Library DynamicTypesAnnotationsLibrary.py xxx
4+
Library SmallLibrary.py ${CURDIR}/translation.json
45

56

67
*** Variables ***
@@ -115,6 +116,11 @@ Python 3.10 New Type Hints
115116
Keyword With Named Only Arguments
116117
Kw With Named Arguments arg=1
117118

119+
SmallLibray With New Name
120+
${data} = SmallLibrary.Other Name 123 abc
121+
Should Be Equal ${data} 123 abc
122+
${data} = SmallLibrary.name_changed_again 1 2
123+
Should Be Equal As Integers ${data} 3
118124

119125
*** Keywords ***
120126
Import DynamicTypesAnnotationsLibrary In Python 3.10 Only

atest/translation.json

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"normal_keyword": {
3+
"name": "other_name",
4+
"doc": "This is new doc"
5+
},
6+
"name_changed": {
7+
"name": "name_changed_again",
8+
"doc": "This is also replaced.\n\nnew line."
9+
}
10+
11+
}

requirements-dev.txt

+1
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ rellu >= 0.7
1212
twine
1313
wheel
1414
typing-extensions >= 4.5.0
15+
approvaltests >= 11.1.1

src/robotlibcore.py

+42-9
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@
1919
https://github.com/robotframework/PythonLibCore
2020
"""
2121
import inspect
22+
import json
2223
import os
2324
from dataclasses import dataclass
25+
from pathlib import Path
2426
from typing import Any, Callable, List, Optional, Union, get_type_hints
2527

28+
from robot.api import logger
2629
from robot.api.deco import keyword # noqa: F401
2730
from robot.errors import DataError
2831
from robot.utils import Importer
@@ -42,28 +45,47 @@ class NoKeywordFound(PythonLibCoreException):
4245
pass
4346

4447

48+
def _translation(translation: Optional[Path] = None):
49+
if translation and isinstance(translation, Path) and translation.is_file():
50+
with translation.open("r") as file:
51+
try:
52+
return json.load(file)
53+
except json.decoder.JSONDecodeError:
54+
logger.warn(f"Could not find file: {translation}")
55+
return {}
56+
else:
57+
return {}
58+
59+
4560
class HybridCore:
46-
def __init__(self, library_components: List) -> None:
61+
def __init__(self, library_components: List, translation: Optional[Path] = None) -> None:
4762
self.keywords = {}
4863
self.keywords_spec = {}
4964
self.attributes = {}
50-
self.add_library_components(library_components)
51-
self.add_library_components([self])
65+
translation_data = _translation(translation)
66+
self.add_library_components(library_components, translation_data)
67+
self.add_library_components([self], translation_data)
5268
self.__set_library_listeners(library_components)
5369

54-
def add_library_components(self, library_components: List):
55-
self.keywords_spec["__init__"] = KeywordBuilder.build(self.__init__) # type: ignore
70+
def add_library_components(self, library_components: List, translation: Optional[dict] = None):
71+
translation = translation if translation else {}
72+
self.keywords_spec["__init__"] = KeywordBuilder.build(self.__init__, translation) # type: ignore
5673
for component in library_components:
5774
for name, func in self.__get_members(component):
5875
if callable(func) and hasattr(func, "robot_name"):
5976
kw = getattr(component, name)
60-
kw_name = func.robot_name or name
77+
kw_name = self.__get_keyword_name(func, name, translation)
6178
self.keywords[kw_name] = kw
62-
self.keywords_spec[kw_name] = KeywordBuilder.build(kw)
79+
self.keywords_spec[kw_name] = KeywordBuilder.build(kw, translation)
6380
# Expose keywords as attributes both using original
6481
# method names as well as possible custom names.
6582
self.attributes[name] = self.attributes[kw_name] = kw
6683

84+
def __get_keyword_name(self, func: Callable, name: str, translation: dict):
85+
if name in translation:
86+
return translation[name]["name"]
87+
return func.robot_name or name
88+
6789
def __set_library_listeners(self, library_components: list):
6890
listeners = self.__get_manually_registered_listeners()
6991
listeners.extend(self.__get_component_listeners([self, *library_components]))
@@ -198,13 +220,24 @@ def __get_keyword_path(self, method):
198220

199221
class KeywordBuilder:
200222
@classmethod
201-
def build(cls, function):
223+
def build(cls, function, translation: Optional[dict] = None):
224+
translation = translation if translation else {}
202225
return KeywordSpecification(
203226
argument_specification=cls._get_arguments(function),
204-
documentation=inspect.getdoc(function) or "",
227+
documentation=cls.get_doc(function, translation),
205228
argument_types=cls._get_types(function),
206229
)
207230

231+
@classmethod
232+
def get_doc(cls, function, translation: dict):
233+
if kw := cls._get_kw_transtation(function, translation):
234+
return kw["doc"]
235+
return inspect.getdoc(function) or ""
236+
237+
@classmethod
238+
def _get_kw_transtation(cls, function, translation: dict):
239+
return translation.get(function.__name__, {})
240+
208241
@classmethod
209242
def unwrap(cls, function):
210243
return inspect.unwrap(function)

utest/test_keyword_builder.py

+12-12
Original file line numberDiff line numberDiff line change
@@ -17,59 +17,59 @@ def dyn_types():
1717

1818

1919
def test_documentation(lib):
20-
spec = KeywordBuilder.build(lib.positional_args)
20+
spec = KeywordBuilder.build(lib.positional_args, {})
2121
assert spec.documentation == "Some documentation\n\nMulti line docs"
22-
spec = KeywordBuilder.build(lib.positional_and_default)
22+
spec = KeywordBuilder.build(lib.positional_and_default, {})
2323
assert spec.documentation == ""
2424

2525

2626
def test_no_args(lib):
27-
spec = KeywordBuilder.build(lib.no_args)
27+
spec = KeywordBuilder.build(lib.no_args, {})
2828
assert spec.argument_specification == []
2929

3030

3131
def test_positional_args(lib):
32-
spec = KeywordBuilder.build(lib.positional_args)
32+
spec = KeywordBuilder.build(lib.positional_args, {})
3333
assert spec.argument_specification == ["arg1", "arg2"]
3434

3535

3636
def test_positional_and_named(lib):
37-
spec = KeywordBuilder.build(lib.positional_and_default)
37+
spec = KeywordBuilder.build(lib.positional_and_default, {})
3838
assert spec.argument_specification == ["arg1", "arg2", ("named1", "string1"), ("named2", 123)]
3939

4040

4141
def test_named_only_default_only(lib):
42-
spec = KeywordBuilder.build(lib.default_only)
42+
spec = KeywordBuilder.build(lib.default_only, {})
4343
assert spec.argument_specification == [("named1", "string1"), ("named2", 123)]
4444

4545

4646
def test_varargs_and_kwargs(lib):
47-
spec = KeywordBuilder.build(lib.varargs_kwargs)
47+
spec = KeywordBuilder.build(lib.varargs_kwargs, {})
4848
assert spec.argument_specification == ["*vargs", "**kwargs"]
4949

5050

5151
def test_named_only_part2(lib):
52-
spec = KeywordBuilder.build(lib.named_only)
52+
spec = KeywordBuilder.build(lib.named_only, {})
5353
assert spec.argument_specification == ["*varargs", "key1", "key2"]
5454

5555

5656
def test_named_only(lib):
57-
spec = KeywordBuilder.build(lib.named_only_with_defaults)
57+
spec = KeywordBuilder.build(lib.named_only_with_defaults, {})
5858
assert spec.argument_specification == ["*varargs", "key1", "key2", ("key3", "default1"), ("key4", True)]
5959

6060

6161
def test_types_in_keyword_deco(lib):
62-
spec = KeywordBuilder.build(lib.positional_args)
62+
spec = KeywordBuilder.build(lib.positional_args, {})
6363
assert spec.argument_types == {"arg1": str, "arg2": int}
6464

6565

6666
def test_types_disabled_in_keyword_deco(lib):
67-
spec = KeywordBuilder.build(lib.types_disabled)
67+
spec = KeywordBuilder.build(lib.types_disabled, {})
6868
assert spec.argument_types is None
6969

7070

7171
def test_types_(lib):
72-
spec = KeywordBuilder.build(lib.args_with_type_hints)
72+
spec = KeywordBuilder.build(lib.args_with_type_hints, {})
7373
assert spec.argument_types == {"arg3": str, "arg4": type(None), "return": bool}
7474

7575

utest/test_plugin_api.py

+11-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import my_plugin_test
1+
from helpers import my_plugin_test
22
import pytest
33
from robotlibcore import Module, PluginError, PluginParser
44

@@ -37,22 +37,22 @@ def test_plugins_string_to_modules(plugin_parser):
3737
]
3838

3939

40-
def test_parse_plugins(plugin_parser):
41-
plugins = plugin_parser.parse_plugins("my_plugin_test.TestClass")
40+
def test_parse_plugins(plugin_parser: PluginParser):
41+
plugins = plugin_parser.parse_plugins("helpers.my_plugin_test.TestClass")
4242
assert len(plugins) == 1
4343
assert isinstance(plugins[0], my_plugin_test.TestClass)
44-
plugins = plugin_parser.parse_plugins("my_plugin_test.TestClass,my_plugin_test.TestClassWithBase")
44+
plugins = plugin_parser.parse_plugins("helpers.my_plugin_test.TestClass,helpers.my_plugin_test.TestClassWithBase")
4545
assert len(plugins) == 2
4646
assert isinstance(plugins[0], my_plugin_test.TestClass)
4747
assert isinstance(plugins[1], my_plugin_test.TestClassWithBase)
4848

4949

5050
def test_parse_plugins_as_list(plugin_parser):
51-
plugins = plugin_parser.parse_plugins(["my_plugin_test.TestClass"])
51+
plugins = plugin_parser.parse_plugins(["helpers.my_plugin_test.TestClass"])
5252
assert len(plugins) == 1
5353
assert isinstance(plugins[0], my_plugin_test.TestClass)
5454
plugins = plugin_parser.parse_plugins(
55-
["my_plugin_test.TestClass", "my_plugin_test.TestClassWithBase"]
55+
["helpers.my_plugin_test.TestClass", "helpers.my_plugin_test.TestClassWithBase"]
5656
)
5757
assert len(plugins) == 2
5858
assert isinstance(plugins[0], my_plugin_test.TestClass)
@@ -61,16 +61,16 @@ def test_parse_plugins_as_list(plugin_parser):
6161

6262
def test_parse_plugins_with_base():
6363
parser = PluginParser(my_plugin_test.LibraryBase)
64-
plugins = parser.parse_plugins("my_plugin_test.TestClassWithBase")
64+
plugins = parser.parse_plugins("helpers.my_plugin_test.TestClassWithBase")
6565
assert len(plugins) == 1
6666
assert isinstance(plugins[0], my_plugin_test.TestClassWithBase)
6767
with pytest.raises(PluginError) as excinfo:
68-
parser.parse_plugins("my_plugin_test.TestClass")
69-
assert "Plugin does not inherit <class 'my_plugin_test.LibraryBase'>" in str(excinfo.value)
68+
parser.parse_plugins("helpers.my_plugin_test.TestClass")
69+
assert "Plugin does not inherit <class 'helpers.my_plugin_test.LibraryBase'>" in str(excinfo.value)
7070

7171

7272
def test_plugin_keywords(plugin_parser):
73-
plugins = plugin_parser.parse_plugins("my_plugin_test.TestClass,my_plugin_test.TestClassWithBase")
73+
plugins = plugin_parser.parse_plugins("helpers.my_plugin_test.TestClass,helpers.my_plugin_test.TestClassWithBase")
7474
keywords = plugin_parser.get_plugin_keywords(plugins)
7575
assert len(keywords) == 2
7676
assert keywords[0] == "another_keyword"
@@ -83,7 +83,7 @@ class PythonObject:
8383
y = 2
8484
python_object = PythonObject()
8585
parser = PluginParser(my_plugin_test.LibraryBase, [python_object])
86-
plugins = parser.parse_plugins("my_plugin_test.TestPluginWithPythonArgs;4")
86+
plugins = parser.parse_plugins("helpers.my_plugin_test.TestPluginWithPythonArgs;4")
8787
assert len(plugins) == 1
8888
plugin = plugins[0]
8989
assert plugin.python_class.x == 1

0 commit comments

Comments
 (0)