Skip to content

Commit

Permalink
New field BytesField & string casting to integer improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
alfred82santa committed Nov 19, 2017
1 parent 95c684e commit 6e1df6c
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 24 deletions.
4 changes: 4 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ Changelog
Version 0.11.0
--------------

- New field type :class:`~dirty_models.fields.BytesField`.

- String to integer casting could use any format allowed by Python: HEX (`0x23`), OCT (`0o43`) or
no-meaning underscores (`1_232_232`).

Version 0.10.1
--------------
Expand Down
60 changes: 55 additions & 5 deletions dirty_models/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,15 +170,24 @@ class IntegerField(BaseField):

@convert_enum
def convert_value(self, value):
if isinstance(value, str):
return int(value, 0)
return int(value)

def check_value(self, value):
return isinstance(value, int)

@can_use_enum
def can_use_value(self, value):
return isinstance(value, float) \
or (isinstance(value, str) and value.isdigit())
if isinstance(value, float):
return True
elif isinstance(value, str):
try:
int(value, 0)
return True
except ValueError:
pass
return False


class FloatField(BaseField):
Expand All @@ -204,8 +213,8 @@ def check_value(self, value):
@can_use_enum
def can_use_value(self, value):
return isinstance(value, int) \
or (isinstance(value, str) and
value.replace('.', '', 1).isnumeric())
or (isinstance(value, str) and
value.replace('.', '', 1).isnumeric())


class BooleanField(BaseField):
Expand Down Expand Up @@ -690,7 +699,6 @@ def __set__(self, obj, value):


class InnerFieldTypeMixin:

def __init__(self, field_type=None, **kwargs):
self._field_type = None
if isinstance(field_type, tuple):
Expand Down Expand Up @@ -923,3 +931,45 @@ def can_use_value(self, value):
pass

return value in self.enum_class.__members__.keys()


class BytesField(BaseField):
"""
It allows to use a bytes as value in a field.
**Automatic cast from:**
* :class:`str`
* :class:`int`
* :class:`bytearray`
* :class:`list` of :class:`int` in range(0, 256)
* :class:`~enum.Enum` if value of enum can be cast.
"""

@convert_enum
def convert_value(self, value):
if isinstance(value, str):
return value.encode()
elif isinstance(value, (list, ListModel, bytearray, int)):
if isinstance(value, int):
value = bytes([value, ])
elif isinstance(value, ListModel):
value = value.export_data()
try:
return bytes(value)
except TypeError:
pass

return None

def check_value(self, value):
return isinstance(value, bytes)

@can_use_enum
def can_use_value(self, value):
return isinstance(value, (int, str, list, ListModel, bytearray))
91 changes: 72 additions & 19 deletions tests/dirty_models/tests_fields.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
from datetime import time, date, datetime, timezone, timedelta
from enum import Enum
from datetime import date, datetime, time, timedelta, timezone
from unittest import TestCase

import iso8601
from dateutil import tz
from enum import Enum

from dirty_models.fields import (IntegerField, StringField, BooleanField,
FloatField, ModelField, TimeField, DateField,
DateTimeField, ArrayField, StringIdField, HashMapField, MultiTypeField, TimedeltaField,
EnumField)
from dirty_models.fields import ArrayField, BooleanField, BytesField, DateField, DateTimeField, EnumField, FloatField, \
HashMapField, IntegerField, ModelField, MultiTypeField, StringField, StringIdField, TimeField, TimedeltaField
from dirty_models.model_types import ListModel
from dirty_models.models import BaseModel, HashMapModel


class TestFields(TestCase):

def test_float_field_using_int(self):
field = FloatField()
self.assertFalse(field.check_value(3))
Expand Down Expand Up @@ -174,6 +171,30 @@ class TestModel(BaseModel):
model.field_name = "3"
self.assertEqual(model.field_name, 3)

def test_int_field_on_class_using_str_hex(self):
class TestModel(BaseModel):
field_name = IntegerField()

model = TestModel()
model.field_name = "0x13"
self.assertEqual(model.field_name, 19)

def test_int_field_on_class_using_str_oct(self):
class TestModel(BaseModel):
field_name = IntegerField()

model = TestModel()
model.field_name = "0o13"
self.assertEqual(model.field_name, 11)

def test_int_field_on_class_using_str_undescore(self):
class TestModel(BaseModel):
field_name = IntegerField()

model = TestModel()
model.field_name = "1_345_232"
self.assertEqual(model.field_name, 1345232)

def test_int_field_on_class_using_dict(self):
class TestModel(BaseModel):
field_name = IntegerField()
Expand Down Expand Up @@ -1242,7 +1263,6 @@ class TestModel(BaseModel):


class ArrayOfStringFieldTests(TestCase):

def setUp(self):
super(ArrayOfStringFieldTests, self).setUp()

