Skip to content

Commit

Permalink
Add argument conversion for pathlib.Path.
Browse files Browse the repository at this point in the history
Fixes #4461.
  • Loading branch information
pekkaklarck committed Sep 18, 2022
1 parent 744a8da commit 14321c0
Show file tree
Hide file tree
Showing 12 changed files with 149 additions and 8 deletions.
6 changes: 6 additions & 0 deletions atest/robot/keywords/type_conversion/annotations.robot
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ Timedelta
Invalid timedelta
Check Test Case ${TESTNAME}

Path
Check Test Case ${TESTNAME}

Invalid Path
Check Test Case ${TESTNAME}

Enum
Check Test Case ${TESTNAME}

Expand Down
6 changes: 6 additions & 0 deletions atest/robot/keywords/type_conversion/default_values.robot
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ Timedelta
Invalid timedelta
Check Test Case ${TESTNAME}

Path
Check Test Case ${TESTNAME}

Invalid Path
Check Test Case ${TESTNAME}

Enum
Check Test Case ${TESTNAME}

Expand Down
6 changes: 6 additions & 0 deletions atest/robot/keywords/type_conversion/keyword_decorator.robot
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ Timedelta
Invalid timedelta
Check Test Case ${TESTNAME}

Path
Check Test Case ${TESTNAME}

Invalid Path
Check Test Case ${TESTNAME}

Enum
Check Test Case ${TESTNAME}

Expand Down
20 changes: 15 additions & 5 deletions atest/testdata/keywords/type_conversion/Annotations.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
from collections import abc
from datetime import datetime, date, timedelta
from decimal import Decimal
try:
from enum import Flag, Enum, IntFlag, IntEnum
except ImportError: # Python 3.5
from enum import Enum, IntEnum
Flag, IntFlag = Enum, IntEnum
from enum import Flag, Enum, IntFlag, IntEnum
from functools import wraps
from numbers import Integral, Real
from os import PathLike
from pathlib import Path, PurePath

# Needed by `eval()` in `_validate_type()`.
import collections
Expand Down Expand Up @@ -101,6 +99,18 @@ def timedelta_(argument: timedelta, expected=None):
_validate_type(argument, expected)


def path(argument: Path, expected=None):
_validate_type(argument, expected)


def pure_path(argument: PurePath, expected=None):
_validate_type(argument, expected)


def path_like(argument: PathLike, expected=None):
_validate_type(argument, expected)


def enum_(argument: MyEnum, expected=None):
_validate_type(argument, expected)

Expand Down
9 changes: 9 additions & 0 deletions atest/testdata/keywords/type_conversion/DefaultValues.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from enum import Flag, Enum, IntFlag, IntEnum
from datetime import datetime, date, timedelta
from decimal import Decimal
from pathlib import Path, PurePath # Path needed by `eval()` in `_validate_type()`.

from robot.api.deco import keyword

Expand Down Expand Up @@ -70,6 +71,14 @@ def timedelta_(argument=timedelta(), expected=None):
_validate_type(argument, expected)


def path(argument=Path(), expected=None):
_validate_type(argument, expected)


def pure_path(argument=PurePath(), expected=None):
_validate_type(argument, expected)


def enum(argument=MyEnum.FOO, expected=None):
_validate_type(argument, expected)

Expand Down
17 changes: 17 additions & 0 deletions atest/testdata/keywords/type_conversion/KeywordDecorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from enum import Flag, Enum, IntFlag, IntEnum
from fractions import Fraction # Needed by `eval()` in `_validate_type()`.
from numbers import Integral, Real
from os import PathLike
from pathlib import Path, PurePath
from typing import Union

from robot.api.deco import keyword
Expand Down Expand Up @@ -101,6 +103,21 @@ def timedelta_(argument, expected=None):
_validate_type(argument, expected)


@keyword(types={'argument': Path})
def path(argument, expected=None):
_validate_type(argument, expected)


@keyword(types={'argument': PurePath})
def pure_path(argument, expected=None):
_validate_type(argument, expected)


@keyword(types={'argument': PathLike})
def path_like(argument, expected=None):
_validate_type(argument, expected)


