diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4509f114..7af43ada 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,45 +1,83 @@ # How to contribute -Thank you in your interest in the podman-py project. We need your help to make it successful. +Thank you in your interest in the podman-py project. We need your help to make +it successful. You may also want to look at: - - https://github.com/containers/libpod - - https://podman.readthedocs.io/en/latest/Reference.html - + +- [podman](https://github.com/containers/podman) +- [podman Reference](https://podman.readthedocs.io/en/latest/Reference.html) + ## Reporting Issues -Before reporting an issue, check our backlog of open issues to see if someone else has already reported it. If so, feel free to add your scenario, or additional information, to the discussion. Or simply "subscribe" to it to be notified when it is updated. -If you find a new issue with the project we'd love to hear about it! The most important aspect of a bug report is that it includes enough information for us to reproduce it. So, please include as much detail as possible and try to remove the extra stuff that doesn't really relate to the issue itself. The easier it is for us to reproduce it, the faster it'll be fixed! +Before reporting an issue, check our backlog of open issues to see if someone +else has already reported it. If so, feel free to add your scenario, or +additional information, to the discussion. Or simply "subscribe" to it to be +notified when it is updated. + +If you find a new issue with the project we'd love to hear about it! The most +important aspect of a bug report is that it includes enough information for us +to reproduce it. So, please include as much detail as possible and try to +remove the extra stuff that doesn't really relate to the issue itself. The +easier it is for us to reproduce it, the faster it'll be fixed! Please don't include any private/sensitive information in your issue! ## Tools we use - - https://www.pylint.org/ - - Python 3.6 - - You may need to use virtualenv to support Python 3.6 - - https://virtualenv.pypa.io/en/latest/ - + +- Python 3.6 +- [pylint](https://www.pylint.org/) +- [black](https://github.com/psf/black) +- [tox](https://tox.readthedocs.io/en/latest/) +- You may need to use [virtualenv](https://virtualenv.pypa.io/en/latest/) to + support Python 3.6 + ## Testing -Depending on the size of your PR we will expect at a minimum unit tests. -Integration tests would be required for large changes. -TBD (how to test) + +Depending on the size of your PR we will expect at a minimum unit tests. +Code will not be merged if unit test coverage drops below 85%. +Integration tests would be required for large changes (TBD). + +Run unit tests and get coverage report: + +``` +pip install tox +tox -e coverage +``` ## Submitting changes - - Create a github pull request (PR) - - We expect a short summary followed by a longer description of why you are making these change(s). - - Include the header `Signed-off-by: Git Hub User ` in your PR description/commit message with your name. - - Setting `user.name` and `user.email` in your git configs allows you to then use `git commit -s`. Let git do the work of signing your commits. - + +- Create a github pull request (PR) +- We expect a short summary followed by a longer description of why you are + making these change(s). +- Include the header `Signed-off-by: Git Hub User ` in your PR + description/commit message with your name. + - Setting `user.name` and `user.email` in your git configs allows you to then + use `git commit -s`. Let git do the work of signing your commits. + ## Where to find other contributors - - For general questions and discussion, please use the IRC #podman channel on irc.freenode.net. - - For discussions around issues/bugs and features, you can use the GitHub [issues](https://github.com/containers/podman-py/issues) and [PRs](https://github.com/containers/podman-py/pulls) tracking system. - + +- For general questions and discussion, please use the IRC #podman channel on + irc.freenode.net. +- For discussions around issues/bugs and features, you can use the + GitHub [issues](https://github.com/containers/podman-py/issues) and + [PRs](https://github.com/containers/podman-py/pulls) tracking system. + ## Coding conventions - - Pass pylint - - exceptions are possible but you will need to make a good argument - - use spaces not tabs for indentation - - This is open source software. Consider the people who will read your code, and make it look nice for them. It's sort of like driving a car: Perhaps you love doing donuts when you're alone, but with passengers the goal is to make the ride as smooth as possible. -Again thank you for your interest and participation. Jhon Honce +- Use [black](https://github.com/psf/black) code formatter. If you have tox + installed, run `tox -e black` to see what changes will be made. You can use + `tox -e black-format` to update the code formatting prior to committing. +- Pass pylint + - exceptions are possible but you will need to make a good argument +- use spaces not tabs for indentation +- This is open source software. Consider the people who will read your code, + and make it look nice for them. It's sort of like driving a car: Perhaps + you love doing donuts when you're alone, but with passengers the goal is to + make the ride as smooth as possible. + +Again thank you for your interest and participation. +Jhon Honce `` -Thanks to Carl Tashian, Participatory Politics Foundation for his fine CONTRIBUTING.md example. +Thanks to Carl Tashian, Participatory Politics Foundation for his fine +CONTRIBUTING.md example. diff --git a/podman/api_connection.py b/podman/api_connection.py index 16e20971..9fa9464f 100644 --- a/podman/api_connection.py +++ b/podman/api_connection.py @@ -17,9 +17,7 @@ class ApiConnection(HTTPConnection, AbstractContextManager): """ApiConnection provides a specialized HTTPConnection to a Podman service.""" - def __init__( - self, url, base="/v1.24/libpod", *args, **kwargs - ): # pylint: disable-msg=W1113 + def __init__(self, url, base="/v1.24/libpod", *args, **kwargs): # pylint: disable-msg=W1113 if url is None or not url: raise ValueError("url is required for service connection.") @@ -28,9 +26,7 @@ def __init__( uri = urllib.parse.urlparse(url) if uri.scheme not in ("unix", "ssh"): raise ValueError( - "The scheme '{}' is not supported, only {}".format( - uri.scheme, supported_schemes - ) + "The scheme '{}' is not supported, only {}".format(uri.scheme, supported_schemes) ) self.uri = uri self.base = base @@ -42,9 +38,7 @@ def connect(self): sock.connect(self.uri.path) self.sock = sock else: - raise NotImplementedError( - "Scheme {} not yet implemented".format(self.uri.scheme) - ) + raise NotImplementedError("Scheme {} not yet implemented".format(self.uri.scheme)) def delete(self, path, params=None): """Basic DELETE wrapper for requests @@ -88,27 +82,18 @@ def post(self, path, params=None, headers=None, encode=False): headers = {} if encode: - if ( - "content-type" not in set(key.lower() for key in headers) - and params - ): + if "content-type" not in set(key.lower() for key in headers) and params: headers["content-type"] = "application/x-www-form-urlencoded" data = urllib.parse.urlencode(params) - return self.request( - "POST", self.join(path), body=data, headers=headers - ) + return self.request("POST", self.join(path), body=data, headers=headers) - def request( - self, method, url, body=None, headers=None, *, encode_chunked=False - ): + def request(self, method, url, body=None, headers=None, *, encode_chunked=False): """Make request to Podman service.""" if headers is None: headers = {} - super().request( - method, url, body, headers, encode_chunked=encode_chunked - ) + super().request(method, url, body, headers, encode_chunked=encode_chunked) response = super().getresponse() # Errors are mapped to exceptions @@ -119,8 +104,7 @@ def request( "Request {}:{} failed: {}".format( method, url, - HTTPStatus.NOT_FOUND.description - or HTTPStatus.NOT_FOUND.phrase, + HTTPStatus.NOT_FOUND.description or HTTPStatus.NOT_FOUND.phrase, ), response, ) @@ -132,8 +116,7 @@ def request( "Request {}:{} failed: {}".format( method, url, - response.reason - or "Response Status Code {}".format(response.status), + response.reason or "Response Status Code {}".format(response.status), ), response, ) diff --git a/podman/errors/__init__.py b/podman/errors/__init__.py index 11125b7e..0af084af 100644 --- a/podman/errors/__init__.py +++ b/podman/errors/__init__.py @@ -12,7 +12,7 @@ def __init__(self, message, response=None): class ImageNotFound(NotFoundError): """HTTP request returned a http.HTTPStatus.NOT_FOUND. - Specialized for Image not found. + Specialized for Image not found. """ @@ -22,13 +22,13 @@ class NetworkNotFound(NotFoundError): class ContainerNotFound(NotFoundError): """HTTP request returned a http.HTTPStatus.NOT_FOUND. - Specialized for Container not found. + Specialized for Container not found. """ class PodNotFound(NotFoundError): """HTTP request returned a http.HTTPStatus.NOT_FOUND. - Specialized for Pod not found. + Specialized for Pod not found. """ diff --git a/podman/networks/__init__.py b/podman/networks/__init__.py index 9bf0bfce..2a8680b8 100644 --- a/podman/networks/__init__.py +++ b/podman/networks/__init__.py @@ -25,9 +25,7 @@ def create(api, name, network): else: data = network path = '/networks/create?name={}'.format(api.quote(name)) - response = api.post(path, - params=data, - headers={'content-type': 'application/json'}) + response = api.post(path, params=data, headers={'content-type': 'application/json'}) return json.loads(str(response.read(), 'utf-8')) diff --git a/podman/tests/unit/images/test_images.py b/podman/tests/unit/images/test_images.py index 1587d12a..442ba403 100644 --- a/podman/tests/unit/images/test_images.py +++ b/podman/tests/unit/images/test_images.py @@ -97,9 +97,7 @@ def test_remove_missing(self): mock_raise.side_effect = podman.errors.ImageNotFound("yikes") self.api.raise_not_found = mock_raise self.request.side_effect = podman.errors.NotFoundError("nope") - self.assertRaises( - podman.errors.ImageNotFound, podman.images.remove, self.api, "foo" - ) + self.assertRaises(podman.errors.ImageNotFound, podman.images.remove, self.api, "foo") def test_tag_image(self): """test tag image""" @@ -138,6 +136,4 @@ def test_history_missing_image(self): mock_raise.side_effect = podman.errors.ImageNotFound("yikes") self.api.raise_image_not_found = mock_raise self.request.side_effect = podman.errors.NotFoundError("nope") - self.assertRaises( - podman.errors.ImageNotFound, podman.images.history, self.api, "foo" - ) + self.assertRaises(podman.errors.ImageNotFound, podman.images.history, self.api, "foo") diff --git a/podman/tests/unit/networks/test_networks.py b/podman/tests/unit/networks/test_networks.py index 3c8e97cf..d0e8b883 100644 --- a/podman/tests/unit/networks/test_networks.py +++ b/podman/tests/unit/networks/test_networks.py @@ -50,7 +50,8 @@ def test_create(self): self.request.assert_called_once_with( '/networks/create?name=test', headers={'content-type': 'application/json'}, - params='{"DisableDNS": false}') + params='{"DisableDNS": false}', + ) def test_create_with_string(self): """test create call with string""" @@ -65,7 +66,8 @@ def test_create_with_string(self): self.request.assert_called_once_with( '/networks/create?name=test', headers={'content-type': 'application/json'}, - params='{"DisableDNS": false}') + params='{"DisableDNS": false}', + ) def test_create_fail(self): """test create call with an error""" @@ -73,11 +75,14 @@ def test_create_fail(self): 'DisableDNS': False, } self.response.status = 400 - self.request.side_effect = podman.errors.RequestError('meh', - self.response) - self.assertRaises(podman.errors.RequestError, - podman.networks.create, - self.api, 'test', json.dumps(network)) + self.request.side_effect = podman.errors.RequestError('meh', self.response) + self.assertRaises( + podman.errors.RequestError, + podman.networks.create, + self.api, + 'test', + json.dumps(network), + ) def test_inspect(self): """test inspect call""" @@ -94,10 +99,9 @@ def test_inspect_missing(self): mock_raise = mock.MagicMock() mock_raise.side_effect = podman.errors.NetworkNotFound('yikes') self.api.raise_not_found = mock_raise - self.assertRaises(podman.errors.NetworkNotFound, - podman.networks.inspect, - self.api, - 'podman') + self.assertRaises( + podman.errors.NetworkNotFound, podman.networks.inspect, self.api, 'podman' + ) def test_list_networks(self): """test networks list call""" @@ -115,8 +119,7 @@ def test_list_networks_filter(self): self.response.read = mock_read ret = podman.networks.list_networks(self.api, 'name=podman') self.assertEqual(ret, [{'Name': 'podman'}]) - self.request.assert_called_once_with('/networks/json', - {'filter': 'name=podman'}) + self.request.assert_called_once_with('/networks/json', {'filter': 'name=podman'}) def test_remove(self): """test remove call""" @@ -138,8 +141,7 @@ def test_remove_force(self): expected = [{'deleted': 'str', 'untagged': ['str']}] ret = podman.networks.remove(self.api, 'foo', True) self.assertEqual(ret, expected) - self.api.delete.assert_called_once_with('/networks/foo', - {'force': True}) + self.api.delete.assert_called_once_with('/networks/foo', {'force': True}) def test_remove_missing(self): """test remove call with missing network""" @@ -147,7 +149,4 @@ def test_remove_missing(self): mock_raise.side_effect = podman.errors.NetworkNotFound('yikes') self.api.raise_not_found = mock_raise self.request.side_effect = podman.errors.NotFoundError('nope') - self.assertRaises(podman.errors.NetworkNotFound, - podman.networks.remove, - self.api, - 'foo') + self.assertRaises(podman.errors.NetworkNotFound, podman.networks.remove, self.api, 'foo') diff --git a/podman/tests/unit/system/test_system.py b/podman/tests/unit/system/test_system.py index 30f476ec..4c37db52 100644 --- a/podman/tests/unit/system/test_system.py +++ b/podman/tests/unit/system/test_system.py @@ -64,9 +64,7 @@ def test_get_info_fail(self): mock_raise = mock.MagicMock() mock_raise.side_effect = podman.errors.ImageNotFound('yikes') self.api.raise_not_found = mock_raise - self.assertRaises(podman.errors.ImageNotFound, - podman.system.get_info, - self.api) + self.assertRaises(podman.errors.ImageNotFound, podman.system.get_info, self.api) def test_show_disk_usage(self): """test df call""" @@ -83,6 +81,4 @@ def test_show_disk_usage_fail(self): mock_raise = mock.MagicMock() mock_raise.side_effect = podman.errors.ImageNotFound('yikes') self.api.raise_not_found = mock_raise - self.assertRaises(podman.errors.ImageNotFound, - podman.system.show_disk_usage, - self.api) + self.assertRaises(podman.errors.ImageNotFound, podman.system.show_disk_usage, self.api) diff --git a/podman/tests/unit/test_api_connection.py b/podman/tests/unit/test_api_connection.py index 91376fb5..13c2df18 100644 --- a/podman/tests/unit/test_api_connection.py +++ b/podman/tests/unit/test_api_connection.py @@ -31,15 +31,11 @@ def setUp(self): def test_missing_url(self): """test missing url to constructor""" - self.assertRaises(ValueError, - ApiConnection, - None) + self.assertRaises(ValueError, ApiConnection, None) def test_invalid_scheme(self): """test invalid scheme to constructor""" - self.assertRaises(ValueError, - ApiConnection, - "tcp://localhost//") + self.assertRaises(ValueError, ApiConnection, "tcp://localhost//") @mock.patch('socket.socket') def test_connect(self, mock_sock): @@ -52,8 +48,7 @@ def test_connect(self, mock_sock): def test_connect_fail(self): """test connect not implemented""" conn = ApiConnection('ssh:///') - self.assertRaises(NotImplementedError, - conn.connect) + self.assertRaises(NotImplementedError, conn.connect) def test_join(self): """test join call""" @@ -64,8 +59,7 @@ def test_join_params(self): """test join call with params""" path = '/foo' params = {'a': '"b"'} - self.assertEqual(self.conn.join(path, params), - '/test/foo?a=%22b%22') + self.assertEqual(self.conn.join(path, params), '/test/foo?a=%22b%22') def test_delete(self): """test delete wrapper""" @@ -78,7 +72,7 @@ def test_delete(self): calls = [ mock.call('DELETE', '/test/baz'), mock.call('DELETE', '/test/foo?a=b'), - mock.call('DELETE', '/test/bar?a=b&c=d') + mock.call('DELETE', '/test/bar?a=b&c=d'), ] mock_req.assert_has_calls(calls) @@ -93,7 +87,7 @@ def test_get(self): calls = [ mock.call('GET', '/test/baz'), mock.call('GET', '/test/foo?a=b'), - mock.call('GET', '/test/bar?a=b&c=d') + mock.call('GET', '/test/bar?a=b&c=d'), ] mock_req.assert_has_calls(calls) @@ -104,20 +98,16 @@ def test_post(self): self.conn.post('/baz') self.conn.post('/foo', params="{'a': 'b'}") - self.conn.post('/bar', - params={'a': 'b', 'c': 'd'}, - headers={'x': 'y'}, - encode=True) + self.conn.post('/bar', params={'a': 'b', 'c': 'd'}, headers={'x': 'y'}, encode=True) calls = [ mock.call('POST', '/test/baz', body=None, headers={}), - mock.call('POST', '/test/foo', body="{'a': 'b'}", - headers={}), - mock.call('POST', '/test/bar', body='a=b&c=d', - headers={ - 'x': 'y', - 'content-type': 'application/x-www-form-urlencoded' - }), - + mock.call('POST', '/test/foo', body="{'a': 'b'}", headers={}), + mock.call( + 'POST', + '/test/bar', + body='a=b&c=d', + headers={'x': 'y', 'content-type': 'application/x-www-form-urlencoded'}, + ), ] mock_req.assert_has_calls(calls) @@ -129,8 +119,7 @@ def test_request(self, mock_request, mock_response): mock_resp.status = 200 mock_response.return_value = mock_resp ret = self.conn.request('GET', 'unix://foo') - mock_request.assert_called_once_with('GET', 'unix://foo', None, {}, - encode_chunked=False) + mock_request.assert_called_once_with('GET', 'unix://foo', None, {}, encode_chunked=False) mock_response.assert_called_once_with() self.assertEqual(ret, mock_resp) @@ -141,11 +130,8 @@ def test_request_not_found(self, mock_request, mock_response): mock_resp = mock.MagicMock() mock_resp.status = 404 mock_response.return_value = mock_resp - self.assertRaises(podman.errors.NotFoundError, - self.conn.request, - 'GET', 'unix://foo') - mock_request.assert_called_once_with('GET', 'unix://foo', None, {}, - encode_chunked=False) + self.assertRaises(podman.errors.NotFoundError, self.conn.request, 'GET', 'unix://foo') + mock_request.assert_called_once_with('GET', 'unix://foo', None, {}, encode_chunked=False) mock_response.assert_called_once_with() @mock.patch('http.client.HTTPConnection.getresponse') @@ -155,11 +141,8 @@ def test_request_request_error(self, mock_request, mock_response): mock_resp = mock.MagicMock() mock_resp.status = 400 mock_response.return_value = mock_resp - self.assertRaises(podman.errors.RequestError, - self.conn.request, - 'GET', 'unix://foo') - mock_request.assert_called_once_with('GET', 'unix://foo', None, {}, - encode_chunked=False) + self.assertRaises(podman.errors.RequestError, self.conn.request, 'GET', 'unix://foo') + mock_request.assert_called_once_with('GET', 'unix://foo', None, {}, encode_chunked=False) mock_response.assert_called_once_with() @mock.patch('http.client.HTTPConnection.getresponse') @@ -169,11 +152,8 @@ def test_request_server_error(self, mock_request, mock_response): mock_resp = mock.MagicMock() mock_resp.status = 500 mock_response.return_value = mock_resp - self.assertRaises(podman.errors.InternalServerError, - self.conn.request, - 'GET', 'unix://foo') - mock_request.assert_called_once_with('GET', 'unix://foo', None, {}, - encode_chunked=False) + self.assertRaises(podman.errors.InternalServerError, self.conn.request, 'GET', 'unix://foo') + mock_request.assert_called_once_with('GET', 'unix://foo', None, {}, encode_chunked=False) mock_response.assert_called_once_with() def test_quote(self): @@ -188,8 +168,5 @@ def test_raise_not_found(self, mock_json): mock_resp = mock.MagicMock() mock_json.return_value = {'cause': 'c', 'message': 'msg'} # pylint: disable=protected-access - self.assertRaises(podman.errors.ImageNotFound, - self.conn.raise_not_found, - exc, - mock_resp) + self.assertRaises(podman.errors.ImageNotFound, self.conn.raise_not_found, exc, mock_resp) mock_json.assert_called_once() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..0a65b095 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,13 @@ +[tool.black] +line-length = 100 +skip-string-normalization = true +include = '\.pyi?$' +exclude = ''' +/( + \.git + | \.tox + | \.venv + | build + | dist +)/ +''' diff --git a/test-requirements.txt b/test-requirements.txt index 7296eef9..14026947 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -2,3 +2,4 @@ fixtures nose coverage pylint +black diff --git a/tox.ini b/tox.ini index fe966bca..f5eb1c3d 100644 --- a/tox.ini +++ b/tox.ini @@ -21,3 +21,13 @@ commands = pylint podman commands = coverage run -m nose coverage report -m --skip-covered --fail-under=80 --omit=podman/tests/* --omit=.tox/* + +[testenv:black] +deps = black +commands = + black --diff --check . + +[testenv:black-format] +deps = black +commands = + black {posargs} .