Codebase list python-pygerrit2 / bf3416f
Import python-pygerrit2_2.0.4.orig.tar.gz Filip Pytloun 6 years ago
29 changed file(s) with 2204 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 Alexander D. Kanevskiy <kad@kad.name>
1 Andrey Devyatkin <andrey.a.devyatkin@gmail.com>
2 Benjamin Foster <bfoster@phi.al>
3 Chad Horohoe <chadh@wikimedia.org>
4 Chris Packham <chris.packham@alliedtelesis.co.nz>
5 Christopher Zee <christopher.xs.zee@gmail.com>
6 David Pursehouse <david.pursehouse@gmail.com>
7 David Pursehouse <david.pursehouse@sonyericsson.com>
8 David Pursehouse <david.pursehouse@sonymobile.com>
9 Ernst Sjostrand <ernst.sjostrand@sonymobile.com>
10 Fabien Boucher <fabien.boucher@enovance.com>
11 Gabriel Féron <feron.gabriel@gmail.com>
12 George Peristerakis <gperiste@redhat.com>
13 Jens Andersen <jens.andersen@gmail.com>
14 Johannes Richter <johannes.richter@kernkonzept.com>
15 Justin Simon <jls5177@gmail.com>
16 Marcin Płonka <mplonka@gmail.com>
17 Nikki Heald <nicky@notnowlewis.com>
18 Peter Theckanath <peter.xa.theckanath@sonymobile.com>
19 Vasiliy Kleschov <vkleschov@cloudlinux.com>
20 Vineet Naik <vineet@helpshift.com>
21 dependabot[bot] <support@dependabot.com>
0 CHANGES
1 =======
2
3 2.0.4
4 -----
5
6 * Add missing docstrings to silence pydocstyle errors
7 * Replace pep257 with pydocstyle
8 * Fix up various pydocstyle warnings, mostly about spacing
9 * pylint: Remove useless subclassing/super() calls
10 * pylint: Don't iterate over range(len()), use enumerate()
11 * Minor pylint fixes, mostly spacing, indentation, capitalization, naming
12 * Bump pyflakes from 1.5.0 to 1.6.0
13 * Bump flake8 from 3.3.0 to 3.5.0
14 * Exclude docs from analysis
15 * Remove doc build; add generated docs folder
16
17 2.0.3
18 -----
19
20 * Allow requests up to version 2.18.4
21 * Fixes #11 Remove pypi downloads shields
22 * Allow to use requests version 2.18.1
23 * Fixes #10: Fix a bug preventing file upload in PUT and POST
24 * Fixes #7: Be less strict about requests version
25 * Upgrade requests to 2.16.0
26 * Adapt to removal of Digest authentication in Gerrit 2.14
27 * Update links to Gerrit 2.14
28 * Upgrade requests to 2.14.2
29 * Add support to return response code
30 * Fix docs folder exclusion in tox config
31 * Upgrade pyflakes and flake8 to latest versions
32 * Remove debug logs
33 * rest: use encoding from response instead of UTF-8
34 * Upgrade requests to 2.13.0
35 * Add back the ReStructured Text version of the README
36
37 2.0.2
38 -----
39
40 * Fix MANIFEST.in with correct README file name
41 * Upgrade flake8 to version 3.2.1
42 * Upgrade requests to 2.12.1
43 * Update Gerrit documentation link to latest major release
44 * Upgrade flake8 and pyflakes to latest versions
45 * Fix README reference in setup.cfg
46 * Upgrade requests to 2.11.1
47 * Convert README to markdown
48
49 2.0.1
50 -----
51
52 * Upgrade requests to version 2.11.0
53 * Revert "Don't use requests.session()"
54 * Upgrade requests to 2.10.0
55 * returning raw response for non-JSON Content-Type
56 * making use of requests' json argument
57 * merging request headers
58 * Remove redundant pylint suppressions
59 * Rewrite the README with better usage instructions
60 * Update prerequisites
61 * Add tox configuration
62 * Use flake8 instead of pep8
63 * GerritReview: dump json content with sorted keys
64 * Example: Don't use 'self' in query when not authenticated
65 * Python3 compatibility fixes
66
67 2.0.0
68 -----
69
70 * Remove SSH support and rename to pygerrit2
71 * Update Gerrit documentation link to latest version
72 * Update link to Gerrit home page
73
74 1.0.0
75 -----
76
77 * Use badges from shields.io
78 * Upgrade pyflakes to 1.0.0
79 * Upgrade PEP-8 to 1.7.0
80 * Include event name in UnhandledEvent's \_\_repr\_\_ output
81 * Upgrade Paramiko to 1.16.0
82 * Upgrade requests to 2.9.1
83 * Raise exception if the json content is invalid
84 * Add missing docstring for Change#from\_json
85 * Added from\_json method for Change model
86 * Updated add\_comment() to support line ranges
87
88 0.2.11
89 ------
90
91 * Upgrade paramiko to version 1.15.2
92 * Add keepalive support to GerritSSHClient
93
94 0.2.10
95 ------
96
97 * Don't use requests.session()
98 * Don't set Content-Type on PUT/POST if there is no body
99 * Add debug log of response content
100
101 0.2.9
102 -----
103
104 * Upgrade requests to 2.7.0
105 * Allow unicode characters in gerrit commands
106 * Update requests to 2.5.1
107 * Fix incorrect docstring
108 * Upgrade pyflakes to 0.8.1
109 * Be less strict about the pbr version used
110 * Restructure docstrings for better html output
111 * Bump pep8 to 1.5.7
112 * Bump paramiko to 1.15.1 and pycrypto to 2.6.1
113 * Bump requests to 2.4.3
114 * Fix documentation build
115 * Bump python requests version to 2.4.1
116
117 0.2.8
118 -----
119
120 * Add inline review support
121 * Replace deprecated assertEquals() with assertEqual()
122 * Update PEP257 to 0.2.4
123 * Update pyflakes to 0.7.3
124
125 0.2.7
126 -----
127
128 * Add message formatter class
129 * Upgrade requests to 2.3.0
130 * Fix paramiko link in the README file
131
132 0.2.6
133 -----
134
135 * Add support for ProxyCommand in GerritSSHClient
136 * Upgrade paramiko to 1.14.0
137 * Revert "Remove support for Gerrit over ssh"
138 * Update the README
139 * Upgdade requests to 2.2.1
140 * Include full description in the setup config
141 * Set Content-Type header on PUT and POST requests
142 * Set HTTP headers to reduce data transfer time
143 * Migrate setup to pbr
144 * Stop using pylint
145 * Remove support for Gerrit over ssh
146
147 0.2.5
148 -----
149
150 * Version 0.2.5
151 * Fix up usage of argparse in examples
152 * Update examples to use argparse instead of optparse
153 * Fix for hanging connections
154
155 0.2.4
156 -----
157
158 * Version 0.2.4
159 * Accept kwargs for request functions
160 * Correct Gerrit documentation link
161 * Add badges to the README
162 * Fix README markup to work properly on Gitlab
163
164 0.2.3
165 -----
166
167 * Version 0.2.3
168 * Use find\_packages() in setup
169
170 0.2.2
171 -----
172
173 * Bump version to 0.2.2
174 * Release notes for 0.2.2
175 * Add user in the Approval object
176 * Add support for CurrentPatchSet
177 * Include change status in Change object
178 * Always set sortkey in Change object
179 * Handle errors in REST API example
180 * Simplify REST API error handling
181 * Add username field in the account data
182 * Add method to run a gerrit command from the client
183 * Add method to get username and Gerrit version
184 * Fix NameError in REST API example
185 * Bump to requests 2.0.1 also in setup.py
186 * Bump requests version up to 2.0.1
187
188 0.2.1
189 -----
190
191 * Update change log for 0.2.1
192 * Document the HTTP password configuration for REST API usage
193 * Reword the prerequisites in the readme
194 * Separate prerequisites and configuration in the readme
195
196 0.2.0
197 -----
198
199 * Bump version to 0.2.0
200 * Update change log for 0.2.0
201 * Suppress pylint "Unable to import" error
202 * Suppress pylint warning about catching too general exception
203 * Fix pylint error in rest\_example.py
204 * Change abandoned and restored events don't have a patchset field
205 * Add myself to authors
206 * Add an authors list that contributors can add themselves to
207 * Fix indentation
208 * Ensure errors in json parsing doesn't leave everything in a broken state
209 * Reason is optional in abandon and restore changes
210 * Return sortKey from query result, to allow resuming query
211 * Update the README to mention the REST API
212 * Avoid busy loop when receiving incoming stream data
213 * Add more detailed examples of SSH interface usage in the README
214 * Update the README notes about ssh configuration
215 * Add support for Kerberos authentication in example script
216 * Allow client to disable SSL certificate verification
217 * Add REST API example
218 * Refactor the authentication handling
219 * Add MANIFEST to .gitignore
220 * Add method to build url from endpoint
221
222 0.1.1
223 -----
224
225 * Bump version to 0.1.1
226 * Add changelog
227 * Make get, put, post and delete REST methods public
228 * Fix #10: Allow to manually specify ssh username and port
229 * Completely refactor the stream event handling
230 * Add missing \_\_repr\_\_ methods on ErrorEvent and UnhandledEvent
231 * Fix initialisation of error event
232 * Fix #11: correct handling of \`identityfile\` in the ssh config
233 * Allow example script to continue if errors are received
234 * Fix #9: Add a bit more detail in the documentation
235 * Fix #8: Support the "topic-changed" stream event
236 * Fix #7: Support the "reviewer-added" stream event
237 * Fix #6: Support the "merge-failed" stream event
238 * Fix #5: Move json parsing and error handling into the event factory
239 * Improved logging in the example script
240 * Fix #3: Don't treat unhandled event types as errors
241 * Fix #4: Keep event's raw json data in the event object
242 * Add \_\_repr\_\_ methods on event and model classes
243 * Remove redundant \`exec\_command\` method
244 * Fix #2: Establish SSH connection in a thread-safe way
245 * Fix #1: Use select.select() instead of select.poll()
246 * Fix authentication setup
247 * Add handling of HTTP error status codes in responses
248 * Add support for HTTP digest authentication
249 * Initial implementation of Gerrit REST API interface
250 * Disable W0142 'Used \* or \*\* magic' in the Pylint configuration
251
252 0.1.0
253 -----
254
255 * Bump version to 0.1.0
256 * Add Python trove classifiers to the setup
257 * Include full license text in the license parameter of setup
258 * Add long description in the setup
259 * Add MANIFEST.in file
260 * Improve formatting in the README file
261 * Rename README to README.rst
262 * Add make target and rules to ensure environment setup tools are OK
263 * Add a make target to build the API documentation zip archive
264 * Don't put copyright and license headers in Python module docstrings
265 * Add a make target to build the source distribution
266 * Add a make target to check for clean git status
267 * Add a make target to check that the git is tagged properly
268 * Add generation of package documentation
269 * Separate test dependency installation into its own build target
270 * Separate environment intialisation into its own build target
271 * Make the shebangs consistent
272 * Add pylint check in the Makefile
273 * Fix pylint warnings
274 * Add PEP-257 conformance check in the Makefile
275 * Add a unit test to ensure dependency package version consistency
276 * Separate package dependencies from verification/test dependencies
277 * Bump paramiko dependency to version 1.11.0
278 * Fix relative imports
279 * Fix one more PEP-257 violation
280 * Add a \`clean\` target in the Makefile
281 * Add PEP-8 conformance check in the Makefile
282 * Add pyflakes check in the Makefile
283 * Add a Makefile to install dependencies and run unit tests
284 * Only allow strings as query terms and commands
285 * Don't hard code version in setup.py
286 * Include command in GerritCommandResult
287 * Don't open ssh connection until needed
288 * Fix UnboundLocalError when stream-event connection fails
289 * Add missing docstrings to conform to PEP-257
290 * Explicitly depend on paramiko v1.7.6
291 * Move requirements.txt to the root
292 * Add requirements file
293
294 0.0.7
295 -----
296
297 * Bump to version 0.0.7
298 * Better error message in example script
299 * Rename the readme and license files
300
301 0.0.6
302 -----
303
304 * Bump to version 0.0.6
305 * Better error handling in the example script
306 * Move license to its own file and add basic documentation
307 * Use correct URL in setup information
308 * Handle socket connection error in SSH client
309 * Python 2.6 style exception handling
310 * Add Eclipse and Pydev project files
311
312 0.0.5
313 -----
314
315 * Bump to version 0.0.5
316 * Revert "Basic thread-safeness in the SSH client"
317 * Encapsulate the SSH command results in a class
318 * Update pylint config to work with version 0.26
319 * Only add query result lines to returned data
320 * Handle JSON errors in query results
321 * Refactor getting the Gerrit version
322 * Reduce nested \`if\` blocks in stream handling
323 * Add pylint configuration file
324 * More error handling improvements in stream
325 * Handle exception when running ssh command
326 * Fix pylint warnings in stream.py
327 * Basic thread-safeness in the SSH client
328 * More exception handling in stream
329
330 0.0.4
331 -----
332
333 * Bump version to 0.0.4
334 * Fix hostname in unit tests
335 * Get Gerrit version during client initialisation
336 * Add query functionality
337 * Add \_\_str\_\_ on event base class
338 * Move SSH client from stream class to main client class
339 * Add license information
340 * Simplify the unit test structure
341
342 0.0.3
343 -----
344
345 * Bump version to 0.0.3
346 * Add an example of how the Gerrit client class is used
347 * Wait for thread to complete when stopping stream
348 * Handle errors when reading event stream
349 * Fix event registration from other module
350 * Refactor event stream handling to use SSH client
351 * Handle invalid port in ssh config
352 * Add GerritSSHClient
353 * Add initial stub of GerritClient class
354 * Inject event name into event classes with decorator
355 * Revert "GerritEventFactory should be a singleton"
356 * GerritEventFactory should be a singleton
357 * Pass input stream in the constructor of GerritStream
358 * Add support for topic name in change attribute
359 * Add event factory and refactor event dispatching
360 * Events unit tests should also test event dispatching
361 * Add helper methods for initialisation from json data
362 * Add setup.py
363
364 0.0.2
365 -----
366
367 * Tidy up docstrings to follow the PEP-257 docstring convention
368 * Refactor into submodules
369 * Add .settings to .gitignore
370 * Remove check for supported approval types
371
372 0.0.1
373 -----
374
375 * Add .gitignore
376 * Add Makefile and script for unit tests
377 * Add unit tests for event handling
378 * Reason is missing in ChangeRestoredEvent
379 * \`comment-added\` event can have multiple approvals
380 * Add support for the \`draft-published\` event
381 * Minor refactoring of unit tests
382 * Fixing PEP-8 and pylint warnings
383 * Python cleanup: inherit from object
384 * Undefined variable in GerritCommentAddedEvent
385 * Check for callable event handler on attach
386 * Minor refactoring and adding initial unit tests
387 * Initial version of class for handling Gerrit stream events
388 * Initial empty commit
0 The MIT License
1
2 Copyright 2011 Sony Ericsson Mobile Communications. All rights reserved.
3 Copyright 2012 Sony Mobile Communications. All rights reserved.
4
5 Permission is hereby granted, free of charge, to any person obtaining a copy
6 of this software and associated documentation files (the "Software"), to deal
7 in the Software without restriction, including without limitation the rights
8 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 copies of the Software, and to permit persons to whom the Software is
10 furnished to do so, subject to the following conditions:
11
12 The above copyright notice and this permission notice shall be included in
13 all copies or substantial portions of the Software.
14
15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 THE SOFTWARE.
0 include README.rst LICENSE requirements.txt
0 # The MIT License
1 #
2 # Copyright 2013 Sony Mobile Communications. All rights reserved.
3 #
4 # Permission is hereby granted, free of charge, to any person obtaining a copy
5 # of this software and associated documentation files (the "Software"), to deal
6 # in the Software without restriction, including without limitation the rights
7 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 # copies of the Software, and to permit persons to whom the Software is
9 # furnished to do so, subject to the following conditions:
10 #
11 # The above copyright notice and this permission notice shall be included in
12 # all copies or substantial portions of the Software.
13 #
14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 # THE SOFTWARE.
21
22 PWD := $(shell pwd)
23 VERSION := $(shell git describe)
24
25 VIRTUALENV := $(shell which virtualenv)
26 ifeq ($(wildcard $(VIRTUALENV)),)
27 $(error virtualenv must be available)
28 endif
29
30 PIP := $(shell which pip)
31 ifeq ($(wildcard $(PIP)),)
32 $(error pip must be available)
33 endif
34
35 REQUIRED_VIRTUALENV ?= 1.10
36 VIRTUALENV_OK := $(shell expr `virtualenv --version | \
37 cut -f2 -d' '` \>= $(REQUIRED_VIRTUALENV))
38
39 all: test
40
41 test: clean unittests pyflakes pep8 pydocstyle
42
43 sdist: valid-virtualenv test
44 bash -c "\
45 source ./pygerrit2env/bin/activate && \
46 python setup.py sdist"
47
48 valid-virtualenv:
49 ifeq ($(VIRTUALENV_OK),0)
50 $(error virtualenv version $(REQUIRED_VIRTUALENV) or higher is needed)
51 endif
52
53 pydocstyle: testenvsetup
54 bash -c "\
55 source ./pygerrit2env/bin/activate && \
56 git ls-files | grep \"\.py$$\" | grep -v docs | xargs pydocstyle"
57
58 pep8: testenvsetup
59 bash -c "\
60 source ./pygerrit2env/bin/activate && \
61 git ls-files | grep \"\.py$$\" | grep -v docs | xargs flake8 --max-line-length 80"
62
63 pyflakes: testenvsetup
64 bash -c "\
65 source ./pygerrit2env/bin/activate && \
66 git ls-files | grep \"\.py$$\" | grep -v docs | xargs pyflakes"
67
68 unittests: testenvsetup
69 bash -c "\
70 source ./pygerrit2env/bin/activate && \
71 python unittests.py"
72
73 testenvsetup: envsetup
74 bash -c "\
75 source ./pygerrit2env/bin/activate && \
76 pip install --upgrade -r test_requirements.txt"
77
78 docenvsetup: envsetup
79 bash -c "\
80 source ./pygerrit2env/bin/activate && \
81 pip install --upgrade -r doc_requirements.txt"
82
83 envsetup: envinit
84 bash -c "\
85 source ./pygerrit2env/bin/activate && \
86 pip install --upgrade -r requirements.txt"
87
88 envinit:
89 bash -c "[ -e ./pygerrit2env/bin/activate ] || virtualenv --system-site-packages ./pygerrit2env"
90
91 clean:
92 @find . -type f -name "*.pyc" -exec rm -f {} \;
93 @rm -rf pygerrit2env pygerrit2.egg-info build dist
0 Metadata-Version: 1.1
1 Name: pygerrit2
2 Version: 2.0.4
3 Summary: Client library for interacting with Gerrit's REST API
4 Home-page: https://github.com/dpursehouse/pygerrit2
5 Author: David Pursehouse
6 Author-email: david.pursehouse@gmail.com
7 License: The MIT License
8 Description-Content-Type: UNKNOWN
9 Description: Pygerrit2 - Client library for interacting with Gerrit Code Review's REST API
10 =============================================================================
11
12 .. image:: https://img.shields.io/pypi/v/pygerrit2.png
13
14 .. image:: https://img.shields.io/pypi/l/pygerrit2.png
15
16 Pygerrit2 provides a simple interface for clients to interact with
17 `Gerrit Code Review`_ via the REST API.
18
19 Prerequisites
20 -------------
21
22 Pygerrit2 is compatible with Python 2.6 and Python 2.7. Support for Python 3
23 is experimental.
24
25 Pygerrit2 depends on the `requests`_ library.
26
27
28 Installation
29 ------------
30
31 To install pygerrit2, simply::
32
33 $ pip install pygerrit2
34
35
36 Usage
37 -----
38
39 This simple example shows how to get the user's open changes. Authentication
40 to Gerrit is done via HTTP Basic authentication, using an explicitly given
41 username and password::
42
43 >>> from requests.auth import HTTPBasicAuth
44 >>> from pygerrit2.rest import GerritRestAPI
45 >>> auth = HTTPBasicAuth('username', 'password')
46 >>> rest = GerritRestAPI(url='http://review.example.net', auth=auth)
47 >>> changes = rest.get("/changes/?q=owner:self%20status:open")
48
49 Note that is is not necessary to add the ``/a/`` prefix on the endpoint
50 URLs. This is automatically added when the API is instantiated with an
51 authentication object.
52
53 If the user's HTTP username and password are defined in the ``.netrc``
54 file::
55
56 machine review.example.net login MyUsername password MyPassword
57
58 then it is possible to authenticate with those credentials::
59
60 >>> from pygerrit2.rest import GerritRestAPI
61 >>> from pygerrit2.rest.auth import HTTPBasicAuthFromNetrc
62 >>> url = 'http://review.example.net'
63 >>> auth = HTTPBasicAuthFromNetrc(url=url)
64 >>> rest = GerritRestAPI(url=url, auth=auth)
65 >>> changes = rest.get("/changes/?q=owner:self%20status:open")
66
67 Note that the HTTP password is not the same as the SSH password. For
68 instructions on how to obtain the HTTP password, refer to Gerrit's
69 `HTTP upload settings`_ documentation.
70
71 Also note that in Gerrit version 2.14, support for HTTP Digest authentication
72 was removed and only HTTP Basic authentication is supported. When using
73 pygerrit2 against an earlier Gerrit version, it may be necessary to replace
74 the `HTTPBasic...` classes with the corresponding `HTTPDigest...` versions.
75
76 Refer to the `example`_ script for a full working example.
77
78 Copyright and License
79 ---------------------
80
81 Copyright 2011 Sony Ericsson Mobile Communications. All rights reserved.
82
83 Copyright 2012 Sony Mobile Communications. All rights reserved.
84
85 Copyright 2016 David Pursehouse. All rights reserved.
86
87 Licensed under The MIT License. Please refer to the `LICENSE`_ file for full
88 license details.
89
90 .. _`Gerrit Code Review`: https://gerritcodereview.com/
91 .. _`requests`: https://github.com/kennethreitz/requests
92 .. _example: https://github.com/dpursehouse/pygerrit2/blob/master/example.py
93 .. _`HTTP upload settings`: https://gerrit-documentation.storage.googleapis.com/Documentation/2.14/user-upload.html#http
94 .. _LICENSE: https://github.com/dpursehouse/pygerrit2/blob/master/LICENSE
95
96
97 Keywords: gerrit
98 rest
99 http
100 Platform: UNKNOWN
101 Classifier: Development Status :: 3 - Alpha
102 Classifier: Environment :: Console
103 Classifier: Intended Audience :: Developers
104 Classifier: License :: OSI Approved :: MIT License
105 Classifier: Natural Language :: English
106 Classifier: Programming Language :: Python
107 Classifier: Programming Language :: Python :: 2.6
108 Classifier: Programming Language :: Python :: 2.7
0 # Pygerrit2 - Client library for interacting with Gerrit Code Review's REST API
1
2 ![Version](https://img.shields.io/pypi/v/pygerrit2.png)
3 ![License](https://img.shields.io/pypi/l/pygerrit2.png)
4
5 Pygerrit2 provides a simple interface for clients to interact with
6 [Gerrit Code Review][gerrit] via the REST API.
7
8 ## Prerequisites
9
10 Pygerrit2 is compatible with Python 2.6 and Python 2.7. Support for Python 3
11 is experimental.
12
13 Pygerrit2 depends on the [requests library][requests].
14
15
16 ## Installation
17
18 To install pygerrit2, simply:
19
20 ```bash
21 $ pip install pygerrit2
22 ```
23
24 ## Usage
25
26 This simple example shows how to get the user's open changes. Authentication
27 to Gerrit is done via HTTP Basic authentication, using an explicitly given
28 username and password:
29
30 ```python
31 from requests.auth import HTTPBasicAuth
32 from pygerrit2.rest import GerritRestAPI
33
34 auth = HTTPBasicAuth('username', 'password')
35 rest = GerritRestAPI(url='http://review.example.net', auth=auth)
36 changes = rest.get("/changes/?q=owner:self%20status:open")
37 ```
38
39 Note that is is not necessary to add the `/a/` prefix on the endpoint
40 URLs. This is automatically added when the API is instantiated with an
41 authentication object.
42
43 If the user's HTTP username and password are defined in the `.netrc`
44 file:
45
46 ```bash
47 machine review.example.net login MyUsername password MyPassword
48 ```
49
50 then it is possible to authenticate with those credentials:
51
52 ```python
53 from pygerrit2.rest import GerritRestAPI
54 from pygerrit2.rest.auth import HTTPBasicAuthFromNetrc
55
56 url = 'http://review.example.net'
57 auth = HTTPBasicAuthFromNetrc(url=url)
58 rest = GerritRestAPI(url=url, auth=auth)
59 changes = rest.get("/changes/?q=owner:self%20status:open")
60 ```
61
62 Note that the HTTP password is not the same as the SSH password. For
63 instructions on how to obtain the HTTP password, refer to Gerrit's
64 [HTTP upload settings documentation][settings].
65
66 Also note that in Gerrit version 2.14, support for HTTP Digest authentication
67 was removed and only HTTP Basic authentication is supported. When using
68 pygerrit2 against an earlier Gerrit version, it may be necessary to replace
69 the `HTTPBasic...` classes with the corresponding `HTTPDigest...` versions.
70
71 Refer to the [example script][example] for a full working example.
72
73
74 # Copyright and License
75
76 Copyright 2011 Sony Ericsson Mobile Communications. All rights reserved.
77
78 Copyright 2012 Sony Mobile Communications. All rights reserved.
79
80 Copyright 2016 David Pursehouse. All rights reserved.
81
82 Licensed under The MIT License. Please refer to the [LICENSE file][license]
83 for full license details.
84
85 [gerrit]: https://gerritcodereview.com/
86 [requests]: https://github.com/kennethreitz/requests
87 [example]: https://github.com/dpursehouse/pygerrit2/blob/master/example.py
88 [settings]: https://gerrit-documentation.storage.googleapis.com/Documentation/2.14/user-upload.html#http
89 [license]: https://github.com/dpursehouse/pygerrit2/blob/master/LICENSE
0 Pygerrit2 - Client library for interacting with Gerrit Code Review's REST API
1 =============================================================================
2
3 .. image:: https://img.shields.io/pypi/v/pygerrit2.png
4
5 .. image:: https://img.shields.io/pypi/l/pygerrit2.png
6
7 Pygerrit2 provides a simple interface for clients to interact with
8 `Gerrit Code Review`_ via the REST API.
9
10 Prerequisites
11 -------------
12
13 Pygerrit2 is compatible with Python 2.6 and Python 2.7. Support for Python 3
14 is experimental.
15
16 Pygerrit2 depends on the `requests`_ library.
17
18
19 Installation
20 ------------
21
22 To install pygerrit2, simply::
23
24 $ pip install pygerrit2
25
26
27 Usage
28 -----
29
30 This simple example shows how to get the user's open changes. Authentication
31 to Gerrit is done via HTTP Basic authentication, using an explicitly given
32 username and password::
33
34 >>> from requests.auth import HTTPBasicAuth
35 >>> from pygerrit2.rest import GerritRestAPI
36 >>> auth = HTTPBasicAuth('username', 'password')
37 >>> rest = GerritRestAPI(url='http://review.example.net', auth=auth)
38 >>> changes = rest.get("/changes/?q=owner:self%20status:open")
39
40 Note that is is not necessary to add the ``/a/`` prefix on the endpoint
41 URLs. This is automatically added when the API is instantiated with an
42 authentication object.
43
44 If the user's HTTP username and password are defined in the ``.netrc``
45 file::
46
47 machine review.example.net login MyUsername password MyPassword
48
49 then it is possible to authenticate with those credentials::
50
51 >>> from pygerrit2.rest import GerritRestAPI
52 >>> from pygerrit2.rest.auth import HTTPBasicAuthFromNetrc
53 >>> url = 'http://review.example.net'
54 >>> auth = HTTPBasicAuthFromNetrc(url=url)
55 >>> rest = GerritRestAPI(url=url, auth=auth)
56 >>> changes = rest.get("/changes/?q=owner:self%20status:open")
57
58 Note that the HTTP password is not the same as the SSH password. For
59 instructions on how to obtain the HTTP password, refer to Gerrit's
60 `HTTP upload settings`_ documentation.
61
62 Also note that in Gerrit version 2.14, support for HTTP Digest authentication
63 was removed and only HTTP Basic authentication is supported. When using
64 pygerrit2 against an earlier Gerrit version, it may be necessary to replace
65 the `HTTPBasic...` classes with the corresponding `HTTPDigest...` versions.
66
67 Refer to the `example`_ script for a full working example.
68
69 Copyright and License
70 ---------------------
71
72 Copyright 2011 Sony Ericsson Mobile Communications. All rights reserved.
73
74 Copyright 2012 Sony Mobile Communications. All rights reserved.
75
76 Copyright 2016 David Pursehouse. All rights reserved.
77
78 Licensed under The MIT License. Please refer to the `LICENSE`_ file for full
79 license details.
80
81 .. _`Gerrit Code Review`: https://gerritcodereview.com/
82 .. _`requests`: https://github.com/kennethreitz/requests
83 .. _example: https://github.com/dpursehouse/pygerrit2/blob/master/example.py
84 .. _`HTTP upload settings`: https://gerrit-documentation.storage.googleapis.com/Documentation/2.14/user-upload.html#http
85 .. _LICENSE: https://github.com/dpursehouse/pygerrit2/blob/master/LICENSE
0 # Minimal makefile for Sphinx documentation
1 #
2
3 # You can set these variables from the command line.
4 SPHINXOPTS =
5 SPHINXBUILD = python -msphinx
6 SPHINXPROJ = pygerrit2
7 SOURCEDIR = .
8 BUILDDIR = _build
9
10 # Put it first so that "make" without argument is like "make help".
11 help:
12 @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
13
14 .PHONY: help Makefile
15
16 # Catch-all target: route all unknown targets to Sphinx using the new
17 # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
18 %: Makefile
19 @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
0 # -*- coding: utf-8 -*-
1 #
2 # pygerrit2 documentation build configuration file, created by
3 # sphinx-quickstart on Sun Oct 22 17:32:32 2017.
4 #
5 # This file is execfile()d with the current directory set to its
6 # containing dir.
7 #
8 # Note that not all possible configuration values are present in this
9 # autogenerated file.
10 #
11 # All configuration values have a default; values that are commented out
12 # serve to show the default.
13
14 # If extensions (or modules to document with autodoc) are in another directory,
15 # add these directories to sys.path here. If the directory is relative to the
16 # documentation root, use os.path.abspath to make it absolute, like shown here.
17 #
18 # import os
19 # import sys
20 # sys.path.insert(0, os.path.abspath('.'))
21
22
23 # -- General configuration ------------------------------------------------
24
25 # If your documentation needs a minimal Sphinx version, state it here.
26 #
27 # needs_sphinx = '1.0'
28
29 # Add any Sphinx extension module names here, as strings. They can be
30 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
31 # ones.
32 extensions = ['sphinx.ext.autodoc']
33
34 # Add any paths that contain templates here, relative to this directory.
35 templates_path = ['_templates']
36
37 # The suffix(es) of source filenames.
38 # You can specify multiple suffix as a list of string:
39 #
40 # source_suffix = ['.rst', '.md']
41 source_suffix = '.rst'
42
43 # The master toctree document.
44 master_doc = 'index'
45
46 # General information about the project.
47 project = u'pygerrit2'
48 copyright = u'2017, David Pursehouse'
49 author = u'David Pursehouse'
50
51 # The version info for the project you're documenting, acts as replacement for
52 # |version| and |release|, also used in various other places throughout the
53 # built documents.
54 #
55 # The short X.Y version.
56 version = u''
57 # The full version, including alpha/beta/rc tags.
58 release = u''
59
60 # The language for content autogenerated by Sphinx. Refer to documentation
61 # for a list of supported languages.
62 #
63 # This is also used if you do content translation via gettext catalogs.
64 # Usually you set "language" from the command line for these cases.
65 language = None
66
67 # List of patterns, relative to source directory, that match files and
68 # directories to ignore when looking for source files.
69 # This patterns also effect to html_static_path and html_extra_path
70 exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
71
72 # The name of the Pygments (syntax highlighting) style to use.
73 pygments_style = 'sphinx'
74
75 # If true, `todo` and `todoList` produce output, else they produce nothing.
76 todo_include_todos = False
77
78
79 # -- Options for HTML output ----------------------------------------------
80
81 # The theme to use for HTML and HTML Help pages. See the documentation for
82 # a list of builtin themes.
83 #
84 html_theme = 'alabaster'
85
86 # Theme options are theme-specific and customize the look and feel of a theme
87 # further. For a list of options available for each theme, see the
88 # documentation.
89 #
90 # html_theme_options = {}
91
92 # Add any paths that contain custom static files (such as style sheets) here,
93 # relative to this directory. They are copied after the builtin static files,
94 # so a file named "default.css" will overwrite the builtin "default.css".
95 html_static_path = ['_static']
96
97 # Custom sidebar templates, must be a dictionary that maps document names
98 # to template names.
99 #
100 # This is required for the alabaster theme
101 # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars
102 html_sidebars = {
103 '**': [
104 'about.html',
105 'navigation.html',
106 'relations.html', # needs 'show_related': True theme option to display
107 'searchbox.html',
108 'donate.html',
109 ]
110 }
111
112
113 # -- Options for HTMLHelp output ------------------------------------------
114
115 # Output file base name for HTML help builder.
116 htmlhelp_basename = 'pygerrit2doc'
117
118
119 # -- Options for LaTeX output ---------------------------------------------
120
121 latex_elements = {
122 # The paper size ('letterpaper' or 'a4paper').
123 #
124 # 'papersize': 'letterpaper',
125
126 # The font size ('10pt', '11pt' or '12pt').
127 #
128 # 'pointsize': '10pt',
129
130 # Additional stuff for the LaTeX preamble.
131 #
132 # 'preamble': '',
133
134 # Latex figure (float) alignment
135 #
136 # 'figure_align': 'htbp',
137 }
138
139 # Grouping the document tree into LaTeX files. List of tuples
140 # (source start file, target name, title,
141 # author, documentclass [howto, manual, or own class]).
142 latex_documents = [
143 (master_doc, 'pygerrit2.tex', u'pygerrit2 Documentation',
144 u'David Pursehouse', 'manual'),
145 ]
146
147
148 # -- Options for manual page output ---------------------------------------
149
150 # One entry per manual page. List of tuples
151 # (source start file, name, description, authors, manual section).
152 man_pages = [
153 (master_doc, 'pygerrit2', u'pygerrit2 Documentation',
154 [author], 1)
155 ]
156
157
158 # -- Options for Texinfo output -------------------------------------------
159
160 # Grouping the document tree into Texinfo files. List of tuples
161 # (source start file, target name, title, author,
162 # dir menu entry, description, category)
163 texinfo_documents = [
164 (master_doc, 'pygerrit2', u'pygerrit2 Documentation',
165 author, 'pygerrit2', 'One line description of project.',
166 'Miscellaneous'),
167 ]
0 .. pygerrit2 documentation master file, created by
1 sphinx-quickstart on Sun Oct 22 17:32:32 2017.
2 You can adapt this file completely to your liking, but it should at least
3 contain the root `toctree` directive.
4
5 Welcome to pygerrit2's documentation!
6 =====================================
7
8 .. toctree::
9 :maxdepth: 2
10 :caption: Contents:
11
12
13
14 Indices and tables
15 ==================
16
17 * :ref:`genindex`
18 * :ref:`modindex`
19 * :ref:`search`
0 @ECHO OFF
1
2 pushd %~dp0
3
4 REM Command file for Sphinx documentation
5
6 if "%SPHINXBUILD%" == "" (
7 set SPHINXBUILD=python -msphinx
8 )
9 set SOURCEDIR=.
10 set BUILDDIR=_build
11 set SPHINXPROJ=pygerrit2
12
13 if "%1" == "" goto help
14
15 %SPHINXBUILD% >NUL 2>NUL
16 if errorlevel 9009 (
17 echo.
18 echo.The Sphinx module was not found. Make sure you have Sphinx installed,
19 echo.then set the SPHINXBUILD environment variable to point to the full
20 echo.path of the 'sphinx-build' executable. Alternatively you may add the
21 echo.Sphinx directory to PATH.
22 echo.
23 echo.If you don't have Sphinx installed, grab it from
24 echo.http://sphinx-doc.org/
25 exit /b 1
26 )
27
28 %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
29 goto end
30
31 :help
32 %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
33
34 :end
35 popd
0 #!/usr/bin/env python
1 # -*- coding: utf-8 -*-
2
3 # The MIT License
4 #
5 # Copyright 2013 Sony Mobile Communications. All rights reserved.
6 #
7 # Permission is hereby granted, free of charge, to any person obtaining a copy
8 # of this software and associated documentation files (the "Software"), to deal
9 # in the Software without restriction, including without limitation the rights
10 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 # copies of the Software, and to permit persons to whom the Software is
12 # furnished to do so, subject to the following conditions:
13 #
14 # The above copyright notice and this permission notice shall be included in
15 # all copies or substantial portions of the Software.
16 #
17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 # THE SOFTWARE.
24
25 """Example of using the Gerrit client REST API."""
26
27 import argparse
28 import logging
29 import sys
30
31 from requests.auth import HTTPBasicAuth, HTTPDigestAuth
32 from requests.exceptions import RequestException
33 try:
34 from requests_kerberos import HTTPKerberosAuth, OPTIONAL
35 _KERBEROS_SUPPORT = True
36 except ImportError:
37 _KERBEROS_SUPPORT = False
38
39 from pygerrit2.rest import GerritRestAPI
40 from pygerrit2.rest.auth import HTTPDigestAuthFromNetrc, HTTPBasicAuthFromNetrc
41
42
43 def _main():
44 descr = 'Send request using Gerrit HTTP API'
45 parser = argparse.ArgumentParser(
46 description=descr,
47 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
48 parser.add_argument('-g', '--gerrit-url', dest='gerrit_url',
49 required=True,
50 help='gerrit server url')
51 parser.add_argument('-b', '--basic-auth', dest='basic_auth',
52 action='store_true',
53 help='(deprecated) use basic auth instead of digest')
54 parser.add_argument('-d', '--digest-auth', dest='digest_auth',
55 action='store_true',
56 help='use digest auth instead of basic')
57 if _KERBEROS_SUPPORT:
58 parser.add_argument('-k', '--kerberos-auth', dest='kerberos_auth',
59 action='store_true',
60 help='use kerberos auth')
61 parser.add_argument('-u', '--username', dest='username',
62 help='username')
63 parser.add_argument('-p', '--password', dest='password',
64 help='password')
65 parser.add_argument('-n', '--netrc', dest='netrc',
66 action='store_true',
67 help='Use credentials from netrc')
68 parser.add_argument('-v', '--verbose', dest='verbose',
69 action='store_true',
70 help='enable verbose (debug) logging')
71
72 options = parser.parse_args()
73
74 level = logging.DEBUG if options.verbose else logging.INFO
75 logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s',
76 level=level)
77
78 if _KERBEROS_SUPPORT and options.kerberos_auth:
79 if options.username or options.password \
80 or options.basic_auth or options.netrc:
81 parser.error("--kerberos-auth may not be used together with "
82 "--username, --password, --basic-auth or --netrc")
83 auth = HTTPKerberosAuth(mutual_authentication=OPTIONAL)
84 elif options.username and options.password:
85 if options.netrc:
86 logging.warning("--netrc option ignored")
87 if options.digest_auth:
88 auth = HTTPDigestAuth(options.username, options.password)
89 else:
90 auth = HTTPBasicAuth(options.username, options.password)
91 elif options.netrc:
92 if options.digest_auth:
93 auth = HTTPDigestAuthFromNetrc(url=options.gerrit_url)
94 else:
95 auth = HTTPBasicAuthFromNetrc(url=options.gerrit_url)
96 else:
97 auth = None
98
99 rest = GerritRestAPI(url=options.gerrit_url, auth=auth)
100
101 try:
102 query = ["status:open"]
103 if auth:
104 query += ["owner:self"]
105 else:
106 query += ["limit:10"]
107 changes = rest.get("/changes/?q=%s" % "%20".join(query))
108 logging.info("%d changes", len(changes))
109 for change in changes:
110 logging.info(change['change_id'])
111 except RequestException as err:
112 logging.error("Error: %s", str(err))
113
114
115 if __name__ == "__main__":
116 sys.exit(_main())
0 # The MIT License
1 #
2 # Copyright 2012 Sony Mobile Communications. All rights reserved.
3 #
4 # Permission is hereby granted, free of charge, to any person obtaining a copy
5 # of this software and associated documentation files (the "Software"), to deal
6 # in the Software without restriction, including without limitation the rights
7 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 # copies of the Software, and to permit persons to whom the Software is
9 # furnished to do so, subject to the following conditions:
10 #
11 # The above copyright notice and this permission notice shall be included in
12 # all copies or substantial portions of the Software.
13 #
14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 # THE SOFTWARE.
21
22 """Module to interface with Gerrit."""
23
24
25 def from_json(json_data, key):
26 """Extract values from JSON data.
27
28 :arg dict json_data: The JSON data
29 :arg str key: Key to get data for.
30
31 :Returns: The value of `key` from `json_data`, or None if `json_data`
32 does not contain `key`.
33
34 """
35 if key in json_data:
36 return json_data[key]
37 return None
38
39
40 def escape_string(string):
41 """Escape a string for use in Gerrit commands.
42
43 :arg str string: The string to escape.
44
45 :returns: The string with necessary escapes and surrounding double quotes
46 so that it can be passed to any of the Gerrit commands that require
47 double-quoted strings.
48
49 """
50 result = string
51 result = result.replace('\\', '\\\\')
52 result = result.replace('"', '\\"')
53 return '"' + result + '"'
54
55
56 class GerritReviewMessageFormatter(object):
57 """Helper class to format review messages that are sent to Gerrit.
58
59 :arg str header: (optional) If specified, will be prepended as the first
60 paragraph of the output message.
61 :arg str footer: (optional) If specified, will be appended as the last
62 paragraph of the output message.
63
64 """
65
66 def __init__(self, header=None, footer=None):
67 """See class docstring."""
68 self.paragraphs = []
69 if header:
70 self.header = header.strip()
71 else:
72 self.header = ""
73 if footer:
74 self.footer = footer.strip()
75 else:
76 self.footer = ""
77
78 def append(self, data):
79 """Append the given `data` to the output.
80
81 :arg data: If a list, it is formatted as a bullet list with each
82 entry in the list being a separate bullet. Otherwise if it is a
83 string, the string is added as a paragraph.
84
85 :raises: ValueError if `data` is not a list or a string.
86
87 """
88 if not data:
89 return
90
91 if isinstance(data, list):
92 # First we need to clean up the data.
93 #
94 # Gerrit creates new bullet items when it gets newline characters
95 # within a bullet list paragraph, so unless we remove the newlines
96 # from the texts the resulting bullet list will contain multiple
97 # bullets and look crappy.
98 #
99 # We add the '*' character on the beginning of each bullet text in
100 # the next step, so we strip off any existing leading '*' that the
101 # caller has added, and then strip off any leading or trailing
102 # whitespace.
103 _items = [x.replace("\n", " ").strip().lstrip('*').strip()
104 for x in data]
105
106 # Create the bullet list only with the items that still have any
107 # text in them after cleaning up.
108 _paragraph = "\n".join(["* %s" % x for x in _items if x])
109 if _paragraph:
110 self.paragraphs.append(_paragraph)
111 elif isinstance(data, str):
112 _paragraph = data.strip()
113 if _paragraph:
114 self.paragraphs.append(_paragraph)
115 else:
116 raise ValueError('Data must be a list or a string')
117
118 def is_empty(self):
119 """Check if the formatter is empty.
120
121 :Returns: True if empty, i.e. no paragraphs have been added.
122
123 """
124 return not self.paragraphs
125
126 def format(self):
127 """Format the message parts to a string.
128
129 :Returns: A string of all the message parts separated into paragraphs,
130 with header and footer paragraphs if they were specified in the
131 constructor.
132
133 """
134 message = ""
135 if self.paragraphs:
136 if self.header:
137 message += (self.header + '\n\n')
138 message += "\n\n".join(self.paragraphs)
139 if self.footer:
140 message += ('\n\n' + self.footer)
141 return message
0 # The MIT License
1 #
2 # Copyright 2013 Sony Mobile Communications. All rights reserved.
3 #
4 # Permission is hereby granted, free of charge, to any person obtaining a copy
5 # of this software and associated documentation files (the "Software"), to deal
6 # in the Software without restriction, including without limitation the rights
7 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 # copies of the Software, and to permit persons to whom the Software is
9 # furnished to do so, subject to the following conditions:
10 #
11 # The above copyright notice and this permission notice shall be included in
12 # all copies or substantial portions of the Software.
13 #
14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 # THE SOFTWARE.
21
22 """Interface to the Gerrit REST API."""
23
24 import json
25 import logging
26 import requests
27
28 GERRIT_MAGIC_JSON_PREFIX = ")]}\'\n"
29 GERRIT_AUTH_SUFFIX = "/a"
30
31
32 def _decode_response(response):
33 """Strip off Gerrit's magic prefix and decode a response.
34
35 :returns:
36 Decoded JSON content as a dict, or raw text if content could not be
37 decoded as JSON.
38
39 :raises:
40 requests.HTTPError if the response contains an HTTP error status code.
41
42 """
43 content = response.content.strip()
44 if response.encoding:
45 content = content.decode(response.encoding)
46 response.raise_for_status()
47 content_type = response.headers.get('content-type', '')
48 if content_type.split(';')[0] != 'application/json':
49 return content
50 if content.startswith(GERRIT_MAGIC_JSON_PREFIX):
51 content = content[len(GERRIT_MAGIC_JSON_PREFIX):]
52 try:
53 return json.loads(content)
54 except ValueError:
55 logging.error('Invalid json content: %s', content)
56 raise
57
58
59 def _merge_dict(result, overrides):
60 """Deep-merge dictionaries.
61
62 :arg dict result: The resulting dictionary
63 :arg dict overrides: Dictionay being merged into the result
64
65 :returns:
66 The resulting dictionary
67
68 """
69 for key in overrides:
70 if (key in result and
71 isinstance(result[key], dict) and
72 isinstance(overrides[key], dict)):
73 _merge_dict(result[key], overrides[key])
74 else:
75 result[key] = overrides[key]
76 return result
77
78
79 class GerritRestAPI(object):
80 """Interface to the Gerrit REST API.
81
82 :arg str url: The full URL to the server, including the `http(s)://`
83 prefix. If `auth` is given, `url` will be automatically adjusted to
84 include Gerrit's authentication suffix.
85 :arg auth: (optional) Authentication handler. Must be derived from
86 `requests.auth.AuthBase`.
87 :arg boolean verify: (optional) Set to False to disable verification of
88 SSL certificates.
89
90 """
91
92 def __init__(self, url, auth=None, verify=True):
93 """See class docstring."""
94 headers = {'Accept': 'application/json',
95 'Accept-Encoding': 'gzip'}
96 self.kwargs = {'auth': auth,
97 'verify': verify,
98 'headers': headers}
99 self.url = url.rstrip('/')
100 self.session = requests.session()
101
102 if auth:
103 if not isinstance(auth, requests.auth.AuthBase):
104 raise ValueError('Invalid auth type; must be derived '
105 'from requests.auth.AuthBase')
106
107 if not self.url.endswith(GERRIT_AUTH_SUFFIX):
108 self.url += GERRIT_AUTH_SUFFIX
109 else:
110 if self.url.endswith(GERRIT_AUTH_SUFFIX):
111 self.url = self.url[: - len(GERRIT_AUTH_SUFFIX)]
112
113 if not self.url.endswith('/'):
114 self.url += '/'
115
116 def make_url(self, endpoint):
117 """Make the full url for the endpoint.
118
119 :arg str endpoint: The endpoint.
120
121 :returns:
122 The full url.
123
124 """
125 endpoint = endpoint.lstrip('/')
126 return self.url + endpoint
127
128 def get(self, endpoint, return_response=False, **kwargs):
129 """Send HTTP GET to the endpoint.
130
131 :arg str endpoint: The endpoint to send to.
132 :arg bool return_response: If true will also return the response
133
134 :returns:
135 JSON decoded result.
136
137 :raises:
138 requests.RequestException on timeout or connection error.
139
140 """
141 kwargs.update(self.kwargs.copy())
142 response = self.session.get(self.make_url(endpoint), **kwargs)
143
144 decoded_response = _decode_response(response)
145
146 if return_response:
147 return decoded_response, response
148 return decoded_response
149
150 def put(self, endpoint, return_response=False, **kwargs):
151 """Send HTTP PUT to the endpoint.
152
153 :arg str endpoint: The endpoint to send to.
154
155 :returns:
156 JSON decoded result.
157
158 :raises:
159 requests.RequestException on timeout or connection error.
160
161 """
162 args = {}
163 if ("data" in kwargs and isinstance(kwargs["data"], dict)) or \
164 "json" in kwargs:
165 _merge_dict(
166 args, {
167 "headers": {
168 "Content-Type": "application/json;charset=UTF-8"
169 }
170 }
171 )
172 _merge_dict(args, self.kwargs.copy())
173 _merge_dict(args, kwargs)
174 response = self.session.put(self.make_url(endpoint), **args)
175
176 decoded_response = _decode_response(response)
177
178 if return_response:
179 return decoded_response, response
180 return decoded_response
181
182 def post(self, endpoint, return_response=False, **kwargs):
183 """Send HTTP POST to the endpoint.
184
185 :arg str endpoint: The endpoint to send to.
186
187 :returns:
188 JSON decoded result.
189
190 :raises:
191 requests.RequestException on timeout or connection error.
192
193 """
194 args = {}
195 if ("data" in kwargs and isinstance(kwargs["data"], dict)) or \
196 "json" in kwargs:
197 _merge_dict(
198 args, {
199 "headers": {
200 "Content-Type": "application/json;charset=UTF-8"
201 }
202 }
203 )
204 _merge_dict(args, self.kwargs.copy())
205 _merge_dict(args, kwargs)
206 response = self.session.post(self.make_url(endpoint), **args)
207
208 decoded_response = _decode_response(response)
209
210 if return_response:
211 return decoded_response, response
212 return decoded_response
213
214 def delete(self, endpoint, return_response=False, **kwargs):
215 """Send HTTP DELETE to the endpoint.
216
217 :arg str endpoint: The endpoint to send to.
218
219 :returns:
220 JSON decoded result.
221
222 :raises:
223 requests.RequestException on timeout or connection error.
224
225 """
226 args = {}
227 if "data" in kwargs or "json" in kwargs:
228 _merge_dict(
229 args, {
230 "headers": {
231 "Content-Type": "application/json;charset=UTF-8"
232 }
233 }
234 )
235 _merge_dict(args, self.kwargs.copy())
236 _merge_dict(args, kwargs)
237 response = self.session.delete(self.make_url(endpoint), **args)
238
239 decoded_response = _decode_response(response)
240
241 if return_response:
242 return decoded_response, response
243 return decoded_response
244
245 def review(self, change_id, revision, review):
246 """Submit a review.
247
248 :arg str change_id: The change ID.
249 :arg str revision: The revision.
250 :arg str review: The review details as a :class:`GerritReview`.
251
252 :returns:
253 JSON decoded result.
254
255 :raises:
256 requests.RequestException on timeout or connection error.
257
258 """
259 endpoint = "changes/%s/revisions/%s/review" % (change_id, revision)
260 self.post(endpoint, data=str(review))
261
262
263 class GerritReview(object):
264 """Encapsulation of a Gerrit review.
265
266 :arg str message: (optional) Cover message.
267 :arg dict labels: (optional) Review labels.
268 :arg dict comments: (optional) Inline comments.
269
270 """
271
272 def __init__(self, message=None, labels=None, comments=None):
273 """See class docstring."""
274 self.message = message if message else ""
275 if labels:
276 if not isinstance(labels, dict):
277 raise ValueError("labels must be a dict.")
278 self.labels = labels
279 else:
280 self.labels = {}
281 if comments:
282 if not isinstance(comments, list):
283 raise ValueError("comments must be a list.")
284 self.comments = {}
285 self.add_comments(comments)
286 else:
287 self.comments = {}
288
289 def set_message(self, message):
290 """Set review cover message.
291
292 :arg str message: Cover message.
293
294 """
295 self.message = message
296
297 def add_labels(self, labels):
298 """Add labels.
299
300 :arg dict labels: Labels to add, for example
301
302 Usage::
303
304 add_labels({'Verified': 1,
305 'Code-Review': -1})
306
307 """
308 self.labels.update(labels)
309
310 def add_comments(self, comments):
311 """Add inline comments.
312
313 :arg dict comments: Comments to add.
314
315 Usage::
316
317 add_comments([{'filename': 'Makefile',
318 'line': 10,
319 'message': 'inline message'}])
320
321 add_comments([{'filename': 'Makefile',
322 'range': {'start_line': 0,
323 'start_character': 1,
324 'end_line': 0,
325 'end_character': 5},
326 'message': 'inline message'}])
327
328 """
329 for comment in comments:
330 if 'filename' and 'message' in list(comment.keys()):
331 msg = {}
332 if 'range' in list(comment.keys()):
333 msg = {"range": comment['range'],
334 "message": comment['message']}
335 elif 'line' in list(comment.keys()):
336 msg = {"line": comment['line'],
337 "message": comment['message']}
338 else:
339 continue
340 file_comment = {comment['filename']: [msg]}
341 if self.comments:
342 if comment['filename'] in list(self.comments.keys()):
343 self.comments[comment['filename']].append(msg)
344 else:
345 self.comments.update(file_comment)
346 else:
347 self.comments.update(file_comment)
348
349 def __str__(self):
350 """Return a string representation."""
351 review_input = {}
352 if self.message:
353 review_input.update({'message': self.message})
354 if self.labels:
355 review_input.update({'labels': self.labels})
356 if self.comments:
357 review_input.update({'comments': self.comments})
358 return json.dumps(review_input, sort_keys=True)
0 # The MIT License
1 #
2 # Copyright 2013 Sony Mobile Communications. All rights reserved.
3 #
4 # Permission is hereby granted, free of charge, to any person obtaining a copy
5 # of this software and associated documentation files (the "Software"), to deal
6 # in the Software without restriction, including without limitation the rights
7 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 # copies of the Software, and to permit persons to whom the Software is
9 # furnished to do so, subject to the following conditions:
10 #
11 # The above copyright notice and this permission notice shall be included in
12 # all copies or substantial portions of the Software.
13 #
14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 # THE SOFTWARE.
21
22 """Authentication handlers."""
23
24 from requests.auth import HTTPDigestAuth, HTTPBasicAuth
25 from requests.utils import get_netrc_auth
26
27
28 class HTTPDigestAuthFromNetrc(HTTPDigestAuth):
29 """HTTP Digest Auth with netrc credentials."""
30
31 def __init__(self, url):
32 """See class docstring."""
33 auth = get_netrc_auth(url)
34 if not auth:
35 raise ValueError("netrc missing or no credentials found in netrc")
36 username, password = auth
37 super(HTTPDigestAuthFromNetrc, self).__init__(username, password)
38
39
40 class HTTPBasicAuthFromNetrc(HTTPBasicAuth):
41 """HTTP Basic Auth with netrc credentials."""
42
43 def __init__(self, url):
44 """See class docstring."""
45 auth = get_netrc_auth(url)
46 if not auth:
47 raise ValueError("netrc missing or no credentials found in netrc")
48 username, password = auth
49 super(HTTPBasicAuthFromNetrc, self).__init__(username, password)
0 Metadata-Version: 1.1
1 Name: pygerrit2
2 Version: 2.0.4
3 Summary: Client library for interacting with Gerrit's REST API
4 Home-page: https://github.com/dpursehouse/pygerrit2
5 Author: David Pursehouse
6 Author-email: david.pursehouse@gmail.com
7 License: The MIT License
8 Description-Content-Type: UNKNOWN
9 Description: Pygerrit2 - Client library for interacting with Gerrit Code Review's REST API
10 =============================================================================
11
12 .. image:: https://img.shields.io/pypi/v/pygerrit2.png
13
14 .. image:: https://img.shields.io/pypi/l/pygerrit2.png
15
16 Pygerrit2 provides a simple interface for clients to interact with
17 `Gerrit Code Review`_ via the REST API.
18
19 Prerequisites
20 -------------
21
22 Pygerrit2 is compatible with Python 2.6 and Python 2.7. Support for Python 3
23 is experimental.
24
25 Pygerrit2 depends on the `requests`_ library.
26
27
28 Installation
29 ------------
30
31 To install pygerrit2, simply::
32
33 $ pip install pygerrit2
34
35
36 Usage
37 -----
38
39 This simple example shows how to get the user's open changes. Authentication
40 to Gerrit is done via HTTP Basic authentication, using an explicitly given
41 username and password::
42
43 >>> from requests.auth import HTTPBasicAuth
44 >>> from pygerrit2.rest import GerritRestAPI
45 >>> auth = HTTPBasicAuth('username', 'password')
46 >>> rest = GerritRestAPI(url='http://review.example.net', auth=auth)
47 >>> changes = rest.get("/changes/?q=owner:self%20status:open")
48
49 Note that is is not necessary to add the ``/a/`` prefix on the endpoint
50 URLs. This is automatically added when the API is instantiated with an
51 authentication object.
52
53 If the user's HTTP username and password are defined in the ``.netrc``
54 file::
55
56 machine review.example.net login MyUsername password MyPassword
57
58 then it is possible to authenticate with those credentials::
59
60 >>> from pygerrit2.rest import GerritRestAPI
61 >>> from pygerrit2.rest.auth import HTTPBasicAuthFromNetrc
62 >>> url = 'http://review.example.net'
63 >>> auth = HTTPBasicAuthFromNetrc(url=url)
64 >>> rest = GerritRestAPI(url=url, auth=auth)
65 >>> changes = rest.get("/changes/?q=owner:self%20status:open")
66
67 Note that the HTTP password is not the same as the SSH password. For
68 instructions on how to obtain the HTTP password, refer to Gerrit's
69 `HTTP upload settings`_ documentation.
70
71 Also note that in Gerrit version 2.14, support for HTTP Digest authentication
72 was removed and only HTTP Basic authentication is supported. When using
73 pygerrit2 against an earlier Gerrit version, it may be necessary to replace
74 the `HTTPBasic...` classes with the corresponding `HTTPDigest...` versions.
75
76 Refer to the `example`_ script for a full working example.
77
78 Copyright and License
79 ---------------------
80
81 Copyright 2011 Sony Ericsson Mobile Communications. All rights reserved.
82
83 Copyright 2012 Sony Mobile Communications. All rights reserved.
84
85 Copyright 2016 David Pursehouse. All rights reserved.
86
87 Licensed under The MIT License. Please refer to the `LICENSE`_ file for full
88 license details.
89
90 .. _`Gerrit Code Review`: https://gerritcodereview.com/
91 .. _`requests`: https://github.com/kennethreitz/requests
92 .. _example: https://github.com/dpursehouse/pygerrit2/blob/master/example.py
93 .. _`HTTP upload settings`: https://gerrit-documentation.storage.googleapis.com/Documentation/2.14/user-upload.html#http
94 .. _LICENSE: https://github.com/dpursehouse/pygerrit2/blob/master/LICENSE
95
96
97 Keywords: gerrit
98 rest
99 http
100 Platform: UNKNOWN
101 Classifier: Development Status :: 3 - Alpha
102 Classifier: Environment :: Console
103 Classifier: Intended Audience :: Developers
104 Classifier: License :: OSI Approved :: MIT License
105 Classifier: Natural Language :: English
106 Classifier: Programming Language :: Python
107 Classifier: Programming Language :: Python :: 2.6
108 Classifier: Programming Language :: Python :: 2.7
0 AUTHORS
1 ChangeLog
2 LICENSE
3 MANIFEST.in
4 Makefile
5 README.md
6 README.rst
7 example.py
8 requirements.txt
9 setup.cfg
10 setup.py
11 test_requirements.txt
12 tox.ini
13 unittests.py
14 docs/Makefile
15 docs/conf.py
16 docs/index.rst
17 docs/make.bat
18 pygerrit2/__init__.py
19 pygerrit2.egg-info/PKG-INFO
20 pygerrit2.egg-info/SOURCES.txt
21 pygerrit2.egg-info/dependency_links.txt
22 pygerrit2.egg-info/not-zip-safe
23 pygerrit2.egg-info/pbr.json
24 pygerrit2.egg-info/requires.txt
25 pygerrit2.egg-info/top_level.txt
26 pygerrit2/rest/__init__.py
27 pygerrit2/rest/auth.py
0 {"git_version": "5eb119b", "is_release": false}
0 pbr>=0.8.0
1 requests<=2.18.4,>=2.10.0
0 pbr>=0.8.0
1 requests>=2.10.0,<=2.18.4
0 [metadata]
1 name = pygerrit2
2 summary = Client library for interacting with Gerrit's REST API
3 author = David Pursehouse
4 author_email = david.pursehouse@gmail.com
5 home-page = https://github.com/dpursehouse/pygerrit2
6 license = The MIT License
7 description-file = README.rst
8 keywords =
9 gerrit
10 rest
11 http
12 classifiers =
13 Development Status :: 3 - Alpha
14 Environment :: Console
15 Intended Audience :: Developers
16 License :: OSI Approved :: MIT License
17 Natural Language :: English
18 Programming Language :: Python
19 Programming Language :: Python :: 2.6
20 Programming Language :: Python :: 2.7
21
22 [egg_info]
23 tag_build =
24 tag_date = 0
25
0 #!/usr/bin/env python
1 # -*- coding: utf-8 -*-
2
3 # The MIT License
4 #
5 # Copyright 2012 Sony Mobile Communications. All rights reserved.
6 #
7 # Permission is hereby granted, free of charge, to any person obtaining a copy
8 # of this software and associated documentation files (the "Software"), to deal
9 # in the Software without restriction, including without limitation the rights
10 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 # copies of the Software, and to permit persons to whom the Software is
12 # furnished to do so, subject to the following conditions:
13 #
14 # The above copyright notice and this permission notice shall be included in
15 # all copies or substantial portions of the Software.
16 #
17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 # THE SOFTWARE.
24
25 """Client library for interacting with Gerrit."""
26
27 import setuptools
28
29
30 def _main():
31 setuptools.setup(
32 packages=setuptools.find_packages(),
33 setup_requires=['pbr'],
34 pbr=True)
35
36
37 if __name__ == "__main__":
38 _main()
0 pydocstyle==2.1.1
1 flake8==3.5.0
2 pyflakes==1.6.0
0 [tox]
1 minversion = 2.1.1
2 envlist = pep8, py35, py27, py26
3
4 [testenv]
5 setenv = VIRTUAL_ENV={envdir}
6 SUBUNIT_FORMATTER=tee testr_subunit_log
7 OS_STDOUT_NOCAPTURE=False
8 LANG=en_US.UTF-8
9 usedevelop = True
10 install_command = pip install {opts} {packages}
11 deps = -r{toxinidir}/test_requirements.txt
12 commands = python unittests.py
13
14 [testenv:pep8]
15 commands = flake8
16
17 [testenv:pyflakes]
18 deps = pyflakes
19 commands = pyflakes pygerrit2 unittests.py example.py setup.py
20
21 [flake8]
22 max-line-length = 80
23 show-source = True
24 exclude = .venv,.tox,dist,docs,build,*.egg,.test,pygerrit2env
0 #!/usr/bin/env python
1 # -*- coding: utf-8 -*-
2
3 # The MIT License
4 #
5 # Copyright 2012 Sony Mobile Communications. All rights reserved.
6 #
7 # Permission is hereby granted, free of charge, to any person obtaining a copy
8 # of this software and associated documentation files (the "Software"), to deal
9 # in the Software without restriction, including without limitation the rights
10 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 # copies of the Software, and to permit persons to whom the Software is
12 # furnished to do so, subject to the following conditions:
13 #
14 # The above copyright notice and this permission notice shall be included in
15 # all copies or substantial portions of the Software.
16 #
17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 # THE SOFTWARE.
24
25 """Unit tests for the Pygerrit2 helper methods."""
26
27 import unittest
28
29 from pygerrit2 import GerritReviewMessageFormatter
30 from pygerrit2.rest import GerritReview, _merge_dict
31
32 EXPECTED_TEST_CASE_FIELDS = ['header', 'footer', 'paragraphs', 'result']
33
34
35 TEST_CASES = [
36 {'header': None,
37 'footer': None,
38 'paragraphs': [],
39 'result': ""},
40 {'header': "Header",
41 'footer': "Footer",
42 'paragraphs': [],
43 'result': ""},
44 {'header': None,
45 'footer': None,
46 'paragraphs': ["Test"],
47 'result': "Test"},
48 {'header': None,
49 'footer': None,
50 'paragraphs': ["Test", "Test"],
51 'result': "Test\n\nTest"},
52 {'header': "Header",
53 'footer': None,
54 'paragraphs': ["Test"],
55 'result': "Header\n\nTest"},
56 {'header': "Header",
57 'footer': None,
58 'paragraphs': ["Test", "Test"],
59 'result': "Header\n\nTest\n\nTest"},
60 {'header': "Header",
61 'footer': "Footer",
62 'paragraphs': ["Test", "Test"],
63 'result': "Header\n\nTest\n\nTest\n\nFooter"},
64 {'header': "Header",
65 'footer': "Footer",
66 'paragraphs': [["One"]],
67 'result': "Header\n\n* One\n\nFooter"},
68 {'header': "Header",
69 'footer': "Footer",
70 'paragraphs': [["One", "Two"]],
71 'result': "Header\n\n* One\n* Two\n\nFooter"},
72 {'header': "Header",
73 'footer': "Footer",
74 'paragraphs': ["Test", ["One"], "Test"],
75 'result': "Header\n\nTest\n\n* One\n\nTest\n\nFooter"},
76 {'header': "Header",
77 'footer': "Footer",
78 'paragraphs': ["Test", ["One", "Two"], "Test"],
79 'result': "Header\n\nTest\n\n* One\n* Two\n\nTest\n\nFooter"},
80 {'header': "Header",
81 'footer': "Footer",
82 'paragraphs': ["Test", "Test", ["One"]],
83 'result': "Header\n\nTest\n\nTest\n\n* One\n\nFooter"},
84 {'header': None,
85 'footer': None,
86 'paragraphs': [["* One", "* Two"]],
87 'result': "* One\n* Two"},
88 {'header': None,
89 'footer': None,
90 'paragraphs': [["* One ", " * Two "]],
91 'result': "* One\n* Two"},
92 {'header': None,
93 'footer': None,
94 'paragraphs': [["*", "*"]],
95 'result': ""},
96 {'header': None,
97 'footer': None,
98 'paragraphs': [["", ""]],
99 'result': ""},
100 {'header': None,
101 'footer': None,
102 'paragraphs': [[" ", " "]],
103 'result': ""},
104 {'header': None,
105 'footer': None,
106 'paragraphs': [["* One", " ", "* Two"]],
107 'result': "* One\n* Two"}]
108
109
110 class TestMergeDict(unittest.TestCase):
111 """Tests for the `_merge_dict` method."""
112
113 def test_merge_into_empty_dict(self):
114 """Test merging into an empty dict."""
115 dct = {}
116 _merge_dict(dct, {'a': 1, 'b': 2})
117 self.assertEqual(dct, {'a': 1, 'b': 2})
118
119 def test_merge_flat(self):
120 """Test merging a flat dict."""
121 dct = {'c': 3}
122 _merge_dict(dct, {'a': 1, 'b': 2})
123 self.assertEqual(dct, {'a': 1, 'b': 2, 'c': 3})
124
125 def test_merge_with_override(self):
126 """Test merging a dict and overriding values."""
127 dct = {'a': 1}
128 _merge_dict(dct, {'a': 0, 'b': 2})
129 self.assertEqual(dct, {'a': 0, 'b': 2})
130
131 def test_merge_two_levels(self):
132 """Test merging a dict with two levels."""
133 dct = {
134 'a': {
135 'A': 1,
136 'AA': 2,
137 },
138 'b': {
139 'B': 1,
140 'BB': 2,
141 },
142 }
143 overrides = {
144 'a': {
145 'AAA': 3,
146 },
147 'b': {
148 'BBB': 3,
149 },
150 }
151 _merge_dict(dct, overrides)
152 self.assertEqual(
153 dct,
154 {
155 'a': {
156 'A': 1,
157 'AA': 2,
158 'AAA': 3,
159 },
160 'b': {
161 'B': 1,
162 'BB': 2,
163 'BBB': 3,
164 },
165 }
166 )
167
168
169 class TestGerritReviewMessageFormatter(unittest.TestCase):
170 """Test that the GerritReviewMessageFormatter class behaves properly."""
171
172 def _check_test_case_fields(self, test_case, i):
173 for field in EXPECTED_TEST_CASE_FIELDS:
174 self.assertTrue(field in test_case,
175 "field '%s' not present in test case #%d" %
176 (field, i))
177 self.assertTrue(
178 isinstance(test_case['paragraphs'], list),
179 "'paragraphs' field is not a list in test case #%d" % i)
180
181 def test_is_empty(self):
182 """Test if message is empty for missing header and footer."""
183 fmt = GerritReviewMessageFormatter(header=None, footer=None)
184 self.assertTrue(fmt.is_empty())
185 fmt.append(['test'])
186 self.assertFalse(fmt.is_empty())
187
188 def test_message_formatting(self):
189 """Test message formatter for different test cases."""
190 for i, test_case in enumerate(TEST_CASES):
191 self._check_test_case_fields(test_case, i)
192 fmt = GerritReviewMessageFormatter(header=test_case['header'],
193 footer=test_case['footer'])
194 for paragraph in test_case['paragraphs']:
195 fmt.append(paragraph)
196 msg = fmt.format()
197 self.assertEqual(msg, test_case['result'],
198 "Formatted message does not match expected "
199 "result in test case #%d:\n[%s]" % (i, msg))
200
201
202 class TestGerritReview(unittest.TestCase):
203 """Test that the GerritReview class behaves properly."""
204
205 def test_str(self):
206 """Test for str function."""
207 obj = GerritReview()
208 self.assertEqual(str(obj), '{}')
209
210 obj2 = GerritReview(labels={'Verified': 1, 'Code-Review': -1})
211 self.assertEqual(
212 str(obj2),
213 '{"labels": {"Code-Review": -1, "Verified": 1}}')
214
215 obj3 = GerritReview(comments=[{'filename': 'Makefile',
216 'line': 10, 'message': 'test'}])
217 self.assertEqual(
218 str(obj3),
219 '{"comments": {"Makefile": [{"line": 10, "message": "test"}]}}')
220
221 obj4 = GerritReview(labels={'Verified': 1, 'Code-Review': -1},
222 comments=[{'filename': 'Makefile', 'line': 10,
223 'message': 'test'}])
224 self.assertEqual(
225 str(obj4),
226 '{"comments": {"Makefile": [{"line": 10, "message": "test"}]},'
227 ' "labels": {"Code-Review": -1, "Verified": 1}}')
228
229 obj5 = GerritReview(comments=[
230 {'filename': 'Makefile', 'line': 15, 'message': 'test'},
231 {'filename': 'Make', 'line': 10, 'message': 'test1'}
232 ])
233 self.assertEqual(
234 str(obj5),
235 '{"comments": {"Make": [{"line": 10, "message": "test1"}],'
236 ' "Makefile": [{"line": 15, "message": "test"}]}}')
237
238
239 if __name__ == '__main__':
240 unittest.main()