Skip to content

Commit

Permalink
Tests: Prune list of provided test.globs for doctests
Browse files Browse the repository at this point in the history
Remove any kinds of symbol aliases from the doctest launcher established
per `test.globs`, in order not to obfuscate them away from the users
reading the examples, and likely aiming to just copy/paste the presented
code snippets.

This is to remove any kinds of idioms or shortcuts from the
doctest documentation, in order to make it clear what is happening.

Also, it explains where object or response mocks are used, and why.
  • Loading branch information
amotl committed Oct 13, 2022
1 parent a2ab48a commit 8c7400e
Show file tree
Hide file tree
Showing 11 changed files with 135 additions and 112 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ eggs/
htmlcov/
out/
parts/
tmp/
7 changes: 6 additions & 1 deletion DEVELOP.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,12 @@ Ignore specific test directories::

./bin/test -vvvv --ignore_dir=testing

Invoke all tests without integration tests (~5 seconds runtime)::
The ``LayerTest`` test cases have quite some overhead. Omitting them will save
a few cycles (~90 seconds runtime)::

./bin/test -t '!LayerTest'

Invoke all tests without integration tests (~3 seconds runtime)::

./bin/test \
-t '!LayerTest' -t '!docs/by-example' \
Expand Down
12 changes: 8 additions & 4 deletions docs/by-example/connection.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ The Connection object
This documentation section outlines different attributes, methods, and
behaviors of the ``crate.client.connection.Connection`` object.

To improve focus and reduce boilerplate, the example code uses both
``ClientMocked``. It is required for demonstration purposes, so the example
does not need a real database connection.

.. rubric:: Table of Contents

.. contents::
Expand All @@ -13,13 +17,13 @@ behaviors of the ``crate.client.connection.Connection`` object.

connect()
=========
::

>>> from crate.client import connect
This section sets up a connection object, and inspects some of its attributes.

We create a new connection object::
>>> from crate.client import connect
>>> from crate.client.test_util import ClientMocked

>>> connection = connect(client=connection_client_mocked)
>>> connection = connect(client=ClientMocked())
>>> connection.lowest_server_version.version
(2, 0, 0)

Expand Down
34 changes: 13 additions & 21 deletions docs/by-example/cursor.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,42 +5,40 @@ The Cursor object
This documentation section outlines different attributes, methods, and
behaviors of the ``crate.client.cursor.Cursor`` object.

To improve focus and reduce boilerplate, it uses a ``set_next_response`` method
only intended for testing.
To improve focus and reduce boilerplate, the example code uses both
``ClientMocked`` and ``set_next_response``. They are required for demonstration
purposes, so the example does not need a real database connection.

.. rubric:: Table of Contents

.. contents::
:local:


Setup
=====
Introduction
============

This section sets up a cursor object, inspects some of its attributes, and sets
up the response for subsequent cursor operations.

::

>>> from crate.client import connect
>>> from crate.client.cursor import Cursor
>>> from crate.client.test_util import ClientMocked

>>> connection = connect(client=connection_client_mocked)
>>> connection = connect(client=ClientMocked())
>>> cursor = connection.cursor()

The rowcount and duration attribute is ``-1``, in case no ``execute()`` has
been performed on the cursor.

::
been performed on the cursor yet.

>>> cursor.rowcount
-1

>>> cursor.duration
-1

Hardcode the next response of the mocked connection client, so we won't need a sql statement
to execute::
Define the response of the mocked connection client. It will be returned on
request without needing to execute an SQL statement.

