Skip to content

Commit

Permalink
Add explicit converter for Any. Fixes #4647.
Browse files Browse the repository at this point in the history
  • Loading branch information
pekkaklarck committed Feb 7, 2023
1 parent 600aa75 commit 1227fc8
Show file tree
Hide file tree
Showing 9 changed files with 129 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ Set with incompatible types
Invalid Set
Check Test Case ${TESTNAME}

Any
Check Test Case ${TESTNAME}

None as default
Check Test Case ${TESTNAME}

Expand Down
95 changes: 51 additions & 44 deletions atest/robot/libdoc/datatypes_py-json.robot
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Init docs
Keyword Arguments
[Template] Verify Argument Models
${MODEL}[keywords][0][args] value operator: AssertionOperator | None = None exp: str = something?
${MODEL}[keywords][1][args] arg: CustomType arg2: CustomType2 arg3: CustomType
${MODEL}[keywords][1][args] arg: CustomType arg2: CustomType2 arg3: CustomType arg4: Unknown
${MODEL}[keywords][2][args] funny: bool | int | float | str | AssertionOperator | Small | GeoLocation | None = equal
${MODEL}[keywords][3][args] location: GeoLocation
${MODEL}[keywords][4][args] list_of_str: List[str] dict_str_int: Dict[str, int] whatever: Any *args: List[Any]
Expand All @@ -38,9 +38,9 @@ TypedDict
... <li><code>accuracy</code> <b>Optional</b> Non-negative accuracy value. Defaults to 0.</li>
... </ul>
... <p>Example usage: <code>{'latitude': 59.95, 'longitude': 30.31667}</code></p>
${MODEL}[typedocs][6][type] TypedDict
${MODEL}[typedocs][6][name] GeoLocation
${MODEL}[typedocs][6][doc] <p>Defines the geolocation.</p>
${MODEL}[typedocs][7][type] TypedDict
${MODEL}[typedocs][7][name] GeoLocation
${MODEL}[typedocs][7][doc] <p>Defines the geolocation.</p>
... <ul>
... <li><code>latitude</code> Latitude between -90 and 90.</li>
... <li><code>longitude</code> Longitude between -180 and 180.</li>
Expand Down Expand Up @@ -74,9 +74,9 @@ Enum
${MODEL}[dataTypes][enums][0][name] AssertionOperator
${MODEL}[dataTypes][enums][0][doc] <p>This is some Doc</p>
... <p>This has was defined by assigning to __doc__.</p>
${MODEL}[typedocs][0][type] Enum
${MODEL}[typedocs][0][name] AssertionOperator
${MODEL}[typedocs][0][doc] <p>This is some Doc</p>
${MODEL}[typedocs][1][type] Enum
${MODEL}[typedocs][1][name] AssertionOperator
${MODEL}[typedocs][1][doc] <p>This is some Doc</p>
... <p>This has was defined by assigning to __doc__.</p>

Enum Members
Expand All @@ -85,66 +85,73 @@ Enum Members
FOR ${cur} ${exp} IN ZIP ${MODEL}[dataTypes][enums][0][members] ${exp_list}
Dictionaries Should Be Equal ${cur} ${exp}
END
FOR ${cur} ${exp} IN ZIP ${MODEL}[typedocs][0][members] ${exp_list}
FOR ${cur} ${exp} IN ZIP ${MODEL}[typedocs][1][members] ${exp_list}
Dictionaries Should Be Equal ${cur} ${exp}
END

Custom types
${MODEL}[typedocs][2][type] Custom
${MODEL}[typedocs][2][name] CustomType
${MODEL}[typedocs][2][doc] <p>Converter method doc is used when defined.</p>
${MODEL}[typedocs][3][type] Custom
${MODEL}[typedocs][3][name] CustomType2
${MODEL}[typedocs][3][doc] <p>Class doc is used when converter method has no doc.</p>
${MODEL}[typedocs][3][name] CustomType
${MODEL}[typedocs][3][doc] <p>Converter method doc is used when defined.</p>
${MODEL}[typedocs][4][type] Custom
${MODEL}[typedocs][4][name] CustomType2
${MODEL}[typedocs][4][doc] <p>Class doc is used when converter method has no doc.</p>

Standard types
${MODEL}[typedocs][1][type] Standard
${MODEL}[typedocs][1][name] boolean
${MODEL}[typedocs][1][doc] <p>Strings <code>TRUE</code>, <code>YES</code>, start=True
${MODEL}[typedocs][0][type] Standard
${MODEL}[typedocs][0][name] Any
${MODEL}[typedocs][0][doc] <p>Any value is accepted. No conversion is done.</p>
${MODEL}[typedocs][2][type] Standard
${MODEL}[typedocs][2][name] boolean
${MODEL}[typedocs][2][doc] <p>Strings <code>TRUE</code>, <code>YES</code>, start=True

