Skip to content

Commit

Permalink
tags/typeconv: add ToInteger, ToString, ToBoolean, ToFloat tags (closes
Browse files Browse the repository at this point in the history
  • Loading branch information
japsu committed Jul 12, 2021
1 parent 1c27ed2 commit fc0d12e
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 0 deletions.
5 changes: 5 additions & 0 deletions emrichen/tags/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from .not_ import Not
from .op import Op
from .typeop import IsBoolean, IsDict, IsInteger, IsList, IsNone, IsNumber, IsString
from .typeconv import ToBoolean, ToInteger, ToFloat, ToString
from .urlencode import URLEncode
from .var import Var
from .void import Void
Expand Down Expand Up @@ -64,6 +65,10 @@
'Op',
'SHA1',
'SHA256',
'ToBoolean',
'ToFloat',
'ToInteger',
'ToString',
'URLEncode',
'Var',
'Void',
Expand Down
56 changes: 56 additions & 0 deletions emrichen/tags/typeconv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from numbers import Number
from collections.abc import Mapping
from typing import Optional, Type, Union

from ..context import Context
from ..void import Void, VoidType
from .base import BaseTag


class _BaseToType(BaseTag):
"""
arguments: Data to convert.
example: "`!{name} ...`"
description: Converts the input to the desired type.
"""

value_types = (object,)
target_type: Type

def enrich(self, context: Context):
return self.target_type(context.enrich(self.data))


class ToBoolean(_BaseToType):
__doc__ = _BaseToType.__doc__
target_type = bool


class ToInteger(_BaseToType):
"""
arguments: Either single argument containing the data to convert, or an object with `value:` and `radix:`.
example: `!ToInteger "50"`, `!ToInteger value: "C0FFEE", radix: 16`
description: Converts the input to Python `int`. Radix is never inferred from input: if not supplied, it is always 10.
"""

target_type = int

def enrich(self, context: Context):
data = context.enrich(self.data)

if isinstance(data, Mapping):
value = data["value"]
radix = data.get("radix", 10)
return self.target_type(value, radix)
else:
return self.target_type(data)


class ToFloat(_BaseToType):
__doc__ = _BaseToType.__doc__
target_type = float


class ToString(_BaseToType):
__doc__ = _BaseToType.__doc__
target_type = str
30 changes: 30 additions & 0 deletions tests/test_typeconv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import pytest

from emrichen import Template, Context
from emrichen.void import Void


@pytest.mark.parametrize(
'tag, val, result',
[
('ToBoolean', 0, False),
# ('ToBoolean', "false", False),
('ToBoolean', "TRUE", True),
('ToInteger', "8", 8),
('ToInteger', {"value": "0644", "radix": 8}, 420),
('ToFloat', "8.2", 8.2),
('ToFloat', 8, 8.0),
('ToFloat', True, 1.0),
('ToString', True, "True"), # TODO too pythonic? should we return lowercase instead?
('ToString', 8, "8"),
# ('ToString', {'a': 5, 'b': 6}, "{'a': 5, 'b': 6}"), # TODO OrderedDict([('a', 5), ('b', 6)])
],
)
def test_typeop(tag, val, result):
resolved = Template.parse(f"!{tag},Lookup 'a'").enrich(Context({'a': val}))[0]
assert resolved == result, f'{tag}({val!r}) returned {resolved}, expected {result}'

# type equivalence instead of isinstance is intended: want strict conformance
assert type(resolved) == type(
result
), f'{tag}({val!r}) returned type {type(resolved)}, expected {type(result)}'

0 comments on commit fc0d12e

Please sign in to comment.