@keyword(types={'argument': MyEnum})
def enum(argument, expected=None):
_validate_type(argument, expected)
Expand Down
23 changes: 23 additions & 0 deletions atest/testdata/keywords/type_conversion/annotations.robot
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ ${FRACTION 1/2} ${{fractions.Fraction(1,2)}}
${DECIMAL 1/2} ${{decimal.Decimal('0.5')}}
${DEQUE} ${{collections.deque([1, 2, 3])}}
${MAPPING} ${{type('M', (collections.abc.Mapping,), {'__getitem__': lambda s, k: {'a': 1}[k], '__iter__': lambda s: iter({'a': 1}), '__len__': lambda s: 1})()}}
${PATH} ${{pathlib.Path('x/y')}}
${PUREPATH} ${{pathlib.PurePath('x/y')}}

*** Test Cases ***
Integer
Expand Down Expand Up @@ -260,6 +262,27 @@ Invalid timedelta
Timedelta 01:02:03:04 error=Invalid time string '01:02:03:04'.
Timedelta ${LIST} arg_type=list

Path
Path path Path('path')
Path two/components Path(r'two${/}components')
Path two${/}components Path(r'two${/}components')
Path ${PATH} Path('x/y')
Path ${PUREPATH} Path('x/y')
PurePath path Path('path')
PurePath two/components Path(r'two${/}components')
PurePath two${/}components Path(r'two${/}components')
PurePath ${PATH} Path('x/y')
PurePath ${PUREPATH} PurePath('x/y')
PathLike path Path('path')
PathLike two/components Path(r'two${/}components')
PathLike two${/}components Path(r'two${/}components')
PathLike ${PATH} Path('x/y')
PathLike ${PUREPATH} PurePath('x/y')

Invalid Path
[Template] Conversion Should Fail
Path ${1} type=Path arg_type=integer

Enum
Enum FOO MyEnum.FOO
Enum bar MyEnum.bar
Expand Down
7 changes: 6 additions & 1 deletion atest/testdata/keywords/type_conversion/conversion.resource
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@ Conversion Should Fail
[Arguments] ${kw} @{args} ${error}= ${type}=${kw.lower()} ${arg_type}= &{kwargs}
${arg} = Evaluate (list($args) + list($kwargs.values()))[0]
${message} = Catenate
... ValueError:
... Argument 'argument' got value '${arg}'${{" (${arg_type})" if $arg_type else ""}}
... that cannot be converted to ${type}${{": ${error}" if $error else "."}}
TRY
Run Keyword ${kw} @{args} &{kwargs}
EXCEPT ValueError: ${message} type=${{'GLOB' if '*' in $error else 'LITERAL'}}
EXCEPT ${message} type=${{'GLOB' if '*' in $error else 'LITERAL'}}
No Operation
EXCEPT AS ${err}
Fail Expected error\n\n \ ${message}\n\nbut got\n\n \ ${err}
ELSE
Fail Expected error '${message}' did not occur.
END

String None is converted to None object
Expand Down
18 changes: 18 additions & 0 deletions atest/testdata/keywords/type_conversion/default_values.robot
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ Resource conversion.resource
*** Variables ***
@{LIST} foo bar
&{DICT} foo=${1} bar=${2}
${PATH} ${{pathlib.Path('x/y')}}
${PUREPATH} ${{pathlib.PurePath('x/y')}}

*** Test Cases ***
Integer
Expand Down Expand Up @@ -174,6 +176,22 @@ Invalid timedelta
Timedelta foobar
Timedelta 01:02:03:04

Path
Path path Path('path')
Path two/components Path(r'two${/}components')
Path two${/}components Path(r'two${/}components')
Path ${PATH} Path('x/y')
Path ${PUREPATH} Path('x/y')
PurePath path Path('path')
PurePath two/components Path(r'two${/}components')
PurePath two${/}components Path(r'two${/}components')
PurePath ${PATH} Path('x/y')
PurePath ${PUREPATH} PurePath('x/y')

Invalid Path
[Template] Invalid value is passed as-is
Path ${1} ${1}

