diff --git a/.gitignore b/.gitignore index efe8469b33..4a39372126 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,8 @@ bin MANIFEST django_tests __pycache__ - +# The directory into which Django has been cloned to run the test suite. +django_tests_dir # Unit test / coverage reports .coverage .nox diff --git a/docs/_static/custom.css b/docs/_static/custom.css new file mode 100644 index 0000000000..bcd37bbd3c --- /dev/null +++ b/docs/_static/custom.css @@ -0,0 +1,9 @@ +div#python2-eol { + border-color: red; + border-width: medium; +} + +/* Ensure minimum width for 'Parameters' / 'Returns' column */ +dl.field-list > dt { + min-width: 100px +} diff --git a/docs/api-reference.rst b/docs/api-reference.rst index 847846a55e..c201e01e10 100644 --- a/docs/api-reference.rst +++ b/docs/api-reference.rst @@ -3,4 +3,7 @@ API Reference The following classes and methods constitute the Django Spanner API. -[this page is under construction] +.. toctree:: + :maxdepth: 1 + + schema-api diff --git a/docs/conf.py b/docs/conf.py index 1cffc0625d..301aa06e84 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -18,8 +18,7 @@ import sys import os - -from version import __version__ +import shlex # If extensions (or modules to document with autodoc) are in another directory, # add this directory to sys.path here. If the directory is relative to the @@ -30,10 +29,12 @@ # See also: https://github.com/docascode/sphinx-docfx-yaml/issues/85 sys.path.insert(0, os.path.abspath(".")) +__version__ = "" + # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. -needs_sphinx = "1.6.3" +needs_sphinx = "1.5.5" # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom @@ -43,6 +44,7 @@ "sphinx.ext.autosummary", "sphinx.ext.intersphinx", "sphinx.ext.coverage", + "sphinx.ext.doctest", "sphinx.ext.napoleon", "sphinx.ext.todo", "sphinx.ext.viewcode", @@ -171,7 +173,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -# html_static_path = ["_static"] +html_static_path = ["_static"] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied @@ -255,28 +257,28 @@ # -- Options for LaTeX output --------------------------------------------- latex_elements = { - # # The paper size ('letterpaper' or 'a4paper'). + # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', - # # The font size ('10pt', '11pt' or '12pt'). + # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', - # # Additional stuff for the LaTeX preamble. + # Additional stuff for the LaTeX preamble. # 'preamble': '', - # # Latex figure (float) alignment + # Latex figure (float) alignment # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source_start_file, target_name, title, author, # documentclass ["howto", "manual", or "own class"]). E.g., -# latex_documents = [ -# ( -# master_doc, -# "django-google-spanner.tex", -# u"Spanner Django Documentation", -# author, -# "manual", -# ) -# ] +latex_documents = [ + ( + master_doc, + "django-google-spanner.tex", + u"Spanner Django Documentation", + author, + "manual", + ) +] # The name of an image file (relative to this directory) # to place at the top of the title page. @@ -349,13 +351,13 @@ # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { - "python": ("http://python.readthedocs.org/en/latest/", None), - "google-auth": ("https://google-auth.readthedocs.io/en/stable", None), + "python": ("https://python.readthedocs.org/en/latest/", None), + "google-auth": ("https://googleapis.dev/python/google-auth/latest/", None), "google.api_core": ( "https://googleapis.dev/python/google-api-core/latest/", None, ), - "grpc": ("https://grpc.io/grpc/python/", None), + "grpc": ("https://grpc.github.io/grpc/python/", None), } diff --git a/docs/index.rst b/docs/index.rst index 5e9fef5773..ca432559cf 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,5 +1,13 @@ .. include:: README.rst +Usage Documentation +------------------- +.. toctree:: + :maxdepth: 1 + :titlesonly: + + schema-usage + API Documentation ----------------- .. toctree:: diff --git a/docs/schema-api.rst b/docs/schema-api.rst new file mode 100644 index 0000000000..c0118ed24a --- /dev/null +++ b/docs/schema-api.rst @@ -0,0 +1,8 @@ +Schema API +===================== + +.. automodule:: django_spanner.schema + :members: + :inherited-members: + + diff --git a/docs/schema-usage.rst b/docs/schema-usage.rst new file mode 100644 index 0000000000..451813b498 --- /dev/null +++ b/docs/schema-usage.rst @@ -0,0 +1,4 @@ +Schema +#################################### + +[this page is under construction] diff --git a/noxfile.py b/noxfile.py index 7bea0b8dda..a19bbc4360 100644 --- a/noxfile.py +++ b/noxfile.py @@ -85,17 +85,37 @@ def default(session): ) +@nox.session(python=UNIT_TEST_PYTHON_VERSIONS) +def unit(session): + """Run the unit test suite.""" + default(session) + + +@nox.session(python=DEFAULT_PYTHON_VERSION) +def cover(session): + """Run the final coverage report. + + This outputs the coverage report aggregating coverage from the unit + test runs (not system test runs), and then erases coverage data. + """ + session.install("coverage", "pytest-cov") + session.run("coverage", "report", "--show-missing", "--fail-under=20") + + session.run("coverage", "erase") + + @nox.session(python=DEFAULT_PYTHON_VERSION) def docs(session): """Build the docs for this library.""" session.install("-e", ".[tracing]") - session.install("sphinx", "alabaster", "recommonmark") + session.install("sphinx", "alabaster", "recommonmark", "django==2.2") shutil.rmtree(os.path.join("docs", "_build"), ignore_errors=True) + # Warnings as errors is disabled for `sphinx-build` because django module + # has warnings. session.run( "sphinx-build", - "-W", # warnings as errors "-T", # show full traceback on exception "-N", # no colors "-b", diff --git a/tests/unit/django_spanner/test_base.py b/tests/unit/django_spanner/test_base.py index c45cd1380d..32d965b9d1 100644 --- a/tests/unit/django_spanner/test_base.py +++ b/tests/unit/django_spanner/test_base.py @@ -6,15 +6,18 @@ import sys import unittest +import os from mock_import import mock_import from unittest import mock @mock_import() -@unittest.skipIf(sys.version_info < (3, 6), reason="Skipping Python 3.5") +@unittest.skipIf( + sys.version_info < (3, 6), reason="Skipping Python versions <= 3.5" +) class TestBase(unittest.TestCase): - PROJECT = "project" + PROJECT = os.environ["GOOGLE_CLOUD_PROJECT"] INSTANCE_ID = "instance_id" DATABASE_ID = "database_id" USER_AGENT = "django_spanner/2.2.0a1" @@ -64,10 +67,10 @@ def test_get_connection_params(self): def test_get_new_connection(self): db_wrapper = self._make_one(self.settings_dict) db_wrapper.Database = mock_database = mock.MagicMock() - mock_database.connect = mock_connect = mock.MagicMock() + mock_database.connect = mock_connection = mock.MagicMock() conn_params = {"test_param": "dummy"} db_wrapper.get_new_connection(conn_params) - mock_connect.assert_called_once_with(**conn_params) + mock_connection.assert_called_once_with(**conn_params) def test_init_connection_state(self): db_wrapper = self._make_one(self.settings_dict) @@ -106,3 +109,10 @@ def test_is_usable(self): mock_connection.cursor = mock.MagicMock(side_effect=Error) self.assertFalse(db_wrapper.is_usable()) + + def test__start_transaction_under_autocommit(self): + db_wrapper = self._make_one(self.settings_dict) + db_wrapper.connection = mock_connection = mock.MagicMock() + mock_connection.cursor = mock_cursor = mock.MagicMock() + db_wrapper._start_transaction_under_autocommit() + mock_cursor.assert_called_once_with() diff --git a/tests/unit/django_spanner/test_client.py b/tests/unit/django_spanner/test_client.py new file mode 100644 index 0000000000..fd02434b04 --- /dev/null +++ b/tests/unit/django_spanner/test_client.py @@ -0,0 +1,44 @@ +# Copyright 2020 Google LLC +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +import sys +import unittest +import os + + +@unittest.skipIf( + sys.version_info < (3, 6), reason="Skipping Python versions <= 3.5" +) +class TestClient(unittest.TestCase): + PROJECT = os.environ["GOOGLE_CLOUD_PROJECT"] + INSTANCE_ID = "instance_id" + DATABASE_ID = "database_id" + USER_AGENT = "django_spanner/2.2.0a1" + OPTIONS = {"option": "dummy"} + + settings_dict = { + "PROJECT": PROJECT, + "INSTANCE": INSTANCE_ID, + "NAME": DATABASE_ID, + "user_agent": USER_AGENT, + "OPTIONS": OPTIONS, + } + + def _get_target_class(self): + from django_spanner.client import DatabaseClient + + return DatabaseClient + + def _make_one(self, *args, **kwargs): + return self._get_target_class()(*args, **kwargs) + + def test_runshell(self): + from google.cloud.spanner_dbapi.exceptions import NotSupportedError + + db_wrapper = self._make_one(self.settings_dict) + + with self.assertRaises(NotSupportedError): + db_wrapper.runshell(parameters=self.settings_dict)