diff --git a/octodns/deprecation.py b/octodns/deprecation.py new file mode 100644 index 0000000..bdb1e88 --- /dev/null +++ b/octodns/deprecation.py @@ -0,0 +1,9 @@ +# +# +# + +from warnings import warn + + +def deprecated(message, stacklevel=2): + warn(message, DeprecationWarning, stacklevel=stacklevel) diff --git a/octodns/processor/meta.py b/octodns/processor/meta.py index ee56ef8..0389388 100644 --- a/octodns/processor/meta.py +++ b/octodns/processor/meta.py @@ -10,6 +10,14 @@ from .. import __version__ from ..record import Record from .base import BaseProcessor +# TODO: remove once we require python >= 3.11 +try: # pragma: no cover + from datetime import UTC +except ImportError: # pragma: no cover + from datetime import timedelta, timezone + + UTC = timezone(timedelta()) + def _keys(values): return set(v.split('=', 1)[0] for v in values) @@ -55,7 +63,7 @@ class MetaProcessor(BaseProcessor): @classmethod def now(cls): - return datetime.utcnow().isoformat() + return datetime.now(UTC).isoformat() @classmethod def uuid(cls): diff --git a/octodns/processor/spf.py b/octodns/processor/spf.py index 0d86d5e..fbd4575 100644 --- a/octodns/processor/spf.py +++ b/octodns/processor/spf.py @@ -10,6 +10,7 @@ from dns.resolver import Answer from octodns.record.base import Record +from ..deprecation import deprecated from .base import BaseProcessor, ProcessorException @@ -55,8 +56,9 @@ class SpfDnsLookupProcessor(BaseProcessor): def __init__(self, name): self.log.debug(f"SpfDnsLookupProcessor: {name}") - self.log.warning( - 'SpfDnsLookupProcessor is DEPRECATED in favor of the version relocated into octodns-spf and will be removed in 2.0' + deprecated( + 'SpfDnsLookupProcessor is DEPRECATED in favor of the version relocated into octodns-spf and will be removed in 2.0', + stacklevel=99, ) super().__init__(name) diff --git a/octodns/provider/yaml.py b/octodns/provider/yaml.py index 8430091..3fc6102 100644 --- a/octodns/provider/yaml.py +++ b/octodns/provider/yaml.py @@ -7,6 +7,7 @@ from collections import defaultdict from os import listdir, makedirs from os.path import isdir, isfile, join +from ..deprecation import deprecated from ..record import Record from ..yaml import safe_dump, safe_load from . import ProviderException @@ -464,6 +465,7 @@ class SplitYamlProvider(YamlProvider): } ) super().__init__(id, directory, *args, **kwargs) - self.log.warning( - '__init__: DEPRECATED use YamlProvider with split_extension, split_catchall, and disable_zonefile instead, will go away in v2.0' + deprecated( + 'SplitYamlProvider is DEPRECATED, use YamlProvider with split_extension, split_catchall, and disable_zonefile instead, will go away in v2.0', + stacklevel=99, ) diff --git a/octodns/record/ds.py b/octodns/record/ds.py index fe25803..2e8bfe4 100644 --- a/octodns/record/ds.py +++ b/octodns/record/ds.py @@ -4,6 +4,7 @@ from logging import getLogger +from ..deprecation import deprecated from ..equality import EqualityTupleMixin from .base import Record, ValuesMixin from .rr import RrParseError @@ -48,8 +49,9 @@ class DsValue(EqualityTupleMixin, dict): # it is safe to assume if public_key or flags are defined then it is "old" style # A DS record without public_key doesn't make any sense and shouldn't have validated previously if "public_key" in value or "flags" in value: - cls.log.warning( - '"algorithm", "flags", "public_key", and "protocol" support is DEPRECATED and will be removed in 2.0' + deprecated( + 'DS properties "algorithm", "flags", "public_key", and "protocol" support is DEPRECATED and will be removed in 2.0', + stacklevel=99, ) try: int(value['flags']) diff --git a/octodns/record/geo.py b/octodns/record/geo.py index af8337c..ade3806 100644 --- a/octodns/record/geo.py +++ b/octodns/record/geo.py @@ -5,6 +5,7 @@ import re from logging import getLogger +from ..deprecation import deprecated from ..equality import EqualityTupleMixin from .base import ValuesMixin from .change import Update @@ -141,8 +142,9 @@ class _GeoMixin(ValuesMixin): reasons = super().validate(name, fqdn, data) try: geo = dict(data['geo']) - cls.log.warning( - 'NOTICE: `geo` record support is deprecated and should be migrated to `dynamic` records' + deprecated( + '`geo` records are DEPRECATED. `dynamic` records should be used instead. Will be removed in 2.0', + stacklevel=99, ) for code, values in geo.items(): reasons.extend(GeoValue._validate_geo(code)) diff --git a/octodns/record/spf.py b/octodns/record/spf.py index 9eb7a4a..f0bd824 100644 --- a/octodns/record/spf.py +++ b/octodns/record/spf.py @@ -2,6 +2,7 @@ # # +from ..deprecation import deprecated from .base import Record from .chunked import _ChunkedValue, _ChunkedValuesMixin @@ -12,8 +13,9 @@ class SpfRecord(_ChunkedValuesMixin, Record): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.log.warning( - 'The SPF record type is DEPRECATED in favor of TXT values and will become an ValidationError in 2.0' + deprecated( + 'The SPF record type is DEPRECATED in favor of TXT values and will become an ValidationError in 2.0', + stacklevel=99, ) diff --git a/octodns/zone.py b/octodns/zone.py index a1ee51d..fe604f7 100644 --- a/octodns/zone.py +++ b/octodns/zone.py @@ -6,6 +6,7 @@ import re from collections import defaultdict from logging import getLogger +from .deprecation import deprecated from .idna import idna_decode, idna_encode from .record import Create, Delete @@ -197,8 +198,9 @@ class Zone(object): # TODO: delete this at v2.0.0rc0 def _remove_record(self, record): - self.log.warning( - '_remove_record: method has been deprecated, used remove_record instead' + deprecated( + '_remove_record has been deprecated, used remove_record instead. Will be removed in 2.0', + stacklevel=3, ) return self.remove_record(record) diff --git a/pyproject.toml b/pyproject.toml index d8a2a3d..74c48d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,4 +9,8 @@ known_first_party="octodns" line_length=80 [tool.pytest.ini_options] -pythonpath = "." +filterwarnings = [ + 'error', + 'ignore:.*DEPRECATED.*2.0', +] +pythonpath = "." \ No newline at end of file diff --git a/requirements-dev.txt b/requirements-dev.txt index e4bfab5..1ccfbcc 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,19 +1,20 @@ # DO NOT EDIT THIS FILE DIRECTLY - use ./script/update-requirements to update -Pygments==2.16.1 -black==23.9.1 +Pygments==2.17.2 +black==23.11.0 build==1.0.3 -certifi==2023.7.22 +certifi==2023.11.17 cffi==1.16.0 -charset-normalizer==3.3.0 +charset-normalizer==3.3.2 click==8.1.7 cmarkgfm==2022.10.27 coverage==7.3.2 docutils==0.20.1 +exceptiongroup==1.2.0 importlib-metadata==6.8.0 iniconfig==2.0.0 isort==5.12.0 jaraco.classes==3.3.0 -keyring==24.2.0 +keyring==24.3.0 markdown-it-py==3.0.0 mdurl==0.1.2 more-itertools==10.1.0 @@ -22,7 +23,7 @@ nh3==0.2.14 packaging==23.2 pathspec==0.11.2 pkginfo==1.9.6 -platformdirs==3.11.0 +platformdirs==4.0.0 pluggy==1.3.0 pprintpp==0.4.0 pycountry-convert==0.7.2 @@ -31,17 +32,17 @@ pycparser==2.21 pyflakes==3.1.0 pyproject_hooks==1.0.0 pytest-cov==4.1.0 -pytest-mock==3.11.1 +pytest-mock==3.12.0 pytest-network==0.0.1 -pytest==7.4.2 +pytest==7.4.3 readme-renderer==42.0 repoze.lru==0.7 requests-toolbelt==1.0.0 requests==2.31.0 rfc3986==2.0.0 -rich==13.6.0 -setuptools==68.2.2 +rich==13.7.0 +tomli==2.0.1 twine==4.0.2 -urllib3==2.0.6 -wheel==0.41.2 +typing_extensions==4.8.0 +urllib3==2.1.0 zipp==3.17.0 diff --git a/tests/test_octodns_record_geo.py b/tests/test_octodns_record_geo.py index 420570e..6481b5f 100644 --- a/tests/test_octodns_record_geo.py +++ b/tests/test_octodns_record_geo.py @@ -213,24 +213,6 @@ class TestRecordGeoCodes(TestCase): self.assertTrue(c >= b) def test_validation(self): - with self.assertLogs('Record', level='WARNING') as cm: - Record.new( - self.zone, - '', - { - 'geo': {'NA': ['1.2.3.5'], 'NA-US': ['1.2.3.5', '1.2.3.6']}, - 'type': 'A', - 'ttl': 600, - 'value': '1.2.3.4', - }, - ) - self.assertEqual( - [ - 'WARNING:Record:NOTICE: `geo` record support is deprecated and should be migrated to `dynamic` records' - ], - cm.output, - ) - # invalid ip address with self.assertRaises(ValidationError) as ctx: Record.new(