diff options
author | Olly Cope <olly@ollycope.com> | 2018-06-11 14:34:30 +0000 |
---|---|---|
committer | Olly Cope <olly@ollycope.com> | 2018-06-11 14:34:30 +0000 |
commit | 460725565c274223edcd6b6eb9020513361aa9a8 (patch) | |
tree | af3ae0862682c23c32abdcf6bb9885becfe7f782 /doc | |
parent | 343928b0df6d354c2d69b71f8105268998d27fd7 (diff) | |
download | yoyo-460725565c274223edcd6b6eb9020513361aa9a8.tar.gz |
Move documentation into sphinx
Diffstat (limited to 'doc')
-rw-r--r-- | doc/Makefile | 20 | ||||
-rw-r--r-- | doc/conf.py | 180 | ||||
-rw-r--r-- | doc/index.rst | 309 |
3 files changed, 509 insertions, 0 deletions
diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..cdf7c80 --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = ../.tox/py36/bin/sphinx-build +SPHINXPROJ = yoyo-migrations +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/doc/conf.py b/doc/conf.py new file mode 100644 index 0000000..f3f8dc0 --- /dev/null +++ b/doc/conf.py @@ -0,0 +1,180 @@ +# -*- coding: utf-8 -*- +# +# Configuration file for the Sphinx documentation builder. +# +# This file does only contain a selection of the most common options. For a +# full list see the documentation: +# http://www.sphinx-doc.org/en/master/config + +import re +import yoyo +import fresco_sphinx_theme + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- Project information ----------------------------------------------------- + +project = 'yoyo-migrations' +copyright = '2018, Oliver Cope' +author = 'Oliver Cope' + +# The full version, including alpha/beta/rc tags +release = yoyo.__version__ + +# The short X.Y version +version = re.sub(r'([\d\.]+)', r'\1', release) + + +# -- General configuration --------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.doctest', + 'sphinx.ext.intersphinx', + 'sphinx.ext.viewcode', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] +template_bridge = 'fresco_sphinx_theme.TemplateBridge' + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path . +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'fresco_sphinx_theme' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +html_theme_options = { + 'repo_url': 'https://bitbucket.org/ollyc/yoyo/', + 'issues_url': 'https://bitbucket.org/ollyc/yoyo/issues/', +} + +# Add any paths that contain custom themes here, relative to this directory. +html_theme_path = fresco_sphinx_theme.get_theme_paths() + +# 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'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# The default sidebars (for documents that don't match any pattern) are +# defined by theme itself. Builtin themes are using these templates by +# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', +# 'searchbox.html']``. +# +# html_sidebars = {} +html_show_sphinx = True +html_show_sourcelink = False + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = 'yoyo-migrationsdoc' + + +# -- Options for LaTeX output ------------------------------------------------ + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # 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]). +latex_documents = [ + (master_doc, 'yoyo-migrations.tex', 'yoyo-migrations Documentation', + 'Oliver Cope', 'manual'), +] + + +# -- Options for manual page output ------------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'yoyo-migrations', 'yoyo-migrations Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ---------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'yoyo-migrations', 'yoyo-migrations Documentation', + author, 'yoyo-migrations', 'One line description of project.', + 'Miscellaneous'), +] + + +# -- Extension configuration ------------------------------------------------- + +# -- Options for intersphinx extension --------------------------------------- + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'https://docs.python.org/': None} diff --git a/doc/index.rst b/doc/index.rst new file mode 100644 index 0000000..ea4ccf8 --- /dev/null +++ b/doc/index.rst @@ -0,0 +1,309 @@ +Yoyo database migrations +######################## + +Yoyo is a Python database schema migration tool. You write migrations as Python +scripts containing raw SQL statements or Python functions. +They can be as simple as this: + +.. code:: python + + # file: migrations/0001.create-foo.py + from yoyo import step + steps = [ + step("CREATE TABLE foo (id INT, bar VARCHAR(20), PRIMARY KEY (id))", + "DROP TABLE foo"), + ] + +Command line usage +================== + +Start a new migration:: + + yoyo new ./migrations -m "Add column to foo" + +Apply migrations from directory ``migrations`` to a PostgreSQL database:: + + yoyo apply --database postgresql://scott:tiger@localhost/db ./migrations + +Rollback migrations previously applied to a MySQL database:: + + yoyo rollback --database mysql://scott:tiger@localhost/database ./migrations + +Reapply (ie rollback then apply again) migrations to a SQLite database at +location ``/home/sheila/important.db``:: + + yoyo reapply --database sqlite:////home/sheila/important.db ./migrations + +By default, yoyo-migrations starts in an interactive mode, prompting you for +each migration file before applying it, making it easy to preview which +migrations to apply and rollback. + +Connections +----------- + +Database connections are specified using a URL. Examples:: + + # SQLite: use 4 slashes for an absolute database path on unix like platforms + database = sqlite:////home/user/mydb.sqlite + + # SQLite: use 3 slashes for a relative path + database = sqlite:///mydb.sqlite + + # SQLite: absolute path on Windows. + database = sqlite:///c:\home\user\mydb.sqlite + + # MySQL: Network database connection + database = mysql://scott:tiger@localhost/mydatabase + + # MySQL: unix socket connection + database = mysql://scott:tiger@/mydatabase?unix_socket=/tmp/mysql.sock + + # MySQL with the MySQLdb driver (instead of pymysql) + database = mysql+mysqldb://scott:tiger@localhost/mydatabase + + # PostgreSQL: database connection + database = postgresql://scott:tiger@localhost/mydatabase + + # PostgreSQL: unix socket connection + database = postgresql://scott:tiger@/mydatabase + + # PostgreSQL: changing the schema (via set search_path) + database = postgresql://scott:tiger@/mydatabase?schema=some_schema + +Password security +----------------- + +You can specify your database username and password either as part of the +database connection string on the command line (exposing your database +password in the process list) +or in a configuration file where other users may be able to read it. + +The ``-p`` or ``--prompt-password`` flag causes yoyo to prompt +for a password, helping prevent your credentials from being leaked. + +Migration files +=============== + +The migrations directory contains a series of migration scripts. Each +migration script is a python file (``.py``) containing a series of steps. Each +step should comprise a migration query and (optionally) a rollback query: + +.. code:: python + + # + # file: migrations/0001.create-foo.py + # + from yoyo import step + step( + "CREATE TABLE foo (id INT, bar VARCHAR(20), PRIMARY KEY (id))", + "DROP TABLE foo", + ) + +Migrations may also declare dependencies on earlier migrations via the +``__depends__`` attribute:: + + # + # file: migrations/0002.modify-foo.py + # + __depends__ = {'0001.create-foo'} + + step( + "ALTER TABLE foo ADD baz INT", + "ALTER TABLE foo DROP baz", + ) + + +The filename of each file (without the .py extension) is used as migration's +identifier. In the absence of a ``__depends__`` attribute, migrations +are applied in filename order, so it's useful to name your files using a date +(eg '20090115-xyz.py') or some other incrementing number. + +yoyo creates a table in your target database, ``_yoyo_migration``, to +track which migrations have been applied. + +Steps may also take an optional argument ``ignore_errors``, which must be one +of ``apply``, ``rollback``, or ``all``. If in the previous example the table +foo might have already been created by another means, we could add +``ignore_errors='apply'`` to the step to allow the migrations to continue +regardless:: + + # + # file: migrations/0001.create-foo.py + # + from yoyo import step + step( + "CREATE TABLE foo (id INT, bar VARCHAR(20), PRIMARY KEY (id))", + "DROP TABLE foo", + ignore_errors='apply', + ) + +Steps can also be python functions taking a database connection as +their only argument:: + + # + # file: migrations/0002.update-keys.py + # + from yoyo import step + def do_step(conn): + cursor = conn.cursor() + cursor.execute( + "INSERT INTO sysinfo " + " (osname, hostname, release, version, arch)" + " VALUES (%s, %s, %s, %s, %s %s)", + os.uname() + ) + + step(do_step) + +Post-apply hook +--------------- + +It can be useful to have a script that is run after every successful migration. +For example you could use this to update database permissions or re-create +views. +To do this, create a special migration file called ``post-apply.py``. +This file should have the same format as any other migration file. + + +Configuration file +================== + +Yoyo looks for a configuration file named ``yoyo.ini`` in the current working +directory or any ancestor directory. + +If no configuration file is found ``yoyo`` will prompt you to +create one, popuplated with the current command line args. + +Using a configuration file saves repeated typing, +avoids your database username and password showing in process listings +and lessens the risk of accidentally running migrations +against the wrong database (ie by re-running an earlier ``yoyo`` entry in +your command history when you have moved to a different directory). + +If you do not want a config file to be loaded +add the ``--no-config`` parameter to the command line options. + +The configuration file may contain the following options:: + + [DEFAULT] + + # List of migration source directories. "%(here)s" is expanded to the + # full path of the directory containing this ini file. + sources = %(here)s/migrations %(here)s/lib/module/migrations + + # Target database + database = postgresql://scott:tiger@localhost/mydb + + # Verbosity level. Goes from 0 (least verbose) to 3 (most verbose) + verbosity = 3 + + # Disable interactive features + batch_mode = on + + # Editor to use when starting new migrations + # "{}" is expanded to the filename of the new migration + editor = /usr/local/bin/vim -f {} + + # An arbitrary command to run after a migration has been created + # "{}" is expanded to the filename of the new migration + post_create_command = hg add {} + + # A prefix to use for generated migration filenames + prefix = myproject_ + + +Config file inheritance may be used to customize configuration per site:: + + # + # file: yoyo-defaults.ini + # + [DEFAULT] + sources = %(here)s/migrations + + # + # file: yoyo.ini + # + [DEFAULT] + + ; Inherit settings from yoyo-defaults.ini + %inherit = %(here)s/yoyo-defaults.ini + + ; Use '?' to avoid raising an error if the file does not exist + %inherit = ?%(here)s/yoyo-defaults.ini + + database = sqlite:///%(here)s/mydb.sqlite + +Transactions +============ + +Each migration runs in a separate transaction. Savepoints are used +to isolate steps within each migration. + +If an error occurs during a step and the step has ``ignore_errors`` set, +then that individual step will be rolled back and +execution will pick up from the next step. +If ``ignore_errors`` is not set then the entire migration will be rolled back +and execution stopped. + +Note that some databases (eg MySQL) do not support rollback on DDL statements +(eg ``CREATE ...`` and ``ALTER ...`` statements). For these databases +you may need to manually intervene to reset the database state +should errors occur in your migration. + +Using ``group`` allows you to nest steps, giving you control of where +rollbacks happen. For example:: + + group([ + step("ALTER TABLE employees ADD tax_code TEXT"), + step("CREATE INDEX tax_code_idx ON employees (tax_code)") + ], ignore_errors='all') + step("UPDATE employees SET tax_code='C' WHERE pay_grade < 4") + step("UPDATE employees SET tax_code='B' WHERE pay_grade >= 6") + step("UPDATE employees SET tax_code='A' WHERE pay_grade >= 8") + +Disabling transactions +---------------------- + +In PostgreSQL it is an error to run certain statements inside a transaction +block. These include: + +.. code::sql + + CREATE TABLE <foo> + ALTER TYPE <enum> ADD ... + +Migrations containing such statements should set +``__transactional__ = False``, eg: + +.. code::python + + __transactional__ = False + + step("CREATE DATABASE mydb", "DROP DATABASE mydb") + +Note that this feature is implemented for the PostgreSQL backend only. + + +Using yoyo from python code +=========================== + +The following example shows how to apply migrations from inside python code:: + + from yoyo import read_migrations + from yoyo import get_backend + + backend = get_backend('postgres://myuser@localhost/mydatabase') + migrations = read_migrations('path/to/migrations') + with backend.lock(): + backend.apply_migrations(backend.to_apply(migrations)) + +.. :vim:sw=4:et + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + +Changelog +========= + +.. include:: ../CHANGELOG.rst |