Enum
Enum FOO MyEnum.FOO
Enum bar MyEnum.bar
Expand Down
26 changes: 24 additions & 2 deletions atest/testdata/keywords/type_conversion/keyword_decorator.robot
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ Resource conversion.resource
&{DICT} foo=${1} bar=${2}
${FRACTION 1/2} ${{fractions.Fraction(1,2)}}
${DECIMAL 1/2} ${{decimal.Decimal('0.5')}}
${u} ${{'u' if sys.version_info[0] == 2 and sys.platform != 'cli' else ''}}
${PATH} ${{pathlib.Path('x/y')}}
${PUREPATH} ${{pathlib.PurePath('x/y')}}

*** Test Cases ***
Integer
Expand Down Expand Up @@ -152,7 +153,7 @@ String
String 2 '2'
String ${42} '42'
String ${None} 'None'
String ${LIST} "[${u}'foo', ${u}'bar']"
String ${LIST} "['foo', 'bar']"

Invalid string
[Template] Conversion Should Fail
Expand Down Expand Up @@ -259,6 +260,27 @@ Invalid timedelta
Timedelta 01:02:03:04 error=Invalid time string '01:02:03:04'.
Timedelta ${LIST} arg_type=list

Path
Path path Path('path')
Path two/components Path(r'two${/}components')
Path two${/}components Path(r'two${/}components')
Path ${PATH} Path('x/y')
Path ${PUREPATH} Path('x/y')
PurePath path Path('path')
PurePath two/components Path(r'two${/}components')
PurePath two${/}components Path(r'two${/}components')
PurePath ${PATH} Path('x/y')
PurePath ${PUREPATH} PurePath('x/y')
PathLike path Path('path')
PathLike two/components Path(r'two${/}components')
PathLike two${/}components Path(r'two${/}components')
PathLike ${PATH} Path('x/y')
PathLike ${PUREPATH} PurePath('x/y')

Invalid Path
[Template] Conversion Should Fail
Path ${1} type=Path arg_type=integer

Enum
Enum FOO MyEnum.FOO
Enum bar MyEnum.bar
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1247,6 +1247,10 @@ Other types cause conversion failures.
| | | | float_ | `time as time string`_ or `time as "timer" string`_. Integers | | `01:02` (same as above) |
| | | | | and floats are considered to be seconds. | |
+-------------+---------------+------------+--------------+----------------------------------------------------------------+--------------------------------------+
| `Path | PathLike_ | | str_ | Strings are converted `Path <pathli_>`__ objects. On Windows | | `/tmp/absolute/path` |
| <pathli_>`__| | | | `/` is converted to :codesc:`\\` automatically. New in RF 5.1. | | `relative/path/file.ext` |
| | | | | | | `name_only.txt` |
+-------------+---------------+------------+--------------+----------------------------------------------------------------+--------------------------------------+
| Enum_ | | | str_ | The specified type must be an enumeration (a subclass of Enum_ | .. sourcecode:: python |
| | | | | or Flag_) and given arguments must match its member names. | |
| | | | | | class Direction(Enum): |
Expand Down Expand Up @@ -1311,6 +1315,8 @@ Other types cause conversion failures.
.. _dt-mod: https://docs.python.org/library/datetime.html#datetime.datetime
.. _date: https://docs.python.org/library/datetime.html#datetime.date
.. _timedelta: https://docs.python.org/library/datetime.html#datetime.timedelta
.. _pathli: https://docs.python.org/library/pathlib.html
.. _PathLike: https://docs.python.org/library/os.html#os.PathLike
.. _Enum: https://docs.python.org/library/enum.html#enum.Enum
.. _Flag: https://docs.python.org/library/enum.html#enum.Flag
.. _IntEnum: https://docs.python.org/library/enum.html#enum.IntEnum
Expand Down
13 changes: 13 additions & 0 deletions src/robot/running/arguments/typeconverters.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
from decimal import InvalidOperation, Decimal
from enum import Enum
from numbers import Integral, Real
from os import PathLike
from pathlib import Path, PurePath

from robot.conf import Languages
from robot.libraries.DateTime import convert_date, convert_time
Expand Down Expand Up @@ -368,6 +370,17 @@ def _convert(self, value, explicit_type=True):
return convert_time(value, result_format='timedelta')


@TypeConverter.register
class PathConverter(TypeConverter):
type = Path
abc = PathLike
type_name = 'Path'
value_types = (str, PurePath)

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


@TypeConverter.register
class NoneConverter(TypeConverter):
type = NoneType
Expand Down

0 comments on commit 14321c0

Please sign in to comment.