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

Remove dependency on deprecated package #1113

Merged
merged 3 commits into from
Mar 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 7 additions & 11 deletions lektor/imagetools.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import filetype

from lektor.reporter import reporter
from lektor.utils import deprecated
from lektor.utils import get_dependent_url
from lektor.utils import locate_executable
from lektor.utils import portable_popen
Expand All @@ -29,24 +30,19 @@ class ThumbnailMode(Enum):

DEFAULT = "fit"

@property
@property # type: ignore[misc] # https://github.com/python/mypy/issues/1362
@deprecated("Use ThumbnailMode.value instead", version="3.3.0")
def label(self):
"""The mode's label as used in templates."""
warnings.warn(
"ThumbnailMode.label is deprecated. (Use ThumbnailMode.value instead.)",
DeprecationWarning,
)
return self.value

@classmethod
@deprecated(
"Use the ThumbnailMode constructor, e.g. 'ThumbnailMode(label)', instead",
version="3.3.0",
)
def from_label(cls, label):
"""Looks up the thumbnail mode by its textual representation."""
warnings.warn(
"ThumbnailMode.from_label is deprecated. "
"Use the ThumbnailMode constructor, "
'e.g. "ThumbnailMode(label)", instead.',
DeprecationWarning,
)
return cls(label)


Expand Down
6 changes: 3 additions & 3 deletions lektor/markdown/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from typing import TYPE_CHECKING
from weakref import ref as weakref

from deprecated import deprecated
from markupsafe import Markup

from lektor.compat import importlib_metadata as metadata
Expand All @@ -16,6 +15,7 @@
from lektor.markdown.controller import Meta
from lektor.markdown.controller import RenderResult
from lektor.sourceobj import SourceObject
from lektor.utils import deprecated

if TYPE_CHECKING: # pragma: no cover
from lektor.environment import Environment
Expand All @@ -35,12 +35,12 @@
get_controller = ControllerCache(controller_class)


@deprecated
@deprecated(version="3.4.0")
def make_markdown(env: "Environment") -> Any: # (Environment) -> mistune.Markdown
return get_controller(env).make_parser()


@deprecated
@deprecated(version="3.4.0")
def markdown_to_html(
text: str, record: SourceObject, field_options: FieldOptions
) -> RenderResult:
Expand Down
6 changes: 3 additions & 3 deletions lektor/markdown/mistune0.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
from typing import Optional

import mistune # type: ignore[import]
from deprecated import deprecated

from lektor.markdown.controller import MarkdownController
from lektor.markdown.controller import Meta # FIXME: move this?
from lektor.markdown.controller import RendererHelper
from lektor.sourceobj import SourceObject
from lektor.utils import deprecated


