diff --git a/.coveragerc b/.coveragerc index 6a18f761d2c4..dd1524307f5c 100644 --- a/.coveragerc +++ b/.coveragerc @@ -6,8 +6,6 @@ omit = */_generated/*.py # Packages in the "google.cloud" package that we don't own. */google/cloud/gapic/* -fail_under = 100 -show_missing = True exclude_lines = # Re-enable the standard pragma pragma: NO COVER diff --git a/.travis.yml b/.travis.yml index b222923baf11..4e5346d52e8b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,9 +6,12 @@ install: script: - tox -e py27 + - (cd core && tox -e py27) - tox -e py34 + - (cd core && tox -e py34) - tox -e lint - tox -e cover + - (cd core && tox -e cover) - tox -e system-tests - tox -e system-tests3 - scripts/update_docs.sh diff --git a/core/.coveragerc b/core/.coveragerc new file mode 100644 index 000000000000..e72bb1216f10 --- /dev/null +++ b/core/.coveragerc @@ -0,0 +1,13 @@ +[run] +branch = True + +[report] +omit = + */google/cloud/_testing.py +fail_under = 100 +show_missing = True +exclude_lines = + # Re-enable the standard pragma + pragma: NO COVER + # Ignore debug-only repr + def __repr__ diff --git a/core/MANIFEST.in b/core/MANIFEST.in new file mode 100644 index 000000000000..cb3a2b9ef4fa --- /dev/null +++ b/core/MANIFEST.in @@ -0,0 +1,4 @@ +include README.rst +graft google +graft unit_tests +global-exclude *.pyc diff --git a/core/README.rst b/core/README.rst new file mode 100644 index 000000000000..d8ec01b781be --- /dev/null +++ b/core/README.rst @@ -0,0 +1,20 @@ +Core Helpers for Google Cloud Python Client Library +=================================================== + +This library is not meant to stand-alone. Instead it defines +common helpers (e.g. base ``Client`` and ``Connection`` classes) +used by all of the ``google-cloud-*``. + + +- `Homepage`_ +- `API Documentation`_ + +.. _Homepage: https://googlecloudplatform.github.io/google-cloud-python/ +.. _API Documentation: http://googlecloudplatform.github.io/google-cloud-python/ + +Quick Start +----------- + +:: + + $ pip install --upgrade google-cloud-core diff --git a/core/google/__init__.py b/core/google/__init__.py new file mode 100644 index 000000000000..b2b833373882 --- /dev/null +++ b/core/google/__init__.py @@ -0,0 +1,20 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +try: + import pkg_resources + pkg_resources.declare_namespace(__name__) +except ImportError: + import pkgutil + __path__ = pkgutil.extend_path(__path__, __name__) diff --git a/core/google/cloud/__init__.py b/core/google/cloud/__init__.py new file mode 100644 index 000000000000..8ac7b74af136 --- /dev/null +++ b/core/google/cloud/__init__.py @@ -0,0 +1,20 @@ +# Copyright 2014 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +try: + import pkg_resources + pkg_resources.declare_namespace(__name__) +except ImportError: + import pkgutil + __path__ = pkgutil.extend_path(__path__, __name__) diff --git a/google/cloud/_helpers.py b/core/google/cloud/_helpers.py similarity index 100% rename from google/cloud/_helpers.py rename to core/google/cloud/_helpers.py diff --git a/google/cloud/_testing.py b/core/google/cloud/_testing.py similarity index 100% rename from google/cloud/_testing.py rename to core/google/cloud/_testing.py diff --git a/google/cloud/client.py b/core/google/cloud/client.py similarity index 100% rename from google/cloud/client.py rename to core/google/cloud/client.py diff --git a/google/cloud/connection.py b/core/google/cloud/connection.py similarity index 99% rename from google/cloud/connection.py rename to core/google/cloud/connection.py index c5ed3a627e99..e5893a34630e 100644 --- a/google/cloud/connection.py +++ b/core/google/cloud/connection.py @@ -28,7 +28,7 @@ """The base of the API call URL.""" DEFAULT_USER_AGENT = 'gcloud-python/{0}'.format( - get_distribution('google-cloud').version) + get_distribution('google-cloud-core').version) """The user agent for google-cloud-python requests.""" diff --git a/google/cloud/credentials.py b/core/google/cloud/credentials.py similarity index 100% rename from google/cloud/credentials.py rename to core/google/cloud/credentials.py diff --git a/google/cloud/environment_vars.py b/core/google/cloud/environment_vars.py similarity index 100% rename from google/cloud/environment_vars.py rename to core/google/cloud/environment_vars.py diff --git a/google/cloud/exceptions.py b/core/google/cloud/exceptions.py similarity index 100% rename from google/cloud/exceptions.py rename to core/google/cloud/exceptions.py diff --git a/google/cloud/iterator.py b/core/google/cloud/iterator.py similarity index 100% rename from google/cloud/iterator.py rename to core/google/cloud/iterator.py diff --git a/google/cloud/operation.py b/core/google/cloud/operation.py similarity index 100% rename from google/cloud/operation.py rename to core/google/cloud/operation.py diff --git a/google/cloud/streaming/__init__.py b/core/google/cloud/streaming/__init__.py similarity index 100% rename from google/cloud/streaming/__init__.py rename to core/google/cloud/streaming/__init__.py diff --git a/google/cloud/streaming/buffered_stream.py b/core/google/cloud/streaming/buffered_stream.py similarity index 100% rename from google/cloud/streaming/buffered_stream.py rename to core/google/cloud/streaming/buffered_stream.py diff --git a/google/cloud/streaming/exceptions.py b/core/google/cloud/streaming/exceptions.py similarity index 100% rename from google/cloud/streaming/exceptions.py rename to core/google/cloud/streaming/exceptions.py diff --git a/google/cloud/streaming/http_wrapper.py b/core/google/cloud/streaming/http_wrapper.py similarity index 100% rename from google/cloud/streaming/http_wrapper.py rename to core/google/cloud/streaming/http_wrapper.py diff --git a/google/cloud/streaming/stream_slice.py b/core/google/cloud/streaming/stream_slice.py similarity index 100% rename from google/cloud/streaming/stream_slice.py rename to core/google/cloud/streaming/stream_slice.py diff --git a/google/cloud/streaming/transfer.py b/core/google/cloud/streaming/transfer.py similarity index 100% rename from google/cloud/streaming/transfer.py rename to core/google/cloud/streaming/transfer.py diff --git a/google/cloud/streaming/util.py b/core/google/cloud/streaming/util.py similarity index 100% rename from google/cloud/streaming/util.py rename to core/google/cloud/streaming/util.py diff --git a/core/setup.py b/core/setup.py new file mode 100644 index 000000000000..97b695e1a71e --- /dev/null +++ b/core/setup.py @@ -0,0 +1,72 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +from setuptools import find_packages +from setuptools import setup + + +PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) + +with open(os.path.join(PACKAGE_ROOT, 'README.rst')) as file_obj: + README = file_obj.read() + +# NOTE: This is duplicated throughout and we should try to +# consolidate. +SETUP_BASE = { + 'author': 'Google Cloud Platform', + 'author_email': 'jjg+google-cloud-python@google.com', + 'scripts': [], + 'url': 'https://github.com/GoogleCloudPlatform/google-cloud-python', + 'license': 'Apache 2.0', + 'platforms': 'Posix; MacOS X; Windows', + 'include_package_data': True, + 'zip_safe': False, + 'classifiers': [ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Operating System :: OS Independent', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Topic :: Internet', + ], +} + + +REQUIREMENTS = [ + 'httplib2 >= 0.9.1', + 'googleapis-common-protos', + 'oauth2client >= 2.0.1, < 3.0.0dev', + 'protobuf >= 3.0.0', + 'six', +] + +setup( + name='google-cloud-core', + version='0.20.0dev', + description='API Client library for Google Cloud: Core Helpers', + long_description=README, + namespace_packages=[ + 'google', + 'google.cloud', + ], + packages=find_packages(), + install_requires=REQUIREMENTS, + **SETUP_BASE +) diff --git a/core/tox.ini b/core/tox.ini new file mode 100644 index 000000000000..182562b6a42f --- /dev/null +++ b/core/tox.ini @@ -0,0 +1,29 @@ +[tox] +envlist = + py27,py34,py35,cover + +[testing] +deps = + pytest +covercmd = + py.test --quiet \ + --cov=google.cloud \ + --cov=unit_tests \ + --cov-config {toxinidir}/.coveragerc \ + unit_tests + +[testenv] +commands = + py.test --quiet {posargs} unit_tests +deps = + {[testing]deps} + +[testenv:cover] +basepython = + python2.7 +commands = + {[testing]covercmd} +deps = + {[testenv]deps} + coverage + pytest-cov diff --git a/core/unit_tests/__init__.py b/core/unit_tests/__init__.py new file mode 100644 index 000000000000..58e0d9153632 --- /dev/null +++ b/core/unit_tests/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/core/unit_tests/streaming/__init__.py b/core/unit_tests/streaming/__init__.py new file mode 100644 index 000000000000..58e0d9153632 --- /dev/null +++ b/core/unit_tests/streaming/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/unit_tests/streaming/test_buffered_stream.py b/core/unit_tests/streaming/test_buffered_stream.py similarity index 89% rename from unit_tests/streaming/test_buffered_stream.py rename to core/unit_tests/streaming/test_buffered_stream.py index 3304e2bd3cc0..b6f4066b11c2 100644 --- a/unit_tests/streaming/test_buffered_stream.py +++ b/core/unit_tests/streaming/test_buffered_stream.py @@ -24,6 +24,20 @@ def _getTargetClass(self): def _makeOne(self, *args, **kw): return self._getTargetClass()(*args, **kw) + def test_ctor_closed_stream(self): + class _Stream(object): + closed = True + + start = 0 + bufsize = 4 + bufstream = self._makeOne(_Stream, start, bufsize) + self.assertIs(bufstream._stream, _Stream) + self.assertEqual(bufstream._start_pos, start) + self.assertEqual(bufstream._buffer_pos, 0) + self.assertEqual(bufstream._buffered_data, b'') + self.assertTrue(bufstream._stream_at_end) + self.assertEqual(bufstream._end_pos, 0) + def test_ctor_start_zero_longer_than_buffer(self): from io import BytesIO CONTENT = b'CONTENT GOES HERE' diff --git a/unit_tests/streaming/test_exceptions.py b/core/unit_tests/streaming/test_exceptions.py similarity index 100% rename from unit_tests/streaming/test_exceptions.py rename to core/unit_tests/streaming/test_exceptions.py diff --git a/unit_tests/streaming/test_http_wrapper.py b/core/unit_tests/streaming/test_http_wrapper.py similarity index 100% rename from unit_tests/streaming/test_http_wrapper.py rename to core/unit_tests/streaming/test_http_wrapper.py diff --git a/unit_tests/streaming/test_stream_slice.py b/core/unit_tests/streaming/test_stream_slice.py similarity index 100% rename from unit_tests/streaming/test_stream_slice.py rename to core/unit_tests/streaming/test_stream_slice.py diff --git a/unit_tests/streaming/test_transfer.py b/core/unit_tests/streaming/test_transfer.py similarity index 100% rename from unit_tests/streaming/test_transfer.py rename to core/unit_tests/streaming/test_transfer.py diff --git a/unit_tests/streaming/test_util.py b/core/unit_tests/streaming/test_util.py similarity index 100% rename from unit_tests/streaming/test_util.py rename to core/unit_tests/streaming/test_util.py diff --git a/unit_tests/test__helpers.py b/core/unit_tests/test__helpers.py similarity index 100% rename from unit_tests/test__helpers.py rename to core/unit_tests/test__helpers.py diff --git a/unit_tests/test_client.py b/core/unit_tests/test_client.py similarity index 100% rename from unit_tests/test_client.py rename to core/unit_tests/test_client.py diff --git a/unit_tests/test_connection.py b/core/unit_tests/test_connection.py similarity index 88% rename from unit_tests/test_connection.py rename to core/unit_tests/test_connection.py index fb4ab1f8564c..af3802b21a9f 100644 --- a/unit_tests/test_connection.py +++ b/core/unit_tests/test_connection.py @@ -70,10 +70,41 @@ def test_http_w_creds(self): def test_user_agent_format(self): from pkg_resources import get_distribution expected_ua = 'gcloud-python/{0}'.format( - get_distribution('google-cloud').version) + get_distribution('google-cloud-core').version) conn = self._makeOne() self.assertEqual(conn.USER_AGENT, expected_ua) + def test__create_scoped_credentials_with_scoped_credentials(self): + klass = self._getTargetClass() + scoped_creds = object() + scope = 'google-specific-scope' + credentials = _Credentials(scoped=scoped_creds) + + result = klass._create_scoped_credentials(credentials, scope) + self.assertIs(result, scoped_creds) + self.assertEqual(credentials._create_scoped_calls, 1) + self.assertEqual(credentials._scopes, [scope]) + + def test__create_scoped_credentials_without_scope_required(self): + klass = self._getTargetClass() + credentials = _Credentials() + + result = klass._create_scoped_credentials(credentials, None) + self.assertIs(result, credentials) + self.assertEqual(credentials._create_scoped_calls, 1) + self.assertEqual(credentials._scopes, []) + + def test__create_scoped_credentials_non_scoped_credentials(self): + klass = self._getTargetClass() + credentials = object() + result = klass._create_scoped_credentials(credentials, None) + self.assertIs(result, credentials) + + def test__create_scoped_credentials_no_credentials(self): + klass = self._getTargetClass() + result = klass._create_scoped_credentials(None, None) + self.assertIsNone(result) + class TestJSONConnection(unittest.TestCase): @@ -375,11 +406,12 @@ def request(self, **kw): class _Credentials(object): - _scopes = None - - def __init__(self, authorized=None): + def __init__(self, authorized=None, scoped=None): self._authorized = authorized + self._scoped = scoped + self._scoped_required = scoped is not None self._create_scoped_calls = 0 + self._scopes = [] def authorize(self, http): self._called_with = http @@ -387,4 +419,8 @@ def authorize(self, http): def create_scoped_required(self): self._create_scoped_calls += 1 - return False + return self._scoped_required + + def create_scoped(self, scope): + self._scopes.append(scope) + return self._scoped diff --git a/unit_tests/test_credentials.py b/core/unit_tests/test_credentials.py similarity index 100% rename from unit_tests/test_credentials.py rename to core/unit_tests/test_credentials.py diff --git a/unit_tests/test_exceptions.py b/core/unit_tests/test_exceptions.py similarity index 92% rename from unit_tests/test_exceptions.py rename to core/unit_tests/test_exceptions.py index 56d8581be036..8460d6d8f1c4 100644 --- a/unit_tests/test_exceptions.py +++ b/core/unit_tests/test_exceptions.py @@ -125,6 +125,17 @@ def test_html_when_json_expected(self): self.assertEqual(exception.message, content) self.assertEqual(list(exception.errors), []) + def test_without_use_json(self): + from google.cloud.exceptions import TooManyRequests + + content = u'error-content' + response = _Response(TooManyRequests.code) + exception = self._callFUT(response, content, use_json=False) + + self.assertIsInstance(exception, TooManyRequests) + self.assertEqual(exception.message, content) + self.assertEqual(list(exception.errors), []) + class _Response(object): def __init__(self, status): diff --git a/unit_tests/test_iterator.py b/core/unit_tests/test_iterator.py similarity index 100% rename from unit_tests/test_iterator.py rename to core/unit_tests/test_iterator.py diff --git a/unit_tests/test_operation.py b/core/unit_tests/test_operation.py similarity index 100% rename from unit_tests/test_operation.py rename to core/unit_tests/test_operation.py diff --git a/scripts/verify_included_modules.py b/scripts/verify_included_modules.py index 0f16e47265c2..264d7876c9cc 100644 --- a/scripts/verify_included_modules.py +++ b/scripts/verify_included_modules.py @@ -59,6 +59,7 @@ ]) PACKAGES = ( '', + 'core', ) diff --git a/setup.py b/setup.py index d1eabf4faa43..5662b02e3b02 100644 --- a/setup.py +++ b/setup.py @@ -50,11 +50,7 @@ REQUIREMENTS = [ - 'httplib2 >= 0.9.1', - 'googleapis-common-protos', - 'oauth2client >= 2.0.1', - 'protobuf >= 3.0.0', - 'six', + 'google-cloud-core', ] GRPC_PACKAGES = [ @@ -72,7 +68,7 @@ setup( name='google-cloud', - version='0.19.0', + version='0.20.0dev', description='API Client library for Google Cloud', long_description=README, namespace_packages=[ diff --git a/tox.ini b/tox.ini index d0cc47ded48d..1e2eba979c5f 100644 --- a/tox.ini +++ b/tox.ini @@ -4,6 +4,7 @@ envlist = [testing] deps = + {toxinidir}/core pytest covercmd = py.test --quiet \ @@ -11,6 +12,13 @@ covercmd = --cov=unit_tests \ --cov-config {toxinidir}/.coveragerc \ unit_tests + py.test --quiet \ + --cov=google.cloud \ + --cov=unit_tests \ + --cov-append \ + --cov-config {toxinidir}/.coveragerc \ + core/unit_tests + coverage report --show-missing --fail-under=100 [testenv] commands =