Skip to content

Commit c0aa0e1

Browse files
refactor: deprecate accessing constants from top-level namespace
We are planning on adding enumerated constants into gitlab/const.py, but if we do that than they will end up being added to the top-level gitlab namespace. We really want to get users to start using `gitlab.const.` to access the constant values in the future. Add the currently defined constants to a list that should not change. Use a module level __getattr__ function so that we can deprecate access to the top-level constants. Add a unit test which verifies we generate a warning when accessing the top-level constants.
1 parent 09a973e commit c0aa0e1

File tree

3 files changed

+83
-17
lines changed

3 files changed

+83
-17
lines changed

gitlab/__init__.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"""Wrapper for the GitLab API."""
1818

1919
import warnings
20+
from typing import Any
2021

2122
import gitlab.config # noqa: F401
2223
from gitlab.__version__ import ( # noqa: F401
@@ -28,7 +29,22 @@
2829
__version__,
2930
)
3031
from gitlab.client import Gitlab, GitlabList # noqa: F401
31-
from gitlab.const import * # noqa: F401,F403
3232
from gitlab.exceptions import * # noqa: F401,F403
3333

3434
warnings.filterwarnings("default", category=DeprecationWarning, module="^gitlab")
35+
36+
37+
# NOTE(jlvillal): We are deprecating access to the gitlab.const values which
38+
# were previously imported into this namespace by the
39+
# 'from gitlab.const import *' statement.
40+
def __getattr__(name: str) -> Any:
41+
# Deprecate direct access to constants without namespace
42+
if name in gitlab.const._DEPRECATED:
43+
warnings.warn(
44+
f"\nDirect access to 'gitlab.{name}' is deprecated and will be "
45+
f"removed in a future major python-gitlab release. Please "
46+
f"use 'gitlab.const.{name}' instead.",
47+
DeprecationWarning,
48+
)
49+
return getattr(gitlab.const, name)
50+
raise AttributeError(f"module {__name__} has no attribute {name}")

gitlab/const.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,41 @@
1717

1818
from gitlab.__version__ import __title__, __version__
1919

20+
# NOTE(jlvillal): '_DEPRECATED' only affects users accessing constants via the
21+
# top-level gitlab.* namespace. See 'gitlab/__init__.py:__getattr__()' for the
22+
# consumer of '_DEPRECATED' For example 'x = gitlab.NO_ACCESS'. We want users
23+
# to instead use constants by doing code like: gitlab.const.NO_ACCESS.
24+
_DEPRECATED = [
25+
"DEFAULT_URL",
26+
"DEVELOPER_ACCESS",
27+
"GUEST_ACCESS",
28+
"MAINTAINER_ACCESS",
29+
"MINIMAL_ACCESS",
30+
"NO_ACCESS",
31+
"NOTIFICATION_LEVEL_CUSTOM",
32+
"NOTIFICATION_LEVEL_DISABLED",
33+
"NOTIFICATION_LEVEL_GLOBAL",
34+
"NOTIFICATION_LEVEL_MENTION",
35+
"NOTIFICATION_LEVEL_PARTICIPATING",
36+
"NOTIFICATION_LEVEL_WATCH",
37+
"OWNER_ACCESS",
38+
"REPORTER_ACCESS",
39+
"SEARCH_SCOPE_BLOBS",
40+
"SEARCH_SCOPE_COMMITS",
41+
"SEARCH_SCOPE_GLOBAL_SNIPPET_TITLES",
42+
"SEARCH_SCOPE_ISSUES",
43+
"SEARCH_SCOPE_MERGE_REQUESTS",
44+
"SEARCH_SCOPE_MILESTONES",
45+
"SEARCH_SCOPE_PROJECT_NOTES",
46+
"SEARCH_SCOPE_PROJECTS",
47+
"SEARCH_SCOPE_USERS",
48+
"SEARCH_SCOPE_WIKI_BLOBS",
49+
"USER_AGENT",
50+
"VISIBILITY_INTERNAL",
51+
"VISIBILITY_PRIVATE",
52+
"VISIBILITY_PUBLIC",
53+
]
54+
2055
DEFAULT_URL: str = "https://gitlab.com"
2156

2257
NO_ACCESS: int = 0

tests/unit/test_gitlab.py

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@
1717
# along with this program. If not, see <http://www.gnu.org/licenses/>.
1818

1919
import pickle
20+
import warnings
2021

2122
import pytest
2223
from httmock import HTTMock, response, urlmatch, with_httmock # noqa
2324

24-
from gitlab import DEFAULT_URL, Gitlab, GitlabList, USER_AGENT
25-
from gitlab.v4.objects import CurrentUser
25+
import gitlab
2626

