Skip to content

Commit 563e167

Browse files
committed
Add 'bundle create' command
This requires API v1.2. Note that we don't update man pages in this change. That will be done separately. Signed-off-by: Stephen Finucane <stephen@that.guru>
1 parent 0174bb7 commit 563e167

File tree

5 files changed

+167
-11
lines changed

5 files changed

+167
-11
lines changed

git_pw/api.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
from git_pw import config
1818

1919
if 0: # noqa
20+
from typing import Any # noqa
21+
from typing import Callable # noqa
2022
from typing import Dict # noqa
2123
from typing import IO # noqa
2224
from typing import List # noqa
@@ -144,6 +146,23 @@ def _get(url, params=None, stream=False):
144146
return rsp
145147

146148

149+
def _post(url, data):
150+
# type: (str, dict) -> requests.Response
151+
"""Make POST request and handle errors."""
152+
LOG.debug('POST %s, data=%r', url, data)
153+
154+
try:
155+
rsp = requests.post(url, auth=_get_auth(), headers=_get_headers(),
156+
data=data)
157+
rsp.raise_for_status()
158+
except requests.exceptions.RequestException as exc:
159+
_handle_error('create', exc)
160+
161+
LOG.debug('Got response')
162+
163+
return rsp
164+
165+
147166
def _patch(url, data):
148167
# type: (str, dict) -> requests.Response
149168
"""Make PATCH request and handle errors."""
@@ -270,6 +289,25 @@ def detail(resource_type, resource_id, params=None):
270289
return _get(url, params, stream=False).json()
271290

272291

