A parser inspired by the django orm.
field tests work
7 files changed, +118 -13
+1-1parsers/fields.py
| @@ -1,7 +1,7 @@ | ||
| 1 | 1 | from . import translators |
| 2 | 2 | |
| 3 | 3 | |
| 4 | -def default_function(x): | |
| 4 | +def default_function(cls, x): | |
| 5 | 5 | return x |
| 6 | 6 | |
| 7 | 7 |
+7-6parsers/manager.py
| @@ -34,18 +34,19 @@ class Manager: | ||
| 34 | 34 | return True |
| 35 | 35 | return False |
| 36 | 36 | |
| 37 | - def raise_unique_constraints(self, **kwargs): | |
| 38 | - o2 = self.cls(**kwargs) | |
| 39 | - unique_fields = [key for key, val in o2.field_items() if val.unique] | |
| 37 | + def raise_unique_constraints(self, o2): | |
| 38 | + unique_fields = o2.unique_fields | |
| 40 | 39 | unique_together = o2.Meta.unique_together |
| 41 | 40 | for o in self.objects: |
| 42 | 41 | for key in unique_fields: |
| 43 | - if o[key] == o2[key]: | |
| 44 | - raise UniqueFieldParseError(self.cls, key, o[key]) | |
| 42 | + oval = o.__dict__[key] | |
| 43 | + o2val = o2.__dict__[key] | |
| 44 | + if o.__dict__[key] == o2.__dict__[key]: | |
| 45 | + raise UniqueFieldParseError(self.cls, key, o.__dict__[key]) | |
| 45 | 46 | unique_together_violation = True |
| 46 | 47 | vals = [] |
| 47 | 48 | for key in unique_together: |
| 48 | - if o[key] != o2[key]: | |
| 49 | + if o.__dict__[key] != o2.__dict__[key]: | |
| 49 | 50 | unique_together_violation = False |
| 50 | 51 | break |
| 51 | 52 | else: |
+3-1parsers/parser.py
| @@ -11,6 +11,8 @@ class Parser: | ||
| 11 | 11 | unique_together = [] |
| 12 | 12 | |
| 13 | 13 | def __init__(self, **kwargs): |
| 14 | + self.unique_fields = [key for key, | |
| 15 | + val in self.field_items() if val.unique] | |
| 14 | 16 | self.objects.set_cls(type(self)) |
| 15 | 17 | missing_fields = set(self.fields())-set(kwargs) |
| 16 | 18 | if 'id' not in missing_fields: |
| @@ -26,7 +28,7 @@ class Parser: | ||
| 26 | 28 | setattr(self, name, field.convert(kwargs[name])) |
| 27 | 29 | |
| 28 | 30 | def save(self): |
| 29 | - self.objects.raise_unique_constraints() | |
| 31 | + self.objects.raise_unique_constraints(self) | |
| 30 | 32 | self.objects.append(self) |
| 31 | 33 | |
| 32 | 34 | def fields(self): |
+6-1parsers/translators.py
| @@ -7,6 +7,9 @@ import datetime as dt | ||
| 7 | 7 | |
| 8 | 8 | def split_hour_minute(str): |
| 9 | 9 | """Extracts hours and minutes into separate strings""" |
| 10 | + if ':' in str and len(str) < 5 or len(str) < 4: | |
| 11 | + raise ValueError( | |
| 12 | + f'Hours and minutes must have at least 4 characters without : and 5 with :.') | |
| 10 | 13 | str = ''.join(re.findall(r'\d+', str)) |
| 11 | 14 | return str[:-2], str[-2:] |
| 12 | 15 | |
| @@ -25,7 +28,9 @@ def hour_minute_to_int(cls, str): | ||
| 25 | 28 | |
| 26 | 29 | def phone(cls, str): |
| 27 | 30 | """Converts numbers to a phone number with country code readiness""" |
| 28 | - nums = ''.join(re.findall(r'\d+'), str) | |
| 31 | + nums = ''.join(re.findall(r'\d+', str)) | |
| 32 | + if len(nums) < 4: | |
| 33 | + raise TypeError(f'Impossible phone number {str}.') | |
| 29 | 34 | if nums[:3] == '011': |
| 30 | 35 | return '+'+nums[3:] |
| 31 | 36 | else: |
+0-1tests/__init__.py
| @@ -1 +0,0 @@ | ||
| 1 | -from |
+3-2tests/test_exceptions.py
| @@ -12,7 +12,7 @@ class Test(parsers.Parser): | ||
| 12 | 12 | unique_together = ['a', 'b', 'c'] |
| 13 | 13 | |
| 14 | 14 | |
| 15 | -def double_d(): | |
| 15 | +def raise_unique_error(): | |
| 16 | 16 | t1 = Test(a='a1', b='b1', c='c1', d='d') |
| 17 | 17 | t1.save() |
| 18 | 18 | t2 = Test(a='a2', b='b2', c='c2', d='d') |
| @@ -27,4 +27,5 @@ def raise_unique_together(): | ||
| 27 | 27 | class TestExceptions(unittest.TestCase): |
| 28 | 28 | |
| 29 | 29 | def test_unique(self): |
| 30 | - self.assertRaises(parsers.UniqueFieldParseError, double_d) | |
| 30 | + self.assertRaises( | |
| 31 | + parsers.exceptions.UniqueFieldParseError, raise_unique_error) |
+98-1tests/test_fields.py
| @@ -1 +1,98 @@ | ||
| 1 | -import uni | |
| 1 | +import unittest | |
| 2 | +import parsers | |
| 3 | +import datetime as dt | |
| 4 | + | |
| 5 | + | |
| 6 | +class FieldParser(parsers.Parser): | |
| 7 | + a = parsers.Field() | |
| 8 | + | |
| 9 | + | |
| 10 | +class HourMinuteIntParser(parsers.Parser): | |
| 11 | + a = parsers.HourMinuteIntField() | |
| 12 | + | |
| 13 | + | |
| 14 | +class HourMinuteDeltaParser(parsers.Parser): | |
| 15 | + a = parsers.HourMinuteDeltaField() | |
| 16 | + | |
| 17 | + | |
| 18 | +class PhoneParser(parsers.Parser): | |
| 19 | + a = parsers.PhoneField() | |
| 20 | + | |
| 21 | + | |
| 22 | +class StrParser(parsers.Parser): | |
| 23 | + a = parsers.StrField() | |
| 24 | + | |
| 25 | + | |
| 26 | +class IntParser(parsers.Parser): | |
| 27 | + a = parsers.IntField() | |
| 28 | + | |
| 29 | + | |
| 30 | +class FloatParser(parsers.Parser): | |
| 31 | + a = parsers.FloatField() | |
| 32 | + | |
| 33 | + | |
| 34 | +class BoolParser(parsers.Parser): | |
| 35 | + a = parsers.BooleanField() | |
| 36 | + | |
| 37 | + | |
| 38 | +class TestField(unittest.TestCase): | |
| 39 | + cls = FieldParser | |
| 40 | + good_vals = ['a'] | |
| 41 | + bad_vals = [] | |
| 42 | + type = str | |
| 43 | + | |
| 44 | + def test_good_vals(self): | |
| 45 | + for val in self.good_vals: | |
| 46 | + t = self.cls(a=val) | |
| 47 | + self.assertEqual(type(t.a), self.type) | |
| 48 | + | |
| 49 | + def test_bad_vals(self): | |
| 50 | + def func(): | |
| 51 | + self.cls(a=val) | |
| 52 | + for val in self.bad_vals: | |
| 53 | + self.assertRaises(Exception, func) | |
| 54 | + | |
| 55 | + | |
| 56 | +class TestHourMinuteDeltaField(TestField): | |
| 57 | + cls = HourMinuteDeltaParser | |
| 58 | + good_vals = ['23421', '23:32', '234:21', '2323'].copy() | |
| 59 | + bad_vals = ['fds', '234', '3:3'].copy() | |
| 60 | + type = dt.timedelta | |
| 61 | + | |
| 62 | + | |
| 63 | +class TestHourMinuteIntField(TestHourMinuteDeltaField): | |
| 64 | + cls = HourMinuteIntParser | |
| 65 | + type = int | |
| 66 | + | |
| 67 | + | |
| 68 | +class TestPhoneField(TestField): | |
| 69 | + cls = PhoneParser | |
| 70 | + good_vals = ['32452345', '232-234-234-'] | |
| 71 | + bad_vals = ['asedrfaw'] | |
| 72 | + | |
| 73 | + def test_foreign(self): | |
| 74 | + t = self.cls(a='011-123-456-789') | |
| 75 | + self.assertEqual(t.a, '+123456789') | |
| 76 | + | |
| 77 | + def test_us(self): | |
| 78 | + t = self.cls(a='123456789') | |
| 79 | + self.assertEqual(t.a, '+1123456789') | |
| 80 | + | |
| 81 | + | |
| 82 | +class TestStrField(TestField): | |
| 83 | + cls = StrParser | |
| 84 | + good_vals = ['asdf'] | |
| 85 | + | |
| 86 | + | |
| 87 | +class TestIntField(TestField): | |
| 88 | + cls = IntParser | |
| 89 | + good_vals = ['2314', '32'] | |
| 90 | + bad_vals = ['adsf'] | |
| 91 | + type = int | |
| 92 | + | |
| 93 | + | |
| 94 | +class TestFloatField(TestField): | |
| 95 | + cls = FloatParser | |
| 96 | + good_vals = ['223.23', '23'] | |
| 97 | + bad_vals = ['asdf'] | |
| 98 | + type = float |