2727
localhost = "http://localhost"
2828
username = "username"
@@ -94,7 +94,7 @@ def test_gitlab_build_list(gl):
9494
@with_httmock(resp_page_1, resp_page_2)
9595
def test_gitlab_all_omitted_when_as_list(gl):
9696
result = gl.http_list("/tests", as_list=False, all=True)
97-
assert isinstance(result, GitlabList)
97+
assert isinstance(result, gitlab.GitlabList)
9898

9999

100100
def test_gitlab_strip_base_url(gl_trailing):
@@ -114,7 +114,7 @@ def test_gitlab_pickability(gl):
114114
original_gl_objects = gl._objects
115115
pickled = pickle.dumps(gl)
116116
unpickled = pickle.loads(pickled)
117-
assert isinstance(unpickled, Gitlab)
117+
assert isinstance(unpickled, gitlab.Gitlab)
118118
assert hasattr(unpickled, "_objects")
119119
assert unpickled._objects == original_gl_objects
120120

@@ -124,24 +124,24 @@ def test_gitlab_token_auth(gl, callback=None):
124124
gl.auth()
125125
assert gl.user.username == username
126126
assert gl.user.id == user_id
127-
assert isinstance(gl.user, CurrentUser)
127+
assert isinstance(gl.user, gitlab.v4.objects.CurrentUser)
128128

129129

130130
def test_gitlab_default_url():
131-
gl = Gitlab()
132-
assert gl.url == DEFAULT_URL
131+
gl = gitlab.Gitlab()
132+
assert gl.url == gitlab.DEFAULT_URL
133133

134134

135135
@pytest.mark.parametrize(
136136
"args, kwargs, expected_url, expected_private_token, expected_oauth_token",
137137
[
138-
([], {}, DEFAULT_URL, None, None),
139-
([None, token], {}, DEFAULT_URL, token, None),
138+
([], {}, gitlab.DEFAULT_URL, None, None),
139+
([None, token], {}, gitlab.DEFAULT_URL, token, None),
140140
([localhost], {}, localhost, None, None),
141141
([localhost, token], {}, localhost, token, None),
142142
([localhost, None, token], {}, localhost, None, token),
143-
([], {"private_token": token}, DEFAULT_URL, token, None),
144-
([], {"oauth_token": token}, DEFAULT_URL, None, token),
143+
([], {"private_token": token}, gitlab.DEFAULT_URL, token, None),
144+
([], {"oauth_token": token}, gitlab.DEFAULT_URL, None, token),
145145
([], {"url": localhost}, localhost, None, None),
146146
([], {"url": localhost, "private_token": token}, localhost, token, None),
147147
([], {"url": localhost, "oauth_token": token}, localhost, None, token),
@@ -162,19 +162,19 @@ def test_gitlab_default_url():
162162
def test_gitlab_args_kwargs(
163163
args, kwargs, expected_url, expected_private_token, expected_oauth_token
164164
):
165-
gl = Gitlab(*args, **kwargs)
165+
gl = gitlab.Gitlab(*args, **kwargs)
166166
assert gl.url == expected_url
167167
assert gl.private_token == expected_private_token
168168
assert gl.oauth_token == expected_oauth_token
169169

170170

171171
def test_gitlab_from_config(default_config):
172172
config_path = default_config
173-
Gitlab.from_config("one", [config_path])
173+
gitlab.Gitlab.from_config("one", [config_path])
174174

175175

176176
def test_gitlab_subclass_from_config(default_config):
177-
class MyGitlab(Gitlab):
177+
class MyGitlab(gitlab.Gitlab):
178178
pass
179179

180180
config_path = default_config
@@ -185,10 +185,25 @@ class MyGitlab(Gitlab):
185185
@pytest.mark.parametrize(
186186
"kwargs,expected_agent",
187187
[
188-
({}, USER_AGENT),
188+
({}, gitlab.USER_AGENT),
189189
({"user_agent": "my-package/1.0.0"}, "my-package/1.0.0"),
190190
],
191191
)
192192
def test_gitlab_user_agent(kwargs, expected_agent):
193-
gl = Gitlab("http://localhost", **kwargs)
193+
gl = gitlab.Gitlab("http://localhost", **kwargs)
194194
assert gl.headers["User-Agent"] == expected_agent
195+
196+
197+
def test_gitlab_deprecated_const():
198+
with warnings.catch_warnings(record=True) as caught_warnings:
199+
gitlab.NO_ACCESS
200+
assert len(caught_warnings) == 1
201+
warning = caught_warnings[0]
202+
assert isinstance(warning.message, DeprecationWarning)
203+
message = str(caught_warnings[0].message)
204+
assert "deprecated" in message
205+
assert "gitlab.const.NO_ACCESS" in message
206+
207+
with warnings.catch_warnings(record=True) as caught_warnings:
208+
gitlab.const.NO_ACCESS
209+
assert len(caught_warnings) == 0

0 commit comments

Comments
 (0)