Standard types with generics
${MODEL}[typedocs][4][type] Standard
${MODEL}[typedocs][4][name] dictionary
${MODEL}[typedocs][4][doc] <p>Strings must be Python <a start=True
${MODEL}[typedocs][8][type] Standard
${MODEL}[typedocs][8][name] list
${MODEL}[typedocs][8][doc] <p>Strings must be Python <a start=True
${MODEL}[typedocs][5][type] Standard
${MODEL}[typedocs][5][name] dictionary
${MODEL}[typedocs][5][doc] <p>Strings must be Python <a start=True
${MODEL}[typedocs][9][type] Standard
${MODEL}[typedocs][9][name] list
${MODEL}[typedocs][9][doc] <p>Strings must be Python <a start=True

Accepted types
${MODEL}[typedocs][1][type] Standard
${MODEL}[typedocs][1][accepts] ['string', 'integer', 'float', 'None']
${MODEL}[typedocs][2][type] Custom
${MODEL}[typedocs][2][accepts] ['string', 'integer']
${MODEL}[typedocs][0][type] Standard
${MODEL}[typedocs][0][accepts] ['Any']
${MODEL}[typedocs][2][type] Standard
${MODEL}[typedocs][2][accepts] ['string', 'integer', 'float', 'None']
${MODEL}[typedocs][3][type] Custom
${MODEL}[typedocs][3][accepts] []
${MODEL}[typedocs][6][type] TypedDict
${MODEL}[typedocs][6][accepts] ['string']
${MODEL}[typedocs][0][type] Enum
${MODEL}[typedocs][0][accepts] ['string']
${MODEL}[typedocs][10][type] Enum
${MODEL}[typedocs][10][accepts] ['string', 'integer']
${MODEL}[typedocs][3][accepts] ['string', 'integer']
${MODEL}[typedocs][4][type] Custom
${MODEL}[typedocs][4][accepts] []
${MODEL}[typedocs][7][type] TypedDict
${MODEL}[typedocs][7][accepts] ['string']
${MODEL}[typedocs][1][type] Enum
${MODEL}[typedocs][1][accepts] ['string']
${MODEL}[typedocs][11][type] Enum
${MODEL}[typedocs][11][accepts] ['string', 'integer']

Usages
${MODEL}[typedocs][1][type] Standard
${MODEL}[typedocs][1][usages] ['Funny Unions']
${MODEL}[typedocs][4][type] Standard
${MODEL}[typedocs][4][usages] ['Typing Types']
${MODEL}[typedocs][2][type] Custom
${MODEL}[typedocs][2][usages] ['Custom']
${MODEL}[typedocs][6][type] TypedDict
${MODEL}[typedocs][6][usages] ['Funny Unions', 'Set Location']
${MODEL}[typedocs][10][type] Enum
${MODEL}[typedocs][10][usages] ['__init__', 'Funny Unions']
${MODEL}[typedocs][2][type] Standard
${MODEL}[typedocs][2][usages] ['Funny Unions']
${MODEL}[typedocs][5][type] Standard
${MODEL}[typedocs][5][usages] ['Typing Types']
${MODEL}[typedocs][3][type] Custom
${MODEL}[typedocs][3][usages] ['Custom']
${MODEL}[typedocs][7][type] TypedDict
${MODEL}[typedocs][7][usages] ['Funny Unions', 'Set Location']
${MODEL}[typedocs][11][type] Enum
${MODEL}[typedocs][11][usages] ['__init__', 'Funny Unions']

Typedoc links in arguments
${MODEL}[keywords][0][args][1][typedocs] {'AssertionOperator': 'AssertionOperator', 'None': 'None'}
${MODEL}[keywords][0][args][2][typedocs] {'str': 'string'}
${MODEL}[keywords][1][args][0][typedocs] {'CustomType': 'CustomType'}
${MODEL}[keywords][1][args][1][typedocs] {'CustomType2': 'CustomType2'}
${MODEL}[keywords][1][args][2][typedocs] {'CustomType': 'CustomType'}
${MODEL}[keywords][1][args][3][typedocs] {}
${MODEL}[keywords][2][args][0][typedocs] {'bool': 'boolean', 'int': 'integer', 'float': 'float', 'str': 'string', 'AssertionOperator': 'AssertionOperator', 'Small': 'Small', 'GeoLocation': 'GeoLocation', 'None': 'None'}
${MODEL}[keywords][4][args][0][typedocs] {'List[str]': 'list'}
${MODEL}[keywords][4][args][1][typedocs] {'Dict[str, int]': 'dictionary'}
${MODEL}[keywords][4][args][2][typedocs] {}
${MODEL}[keywords][4][args][2][typedocs] {'Any': 'Any'}
${MODEL}[keywords][4][args][3][typedocs] {'List[Any]': 'list'}

