1616# along with this program. If not, see <http://www.gnu.org/licenses/>.
1717
1818import importlib
19+ import textwrap
1920from types import ModuleType
2021from typing import Any , Dict , Iterable , NamedTuple , Optional , Tuple , Type
2122
23+ import gitlab
2224from gitlab import types as g_types
2325from gitlab .exceptions import GitlabParsingError
2426
3234]
3335
3436
37+ _URL_ATTRIBUTE_ERROR = (
38+ f"https://python-gitlab.readthedocs.io/en/{ gitlab .__version__ } /"
39+ f"faq.html#attribute-error-list"
40+ )
41+
42+
3543class RESTObject (object ):
3644 """Represents an object built from server data.
3745
@@ -45,13 +53,20 @@ class RESTObject(object):
4553
4654 _id_attr : Optional [str ] = "id"
4755 _attrs : Dict [str , Any ]
56+ _created_from_list : bool # Indicates if object was created from a list() action
4857 _module : ModuleType
4958 _parent_attrs : Dict [str , Any ]
5059 _short_print_attr : Optional [str ] = None
5160 _updated_attrs : Dict [str , Any ]
5261 manager : "RESTManager"
5362
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 :
5570 if not isinstance (attrs , dict ):
5671 raise GitlabParsingError (
5772 "Attempted to initialize RESTObject with a non-dictionary value: "
@@ -64,6 +79,7 @@ def __init__(self, manager: "RESTManager", attrs: Dict[str, Any]) -> None:
6479 "_attrs" : attrs ,
6580 "_updated_attrs" : {},
6681 "_module" : importlib .import_module (self .__module__ ),
82+ "_created_from_list" : created_from_list ,
6783 }
6884 )
6985 self .__dict__ ["_parent_attrs" ] = self .manager .parent_attrs
@@ -106,8 +122,22 @@ def __getattr__(self, name: str) -> Any:
106122 except KeyError :
107123 try :
108124 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
111141
112142 def __setattr__ (self , name : str , value : Any ) -> None :
113143 self .__dict__ ["_updated_attrs" ][name ] = value
@@ -229,7 +259,7 @@ def __next__(self) -> RESTObject:
229259
230260 def next (self ) -> RESTObject :
231261 data = self ._list .next ()
232- return self ._obj_cls (self .manager , data )
262+ return self ._obj_cls (self .manager , data , created_from_list = True )
233263
234264 @property
235265 def current_page (self ) -> int :
0 commit comments