Skip to content

Commit 95afe66

Browse files
committed
Implementing resource_manager.Client.list_projects().
1 parent 078caf5 commit 95afe66

File tree

2 files changed

+215
-2
lines changed

2 files changed

+215
-2
lines changed

gcloud/resource_manager/client.py

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,67 @@ def project(self, project_id, name=None, labels=None):
7575
return Project(project_id=project_id,
7676
client=self, name=name, labels=labels)
7777

78+
def list_projects(self, filter_params=None, page_size=None):
79+
"""List the projects visible to this client.
80+
81+
Example::
82+
83+
>>> from gcloud import resource_manager
84+
>>> client = resource_manager.Client()
85+
>>> for project in client.list_projects():
86+
... print project.project_id
87+
88+
List all projects with label ``'environment'`` set to ``'prod'``
89+
(filtering by labels)::
90+
91+
>>> from gcloud import resource_manager
92+
>>> client = resource_manager.Client()
93+
>>> env_filter = {'labels.environment': 'prod'}
94+
>>> for project in client.list_projects(env_filter):
95+
... print project.project_id
96+
97+
See:
98+
https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects/list
99+
100+
Complete filtering example::
101+
102+
>>> project_filter = { # Return projects with...
103+
... 'name': 'My Project', # name set to 'My Project'.
104+
... 'id': 'my-project-id', # id set to 'my-project-id'.
105+
... 'labels.stage': 'prod', # the label 'stage' set to 'prod'
106+
... 'labels.color': '*' # a label 'color' set to anything.
107+
... }
108+
>>> client.list_projects(project_filter)
109+
110+
:type filter_params: dict
111+
:param filter_params: (Optional) A dictionary of filter options where
112+
each key is a property to filter on, and each
113+
value is the (case-insensitive) value to check
114+
(or the glob ``*`` to check for existence of the
115+
property). See the example above for more
116+
details.
117+
118+
:type page_size: int
119+
:param page_size: (Optional) Maximum number of projects to return in a
120+
single page. If not passed, defaults to a value set
121+
by the API.
122+
123+
:rtype: :class:`_ProjectIterator`
124+
:returns: A project iterator. The iterator will make multiple API
125+
requests if you continue iterating and there are more
126+
pages of results. Each item returned will be a.
127+
:class:`.Project`.
128+
"""
129+
extra_params = {}
130+
131+
if page_size is not None:
132+
extra_params['pageSize'] = page_size
133+
134+
if filter_params is not None:
135+
extra_params['filter'] = filter_params
136+
137+
return _ProjectIterator(self, extra_params=extra_params)
138+
78139

79140
class _ProjectIterator(Iterator):
80141
"""An iterator over a list of Project resources.
@@ -86,8 +147,9 @@ class _ProjectIterator(Iterator):
86147
:type client: :class:`gcloud.resource_manager.client.Client`
87148
:param client: The client to use for making connections.
88149
89-
:type extra_params: dict or :data:`NoneType <types.NoneType>`
90-
:param extra_params: Extra query string parameters for the API call.
150+
:type extra_params: dict
151+
:param extra_params: (Optional) Extra query string parameters for
152+
the API call.
91153
"""
92154

93155
def __init__(self, client, extra_params=None):

gcloud/resource_manager/test_client.py

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,145 @@ def test_project_factory(self):
104104
self.assertEqual(project.name, name)
105105
self.assertEqual(project.labels, labels)
106106