>>> connection.client.set_next_response({
... "rows":[ [ "North West Ripple", 1 ], [ "Arkintoofle Minor", 3 ], [ "Alpha Centauri", 3 ] ],
Expand Down Expand Up @@ -179,8 +177,6 @@ Iterating over a new cursor without results will immediately raise a Programming
description
===========

::

>>> cursor.description
(('name', None, None, None, None, None, None), ('position', None, None, None, None, None, None))

Expand Down Expand Up @@ -324,8 +320,6 @@ The cursor object can optionally convert database types to native Python data
types. Currently, this is implemented for the CrateDB data types ``IP`` and
``TIMESTAMP`` on behalf of the ``DefaultTypeConverter``.

::

>>> cursor = connection.cursor(converter=Cursor.get_default_converter())

>>> connection.client.set_next_response({
Expand All @@ -351,7 +345,7 @@ inspect the ``DataType`` enum, or the documentation about the list of available
`CrateDB data type identifiers for the HTTP interface`_.

To create a simple converter for converging CrateDB's ``BIT`` type to Python's
``int`` type::
``int`` type.

>>> from crate.client.converter import Converter, DataType

Expand All @@ -360,7 +354,7 @@ To create a simple converter for converging CrateDB's ``BIT`` type to Python's
>>> cursor = connection.cursor(converter=converter)

Proof that the converter works correctly, ``B\'0110\'`` should be converted to
``6``. CrateDB's ``BIT`` data type has the numeric identifier ``25``::
``6``. CrateDB's ``BIT`` data type has the numeric identifier ``25``.

>>> connection.client.set_next_response({
... "col_types": [25],
Expand All @@ -386,8 +380,6 @@ desired time zone.
For your reference, in the following examples, epoch 1658167836758 is
``Mon, 18 Jul 2022 18:10:36 GMT``.

::

>>> import datetime
>>> tz_mst = datetime.timezone(datetime.timedelta(hours=7), name="MST")
>>> cursor = connection.cursor(time_zone=tz_mst)
Expand All @@ -414,7 +406,7 @@ The available options are:
- ``zoneinfo.ZoneInfo("Australia/Sydney")``
- ``+0530`` (UTC offset in string format)

Let's exercise all of them::
Let's exercise all of them:

>>> cursor.time_zone = datetime.timezone.utc
>>> cursor.execute('')
Expand Down
36 changes: 32 additions & 4 deletions docs/by-example/sqlalchemy/cru.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,42 @@ and updating complex data types with nested Python dictionaries.
:local:


Setup
=====
Introduction
============

Import the relevant symbols:

>>> import sqlalchemy as sa
>>> from datetime import datetime
>>> from sqlalchemy.ext.declarative import declarative_base
>>> from sqlalchemy.orm import sessionmaker
>>> from sqlalchemy.sql import text
>>> from crate.client.sqlalchemy.types import ObjectArray

Establish a connection to the database:

>>> engine = sa.create_engine(f"crate://{crate_host}")
>>> connection = engine.connect()

Define the ORM schema for the ``Location`` entity:

>>> Base = declarative_base(bind=engine)

>>> class Location(Base):
... __tablename__ = 'locations'
... name = sa.Column(sa.String, primary_key=True)
... kind = sa.Column(sa.String)
... date = sa.Column(sa.Date, default=lambda: datetime.utcnow().date())
... datetime_tz = sa.Column(sa.DateTime, default=datetime.utcnow)
... datetime_notz = sa.Column(sa.DateTime, default=datetime.utcnow)
... nullable_datetime = sa.Column(sa.DateTime)
... nullable_date = sa.Column(sa.Date)
... flag = sa.Column(sa.Boolean)
... details = sa.Column(ObjectArray)

Create a session with SQLAlchemy:

>>> session = sessionmaker(bind=engine)()

Retrieve
========
Expand Down Expand Up @@ -247,8 +276,7 @@ Refresh "characters" table:
>>> _ = connection.execute("REFRESH TABLE characters")

>>> session.refresh(char)
>>> import pprint
>>> pprint.pprint(char.details)
>>> pprint(char.details)
{'name': {'first': 'Trillian', 'last': 'Dent'}, 'size': 45}

.. Hidden: close connection
Expand Down
34 changes: 28 additions & 6 deletions docs/by-example/sqlalchemy/getting-started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,28 @@ as well as the use of complex and geospatial data types.
:local:


Introduction
============

Import the relevant symbols:

>>> import sqlalchemy as sa
>>> from sqlalchemy.ext.declarative import declarative_base
>>> from sqlalchemy.orm import sessionmaker
>>> from sqlalchemy.sql import text

Establish a connection to the database:

>>> engine = sa.create_engine(f"crate://{crate_host}")
>>> connection = engine.connect()

>>> Base = declarative_base(bind=engine)

Create a session with SQLAlchemy:

>>> session = sessionmaker(bind=engine)()


Connection String
=================

Expand Down Expand Up @@ -116,9 +138,9 @@ defined in the schema:

After ``INSERT`` statements are sent to the database the newly inserted rows
aren't immediately available for search because the index is only updated
periodically:
periodically. In order to synchronize that, refresh the table:

>>> refresh("characters")
>>> _ = connection.execute(text("REFRESH TABLE characters"))

A regular select query will then fetch the whole documents:

Expand Down Expand Up @@ -180,7 +202,7 @@ This will generate an UPDATE statement roughly like this:

"UPDATE characters set more_details = ? ...", ([{'foo': 1, 'bar': 10}, {'foo': 2}, {'foo': 3}],)

>>> refresh("characters")
>>> _ = connection.execute(text("REFRESH TABLE characters"))

To do queries against fields of ``ObjectArray``s you have to use the
``.any(value, operator=operators.eq)`` method on a subscript, because accessing
Expand Down Expand Up @@ -241,10 +263,10 @@ session:
>>> tokyo = City(coordinate=point, area=area, name='Tokyo')
>>> session.add(tokyo)
>>> session.commit()
>>> _ = connection.execute(text("REFRESH TABLE cities"))

When retrieved, they are retrieved as the corresponding geojson objects:

>>> refresh("cities")
>>> query = session.query(City.name, City.coordinate, City.area)
>>> query.all()
[('Tokyo', (139.75999999791384, 35.67999996710569), {"coordinates": [[[139.806, 35.515], [139.919, 35.703], [139.768, 35.817], [139.575, 35.76], [139.584, 35.619], [139.806, 35.515]]], "type": "Polygon"})]
Expand Down Expand Up @@ -425,7 +447,7 @@ Let's add a task to the ``Todo`` table:
>>> task = Todos(content='Write Tests', status='done')
>>> session.add(task)
>>> session.commit()
>>> refresh("todos")
>>> _ = connection.execute(text("REFRESH TABLE todos"))

Using ``insert().from_select()`` to archive the task in ``ArchivedTasks``
table:
Expand All @@ -434,7 +456,7 @@ table:
>>> ins = insert(ArchivedTasks).from_select(['id','content'], sel)
>>> result = session.execute(ins)
>>> session.commit()
>>> refresh("archived_tasks")
>>> _ = connection.execute(text("REFRESH TABLE archived_tasks"))

This will result in the following query:

Expand Down
3 changes: 3 additions & 0 deletions docs/by-example/sqlalchemy/inspection-reflection.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ A low level interface which provides a backend-agnostic system of loading lists
of schema, table, column, and constraint descriptions from a given database is
available. This is known as the `SQLAlchemy inspector`_.

>>> import sqlalchemy as sa

>>> engine = sa.create_engine(f"crate://{crate_host}")
>>> inspector = sa.inspect(engine)


Expand Down
20 changes: 15 additions & 5 deletions docs/by-example/sqlalchemy/internals.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,34 @@ focuses on showing specific internals.
CrateDialect
============

The initialize method sets the default schema name and version info:
Import the relevant symbols:

>>> import sqlalchemy as sa
>>> from crate.client.sqlalchemy.dialect import CrateDialect

Establish a connection to the database:

>>> engine = sa.create_engine(f"crate://{crate_host}")
>>> connection = engine.connect()

After initializing the dialect instance with a connection instance,

>>> dialect = CrateDialect()
>>> dialect.initialize(connection)

the database server version and default schema name can be inquired.

>>> dialect.server_version_info >= (1, 0, 0)
True

Check if table exists:
Check if schema exists:

>>> dialect.has_table(connection, 'locations')
>>> dialect.has_schema(connection, 'doc')
True

Check if schema exists:
Check if table exists:

>>> dialect.has_schema(connection, 'doc')
>>> dialect.has_table(connection, 'locations')
True

.. Hidden: close connection
Expand Down
5 changes: 3 additions & 2 deletions docs/sqlalchemy.rst
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ Table definition
----------------

Here is an example SQLAlchemy table definition using the `declarative
system`_::
system`_:

>>> from sqlalchemy.ext import declarative
>>> from crate.client.sqlalchemy import types
Expand Down Expand Up @@ -402,7 +402,8 @@ default, which is a short time for me and you, but a long time for your code).

You can request a `table refresh`_ to update the index manually::

>>> refresh("characters")
>>> connection = engine.connect()
>>> _ = connection.execute(text("REFRESH TABLE characters"))

.. NOTE::

Expand Down
Loading

0 comments on commit 8c7400e

Please sign in to comment.