diff --git a/README.md b/README.md index afbccb3..e57f20e 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ Here the hierarchy of possible installation targets available when running `pip - `[xls]` - `[xml]` - `[yaml]` + - `[parse]` - `[s3]` ## Usage diff --git a/benedict/dicts/parse/parse_util.py b/benedict/dicts/parse/parse_util.py index 7146069..dc68c5a 100644 --- a/benedict/dicts/parse/parse_util.py +++ b/benedict/dicts/parse/parse_util.py @@ -2,13 +2,21 @@ import re from datetime import datetime from decimal import Decimal, DecimalException -import ftfy -import phonenumbers -from dateutil import parser as date_parser -from MailChecker import MailChecker -from phonenumbers import PhoneNumberFormat, phonenumberutil +try: + import ftfy + import phonenumbers + from dateutil import parser as date_parser + from MailChecker import MailChecker + from phonenumbers import PhoneNumberFormat, phonenumberutil + + parse_installed = True +except ModuleNotFoundError: + parse_installed = False + + from slugify import slugify +from benedict.extras import require_parse from benedict.serializers import JSONSerializer from benedict.utils import type_util @@ -66,6 +74,7 @@ def _parse_datetime_from_timestamp(val): def parse_datetime(val, format=None): + require_parse(installed=parse_installed) if type_util.is_datetime(val): return val s = str(val) @@ -124,6 +133,7 @@ def _parse_email(val, check_blacklist=True): def parse_email(val, check_blacklist=True): + require_parse(installed=parse_installed) return _parse_with(val, None, _parse_email, check_blacklist=check_blacklist) @@ -183,6 +193,7 @@ def _parse_phonenumber(val, country_code=None): def parse_phonenumber(val, country_code=None): + require_parse(installed=parse_installed) s = parse_str(val) if not s: return None @@ -205,6 +216,7 @@ def parse_slug(val): def parse_str(val): + require_parse(installed=parse_installed) if type_util.is_string(val): val = ftfy.fix_text(val) else: diff --git a/benedict/extras.py b/benedict/extras.py index 6aedd4c..476e49f 100644 --- a/benedict/extras.py +++ b/benedict/extras.py @@ -1,6 +1,7 @@ from benedict.exceptions import ExtrasRequireModuleNotFoundError __all__ = [ + "require_parse", "require_s3", "require_toml", "require_xls", @@ -14,6 +15,10 @@ def _require_optional_dependencies(*, target, installed): raise ExtrasRequireModuleNotFoundError(target=target) +def require_parse(*, installed): + _require_optional_dependencies(target="parse", installed=installed) + + def require_s3(*, installed): _require_optional_dependencies(target="s3", installed=installed) diff --git a/pyproject.toml b/pyproject.toml index 5fe52e0..9d2204c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,10 +87,6 @@ classifiers = [ "Topic :: Utilities", ] dependencies = [ - "ftfy >= 6.0.0, < 7.0.0", - "mailchecker >= 4.1.0, < 6.0.0", - "phonenumbers >= 8.12.0, < 9.0.0", - "python-dateutil >= 2.8.0, < 3.0.0", "python-fsutil >= 0.9.3, < 1.0.0", "python-slugify >= 7.0.0, < 9.0.0", "requests >= 2.26.0, < 3.0.0", @@ -118,11 +114,17 @@ Twitter = "https://twitter.com/fabiocaccamo" [project.optional-dependencies] all = [ - "python-benedict[io,s3]", + "python-benedict[io,parse,s3]", ] io = [ "python-benedict[toml,xls,xml,yaml]", ] +parse = [ + "ftfy >= 6.0.0, < 7.0.0", + "mailchecker >= 4.1.0, < 6.0.0", + "phonenumbers >= 8.12.0, < 9.0.0", + "python-dateutil >= 2.8.0, < 3.0.0", +] s3 = [ "boto3 >= 1.24.89, < 2.0.0", ] diff --git a/tests/dicts/parse/test_parse_dict.py b/tests/dicts/parse/test_parse_dict.py index bf7e225..3157bb3 100644 --- a/tests/dicts/parse/test_parse_dict.py +++ b/tests/dicts/parse/test_parse_dict.py @@ -1,8 +1,10 @@ import unittest from datetime import datetime from decimal import Decimal +from unittest.mock import patch from benedict.dicts.parse import ParseDict +from benedict.exceptions import ExtrasRequireModuleNotFoundError class parse_dict_test_case(unittest.TestCase): @@ -185,6 +187,16 @@ class parse_dict_test_case(unittest.TestCase): r = datetime(2019, 5, 1, 0, 0) self.assertEqual(b.get_datetime("a"), r) + @patch("benedict.dicts.parse.parse_util.parse_installed", False) + def test_get_datetime_with_with_extra_not_installed(self): + with self.assertRaises(ExtrasRequireModuleNotFoundError): + d = { + "a": "2019-05-01", + } + b = ParseDict(d) + r = datetime(2019, 5, 1, 0, 0) + self.assertEqual(b.get_datetime("a", format="%Y-%m-%d"), r) + def test_get_datetime_list(self): d = { "a": ["2019-05-01", "2018-12-31", "Hello World"], @@ -335,6 +347,15 @@ class parse_dict_test_case(unittest.TestCase): # invalid key self.assertEqual(b.get_email("e"), "") + @patch("benedict.dicts.parse.parse_util.parse_installed", False) + def test_get_email_with_extra_not_installed(self): + with self.assertRaises(ExtrasRequireModuleNotFoundError): + d = { + "a": "fabio@caccamo.com", + } + b = ParseDict(d) + b.get_email("a") + def test_get_int(self): d = { "a": 1, @@ -504,6 +525,15 @@ class parse_dict_test_case(unittest.TestCase): p = b.get_phonenumber("z") self.assertEqual(p, {}) + @patch("benedict.dicts.parse.parse_util.parse_installed", False) + def test_get_phonenumber_with_extra_not_installed(self): + with self.assertRaises(ExtrasRequireModuleNotFoundError): + d = { + "a": "3334445566", + } + b = ParseDict(d) + b.get_phonenumber("a") + def test_get_slug(self): d = { "a": " Hello World ", @@ -550,6 +580,15 @@ class parse_dict_test_case(unittest.TestCase): self.assertEqual(b.get_str("b"), "Hello World") self.assertEqual(b.get_str("c"), "1") + @patch("benedict.dicts.parse.parse_util.parse_installed", False) + def test_get_str_with_extra_not_installed(self): + with self.assertRaises(ExtrasRequireModuleNotFoundError): + d = { + "a": "Hello World", + } + b = ParseDict(d) + b.get_str("a") + def test_get_str_fix_encoding(self): d = { "a": "Sexâ\x80\x99n Drug", diff --git a/tests/dicts/parse/test_parse_util.py b/tests/dicts/parse/test_parse_util.py index ec3a644..49eac9d 100644 --- a/tests/dicts/parse/test_parse_util.py +++ b/tests/dicts/parse/test_parse_util.py @@ -34,6 +34,10 @@ class parse_util_test_case(unittest.TestCase): # TODO pass + def test_parse_datetime_with_extra_not_installed(self): + # TODO + pass + def test_parse_decimal(self): # TODO pass @@ -50,6 +54,10 @@ class parse_util_test_case(unittest.TestCase): # TODO pass + def test_parse_email_with_extra_not_installed(self): + # TODO + pass + def test_parse_int(self): # TODO pass @@ -90,6 +98,10 @@ class parse_util_test_case(unittest.TestCase): # TODO pass + def test_parse_phonenumber_with_extra_not_installed(self): + # TODO + pass + def test_parse_slug(self): # TODO pass @@ -98,6 +110,10 @@ class parse_util_test_case(unittest.TestCase): # TODO pass + def test_parse_str_with_extra_not_installed(self): + # TODO + pass + def test_parse_uuid(self): # TODO pass