codecov-io: # [Codecov](https://codecov.io/gh/briancurtin/deprecation/pull/14?src=pr&el=h1) Report
> Merging [#14](https://codecov.io/gh/briancurtin/deprecation/pull/14?src=pr&el=desc) into [master](https://codecov.io/gh/briancurtin/deprecation/commit/3ad94c2de7c313d0ca20a8ce4c9247a0faaa8018?src=pr&el=desc) will **decrease** coverage by `7.69%`.
> The diff coverage is `66.66%`.

[![Impacted file tree graph](https://codecov.io/gh/briancurtin/deprecation/pull/14/graphs/tree.svg?token=6w8zqu2CEQ&height=150&src=pr&width=650)](https://codecov.io/gh/briancurtin/deprecation/pull/14?src=pr&el=tree)

```diff
@@           Coverage Diff            @@
##           master     #14     +/-   ##
========================================
- Coverage     100%   92.3%   -7.7%     
========================================
  Files           1       1             
  Lines          62      78     +16     
  Branches       10      15      +5     
========================================
+ Hits           62      72     +10     
- Misses          0       3      +3     
- Partials        0       3      +3
```


| [Impacted Files](https://codecov.io/gh/briancurtin/deprecation/pull/14?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [deprecation.py](https://codecov.io/gh/briancurtin/deprecation/pull/14/diff?src=pr&el=tree#diff-ZGVwcmVjYXRpb24ucHk=) | `92.3% <66.66%> (-7.7%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/briancurtin/deprecation/pull/14?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/briancurtin/deprecation/pull/14?src=pr&el=footer). Last update [3ad94c2...7304a70](https://codecov.io/gh/briancurtin/deprecation/pull/14?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

diff --git a/deprecation.py b/deprecation.py
index 00598db..a64b5b7 100644
--- a/deprecation.py
+++ b/deprecation.py
@@ -9,6 +9,7 @@
 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 # License for the specific language governing permissions and limitations
 # under the License.
+import collections
 import functools
 import textwrap
 import warnings
@@ -51,9 +52,22 @@ class DeprecatedWarning(DeprecationWarning):
         super(DeprecatedWarning, self).__init__()
 
     def __str__(self):
-        return ("%s is deprecated as of %s and will "
-                "be removed in %s. %s" % (self.function, self.deprecated_in,
-                                          self.removed_in, self.details))
+        # Use a defaultdict to give us the empty string
+        # when a part isn't included.
+        parts = collections.defaultdict(str)
+        parts["function"] = self.function
+
+        if self.deprecated_in:
+            parts["deprecated"] = " as of %s" % self.deprecated_in
+        if self.removed_in:
+            parts["removed"] = " and will be removed in %s" % self.removed_in
+        if any([self.deprecated_in, self.removed_in, self.details]):
+            parts["period"] = "."
+        if self.details:
+            parts["details"] = " %s" % self.details
+
+        return ("%(function)s is deprecated%(deprecated)s%(removed)s"
+                "%(period)s%(details)s" % (parts))
 
 
 class UnsupportedWarning(DeprecatedWarning):
@@ -67,9 +81,15 @@ class UnsupportedWarning(DeprecatedWarning):
     """
 
     def __str__(self):
-        return ("%s is unsupported as of %s. %s" % (self.function,
-                                                    self.removed_in,
-                                                    self.details))
+        parts = collections.defaultdict(str)
+        parts["function"] = self.function
+        parts["removed"] = self.removed_in
+
+        if self.details:
+            parts["details"] = " %s" % self.details
+
+        return ("%(function)s is unsupported as of %(removed)s."
+                "%(details)s" % (parts))
 
 
 def deprecated(deprecated_in=None, removed_in=None, current_version=None,

DeprecatedWarning.__str__ should handle None arguments better
`DeprecatedWarning.__str__`doesn't play nicely with `None` values, such as what happens when you call `deprecation.deprecated()` with no arguments. This ends up showing up in the interpreter messages when run with `python -Wd`, though it doesn't affect docstrings.

    /.../deprecation.py:183: DeprecatedWarning: fn is deprecated as of None and will be removed in None. 
      warnings.warn(the_warning)
diff --git a/tests/test_deprecation.py b/tests/test_deprecation.py
index cf9d420..55e05bd 100644
--- a/tests/test_deprecation.py
+++ b/tests/test_deprecation.py
@@ -71,14 +71,33 @@ class Test_deprecated(unittest2.TestCase):
         ret_val = "lololol"
 
         for test in [{"args": {},  # No args just means deprecated
-                      "warning": deprecation.DeprecatedWarning},
+                      "warning": deprecation.DeprecatedWarning,
+                      "message": "method is deprecated"},
+                     {"args": {"details": "do something else."},
+                      "warning": deprecation.DeprecatedWarning,
+                      "message": "method is deprecated. do something else."},
                      {"args": {"deprecated_in": "1.0",
                                "current_version": "2.0"},
-                      "warning": deprecation.DeprecatedWarning},
+                      "warning": deprecation.DeprecatedWarning,
+                      "message": "method is deprecated as of 1.0."},
+                     {"args": {"deprecated_in": "1.0",
+                               "removed_in": "3.0",
+                               "current_version": "2.0"},
+                      "warning": deprecation.DeprecatedWarning,
+                      "message": ("method is deprecated as of 1.0 "
+                                  "and will be removed in 3.0.")},
                      {"args": {"deprecated_in": "1.0",
                                "removed_in": "2.0",
                                "current_version": "2.0"},
-                      "warning": deprecation.UnsupportedWarning}]:
+                      "warning": deprecation.UnsupportedWarning,
+                      "message": "method is unsupported as of 2.0."},
+                     {"args": {"deprecated_in": "1.0",
+                               "removed_in": "2.0",
+                               "current_version": "2.0",
+                               "details": "do something else."},
+                      "warning": deprecation.UnsupportedWarning,
+                      "message": ("method is unsupported as of 2.0. "
+                                  "do something else.")}]:
             with self.subTest(**test):
                 class Test(object):
                     @deprecation.deprecated(**test["args"])
@@ -93,6 +112,8 @@ class Test_deprecated(unittest2.TestCase):
 
                 self.assertEqual(len(caught_warnings), 1)
                 self.assertEqual(caught_warnings[0].category, test["warning"])
+                self.assertEqual(str(caught_warnings[0].message),
+                                 test["message"])
 
     def test_DeprecatedWarning_not_raised(self):
         ret_val = "lololol"

pip install -e .
parse_log_pytest
pytest
unittest2 pytest
3.6
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs @ file:///opt/conda/conda-bld/attrs_1642510447205/work
certifi==2021.5.30
-e git+https://github.com/briancurtin/deprecation.git@3ad94c2de7c313d0ca20a8ce4c9247a0faaa8018#egg=deprecation
importlib-metadata @ file:///tmp/build/80754af9/importlib-metadata_1631916693255/work
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
linecache2==1.0.0
more-itertools @ file:///tmp/build/80754af9/more-itertools_1637733554872/work
packaging @ file:///tmp/build/80754af9/packaging_1637314298585/work
pluggy @ file:///tmp/build/80754af9/pluggy_1615976315926/work
py @ file:///opt/conda/conda-bld/py_1644396412707/work
pyparsing @ file:///tmp/build/80754af9/pyparsing_1635766073266/work
pytest==6.2.4
six==1.17.0
toml @ file:///tmp/build/80754af9/toml_1616166611790/work
traceback2==1.4.0
typing_extensions @ file:///opt/conda/conda-bld/typing_extensions_1647553014482/work
unittest2==1.1.0
zipp @ file:///tmp/build/80754af9/zipp_1633618647012/work

name: deprecation
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - attrs=21.4.0=pyhd3eb1b0_0
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - importlib-metadata=4.8.1=py36h06a4308_0
  - importlib_metadata=4.8.1=hd3eb1b0_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - more-itertools=8.12.0=pyhd3eb1b0_0
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - packaging=21.3=pyhd3eb1b0_0
  - pip=21.2.2=py36h06a4308_0
  - pluggy=0.13.1=py36h06a4308_0
  - py=1.11.0=pyhd3eb1b0_0
  - pyparsing=3.0.4=pyhd3eb1b0_0
  - pytest=6.2.4=py36h06a4308_2
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - toml=0.10.2=pyhd3eb1b0_0
  - typing_extensions=4.1.1=pyh06a4308_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zipp=3.6.0=pyhd3eb1b0_0
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - argparse==1.4.0
      - linecache2==1.0.0
      - six==1.17.0
      - traceback2==1.4.0
      - unittest2==1.1.0
prefix: /opt/conda/envs/deprecation

tests/test_deprecation.py::Test_deprecated::test_warning_raised
tests/test_deprecation.py::Test_deprecated::test_DeprecatedWarning_not_raised tests/test_deprecation.py::Test_deprecated::test_docstring tests/test_deprecation.py::Test_deprecated::test_multiline_docstring tests/test_deprecation.py::Test_deprecated::test_removing_without_deprecating tests/test_deprecation.py::Test_fail_if_not_removed::test_DeprecatedWarning_doesnt_fail tests/test_deprecation.py::Test_fail_if_not_removed::test_UnsupportedWarning_causes_failure tests/test_deprecation.py::Test_fail_if_not_removed::test_literal_DeprecatedWarning
deprecation.py
deprecation.py