*** Keywords ***
Expand Down
37 changes: 23 additions & 14 deletions atest/robot/libdoc/datatypes_py-xml.robot
Original file line number Diff line number Diff line change
Expand Up @@ -52,49 +52,58 @@ Custom

Standard
DataType Standard Should Be 0
... Any
... Any value is accepted. No conversion is done.
DataType Standard Should Be 1
... boolean
... Strings ``TRUE``, ``YES``, ``ON`` and ``1`` are converted to Boolean ``True``,

Standard with generics
DataType Standard Should Be 1
DataType Standard Should Be 2
... dictionary
... Strings must be Python [[]https://docs.python.org/library/stdtypes.html#dict|dictionary]
DataType Standard Should Be 4
DataType Standard Should Be 5
... list
... Strings must be Python [[]https://docs.python.org/library/stdtypes.html#list|list]

Accepted types
Accepted Types Should Be 1 Standard boolean
Accepted Types Should Be 0 Standard Any
... Any
Accepted Types Should Be 2 Standard boolean
... string integer float None
Accepted Types Should Be 2 Custom CustomType
Accepted Types Should Be 3 Custom CustomType
... string integer
Accepted Types Should Be 3 Custom CustomType2
Accepted Types Should Be 6 TypedDict GeoLocation
Accepted Types Should Be 4 Custom CustomType2
Accepted Types Should Be 7 TypedDict GeoLocation
... string
Accepted Types Should Be 0 Enum AssertionOperator
Accepted Types Should Be 1 Enum AssertionOperator
... string
Accepted Types Should Be 10 Enum Small
Accepted Types Should Be 11 Enum Small
... string integer

Usages
Usages Should Be 1 Standard boolean
Usages Should Be 0 Standard Any
... Typing Types
Usages Should Be 2 Standard boolean
... Funny Unions
Usages Should Be 4 Standard dictionary
Usages Should Be 5 Standard dictionary
... Typing Types
Usages Should Be 2 Custom CustomType
Usages Should Be 3 Custom CustomType
... Custom
Usages Should be 6 TypedDict GeoLocation
Usages Should be 7 TypedDict GeoLocation
... Funny Unions Set Location
Usages Should Be 10 Enum Small
Usages Should Be 11 Enum Small
... __init__ Funny Unions

Typedoc links in arguments
Typedoc links should be 0 1 AssertionOperator None
Typedoc links should be 0 2 str:string
Typedoc links should be 1 0 CustomType
Typedoc links should be 1 1 CustomType2
Typedoc links should be 1 2 CustomType
Typedoc links should be 1 3 Unknown:
Typedoc links should be 2 0 bool:boolean int:integer float str:string AssertionOperator Small GeoLocation None
Typedoc links should be 4 0 List[str]:list
Typedoc links should be 4 1 Dict[str, int]:dictionary
Typedoc links should be 4 2 Any:
Typedoc links should be 4 2 Any:Any
Typedoc links should be 4 3 List[Any]:list
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ def mutable_set_with_types(argument: MutableSet[float], expected=None):
_validate_type(argument, expected)


def any_(argument: Any = 1, expected=None):
_validate_type(argument, expected)


def none_as_default(argument: List = None, expected=None):
_validate_type(argument, expected)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,17 @@ Invalid Set
Set {} error=Value is dictionary, not set.
Set ooops error=Invalid expression.

Any
Any hello 'hello'
Any 42 '42'
Any ${42} 42
Any None 'None'
Any ${None} None

None as default
None as default
None as default [1, 2, 3, 4] [1, 2, 3, 4]
None as default NoNe None

None as default with Any
[Documentation] `a: Any = None` was same as `a: Any|None = None` prior to Python 3.11.
Expand Down
8 changes: 6 additions & 2 deletions atest/testdata/libdoc/DataTypesLibrary.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ def __init__(self, value):
self.value = value


class Unknown:
pass


class A:
@classmethod
def not_used_converter_should_not_be_documented(cls, value):
Expand Down Expand Up @@ -103,7 +107,7 @@ def set_location(self, location: GeoLocation):
def assert_something(self, value, operator: Optional[AssertionOperator] = None, exp: str = 'something?'):
"""This links to `AssertionOperator` .
This is the next Line that links to 'Set Location` .
This is the next Line that links to `Set Location` .
"""
pass

Expand All @@ -124,5 +128,5 @@ def funny_unions(self,
def typing_types(self, list_of_str: List[str], dict_str_int: Dict[str, int], whatever: Any, *args: List[Any]):
pass

def custom(self, arg: CustomType, arg2: 'CustomType2', arg3: CustomType):
def custom(self, arg: CustomType, arg2: 'CustomType2', arg3: CustomType, arg4: Unknown):
pass
Original file line number Diff line number Diff line change
Expand Up @@ -1274,6 +1274,12 @@ Other types cause conversion failures.
| None_ | | NoneType | str_ | String `NONE` (case-insensitive) is converted to the Python | | `None` |
| | | | | `None` object. Other values cause an error. | |
+-------------+---------------+------------+--------------+----------------------------------------------------------------+--------------------------------------+
| Any_ | | | Any | Any value is accepted. No conversion is done. | |
| | | | | | |
| | | | | New in RF 6.1. Any_ was not recognized with earlier versions, | |
| | | | | but conversion may have been done based on `default values | |
| | | | | <Implicit argument types based on default values_>`__. | |
+-------------+---------------+------------+--------------+----------------------------------------------------------------+--------------------------------------+
| list_ | Sequence_ | | str_, | Strings must be Python list literals. They are converted | | `['one', 'two']` |
| | | | Sequence_ | to actual lists using the `ast.literal_eval`_ function. | | `[('one', 1), ('two', 2)]` |
| | | | | They can contain any values `ast.literal_eval` supports, | |
Expand Down Expand Up @@ -1304,13 +1310,14 @@ Other types cause conversion failures.
| | | | | | | `{'width': 1600, 'enabled': True}` |
+-------------+---------------+------------+--------------+----------------------------------------------------------------+--------------------------------------+

.. note:: Starting from Robot Framework 5.0, types that are automatically converted are
.. note:: Starting from Robot Framework 5.0, types that have a converted are
automatically shown in Libdoc_ outputs.

.. note:: Prior to Robot Framework 4.0, most types supported converting string `NONE` (case-insensitively) to Python
`None`. That support has been removed and `None` conversion is only done if an argument has `None` as an
explicit type or as a default value.

.. _Any: https://docs.python.org/library/typing.html#typing.Any
.. _bool: https://docs.python.org/library/functions.html#bool
.. _int: https://docs.python.org/library/functions.html#int
.. _Integral: https://docs.python.org/library/numbers.html#numbers.Integral
Expand Down Expand Up @@ -1796,7 +1803,6 @@ information about conversion. It is especially important to document
converter functions registered for existing types, because their own
documentation is likely not very useful in this context.


`@keyword` decorator
~~~~~~~~~~~~~~~~~~~~

Expand Down
4 changes: 4 additions & 0 deletions src/robot/libdocpkg/standardtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@
from datetime import date, datetime, timedelta
from decimal import Decimal
from pathlib import Path
from typing import Any


STANDARD_TYPE_DOCS = {
Any: '''\
Any value is accepted. No conversion is done.
''',
bool: '''\
Strings ``TRUE``, ``YES``, ``ON`` and ``1`` are converted to Boolean ``True``,
the empty string as well as strings ``FALSE``, ``NO``, ``OFF`` and ``0``
Expand Down
23 changes: 22 additions & 1 deletion src/robot/running/arguments/typeconverters.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@
from ast import literal_eval
from collections import OrderedDict
from collections.abc import ByteString, Container, Mapping, Sequence, Set
from typing import Any, Tuple, TypeVar, Union
from datetime import datetime, date, timedelta
from decimal import InvalidOperation, Decimal
from enum import Enum
from numbers import Integral, Real
from os import PathLike
from pathlib import Path, PurePath
from typing import Any, Tuple, TypeVar, Union

from robot.conf import Languages
from robot.libraries.DateTime import convert_date, convert_time
Expand Down Expand Up @@ -216,6 +216,27 @@ def _find_by_int_value(self, enum, value):
f"Available: {seq2str(values)}")


@TypeConverter.register
class AnyConverter(TypeConverter):
type = Any
type_name = 'Any'
aliases = ('any',)
value_types = (Any,)

@classmethod
def handles(cls, type_):
return type_ is Any

def no_conversion_needed(self, value):
return True

def _convert(self, value, explicit_type=True):
return value

def _handles_value(self, value):
return True


@TypeConverter.register
class StringConverter(TypeConverter):
type = str
Expand Down

0 comments on commit 1227fc8

Please sign in to comment.