Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
7 changes: 4 additions & 3 deletions emrichen/tags/typeop.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from numbers import Number
from typing import Optional, Union
from typing import Optional, Type, Union

from ..context import Context
from ..void import Void, VoidType
Expand All @@ -12,8 +12,9 @@ class _BaseIsType(BaseTag):
example: "`!{name} ...`"
description: Returns True if the value enriched is of the given type, False otherwise.
"""
requisite_type = None

value_types = (object,)
requisite_type: Type
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pyrekt


def enrich(self, context: Context) -> bool:
return self.check(context.enrich(self.data))
Expand Down Expand Up @@ -67,4 +68,4 @@ class IsNone(_BaseIsType):
"""

def check(self, value: Optional[Union[VoidType, str]]) -> bool:
return (value is None or value is Void)
return value is None or value is Void
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Black

7 changes: 5 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@


with open(os.path.join(source_dir, 'emrichen', '__init__.py')) as f:
version = re.search("__version__ = ['\"]([^'\"]+)['\"]", f.read()).group(1)
init_file = f.read()
match = re.search("__version__ = ['\"]([^'\"]+)['\"]", init_file)
assert match, "Failed to parse version from emrichen/__init__.py"
version = match.group(1)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pyrekt



with open(os.path.join(source_dir, 'README.md'), encoding='utf-8') as f:
Expand All @@ -29,7 +32,7 @@
author='Santtu Pajukanta',
author_email='santtu@pajukanta.fi',
url='http://github.com/con2/emrichen',
packages = find_packages(exclude=["tests"]),
packages=find_packages(exclude=["tests"]),
zip_safe=True,
entry_points={
'console_scripts': [
Expand Down
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)}'