Skip to content

Commit d438fe2

Browse files
committed
valuesresourceset object
1 parent 284d884 commit d438fe2

File tree

4 files changed

+150
-2
lines changed

4 files changed

+150
-2
lines changed

CHANGELOG.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ Changelog
2121
* Tracker - issues relation
2222
* IssueStatus - issues relation
2323

24+
- Added: Introduced a ``values()`` method in a ResourceSet which returns ValuesResourceSet — a
25+
ResourceSet subclass that returns dictionaries when used as an iterable, rather than resource-instance
26+
objects (see `docs <http://python-redmine.readthedocs.org/operations.html#filter>`__ for details)
2427
- Added: Introduced ``update()`` and ``delete()`` methods in a ResourceSet object which allow to
2528
bulk update or bulk delete all resources in a ResourceSet object (see `docs <http://python-redmine.
2629
readthedocs.org/operations.html#filter>`__ for details)

docs/operations.rst

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,8 @@ Returns a ResourceSet object that contains Resource objects filtered by some con
139139
140140
.. hint::
141141

142-
ResourceSet object provides 4 helper methods ``get()``, ``filter()``, ``update()`` and
143-
``delete()``:
142+
ResourceSet object provides 5 helper methods ``get()``, ``filter()``, ``update()``, ``delete()``
143+
and ``values()``:
144144

145145
* **get**. Returns a single resource from the ResourceSet by resource id:
146146

@@ -177,6 +177,34 @@ Returns a ResourceSet object that contains Resource objects filtered by some con
177177
178178
redmine.project.get('vacation').issues.delete()
179179
180+
* **values**. Returns a ValuesResourceSet — a ResourceSet subclass that returns dictionaries when
181+
used as an iterable, rather than resource-instance objects. Each of those dictionaries represents
182+
a resource, with the keys corresponding to the attribute names of resource objects. This example
183+
compares the dictionaries of values() with the normal resource objects:
184+
185+
.. versionadded:: 1.0.0
186+
187+
|
188+
189+
.. code-block:: python
190+
191+
>>> list(redmine.issue_status.all(limit=1))
192+
[<redmine.resources.IssueStatus #1 "New">]
193+
>>> list(redmine.issue_status.all(limit=1).values())
194+
[{'id': 1, 'is_default': True, 'name': 'New'}]
195+
196+
The values() method takes optional positional arguments, \*fields, which specify field names to
197+
which resource fields should be limited. If you specify the fields, each dictionary will contain
198+
only the field keys/values for the fields you specify. If you don’t specify the fields, each
199+
dictionary will contain a key and value for every field in the resource:
200+
201+
.. code-block:: python
202+
203+
>>> list(redmine.issue_status.all(limit=1).values())
204+
[{'id': 1, 'is_default': True, 'name': 'New'}]
205+
>>> list(redmine.issue_status.all(limit=1).values('id', 'name'))
206+
[{'id': 1, 'name': 'New'}]
207+
180208
ResourceSet object also provides some attributes:
181209
182210
* **limit**. What limit value was used to retrieve this ResourceSet:

redmine/resultsets.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ def delete(self):
5858

5959
return True
6060

61+
def values(self, *fields):
62+
"""Returns ValuesResourceSet object which represents Resource as a dictionary"""
63+
return ValuesResourceSet(self.manager, resources=self.resources, fields=fields)
64+
6165
@property
6266
def total_count(self):
6367
"""Returns total count of available resources, this is known only after ResourceSet evaluation"""
@@ -109,3 +113,70 @@ def __repr__(self):
109113
self.__class__.__name__,
110114
self.manager.resource_class.__name__
111115
)
116+
117+
118+
class ValuesResourceSet(ResourceSet):
119+
"""Represents a set of Redmine resources as dictionaries"""
120+
def __init__(self, manager, resources=None, fields=()):
121+
"""Accepts optional fields iterable which sets field names each resource will contain"""
122+
super(ValuesResourceSet, self).__init__(manager, resources)
123+
self.fields = fields
124+
self.resource_internal_id = 'title' if self.manager.resource_class.__name__ == 'WikiPage' else 'id'
125+
126+
def get(self, resource_id, default=None):
127+
"""Returns a single item from a ValuesResourceSet by resource id"""
128+
for resource in self:
129+
if int(resource_id) == resource[self.resource_internal_id]:
130+
return resource
131+
132+
return default
133+
134+
def filter(self, resource_ids):
135+
"""Returns a ValuesResourceSet with requested resource ids"""
136+
if not isinstance(resource_ids, (tuple, list)):
137+
raise ResourceSetFilterParamError
138+
139+
resources = []
140+
141+
for resource in self:
142+
if resource[self.resource_internal_id] in resource_ids:
143+
resources.append(resource)
144+
145+
return ValuesResourceSet(self.manager, resources=resources, fields=self.fields)
146+
147+
def update(self, **fields):
148+
"""Updates fields of all resources in a ValuesResourceSet with the given values"""
149+
resources = []
150+
151+
for resource in self:
152+
for field in fields:
153+
resource[field] = fields[field]
154+
155+
self.manager.update(resource[self.resource_internal_id], **fields)
156+
resources.append(resource)
157+
158+
return ValuesResourceSet(self.manager, resources=resources, fields=self.fields)
159+
160+
def delete(self):
161+
"""Deletes all resources in a ValuesResourceSet"""
162+
for resource in self:
163+
self.manager.delete(resource[self.resource_internal_id])
164+
165+
return True
166+
167+
def __iter__(self):
168+
"""Returns requested resources in a lazy fashion"""
169+
if self.resources is None:
170+
self._evaluate()
171+
172+
for resource in self.resources:
173+
if not self.fields:
174+
yield resource
175+
else:
176+
fields = {}
177+
178+
for field in resource:
179+
if field in self.fields:
180+
fields.update({field: resource[field]})
181+
182+
yield fields