292+
def create(resource_type, data):
293+
# type: (str, dict) -> dict
294+
"""Create a new API resource.
295+
296+
POST /{resource}/
297+
298+
Arguments:
299+
resource_type: The resource endpoint name.
300+
params: Fields to update.
301+
302+
Returns:
303+
A dictionary representing the detailed view of a given resource.
304+
"""
305+
# NOTE(stephenfin): All resources must have a trailing '/'
306+
url = '/'.join([_get_server(), resource_type, ''])
307+
308+
return _post(url, data).json()
309+
310+
273311
def update(resource_type, resource_id, data):
274312
# type: (str, int, dict) -> dict
275313
"""Update a specific API resource.
@@ -290,6 +328,23 @@ def update(resource_type, resource_id, data):
290328
return _patch(url, data).json()
291329

292330

331+
def validate_minimum_version(min_version, msg):
332+
# type: (Tuple[int, int], str) -> Callable[[Any], Any]
333+
334+
def inner(f):
335+
@click.pass_context
336+
def new_func(ctx, *args, **kwargs):
337+
if version() < min_version:
338+
LOG.error(msg)
339+
sys.exit(1)
340+
341+
return ctx.invoke(f, *args, **kwargs)
342+
343+
return update_wrapper(new_func, f)
344+
345+
return inner
346+
347+
293348
def validate_multiple_filter_support(f):
294349

295350
@click.pass_context

git_pw/bundle.py

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -77,17 +77,7 @@ def download_cmd(bundle_id, output):
7777
LOG.info('Downloaded bundle to %s', path)
7878

7979

80-
@click.command(name='show')
81-
@utils.format_options
82-
@click.argument('bundle_id')
83-
def show_cmd(fmt, bundle_id):
84-
"""Show information about bundle.
85-
86-
Retrieve Patchwork metadata for a bundle.
87-
"""
88-
LOG.debug('Showing bundle: id=%s', bundle_id)
89-
90-
bundle = _get_bundle(bundle_id)
80+
def _show_bundle(bundle, fmt):
9181

9282
def _format_patch(patch):
9383
return '%-4d %s' % (patch.get('id'), patch.get('name'))
@@ -108,6 +98,21 @@ def _format_patch(patch):
10898
utils.echo(output, ['Property', 'Value'], fmt=fmt)
10999

110100

101+
@click.command(name='show')
102+
@utils.format_options
103+
@click.argument('bundle_id')
104+
def show_cmd(fmt, bundle_id):
105+
"""Show information about bundle.
106+
107+
Retrieve Patchwork metadata for a bundle.
108+
"""
109+
LOG.debug('Showing bundle: id=%s', bundle_id)
110+
111+
bundle = _get_bundle(bundle_id)
112+
113+
_show_bundle(bundle, fmt)
114+
115+
111116
@click.command(name='list')
112117
@click.option('--owner', metavar='OWNER', multiple=True,
113118
help='Show only bundles with these owners. Should be an email, '
@@ -162,3 +167,34 @@ def list_cmd(owner, limit, page, sort, fmt, headers, name):
162167
output[-1].append(item[idx])
163168

164169
utils.echo_via_pager(output, headers, fmt=fmt)
170+
171+
172+
@click.command(name='create')
173+
@click.option('--public/--private', default=False,
174+
help='Allow other users to view this bundle. If private, only '
175+
'you will be able to see this bundle.')
176+
@click.argument('name')
177+
@click.argument('patch_ids', type=click.INT, nargs=-1, required=True)
178+
@api.validate_minimum_version(
179+
(1, 2), 'Creating bundles is only supported from API version 1.2',
180+
)
181+
@utils.format_options
182+
def create_cmd(name, patch_ids, public, fmt):
183+
"""Create a bundle.
184+
185+
Create a bundle with the given NAME and patches from PATCH_ID.
186+
187+
Requires API version 1.2 or greater.
188+
"""
189+
LOG.debug('Create bundle: name=%s, patches=%s, public=%s',
190+
name, patch_ids, public)
191+
192+
data = [
193+
('name', name),
194+
('patches', patch_ids),
195+
('public', public),
196+
]
197+
198+
bundle = api.create('bundles', data)
199+
200+
_show_bundle(bundle, fmt)

git_pw/shell.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,4 +122,5 @@ def bundle():
122122
bundle.add_command(bundle_cmds.apply_cmd)
123123
bundle.add_command(bundle_cmds.show_cmd)
124124
bundle.add_command(bundle_cmds.download_cmd)
125+
bundle.add_command(bundle_cmds.create_cmd)
125126
bundle.add_command(bundle_cmds.list_cmd)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
features:
3+
- |
4+
The following ``bundle`` commands have been added:
5+
6+
- ``bundle create``
7+
8+
Together, these allow for creation, modification and deletion of bundles.
9+
Bundles are custom, user-defined groups of patches that can be used to keep
10+
patch lists, preserving order, for future inclusion in a tree.
11+
12+
These commands require API v1.2.

tests/test_bundle.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,3 +308,55 @@ def test_list_api_v1_1(self, mock_log, mock_echo, mock_index,
308308

309309
# We shouldn't see a warning about multiple versions either
310310
assert not mock_log.warning.called
311+
312+
313+
@mock.patch('git_pw.api.version', return_value=(1, 2))
314+
@mock.patch('git_pw.api.create')
315+
@mock.patch('git_pw.utils.echo_via_pager')
316+
class CreateTestCase(unittest.TestCase):
317+
318+
@staticmethod
319+
def _get_bundle(**kwargs):
320+
return ShowTestCase._get_bundle(**kwargs)
321+
322+
def test_create(self, mock_echo, mock_create, mock_version):
323+
"""Validate standard behavior."""
324+
325+
mock_create.return_value = self._get_bundle()
326+
327+
runner = CLIRunner()
328+
result = runner.invoke(bundle.create_cmd, ['hello', '1', '2'])
329+
330+
assert result.exit_code == 0, result
331+
mock_create.assert_called_once_with(
332+
'bundles',
333+
[('name', 'hello'), ('patches', (1, 2)), ('public', False)]
334+
)
335+
336+
def test_create_with_public(self, mock_echo, mock_create, mock_version):
337+
"""Validate behavior with --public option."""
338+
339+
mock_create.return_value = self._get_bundle()
340+
341+
runner = CLIRunner()
342+
result = runner.invoke(bundle.create_cmd, [
343+
'hello', '1', '2', '--public'])
344+
345+
assert result.exit_code == 0, result
346+
mock_create.assert_called_once_with(
347+
'bundles',
348+
[('name', 'hello'), ('patches', (1, 2)), ('public', True)]
349+
)
350+
351+
@mock.patch('git_pw.api.LOG')
352+
def test_create_api_v1_1(
353+
self, mock_log, mock_echo, mock_create, mock_version
354+
):
355+
356+
mock_version.return_value = (1, 1)
357+
358+
runner = CLIRunner()
359+
result = runner.invoke(bundle.create_cmd, ['hello', '1', '2'])
360+
361+
assert result.exit_code == 1, result
362+
assert mock_log.error.called

0 commit comments

Comments
 (0)