Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add task to check for deprecations #1044

Closed
wants to merge 24 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ jobs:
git fetch
.circleci/is-version-number-acceptable.sh

- run:
name: Check expired deprecations
command: make check-deprecated

submit_coverage:
docker:
- image: python:3.7
Expand Down
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
# Changelog

# 36.0.0 [#1044](https://github.com/openfisca/openfisca-core/pull/1044)

Follows discussion on #1033
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary in the CHANGELOG.


#### New features

- Introduce `make check-deprecated`
- Allows for listing the features marked as deprecated.

Example:

```
$ make check-deprecated

[⚙] Check for features marked as deprecated...
[!] commons.dummy.__init__:17 => Deprecated since: 34.7.0. Expiration status: 36.0.0 (current: 36.0.0).
[/] 18% |█████████·········································|
```

### 35.5.1 [#1046](https://github.com/openfisca/openfisca-core/pull/1046)

#### Non-technical changes
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ print_pass = echo $$(tput setaf 2)[✓]$$(tput sgr0) $$(tput setaf 8)$1$$(tput s
## Similar to `print_work`, but this will read the comments above a task, and
## print them to the user at the start of each task. The `$1` is a function
## argument.
print_help = sed -n "/^$1/ { x ; p ; } ; s/\#\#/$(print_work)/ ; s/\./…/ ; x" ${MAKEFILE_LIST}
print_help = sed -n "/^$1/ { x ; p ; } ; s/\#\#/\r$(print_work)/ ; s/\./…/ ; x" ${MAKEFILE_LIST}

## Same as `make`.
.DEFAULT_GOAL := all
Expand Down
1 change: 1 addition & 0 deletions openfisca_core/commons/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

from .dummy import Dummy # noqa: F401

from .decorators import deprecated # noqa: F401
from .formulas import apply_thresholds, concat, switch # noqa: F401
from .misc import empty_clone, stringify_array # noqa: F401
from .rates import average_rate, marginal_rate # noqa: F401
111 changes: 111 additions & 0 deletions openfisca_core/commons/decorators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import functools
import warnings
import typing
from typing import Any, Callable, Sequence, TypeVar

T = Callable[..., Any]
F = TypeVar("F", bound = T)


class deprecated:
"""Allows (soft) deprecating a functionality of OpenFisca.

Attributes:
since:
Since when the functionality is deprecated.
expires:
When will it be removed forever?

Args:
since:
Since when the functionality is deprecated.
expires:
When will it be removed forever? Note that this value, if set to a
valid semantic version, it has to be a major one.

Raises:
ValueError:
When :attr:`expires` is set to a version, but not to a major one.


Examples:
>>> @deprecated(since = "35.5.0", expires = "in the future")
... def obsolete():
... return "I'm obsolete!"

>>> repr(obsolete)
'<function obsolete ...>'

>>> str(obsolete)
'<function obsolete ...>'

.. versionadded:: 36.0.0

"""

since: str
expires: str

def __init__(self, *, since: str, expires: str) -> None:
self.since = since
self.expires = self._parse(expires)

def __call__(self, function: F) -> F:
"""Wraps a function to return another one, decorated.

Args:
function: The function or method to decorate.

Returns:
:obj:`callable`: The decorated function.

Examples:
>>> def obsolete():
... return "I'm obsolete!"

>>> decorator = deprecated(
... since = "35.5.0",
... expires = "in the future",
... )

>>> decorator(obsolete)
<function obsolete ...>

"""

def wrapper(*args: Any, **kwds: Any) -> Any:
message: Sequence[str]
message = [
f"{function.__qualname__} has been deprecated since",
f"version {self.since}, and will be removed in",
f"{self.expires}.",
]

warnings.warn(" ".join(message), DeprecationWarning)
return function(*args, **kwds)

functools.update_wrapper(wrapper, function)
return typing.cast(F, wrapper)

@staticmethod
def _parse(expires: str) -> str:
minor: str
patch: str
message: Sequence[str]

if expires.find(".") == -1:
return expires

_, minor, patch, *_ = expires.split(".")

if minor != "0" or patch != "0":
message = [
"Deprecations can only expire on major releases.",
f"Or, {expires} is not a major one.",
"To learn more about semantic versioning:",
"https://semver.org/"
]

raise ValueError(" ".join(message))

return expires
21 changes: 13 additions & 8 deletions openfisca_core/commons/dummy.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import warnings
from .decorators import deprecated


class Dummy:
"""A class that does nothing."""
"""A class that did nothing.

Examples:
>>> Dummy()
<openfisca_core.commons.dummy.Dummy object...

.. deprecated:: 34.7.0
:class:`.Dummy` has been deprecated and it will be removed in 36.0.0.

"""

@deprecated(since = "34.7.0", expires = "36.0.0")
def __init__(self) -> None:
message = [
"The 'Dummy' class has been deprecated since version 34.7.0,",
"and will be removed in the future.",
]
warnings.warn(" ".join(message), DeprecationWarning)
pass
...
Empty file.
31 changes: 31 additions & 0 deletions openfisca_core/commons/tests/test_decorators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import re

import pytest

from openfisca_core.commons import deprecated


def test_deprecated():
"""The decorated function throws a deprecation warning when used."""

since = "yesterday"
expires = "tomorrow"
message = re.compile(f"^.*{since}.*{expires}.*$")

@deprecated(since = since, expires = expires)
def function(a: int, b: float) -> float:
return a + b

with pytest.warns(DeprecationWarning, match = message):
assert function(1, 2.) == 3.


def test_deprecated_when_illegal():
"""Raises an error when the deprecation expiration is not a major."""

since = "yesterday"
expires = "1.2.3"
message = "Deprecations can only expire on major releases"

with pytest.raises(ValueError, match = message):
deprecated(since = since, expires = expires)
10 changes: 10 additions & 0 deletions openfisca_core/commons/tests/test_dummy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import pytest

from openfisca_core.commons import Dummy


def test_dummy_deprecation():
"""Dummy throws a deprecation warning when instantiated."""

with pytest.warns(DeprecationWarning):
assert Dummy()
1 change: 1 addition & 0 deletions openfisca_tasks/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from ._check_deprecated import CheckDeprecated # noqa: F401
19 changes: 19 additions & 0 deletions openfisca_tasks/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from __future__ import annotations

import sys

import openfisca_tasks as tasks

from ._progress_bar import ProgressBar
from ._protocols import HasExit, SupportsProgress


if __name__ == "__main__":
task: HasExit
task = tasks.__getattribute__(sys.argv[1])()

progress: SupportsProgress
progress = ProgressBar()

task(progress)
sys.exit(task.exit)
Loading