tests/test_resultsets.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,52 @@ def test_update_method(self):
117117
def test_delete_method(self):
118118
self.assertEqual(self.redmine.project.all().delete(), True)
119119

120+
def test_values_method(self):
121+
from redmine.resultsets import ValuesResourceSet
122+
self.assertIsInstance(self.redmine.project.all().values(), ValuesResourceSet)
123+
124+
def test_values_resourceset_supports_iteration(self):
125+
projects = list(self.redmine.project.all().values())
126+
self.assertEqual(projects[0]['name'], 'Foo')
127+
self.assertEqual(projects[0]['identifier'], 'foo')
128+
self.assertEqual(projects[0]['id'], 1)
129+
self.assertEqual(projects[1]['name'], 'Bar')
130+
self.assertEqual(projects[1]['identifier'], 'bar')
131+
self.assertEqual(projects[1]['id'], 2)
132+
133+
def test_values_resourceset_supports_field_limits(self):
134+
projects = list(self.redmine.project.all().values('id'))
135+
self.assertEqual(projects[0]['id'], 1)
136+
self.assertRaises(KeyError, lambda: projects[0]['name'])
137+
self.assertEqual(projects[1]['id'], 2)
138+
self.assertRaises(KeyError, lambda: projects[1]['name'])
139+
140+
def test_values_resourceset_get_method_resource_found(self):
141+
projects = self.redmine.project.all().values().get(2)
142+
self.assertEqual(projects['id'], 2)
143+
144+
def test_values_resourceset_get_method_resource_not_found(self):
145+
projects = self.redmine.project.all().values().get(6)
146+
self.assertEqual(projects, None)
147+
148+
def test_values_resourceset_filter_method(self):
149+
projects = self.redmine.project.all().values().filter((1, 3))
150+
self.assertEqual(projects[0]['id'], 1)
151+
self.assertEqual(projects[1]['id'], 3)
152+
153+
def test_values_resourceset_update_method(self):
154+
projects = self.redmine.project.all().values().update(name='FooBar')
155+
self.assertEqual(projects[0]['name'], 'FooBar')
156+
self.assertEqual(projects[1]['name'], 'FooBar')
157+
self.assertEqual(projects[2]['name'], 'FooBar')
158+
159+
def test_values_resourceset_delete_method(self):
160+
self.assertEqual(self.redmine.project.all().values().delete(), True)
161+
162+
def test_values_resourceset_filter_param_exception(self):
163+
from redmine.exceptions import ResourceSetFilterParamError
164+
self.assertRaises(ResourceSetFilterParamError, lambda: self.redmine.project.all().values().filter(1))
165+
120166
def test_filter_param_exception(self):
121167
from redmine.exceptions import ResourceSetFilterParamError
122168
self.assertRaises(ResourceSetFilterParamError, lambda: self.redmine.project.all().filter(1))

0 commit comments

Comments
 (0)