summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn L. Villalovos <john@sodarock.com>2022-02-06 11:37:51 -0800
committerJohn L. Villalovos <john@sodarock.com>2022-02-06 11:37:51 -0800
commit6ca9aa2960623489aaf60324b4709848598aec91 (patch)
treeaa1fffb8cd926cc3c096684b0f07475b5f60bec5
parent4cb7d9224fb24e4a13fdff8271de5ce083ad7757 (diff)
downloadgitlab-6ca9aa2960623489aaf60324b4709848598aec91.tar.gz
chore: create a custom `warnings.warn` wrapper
Create a custom `warnings.warn` wrapper that will walk the stack trace to find the first frame outside of the `gitlab/` path to print the warning against. This will make it easier for users to find where in their code the error is generated from
-rw-r--r--gitlab/__init__.py13
-rw-r--r--gitlab/utils.py38
-rw-r--r--gitlab/v4/objects/artifacts.py11
-rw-r--r--gitlab/v4/objects/projects.py21
-rw-r--r--tests/unit/test_utils.py19
5 files changed, 82 insertions, 20 deletions
diff --git a/gitlab/__init__.py b/gitlab/__init__.py
index 5f168ac..8cffecd 100644
--- a/gitlab/__init__.py
+++ b/gitlab/__init__.py
@@ -20,6 +20,7 @@ import warnings
from typing import Any
import gitlab.config # noqa: F401
+from gitlab import utils as _utils
from gitlab._version import ( # noqa: F401
__author__,
__copyright__,
@@ -40,11 +41,13 @@ warnings.filterwarnings("default", category=DeprecationWarning, module="^gitlab"
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}")
diff --git a/gitlab/utils.py b/gitlab/utils.py
index 7b01d17..1979355 100644
--- a/gitlab/utils.py
+++ b/gitlab/utils.py
@@ -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
@@ -90,3 +93,36 @@ class EncodedId(str):
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,
+ )
diff --git a/gitlab/v4/objects/artifacts.py b/gitlab/v4/objects/artifacts.py
index dee2880..55d762b 100644
--- a/gitlab/v4/objects/artifacts.py
+++ b/gitlab/v4/objects/artifacts.py
@@ -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
@@ -34,10 +33,12 @@ class ProjectArtifactManager(RESTManager):
*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,
diff --git a/gitlab/v4/objects/projects.py b/gitlab/v4/objects/projects.py
index d1e993b..81eb624 100644
--- a/gitlab/v4/objects/projects.py
+++ b/gitlab/v4/objects/projects.py
@@ -1,4 +1,3 @@
-import warnings
from typing import Any, Callable, cast, Dict, List, Optional, TYPE_CHECKING, Union
import requests
@@ -548,10 +547,12 @@ class Project(RefreshMixin, SaveMixin, ObjectDeleteMixin, RepositoryMixin, RESTO
@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)
@@ -562,10 +563,12 @@ class Project(RefreshMixin, SaveMixin, ObjectDeleteMixin, RepositoryMixin, RESTO
*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)
diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py
index 9f90983..7641c69 100644
--- a/tests/unit/test_utils.py
+++ b/tests/unit/test_utils.py
@@ -16,6 +16,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import json
+import warnings
from gitlab import utils
@@ -76,3 +77,21 @@ class TestEncodedId:
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