Skip to content

Commit

Permalink
Some fixes to docs
Browse files Browse the repository at this point in the history
  • Loading branch information
albireox committed May 13, 2020
1 parent 75b0455 commit 8cf631d
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 60 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ This document records the main changes to the ``sdssdb`` code.

* :release:`0.2.0 <2018-12-14>`
* Removed some unused files from the template.
* :feature:`7` Added `~sdssdb.utils.create_schema_graph` function to generate schema graphs and use it to auto-generate graphs in the documentation for the supported databases.
* :feature:`7` Added `~sdssdb.utils.schemadisplay.create_schema_graph` function to generate schema graphs and use it to auto-generate graphs in the documentation for the supported databases.
* Fixed command line example about how to install with ``sdss_install``.
* Added ``mangadb`` schema for SQLA.
* Use ``tmass_pts_key`` for fk relationship between ``GaiaDR2TmassBestNeighbour`` and ``TwoMassPsc``.
Expand Down
2 changes: 1 addition & 1 deletion docs/sphinx/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@ Utils
:members:
:show-inheritance:

.. autofunction:: sdssdb.utils.create_schema_graph
.. autofunction:: sdssdb.utils.schemadisplay.create_schema_graph
114 changes: 57 additions & 57 deletions docs/sphinx/contributing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ In addition to improvements to the code, you can contribute database connections
| |__ schema1.py
| |__ schema2.py
Let's imagine you want to create files for a database called ``awesomedb`` which has two schemas: ``amazing``
and ``stupendous``. Depending on whether you are creating model classes for peewee or sqlalchemy (or both),
you will need to create a directory called ``awesomedb`` under the correct library directory with a
``__init__.py`` file and two ``amazing.py`` and ``stupendous.py`` files. The following sections will show you
Let's imagine you want to create files for a database called ``awesomedb`` which has two schemas: ``amazing``
and ``stupendous``. Depending on whether you are creating model classes for peewee or sqlalchemy (or both),
you will need to create a directory called ``awesomedb`` under the correct library directory with a
``__init__.py`` file and two ``amazing.py`` and ``stupendous.py`` files. The following sections will show you
how to fill out those files depending on the library used.


Expand Down Expand Up @@ -81,7 +81,7 @@ Using reflection with Peewee

Peewee provides a :ref:`reflection <peewee:reflection>` utility (internally used by ``pwiz``). Based on this tool we developed a `reflection metaclass <.ReflectMeta>` that can be used to expedite the creating of models by only requiring to define foreign keys. Note that this is not an official component of Peewee and it comes with certain caveats. Before using the reflection metaclass, make sure to read the `API documentation <.ReflectMeta>`.

To define a base class with reflection with do ::
To define a base class with reflection we do ::

import peewee
from sdssdb.peewee import ReflectMeta
Expand All @@ -98,7 +98,7 @@ To define a base class with reflection with do ::
schema = 'stupendous'
table_name = 'stupendous_table'

When the connection is created this model will be reflected and autocompleted with all the columns that exist in the table. The reflection does not include `foreign keys <peewee:ForeignKeyField>`, which must be created manually (along with their referenced columns). You can check the `catalogdb <https://github.com/sdss/sdssdb/blob/master/python/sdssdb/peewee/sdss5db/catalogdb.py>`__ models for an implementation of this type.
When the connection is created this model will be reflected and autocompleted with all the columns that exist in the table. The reflection will include the `foreign keys <peewee:ForeignKeyField>` that have been defined for the table in the database. Sometimes this is not desirable and we'd rather create them manually. In this case we can add the attribute ``reflection_options = {'skip_foreign_keys': True}`` to ``Meta`` in the ``ReflectBaseModel``. You can check the `catalogdb <https://github.com/sdss/sdssdb/blob/master/python/sdssdb/peewee/sdss5db/catalogdb.py>`__ models for an implementation of this type.


SQLAlchemy
Expand Down Expand Up @@ -159,26 +159,26 @@ For the model classes you will need to write the files manually but there is no
database.add_base(Base)