Expand Down Expand Up @@ -1270,7 +1290,6 @@ def test_array_field_no_autolist(self):


class IntegerFieldTests(TestCase):

class TestEnum(Enum):
value_1 = 1
value_2 = '2'
Expand Down Expand Up @@ -1336,7 +1355,6 @@ def test_using_enum_fail(self):


class MultiTypeFieldSimpleTypesTests(TestCase):

def setUp(self):
super(MultiTypeFieldSimpleTypesTests, self).setUp()

Expand Down Expand Up @@ -1402,7 +1420,6 @@ def test_multi_field_desc(self):


class MultiTypeFieldComplexTypesTests(TestCase):

def setUp(self):
super(MultiTypeFieldComplexTypesTests, self).setUp()

Expand Down Expand Up @@ -1445,7 +1462,6 @@ def test_get_field_type_by_value_fail(self):


class AutoreferenceModelFieldTests(TestCase):

def setUp(self):
super(AutoreferenceModelFieldTests, self).setUp()

Expand All @@ -1465,7 +1481,6 @@ def test_model_reference(self):


class TimedeltaFieldTests(TestCase):

def setUp(self):
self.field = TimedeltaField()

Expand All @@ -1492,7 +1507,6 @@ def test_convert_value_float(self):


class DateTimeFieldWithTimezoneTests(TestCase):

def test_no_timezone_none(self):
class Model(BaseModel):
date_time_field = DateTimeField()
Expand Down Expand Up @@ -1558,7 +1572,6 @@ class Model(BaseModel):
self.assertEqual(model.date_time_field.tzinfo, timezone.utc)

def test_export_definition(self):

field = DateTimeField(name='test_field', alias=[], default_timezone=timezone.utc, force_timezone=True)

self.assertEqual(field.export_definition(),
Expand All @@ -1571,7 +1584,6 @@ def test_export_definition(self):


class TimeFieldWithTimezoneTests(TestCase):

def test_no_timezone_none(self):
class Model(BaseModel):
time_field = TimeField()
Expand Down Expand Up @@ -1611,7 +1623,6 @@ class Model(BaseModel):
self.assertEqual(model.time_field.tzinfo, tz.gettz('Europe/Amsterdam'))

def test_export_definition(self):

field = TimeField(name='test_field', alias=[], default_timezone=timezone.utc)

self.assertEqual(field.export_definition(),
Expand All @@ -1623,9 +1634,7 @@ def test_export_definition(self):


class EnumFieldTests(TestCase):

class TestEnum(Enum):

value_1 = 'value1'
value_2 = 2

Expand Down Expand Up @@ -1668,3 +1677,47 @@ def test_export_definition(self):
'enum_class': self.TestEnum,
'name': 'test_field', 'read_only': False},
self.field.export_definition())


class BytesFieldTests(TestCase):
def setUp(self):
self.field = BytesField(name='test_field', alias=[])

def test_check_value(self):
self.assertTrue(self.field.check_value(b'2332345as'))

def test_check_value_fail(self):
self.assertFalse(self.field.check_value('2332345as'))
self.assertFalse(self.field.check_value(12))
self.assertFalse(self.field.check_value(12.3))
self.assertFalse(self.field.check_value(bytearray([12, 43, 52])))
self.assertFalse(self.field.check_value([12, 43, 52]))
self.assertFalse(self.field.check_value({'sasa': 'asasas'}))

def test_can_use_value_check_values(self):
self.assertTrue(self.field.can_use_value('2332345as'))
self.assertTrue(self.field.can_use_value(12))
self.assertTrue(self.field.can_use_value(bytearray([12, 43, 52])))
self.assertTrue(self.field.can_use_value([12, 43, 52]))
self.assertTrue(self.field.can_use_value(ListModel([12, 43, 52])))

def test_can_use_value_check_values_fail(self):
self.assertFalse(self.field.can_use_value(12.3))
self.assertFalse(self.field.can_use_value({'sasa': 'asasas'}))

def test_convert_value_from_values(self):
self.assertEqual(self.field.convert_value('2332345as'), b'2332345as')
self.assertEqual(self.field.convert_value(12), b'\x0c')
self.assertEqual(self.field.convert_value(bytearray([12, 43, 52])), b'\x0c+4')
self.assertEqual(self.field.convert_value([12, 43, 52]), b'\x0c+4')
self.assertEqual(self.field.convert_value(ListModel([12, 43, 52])), b'\x0c+4')

def test_convert_value_from_invalid_values(self):
self.assertIsNone(self.field.convert_value(ListModel([{'ass': 'as'}])))

def test_export_definition(self):
self.assertEqual(self.field.export_definition(),
{'alias': [],
'doc': 'BytesField field',
'name': 'test_field', 'read_only': False},
self.field.export_definition())

0 comments on commit 6e1df6c

Please sign in to comment.