def _escape(text: str) -> str:
Expand All @@ -24,12 +24,12 @@ class ImprovedRenderer(
lektor: ClassVar = RendererHelper()

@property # type: ignore[misc] # https://github.com/python/mypy/issues/1362
@deprecated("Use ImprovedRenderer.lektor.record instead.")
@deprecated("Use ImprovedRenderer.lektor.record instead.", version="3.4.0")
def record(self) -> SourceObject:
return self.lektor.record

@property # type: ignore[misc]
@deprecated("Use ImprovedRenderer.lektor.meta instead.")
@deprecated("Use ImprovedRenderer.lektor.meta instead.", version="3.4.0")
def meta(self) -> Meta:
return self.lektor.meta

Expand Down
1 change: 1 addition & 0 deletions lektor/pluginsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ def emit(self, event, **kwargs):
rv[plugin.id] = handler(**kw)
if "extra_flags" not in kw:
warnings.warn(
# deprecated since 3.2.0
f"The plugin {plugin.id!r} function {funcname!r} does not "
"accept extra_flags. "
"It should be updated to accept `**extra` so that it will "
Expand Down
3 changes: 2 additions & 1 deletion lektor/publisher.py
Original file line number Diff line number Diff line change
Expand Up @@ -836,6 +836,7 @@ def _parse_url(
" ".join(
cleandoc(self._DEFAULT_BRANCH_DEPRECATION_MSG).splitlines()
),
# deprecated in version 3.4.0
category=DeprecationWarning,
)
branch = "master"
Expand Down Expand Up @@ -872,7 +873,7 @@ def _parse_url(
Currently, by default, Lektor pushes to the 'master' branch when
deploying to GitHub pages repositories. In a future version of
Lektor, the default branch will GitHub's new default, 'main'.
It is suggest that you explicitly set which branch to push to.
It is suggested that you explicitly set which branch to push to.
"""

@staticmethod
Expand Down
108 changes: 107 additions & 1 deletion lektor/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import annotations

import codecs
import json
import os
Expand All @@ -9,12 +10,18 @@
import tempfile
import unicodedata
import uuid
import warnings
from contextlib import contextmanager
from dataclasses import dataclass
from datetime import datetime
from functools import lru_cache
from functools import wraps
from pathlib import PurePosixPath
from typing import Any
from typing import Callable
from typing import Hashable
from typing import Iterable
from typing import overload
from typing import TypeVar
from urllib.parse import urlsplit

Expand Down Expand Up @@ -712,3 +719,102 @@ def unique_everseen(seq: Iterable[_H]) -> Iterable[_H]:
if val not in seen:
seen.add(val)
yield val


class DeprecatedWarning(DeprecationWarning):
"""Warning category issued by our ``deprecated`` decorator."""

def __init__(
self,
name: str,
reason: str | None = None,
version: str | None = None,
):
self.name = name
self.reason = reason
self.version = version

def __str__(self) -> str:
message = f"{self.name!r} is deprecated"
if self.reason:
message += f" ({self.reason})"
if self.version:
message += f" since version {self.version}"
return message


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


@dataclass
class _Deprecate:
"""A decorator to mark callables as deprecated."""

reason: str | None = None
version: str | None = None

def __call__(self, wrapped: _F) -> _F:
if not callable(wrapped):
raise TypeError("do not know how to deprecate {wrapped!r}")

message = DeprecatedWarning(wrapped.__name__, self.reason, self.version)

@wraps(wrapped)
def wrapper(*args: Any, **kwargs: Any) -> Any:
warnings.warn(message, stacklevel=2)
with warnings.catch_warnings():
# ignore any of our own custom warnings generated by wrapped callable
warnings.simplefilter("ignore", category=DeprecatedWarning)
return wrapped(*args, **kwargs)

return wrapper # type: ignore[return-value]


@overload
def deprecated(
__wrapped: Callable[..., Any],
*,
reason: str | None = ...,
version: str | None = ...,
) -> Callable[..., Any]:
...


@overload
def deprecated(__reason: str, *, version: str | None = ...) -> _Deprecate:
...


@overload
def deprecated(*, reason: str | None = ..., version: str | None = ...) -> _Deprecate:
...


def deprecated(*args: Any, **kwargs: Any) -> _F | _Deprecate:
"""A decorator to mark callables or descriptors as deprecated.

This can be used to decorate functions, methods, classes, and descriptors.
In particular, this decorator can be applied to instances of ``property``,
``functools.cached_property`` and ``werkzeug.utils.cached_property``.

When the decorated object is called (or — in the case of a descriptor — accessed), a
``DeprecationWarning`` is issued.

The warning message will include the name of the decorated object, and may include
further information if provided from the ``reason`` and ``version`` arguments.

"""
if len(args) > 1:
raise TypeError("deprecated accepts a maximum of one positional parameter")

wrapped: _F | None = None
if args:
if isinstance(args[0], str):
kwargs.setdefault("reason", args[0])
else:
wrapped = args[0]

deprecate = _Deprecate(**kwargs)
if wrapped is not None:
return deprecate(wrapped)
return deprecate
1 change: 0 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ python_requires = >=3.7
install_requires =
Babel
click>=6.0
deprecated
EXIFRead
filetype>=1.0.7
Flask
Expand Down
18 changes: 12 additions & 6 deletions tests/test_markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,16 +227,20 @@ def improved_renderer():
@pytest.mark.skipif(not MISTUNE_VERSION.startswith("0."), reason="not mistune0")
@pytest.mark.usefixtures("renderer_context")
def test_ImprovedRenderer_record(record, improved_renderer):
with pytest.deprecated_call() as warnings:
with pytest.deprecated_call(
match=r"Use .*Renderer\.lektor.record instead"
) as warnings:
assert improved_renderer.record is record
assert re.search(r"Use .*Renderer\.lektor.record instead", str(warnings[0].message))
assert all(w.filename == __file__ for w in warnings)


@pytest.mark.skipif(not MISTUNE_VERSION.startswith("0."), reason="not mistune0")
def test_ImprovedRenderer_meta(renderer_context, improved_renderer):
with pytest.deprecated_call() as warnings:
with pytest.deprecated_call(
match=r"Use .*Renderer\.lektor.meta instead"
) as warnings:
assert improved_renderer.meta is renderer_context.meta
assert re.search(r"Use .*Renderer\.lektor.meta instead", str(warnings[0].message))
assert all(w.filename == __file__ for w in warnings)


@pytest.mark.parametrize(
Expand Down Expand Up @@ -288,15 +292,17 @@ def test_ImprovedRenderer_image(src, title, alt, expected, improved_renderer):


def test_make_markdown(env):
with pytest.deprecated_call():
with pytest.deprecated_call() as warnings:
md = make_markdown(env)
assert all(w.filename == __file__ for w in warnings)
assert md("foo").strip() == "<p>foo</p>"


@pytest.mark.usefixtures("context")
def test_markdown_to_html(record, field_options):
with pytest.deprecated_call():
with pytest.deprecated_call() as warnings:
result = markdown_to_html("goober", record, field_options)
assert all(w.filename == __file__ for w in warnings)
assert result.html.rstrip() == "<p>goober</p>"


Expand Down
71 changes: 70 additions & 1 deletion tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# coding: utf-8
from contextlib import contextmanager

import pytest

from lektor.utils import build_url
from lektor.utils import deprecated
from lektor.utils import is_path_child_of
from lektor.utils import join_path
from lektor.utils import magic_split_ext
Expand Down Expand Up @@ -140,3 +142,70 @@ def test_make_relative_url_relative_source_absolute_target():
)
def test_unique_everseen(seq, expected):
assert tuple(unique_everseen(seq)) == expected


@contextmanager
def _local_deprecated_call(match=None):
"""Like pytest.deprecated_call, but also check that all warnings
are attributed to this file.
"""
with pytest.deprecated_call(match=match) as warnings:
yield warnings
assert all(w.filename == __file__ for w in warnings)


@pytest.mark.parametrize(
"kwargs, match",
[
({}, r"^'f' is deprecated$"),
({"reason": "testing"}, r"^'f' is deprecated \(testing\)$"),
(
{"reason": "testing", "version": "1.2.3"},
r"^'f' is deprecated \(testing\) since version 1.2.3$",
),
],
)
def test_deprecated_function(kwargs, match):
@deprecated(**kwargs)
def f():
return 42

with _local_deprecated_call(match=match):
assert f() == 42


def test_deprecated_method():
class Cls:
@deprecated
def f(self): # pylint: disable=no-self-use
return 42

@deprecated
def g(self):
return self.f()

with _local_deprecated_call(match=r"^'g' is deprecated$") as warnings:
assert Cls().g() == 42
assert len(warnings) == 1


def test_deprecated_classmethod():
class Cls:
@classmethod
@deprecated
def f(cls):
return 42

@classmethod
@deprecated
def g(cls):
return cls.f()

with _local_deprecated_call(match=r"^'g' is deprecated$") as warnings:
assert Cls().g() == 42
assert len([w.message for w in warnings]) == 1


def test_deprecated_raises_type_error():
with pytest.raises(TypeError):
deprecated(0)