Skip to content

Commit

Permalink
Merge pull request #73 from fyndata/develop
Browse files Browse the repository at this point in the history
Release v0.7.2
  • Loading branch information
glarrain committed Jul 8, 2019
2 parents b33ac46 + 53469cc commit 26c80ff
Show file tree
Hide file tree
Showing 13 changed files with 206 additions and 18 deletions.
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.7.1
current_version = 0.7.2
commit = True
tag = True

Expand Down
9 changes: 9 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
History
-------

0.7.2 (2019-07-08)
+++++++++++++++++++++++

* (PR #72, 2019-07-08) extras: Handle ``str``-typed RUTs in Django ``RutField.get_prep_value()``
* (PR #70, 2019-07-05) rut: Add less-than and greater-than methods
* (PR #71, 2019-07-05) rut: Strip leading zeros from RUTs
* (PR #69, 2019-07-02) libs.tz_utils: Fix setting of time zone information in datetimes
* (PR #68, 2019-06-27) requirements: update all those for 'release' and 'test'

0.7.1 (2019-06-20)
+++++++++++++++++++++++

Expand Down
2 changes: 1 addition & 1 deletion cl_sii/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
"""


__version__ = '0.7.1'
__version__ = '0.7.2'
8 changes: 6 additions & 2 deletions cl_sii/extras/dj_model_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def from_db_value(
# note: there is no parent implementation, for performance reasons.
return self.to_python(value)

def get_prep_value(self, value: Optional[Rut]) -> Optional[str]:
def get_prep_value(self, value: Optional[object]) -> Optional[str]:
"""
Convert the model's attribute value to a format suitable for the DB.
Expand All @@ -115,9 +115,13 @@ def get_prep_value(self, value: Optional[Rut]) -> Optional[str]:
However, these are preliminary non-DB specific value checks and
conversions (otherwise customize :meth:`get_db_prep_value`).
Note: Before returning, ``value`` will be passed to :meth:`to_python` so that, if needed, it
will be converted to an instance of :class:`Rut`, which is very convenient in cases such
as when the type of ``value`` is :class:`str`.
"""
value = super().get_prep_value(value)
return value if value is None else value.canonical
value_rut: Optional[Rut] = self.to_python(value)
return value_rut if value_rut is None else value_rut.canonical

def to_python(self, value: Optional[object]) -> Optional[Rut]:
"""
Expand Down
7 changes: 7 additions & 0 deletions cl_sii/libs/tz_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ def get_now_tz_aware() -> datetime:
# - `pytz.UTC.localize(datetime.utcnow())`

# source: 'django.utils.timezone.now' @ Django 2.1.3
# warning: setting 'tzinfo' does not work for many timezones. To be safe, only use it for UTC
# and None.
# > Unfortunately using the tzinfo argument of the standard datetime constructors
# > "does not work" with pytz for many timezones.
# https://pythonhosted.org/pytz/#localized-times-and-date-arithmetic
return datetime.utcnow().replace(tzinfo=TZ_UTC)


Expand Down Expand Up @@ -74,6 +79,8 @@ def convert_naive_dt_to_tz_aware(dt: datetime, tz: PytzTimezone) -> datetime:
:raises ValueError: if ``dt`` is already timezone-aware
"""
# equivalent to:
# dt.astimezone(tz)
dt_tz_aware = tz.localize(dt) # type: datetime
return dt_tz_aware

Expand Down
5 changes: 3 additions & 2 deletions cl_sii/rcv/data_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,9 @@ def as_date(self) -> date:

def as_datetime(self) -> datetime:
# note: timezone-aware
return datetime(self.year, self.month, day=1, hour=0, minute=0, second=0).replace(
tzinfo=SII_OFFICIAL_TZ)
return tz_utils.convert_naive_dt_to_tz_aware(
datetime(self.year, self.month, day=1, hour=0, minute=0, second=0),
SII_OFFICIAL_TZ)


@dataclasses.dataclass(frozen=True)
Expand Down
11 changes: 10 additions & 1 deletion cl_sii/rut/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,15 @@ def __str__(self) -> str:
def __repr__(self) -> str:
return f"Rut('{self.canonical}')"

def __lt__(self, other: object) -> bool:
if isinstance(other, Rut):
return int(self.digits) < int(other.digits)
else:
return NotImplemented

def __le__(self, other: object) -> bool:
return self.__lt__(other) or self.__eq__(other)

def __eq__(self, other: object) -> bool:
if isinstance(other, Rut):
return self.canonical == other.canonical
Expand All @@ -135,7 +144,7 @@ def __hash__(self) -> int:
def clean_str(cls, value: str) -> str:
# note: unfortunately `value.strip('.')` does not remove all the occurrences of '.' in
# 'value' (only the leading and trailing ones).
return value.strip().replace('.', '').upper()
return value.strip().lstrip('0').replace('.', '').upper()

@classmethod
def calc_dv(cls, rut_digits: str) -> str:
Expand Down
2 changes: 1 addition & 1 deletion requirements/release.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ pkginfo==1.5.0.1
readme-renderer==24.0
requests==2.22.0
requests-toolbelt==0.9.1
tqdm==4.32.1
tqdm==4.32.2
20 changes: 14 additions & 6 deletions requirements/test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
codecov==2.0.15
coverage==4.5.3
flake8==3.7.7
mypy==0.701
tox==3.9.0
mypy==0.711
tox==3.13.1

# Packages dependencies:
# - codecov:
Expand All @@ -23,19 +23,27 @@ tox==3.9.0
# - typed-ast
# - tox:
# - filelock
# - importlib-metadata
# - zipp
# - packaging
# - pyparsing
# - pluggy
# - py
# - toml
# - virtualenv
entrypoints==0.3
filelock==3.0.10
filelock==3.0.12
importlib-metadata==0.18
mccabe==0.6.1
mypy-extensions==0.4.1
pluggy==0.9.0
packaging==19.0
pluggy==0.12.0
py==1.8.0
pycodestyle==2.5.0
pyflakes==2.1.1
pyparsing==2.4.0
requests
toml==0.10.0
typed-ast==1.3.5
virtualenv==16.5.0
typed-ast==1.4.0
virtualenv==16.6.1
zipp==0.5.1
1 change: 1 addition & 0 deletions scripts/clean_dte_xml_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def clean_dte_xml_file(input_file_path: str, output_file_path: str) -> Iterable[
remove_doc_personalizado=True,
)

# TODO: add exception with a nice message for the caller.
cl_sii.dte.parse.validate_dte_xml(xml_doc_cleaned)

with open(output_file_path, 'w+b') as f:
Expand Down
30 changes: 27 additions & 3 deletions tests/test_extras_dj_model_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,35 @@

import django.db.models # noqa: F401

from cl_sii.extras.dj_model_fields import Rut, RutField # noqa: F401
from cl_sii.extras.dj_model_fields import Rut, RutField


class RutFieldTest(unittest.TestCase):
valid_rut_canonical: str
valid_rut_instance: Rut
valid_rut_verbose_leading_zero_lowercase: str

# TODO: implement!
@classmethod
def setUpClass(cls) -> None:
cls.valid_rut_canonical = '60803000-K'
cls.valid_rut_instance = Rut(cls.valid_rut_canonical)
cls.valid_rut_verbose_leading_zero_lowercase = '060.803.000-k'

pass
def test_get_prep_value_of_canonical_str(self) -> None:
prepared_value = RutField().get_prep_value(self.valid_rut_canonical)
self.assertIsInstance(prepared_value, str)
self.assertEqual(prepared_value, self.valid_rut_canonical)

def test_get_prep_value_of_non_canonical_str(self) -> None:
prepared_value = RutField().get_prep_value(self.valid_rut_verbose_leading_zero_lowercase)
self.assertIsInstance(prepared_value, str)
self.assertEqual(prepared_value, self.valid_rut_canonical)

def test_get_prep_value_of_Rut(self) -> None:
prepared_value = RutField().get_prep_value(self.valid_rut_instance)
self.assertIsInstance(prepared_value, str)
self.assertEqual(prepared_value, self.valid_rut_canonical)

def test_get_prep_value_of_None(self) -> None:
prepared_value = RutField().get_prep_value(None)
self.assertIsNone(prepared_value)
1 change: 0 additions & 1 deletion tests/test_libs_xml_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ def test_parse_untrusted_xml_valid(self) -> None:
b'</root>')
xml = parse_untrusted_xml(value)
self.assertIsInstance(xml, XmlElement)
# print(xml)
self.assertEqual(
lxml.etree.tostring(xml, pretty_print=False),
value)
Expand Down
126 changes: 126 additions & 0 deletions tests/test_rut.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,20 @@ class RutTest(unittest.TestCase):
valid_rut_digits: str
valid_rut_digits_with_dots: str
valid_rut_verbose: str
valid_rut_leading_zero: str

invalid_rut_canonical: str
invalid_rut_dv: str

valid_rut_instance: rut.Rut
invalid_rut_instance: rut.Rut
valid_rut_instance_copy: rut.Rut
valid_rut_leading_zero_instance: rut.Rut

valid_rut_2_canonical: str
valid_rut_2_instance: rut.Rut
valid_rut_3_canonical: str
valid_rut_3_instance: rut.Rut

@classmethod
def setUpClass(cls) -> None:
Expand All @@ -25,12 +33,20 @@ def setUpClass(cls) -> None:
cls.valid_rut_digits = '6824160'
cls.valid_rut_digits_with_dots = '6.824.160'
cls.valid_rut_verbose = '6.824.160-K'
cls.valid_rut_leading_zero = '06824160-K'

cls.invalid_rut_canonical = '6824160-0'
cls.invalid_rut_dv = '0'

cls.valid_rut_instance = rut.Rut(cls.valid_rut_canonical)
cls.invalid_rut_instance = rut.Rut(cls.invalid_rut_canonical)
cls.valid_rut_instance_copy = rut.Rut(cls.valid_rut_canonical)
cls.valid_rut_leading_zero_instance = rut.Rut(cls.valid_rut_leading_zero)

cls.valid_rut_2_canonical = '60803000-K'
cls.valid_rut_2_instance = rut.Rut(cls.valid_rut_2_canonical)
cls.valid_rut_3_canonical = '61002000-3'
cls.valid_rut_3_instance = rut.Rut(cls.valid_rut_3_canonical)

############################################################################
# instance
Expand Down Expand Up @@ -133,6 +149,50 @@ def test__repr__(self) -> None:
rut_repr = f"Rut('{self.valid_rut_canonical}')"
self.assertEqual(self.valid_rut_instance.__repr__(), rut_repr)

def test__lt__true(self) -> None:
# "<"
self.assertLess(self.valid_rut_instance, self.valid_rut_2_instance)
self.assertLess(self.valid_rut_2_instance, self.valid_rut_3_instance)

# "<" and leading zero
self.assertLess(self.valid_rut_leading_zero_instance, self.valid_rut_2_instance)

def test__lt__false(self) -> None:
# ">"
self.assertFalse(self.valid_rut_2_instance < self.valid_rut_instance)
self.assertFalse(self.valid_rut_3_instance < self.valid_rut_2_instance)

# ">" and leading zero
self.assertFalse(self.valid_rut_2_instance < self.valid_rut_leading_zero_instance)

# "="
self.assertFalse(self.valid_rut_instance < self.valid_rut_instance_copy)

def test__lt__not_rut_instance(self) -> None:
self.assertIs(self.valid_rut_instance.__lt__(self.valid_rut_canonical), NotImplemented)

def test__le__true(self) -> None:
# "<"
self.assertLessEqual(self.valid_rut_instance, self.valid_rut_2_instance)
self.assertLessEqual(self.valid_rut_2_instance, self.valid_rut_3_instance)

# "<" and leading zero
self.assertLessEqual(self.valid_rut_leading_zero_instance, self.valid_rut_2_instance)

# "="
self.assertLessEqual(self.valid_rut_instance, self.valid_rut_instance_copy)

def test__le__false(self) -> None:
# ">"
self.assertFalse(self.valid_rut_2_instance <= self.valid_rut_instance)
self.assertFalse(self.valid_rut_3_instance <= self.valid_rut_2_instance)

# ">" and leading zero
self.assertFalse(self.valid_rut_2_instance <= self.valid_rut_leading_zero_instance)

def test__le__not_rut_instance(self) -> None:
self.assertIs(self.valid_rut_instance.__le__(self.valid_rut_canonical), NotImplemented)

def test__eq__true(self) -> None:
rut_instance = rut.Rut(self.valid_rut_canonical)
self.assertTrue(self.valid_rut_instance.__eq__(rut_instance))
Expand All @@ -143,6 +203,56 @@ def test__eq__false(self) -> None:
def test__eq__not_rut_instance(self) -> None:
self.assertFalse(self.valid_rut_instance.__eq__(self.valid_rut_canonical))

def test__gt__true(self) -> None:
# ">"
self.assertGreater(self.valid_rut_2_instance, self.valid_rut_instance)
self.assertGreater(self.valid_rut_3_instance, self.valid_rut_2_instance)

# ">" and leading zero
self.assertGreater(self.valid_rut_2_instance, self.valid_rut_leading_zero_instance)

def test__gt__false(self) -> None:
# "<"
self.assertFalse(self.valid_rut_instance > self.valid_rut_2_instance)
self.assertFalse(self.valid_rut_2_instance > self.valid_rut_3_instance)

# "<" and leading zero
self.assertFalse(self.valid_rut_leading_zero_instance > self.valid_rut_2_instance)

# "="
self.assertFalse(self.valid_rut_instance > self.valid_rut_instance_copy)

def test__gt__not_rut_instance(self) -> None:
self.assertIs(
self.valid_rut_instance.__gt__(self.valid_rut_canonical), # type: ignore
NotImplemented,
)

def test__ge__true(self) -> None:
# ">"
self.assertGreaterEqual(self.valid_rut_2_instance, self.valid_rut_instance)
self.assertGreaterEqual(self.valid_rut_3_instance, self.valid_rut_2_instance)

# ">" and leading zero
self.assertGreaterEqual(self.valid_rut_2_instance, self.valid_rut_leading_zero_instance)

# "="
self.assertGreaterEqual(self.valid_rut_instance, self.valid_rut_instance_copy)

def test__ge__false(self) -> None:
# "<"
self.assertFalse(self.valid_rut_instance >= self.valid_rut_2_instance)
self.assertFalse(self.valid_rut_2_instance >= self.valid_rut_3_instance)

# "<" and leading zero
self.assertFalse(self.valid_rut_leading_zero_instance >= self.valid_rut_2_instance)

def test__ge__not_rut_instance(self) -> None:
self.assertIs(
self.valid_rut_instance.__ge__(self.valid_rut_canonical), # type: ignore
NotImplemented,
)

def test__hash__(self) -> None:
rut_hash = hash(self.valid_rut_instance.canonical)
self.assertEqual(self.valid_rut_instance.__hash__(), rut_hash)
Expand All @@ -165,6 +275,22 @@ def test_clean_type_error(self) -> None:
message = exception.args[0]
self.assertEqual(message, "'int' object has no attribute 'strip'")

def test_clean_str_leading_zeros(self) -> None:
# One leading zero
rut_value = f'0{self.valid_rut_canonical}'
clean_rut = rut.Rut.clean_str(rut_value)
self.assertEqual(clean_rut, self.valid_rut_canonical)

# Two leading zeros
rut_value = f'00{self.valid_rut_canonical}'
clean_rut = rut.Rut.clean_str(rut_value)
self.assertEqual(clean_rut, self.valid_rut_canonical)

# Eight leading zeros
rut_value = f'00000000{self.valid_rut_canonical}'
clean_rut = rut.Rut.clean_str(rut_value)
self.assertEqual(clean_rut, self.valid_rut_canonical)

def test_calc_dv_ok(self) -> None:
dv = rut.Rut.calc_dv(self.valid_rut_digits)
self.assertEqual(dv, self.valid_rut_dv)
Expand Down

0 comments on commit 26c80ff

Please sign in to comment.