In this example we have two tables, ``user`` and ``address`` that we model as ``User`` and ``Address``
respectively. Note that we don't need to specify any column at this point, just the ``__tablename__``
metadata property. All model classes need to subclass from ``Base``, which in turn subclasses from
`~sqlalchemy.ext.declarative.AbstractConcreteBase` and ``AwesomedbBase``. We can use the special attribute
``print_fields`` to define a list of fields that will be output in the standard representation of the model
In this example we have two tables, ``user`` and ``address`` that we model as ``User`` and ``Address``
respectively. Note that we don't need to specify any column at this point, just the ``__tablename__``
metadata property. All model classes need to subclass from ``Base``, which in turn subclasses from
`~sqlalchemy.ext.declarative.AbstractConcreteBase` and ``AwesomedbBase``. We can use the special attribute
``print_fields`` to define a list of fields that will be output in the standard representation of the model
instances (primary keys and ``label`` fields are always output).

The ``define_relations`` function must contain all the foreign key relationships for this model. In this
case there only one relationship that allows to retrieve the address for a given ``User`` (and its
back reference). We need to encapsulate the relationships in a function so that they can be recreated if
we change the database connection to point to a different database. Finally, we add the
The ``define_relations`` function must contain all the foreign key relationships for this model. In this
case there only one relationship that allows to retrieve the address for a given ``User`` (and its
back reference). We need to encapsulate the relationships in a function so that they can be recreated if
we change the database connection to point to a different database. Finally, we add the
``database.add_base(Base)`` statement to bind the base to the database connection.

Testing Your New Database
-------------------------

After creating your database, you will want to ensure its stability and robustness as you expand its
capabilities over time. This can be done by writing tests against your database. The testing directory system
is very similar to the `sdssdb` database directory, with test database files located within separate library
folders for ``peewee`` databases (``pwdbs``) or ``sqlalchemy`` databases (``sqladbs``).
After creating your database, you will want to ensure its stability and robustness as you expand its
capabilities over time. This can be done by writing tests against your database. The testing directory system
is very similar to the `sdssdb` database directory, with test database files located within separate library
folders for ``peewee`` databases (``pwdbs``) or ``sqlalchemy`` databases (``sqladbs``).

