Skip to content

Commit 89d3a44

Browse files
committed
Improve test coverage.
1 parent ba8a54b commit 89d3a44

File tree

10 files changed

+42
-232
lines changed

10 files changed

+42
-232
lines changed

domdf_python_tools/bases.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,10 @@
5353

5454
# 3rd party
5555
import pydash # type: ignore
56-
from typing_extensions import Protocol
5756

5857
# this package
5958
from domdf_python_tools.doctools import prettify_docstrings
59+
from domdf_python_tools.typing import SupportsIndex
6060

6161
__all__ = [
6262
"Dictable",
@@ -369,12 +369,6 @@ def extend(self, other: Iterable[_T]) -> None:
369369
self.data.extend(other)
370370

371371

372-
class _SupportsIndex(Protocol):
373-
374-
def __index__(self) -> int:
375-
...
376-
377-
378372
@prettify_docstrings
379373
class UserFloat(Real):
380374
"""
@@ -385,7 +379,7 @@ class UserFloat(Real):
385379
.. versionadded:: 1.6.0
386380
"""
387381

388-
def __init__(self, value: Union[SupportsFloat, _SupportsIndex, str, bytes, bytearray] = 0.0):
382+
def __init__(self, value: Union[SupportsFloat, SupportsIndex, str, bytes, bytearray] = 0.0):
389383
self._value = (float(value), )
390384

391385
def as_integer_ratio(self) -> Tuple[int, int]:

domdf_python_tools/dates.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,8 +294,8 @@ def get_timezone(tz: str, date: Optional[datetime.datetime] = None) -> Optional[
294294
:param date: The date to obtain the timezone for
295295
"""
296296

297-
if date is None:
298-
date = datetime.datetime.utcnow() # pragma: no cover (hard to test)
297+
if date is None: # pragma: no cover (hard to test)
298+
date = datetime.datetime.utcnow()
299299

300300
d = date.replace(tzinfo=None)
301301

domdf_python_tools/paths.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,7 @@ def load_json(
623623
**kwargs,
624624
)
625625

626-
if sys.version_info < (3, 7):
626+
if sys.version_info < (3, 7): # pragma: no cover (<py37)
627627

628628
def is_mount(self) -> bool:
629629
"""
@@ -652,7 +652,7 @@ def is_mount(self) -> bool:
652652
parent_ino = parent.stat().st_ino
653653
return ino == parent_ino
654654

655-
if sys.version_info < (3, 8):
655+
if sys.version_info < (3, 8): # pragma: no cover (<py38)
656656

657657
def rename(self: _P, target: Union[str, pathlib.PurePath]) -> _P: # type: ignore
658658
"""
@@ -710,7 +710,7 @@ def unlink(self, missing_ok: bool = False) -> None:
710710
if not missing_ok:
711711
raise
712712

713-
if sys.version_info < (3, 9):
713+
if sys.version_info < (3, 9): # pragma: no cover (<py39)
714714

715715
def __enter__(self):
716716
return self

domdf_python_tools/typing.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
"HasHead",
7373
"String",
7474
"FrameOrSeries",
75+
"SupportsIndex",
7576
]
7677

7778
#: Type hint for objects that represent filesystem paths.
@@ -218,3 +219,14 @@ def to_string(self, *args, **kwargs) -> Optional[str]:
218219
#
219220
# def __lt__(self, other: Any) -> bool:
220221
# ... # pragma: no cover
222+
223+
224+
class SupportsIndex(Protocol):
225+
"""
226+
:class:`typing.Protocol` for classes that support ``__index__``.
227+
228+
.. versionadded:: 2.0.0
229+
"""
230+
231+
def __index__(self) -> int: # pragma: no cover
232+
...

domdf_python_tools/utils.py

Lines changed: 8 additions & 174 deletions
Original file line numberDiff line numberDiff line change
@@ -73,23 +73,19 @@
7373
#
7474

7575
# stdlib
76-
import functools
7776
import inspect
7877
import sys
79-
import textwrap
8078
import typing
81-
import warnings
82-
from datetime import date
8379
from math import log10
8480
from pprint import pformat
8581
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union
8682

8783
# 3rd party
88-
import deprecation # type: ignore
89-
from packaging import version
84+
from deprecation_alias import deprecated # type: ignore # TODO
9085

9186
# this package
9287
import domdf_python_tools.words
88+
from domdf_python_tools import __version__
9389
from domdf_python_tools.typing import HasHead, String
9490

9591
if typing.TYPE_CHECKING or domdf_python_tools.__docs: # pragma: no cover
@@ -358,174 +354,12 @@ def head(obj: Union[Tuple, List, "DataFrame", "Series", String, HasHead], n: int
358354
return str(obj[:n]) + etc # type: ignore
359355

360356

361-
def deprecated(
362-
deprecated_in: Optional[str] = None,
363-
removed_in: Optional[str] = None,
364-
current_version: Optional[str] = None,
365-
details: str = '',
366-
name: Optional[str] = None
367-
):
368-
r"""Decorate a function to signify its deprecation.
369-
370-
This function wraps a method that will soon be removed and does two things:
371-
372-
* The docstring of the method will be modified to include a notice
373-
about deprecation, e.g., "Deprecated since 0.9.11. Use foo instead."
374-
* Raises a :class:`~deprecation.DeprecatedWarning`
375-
via the :mod:`warnings` module, which is a subclass of the built-in
376-
:class:`DeprecationWarning`. Note that built-in
377-
:class:`DeprecationWarning`\s are ignored by default, so for users
378-
to be informed of said warnings they will need to enable them -- see
379-
the :mod:`warnings` module documentation for more details.
380-
381-
:param deprecated_in: The version at which the decorated method is considered
382-
deprecated. This will usually be the next version to be released when
383-
the decorator is added. The default is :py:obj:`None`, which effectively
384-
means immediate deprecation. If this is not specified, then the
385-
``removed_in`` and ``current_version`` arguments are ignored.
386-
:no-default deprecated_in:
387-
388-
:param removed_in: The version or :class:`datetime.date` when the decorated
389-
method will be removed. The default is :py:obj:`None`, specifying that
390-
the function is not currently planned to be removed.
391-
392-
.. note::
393-
394-
This parameter cannot be set to a value if ``deprecated_in=None``.
395-
396-
:no-default removed_in:
397-
398-
:param current_version: The source of version information for the currently
399-
running code. This will usually be a ``__version__`` attribute in your
400-
library. The default is :py:obj:`None`. When ``current_version=None``
401-
the automation to determine if the wrapped function is actually in
402-
a period of deprecation or time for removal does not work, causing a
403-
:class:`~deprecation.DeprecatedWarning` to be raised in all cases.
404-
:no-default current_version:
405-
406-
:param details: Extra details to be added to the method docstring and
407-
warning. For example, the details may point users to a replacement
408-
method, such as "Use the foo_bar method instead".
409-
410-
:param name: The name of the deprecated function, if an alias is being
411-
deprecated. Default is to the name of the decorated function.
412-
:no-default name:
413-
"""
414-
415-
# You can't just jump to removal. It's weird, unfair, and also makes
416-
# building up the docstring weird.
417-
if deprecated_in is None and removed_in is not None:
418-
raise TypeError("Cannot set removed_in to a value without also setting deprecated_in")
419-
420-
# Only warn when it's appropriate. There may be cases when it makes sense
421-
# to add this decorator before a formal deprecation period begins.
422-
# In CPython, PendingDeprecatedWarning gets used in that period,
423-
# so perhaps mimick that at some point.
424-
is_deprecated = False
425-
is_unsupported = False
426-
427-
# StrictVersion won't take a None or a "", so make whatever goes to it
428-
# is at least *something*. Compare versions only if removed_in is not
429-
# of type datetime.date
430-
if isinstance(removed_in, date):
431-
if date.today() >= removed_in:
432-
is_unsupported = True
433-
else:
434-
is_deprecated = True
435-
elif current_version:
436-
current_version = version.parse(current_version) # type: ignore
437-
438-
if removed_in is not None and current_version >= version.parse(removed_in): # type: ignore
439-
is_unsupported = True
440-
elif deprecated_in is not None and current_version >= version.parse(deprecated_in): # type: ignore
441-
is_deprecated = True
442-
else:
443-
# If we can't actually calculate that we're in a period of
444-
# deprecation...well, they used the decorator, so it's deprecated.
445-
# This will cover the case of someone just using
446-
# @deprecated("1.0") without the other advantages.
447-
is_deprecated = True
448-
449-
should_warn = any([is_deprecated, is_unsupported])
450-
451-
def _function_wrapper(function):
452-
# Everything *should* have a docstring, but just in case...
453-
existing_docstring = function.__doc__ or ''
454-
455-
# split docstring at first occurrence of newline
456-
string_list = existing_docstring.split('\n', 1)
457-
458-
if should_warn:
459-
# The various parts of this decorator being optional makes for
460-
# a number of ways the deprecation notice could go. The following
461-
# makes for a nicely constructed sentence with or without any
462-
# of the parts.
463-
464-
parts = {"deprecated_in": '', "removed_in": '', "details": ''}
465-
466-
if deprecated_in:
467-
parts["deprecated_in"] = f" {deprecated_in}"
468-
if removed_in:
469-
# If removed_in is a date, use "removed on"
470-
# If removed_in is a version, use "removed in"
471-
if isinstance(removed_in, date):
472-
parts["removed_in"] = f"\n This will be removed on {removed_in}."
473-
else:
474-
parts["removed_in"] = f"\n This will be removed in {removed_in}."
475-
if details:
476-
parts["details"] = f" {details}"
477-
478-
deprecation_note = (".. deprecated::{deprecated_in}{removed_in}{details}".format_map(parts))
479-
480-
# default location for insertion of deprecation note
481-
loc = 1
482-
483-
if len(string_list) > 1:
484-
# With a multi-line docstring, when we modify
485-
# existing_docstring to add our deprecation_note,
486-
# if we're not careful we'll interfere with the
487-
# indentation levels of the contents below the
488-
# first line, or as PEP 257 calls it, the summary
489-
# line. Since the summary line can start on the
490-
# same line as the """, dedenting the whole thing
491-
# won't help. Split the summary and contents up,
492-
# dedent the contents independently, then join
493-
# summary, dedent'ed contents, and our
494-
# deprecation_note.
495-
496-
# in-place dedent docstring content
497-
string_list[1] = textwrap.dedent(string_list[1])
498-
499-
# we need another newline
500-
string_list.insert(loc, '\n')
501-
502-
# change the message_location if we add to end of docstring
503-
# do this always if not "top"
504-
if deprecation.message_location != "top":
505-
loc = 3
506-
507-
# insert deprecation note and dual newline
508-
string_list.insert(loc, deprecation_note)
509-
string_list.insert(loc, "\n\n")
510-
511-
@functools.wraps(function)
512-
def _inner(*args, **kwargs):
513-
if should_warn:
514-
if is_unsupported:
515-
cls = deprecation.UnsupportedWarning
516-
else:
517-
cls = deprecation.DeprecatedWarning
518-
519-
the_warning = cls(name or function.__name__, deprecated_in, removed_in, details)
520-
warnings.warn(the_warning, category=DeprecationWarning, stacklevel=2)
521-
522-
return function(*args, **kwargs)
523-
524-
_inner.__doc__ = ''.join(string_list)
525-
526-
return _inner
527-
528-
return _function_wrapper
357+
deprecated = deprecated(
358+
deprecated_in="2.0.0",
359+
removed_in="2.3.0",
360+
current_version=__version__,
361+
details="Use the new 'deprecation-alias' package instead."
362+
)(deprecated) # yapf: disable
529363

530364

531365
def magnitude(x: float) -> int:

requirements.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
deprecation>=2.1.0
1+
deprecation-alias>=0.1.0
22
importlib-metadata>=1.5.0; python_version < "3.8"
33
importlib-resources>=3.0.0; python_version < "3.7"
44
natsort>=7.1.0
5-
packaging>=20.4
65
pydash>=4.7.4
76
typing-extensions>=3.7.4.3

tests/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
coverage>=5.1
22
coverage-pyver-pragma>=0.0.6
33
faker>=4.1.2
4+
flake8>=3.8.4
45
iniconfig!=1.1.0,>=1.0.1
56
pandas>=1.0.0; implementation_name == "cpython" and python_version < "3.10"
67
pytest>=6.0.0

tests/test_import_tools.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55

66
# 3rd party
77
import pytest
8+
from pytest_regressions.data_regression import DataRegressionFixture
89

910
# this package
10-
from domdf_python_tools.import_tools import discover
11+
from domdf_python_tools.import_tools import discover, discover_entry_points
1112

1213
sys.path.append('.')
1314
sys.path.append("tests")
@@ -94,3 +95,12 @@ def raises_attribute_error(obj, **kwargs):
9495
def test_discover_errors(obj, expects):
9596
with expects:
9697
discover(obj)
98+
99+
100+
def test_discover_entry_points(data_regression: DataRegressionFixture):
101+
102+
entry_points = [
103+
f.__name__
104+
for f in discover_entry_points("flake8.extension", lambda f: f.__name__.startswith("break"))
105+
]
106+
data_regression.check(entry_points)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- break_after_binary_operator
2+
- break_before_binary_operator

tests/test_utils.py

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -380,48 +380,6 @@ def test_str(self):
380380
assert head("Hello World", 5) == "Hello..."
381381

382382

383-
def test_deprecation():
384-
385-
def func(*args, **kwargs):
386-
"""
387-
A normal function.
388-
"""
389-
390-
return args, kwargs
391-
392-
@deprecated(deprecated_in='1', removed_in='3', current_version='2', details="use 'bar' instead.")
393-
def deprecated_func(*args, **kwargs):
394-
"""
395-
A deprecated function.
396-
"""
397-
398-
return args, kwargs
399-
400-
deprecated_alias = deprecated(
401-
deprecated_in='1',
402-
removed_in='3',
403-
current_version='2',
404-
details="use 'bar' instead.",
405-
name="deprecated_alias",
406-
)(func) # yapf: disable
407-
408-
with pytest.warns(DeprecationWarning) as record:
409-
assert deprecated_func(1, a_list=['a', 'b']) == ((1, ), {"a_list": ['a', 'b']})
410-
assert deprecated_alias(1, a_list=['a', 'b']) == ((1, ), {"a_list": ['a', 'b']})
411-
412-
assert len(record) == 2
413-
assert record[0].message.args == ( # type: ignore
414-
"deprecated_func", '1', '3', "use 'bar' instead."
415-
)
416-
assert record[1].message.args == ( # type: ignore
417-
"deprecated_alias", '1', '3', "use 'bar' instead."
418-
)
419-
420-
assert ".. deprecated::" in deprecated_func.__doc__
421-
assert ".. deprecated::" in deprecated_alias.__doc__
422-
assert ".. deprecated::" not in func.__doc__ # type: ignore
423-
424-
425383
def test_trim_precision():
426384
assert 170.10000000000002 != 170.1
427385
assert trim_precision(170.10000000000002, 1) == 170.1

0 commit comments

Comments
 (0)