107+
def test_list_projects_return_type(self):
108+
from gcloud.resource_manager.client import _ProjectIterator
109+
110+
credentials = _Credentials()
111+
client = self._makeOne(credentials=credentials)
112+
# Patch the connection with one we can easily control.
113+
client.connection = _Connection({})
114+
115+
results = client.list_projects()
116+
self.assertIsInstance(results, _ProjectIterator)
117+
118+
def test_list_projects_no_paging(self):
119+
credentials = _Credentials()
120+
client = self._makeOne(credentials=credentials)
121+
122+
PROJECT_ID = 'project-id'
123+
PROJECT_NUMBER = 1
124+
STATUS = 'ACTIVE'
125+
PROJECTS_RESOURCE = {
126+
'projects': [
127+
{
128+
'projectId': PROJECT_ID,
129+
'projectNumber': PROJECT_NUMBER,
130+
'lifecycleState': STATUS,
131+
},
132+
],
133+
}
134+
# Patch the connection with one we can easily control.
135+
client.connection = _Connection(PROJECTS_RESOURCE)
136+
# Make sure there will be no paging.
137+
self.assertFalse('nextPageToken' in PROJECTS_RESOURCE)
138+
139+
results = list(client.list_projects())
140+
141+
project, = results
142+
self.assertEqual(project.project_id, PROJECT_ID)
143+
self.assertEqual(project.number, PROJECT_NUMBER)
144+
self.assertEqual(project.status, STATUS)
145+
146+
def test_list_projects_with_paging(self):
147+
credentials = _Credentials()
148+
client = self._makeOne(credentials=credentials)
149+
150+
PROJECT_ID1 = 'project-id'
151+
PROJECT_NUMBER1 = 1
152+
STATUS = 'ACTIVE'
153+
TOKEN = 'next-page-token'
154+
FIRST_PROJECTS_RESOURCE = {
155+
'projects': [
156+
{
157+
'projectId': PROJECT_ID1,
158+
'projectNumber': PROJECT_NUMBER1,
159+
'lifecycleState': STATUS,
160+
},
161+
],
162+
'nextPageToken': TOKEN,
163+
}
164+
PROJECT_ID2 = 'project-id-2'
165+
PROJECT_NUMBER2 = 42
166+
SECOND_PROJECTS_RESOURCE = {
167+
'projects': [
168+
{
169+
'projectId': PROJECT_ID2,
170+
'projectNumber': PROJECT_NUMBER2,
171+
'lifecycleState': STATUS,
172+
},
173+
],
174+
}
175+
# Patch the connection with one we can easily control.
176+
client.connection = _Connection(FIRST_PROJECTS_RESOURCE,
177+
SECOND_PROJECTS_RESOURCE)
178+
179+
# Page size = 1 with two response means we'll have two requests.
180+
results = list(client.list_projects(page_size=1))
181+
182+
# Check that the results are as expected.
183+
project1, project2 = results
184+
self.assertEqual(project1.project_id, PROJECT_ID1)
185+
self.assertEqual(project1.number, PROJECT_NUMBER1)
186+
self.assertEqual(project1.status, STATUS)
187+
self.assertEqual(project2.project_id, PROJECT_ID2)
188+
self.assertEqual(project2.number, PROJECT_NUMBER2)
189+
self.assertEqual(project2.status, STATUS)
190+
191+
# Check that two requests were required since page_size=1.
192+
request1, request2 = client.connection._requested
193+
self.assertEqual(request1, {
194+
'path': '/projects',
195+
'method': 'GET',
196+
'query_params': {
197+
'pageSize': 1,
198+
},
199+
})
200+
self.assertEqual(request2, {
201+
'path': '/projects',
202+
'method': 'GET',
203+
'query_params': {
204+
'pageSize': 1,
205+
'pageToken': TOKEN,
206+
},
207+
})
208+
209+
def test_list_projects_with_filter(self):
210+
credentials = _Credentials()
211+
client = self._makeOne(credentials=credentials)
212+
213+
PROJECT_ID = 'project-id'
214+
PROJECT_NUMBER = 1
215+
STATUS = 'ACTIVE'
216+
PROJECTS_RESOURCE = {
217+
'projects': [
218+
{
219+
'projectId': PROJECT_ID,
220+
'projectNumber': PROJECT_NUMBER,
221+
'lifecycleState': STATUS,
222+
},
223+
],
224+
}
225+
# Patch the connection with one we can easily control.
226+
client.connection = _Connection(PROJECTS_RESOURCE)
227+
228+
FILTER_PARAMS = {'id': 'project-id'}
229+
results = list(client.list_projects(filter_params=FILTER_PARAMS))
230+
231+
project, = results
232+
self.assertEqual(project.project_id, PROJECT_ID)
233+
self.assertEqual(project.number, PROJECT_NUMBER)
234+
self.assertEqual(project.status, STATUS)
235+
236+
# Check that the filter made it in the request.
237+
request, = client.connection._requested
238+
self.assertEqual(request, {
239+
'path': '/projects',
240+
'method': 'GET',
241+
'query_params': {
242+
'filter': FILTER_PARAMS,
243+
},
244+
})
245+
107246

108247
class _Credentials(object):
109248

@@ -116,3 +255,15 @@ def create_scoped_required():
116255
def create_scoped(self, scope):
117256
self._scopes = scope
118257
return self
258+
259+
260+
class _Connection(object):
261+
262+
def __init__(self, *responses):
263+
self._responses = responses
264+
self._requested = []
265+
266+
def api_request(self, **kw):
267+
self._requested.append(kw)
268+
response, self._responses = self._responses[0], self._responses[1:]
269+
return response

0 commit comments

Comments
 (0)