.. code-block:: none
Expand Down Expand Up @@ -207,15 +207,15 @@ folders for ``peewee`` databases (``pwdbs``) or ``sqlalchemy`` databases (``sql
|__ conftest.py
|__ test_generic_items.py
Most Python testing frameworks look for tests in files named ``test_xxxx.py``. Under each library we create a
Most Python testing frameworks look for tests in files named ``test_xxxx.py``. Under each library we create a
``test_xxxx`` file for each new database we want to test. Since we've created a new ``awesomedb`` database, our
testing file will be ``test_awesomedb.py``. This file gets placed under either the ``pwdbs`` or ``sqladbs`` (or both)
depending on if your database is using ``peeewee`` or ``sqlalchemy``.
depending on if your database is using ``peeewee`` or ``sqlalchemy``.

``sdssdb`` uses `pytest <https://docs.pytest.org/en/latest/>`_ as its testing framework, and assumes user
``sdssdb`` uses `pytest <https://docs.pytest.org/en/latest/>`_ as its testing framework, and assumes user
familiarity with pytest. The test directories contain ``conftest.py`` files which are files used for sharing
fixture functions between tests. See `here <https://docs.pytest.org/en/latest/fixture.html#conftest-py-sharing-fixture-functions>`_
for more details. You will also see files called ``models`` and ``factories``. We will come back to these later.
for more details. You will also see files called ``models`` and ``factories``. We will come back to these later.

Peewee
^^^^^^
Expand All @@ -239,22 +239,22 @@ We follow pytest's `test naming convention <https://docs.pytest.org/en/latest/go
for naming test files as well as tests within files. In our ``test_awesomedb`` file, we group similar tests
by schema together into ``Test`` classes, i.e. for the ``stupendous`` schema, we create a ``TestStupendous`` class.
All tests related to the ``stupendous`` schema will be defined in this class. Individual tests within each class
are defined as methods on the class, named with ``test_xxxx``.
are defined as methods on the class, named with ``test_xxxx``.

In order for our test class to understand that we wish to use the ``awesomedb`` database for all defined tests, we
use the provided ``database`` fixture function and parametrize it with the ``awesomedb`` database. See
`fixture parametrization <https://docs.pytest.org/en/latest/fixture.html#parametrizing-fixtures>`_ to learn more
use the provided ``database`` fixture function and parametrize it with the ``awesomedb`` database. See
`fixture parametrization <https://docs.pytest.org/en/latest/fixture.html#parametrizing-fixtures>`_ to learn more
about how to parametrize tests or fixtures.

We've defined a simple test, ``test_user_count``, that checks that our ``user`` table returns
We've defined a simple test, ``test_user_count``, that checks that our ``user`` table returns
some number of results > 0. In this case, we are a performing a simple select statement that does not modify the
database. If we are writing tests that perform write operations on the database, we could use the provided
``transaction`` fixture to ensure all changes are rolled back.
``transaction`` fixture to ensure all changes are rolled back.

SQLAlchemy
^^^^^^^^^^

The example ``test_awesomedb.py`` file for a ``sqlalchemy`` database will look very similar to the
The example ``test_awesomedb.py`` file for a ``sqlalchemy`` database will look very similar to the
``peewee`` version.
::

Expand All @@ -272,30 +272,30 @@ The example ``test_awesomedb.py`` file for a ``sqlalchemy`` database will look v
user_ct = session.query(stupendous.User).count()
assert user_ct > 0

There are two main differences in this file from the ``peewee`` version. The first is that we must wrap the
import of the ``stupendous`` models inside a conditional that checks if the database has been successfully
There are two main differences in this file from the ``peewee`` version. The first is that we must wrap the
import of the ``stupendous`` models inside a conditional that checks if the database has been successfully
connected to. This is needed because importing ``sqlalchemy`` models when no database exists, or
cannot connect, breaks other succcessful database imports. The second change is the use of the ``session``
fixture inside the test. Since ``sqlalchemy`` needs a db session to perform queries, we use the
provided ``session`` pytest fixture. This fixture will ensure that all changes made to the database
are rolled back and not permanent.
cannot connect, breaks other succcessful database imports. The second change is the use of the ``session``
fixture inside the test. Since ``sqlalchemy`` needs a db session to perform queries, we use the
provided ``session`` pytest fixture. This fixture will ensure that all changes made to the database
are rolled back and not permanent.

Generating and Inserting Fake Data into Your Database Tables
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

If you are only interested in writing simple tests that test real data in your database
tables, then you can stop here and start writing your tests. Sometimes, however, you may want to write
tests for special database queries or model functions where you don't quite have the right data, or enough
of it, loaded. In these cases, we can generate fake data and insert it dynamically into our database tables.
To do so, we have to create a "model factory". This factory creates fake data based on a database Model.
If you are only interested in writing simple tests that test real data in your database
tables, then you can stop here and start writing your tests. Sometimes, however, you may want to write
tests for special database queries or model functions where you don't quite have the right data, or enough
of it, loaded. In these cases, we can generate fake data and insert it dynamically into our database tables.
To do so, we have to create a "model factory". This factory creates fake data based on a database Model.

The following examples use the following resources to generate fake data:

- `factory_boy <https://factoryboy.readthedocs.io/en/latest/>`_ - creates db model factories to generate fake entries
- `faker <https://faker.readthedocs.io/en/master/index.html>`_ - creates fake data as needed by models
- `pytest-factoryboy <https://pytest-factoryboy.readthedocs.io/en/latest/>`_ - turns model factories into pytest fixtures

Let's see how to create factories to generate fake Users and Addressess, inside the ``factories.py`` file,
Let's see how to create factories to generate fake Users and Addressess, inside the ``factories.py`` file,
using the ``peewee`` library implementation as an example.
::

Expand Down Expand Up @@ -329,15 +329,15 @@ using the ``peewee`` library implementation as an example.
# establishes the one-to-one relationship
address = factory.SubFactory(AddressFactory)

If the ``User`` and ``Address`` models created previously have the following columns on each table, we use
the `factorboy declarations <https://factoryboy.readthedocs.io/en/latest/reference.html#declarations>`_
If the ``User`` and ``Address`` models created previously have the following columns on each table, we use
the `factorboy declarations <https://factoryboy.readthedocs.io/en/latest/reference.html#declarations>`_
and `factory.Faker providers <https://faker.readthedocs.io/en/master/providers.html>`_ to assign each column
a fake data generator. For each factory we need to define a ``Meta`` class in it that defines the database
a fake data generator. For each factory we need to define a ``Meta`` class in it that defines the database
model associated with it, as well as the database it belongs to.

These factories allow us to create fake instances of data that automatically inserts into the
designated database table. To create an instance locally without database insertion, you can use
``UserFactory.build`` or to create in bulk, use ``UserFactory.create_batch``.
These factories allow us to create fake instances of data that automatically inserts into the
designated database table. To create an instance locally without database insertion, you can use
``UserFactory.build`` or to create in bulk, use ``UserFactory.create_batch``.
::

>>> user = UserFactory()
Expand All @@ -346,7 +346,7 @@ designated database table. To create an instance locally without database inser
>>> user.address
>>> <Address: pk=1>

The more common use however will be in tests. These factories automatically get converted into pytest
The more common use however will be in tests. These factories automatically get converted into pytest
fixture functions using ``pytest-factoryboy``. Let's see how we would use this in ``test_awesomedb.py``.
::

Expand All @@ -359,9 +359,9 @@ fixture functions using ``pytest-factoryboy``. Let's see how we would use this
user = stupendous.User.get(stupendous.User.first=='New Bob')
assert user.first == 'New Bob'

Notice the lowercase-underscore syntax. This is the fixture name of the ``UserFactory``. The above examples
were written using the ``peeweee`` implementation. For real examples, see the sdss5db tests in
``tests/pwdbs/test_sdss5db.py`` and associated factories in ``test/pwdbs/factories.py``. The ``sqlalchemy``
Notice the lowercase-underscore syntax. This is the fixture name of the ``UserFactory``. The above examples
were written using the ``peeweee`` implementation. For real examples, see the sdss5db tests in
``tests/pwdbs/test_sdss5db.py`` and associated factories in ``test/pwdbs/factories.py``. The ``sqlalchemy``
version of defining a factory is very similar.
::

Expand All @@ -384,7 +384,7 @@ version of defining a factory is very similar.
Because ``sqlalchemy`` models cannot be imported when no database exists locally, we must use
``get_model_from_database`` to conditionally import the models we need, and place the factory class inside
a conditional. Additionally, the factory Meta class needs the ``sqlalchemy`` Session rather the database itself.
All other behaviours and defintions are the same. For examples of ``sqlalchemy`` factories and their uses, see
All other behaviours and defintions are the same. For examples of ``sqlalchemy`` factories and their uses, see
``tests/sqladbs/factories.py`` and the mangadb tests in ``tests/sqladbs/test_mangadb.py``.

Using a Generic Test Database
Expand All @@ -393,7 +393,7 @@ Using a Generic Test Database
Sometimes you may want to test a function common to many databases, or a generic database connection, or simply
not want to mess with real databases. In these cases, a temporary test postgres database is available to use.
By default, when no real database is passed into the ``database`` fixture function, the test database is generated.
For example, the ``peewee`` test example case from earlier would now be the following, with the pytest
For example, the ``peewee`` test example case from earlier would now be the following, with the pytest
parametrization line removed.
::

Expand All @@ -405,11 +405,11 @@ parametrization line removed.
assert user_ct > 0

This test would now use the temporary database, which is setup and destroyed for each test module. Because
the test database is created as a blank slate, all database models must be created as well, in addition to any
model factories. These models can be stored in the ``models.py`` file under the respective library directories.
the test database is created as a blank slate, all database models must be created as well, in addition to any
model factories. These models can be stored in the ``models.py`` file under the respective library directories.
See any of the ``models.py`` files for examples of creating test database models, and ``factories.py`` for their
associated factories. See any of the tests defined in ``test_factory.py`` for examples of how to write tests
against temporary database models defined in ``models.py``.
associated factories. See any of the tests defined in ``test_factory.py`` for examples of how to write tests
against temporary database models defined in ``models.py``.

Should I use Peewee or SQLAlchemy?
----------------------------------
Expand Down
Binary file added docs/sphinx/objects.inv
Binary file not shown.
2 changes: 1 addition & 1 deletion python/sdssdb/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ def get_model(self, table_name, schema=None):
return None

def get_introspector(self, schema=None):
"""Gets a Peewee database :pyclass:`peewee:Instrospector`."""
"""Gets a Peewee database :class:`peewee:Introspector`."""

schema_key = schema or ''

Expand Down

0 comments on commit 8cf631d

Please sign in to comment.