Skip to content

chore: create a custom warnings.warn wrapper #1882

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

Merged
merged 1 commit into from
Feb 6, 2022
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions gitlab/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from typing import Any

import gitlab.config # noqa: F401
from gitlab import utils as _utils
from gitlab._version import ( # noqa: F401
__author__,
__copyright__,
Expand All @@ -40,11 +41,13 @@
def __getattr__(name: str) -> Any:
# Deprecate direct access to constants without namespace
if name in gitlab.const._DEPRECATED:
warnings.warn(
f"\nDirect access to 'gitlab.{name}' is deprecated and will be "
f"removed in a future major python-gitlab release. Please "
f"use 'gitlab.const.{name}' instead.",
DeprecationWarning,
_utils.warn(
message=(
f"\nDirect access to 'gitlab.{name}' is deprecated and will be "
f"removed in a future major python-gitlab release. Please "
f"use 'gitlab.const.{name}' instead."
),
category=DeprecationWarning,
)
return getattr(gitlab.const, name)
raise AttributeError(f"module {__name__} has no attribute {name}")
38 changes: 37 additions & 1 deletion gitlab/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import pathlib
import traceback
import urllib.parse
from typing import Any, Callable, Dict, Optional, Union
import warnings
from typing import Any, Callable, Dict, Optional, Type, Union

import requests

Expand Down Expand Up @@ -90,3 +93,36 @@ def __new__( # type: ignore

def remove_none_from_dict(data: Dict[str, Any]) -> Dict[str, Any]:
return {k: v for k, v in data.items() if v is not None}


def warn(
message: str,
*,
category: Optional[Type] = None,
source: Optional[Any] = None,
) -> None:
"""This `warnings.warn` wrapper function attempts to show the location causing the
warning in the user code that called the library.

It does this by walking up the stack trace to find the first frame located outside
the `gitlab/` directory. This is helpful to users as it shows them their code that
is causing the warning.
"""
# Get `stacklevel` for user code so we indicate where issue is in
# their code.
pg_dir = pathlib.Path(__file__).parent.resolve()
stack = traceback.extract_stack()
stacklevel = 1
warning_from = ""
for stacklevel, frame in enumerate(reversed(stack), start=1):
if stacklevel == 2:
warning_from = f" (python-gitlab: {frame.filename}:{frame.lineno})"
frame_dir = str(pathlib.Path(frame.filename).parent.resolve())
if not frame_dir.startswith(str(pg_dir)):
break
warnings.warn(
message=message + warning_from,
category=category,
stacklevel=stacklevel,
source=source,
)
11 changes: 6 additions & 5 deletions gitlab/v4/objects/artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
GitLab API:
https://docs.gitlab.com/ee/api/job_artifacts.html
"""
import warnings
from typing import Any, Callable, Optional, TYPE_CHECKING

import requests
Expand Down Expand Up @@ -34,10 +33,12 @@ def __call__(
*args: Any,
**kwargs: Any,
) -> Optional[bytes]:
warnings.warn(
"The project.artifacts() method is deprecated and will be "
"removed in a future version. Use project.artifacts.download() instead.\n",
DeprecationWarning,
utils.warn(
message=(
"The project.artifacts() method is deprecated and will be removed in a "
"future version. Use project.artifacts.download() instead.\n"
),
category=DeprecationWarning,
)
return self.download(
*args,
Expand Down
21 changes: 12 additions & 9 deletions gitlab/v4/objects/projects.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import warnings
from typing import Any, Callable, cast, Dict, List, Optional, TYPE_CHECKING, Union

import requests
Expand Down Expand Up @@ -548,10 +547,12 @@ def transfer(self, to_namespace: Union[int, str], **kwargs: Any) -> None:

@cli.register_custom_action("Project", ("to_namespace",))
def transfer_project(self, *args: Any, **kwargs: Any) -> None:
warnings.warn(
"The project.transfer_project() method is deprecated and will be "
"removed in a future version. Use project.transfer() instead.",
DeprecationWarning,
utils.warn(
message=(
"The project.transfer_project() method is deprecated and will be "
"removed in a future version. Use project.transfer() instead."
),
category=DeprecationWarning,
)
return self.transfer(*args, **kwargs)

Expand All @@ -562,10 +563,12 @@ def artifact(
*args: Any,
**kwargs: Any,
) -> Optional[bytes]:
warnings.warn(
"The project.artifact() method is deprecated and will be "
"removed in a future version. Use project.artifacts.raw() instead.",
DeprecationWarning,
utils.warn(
message=(
"The project.artifact() method is deprecated and will be "
"removed in a future version. Use project.artifacts.raw() instead."
),
category=DeprecationWarning,
)
return self.artifacts.raw(*args, **kwargs)

Expand Down
19 changes: 19 additions & 0 deletions tests/unit/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import json
import warnings

from gitlab import utils

Expand Down Expand Up @@ -76,3 +77,21 @@ def test_json_serializable(self):

obj = utils.EncodedId("we got/a/path")
assert '"we%20got%2Fa%2Fpath"' == json.dumps(obj)


class TestWarningsWrapper:
def test_warn(self):
warn_message = "short and stout"
warn_source = "teapot"

with warnings.catch_warnings(record=True) as caught_warnings:
utils.warn(message=warn_message, category=UserWarning, source=warn_source)
assert len(caught_warnings) == 1
warning = caught_warnings[0]
# File name is this file as it is the first file outside of the `gitlab/` path.
assert __file__ == warning.filename
assert warning.category == UserWarning
assert isinstance(warning.message, UserWarning)
assert warn_message in str(warning.message)
assert __file__ in str(warning.message)
assert warn_source == warning.source