16
16
# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
17
18
18
import importlib
19
+ import textwrap
19
20
from types import ModuleType
20
21
from typing import Any , Dict , Iterable , NamedTuple , Optional , Tuple , Type
21
22
23
+ import gitlab
22
24
from gitlab import types as g_types
23
25
from gitlab .exceptions import GitlabParsingError
24
26
32
34
]
33
35
34
36
37
+ _URL_ATTRIBUTE_ERROR = (
38
+ f"https://python-gitlab.readthedocs.io/en/{ gitlab .__version__ } /"
39
+ f"faq.html#attribute-error-list"
40
+ )
41
+
42
+
35
43
class RESTObject (object ):
36
44
"""Represents an object built from server data.
37
45
@@ -45,13 +53,20 @@ class RESTObject(object):
45
53
46
54
_id_attr : Optional [str ] = "id"
47
55
_attrs : Dict [str , Any ]
56
+ _created_from_list : bool # Indicates if object was created from a list() action
48
57
_module : ModuleType
49
58
_parent_attrs : Dict [str , Any ]
50
59
_short_print_attr : Optional [str ] = None
51
60
_updated_attrs : Dict [str , Any ]
52
61
manager : "RESTManager"
53
62
54
- def __init__ (self , manager : "RESTManager" , attrs : Dict [str , Any ]) -> None :
63
+ def __init__ (
64
+ self ,
65
+ manager : "RESTManager" ,
66
+ attrs : Dict [str , Any ],
67
+ * ,
68
+ created_from_list : bool = False ,
69
+ ) -> None :
55
70
if not isinstance (attrs , dict ):
56
71
raise GitlabParsingError (
57
72
"Attempted to initialize RESTObject with a non-dictionary value: "
@@ -64,6 +79,7 @@ def __init__(self, manager: "RESTManager", attrs: Dict[str, Any]) -> None:
64
79
"_attrs" : attrs ,
65
80
"_updated_attrs" : {},
66
81
"_module" : importlib .import_module (self .__module__ ),
82
+ "_created_from_list" : created_from_list ,
67
83
}
68
84
)
69
85
self .__dict__ ["_parent_attrs" ] = self .manager .parent_attrs
@@ -106,8 +122,22 @@ def __getattr__(self, name: str) -> Any:
106
122
except KeyError :
107
123
try :
108
124
return self .__dict__ ["_parent_attrs" ][name ]
109
- except KeyError :
110
- raise AttributeError (name )
125
+ except KeyError as exc :
126
+ message = (
127
+ f"{ type (self ).__name__ !r} object has no attribute { name !r} "
128
+ )
129
+ if self ._created_from_list :
130
+ message = (
131
+ f"{ message } \n \n "
132
+ + textwrap .fill (
133
+ f"{ self .__class__ !r} was created via a list() call and "
134
+ f"only a subset of the data may be present. To ensure "
135
+ f"all data is present get the object using a "
136
+ f"get(object.id) call. For more details, see:"
137
+ )
138
+ + f"\n \n { _URL_ATTRIBUTE_ERROR } "
139
+ )
140
+ raise AttributeError (message ) from exc
111
141
112
142
def __setattr__ (self , name : str , value : Any ) -> None :
113
143
self .__dict__ ["_updated_attrs" ][name ] = value
@@ -229,7 +259,7 @@ def __next__(self) -> RESTObject:
229
259
230
260
def next (self ) -> RESTObject :
231
261
data = self ._list .next ()
232
- return self ._obj_cls (self .manager , data )
262
+ return self ._obj_cls (self .manager , data , created_from_list = True )
233
263
234
264
@property
235
265
def current_page (self ) -> int :
0 commit comments