├── .gitignore ├── CHANGES ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README ├── docs ├── Makefile ├── _build │ ├── doctrees │ │ ├── api │ │ │ ├── column.doctree │ │ │ ├── database.doctree │ │ │ ├── foreignkey.doctree │ │ │ ├── index.doctree │ │ │ ├── option.doctree │ │ │ ├── schema.doctree │ │ │ └── table.doctree │ │ ├── changes.doctree │ │ ├── chap1.doctree │ │ ├── environment.pickle │ │ ├── generated │ │ │ ├── schemaobjects.column.doctree │ │ │ ├── schemaobjects.database.doctree │ │ │ ├── schemaobjects.index.doctree │ │ │ ├── schemaobjects.schema.doctree │ │ │ └── schemaobjects.table.doctree │ │ ├── index.doctree │ │ └── upgrading.doctree │ └── html │ │ ├── .buildinfo │ │ ├── .htaccess │ │ ├── _sources │ │ ├── api │ │ │ ├── column.txt │ │ │ ├── database.txt │ │ │ ├── foreignkey.txt │ │ │ ├── index.txt │ │ │ ├── option.txt │ │ │ ├── schema.txt │ │ │ └── table.txt │ │ ├── changes.txt │ │ ├── chap1.txt │ │ ├── generated │ │ │ ├── schemaobjects.column.txt │ │ │ ├── schemaobjects.database.txt │ │ │ ├── schemaobjects.index.txt │ │ │ ├── schemaobjects.schema.txt │ │ │ └── schemaobjects.table.txt │ │ ├── index.txt │ │ └── upgrading.txt │ │ ├── _static │ │ ├── basic.css │ │ ├── default.css │ │ ├── doctools.js │ │ ├── file.png │ │ ├── jquery.js │ │ ├── minus.png │ │ ├── plus.png │ │ ├── pygments.css │ │ └── searchtools.js │ │ ├── api │ │ ├── column.html │ │ ├── database.html │ │ ├── foreignkey.html │ │ ├── index.html │ │ ├── option.html │ │ ├── schema.html │ │ └── table.html │ │ ├── changes.html │ │ ├── chap1.html │ │ ├── generated │ │ ├── schemaobjects.column.html │ │ ├── schemaobjects.database.html │ │ ├── schemaobjects.index.html │ │ ├── schemaobjects.schema.html │ │ └── schemaobjects.table.html │ │ ├── genindex.html │ │ ├── index.html │ │ ├── modindex.html │ │ ├── objects.inv │ │ ├── search.html │ │ ├── searchindex.js │ │ └── upgrading.html ├── api │ ├── column.rst │ ├── database.rst │ ├── foreignkey.rst │ ├── index.rst │ ├── option.rst │ ├── schema.rst │ └── table.rst ├── changes.rst ├── conf.py ├── index.rst └── upgrading.rst ├── schemaobject ├── __init__.py ├── collections.py ├── column.py ├── connection.py ├── database.py ├── foreignkey.py ├── index.py ├── option.py ├── procedure.py ├── schema.py ├── table.py ├── trigger.py └── view.py ├── setup.py └── tests ├── __init__.py ├── test_collections.py ├── test_column.py ├── test_connection.py ├── test_database.py ├── test_db └── sakila-schema.sql ├── test_foreignkey.py ├── test_index.py ├── test_option.py ├── test_schema.py └── test_table.py /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | *egg-info 4 | *.pyc 5 | .* # Ignore all dotfiles... 6 | !.gitignore # except for .gitignore 7 | .idea -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | == 0.5.8 2 | * Fixed index dropping bug 3 | 4 | == 0.5.7 5 | * fixed bugs 6 | 7 | == 0.5.6 8 | * fixed bugs 9 | * merged with others commits 10 | 11 | == 0.5.5 12 | * merged with others commits 13 | 14 | == 0.5.4 15 | * Sub-parts of indices are now correctly added 16 | * Fixes error with foreign keys against unique or primary key columns 17 | 18 | == 0.5.3 / 2010-04-19 19 | * Column types are no longer forced uppercase 20 | - Fixes ENUM, SET bug where data values were incorrectly uppercased 21 | 22 | == 0.5.2 / 2009-12-02 23 | * Column DEFAULT fixes 24 | - strings are now quoted (except CURRENT_TIMESTAMP), 25 | - non-None values handled ('', 0) 26 | * CHARACTER SET AND COLLATE omitted from column definition if same as parent Table. 27 | * Fixed Column definition syntax (ordering of attributes) 28 | 29 | == 0.5.1 / 2009-11-10 30 | * bug fixes 31 | * updated test configuration, added more tests 32 | * added ability to retrieve comments on columns 33 | * updated collections.orderedDict 34 | - added support for index(), insert() and __delitem__() 35 | * changed output of database options 36 | - CHARSET is now CHARACTER SET 37 | - is now = 38 | * newlines and multiple spaces removed from table.create() string 39 | * Changed DatabaseConnection to explicitly require a call to connect() 40 | * backported to work with python 2.4 41 | * schema name dropped from table.alter() and table.drop() to be consistent with table.create() 42 | 43 | == 0.5.0 / 2009-09-17 44 | * Initial public release -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2009-2010 Mitch Matuson 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README CHANGES *.py 2 | recursive-include schemaobject *.py 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmatuson/SchemaObject/52d296b5a7824a3645995683f2a4913a968ff579/Makefile -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | SchemaObject v0.5.10 documentation 2 | +++++++++++++++++++++++++++++++++ 3 | 4 | SchemaObject provides a simple, easy to use Python object interface to a MySQL database schema. You can effortlessly write tools to test, validate, sync, migrate, or manage your schema as well as generate the SQL necessary to make changes to the it. 5 | 6 | Example 1: Verify all tables are InnoDB 7 | --------------------------------------- 8 | import schemaobject 9 | schema = schemaobject.SchemaObject('mysql://username:password@localhost:3306/mydb') 10 | tables = schema.databases['mydb'].tables #or schema.selected.tables 11 | for t in tables: 12 | assert tables[t].options['engine'].value == 'InnoDB' 13 | 14 | 15 | Example 2: Verify our MySQL instance is at least version 5.1 16 | ------------------------------------------------------------ 17 | import schemaobject 18 | schema = schemaobject.SchemaObject('mysql://username:password@localhost:3306/mydb') 19 | assert schema.version >= '5.1.0' 20 | 21 | 22 | Notes and Limitations 23 | --------------------- 24 | * SchemaObject instances are read-only. Modifying the object or calling create(), modify(), alter(), or drop() will not change your schema. 25 | * The MySQL User needs to have privileges to execute SELECT and SHOW statements, as well as access the INFORMATION_SCHEMA. 26 | * All Databases, Tables, Columns, Indexes, and Foreign Keys are lazily loaded. 27 | * SchemaObject does not load Events, Triggers, or Stored Procedures. 28 | 29 | 30 | Download and Install 31 | ==================== 32 | 33 | Prerequisites 34 | ------------- 35 | * SchemaObject has been tested against Python 3.9 36 | * To use SchemaObject, you need to have MySQL , version 5.0 or higher and MySQLdb , version 1.2.1p2 or higher installed. 37 | * To run the test suite, you need to install a copy of the Sakila Database , version 0.8 38 | 39 | 40 | Installing with easy_install 41 | ---------------------------- 42 | sudo easy_install schemaobject 43 | 44 | Installing the latest development version 45 | ----------------------------------------- 46 | git clone git://github.com/mmatuson/SchemaObject.git 47 | cd schemaobject 48 | sudo python setup.py install 49 | 50 | 51 | Status & License 52 | ================ 53 | SchemaObject is under active development and released under the Apache License, Version 2.0 . 54 | 55 | You can obtain a copy of the latest source code from the Git repository , or fork it on Github . 56 | 57 | You can report bugs via the SchemaObject Issues page 58 | 59 | Comments, questions, and feature requests can be sent to code at matuson dot com 60 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | 15 | .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest 16 | 17 | help: 18 | @echo "Please use \`make ' where is one of" 19 | @echo " html to make standalone HTML files" 20 | @echo " dirhtml to make HTML files named index.html in directories" 21 | @echo " pickle to make pickle files" 22 | @echo " json to make JSON files" 23 | @echo " htmlhelp to make HTML files and a HTML help project" 24 | @echo " qthelp to make HTML files and a qthelp project" 25 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 26 | @echo " changes to make an overview of all changed/added/deprecated items" 27 | @echo " linkcheck to check all external links for integrity" 28 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 29 | 30 | clean: 31 | -rm -rf $(BUILDDIR)/* 32 | 33 | html: 34 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 35 | @echo 36 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 37 | 38 | dirhtml: 39 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 40 | @echo 41 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 42 | 43 | pickle: 44 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 45 | @echo 46 | @echo "Build finished; now you can process the pickle files." 47 | 48 | json: 49 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 50 | @echo 51 | @echo "Build finished; now you can process the JSON files." 52 | 53 | htmlhelp: 54 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 55 | @echo 56 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 57 | ".hhp project file in $(BUILDDIR)/htmlhelp." 58 | 59 | qthelp: 60 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 61 | @echo 62 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 63 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 64 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/SchemaObjects.qhcp" 65 | @echo "To view the help file:" 66 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/SchemaObjects.qhc" 67 | 68 | latex: 69 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 70 | @echo 71 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 72 | @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ 73 | "run these through (pdf)latex." 74 | 75 | changes: 76 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 77 | @echo 78 | @echo "The overview file is in $(BUILDDIR)/changes." 79 | 80 | linkcheck: 81 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 82 | @echo 83 | @echo "Link check complete; look for any errors in the above output " \ 84 | "or in $(BUILDDIR)/linkcheck/output.txt." 85 | 86 | doctest: 87 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 88 | @echo "Testing of doctests in the sources finished, look at the " \ 89 | "results in $(BUILDDIR)/doctest/output.txt." 90 | -------------------------------------------------------------------------------- /docs/_build/doctrees/api/column.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmatuson/SchemaObject/52d296b5a7824a3645995683f2a4913a968ff579/docs/_build/doctrees/api/column.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/api/database.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmatuson/SchemaObject/52d296b5a7824a3645995683f2a4913a968ff579/docs/_build/doctrees/api/database.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/api/foreignkey.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmatuson/SchemaObject/52d296b5a7824a3645995683f2a4913a968ff579/docs/_build/doctrees/api/foreignkey.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/api/index.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmatuson/SchemaObject/52d296b5a7824a3645995683f2a4913a968ff579/docs/_build/doctrees/api/index.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/api/option.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmatuson/SchemaObject/52d296b5a7824a3645995683f2a4913a968ff579/docs/_build/doctrees/api/option.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/api/schema.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmatuson/SchemaObject/52d296b5a7824a3645995683f2a4913a968ff579/docs/_build/doctrees/api/schema.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/api/table.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmatuson/SchemaObject/52d296b5a7824a3645995683f2a4913a968ff579/docs/_build/doctrees/api/table.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/changes.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmatuson/SchemaObject/52d296b5a7824a3645995683f2a4913a968ff579/docs/_build/doctrees/changes.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/chap1.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmatuson/SchemaObject/52d296b5a7824a3645995683f2a4913a968ff579/docs/_build/doctrees/chap1.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/environment.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmatuson/SchemaObject/52d296b5a7824a3645995683f2a4913a968ff579/docs/_build/doctrees/environment.pickle -------------------------------------------------------------------------------- /docs/_build/doctrees/generated/schemaobjects.column.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmatuson/SchemaObject/52d296b5a7824a3645995683f2a4913a968ff579/docs/_build/doctrees/generated/schemaobjects.column.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/generated/schemaobjects.database.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmatuson/SchemaObject/52d296b5a7824a3645995683f2a4913a968ff579/docs/_build/doctrees/generated/schemaobjects.database.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/generated/schemaobjects.index.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmatuson/SchemaObject/52d296b5a7824a3645995683f2a4913a968ff579/docs/_build/doctrees/generated/schemaobjects.index.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/generated/schemaobjects.schema.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmatuson/SchemaObject/52d296b5a7824a3645995683f2a4913a968ff579/docs/_build/doctrees/generated/schemaobjects.schema.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/generated/schemaobjects.table.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmatuson/SchemaObject/52d296b5a7824a3645995683f2a4913a968ff579/docs/_build/doctrees/generated/schemaobjects.table.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/index.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmatuson/SchemaObject/52d296b5a7824a3645995683f2a4913a968ff579/docs/_build/doctrees/index.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/upgrading.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmatuson/SchemaObject/52d296b5a7824a3645995683f2a4913a968ff579/docs/_build/doctrees/upgrading.doctree -------------------------------------------------------------------------------- /docs/_build/html/.buildinfo: -------------------------------------------------------------------------------- 1 | # Sphinx build info version 1 2 | # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. 3 | config: 41efa8991f9b3ff776b1503fe53cd134 4 | tags: fbb0d17656682115ca4d033fb2f83ba1 5 | -------------------------------------------------------------------------------- /docs/_build/html/.htaccess: -------------------------------------------------------------------------------- 1 | DirectoryIndex index.html -------------------------------------------------------------------------------- /docs/_build/html/_sources/api/column.txt: -------------------------------------------------------------------------------- 1 | =============== 2 | Column 3 | =============== 4 | 5 | .. automodule:: schemaobject.column 6 | :members: -------------------------------------------------------------------------------- /docs/_build/html/_sources/api/database.txt: -------------------------------------------------------------------------------- 1 | =============== 2 | Database 3 | =============== 4 | 5 | .. automodule:: schemaobject.database 6 | :members: 7 | 8 | -------------------------------------------------------------------------------- /docs/_build/html/_sources/api/foreignkey.txt: -------------------------------------------------------------------------------- 1 | =============== 2 | Foreign Key 3 | =============== 4 | 5 | .. automodule:: schemaobject.foreignkey 6 | :members: -------------------------------------------------------------------------------- /docs/_build/html/_sources/api/index.txt: -------------------------------------------------------------------------------- 1 | =============== 2 | Index 3 | =============== 4 | 5 | .. automodule:: schemaobject.index 6 | :members: 7 | 8 | .. classmethod:: format_sub_part(field, length) -------------------------------------------------------------------------------- /docs/_build/html/_sources/api/option.txt: -------------------------------------------------------------------------------- 1 | =============== 2 | Option 3 | =============== 4 | 5 | .. automodule:: schemaobject.option 6 | :members: -------------------------------------------------------------------------------- /docs/_build/html/_sources/api/schema.txt: -------------------------------------------------------------------------------- 1 | ======================== 2 | Schema (MySQL Instance) 3 | ======================== 4 | 5 | .. automodule:: schemaobject.schema 6 | :members: -------------------------------------------------------------------------------- /docs/_build/html/_sources/api/table.txt: -------------------------------------------------------------------------------- 1 | =============== 2 | Table 3 | =============== 4 | 5 | .. automodule:: schemaobject.table 6 | :members: -------------------------------------------------------------------------------- /docs/_build/html/_sources/changes.txt: -------------------------------------------------------------------------------- 1 | ======================= 2 | Changes to SchemaObject 3 | ======================= 4 | .. include:: ../CHANGES 5 | -------------------------------------------------------------------------------- /docs/_build/html/_sources/chap1.txt: -------------------------------------------------------------------------------- 1 | Database 2 | ================ 3 | Some text, *italic text*, **bold text** 4 | 5 | * bulleted list. There needs to be a space right after the "*" 6 | * item 2 7 | 8 | .. note:: 9 | This is a note. 10 | 11 | Here's some Python code: 12 | 13 | >>> for i in range(10): 14 | ... print i -------------------------------------------------------------------------------- /docs/_build/html/_sources/generated/schemaobjects.column.txt: -------------------------------------------------------------------------------- 1 | :mod:`schemaobjects.column` 2 | =========================== 3 | 4 | 5 | .. automodule:: schemaobjects.column 6 | 7 | 8 | Functions 9 | ---------- 10 | 11 | .. autofunction:: column_schema_builder 12 | 13 | 14 | 15 | 16 | Classes 17 | -------- 18 | 19 | .. autoclass:: ColumnSchema 20 | :show-inheritance: 21 | :members: 22 | :inherited-members: 23 | :undoc-members: 24 | 25 | 26 | .. autoclass:: OrderedDict 27 | :show-inheritance: 28 | :members: 29 | :inherited-members: 30 | :undoc-members: 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /docs/_build/html/_sources/generated/schemaobjects.database.txt: -------------------------------------------------------------------------------- 1 | :mod:`schemaobjects.database` 2 | ============================= 3 | 4 | 5 | .. automodule:: schemaobjects.database 6 | 7 | 8 | Functions 9 | ---------- 10 | 11 | .. autofunction:: database_schema_builder 12 | 13 | .. autofunction:: table_schema_builder 14 | 15 | 16 | 17 | 18 | Classes 19 | -------- 20 | 21 | .. autoclass:: DatabaseSchema 22 | :show-inheritance: 23 | :members: 24 | :inherited-members: 25 | :undoc-members: 26 | 27 | 28 | .. autoclass:: OrderedDict 29 | :show-inheritance: 30 | :members: 31 | :inherited-members: 32 | :undoc-members: 33 | 34 | 35 | .. autoclass:: SchemaOption 36 | :show-inheritance: 37 | :members: 38 | :inherited-members: 39 | :undoc-members: 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /docs/_build/html/_sources/generated/schemaobjects.index.txt: -------------------------------------------------------------------------------- 1 | :mod:`schemaobjects.index` 2 | ========================== 3 | 4 | 5 | .. automodule:: schemaobjects.index 6 | 7 | 8 | Functions 9 | ---------- 10 | 11 | .. autofunction:: index_schema_builder 12 | 13 | 14 | 15 | 16 | Classes 17 | -------- 18 | 19 | .. autoclass:: IndexSchema 20 | :show-inheritance: 21 | :members: 22 | :inherited-members: 23 | :undoc-members: 24 | 25 | 26 | .. autoclass:: OrderedDict 27 | :show-inheritance: 28 | :members: 29 | :inherited-members: 30 | :undoc-members: 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /docs/_build/html/_sources/generated/schemaobjects.schema.txt: -------------------------------------------------------------------------------- 1 | :mod:`schemaobjects.schema` 2 | =========================== 3 | 4 | 5 | .. automodule:: schemaobjects.schema 6 | 7 | 8 | Functions 9 | ---------- 10 | 11 | .. autofunction:: database_schema_builder 12 | 13 | 14 | 15 | 16 | Classes 17 | -------- 18 | 19 | .. autoclass:: DatabaseConnection 20 | :show-inheritance: 21 | :members: 22 | :inherited-members: 23 | :undoc-members: 24 | 25 | 26 | .. autoclass:: SchemaObject 27 | :show-inheritance: 28 | :members: 29 | :inherited-members: 30 | :undoc-members: 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /docs/_build/html/_sources/generated/schemaobjects.table.txt: -------------------------------------------------------------------------------- 1 | :mod:`schemaobjects.table` 2 | ========================== 3 | 4 | 5 | .. automodule:: schemaobjects.table 6 | 7 | 8 | Functions 9 | ---------- 10 | 11 | .. autofunction:: column_schema_builder 12 | 13 | .. autofunction:: foreign_key_schema_builder 14 | 15 | .. autofunction:: index_schema_builder 16 | 17 | .. autofunction:: table_schema_builder 18 | 19 | 20 | 21 | 22 | Classes 23 | -------- 24 | 25 | .. autoclass:: OrderedDict 26 | :show-inheritance: 27 | :members: 28 | :inherited-members: 29 | :undoc-members: 30 | 31 | 32 | .. autoclass:: SchemaOption 33 | :show-inheritance: 34 | :members: 35 | :inherited-members: 36 | :undoc-members: 37 | 38 | 39 | .. autoclass:: TableSchema 40 | :show-inheritance: 41 | :members: 42 | :inherited-members: 43 | :undoc-members: 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /docs/_build/html/_sources/index.txt: -------------------------------------------------------------------------------- 1 | SchemaObject v0.5.2 documentation 2 | +++++++++++++++++++++++++++++++++ 3 | 4 | Introduction and Examples 5 | ------------------------- 6 | SchemaObject provides a simple, easy to use Python object interface to a MySQL database schema. You can effortlessly write tools to test, validate, sync, migrate, or manage your schema as well as generate the SQL necessary to make changes to it. 7 | 8 | **Verify all tables are InnoDB** 9 | 10 | :: 11 | 12 | import schemaobject 13 | schema = schemaobject.SchemaObject('mysql://username:password@localhost:3306/mydb') 14 | tables = schema.databases['mydb'].tables #or schema.selected.tables 15 | for t in tables: 16 | assert tables[t].options['engine'].value == 'InnoDB' 17 | 18 | 19 | **Verify our MySQL instance is at least version 5.1** 20 | 21 | :: 22 | 23 | import schemaobject 24 | schema = schemaobject.SchemaObject('mysql://username:password@localhost:3306/mydb') 25 | assert schema.version >= '5.1.0' 26 | 27 | 28 | **Notes and Limitations** 29 | 30 | * SchemaObject instances are read-only. Modifying the object or calling create(), modify(), alter(), or drop() will not change your schema. 31 | * The MySQL User needs to have privileges to execute SELECT and SHOW statements, as well as access the INFORMATION_SCHEMA. 32 | * All Databases, Tables, Columns, Indexes, and Foreign Keys are lazily loaded. 33 | * SchemaObject does not load Events, Triggers, or Stored Procedures. 34 | 35 | What's New in This Version 36 | -------------------------- 37 | See the history of :doc:`CHANGES ` 38 | 39 | .. _installing: 40 | 41 | Download and Install 42 | -------------------- 43 | 44 | **Prerequisites** 45 | 46 | * SchemaObject has been tested against Python 2.4, 2.5, and 2.6. 47 | * To use SchemaObject, you need to have `MySQL `_, version 5.0 or higher and `MySQLdb `_, version 1.2.1p2 or higher installed. 48 | * To run the test suite, you need to install a copy of the `Sakila Database `_, version 0.8 49 | 50 | **Standard Installation** 51 | 52 | Download `SchemaObject-0.5.2 `_ 53 | :: 54 | tar xvzf SchemaObject-0.5.2.tar.gz 55 | cd SchemaObject-0.5.2 56 | sudo python setup.py install 57 | 58 | **Installing with easy_install** 59 | :: 60 | 61 | sudo easy_install schemaobject 62 | 63 | **Installing the latest development version** 64 | :: 65 | 66 | git clone git://github.com/mmatuson/SchemaObject.git 67 | cd schemaobject 68 | sudo python setup.py install 69 | 70 | To upgrade to a new version of SchemaObject, see :doc:`Upgrading ` 71 | 72 | Documentation 73 | ------------- 74 | .. toctree:: 75 | :maxdepth: 1 76 | 77 | api/schema.rst 78 | api/database.rst 79 | api/table.rst 80 | api/column.rst 81 | api/index.rst 82 | api/foreignkey.rst 83 | api/option.rst 84 | 85 | 86 | Projects using SchemaObject 87 | --------------------------- 88 | `Schema Sync `_ - a MySQL schema versioning and migration utility 89 | 90 | 91 | Status & License 92 | ----------------- 93 | SchemaObject is under active development and released under the `Apache License, Version 2.0 `_. 94 | 95 | You can obtain a copy of the latest source code from the `Git repository `_, or fork it on `Github `_. 96 | 97 | You can report bugs via the `SchemaObject Issues page `_. 98 | 99 | Comments, questions, and feature requests can be sent to code at matuson dot com 100 | 101 | -------------------------------------------------------------------------------- /docs/_build/html/_sources/upgrading.txt: -------------------------------------------------------------------------------- 1 | ===================== 2 | Upgrading SchemaObject 3 | ===================== 4 | 5 | Upgrading with easy_install 6 | -------------------------- 7 | :: 8 | 9 | sudo easy_install --upgrade schemaobject 10 | 11 | 12 | Upgrading the Standard Installation 13 | ----------------------------------- 14 | 15 | If you installed SchemaObject using setup.py install, you can upgrade by deleting the SchemaObject directory from your Python site-packages (or virtualenv) and :ref:`re-installing ` the new version. 16 | 17 | **Where are my site-packages stored?** 18 | 19 | The location of the site-packages directory depends on the operating system, and the location in which Python was installed. To find out your system’s site-packages location, execute the following: 20 | :: 21 | 22 | python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()" 23 | 24 | Note that this should be run from a shell prompt, not a Python interactive prompt. 25 | Thanks to `the Django install page `_ for these helpful instructions. 26 | 27 | -------------------------------------------------------------------------------- /docs/_build/html/_static/basic.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Sphinx stylesheet -- basic theme 3 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | */ 5 | 6 | /* -- main layout ----------------------------------------------------------- */ 7 | 8 | div.clearer { 9 | clear: both; 10 | } 11 | 12 | /* -- relbar ---------------------------------------------------------------- */ 13 | 14 | div.related { 15 | width: 100%; 16 | font-size: 90%; 17 | } 18 | 19 | div.related h3 { 20 | display: none; 21 | } 22 | 23 | div.related ul { 24 | margin: 0; 25 | padding: 0 0 0 10px; 26 | list-style: none; 27 | } 28 | 29 | div.related li { 30 | display: inline; 31 | } 32 | 33 | div.related li.right { 34 | float: right; 35 | margin-right: 5px; 36 | } 37 | 38 | /* -- sidebar --------------------------------------------------------------- */ 39 | 40 | div.sphinxsidebarwrapper { 41 | padding: 10px 5px 0 10px; 42 | } 43 | 44 | div.sphinxsidebar { 45 | float: left; 46 | width: 230px; 47 | margin-left: -100%; 48 | font-size: 90%; 49 | } 50 | 51 | div.sphinxsidebar ul { 52 | list-style: none; 53 | } 54 | 55 | div.sphinxsidebar ul ul, 56 | div.sphinxsidebar ul.want-points { 57 | margin-left: 20px; 58 | list-style: square; 59 | } 60 | 61 | div.sphinxsidebar ul ul { 62 | margin-top: 0; 63 | margin-bottom: 0; 64 | } 65 | 66 | div.sphinxsidebar form { 67 | margin-top: 10px; 68 | } 69 | 70 | div.sphinxsidebar input { 71 | border: 1px solid #98dbcc; 72 | font-family: sans-serif; 73 | font-size: 1em; 74 | } 75 | 76 | img { 77 | border: 0; 78 | } 79 | 80 | /* -- search page ----------------------------------------------------------- */ 81 | 82 | ul.search { 83 | margin: 10px 0 0 20px; 84 | padding: 0; 85 | } 86 | 87 | ul.search li { 88 | padding: 5px 0 5px 20px; 89 | background-image: url(file.png); 90 | background-repeat: no-repeat; 91 | background-position: 0 7px; 92 | } 93 | 94 | ul.search li a { 95 | font-weight: bold; 96 | } 97 | 98 | ul.search li div.context { 99 | color: #888; 100 | margin: 2px 0 0 30px; 101 | text-align: left; 102 | } 103 | 104 | ul.keywordmatches li.goodmatch a { 105 | font-weight: bold; 106 | } 107 | 108 | /* -- index page ------------------------------------------------------------ */ 109 | 110 | table.contentstable { 111 | width: 90%; 112 | } 113 | 114 | table.contentstable p.biglink { 115 | line-height: 150%; 116 | } 117 | 118 | a.biglink { 119 | font-size: 1.3em; 120 | } 121 | 122 | span.linkdescr { 123 | font-style: italic; 124 | padding-top: 5px; 125 | font-size: 90%; 126 | } 127 | 128 | /* -- general index --------------------------------------------------------- */ 129 | 130 | table.indextable td { 131 | text-align: left; 132 | vertical-align: top; 133 | } 134 | 135 | table.indextable dl, table.indextable dd { 136 | margin-top: 0; 137 | margin-bottom: 0; 138 | } 139 | 140 | table.indextable tr.pcap { 141 | height: 10px; 142 | } 143 | 144 | table.indextable tr.cap { 145 | margin-top: 10px; 146 | background-color: #f2f2f2; 147 | } 148 | 149 | img.toggler { 150 | margin-right: 3px; 151 | margin-top: 3px; 152 | cursor: pointer; 153 | } 154 | 155 | /* -- general body styles --------------------------------------------------- */ 156 | 157 | a.headerlink { 158 | visibility: hidden; 159 | } 160 | 161 | h1:hover > a.headerlink, 162 | h2:hover > a.headerlink, 163 | h3:hover > a.headerlink, 164 | h4:hover > a.headerlink, 165 | h5:hover > a.headerlink, 166 | h6:hover > a.headerlink, 167 | dt:hover > a.headerlink { 168 | visibility: visible; 169 | } 170 | 171 | div.body p.caption { 172 | text-align: inherit; 173 | } 174 | 175 | div.body td { 176 | text-align: left; 177 | } 178 | 179 | .field-list ul { 180 | padding-left: 1em; 181 | } 182 | 183 | .first { 184 | margin-top: 0 !important; 185 | } 186 | 187 | p.rubric { 188 | margin-top: 30px; 189 | font-weight: bold; 190 | } 191 | 192 | /* -- sidebars -------------------------------------------------------------- */ 193 | 194 | div.sidebar { 195 | margin: 0 0 0.5em 1em; 196 | border: 1px solid #ddb; 197 | padding: 7px 7px 0 7px; 198 | background-color: #ffe; 199 | width: 40%; 200 | float: right; 201 | } 202 | 203 | p.sidebar-title { 204 | font-weight: bold; 205 | } 206 | 207 | /* -- topics ---------------------------------------------------------------- */ 208 | 209 | div.topic { 210 | border: 1px solid #ccc; 211 | padding: 7px 7px 0 7px; 212 | margin: 10px 0 10px 0; 213 | } 214 | 215 | p.topic-title { 216 | font-size: 1.1em; 217 | font-weight: bold; 218 | margin-top: 10px; 219 | } 220 | 221 | /* -- admonitions ----------------------------------------------------------- */ 222 | 223 | div.admonition { 224 | margin-top: 10px; 225 | margin-bottom: 10px; 226 | padding: 7px; 227 | } 228 | 229 | div.admonition dt { 230 | font-weight: bold; 231 | } 232 | 233 | div.admonition dl { 234 | margin-bottom: 0; 235 | } 236 | 237 | p.admonition-title { 238 | margin: 0px 10px 5px 0px; 239 | font-weight: bold; 240 | } 241 | 242 | div.body p.centered { 243 | text-align: center; 244 | margin-top: 25px; 245 | } 246 | 247 | /* -- tables ---------------------------------------------------------------- */ 248 | 249 | table.docutils { 250 | border: 0; 251 | border-collapse: collapse; 252 | } 253 | 254 | table.docutils td, table.docutils th { 255 | padding: 1px 8px 1px 0; 256 | border-top: 0; 257 | border-left: 0; 258 | border-right: 0; 259 | border-bottom: 1px solid #aaa; 260 | } 261 | 262 | table.field-list td, table.field-list th { 263 | border: 0 !important; 264 | } 265 | 266 | table.footnote td, table.footnote th { 267 | border: 0 !important; 268 | } 269 | 270 | th { 271 | text-align: left; 272 | padding-right: 5px; 273 | } 274 | 275 | /* -- other body styles ----------------------------------------------------- */ 276 | 277 | dl { 278 | margin-bottom: 15px; 279 | } 280 | 281 | dd p { 282 | margin-top: 0px; 283 | } 284 | 285 | dd ul, dd table { 286 | margin-bottom: 10px; 287 | } 288 | 289 | dd { 290 | margin-top: 3px; 291 | margin-bottom: 10px; 292 | margin-left: 30px; 293 | } 294 | 295 | dt:target, .highlight { 296 | background-color: #fbe54e; 297 | } 298 | 299 | dl.glossary dt { 300 | font-weight: bold; 301 | font-size: 1.1em; 302 | } 303 | 304 | .field-list ul { 305 | margin: 0; 306 | padding-left: 1em; 307 | } 308 | 309 | .field-list p { 310 | margin: 0; 311 | } 312 | 313 | .refcount { 314 | color: #060; 315 | } 316 | 317 | .optional { 318 | font-size: 1.3em; 319 | } 320 | 321 | .versionmodified { 322 | font-style: italic; 323 | } 324 | 325 | .system-message { 326 | background-color: #fda; 327 | padding: 5px; 328 | border: 3px solid red; 329 | } 330 | 331 | .footnote:target { 332 | background-color: #ffa 333 | } 334 | 335 | /* -- code displays --------------------------------------------------------- */ 336 | 337 | pre { 338 | overflow: auto; 339 | } 340 | 341 | td.linenos pre { 342 | padding: 5px 0px; 343 | border: 0; 344 | background-color: transparent; 345 | color: #aaa; 346 | } 347 | 348 | table.highlighttable { 349 | margin-left: 0.5em; 350 | } 351 | 352 | table.highlighttable td { 353 | padding: 0 0.5em 0 0.5em; 354 | } 355 | 356 | tt.descname { 357 | background-color: transparent; 358 | font-weight: bold; 359 | font-size: 1.2em; 360 | } 361 | 362 | tt.descclassname { 363 | background-color: transparent; 364 | } 365 | 366 | tt.xref, a tt { 367 | background-color: transparent; 368 | font-weight: bold; 369 | } 370 | 371 | h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { 372 | background-color: transparent; 373 | } 374 | 375 | /* -- math display ---------------------------------------------------------- */ 376 | 377 | img.math { 378 | vertical-align: middle; 379 | } 380 | 381 | div.body div.math p { 382 | text-align: center; 383 | } 384 | 385 | span.eqno { 386 | float: right; 387 | } 388 | 389 | /* -- printout stylesheet --------------------------------------------------- */ 390 | 391 | @media print { 392 | div.document, 393 | div.documentwrapper, 394 | div.bodywrapper { 395 | margin: 0; 396 | width: 100%; 397 | } 398 | 399 | div.sphinxsidebar, 400 | div.related, 401 | div.footer, 402 | #top-link { 403 | display: none; 404 | } 405 | } 406 | -------------------------------------------------------------------------------- /docs/_build/html/_static/default.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Sphinx stylesheet -- default theme 3 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | */ 5 | 6 | @import url("basic.css"); 7 | 8 | /* -- page layout ----------------------------------------------------------- */ 9 | 10 | body { 11 | font-family: sans-serif; 12 | font-size: 100%; 13 | background-color: #11303d; 14 | color: #000; 15 | margin: 0; 16 | padding: 0; 17 | } 18 | 19 | div.document { 20 | background-color: #1c4e63; 21 | } 22 | 23 | div.documentwrapper { 24 | float: left; 25 | width: 100%; 26 | } 27 | 28 | div.bodywrapper { 29 | margin: 0 0 0 230px; 30 | } 31 | 32 | div.body { 33 | background-color: #ffffff; 34 | color: #000000; 35 | padding: 0 20px 30px 20px; 36 | } 37 | 38 | div.footer { 39 | color: #ffffff; 40 | width: 100%; 41 | padding: 9px 0 9px 0; 42 | text-align: center; 43 | font-size: 75%; 44 | } 45 | 46 | div.footer a { 47 | color: #ffffff; 48 | text-decoration: underline; 49 | } 50 | 51 | div.related { 52 | background-color: #133f52; 53 | line-height: 30px; 54 | color: #ffffff; 55 | } 56 | 57 | div.related a { 58 | color: #ffffff; 59 | } 60 | 61 | div.sphinxsidebar { 62 | } 63 | 64 | div.sphinxsidebar h3 { 65 | font-family: 'Trebuchet MS', sans-serif; 66 | color: #ffffff; 67 | font-size: 1.4em; 68 | font-weight: normal; 69 | margin: 0; 70 | padding: 0; 71 | } 72 | 73 | div.sphinxsidebar h3 a { 74 | color: #ffffff; 75 | } 76 | 77 | div.sphinxsidebar h4 { 78 | font-family: 'Trebuchet MS', sans-serif; 79 | color: #ffffff; 80 | font-size: 1.3em; 81 | font-weight: normal; 82 | margin: 5px 0 0 0; 83 | padding: 0; 84 | } 85 | 86 | div.sphinxsidebar p { 87 | color: #ffffff; 88 | } 89 | 90 | div.sphinxsidebar p.topless { 91 | margin: 5px 10px 10px 10px; 92 | } 93 | 94 | div.sphinxsidebar ul { 95 | margin: 10px; 96 | padding: 0; 97 | color: #ffffff; 98 | } 99 | 100 | div.sphinxsidebar a { 101 | color: #98dbcc; 102 | } 103 | 104 | div.sphinxsidebar input { 105 | border: 1px solid #98dbcc; 106 | font-family: sans-serif; 107 | font-size: 1em; 108 | } 109 | 110 | /* -- body styles ----------------------------------------------------------- */ 111 | 112 | a { 113 | color: #355f7c; 114 | text-decoration: none; 115 | } 116 | 117 | a:hover { 118 | text-decoration: underline; 119 | } 120 | 121 | div.body p, div.body dd, div.body li { 122 | text-align: justify; 123 | line-height: 130%; 124 | } 125 | 126 | div.body h1, 127 | div.body h2, 128 | div.body h3, 129 | div.body h4, 130 | div.body h5, 131 | div.body h6 { 132 | font-family: 'Trebuchet MS', sans-serif; 133 | background-color: #f2f2f2; 134 | font-weight: normal; 135 | color: #20435c; 136 | border-bottom: 1px solid #ccc; 137 | margin: 20px -20px 10px -20px; 138 | padding: 3px 0 3px 10px; 139 | } 140 | 141 | div.body h1 { margin-top: 0; font-size: 200%; } 142 | div.body h2 { font-size: 160%; } 143 | div.body h3 { font-size: 140%; } 144 | div.body h4 { font-size: 120%; } 145 | div.body h5 { font-size: 110%; } 146 | div.body h6 { font-size: 100%; } 147 | 148 | a.headerlink { 149 | color: #c60f0f; 150 | font-size: 0.8em; 151 | padding: 0 4px 0 4px; 152 | text-decoration: none; 153 | } 154 | 155 | a.headerlink:hover { 156 | background-color: #c60f0f; 157 | color: white; 158 | } 159 | 160 | div.body p, div.body dd, div.body li { 161 | text-align: justify; 162 | line-height: 130%; 163 | } 164 | 165 | div.admonition p.admonition-title + p { 166 | display: inline; 167 | } 168 | 169 | div.note { 170 | background-color: #eee; 171 | border: 1px solid #ccc; 172 | } 173 | 174 | div.seealso { 175 | background-color: #ffc; 176 | border: 1px solid #ff6; 177 | } 178 | 179 | div.topic { 180 | background-color: #eee; 181 | } 182 | 183 | div.warning { 184 | background-color: #ffe4e4; 185 | border: 1px solid #f66; 186 | } 187 | 188 | p.admonition-title { 189 | display: inline; 190 | } 191 | 192 | p.admonition-title:after { 193 | content: ":"; 194 | } 195 | 196 | pre { 197 | padding: 5px; 198 | background-color: #eeffcc; 199 | color: #333333; 200 | line-height: 120%; 201 | border: 1px solid #ac9; 202 | border-left: none; 203 | border-right: none; 204 | } 205 | 206 | tt { 207 | background-color: #ecf0f3; 208 | padding: 0 1px 0 1px; 209 | font-size: 0.95em; 210 | } -------------------------------------------------------------------------------- /docs/_build/html/_static/doctools.js: -------------------------------------------------------------------------------- 1 | /// XXX: make it cross browser 2 | 3 | /** 4 | * make the code below compatible with browsers without 5 | * an installed firebug like debugger 6 | */ 7 | if (!window.console || !console.firebug) { 8 | var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml", 9 | "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"]; 10 | window.console = {}; 11 | for (var i = 0; i < names.length; ++i) 12 | window.console[names[i]] = function() {} 13 | } 14 | 15 | /** 16 | * small helper function to urldecode strings 17 | */ 18 | jQuery.urldecode = function(x) { 19 | return decodeURIComponent(x).replace(/\+/g, ' '); 20 | } 21 | 22 | /** 23 | * small helper function to urlencode strings 24 | */ 25 | jQuery.urlencode = encodeURIComponent; 26 | 27 | /** 28 | * This function returns the parsed url parameters of the 29 | * current request. Multiple values per key are supported, 30 | * it will always return arrays of strings for the value parts. 31 | */ 32 | jQuery.getQueryParameters = function(s) { 33 | if (typeof s == 'undefined') 34 | s = document.location.search; 35 | var parts = s.substr(s.indexOf('?') + 1).split('&'); 36 | var result = {}; 37 | for (var i = 0; i < parts.length; i++) { 38 | var tmp = parts[i].split('=', 2); 39 | var key = jQuery.urldecode(tmp[0]); 40 | var value = jQuery.urldecode(tmp[1]); 41 | if (key in result) 42 | result[key].push(value); 43 | else 44 | result[key] = [value]; 45 | } 46 | return result; 47 | } 48 | 49 | /** 50 | * small function to check if an array contains 51 | * a given item. 52 | */ 53 | jQuery.contains = function(arr, item) { 54 | for (var i = 0; i < arr.length; i++) { 55 | if (arr[i] == item) 56 | return true; 57 | } 58 | return false; 59 | } 60 | 61 | /** 62 | * highlight a given string on a jquery object by wrapping it in 63 | * span elements with the given class name. 64 | */ 65 | jQuery.fn.highlightText = function(text, className) { 66 | function highlight(node) { 67 | if (node.nodeType == 3) { 68 | var val = node.nodeValue; 69 | var pos = val.toLowerCase().indexOf(text); 70 | if (pos >= 0 && !jQuery.className.has(node.parentNode, className)) { 71 | var span = document.createElement("span"); 72 | span.className = className; 73 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 74 | node.parentNode.insertBefore(span, node.parentNode.insertBefore( 75 | document.createTextNode(val.substr(pos + text.length)), 76 | node.nextSibling)); 77 | node.nodeValue = val.substr(0, pos); 78 | } 79 | } 80 | else if (!jQuery(node).is("button, select, textarea")) { 81 | jQuery.each(node.childNodes, function() { 82 | highlight(this) 83 | }); 84 | } 85 | } 86 | return this.each(function() { 87 | highlight(this); 88 | }); 89 | } 90 | 91 | /** 92 | * Small JavaScript module for the documentation. 93 | */ 94 | var Documentation = { 95 | 96 | init : function() { 97 | this.fixFirefoxAnchorBug(); 98 | this.highlightSearchWords(); 99 | this.initModIndex(); 100 | }, 101 | 102 | /** 103 | * i18n support 104 | */ 105 | TRANSLATIONS : {}, 106 | PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, 107 | LOCALE : 'unknown', 108 | 109 | // gettext and ngettext don't access this so that the functions 110 | // can savely bound to a different name (_ = Documentation.gettext) 111 | gettext : function(string) { 112 | var translated = Documentation.TRANSLATIONS[string]; 113 | if (typeof translated == 'undefined') 114 | return string; 115 | return (typeof translated == 'string') ? translated : translated[0]; 116 | }, 117 | 118 | ngettext : function(singular, plural, n) { 119 | var translated = Documentation.TRANSLATIONS[singular]; 120 | if (typeof translated == 'undefined') 121 | return (n == 1) ? singular : plural; 122 | return translated[Documentation.PLURALEXPR(n)]; 123 | }, 124 | 125 | addTranslations : function(catalog) { 126 | for (var key in catalog.messages) 127 | this.TRANSLATIONS[key] = catalog.messages[key]; 128 | this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); 129 | this.LOCALE = catalog.locale; 130 | }, 131 | 132 | /** 133 | * add context elements like header anchor links 134 | */ 135 | addContextElements : function() { 136 | $('div[id] > :header:first').each(function() { 137 | $('\u00B6'). 138 | attr('href', '#' + this.id). 139 | attr('title', _('Permalink to this headline')). 140 | appendTo(this); 141 | }); 142 | $('dt[id]').each(function() { 143 | $('\u00B6'). 144 | attr('href', '#' + this.id). 145 | attr('title', _('Permalink to this definition')). 146 | appendTo(this); 147 | }); 148 | }, 149 | 150 | /** 151 | * workaround a firefox stupidity 152 | */ 153 | fixFirefoxAnchorBug : function() { 154 | if (document.location.hash && $.browser.mozilla) 155 | window.setTimeout(function() { 156 | document.location.href += ''; 157 | }, 10); 158 | }, 159 | 160 | /** 161 | * highlight the search words provided in the url in the text 162 | */ 163 | highlightSearchWords : function() { 164 | var params = $.getQueryParameters(); 165 | var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; 166 | if (terms.length) { 167 | var body = $('div.body'); 168 | window.setTimeout(function() { 169 | $.each(terms, function() { 170 | body.highlightText(this.toLowerCase(), 'highlight'); 171 | }); 172 | }, 10); 173 | $('') 175 | .appendTo($('.sidebar .this-page-menu')); 176 | } 177 | }, 178 | 179 | /** 180 | * init the modindex toggle buttons 181 | */ 182 | initModIndex : function() { 183 | var togglers = $('img.toggler').click(function() { 184 | var src = $(this).attr('src'); 185 | var idnum = $(this).attr('id').substr(7); 186 | console.log($('tr.cg-' + idnum).toggle()); 187 | if (src.substr(-9) == 'minus.png') 188 | $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); 189 | else 190 | $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); 191 | }).css('display', ''); 192 | if (DOCUMENTATION_OPTIONS.COLLAPSE_MODINDEX) { 193 | togglers.click(); 194 | } 195 | }, 196 | 197 | /** 198 | * helper function to hide the search marks again 199 | */ 200 | hideSearchWords : function() { 201 | $('.sidebar .this-page-menu li.highlight-link').fadeOut(300); 202 | $('span.highlight').removeClass('highlight'); 203 | }, 204 | 205 | /** 206 | * make the url absolute 207 | */ 208 | makeURL : function(relativeURL) { 209 | return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; 210 | }, 211 | 212 | /** 213 | * get the current relative url 214 | */ 215 | getCurrentURL : function() { 216 | var path = document.location.pathname; 217 | var parts = path.split(/\//); 218 | $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { 219 | if (this == '..') 220 | parts.pop(); 221 | }); 222 | var url = parts.join('/'); 223 | return path.substring(url.lastIndexOf('/') + 1, path.length - 1); 224 | } 225 | }; 226 | 227 | // quick alias for translations 228 | _ = Documentation.gettext; 229 | 230 | $(document).ready(function() { 231 | Documentation.init(); 232 | }); 233 | -------------------------------------------------------------------------------- /docs/_build/html/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmatuson/SchemaObject/52d296b5a7824a3645995683f2a4913a968ff579/docs/_build/html/_static/file.png -------------------------------------------------------------------------------- /docs/_build/html/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmatuson/SchemaObject/52d296b5a7824a3645995683f2a4913a968ff579/docs/_build/html/_static/minus.png -------------------------------------------------------------------------------- /docs/_build/html/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmatuson/SchemaObject/52d296b5a7824a3645995683f2a4913a968ff579/docs/_build/html/_static/plus.png -------------------------------------------------------------------------------- /docs/_build/html/_static/pygments.css: -------------------------------------------------------------------------------- 1 | .hll { background-color: #ffffcc } 2 | .c { color: #408090; font-style: italic } /* Comment */ 3 | .err { border: 1px solid #FF0000 } /* Error */ 4 | .k { color: #007020; font-weight: bold } /* Keyword */ 5 | .o { color: #666666 } /* Operator */ 6 | .cm { color: #408090; font-style: italic } /* Comment.Multiline */ 7 | .cp { color: #007020 } /* Comment.Preproc */ 8 | .c1 { color: #408090; font-style: italic } /* Comment.Single */ 9 | .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ 10 | .gd { color: #A00000 } /* Generic.Deleted */ 11 | .ge { font-style: italic } /* Generic.Emph */ 12 | .gr { color: #FF0000 } /* Generic.Error */ 13 | .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 14 | .gi { color: #00A000 } /* Generic.Inserted */ 15 | .go { color: #303030 } /* Generic.Output */ 16 | .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ 17 | .gs { font-weight: bold } /* Generic.Strong */ 18 | .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 19 | .gt { color: #0040D0 } /* Generic.Traceback */ 20 | .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ 21 | .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ 22 | .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ 23 | .kp { color: #007020 } /* Keyword.Pseudo */ 24 | .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ 25 | .kt { color: #902000 } /* Keyword.Type */ 26 | .m { color: #208050 } /* Literal.Number */ 27 | .s { color: #4070a0 } /* Literal.String */ 28 | .na { color: #4070a0 } /* Name.Attribute */ 29 | .nb { color: #007020 } /* Name.Builtin */ 30 | .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ 31 | .no { color: #60add5 } /* Name.Constant */ 32 | .nd { color: #555555; font-weight: bold } /* Name.Decorator */ 33 | .ni { color: #d55537; font-weight: bold } /* Name.Entity */ 34 | .ne { color: #007020 } /* Name.Exception */ 35 | .nf { color: #06287e } /* Name.Function */ 36 | .nl { color: #002070; font-weight: bold } /* Name.Label */ 37 | .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ 38 | .nt { color: #062873; font-weight: bold } /* Name.Tag */ 39 | .nv { color: #bb60d5 } /* Name.Variable */ 40 | .ow { color: #007020; font-weight: bold } /* Operator.Word */ 41 | .w { color: #bbbbbb } /* Text.Whitespace */ 42 | .mf { color: #208050 } /* Literal.Number.Float */ 43 | .mh { color: #208050 } /* Literal.Number.Hex */ 44 | .mi { color: #208050 } /* Literal.Number.Integer */ 45 | .mo { color: #208050 } /* Literal.Number.Oct */ 46 | .sb { color: #4070a0 } /* Literal.String.Backtick */ 47 | .sc { color: #4070a0 } /* Literal.String.Char */ 48 | .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ 49 | .s2 { color: #4070a0 } /* Literal.String.Double */ 50 | .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ 51 | .sh { color: #4070a0 } /* Literal.String.Heredoc */ 52 | .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ 53 | .sx { color: #c65d09 } /* Literal.String.Other */ 54 | .sr { color: #235388 } /* Literal.String.Regex */ 55 | .s1 { color: #4070a0 } /* Literal.String.Single */ 56 | .ss { color: #517918 } /* Literal.String.Symbol */ 57 | .bp { color: #007020 } /* Name.Builtin.Pseudo */ 58 | .vc { color: #bb60d5 } /* Name.Variable.Class */ 59 | .vg { color: #bb60d5 } /* Name.Variable.Global */ 60 | .vi { color: #bb60d5 } /* Name.Variable.Instance */ 61 | .il { color: #208050 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /docs/_build/html/api/option.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | Option — SchemaObject v0.5.2 documentation 9 | 10 | 11 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 41 | 42 |
43 |
44 |
45 |
46 | 47 |
48 |

Option

49 |
50 |
51 | class schemaobject.option.SchemaOption(name, value=None)
52 |
53 |
Object representation of a database or table option
54 |
>>> schema.databases['sakila'].tables['rental'].options['engine'].name
 55 | 'ENGINE'
 56 | >>> schema.databases['sakila'].tables['rental'].options['engine'].value
 57 | 'InnoDB'
 58 | >>> schema.databases['sakila'].tables['rental'].options['engine'].create()
 59 | 'ENGINE=InnoDB'
 60 | 
61 |
62 |
63 |
64 |
65 |
66 | create()
67 |
68 |
Generate the SQL for this option
69 |
>>> schema.databases['sakila'].options['charset'].create()
 70 | 'CHARSET=latin1'
 71 | >>> schema.databases['sakila'].tables['rental'].options['engine'].create()
 72 | 'ENGINE=InnoDB'
 73 | >>> schema.databases['sakila'].tables['rental'].options['auto_increment'].create()
 74 | 'AUTO_INCREMENT=1'
 75 | 
76 |
77 |
78 |
79 |
80 | 81 |
82 | 83 |
84 | 85 | 86 |
87 |
88 |
89 |
90 |
91 |

Previous topic

92 |

Foreign Key

94 |

This Page

95 | 99 | 111 | 112 |
113 |
114 |
115 |
116 | 131 | 135 | 136 | -------------------------------------------------------------------------------- /docs/_build/html/api/schema.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | Schema (MySQL Instance) — SchemaObject v0.5.2 documentation 9 | 10 | 11 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 45 | 46 |
47 |
48 |
49 |
50 | 51 |
52 |

Schema (MySQL Instance)

53 |
54 |
55 | class schemaobject.schema.SchemaObject(connection_url)
56 |

Object representation of a single MySQL instance. 57 | If database name is not specified in connection_url, 58 | all databases on the MySQL instance will be loaded.

59 |

connection_url - the database url as per RFC1738

60 |
>>> schema  = schemaobject.SchemaObject('mysql://username:password@localhost:3306/sakila')
 61 | >>> schema.host
 62 | 'localhost'
 63 | >>> schema.port
 64 | 3306
 65 | >>> schema.user
 66 | 'username'
 67 | >>> schema.version
 68 | '5.1.30'
 69 | >>> schema.selected.name
 70 | 'sakila'
 71 | 
72 |
73 |
74 |
75 | databases
76 |

Lazily loaded dictionary of the databases within this MySQL instance.

77 |

See DatabaseSchema for usage:

78 |
#if database name is specified in the connection url
 79 | >>> len(schema.databases)
 80 | 1
 81 | >>> schema.databases.keys()
 82 | ['sakila']
83 |
84 |
85 | 86 |
87 |
88 | selected
89 |

Returns the DatabaseSchema object associated with the database name in the connection url

90 |
>>> schema.selected.name
 91 | 'sakila'
 92 | 
93 |
94 |
95 | 96 |
97 | 98 |
99 | 100 | 101 |
102 |
103 |
104 |
105 |
106 |

Previous topic

107 |

SchemaObject v0.5.1 documentation

109 |

Next topic

110 |

Database

112 |

This Page

113 | 117 | 129 | 130 |
131 |
132 |
133 |
134 | 152 | 156 | 157 | -------------------------------------------------------------------------------- /docs/_build/html/changes.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | Changes to SchemaObject — SchemaObject v0.5.2 documentation 9 | 10 | 11 | 20 | 21 | 22 | 23 | 24 | 25 | 37 | 38 |
39 |
40 |
41 |
42 | 43 |
44 |

Changes to SchemaObject

45 |
46 |
== 0.5.2 / 2009-12-02
47 |
    48 |
  • 49 |
    Column DEFAULT fixes
    50 |
      51 |
    • strings are now quoted (except CURRENT_TIMESTAMP),
    • 52 |
    • non-None values handled (‘’, 0)
    • 53 |
    54 |
    55 |
    56 |
  • 57 |
  • CHARACTER SET AND COLLATE omitted from column definition if same as parent Table.

    58 |
  • 59 |
  • Fixed Column definition syntax (ordering of attributes)

    60 |
  • 61 |
62 |
63 |
== 0.5.1 / 2009-11-10
64 |
    65 |
  • bug fixes

    66 |
  • 67 |
  • updated test configuration, added more tests

    68 |
  • 69 |
  • added ability to retrieve comments on columns

    70 |
  • 71 |
  • 72 |
    updated collections.orderedDict
    73 |
      74 |
    • added support for index(), insert() and __delitem__()
    • 75 |
    76 |
    77 |
    78 |
  • 79 |
  • 80 |
    changed output of database options
    81 |
      82 |
    • CHARSET is now CHARACTER SET
    • 83 |
    • <key> <value> is now <key>=<value>
    • 84 |
    85 |
    86 |
    87 |
  • 88 |
  • newlines and multiple spaces removed from table.create() string

    89 |
  • 90 |
  • Changed DatabaseConnection to explicitly require a call to connect()

    91 |
  • 92 |
  • backported to work with python 2.4

    93 |
  • 94 |
  • schema name dropped from table.alter() and table.drop() to be consistent with table.create()

    95 |
  • 96 |
97 |
98 |
== 0.5.0 / 2009-09-17
99 |
    100 |
  • Initial public release
  • 101 |
102 |
103 |
104 |
105 | 106 | 107 |
108 |
109 |
110 |
111 |
112 |

This Page

113 | 117 | 129 | 130 |
131 |
132 |
133 |
134 | 146 | 150 | 151 | -------------------------------------------------------------------------------- /docs/_build/html/chap1.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | Database — SchemaObject v0.5 documentation 9 | 10 | 11 | 20 | 21 | 22 | 23 | 24 | 25 | 37 | 38 |
39 |
40 |
41 |
42 | 43 |
44 |

Database

45 |

Some text, italic text, bold text

46 |
    47 |
  • bulleted list. There needs to be a space right after the “*”
  • 48 |
  • item 2
  • 49 |
50 |
51 |

Note

52 |

This is a note.

53 |
54 |

Here’s some Python code:

55 |
>>> for i in range(10):
 56 | ...     print i
 57 | 
58 |
59 |
60 | 61 | 62 |
63 |
64 |
65 |
66 |
67 |

This Page

68 | 72 | 84 | 85 |
86 |
87 |
88 |
89 | 101 | 105 | 106 | -------------------------------------------------------------------------------- /docs/_build/html/generated/schemaobjects.schema.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | schemaobjects.schema — SchemaObjects v0.5 documentation 9 | 10 | 11 | 20 | 21 | 22 | 23 | 24 | 25 | 37 | 38 |
39 |
40 |
41 |
42 | 43 |
44 |

schemaobjects.schema

45 |
46 |

Functions

47 |
48 |
49 | schemaobjects.schema.database_schema_builder(instance)
50 |
Load the Databases in this instance of mysql
51 | 52 |
53 |
54 |

Classes

55 |
56 |
57 | class schemaobjects.schema.DatabaseConnection(connection_url)
58 |

Bases: object

59 |

A lightweight wrapper around MySQLdb DB-API

60 |
61 |
62 | close()
63 |
Close the database connection.
64 | 65 |
66 |
67 | connect()
68 |
Connect to the databse
69 | 70 |
71 |
72 | execute(sql, values=None)
73 |
74 | 75 |
76 |
77 | version
78 |
79 | 80 |
81 | 82 |
83 |
84 | class schemaobjects.schema.SchemaObject(connection_url)
85 |

Bases: object

86 |

Load the entire MySQL Instance 87 | returns a list of DatabaseSchemaObjects

88 |
89 |
90 | databases
91 |
92 | 93 |
94 | 95 |
96 |
97 | 98 | 99 |
100 |
101 |
102 |
103 |
104 |

Table Of Contents

105 | 112 | 113 |

This Page

114 | 118 | 130 | 131 |
132 |
133 |
134 |
135 | 147 | 151 | 152 | -------------------------------------------------------------------------------- /docs/_build/html/modindex.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | Global Module Index — SchemaObject v0.5.2 documentation 9 | 10 | 11 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 40 | 41 |
42 |
43 |
44 |
45 | 46 | 47 |

Global Module Index

48 | S 49 |
50 | 51 | 52 | 53 | 55 | 58 | 59 | 62 | 63 | 66 | 67 | 70 | 71 | 74 | 75 | 78 | 79 | 82 | 83 | 86 |
 
S
56 | schemaobject 57 |
    60 | schemaobject.column 61 |
    64 | schemaobject.database 65 |
    68 | schemaobject.foreignkey 69 |
    72 | schemaobject.index 73 |
    76 | schemaobject.option 77 |
    80 | schemaobject.schema 81 |
    84 | schemaobject.table 85 |
87 | 88 | 89 |
90 |
91 |
92 |
93 |
94 | 106 | 107 |
108 |
109 |
110 |
111 | 123 | 127 | 128 | -------------------------------------------------------------------------------- /docs/_build/html/objects.inv: -------------------------------------------------------------------------------- 1 | # Sphinx inventory version 1 2 | # Project: SchemaObject 3 | # Version: 0.5 4 | schemaobject.index mod api/index.html 5 | schemaobject.option mod api/option.html 6 | schemaobject.column mod api/column.html 7 | schemaobject.database mod api/database.html 8 | schemaobject.schema mod api/schema.html 9 | schemaobject.table mod api/table.html 10 | schemaobject.foreignkey mod api/foreignkey.html 11 | schemaobject.index.format_sub_part classmethod api/index.html 12 | schemaobject.column.ColumnSchema.define method api/column.html 13 | schemaobject.index.IndexSchema.format_sub_part classmethod api/index.html 14 | schemaobject.column.ColumnSchema.drop method api/column.html 15 | schemaobject.foreignkey.ForeignKeySchema.drop method api/foreignkey.html 16 | schemaobject.database.DatabaseSchema.alter method api/database.html 17 | schemaobject.database.DatabaseSchema class api/database.html 18 | schemaobject.column.ColumnSchema.create method api/column.html 19 | schemaobject.foreignkey.ForeignKeySchema.create method api/foreignkey.html 20 | schemaobject.table.TableSchema.indexes attribute api/table.html 21 | schemaobject.schema.SchemaObject class api/schema.html 22 | schemaobject.database.DatabaseSchema.tables attribute api/database.html 23 | schemaobject.table.TableSchema.alter method api/table.html 24 | schemaobject.database.DatabaseSchema.drop method api/database.html 25 | schemaobject.index.index_schema_builder function api/index.html 26 | schemaobject.schema.SchemaObject.databases attribute api/schema.html 27 | schemaobject.database.DatabaseSchema.create method api/database.html 28 | schemaobject.column.column_schema_builder function api/column.html 29 | schemaobject.table.TableSchema class api/table.html 30 | schemaobject.index.IndexSchema.create method api/index.html 31 | schemaobject.table.TableSchema.drop method api/table.html 32 | schemaobject.schema.SchemaObject.selected attribute api/schema.html 33 | schemaobject.foreignkey.ForeignKeySchema class api/foreignkey.html 34 | schemaobject.index.IndexSchema class api/index.html 35 | schemaobject.index.IndexSchema.drop method api/index.html 36 | schemaobject.column.ColumnSchema class api/column.html 37 | schemaobject.database.DatabaseSchema.options attribute api/database.html 38 | schemaobject.table.TableSchema.columns attribute api/table.html 39 | schemaobject.database.database_schema_builder function api/database.html 40 | schemaobject.database.DatabaseSchema.select method api/database.html 41 | schemaobject.foreignkey.foreign_key_schema_builder function api/foreignkey.html 42 | schemaobject.option.SchemaOption.create method api/option.html 43 | schemaobject.table.TableSchema.foreign_keys attribute api/table.html 44 | schemaobject.option.SchemaOption class api/option.html 45 | schemaobject.column.ColumnSchema.modify method api/column.html 46 | schemaobject.table.table_schema_builder function api/table.html 47 | schemaobject.table.TableSchema.create method api/table.html 48 | schemaobject.table.TableSchema.options attribute api/table.html 49 | -------------------------------------------------------------------------------- /docs/_build/html/search.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | Search — SchemaObject v0.5.2 documentation 9 | 10 | 11 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 38 | 39 |
40 |
41 |
42 |
43 | 44 |

Search

45 |
46 | 47 |

48 | Please activate JavaScript to enable the search 49 | functionality. 50 |

51 |
52 |

53 | From here you can search these documents. Enter your search 54 | words into the box below and click "search". Note that the search 55 | function will automatically search for all of the words. Pages 56 | containing fewer words won't appear in the result list. 57 |

58 |
59 | 60 | 61 | 62 |
63 | 64 |
65 | 66 |
67 | 68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | 89 | 90 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /docs/_build/html/searchindex.js: -------------------------------------------------------------------------------- 1 | Search.setIndex({desctypes:{"0":"classmethod","1":"method","2":"class","3":"attribute","4":"function"},terms:{represent:[0,2,5,6,7,8,9],all:[0,1,2,5,6,7,9],code:1,queri:[],last_upd:[5,9],abil:3,"1p2":1,follow:4,create_opt:5,whose:[],depend:4,current_timestamp:[3,5,9],tableschemabuild:5,sent:1,sourc:1,string:[3,9],fals:9,util:1,foreignkeyschema:[6,5],customer_id:[2,5,9],list:2,iter:[],"try":[],item:[],optionschema:[5,7],hostiri:[],pleas:[],work:3,schemaopt:8,sync:1,mysqldb:1,download:1,btree:2,port:0,index:[2,1,5,3],what:1,lazili:[0,1,5,7],neg:[],current:[],delet:[6,4],version:[0,1,4],"new":[1,4],net:[],"public":3,neq:[],hash:2,iteritem:[],fk_rental_inventori:[6,5],gener:[1,2,5,6,7,8,9],here:[],address:5,modifi:[1,9],sinc:[],valu:[1,3,8],search:[],datetim:5,chang:[1,3],get_python_lib:4,configur:3,prerequisit:1,extra:9,apach:1,modul:[],inequ:[2,5,7,6,9],put:[],api:[],instal:[1,4],txt:[],select:[0,1,7],from:[1,4,3],upgrad:[1,4],next:[],call:[1,2,3,5,6,7,9],type:[2,9],more:3,fulltext:2,src:[],under:[1,2,5,6,7,9],ital:[],site:4,inventory_id:[2,6,5,9],idx_fk_inventory_id:[2,5],fk_rental_custom:[6,5],tableschema:[2,5,7,6,9],must:[2,5,7,6,9],none:[2,6,3,8,9],retriev:3,setup:[1,4],databaseschemaobject:[],uniqu:[2,5],dev:[],histori:1,innbodb:[],referenced_column:6,prompt:4,tar:1,process:[],sudo:[1,4],indic:[],liter:[],unsign:[5,9],alwai:[2,6],multipl:3,newlin:3,quot:3,rather:[],charset:[5,7,3,8,9],write:1,verifi:1,simpl:1,updat:[6,3,5,9],mess:[],clone:1,after:9,availal:[7,9],associ:0,inventori:[6,5],github:1,return_d:[5,9],classmethod:2,correspond:[],django:4,issu:1,allow:[],order:3,help:4,over:[],major:[],sysconfig:4,privileg:1,retrun:[],smallint:[5,9],thank:4,itervalu:[],fix:3,idx_fk_staff_id:[2,5],mydb:1,alter:[5,1,7,3],them:[],"return":[0,2,5,6,7,9],thei:[],python:[1,4,3],timestamp:[5,9],databs:7,usga:[],initi:3,now:3,rental:[2,6,5,8,9],introduct:1,"__delitem__":3,name:[0,2,3,5,6,7,8,9],drop:[1,2,3,5,6,7,9],instruct:4,separ:[],innodb:[1,5,8],mode:[],found:[],mean:[],individu:[],procedur:1,"static":[],connect:[0,3],our:1,todo:[],event:1,out:4,space:3,categori:5,print:[4,7],migrat:1,manipul:[],standard:[1,4],base:[],dictionari:[0,2,5,6,7,9],releas:[1,3],org:[],log:[],setdefault:[],could:[],omit:3,synchron:[],length:2,interact:4,first:9,oper:4,rang:[],dont:[],default_databas:[],restrict:6,film_actor:5,done:[],least:1,stabl:[],primari:[2,5],given:[],licens:1,system:4,wrapper:[],citi:5,statement:[1,9],"final":[],store:[5,1,4],schema:[0,1,2,3,5,6,7,8,9],shell:4,option:[5,1,7,3,8],mmatuson:1,shallow:[],referenced_table_schema:6,specifi:0,part:[],than:[],kind:2,provid:1,remov:3,charact:[5,7,3],project:1,str:[],were:[],idx_title_descript:2,pre:[],fork:1,arg:9,pri:9,ani:[],packag:4,have:1,tabl:[1,2,3,5,6,7,8,9],need:1,"null":[5,9],"__neq__":[5,9],engin:[1,5,8],self:[],note:[1,2,4,5,6,7,9],also:[],which:4,tool:1,singl:[0,2,5,6,7,9],copi:1,create_syntax:[],usernam:[0,1],object:[0,1,2,5,6,7,8,9],beta:[],pair:[],payment:5,appear:9,latin1:[7,8],url:0,doc:[],clear:[],request:1,doe:1,dot:1,has_kei:[],show:1,text:[],syntax:[5,7,3],find:4,staff:[5,9],access:1,onli:1,explicitli:3,locat:4,rtree:2,activ:1,should:[4,9],dict:[],iterkei:[],sakila:[0,1,2,5,6,7,8,9],get:[],popitem:[],report:1,format_sub_part:2,requir:3,ietf:[],yield:[],method:[],rfc:[],stuff:[],through:[],grab:[],where:4,view:[],set:[2,3,5,6,7,9],ordinal_posit:9,see:[0,1,5,7],auto_incr:[5,8,9],close:[],row_format:5,statu:1,kei:[0,1,2,5,6,3,9],databas:[0,1,2,3,5,6,7,8,9],between:[],"import":[1,4],attribut:[2,6,5,3,9],parent:[2,3,5,6,7,9],sub_part_length:2,columnschemabuild:9,entir:[],len:[0,5,7],otherwis:[],delete_rul:6,mediumint:5,equal:[2,5,7,6,9],against:1,foreign:[6,1,5],instanc:[0,1,2,5,6,7,9],databaseconnect:3,countri:5,com:1,load:[0,1,2,5,6,7,9],simpli:[],can:[1,4],pop:[],header:[],refman:[],non_uniqu:2,empti:[],much:[],assert:1,fk_rental_staff:[6,5],"__eq__":[5,9],those:[],update_rul:6,rfc1738:0,frozen:[],properti:[],sourceforg:[],foreignkeyschemabuild:6,defin:9,behavior:[],error:[],xvzf:1,ordereddict:3,indexschema:[2,5],table_schema:6,non:3,kwarg:9,lightweight:[],develop:1,welcom:[],make:1,mayb:[],same:3,handl:3,html:[],idx_fk_customer_id:[2,5],document:1,higher:1,http:[],utf8:[5,9],effect:[],databaseschemabuild:7,rais:[],effortlessli:1,user:[0,1],collat:[2,5,7,3,9],distutil:4,fromkei:[],well:1,exampl:[2,1,5,6,9],command:[],thi:[0,1,2,4,5,6,7,8,9],latest:1,comment:[2,1,5,3,9],execut:[1,4],obtain:1,via:1,mysql:[0,1,2,5,6,7],languag:5,easi:1,utf8_general_ci:[],except:3,add:[2,6,9],els:[],table_nam:[2,6],around:[],format:2,read:1,field_nam:2,password:[0,1,9],insert:3,rental_id:[5,9],specif:[],docutil:[],integ:[],collect:3,"boolean":9,databaseschema:[0,5,7],cascad:[6,5],output:3,page:[1,4],www:[],right:[],deal:[],lastest:[],some:[],intern:[],sure:[],virtualenv:4,connection_url:0,definit:[3,9],per:[0,5,7],necessari:1,content:[],localhost:[0,1],refer:[6,5],run:[1,4],bold:[],usag:[0,5,7],host:0,repositori:1,comparison:[2,5,7,6,9],about:[],actual:[],constraint:[6,5],column:[1,2,5,6,3,9],manag:1,with_com:9,sub_part:2,lenght:[],within:[0,5,7],easy_instal:[1,4],automat:[2,5,7,6,9],doesnt:[],been:1,your:[1,4],matuson:1,film_categori:5,staff_id:[5,9],git:1,span:[],fabric:[],support:[2,3,5,6,7,9],question:1,submit:[],custom:5,avail:[2,6,5],trigger:1,interfac:1,includ:[],suit:1,rental_d:[2,5,9],information_schema:1,"function":[2,5,7,6,9],utf8_bin:9,film_text:[2,5],tupl:2,keyerror:[],indexschemabuild:2,line:[],"true":2,bug:[1,3],match_opt:6,made:[],consist:3,possibl:2,"default":[3,5,9],below:[],limit:1,foreignkei:6,backport:3,columnschema:[5,9],featur:1,creat:[1,2,3,5,6,7,8,9],"int":[5,9],exist:[],film:5,titl:2,"_format_sub_part":[],when:[2,5,7,6,9],actor:5,field:[2,9],valid:1,spatial:2,test:[1,3],you:[1,2,4,5,6,7,9],foreign_kei:[6,5],referenced_table_nam:6,symbol:6,"class":[0,2,5,6,7,8,9],sql:[1,2,5,6,7,8,9],latin1_swedish_ci:7,bullet:[],directori:4,descript:2,tinyint:5,ignor:[5,9],push:[],chema:[],schemaobejct:[],schemaobject:[0,1,2,3,4,5,6,7,8,9]},titles:["Schema (MySQL Instance)","SchemaObject v0.5.2 documentation","Index","Changes to SchemaObject","Upgrading SchemaObject","Table","Foreign Key","Database","Option","Column"],modules:{"schemaobject.table":5,"schemaobject.option":8,"schemaobject.column":9,"schemaobject.database":7,"schemaobject.schema":0,"schemaobject.index":2,"schemaobject.foreignkey":6},descrefs:{"schemaobject.table":{TableSchemaBuilder:[5,4],TableSchema:[5,2]},"schemaobject.schema.SchemaObject":{selected:[0,3],databases:[0,3]},"schemaobject.option":{SchemaOption:[8,2]},"schemaobject.foreignkey.ForeignKeySchema":{create:[6,1],drop:[6,1]},"schemaobject.column":{ColumnSchema:[9,2],ColumnSchemaBuilder:[9,4]},"schemaobject.option.SchemaOption":{create:[8,1]},"schemaobject.database":{DatabaseSchema:[7,2],DatabaseSchemaBuilder:[7,4]},"schemaobject.index.IndexSchema":{create:[2,1],drop:[2,1],format_sub_part:[2,0]},"schemaobject.schema":{SchemaObject:[0,2]},"schemaobject.database.DatabaseSchema":{tables:[7,3],create:[7,1],drop:[7,1],options:[7,3],alter:[7,1],select:[7,1]},"schemaobject.index":{IndexSchemaBuilder:[2,4],format_sub_part:[2,0],IndexSchema:[2,2]},"schemaobject.column.ColumnSchema":{create:[9,1],drop:[9,1],modify:[9,1],define:[9,1]},"schemaobject.table.TableSchema":{create:[5,1],drop:[5,1],indexes:[5,3],foreign_keys:[5,3],options:[5,3],alter:[5,1],columns:[5,3]},"schemaobject.foreignkey":{ForeignKeySchema:[6,2],ForeignKeySchemaBuilder:[6,4]}},filenames:["api/schema","index","api/index","changes","upgrading","api/table","api/foreignkey","api/database","api/option","api/column"]}) -------------------------------------------------------------------------------- /docs/_build/html/upgrading.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | Upgrading SchemaObject — SchemaObject v0.5.2 documentation 9 | 10 | 11 | 20 | 21 | 22 | 23 | 24 | 25 | 37 | 38 |
39 |
40 |
41 |
42 | 43 |
44 |

Upgrading SchemaObject

45 |
46 |

Upgrading with easy_install

47 |
sudo easy_install --upgrade schemaobject
48 |
49 |
50 |
51 |

Upgrading the Standard Installation

52 |

If you installed SchemaObject using setup.py install, you can upgrade by deleting the SchemaObject directory from your Python site-packages (or virtualenv) and re-installing the new version.

53 |

Where are my site-packages stored?

54 |

The location of the site-packages directory depends on the operating system, and the location in which Python was installed. To find out your system’s site-packages location, execute the following:

55 |
python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()"
56 |
57 |

Note that this should be run from a shell prompt, not a Python interactive prompt. 58 | Thanks to the Django install page for these helpful instructions.

59 |
60 |
61 | 62 | 63 |
64 |
65 |
66 |
67 |
68 |

Table Of Contents

69 | 76 | 77 |

This Page

78 | 82 | 94 | 95 |
96 |
97 |
98 |
99 | 111 | 115 | 116 | -------------------------------------------------------------------------------- /docs/api/column.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | Column 3 | =============== 4 | 5 | .. automodule:: schemaobject.column 6 | :members: -------------------------------------------------------------------------------- /docs/api/database.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | Database 3 | =============== 4 | 5 | .. automodule:: schemaobject.database 6 | :members: 7 | 8 | -------------------------------------------------------------------------------- /docs/api/foreignkey.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | Foreign Key 3 | =============== 4 | 5 | .. automodule:: schemaobject.foreignkey 6 | :members: -------------------------------------------------------------------------------- /docs/api/index.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | Index 3 | =============== 4 | 5 | .. automodule:: schemaobject.index 6 | :members: 7 | 8 | .. classmethod:: format_sub_part(field, length) -------------------------------------------------------------------------------- /docs/api/option.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | Option 3 | =============== 4 | 5 | .. automodule:: schemaobject.option 6 | :members: -------------------------------------------------------------------------------- /docs/api/schema.rst: -------------------------------------------------------------------------------- 1 | ======================== 2 | Schema (MySQL Instance) 3 | ======================== 4 | 5 | .. automodule:: schemaobject.schema 6 | :members: -------------------------------------------------------------------------------- /docs/api/table.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | Table 3 | =============== 4 | 5 | .. automodule:: schemaobject.table 6 | :members: -------------------------------------------------------------------------------- /docs/changes.rst: -------------------------------------------------------------------------------- 1 | ======================= 2 | Changes to SchemaObject 3 | ======================= 4 | .. include:: ../CHANGES 5 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # SchemaObjects documentation build configuration file, created by 4 | # sphinx-quickstart on Wed Sep 16 09:45:22 2009. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | #sys.path.append(os.path.abspath('.')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # Add any Sphinx extension module names here, as strings. They can be extensions 24 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 25 | extensions = ['sphinx.ext.autodoc'] 26 | 27 | # Add any paths that contain templates here, relative to this directory. 28 | templates_path = ['_templates'] 29 | 30 | # The suffix of source filenames. 31 | source_suffix = '.rst' 32 | 33 | # The encoding of source files. 34 | #source_encoding = 'utf-8' 35 | 36 | # The master toctree document. 37 | master_doc = 'index' 38 | 39 | # General information about the project. 40 | project = 'SchemaObject' 41 | copyright = '2009, Mitch Matuson' 42 | 43 | # The version info for the project you're documenting, acts as replacement for 44 | # |version| and |release|, also used in various other places throughout the 45 | # built documents. 46 | # 47 | # The short X.Y version. 48 | version = '0.5' 49 | # The full version, including alpha/beta/rc tags. 50 | release = '0.5.2' 51 | 52 | # The language for content autogenerated by Sphinx. Refer to documentation 53 | # for a list of supported languages. 54 | #language = None 55 | 56 | # There are two options for replacing |today|: either, you set today to some 57 | # non-false value, then it is used: 58 | #today = '' 59 | # Else, today_fmt is used as the format for a strftime call. 60 | #today_fmt = '%B %d, %Y' 61 | 62 | # List of documents that shouldn't be included in the build. 63 | #unused_docs = [] 64 | 65 | # List of directories, relative to source directory, that shouldn't be searched 66 | # for source files. 67 | exclude_trees = ['_build'] 68 | 69 | # The reST default role (used for this markup: `text`) to use for all documents. 70 | #default_role = None 71 | 72 | # If true, '()' will be appended to :func: etc. cross-reference text. 73 | #add_function_parentheses = True 74 | 75 | # If true, the current module name will be prepended to all description 76 | # unit titles (such as .. function::). 77 | #add_module_names = True 78 | 79 | # If true, sectionauthor and moduleauthor directives will be shown in the 80 | # output. They are ignored by default. 81 | #show_authors = False 82 | 83 | # The name of the Pygments (syntax highlighting) style to use. 84 | pygments_style = 'sphinx' 85 | 86 | # A list of ignored prefixes for module index sorting. 87 | #modindex_common_prefix = [] 88 | 89 | 90 | # -- Options for HTML output --------------------------------------------------- 91 | 92 | # The theme to use for HTML and HTML Help pages. Major themes that come with 93 | # Sphinx are currently 'default' and 'sphinxdoc'. 94 | html_theme = 'default' 95 | 96 | # Theme options are theme-specific and customize the look and feel of a theme 97 | # further. For a list of options available for each theme, see the 98 | # documentation. 99 | #html_theme_options = {} 100 | 101 | # Add any paths that contain custom themes here, relative to this directory. 102 | #html_theme_path = [] 103 | 104 | # The name for this set of Sphinx documents. If None, it defaults to 105 | # " v documentation". 106 | #html_title = None 107 | 108 | # A shorter title for the navigation bar. Default is the same as html_title. 109 | #html_short_title = None 110 | 111 | # The name of an image file (relative to this directory) to place at the top 112 | # of the sidebar. 113 | #html_logo = None 114 | 115 | # The name of an image file (within the static path) to use as favicon of the 116 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 117 | # pixels large. 118 | #html_favicon = None 119 | 120 | # Add any paths that contain custom static files (such as style sheets) here, 121 | # relative to this directory. They are copied after the builtin static files, 122 | # so a file named "default.css" will overwrite the builtin "default.css". 123 | html_static_path = ['_static'] 124 | 125 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 126 | # using the given strftime format. 127 | #html_last_updated_fmt = '%b %d, %Y' 128 | 129 | # If true, SmartyPants will be used to convert quotes and dashes to 130 | # typographically correct entities. 131 | #html_use_smartypants = True 132 | 133 | # Custom sidebar templates, maps document names to template names. 134 | #html_sidebars = {} 135 | 136 | # Additional templates that should be rendered to pages, maps page names to 137 | # template names. 138 | #html_additional_pages = {} 139 | 140 | # If false, no module index is generated. 141 | #html_use_modindex = True 142 | 143 | # If false, no index is generated. 144 | #html_use_index = True 145 | 146 | # If true, the index is split into individual pages for each letter. 147 | #html_split_index = False 148 | 149 | # If true, links to the reST sources are added to the pages. 150 | #html_show_sourcelink = True 151 | 152 | # If true, an OpenSearch description file will be output, and all pages will 153 | # contain a tag referring to it. The value of this option must be the 154 | # base URL from which the finished HTML is served. 155 | #html_use_opensearch = '' 156 | 157 | # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). 158 | #html_file_suffix = '' 159 | 160 | # Output file base name for HTML help builder. 161 | htmlhelp_basename = 'SchemaObjectsdoc' 162 | 163 | 164 | # -- Options for LaTeX output -------------------------------------------------- 165 | 166 | # The paper size ('letter' or 'a4'). 167 | #latex_paper_size = 'letter' 168 | 169 | # The font size ('10pt', '11pt' or '12pt'). 170 | #latex_font_size = '10pt' 171 | 172 | # Grouping the document tree into LaTeX files. List of tuples 173 | # (source start file, target name, title, author, documentclass [howto/manual]). 174 | latex_documents = [ 175 | ('index', 'SchemaObjects.tex', 'SchemaObjects Documentation', 176 | 'Mitch Matuson', 'manual'), 177 | ] 178 | 179 | # The name of an image file (relative to this directory) to place at the top of 180 | # the title page. 181 | #latex_logo = None 182 | 183 | # For "manual" documents, if this is true, then toplevel headings are parts, 184 | # not chapters. 185 | #latex_use_parts = False 186 | 187 | # Additional stuff for the LaTeX preamble. 188 | #latex_preamble = '' 189 | 190 | # Documents to append as an appendix to all manuals. 191 | #latex_appendices = [] 192 | 193 | # If false, no module index is generated. 194 | #latex_use_modindex = True 195 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | SchemaObject v0.5.2 documentation 2 | +++++++++++++++++++++++++++++++++ 3 | 4 | Introduction and Examples 5 | ------------------------- 6 | SchemaObject provides a simple, easy to use Python object interface to a MySQL database schema. You can effortlessly write tools to test, validate, sync, migrate, or manage your schema as well as generate the SQL necessary to make changes to it. 7 | 8 | **Verify all tables are InnoDB** 9 | 10 | :: 11 | 12 | import schemaobject 13 | schema = schemaobject.SchemaObject('mysql://username:password@localhost:3306/mydb') 14 | tables = schema.databases['mydb'].tables #or schema.selected.tables 15 | for t in tables: 16 | assert tables[t].options['engine'].value == 'InnoDB' 17 | 18 | 19 | **Verify our MySQL instance is at least version 5.1** 20 | 21 | :: 22 | 23 | import schemaobject 24 | schema = schemaobject.SchemaObject('mysql://username:password@localhost:3306/mydb') 25 | assert schema.version >= '5.1.0' 26 | 27 | 28 | **Notes and Limitations** 29 | 30 | * SchemaObject instances are read-only. Modifying the object or calling create(), modify(), alter(), or drop() will not change your schema. 31 | * The MySQL User needs to have privileges to execute SELECT and SHOW statements, as well as access the INFORMATION_SCHEMA. 32 | * All Databases, Tables, Columns, Indexes, and Foreign Keys are lazily loaded. 33 | * SchemaObject does not load Events, Triggers, or Stored Procedures. 34 | 35 | What's New in This Version 36 | -------------------------- 37 | See the history of :doc:`CHANGES ` 38 | 39 | .. _installing: 40 | 41 | Download and Install 42 | -------------------- 43 | 44 | **Prerequisites** 45 | 46 | * SchemaObject has been tested against Python 2.4, 2.5, and 2.6. 47 | * To use SchemaObject, you need to have `MySQL `_, version 5.0 or higher and `MySQLdb `_, version 1.2.1p2 or higher installed. 48 | * To run the test suite, you need to install a copy of the `Sakila Database `_, version 0.8 49 | 50 | **Standard Installation** 51 | 52 | Download `SchemaObject-0.5.2 `_ 53 | :: 54 | tar xvzf SchemaObject-0.5.2.tar.gz 55 | cd SchemaObject-0.5.2 56 | sudo python setup.py install 57 | 58 | **Installing with easy_install** 59 | :: 60 | 61 | sudo easy_install schemaobject 62 | 63 | **Installing the latest development version** 64 | :: 65 | 66 | git clone git://github.com/mmatuson/SchemaObject.git 67 | cd schemaobject 68 | sudo python setup.py install 69 | 70 | To upgrade to a new version of SchemaObject, see :doc:`Upgrading ` 71 | 72 | Documentation 73 | ------------- 74 | .. toctree:: 75 | :maxdepth: 1 76 | 77 | api/schema.rst 78 | api/database.rst 79 | api/table.rst 80 | api/column.rst 81 | api/index.rst 82 | api/foreignkey.rst 83 | api/option.rst 84 | 85 | 86 | Projects using SchemaObject 87 | --------------------------- 88 | `Schema Sync `_ - a MySQL schema versioning and migration utility 89 | 90 | 91 | Status & License 92 | ----------------- 93 | SchemaObject is under active development and released under the `Apache License, Version 2.0 `_. 94 | 95 | You can obtain a copy of the latest source code from the `Git repository `_, or fork it on `Github `_. 96 | 97 | You can report bugs via the `SchemaObject Issues page `_. 98 | 99 | Comments, questions, and feature requests can be sent to code at matuson dot com 100 | 101 | -------------------------------------------------------------------------------- /docs/upgrading.rst: -------------------------------------------------------------------------------- 1 | ===================== 2 | Upgrading SchemaObject 3 | ===================== 4 | 5 | Upgrading with easy_install 6 | -------------------------- 7 | :: 8 | 9 | sudo easy_install --upgrade schemaobject 10 | 11 | 12 | Upgrading the Standard Installation 13 | ----------------------------------- 14 | 15 | If you installed SchemaObject using setup.py install, you can upgrade by deleting the SchemaObject directory from your Python site-packages (or virtualenv) and :ref:`re-installing ` the new version. 16 | 17 | **Where are my site-packages stored?** 18 | 19 | The location of the site-packages directory depends on the operating system, and the location in which Python was installed. To find out your system’s site-packages location, execute the following: 20 | :: 21 | 22 | python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()" 23 | 24 | Note that this should be run from a shell prompt, not a Python interactive prompt. 25 | Thanks to `the Django install page `_ for these helpful instructions. 26 | 27 | -------------------------------------------------------------------------------- /schemaobject/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | __author__ = 'Mitch Matuson, Mustafa Ozgur' 3 | __copyright__ = """ 4 | Copyright 2009-2016 Mitch Matuson 5 | Copyright 2016 Mustafa Ozgur 6 | """ 7 | __version__ = "0.5.9" 8 | __license__ = "Apache 2.0" 9 | 10 | # shortcut to SchemaObject() 11 | from schemaobject.schema import SchemaObject 12 | -------------------------------------------------------------------------------- /schemaobject/collections.py: -------------------------------------------------------------------------------- 1 | class OrderedDict(dict): 2 | """ 3 | A Dictionary whose items are returned in the order they were first added 4 | """ 5 | 6 | def __init__(self): 7 | self._sequence = [] 8 | self._current = 0 9 | super(OrderedDict, self).__init__() 10 | 11 | def __setitem__(self, item, value): 12 | self._sequence.append(item) 13 | super(OrderedDict, self).__setitem__(item, value) 14 | 15 | def iterkeys(self): 16 | for key in self._sequence: 17 | yield key 18 | 19 | def keys(self): 20 | return self._sequence 21 | 22 | def iteritems(self): 23 | for key in self._sequence: 24 | yield (key, super(OrderedDict, self).__getitem__(key)) 25 | 26 | def items(self): 27 | return [(k, super(OrderedDict, self).__getitem__(k)) for k in self._sequence] 28 | 29 | def __iter__(self): 30 | return self.iterkeys() 31 | 32 | def __next__(self): 33 | i = self._current 34 | self._current += 1 35 | if self._current > len(self._sequence): 36 | self._current = 0 37 | raise StopIteration 38 | 39 | return self._sequence[i] 40 | 41 | def index(self, item): 42 | try: 43 | return self._sequence.index(item) 44 | except ValueError: 45 | raise 46 | 47 | def insert(self, pos, key_value_tuple): 48 | key, value = key_value_tuple 49 | super(OrderedDict, self).__setitem__(key, value) 50 | self._sequence.insert(pos, key) 51 | 52 | def __delitem__(self, key): 53 | self._sequence.remove(key) 54 | super(OrderedDict, self).__delitem__(key) 55 | -------------------------------------------------------------------------------- /schemaobject/column.py: -------------------------------------------------------------------------------- 1 | from schemaobject.collections import OrderedDict 2 | 3 | 4 | def column_schema_builder(table): 5 | """ 6 | Returns a dictionary loaded with all of the columns availale in the table. 7 | ``table`` must be an instance of TableSchema. 8 | 9 | .. note:: 10 | This function is automatically called for you and set to 11 | ``schema.databases[name].tables[name].columns`` 12 | when you create an instance of SchemaObject 13 | """ 14 | conn = table.parent.parent.connection 15 | cols = OrderedDict() 16 | sql = """ 17 | SELECT TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, 18 | IS_NULLABLE, COLUMN_TYPE, COLUMN_KEY, CHARACTER_MAXIMUM_LENGTH, 19 | CHARACTER_SET_NAME, COLLATION_NAME, EXTRA, COLUMN_COMMENT 20 | FROM information_schema.COLUMNS 21 | WHERE TABLE_SCHEMA='%s' 22 | AND TABLE_NAME='%s' 23 | ORDER BY ORDINAL_POSITION 24 | """ 25 | columns = conn.execute(sql % (table.parent.name, table.name)) 26 | if not columns: 27 | return cols 28 | 29 | for col in columns: 30 | field = col['COLUMN_NAME'] 31 | column = ColumnSchema(name=field, parent=table) 32 | 33 | column.ordinal_position = col['ORDINAL_POSITION'] 34 | column.field = col['COLUMN_NAME'] 35 | column.type = col['COLUMN_TYPE'] 36 | column.charset = col['CHARACTER_SET_NAME'] 37 | column.collation = col['COLLATION_NAME'] 38 | 39 | column.key = col['COLUMN_KEY'] 40 | column.default = col['COLUMN_DEFAULT'] 41 | column.extra = col['EXTRA'] 42 | column.comment = col['COLUMN_COMMENT'] 43 | 44 | if col['IS_NULLABLE'] == "YES": 45 | column.null = True 46 | else: 47 | column.null = False 48 | 49 | cols[field] = column 50 | 51 | return cols 52 | 53 | 54 | class ColumnSchema(object): 55 | """ 56 | Object representation of a single column. 57 | Supports equality and inequality comparison of ColumnSchema. 58 | 59 | ``name`` is the column name. 60 | ``parent`` is an instance of TableSchema 61 | 62 | .. note:: 63 | ColumnSchema objects are automatically created for you by column_schema_builder 64 | and loaded under ``schema.databases[name].tables[name].columns`` 65 | 66 | .. note:: 67 | Attributes ``key``, ``comment`` are ignored in ``__eq__``, ``__neq__`` comparisons. 68 | 69 | Example 70 | 71 | >>> schema.databases['sakila'].tables['rental'].columns.keys() 72 | ['rental_id', 'rental_date', 'inventory_id', 'customer_id', 'return_date', 'staff_id', 'last_update'] 73 | 74 | Column Attributes 75 | >>> schema.databases['sakila'].tables['rental'].columns['rental_id'].name 76 | 'rental_id' 77 | >>> schema.databases['sakila'].tables['rental'].columns['rental_id'].field 78 | 'rental_id' 79 | >>> schema.databases['sakila'].tables['rental'].columns['rental_id'].ordinal_position 80 | 1L 81 | >>> schema.databases['sakila'].tables['rental'].columns['rental_id'].type 82 | 'INT(11)' 83 | >>> schema.databases['sakila'].tables['staff'].columns['password'].charset 84 | 'utf8' 85 | >>> schema.databases['sakila'].tables['staff'].columns['password'].collation 86 | 'utf8_bin' 87 | >>> schema.databases['sakila'].tables['rental'].columns['rental_id'].null 88 | False 89 | >>> schema.databases['sakila'].tables['rental'].columns['rental_id'].key 90 | 'PRI' 91 | >>> schema.databases['sakila'].tables['rental'].columns['last_update'].default 92 | 'CURRENT_TIMESTAMP' 93 | >>> schema.databases['sakila'].tables['rental'].columns['rental_id'].extra 94 | 'auto_increment' 95 | >>> schema.databases['sakila'].tables['rental'].columns['rental_id'].comment 96 | '' 97 | """ 98 | 99 | def __init__(self, name, parent): 100 | 101 | self.parent = parent 102 | self.name = name 103 | self.field = name # alias for name, following mysql spec 104 | self.ordinal_position = 0 105 | self.type = None 106 | self.charset = None 107 | self.collation = None 108 | self.null = None 109 | self.key = None 110 | self.default = None 111 | self.extra = None 112 | self.comment = None 113 | 114 | def define(self, after=None, with_comment=False): 115 | """ 116 | Generate the SQL for this column definition. 117 | 118 | ``after`` is the name(string) of the column this should appear after. 119 | If ``after`` is None, ``FIRST`` is used. 120 | 121 | ``with_comment`` boolean, add column comment to sql statement 122 | 123 | >>> schema.databases['sakila'].tables['rental'].columns['last_update'].define(after="staff_id") 124 | '`last_update` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP AFTER `staff_id`' 125 | >>> schema.databases['sakila'].tables['rental'].columns['rental_id'].define() 126 | '`rental_id` INT(11) NOT NULL auto_increment FIRST' 127 | """ 128 | sql = ["`%s` %s" % (self.field, self.type)] 129 | 130 | if (self.collation and 131 | self.charset and 132 | ( 133 | self.parent.options['charset'].value != self.charset or 134 | self.parent.options['collation'].value != self.collation 135 | )): 136 | sql.append("CHARACTER SET %s COLLATE %s" % (self.charset, self.collation)) 137 | 138 | if not self.null: 139 | sql.append("NOT NULL") 140 | else: 141 | sql.append("NULL") 142 | 143 | try: 144 | basestring 145 | except NameError: 146 | basestring = str 147 | 148 | if self.default is not None and isinstance(self.default, (str, basestring)) \ 149 | and self.default != 'CURRENT_TIMESTAMP': 150 | sql.append("DEFAULT '%s'" % self.default) 151 | elif self.default is not None: 152 | sql.append("DEFAULT %s" % self.default) 153 | 154 | if self.extra: 155 | sql.append(self.extra) 156 | 157 | if with_comment and self.comment: 158 | sql.append("COMMENT '%s'" % self.comment) 159 | 160 | if after: 161 | sql.append("AFTER `%s`" % after) 162 | else: 163 | sql.append("FIRST") 164 | 165 | return ' '.join(sql) 166 | 167 | def create(self, *args, **kwargs): 168 | """ 169 | Generate the SQL to create (ADD) this column. 170 | 171 | ``after`` is the name(string) of the column this should appear after. 172 | If ``after`` is None, ``FIRST`` is used. 173 | 174 | ``with_comment`` boolean, add column comment to sql statement 175 | 176 | >>> schema.databases['sakila'].tables['rental'].columns['last_update'].create(after="staff_id") 177 | 'ADD COLUMN `last_update` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP AFTER `staff_id`' 178 | >>> schema.databases['sakila'].tables['rental'].columns['rental_id'].create() 179 | 'ADD COLUMN `rental_id` INT(11) NOT NULL auto_increment FIRST' 180 | """ 181 | return "ADD COLUMN %s" % self.define(*args, **kwargs) 182 | 183 | def modify(self, *args, **kwargs): 184 | """ 185 | Generate the SQL to modify this column. 186 | 187 | ``after`` is the name(string) of the column this should appear after. 188 | If ``after`` is None, ``FIRST`` is used.x 189 | 190 | ``with_comment`` boolean, add column comment to sql statement 191 | 192 | >>> schema.databases['sakila'].tables['rental'].columns['customer_id'].define(after="inventory_id") 193 | '`customer_id` SMALLINT(5) UNSIGNED NOT NULL AFTER `inventory_id`' 194 | >>> schema.databases['sakila'].tables['rental'].columns['customer_id'].default = 123 195 | >>> schema.databases['sakila'].tables['rental'].columns['customer_id'].modify(after="inventory_id") 196 | 'MODIFY COLUMN `customer_id` SMALLINT(5) UNSIGNED NOT NULL DEFAULT 123 AFTER `inventory_id`' 197 | """ 198 | return "MODIFY COLUMN %s" % self.define(*args, **kwargs) 199 | 200 | def drop(self): 201 | """ 202 | Generate the SQL to drop this column:: 203 | 204 | >>> schema.databases['sakila'].tables['rental'].columns['rental_id'].drop() 205 | 'DROP COLUMN `rental_id`' 206 | """ 207 | return "DROP COLUMN `%s`" % self.field 208 | 209 | def __eq__(self, other): 210 | if not isinstance(other, ColumnSchema): 211 | return False 212 | 213 | return ((self.field == other.field) 214 | and (self.type == other.type) 215 | and (self.null == other.null) 216 | and (self.default == other.default) 217 | and (self.extra == other.extra) 218 | and (self.collation == other.collation)) 219 | 220 | def __ne__(self, other): 221 | return not self.__eq__(other) 222 | -------------------------------------------------------------------------------- /schemaobject/connection.py: -------------------------------------------------------------------------------- 1 | import pymysql 2 | import re 3 | 4 | REGEX_RFC1738 = re.compile(r''' 5 | (?P\w+):// 6 | (?: 7 | (?P[^:/]*) 8 | (?::(?P[^/]*))? 9 | @)? 10 | (?: 11 | (?P[^/:]*) 12 | (?::(?P[^/]*))? 13 | )? 14 | (?:/(?P.*))? 15 | ''', re.X) 16 | 17 | 18 | def parse_database_url(url): 19 | matches = REGEX_RFC1738.match(url) 20 | result = {} 21 | 22 | if matches: 23 | if matches.group('protocol'): 24 | result['protocol'] = matches.group('protocol') 25 | 26 | if matches.group('username'): 27 | result['user'] = matches.group('username') 28 | 29 | if matches.group('password'): 30 | result['passwd'] = matches.group('password') 31 | 32 | if matches.group('database'): 33 | result['db'] = matches.group('database') 34 | 35 | if matches.group('host'): 36 | result['host'] = matches.group('host') 37 | 38 | try: 39 | result['port'] = int(matches.group('port')) 40 | except (TypeError, ValueError): 41 | pass 42 | 43 | return result 44 | 45 | 46 | def build_database_url(host, protocol='mysql', username='root', password='', port=3306, database=None): 47 | if password: 48 | password = ':' + password 49 | result = "%s://%s%s@%s:%i/" % (protocol, username, password, host, port,) 50 | if database: 51 | result = result + database 52 | return result 53 | 54 | 55 | class DatabaseConnection(object): 56 | """A lightweight wrapper around MySQLdb DB-API""" 57 | 58 | def __init__(self): 59 | self._db = None 60 | self.db = None 61 | self.host = None 62 | self.port = None 63 | self.user = None 64 | 65 | @property 66 | def version(self): 67 | result = self.execute("SELECT VERSION() as version") 68 | return result[0]['version'] 69 | 70 | def execute(self, sql, values=None): 71 | cursor = self._db.cursor() 72 | 73 | try: 74 | basestring 75 | except NameError: 76 | basestring = str 77 | 78 | if isinstance(values, (str, basestring)): 79 | values = (values,) 80 | cursor.execute(sql, values) 81 | 82 | if not cursor.rowcount: 83 | return None 84 | 85 | fields = [field[0] for field in cursor.description] 86 | rows = cursor.fetchall() 87 | 88 | cursor.close() 89 | a = [dict(zip(fields, row)) for row in rows] 90 | return a 91 | 92 | def connect(self, connection_url, charset): 93 | """Connect to the database""" 94 | 95 | kwargs = parse_database_url(connection_url) 96 | if not (kwargs and kwargs['protocol'] == 'mysql'): 97 | raise TypeError("Connection protocol must be MySQL!") 98 | 99 | self.db = kwargs.get('db', None) 100 | self.host = kwargs.get('host', 'localhost') 101 | self.port = kwargs.get('port', 3306) 102 | self.user = kwargs.get('user', None) 103 | kwargs['charset'] = charset 104 | 105 | # can't pass protocol to MySQLdb 106 | del kwargs['protocol'] 107 | # insert charset option 108 | kwargs['charset'] = charset 109 | self._db = pymysql.connect(**kwargs) 110 | 111 | def close(self): 112 | """Close the database connection.""" 113 | if self._db is not None: 114 | self._db.close() 115 | 116 | def __del__(self): 117 | self.close() 118 | 119 | 120 | # Alias MySQL exception 121 | DatabaseError = pymysql.Error 122 | -------------------------------------------------------------------------------- /schemaobject/database.py: -------------------------------------------------------------------------------- 1 | from schemaobject.option import SchemaOption 2 | from schemaobject.table import table_schema_builder 3 | from schemaobject.procedure import procedure_schema_builder 4 | from schemaobject.collections import OrderedDict 5 | from schemaobject.trigger import trigger_schema_builder 6 | from schemaobject.view import view_schema_builder 7 | 8 | 9 | def database_schema_builder(instance): 10 | """ 11 | Returns a dictionary loaded with all of the databases availale on 12 | the MySQL instance. ``instance`` must be an instance SchemaObject. 13 | 14 | .. note:: 15 | This function is automatically called for you and set to 16 | ``schema.databases`` when you create an instance of SchemaObject 17 | 18 | """ 19 | conn = instance.connection 20 | d = OrderedDict() 21 | sql = """ 22 | SELECT SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME, 23 | DEFAULT_COLLATION_NAME 24 | FROM information_schema.SCHEMATA 25 | """ 26 | if conn.db: 27 | sql += " WHERE SCHEMA_NAME = %s" 28 | params = conn.db 29 | else: 30 | params = None 31 | 32 | databases = conn.execute(sql, (params,)) 33 | 34 | if not databases: 35 | return d 36 | 37 | for db_info in databases: 38 | name = db_info['SCHEMA_NAME'] 39 | 40 | db = DatabaseSchema(name=name, parent=instance) 41 | db.options['charset'] = SchemaOption("CHARACTER SET", db_info['DEFAULT_CHARACTER_SET_NAME']) 42 | db.options['collation'] = SchemaOption("COLLATE", db_info['DEFAULT_COLLATION_NAME']) 43 | 44 | d[name] = db 45 | 46 | return d 47 | 48 | 49 | class DatabaseSchema(object): 50 | """ 51 | Object representation of a single database schema 52 | (as per `CREATE DATABASE Syntax `_). 53 | Supports equality and inequality comparison of DatabaseSchemas. 54 | 55 | ``name`` is the database name. 56 | ``parent`` is an instance of SchemaObject 57 | 58 | '>>> for db in schema.databases: 59 | ... print schema.databases[db].name 60 | ... 61 | sakila 62 | '>>> schema.databases['sakila'].name 63 | 'sakila' 64 | 65 | .. note:: 66 | DatabaseSchema objects are automatically created for you 67 | by database_schema_builder and loaded under ``schema.databases`` 68 | """ 69 | 70 | def __init__(self, name, parent): 71 | self.parent = parent 72 | self.name = name 73 | self._options = None 74 | self._tables = None 75 | self._procedures = None 76 | self._triggers = None 77 | self._views = None 78 | 79 | @property 80 | def tables(self): 81 | """ 82 | Lazily loaded dictionary of all the tables within this database. See TableSchema for usage 83 | '>>> len(schema.databases['sakila'].tables) 84 | 16 85 | """ 86 | if self._tables is None: 87 | self._tables = table_schema_builder(database=self) 88 | 89 | return self._tables 90 | 91 | @property 92 | def views(self): 93 | """ 94 | Lazily loaded dictionnary of all the views within this database. See ViewSchema for usage 95 | """ 96 | if self._views is None: 97 | self._views = view_schema_builder(database=self) 98 | 99 | return self._views 100 | 101 | @property 102 | def options(self): 103 | """ 104 | Dictionary of the supported MySQL database options. See OptionSchema for usage. 105 | 106 | * CHARACTER SET == ``options['charset']`` 107 | * COLLATE == ``options['collation']`` 108 | """ 109 | if self._options is None: 110 | self._options = OrderedDict() 111 | 112 | return self._options 113 | 114 | @property 115 | def procedures(self): 116 | """ 117 | Lazily loaded dictionnary of all the procedures within this database. See ProcedureSchema for usage. 118 | """ 119 | if self._procedures is None: 120 | self._procedures = procedure_schema_builder(database=self) 121 | 122 | return self._procedures 123 | 124 | @property 125 | def triggers(self): 126 | """ 127 | Lazily loaded dictionnary of all the triggers within this database. See TriggerSchema for usage. 128 | """ 129 | if self._triggers is None: 130 | self._triggers = trigger_schema_builder(database=self) 131 | 132 | return self._triggers 133 | 134 | def select(self): 135 | """ 136 | Generate the SQL to select this database 137 | >>> schema.databases['sakila'].select() 138 | 'USE `sakila`;' 139 | """ 140 | return "USE `%s`;" % self.name 141 | 142 | def fk_checks(self, val=1): 143 | if val not in (0, 1): 144 | val = 1 145 | 146 | return "SET FOREIGN_KEY_CHECKS = %s;" % val 147 | 148 | def alter(self): 149 | """ 150 | Generate the SQL to alter this database 151 | >>> schema.databases['sakila'].alter() 152 | 'ALTER DATABASE `sakila`' 153 | """ 154 | return "ALTER DATABASE `%s`" % self.name 155 | 156 | def create(self): 157 | """ 158 | Generate the SQL to create this databse 159 | >>> schema.databases['sakila'].create() 160 | 'CREATE DATABASE `sakila` CHARACTER SET=latin1 COLLATE=latin1_swedish_ci;' 161 | """ 162 | return "CREATE DATABASE `%s` %s %s;" % ( 163 | self.name, self.options['charset'].create(), self.options['collation'].create() 164 | ) 165 | 166 | def drop(self): 167 | """ 168 | Generate the SQL to drop this database 169 | >>> schema.databases['sakila'].drop() 170 | 'DROP DATABASE `sakila`;' 171 | """ 172 | return "DROP DATABASE `%s`;" % self.name 173 | 174 | def __eq__(self, other): 175 | if not isinstance(other, DatabaseSchema): 176 | return False 177 | return ( 178 | (self.options['charset'] == other.options['charset']) 179 | and (self.options['collation'] == other.options['collation']) 180 | and (self.name == other.name) 181 | and (self.tables == other.tables) 182 | ) 183 | 184 | def __ne__(self, other): 185 | return not self.__eq__(other) 186 | -------------------------------------------------------------------------------- /schemaobject/foreignkey.py: -------------------------------------------------------------------------------- 1 | from schemaobject.collections import OrderedDict 2 | 3 | 4 | def foreign_key_schema_builder(table): 5 | """ 6 | Returns a dictionary loaded with all of the foreign keys available in the table. 7 | ``table`` must be an instance of TableSchema. 8 | 9 | .. note:: 10 | This function is automatically called for you and set to 11 | ``schema.databases[name].tables[name].foreign_keys`` when you create an instance of SchemaObject 12 | """ 13 | 14 | conn = table.parent.parent.connection 15 | fkeys = OrderedDict() 16 | 17 | def _get_reference_rules(information_schema, table_name, constraint_name): 18 | """ 19 | Returns tuple of strings (update_rule, delete_rule) 20 | (None,None) if constraint not found 21 | 22 | """ 23 | # select UPDATE_RULE, DELETE_RULE from information_schema.REFERENTIAL_CONSTRAINTS where CONSTRAINT_SCHEMA = 'sakila' and TABLE_NAME = 'payment' and CONSTRAINT_NAME = 'fk_payment_customer'; 24 | sql = """ 25 | SELECT UPDATE_RULE, 26 | DELETE_RULE 27 | FROM information_schema.REFERENTIAL_CONSTRAINTS 28 | WHERE CONSTRAINT_SCHEMA = '%s' and TABLE_NAME = '%s' and CONSTRAINT_NAME = '%s' 29 | """ 30 | result = conn.execute(sql % (information_schema, table_name, constraint_name)) 31 | if result: 32 | return result[0]['UPDATE_RULE'], result[0]['DELETE_RULE'] 33 | else: 34 | return None, None 35 | 36 | sql = """ 37 | SELECT K.CONSTRAINT_NAME, 38 | K.TABLE_SCHEMA, K.TABLE_NAME, K.COLUMN_NAME, 39 | K.REFERENCED_TABLE_SCHEMA, K.REFERENCED_TABLE_NAME, K.REFERENCED_COLUMN_NAME, 40 | K.POSITION_IN_UNIQUE_CONSTRAINT 41 | FROM information_schema.KEY_COLUMN_USAGE K, information_schema.TABLE_CONSTRAINTS T 42 | WHERE K.CONSTRAINT_NAME = T.CONSTRAINT_NAME 43 | AND T.CONSTRAINT_TYPE = 'FOREIGN KEY' 44 | AND K.CONSTRAINT_SCHEMA='%s' 45 | AND K.TABLE_NAME='%s' 46 | AND K.REFERENCED_TABLE_NAME is not null 47 | """ 48 | constraints = conn.execute(sql % (table.parent.name, table.name)) 49 | 50 | if not constraints: 51 | return fkeys 52 | 53 | for fk in constraints: 54 | n = fk['CONSTRAINT_NAME'] 55 | 56 | if n not in fkeys: 57 | fk_item = ForeignKeySchema(name=n, parent=table) 58 | fk_item.symbol = n 59 | fk_item.table_schema = fk['TABLE_SCHEMA'] 60 | fk_item.table_name = fk['TABLE_NAME'] 61 | fk_item.referenced_table_schema = fk['REFERENCED_TABLE_SCHEMA'] 62 | fk_item.referenced_table_name = fk['REFERENCED_TABLE_NAME'] 63 | fk_item.update_rule, fk_item.delete_rule = _get_reference_rules(fk_item.table_schema, 64 | fk_item.table_name, fk_item.symbol) 65 | fkeys[n] = fk_item 66 | 67 | # POSITION_IN_UNIQUE_CONSTRAINT may be None 68 | pos = fk['POSITION_IN_UNIQUE_CONSTRAINT'] or 0 69 | 70 | # columns for this fk 71 | if fk['COLUMN_NAME'] not in fkeys[n].columns: 72 | fkeys[n].columns.insert(pos, fk['COLUMN_NAME']) 73 | 74 | # referenced columns for this fk 75 | if fk['REFERENCED_COLUMN_NAME'] not in fkeys[n].referenced_columns: 76 | fkeys[n].referenced_columns.insert(pos, fk['REFERENCED_COLUMN_NAME']) 77 | 78 | return fkeys 79 | 80 | 81 | class ForeignKeySchema(object): 82 | """ 83 | Object representation of a single foreign key. 84 | Supports equality and inequality comparison of ForeignKeySchema. 85 | 86 | ``name`` is the column name. 87 | ``parent`` is an instance of TableSchema 88 | 89 | .. note:: 90 | ForeignKeySchema objects are automatically created for you by foreign_key_schema_builder 91 | and loaded under ``schema.databases[name].tables[name].foreign_keys`` 92 | 93 | Example 94 | 95 | '>>> schema.databases['sakila'].tables['rental'].foreign_keys.keys() 96 | ['fk_rental_customer', 'fk_rental_inventory', 'fk_rental_staff'] 97 | 98 | 99 | Foreign Key Attributes 100 | '>>> schema.databases['sakila'].tables['rental'].foreign_keys['fk_rental_inventory'].name 101 | 'fk_rental_inventory' 102 | '>>> schema.databases['sakila'].tables['rental'].foreign_keys['fk_rental_inventory'].symbol 103 | 'fk_rental_inventory' 104 | '>>> schema.databases['sakila'].tables['rental'].foreign_keys['fk_rental_inventory'].table_schema 105 | 'sakila' 106 | '>>> schema.databases['sakila'].tables['rental'].foreign_keys['fk_rental_inventory'].table_name 107 | 'rental' 108 | '>>> schema.databases['sakila'].tables['rental'].foreign_keys['fk_rental_inventory'].columns 109 | ['inventory_id'] 110 | '>>> schema.databases['sakila'].tables['rental'].foreign_keys['fk_rental_inventory'].referenced_table_name 111 | 'inventory' 112 | '>>> schema.databases['sakila'].tables['rental'].foreign_keys['fk_rental_inventory'].referenced_table_schema 113 | 'sakila' 114 | '>>> schema.databases['sakila'].tables['rental'].foreign_keys['fk_rental_inventory'].referenced_columns 115 | ['inventory_id'] 116 | #match_option will always be None in MySQL 5.x, 6.x 117 | '>>> schema.databases['sakila'].tables['rental'].foreign_keys['fk_rental_inventory'].match_option 118 | '>>> schema.databases['sakila'].tables['rental'].foreign_keys['fk_rental_inventory'].update_rule 119 | 'CASCADE' 120 | '>>> schema.databases['sakila'].tables['rental'].foreign_keys['fk_rental_inventory'].delete_rule 121 | 'RESTRICT' 122 | """ 123 | 124 | def __init__(self, name, parent): 125 | self.parent = parent 126 | 127 | # name of the fk constraint 128 | self.name = name 129 | self.symbol = name 130 | 131 | # primary table info 132 | self.table_schema = None 133 | self.table_name = None 134 | self.columns = [] 135 | 136 | # referenced table info 137 | self.referenced_table_schema = None 138 | self.referenced_table_name = None 139 | self.referenced_columns = [] 140 | 141 | # constraint options 142 | self.match_option = None # will always be none in mysql 5.0-6.0 143 | self.update_rule = None 144 | self.delete_rule = None 145 | 146 | @classmethod 147 | def _format_referenced_col(cls, field, length): 148 | """ 149 | Generate the SQL to format referenced columns in a foreign key 150 | """ 151 | if length: 152 | return "`%s`(%d)" % (field, length) 153 | else: 154 | return "`%s`" % field 155 | 156 | def create(self): 157 | """ 158 | Generate the SQL to create (ADD) this foreign key 159 | 160 | '>>> schema.databases['sakila'].tables['rental'].foreign_keys['fk_rental_inventory'].create() 161 | 'ADD CONSTRAINT `fk_rental_inventory` 162 | FOREIGN KEY `fk_rental_inventory` (`inventory_id`) 163 | REFERENCES `inventory` (`inventory_id`) 164 | ON DELETE RESTRICT ON UPDATE CASCADE' 165 | 166 | .. note: 167 | match_option is ignored when creating a foreign key. 168 | """ 169 | sql = ["ADD CONSTRAINT `%s`" % self.symbol, 170 | "FOREIGN KEY `%s` (%s)" % (self.symbol, ",".join([("`%s`" % c) for c in self.columns]))] 171 | 172 | if self.referenced_table_schema != self.table_schema: 173 | sql.append("REFERENCES `%s`.`%s`" % (self.referenced_table_schema, self.referenced_table_name)) 174 | else: 175 | sql.append("REFERENCES `%s`" % self.referenced_table_name) 176 | 177 | sql.append("(%s)" % ",".join([("`%s`" % c) for c in self.referenced_columns])) 178 | 179 | if self.delete_rule: 180 | sql.append("ON DELETE %s" % self.delete_rule) 181 | 182 | if self.update_rule: 183 | sql.append("ON UPDATE %s" % self.update_rule) 184 | 185 | return ' '.join(sql) 186 | 187 | def drop(self): 188 | """ 189 | Generate the SQL to drop this foreign key 190 | 191 | '>>> schema.databases['sakila'].tables['rental'].foreign_keys['fk_rental_inventory'].drop() 192 | 'DROP FOREIGN KEY `fk_rental_inventory`' 193 | """ 194 | return "DROP FOREIGN KEY `%s`" % self.symbol 195 | 196 | def __eq__(self, other): 197 | if not isinstance(other, ForeignKeySchema): 198 | return False 199 | 200 | # table_schema and referenced_table_schema are ignored 201 | return ((self.table_name == other.table_name) 202 | and (self.referenced_table_name == other.referenced_table_name) 203 | and (self.update_rule == other.update_rule) 204 | and (self.delete_rule == other.delete_rule) 205 | and (self.columns == other.columns) 206 | and (self.referenced_columns == other.referenced_columns)) 207 | 208 | def __ne__(self, other): 209 | return not self.__eq__(other) 210 | -------------------------------------------------------------------------------- /schemaobject/index.py: -------------------------------------------------------------------------------- 1 | from schemaobject.collections import OrderedDict 2 | 3 | 4 | def index_schema_builder(table): 5 | """ 6 | Returns a dictionary loaded with all of the indexes available in the table. 7 | ``table`` must be an instance of TableSchema. 8 | 9 | .. note:: 10 | This function is automatically called for you and set to 11 | ``schema.databases[name].tables[name].indexes`` when you create an instance of SchemaObject 12 | """ 13 | conn = table.parent.parent.connection 14 | 15 | idx = OrderedDict() 16 | indexes = conn.execute("SHOW INDEXES FROM `%s`.`%s`" % (table.parent.name, table.name)) 17 | 18 | if not indexes: 19 | return idx 20 | 21 | for index in indexes: 22 | n = index['Key_name'] 23 | if n not in idx: 24 | indexitem = IndexSchema(name=n, parent=table) 25 | indexitem.non_unique = (bool(index['Non_unique'])) # == not unique 26 | indexitem.table_name = index['Table'] 27 | 28 | key_type = index['Index_type'].upper() 29 | 30 | if index['Key_name'].upper() == "PRIMARY": 31 | indexitem.kind = "PRIMARY" 32 | elif not indexitem.non_unique: 33 | indexitem.kind = "UNIQUE" 34 | elif key_type in ('FULLTEXT', 'SPATIAL'): 35 | indexitem.kind = key_type 36 | else: 37 | indexitem.kind = "INDEX" 38 | 39 | if key_type in ('BTREE', 'HASH', 'RTREE'): 40 | indexitem.type = key_type 41 | 42 | indexitem.collation = index['Collation'] 43 | indexitem.comment = index['Comment'] 44 | 45 | idx[n] = indexitem 46 | 47 | if index['Column_name'] not in idx[n].fields: 48 | idx[n].fields.insert(index['Seq_in_index'], (index['Column_name'], index['Sub_part'] or 0)) 49 | 50 | return idx 51 | 52 | 53 | class IndexSchema(object): 54 | """ 55 | Object representation of a single index. 56 | Supports equality and inequality comparison of IndexSchema. 57 | 58 | ``name`` is the column name. 59 | ``parent`` is an instance of TableSchema 60 | 61 | .. note:: 62 | IndexSchema objects are automatically created for you by index_schema_builder 63 | and loaded under ``schema.databases[name].tables[name].indexes`` 64 | 65 | Example 66 | 67 | >>> schema.databases['sakila'].tables['rental'].indexes.keys() 68 | ['PRIMARY', 'rental_date', 'idx_fk_inventory_id', 'idx_fk_customer_id', 'idx_fk_staff_id'] 69 | 70 | Index Attributes 71 | 72 | >>> schema.databases['sakila'].tables['rental'].indexes['idx_fk_customer_id'].name 73 | 'idx_fk_customer_id' 74 | >>> schema.databases['sakila'].tables['rental'].indexes['idx_fk_customer_id'].table_name 75 | 'rental' 76 | >>> schema.databases['sakila'].tables['rental'].indexes['idx_fk_customer_id'].non_unique 77 | True 78 | >>> schema.databases['sakila'].tables['rental'].indexes['idx_fk_customer_id'].fields 79 | [('customer_id', None)] 80 | #possible types: BTREE, RTREE, HASH 81 | >>> schema.databases['sakila'].tables['rental'].indexes['idx_fk_customer_id'].type 82 | 'BTREE' 83 | #possible kinds: PRIMARY, UNIQUE, FULLTEXT, SPATIAL, INDEX 84 | >>> schema.databases['sakila'].tables['rental'].indexes['rental_date'].kind 85 | 'UNIQUE' 86 | >>> schema.databases['sakila'].tables['film_text'].indexes['idx_title_description'].kind 87 | 'FULLTEXT' 88 | #fields is a list of tuples (field_name, sub_part_length) 89 | >>> schema.databases['sakila'].tables['film_text'].indexes['idx_title_description'].fields 90 | [('title', 0), ('description', 0)] 91 | #collation will always be A in MySQL 5.x - 6.x 92 | >>> schema.databases['sakila'].tables['rental'].indexes['idx_fk_customer_id'].collation 93 | 'A' 94 | >>> schema.databases['sakila'].tables['rental'].indexes['idx_fk_customer_id'].comment 95 | '' 96 | """ 97 | 98 | def __init__(self, name, parent): 99 | self.parent = parent 100 | self.name = name 101 | self.table_name = None 102 | self.non_unique = False 103 | self.fields = [] 104 | self.type = None 105 | self.kind = None 106 | self.collation = None # ignored by the parser. mysql 5.0+ all == A (ASC) 107 | self.comment = None 108 | 109 | @classmethod 110 | def format_sub_part(cls, field, length): 111 | """ 112 | Generate the SQL to format the sub_part length of an indexed column name 113 | 114 | >>> schemaobjects.index.IndexSchema.format_sub_part('column', 0) 115 | '`column`' 116 | >>> schemaobjects.index.IndexSchema.format_sub_part('column', 5) 117 | '`column`(5)' 118 | """ 119 | try: 120 | if not length: 121 | raise ValueError 122 | 123 | length = int(length) 124 | return "`%s`(%d)" % (field, length) 125 | 126 | except ValueError: 127 | return "`%s`" % (field,) 128 | 129 | def create(self): 130 | """ 131 | Generate the SQL to create (ADD) this index 132 | 133 | >>> schema.databases['sakila'].tables['film_text'].indexes['idx_title_description'].create() 134 | 'ADD FULLTEXT INDEX `idx_title_description` (`title`, `description`)' 135 | >>> schema.databases['sakila'].tables['rental'].indexes['rental_date'].create() 136 | 'ADD UNIQUE INDEX `rental_date` (`rental_date`, `inventory_id`, `customer_id`) USING BTREE' 137 | 138 | .. note: 139 | Collation is ignored when creating an index (MySQL default is 'A'). 140 | """ 141 | sql = [] 142 | 143 | if self.kind == "PRIMARY": 144 | sql.append("ADD PRIMARY KEY") 145 | elif self.kind == "UNIQUE": 146 | sql.append("ADD UNIQUE INDEX `%s`" % self.name) 147 | elif self.kind in ('FULLTEXT', 'SPATIAL'): 148 | sql.append("ADD %s INDEX `%s`" % (self.kind, self.name)) 149 | else: 150 | sql.append("ADD INDEX `%s`" % self.name) 151 | 152 | sql.append("(%s)" % ", ".join([self.format_sub_part(f, l) for f, l in self.fields])) 153 | 154 | if self.type in ('BTREE', 'HASH', 'RTREE'): 155 | sql.append("USING %s" % self.type) 156 | 157 | return ' '.join(sql) 158 | 159 | def drop(self, alter_table=True): 160 | """ 161 | Generate the SQL to drop this index 162 | 163 | >>> schema.databases['sakila'].tables['rental'].indexes['PRIMARY'].drop() 164 | 'DROP PRIMARY KEY' 165 | >>> schema.databases['sakila'].tables['rental'].indexes['rental_date'].drop() 166 | 'DROP INDEX `rental_date`' 167 | """ 168 | if self.name == 'PRIMARY': 169 | return "DROP PRIMARY KEY" 170 | elif alter_table: 171 | return "DROP INDEX `%s`" % (self.name) 172 | else: 173 | return "DROP INDEX `%s` ON `%s`" % (self.name, self.parent.name) 174 | 175 | 176 | def __eq__(self, other): 177 | if not isinstance(other, IndexSchema): 178 | return False 179 | 180 | return ((self.name == other.name) 181 | and (self.table_name == other.table_name) 182 | and (self.type == other.type) 183 | and (self.kind == other.kind) 184 | and (self.collation == other.collation) 185 | and (self.non_unique == other.non_unique) 186 | and (self.fields == other.fields)) 187 | 188 | def __ne__(self, other): 189 | return not self.__eq__(other) 190 | -------------------------------------------------------------------------------- /schemaobject/option.py: -------------------------------------------------------------------------------- 1 | class SchemaOption(object): 2 | """ 3 | Object representation of a database or table option 4 | >>> schema.databases['sakila'].tables['rental'].options['engine'].name 5 | 'ENGINE' 6 | >>> schema.databases['sakila'].tables['rental'].options['engine'].value 7 | 'InnoDB' 8 | >>> schema.databases['sakila'].tables['rental'].options['engine'].create() 9 | 'ENGINE=InnoDB' 10 | """ 11 | 12 | def __init__(self, name, value=None): 13 | self.name = name 14 | self.value = value 15 | 16 | def _get_value(self): 17 | return self._value 18 | 19 | def _set_value(self, val): 20 | self._value = val 21 | 22 | value = property(fget=_get_value, fset=_set_value) 23 | 24 | def create(self): 25 | """ 26 | Generate the SQL for this option 27 | >>> schema.databases['sakila'].options['charset'].create() 28 | 'CHARSET=latin1' 29 | >>> schema.databases['sakila'].tables['rental'].options['engine'].create() 30 | 'ENGINE=InnoDB' 31 | >>> schema.databases['sakila'].tables['rental'].options['auto_increment'].create() 32 | 'AUTO_INCREMENT=1' 33 | """ 34 | 35 | # MySQL stores misc options pre-formatted in 1 field (CREATE_OPTIONS) 36 | if not self.name: 37 | return self.value 38 | 39 | if self.name == "COMMENT": 40 | if not self.value: 41 | self.value = '' 42 | return "%s='%s'" % (self.name, self.value) 43 | 44 | if not self.value: 45 | return '' 46 | 47 | if isinstance(self.value, str) and ' ' in self.value: 48 | return "%s='%s'" % (self.name, self.value) 49 | 50 | return "%s=%s" % (self.name, self.value) 51 | 52 | def __eq__(self, other): 53 | if not isinstance(other, SchemaOption): 54 | return False 55 | return (self.value == other.value) and (self.name == other.name) 56 | 57 | def __ne__(self, other): 58 | return not self.__eq__(other) 59 | -------------------------------------------------------------------------------- /schemaobject/procedure.py: -------------------------------------------------------------------------------- 1 | import re 2 | from schemaobject.collections import OrderedDict 3 | 4 | 5 | def procedure_schema_builder(database): 6 | conn = database.parent.connection 7 | 8 | p = OrderedDict() 9 | 10 | sql = """ 11 | SELECT ROUTINE_NAME 12 | FROM information_schema.routines 13 | WHERE ROUTINE_TYPE='PROCEDURE' 14 | AND ROUTINE_SCHEMA='%s' 15 | """ 16 | 17 | procedures = conn.execute(sql % database.name) 18 | 19 | if not procedures: 20 | return p 21 | 22 | for procedure in procedures: 23 | pname = procedure['ROUTINE_NAME'] 24 | sql = "SHOW CREATE PROCEDURE %s" 25 | proc_desc = conn.execute(sql % pname) 26 | if not proc_desc: 27 | continue 28 | 29 | proc_desc = proc_desc[0] 30 | 31 | pp = ProcedureSchema(name=pname, parent=database) 32 | if not proc_desc['Create Procedure']: 33 | pp.definition = "() BEGIN SELECT 'Cannot access to mysql.proc in source DB'; END" 34 | pp.raw_definition = pp.definition 35 | else: 36 | s = re.search('\(', proc_desc['Create Procedure']) 37 | if not s: 38 | continue 39 | 40 | definition = re.sub('--.*', 41 | '', 42 | proc_desc['Create Procedure'][s.start():]) 43 | 44 | pp.definition = re.sub('\s+', ' ', definition) 45 | pp.raw_definition = proc_desc['Create Procedure'][s.start():] 46 | p[pname] = pp 47 | 48 | return p 49 | 50 | 51 | class ProcedureSchema(object): 52 | def __init__(self, name, parent): 53 | self.parent = parent 54 | self.name = name 55 | self.definition = None 56 | 57 | def define(self): 58 | return "`%s` %s" % (self.name, self.raw_definition) 59 | 60 | def create(self): 61 | # SELECT 1 is used so that filters applied to data don't mess 62 | # with the last DELIMITER 63 | return "DELIMITER ;; CREATE PROCEDURE %s;; DELIMITER ; SELECT 1;" % self.define() 64 | 65 | def modify(self, *args, **kwargs): 66 | pass # Not needed for now, one cannot alter body 67 | 68 | def drop(self): 69 | return "DROP PROCEDURE `%s`;" % self.name 70 | 71 | def __eq__(self, other): 72 | if not isinstance(other, ProcedureSchema): 73 | return False 74 | 75 | return ((self.name == other.name) 76 | and (self.definition == other.definition)) 77 | 78 | def __ne__(self, other): 79 | return not self.__eq__(other) 80 | -------------------------------------------------------------------------------- /schemaobject/schema.py: -------------------------------------------------------------------------------- 1 | from schemaobject.connection import DatabaseConnection 2 | from schemaobject.database import database_schema_builder 3 | 4 | 5 | class SchemaObject(object): 6 | """ 7 | Object representation of a single MySQL instance. 8 | If database name is not specified in ``connection_url``, 9 | all databases on the MySQL instance will be loaded. 10 | 11 | ``connection_url`` - the database url as per `RFC1738 `_ 12 | 13 | >>> schema = schemaobject.SchemaObject('mysql://username:password@localhost:3306/sakila', charset='utf8') 14 | >>> schema.host 15 | 'localhost' 16 | >>> schema.port 17 | 3306 18 | >>> schema.user 19 | 'username' 20 | >>> schema.version 21 | '5.1.30' 22 | >>> schema.charset 23 | 'utf8' 24 | >>> schema.selected.name 25 | 'sakila' 26 | 27 | """ 28 | 29 | def __init__(self, connection_url, charset): 30 | 31 | self._databases = None 32 | 33 | self.connection = DatabaseConnection() 34 | self.connection.connect(connection_url, charset) 35 | 36 | self.host = self.connection.host 37 | self.port = self.connection.port 38 | self.user = self.connection.user 39 | self.version = self.connection.version 40 | 41 | @property 42 | def selected(self): 43 | """ 44 | Returns the DatabaseSchema object associated with the database name in the connection url 45 | 46 | >>> schema.selected.name 47 | 'sakila' 48 | """ 49 | if self.connection.db: 50 | return self.databases[self.connection.db] 51 | else: 52 | return None 53 | 54 | @property 55 | def databases(self): 56 | """ 57 | Lazily loaded dictionary of the databases within this MySQL instance. 58 | 59 | See DatabaseSchema for usage:: 60 | 61 | #if database name is specified in the connection url 62 | >>> len(schema.databases) 63 | 1 64 | >>> schema.databases.keys() 65 | ['sakila'] 66 | 67 | """ 68 | if self._databases is None: 69 | self._databases = database_schema_builder(instance=self) 70 | 71 | return self._databases 72 | -------------------------------------------------------------------------------- /schemaobject/table.py: -------------------------------------------------------------------------------- 1 | import re 2 | from schemaobject.collections import OrderedDict 3 | from schemaobject.column import column_schema_builder 4 | from schemaobject.index import index_schema_builder 5 | from schemaobject.foreignkey import foreign_key_schema_builder 6 | from schemaobject.option import SchemaOption 7 | 8 | REGEX_MULTI_SPACE = re.compile('\s\s+') 9 | 10 | 11 | def table_schema_builder(database): 12 | """ 13 | Returns a dictionary loaded with all of the tables available in the database. 14 | ``database`` must be an instance of DatabaseSchema. 15 | 16 | .. note:: 17 | This function is automatically called for you and set to 18 | ``schema.databases[name].tables`` when you create an instance of SchemaObject 19 | """ 20 | conn = database.parent.connection 21 | 22 | t = OrderedDict() 23 | sql = """ 24 | SELECT TABLE_NAME, ENGINE, ROW_FORMAT, AUTO_INCREMENT, 25 | CREATE_OPTIONS, TABLE_COLLATION, TABLE_COMMENT 26 | FROM information_schema.`TABLES` 27 | WHERE TABLE_SCHEMA='%s' 28 | AND not isnull(ENGINE) 29 | """ 30 | tables = conn.execute(sql % database.name) 31 | 32 | if not tables: 33 | return t 34 | 35 | for table_info in tables: 36 | 37 | name = table_info['TABLE_NAME'] 38 | 39 | if "TABLE_COLLATION" not in table_info: 40 | charset = None 41 | 42 | pos = table_info['TABLE_COLLATION'].find('_') 43 | 44 | if not pos: 45 | charset = table_info['TABLE_COLLATION'] 46 | else: 47 | charset = table_info['TABLE_COLLATION'][:pos] 48 | 49 | table = TableSchema(name=name, parent=database) 50 | table.options['engine'] = SchemaOption('ENGINE', table_info['ENGINE']) 51 | table.options['charset'] = SchemaOption("CHARSET", charset) 52 | table.options['collation'] = SchemaOption("COLLATE", table_info['TABLE_COLLATION']) 53 | table.options['row_format'] = SchemaOption('ROW_FORMAT', table_info['ROW_FORMAT']) 54 | table.options['auto_increment'] = SchemaOption('AUTO_INCREMENT', table_info['AUTO_INCREMENT']) 55 | table.options['create_options'] = SchemaOption(None, table_info['CREATE_OPTIONS']) 56 | table.options['comment'] = SchemaOption('COMMENT', table_info['TABLE_COMMENT']) 57 | 58 | t[name] = table 59 | return t 60 | 61 | 62 | class TableSchema(object): 63 | """ 64 | Object representation of a single table 65 | (as per `CREATE TABLE Syntax `_). 66 | Supports equality and inequality comparison of TableSchema. 67 | 68 | ``name`` is the column name. 69 | ``parent`` is an instance of DatabaseSchema 70 | 71 | .. note:: 72 | TableSchema objects are automatically created for you by table_schema_builder 73 | and loaded under ``schema.databases[name].tables`` 74 | 75 | .. note:: 76 | table options ``auto_increment``, ``comment`` are ignored in ``__eq__``, ``__neq__`` comparisons. 77 | 78 | Example 79 | 80 | >>> schema.databases['sakila'].tables.keys() 81 | ['actor', 'address', 'category', 'city', 'country', 'customer', 'film', 82 | 'film_actor', 'film_category', 'film_text', 'inventory', 'language', 83 | 'payment', 'rental', 'staff', 'store'] 84 | >>> schema.databases['sakila'].tables['rental'].options.keys() 85 | ['engine', 'charset', 'collation', 'row_format', 'auto_increment', 'create_options', 'comment'] 86 | >>> schema.databases['sakila'].tables['rental'].indexes.keys() 87 | ['PRIMARY', 'rental_date', 'idx_fk_inventory_id', 'idx_fk_customer_id', 'idx_fk_staff_id'] 88 | >>> schema.databases['sakila'].tables['rental'].foreign_keys.keys() 89 | ['fk_rental_customer', 'fk_rental_inventory', 'fk_rental_staff'] 90 | 91 | Table Attributes 92 | 93 | >>> schema.databases['sakila'].tables['rental'].name 94 | 'rental' 95 | 96 | Table Options 97 | 98 | * ENGINE == ``options['engine']`` 99 | * CHARSET, CHARACTER SET == ``options['charset']`` 100 | * COLLATE == ``options['collation']`` 101 | * ROW_FORMAT == ``options['row_format']`` 102 | * AUTO_INCREMENT == ``options['auto_increment']`` 103 | * CREATE_OPTIONS == ``options['create_options']`` 104 | * COMMENT == ``options['comment']`` 105 | """ 106 | 107 | def __init__(self, name, parent): 108 | self.parent = parent 109 | self.name = name 110 | self._options = None 111 | self._columns = None 112 | self._indexes = None 113 | self._foreign_keys = None 114 | 115 | @property 116 | def columns(self): 117 | """ 118 | Lazily loaded dictionary of all the columns within this table. See ColumnSchema for usage 119 | 120 | >>> len(schema.databases['sakila'].tables['rental'].columns) 121 | 7 122 | >>> schema.databases['sakila'].tables['rental'].columns.keys() 123 | ['rental_id', 'rental_date', 'inventory_id', 'customer_id', 'return_date', 'staff_id', 'last_update' 124 | """ 125 | if self._columns is None: 126 | self._columns = column_schema_builder(table=self) 127 | return self._columns 128 | 129 | @property 130 | def indexes(self): 131 | """ 132 | Lazily loaded dictionary of all the indexes within this table. See IndexSchema for usage 133 | 134 | >>> len(schema.databases['sakila'].tables['rental'].indexes) 135 | 5 136 | >>> schema.databases['sakila'].tables['rental'].indexes.keys() 137 | ['PRIMARY', 'rental_date', 'idx_fk_inventory_id', 'idx_fk_customer_id', 'idx_fk_staff_id'] 138 | """ 139 | if self._indexes is None: 140 | self._indexes = index_schema_builder(table=self) 141 | return self._indexes 142 | 143 | @property 144 | def foreign_keys(self): 145 | """ 146 | Lazily loaded dictionary of all the foreign keys within this table. See ForeignKeySchema for usage 147 | 148 | >>> len(schema.databases['sakila'].tables['rental'].foreign_keys) 149 | 3 150 | >>> schema.databases['sakila'].tables['rental'].foreign_keys.keys() 151 | ['fk_rental_customer', 'fk_rental_inventory', 'fk_rental_staff'] 152 | """ 153 | if self._foreign_keys is None: 154 | self._foreign_keys = foreign_key_schema_builder(table=self) 155 | return self._foreign_keys 156 | 157 | @property 158 | def options(self): 159 | """ 160 | Dictionary of the supported MySQL table options. See OptionSchema for usage. 161 | 162 | * ENGINE == ``options['engine']`` 163 | * CHARSET, CHARACTER SET == ``options['charset']`` 164 | * COLLATE == ``options['collation']`` 165 | * ROW_FORMAT == ``options['row_format']`` 166 | * AUTO_INCREMENT == ``options['auto_increment']`` 167 | * CREATE_OPTIONS == ``options['create_options']`` 168 | * COMMENT == ``options['comment']`` 169 | """ 170 | if self._options is None: 171 | self._options = OrderedDict() 172 | return self._options 173 | 174 | def alter(self): 175 | """ 176 | Generate the SQL to alter this table 177 | >>> schema.databases['sakila'].tables['rental'].alter() 178 | 'ALTER TABLE `rental`' 179 | """ 180 | return "ALTER TABLE `%s`" % (self.name) 181 | 182 | def create(self): 183 | """ 184 | Generate the SQL to create a this table 185 | >>> schema.databases['sakila'].tables['rental'].create() 186 | 'CREATE TABLE `rental` ( 187 | `rental_id` int(11) NOT NULL AUTO_INCREMENT, 188 | `rental_date` datetime NOT NULL, 189 | `inventory_id` mediumint(8) unsigned NOT NULL, 190 | `customer_id` smallint(5) unsigned NOT NULL, 191 | `return_date` datetime DEFAULT NULL, 192 | `staff_id` tinyint(3) unsigned NOT NULL, 193 | `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 194 | PRIMARY KEY (`rental_id`), 195 | UNIQUE KEY `rental_date` (`rental_date`,`inventory_id`,`customer_id`), 196 | KEY `idx_fk_inventory_id` (`inventory_id`), 197 | KEY `idx_fk_customer_id` (`customer_id`), 198 | KEY `idx_fk_staff_id` (`staff_id`), 199 | CONSTRAINT `fk_rental_customer` FOREIGN KEY (`customer_id`) REFERENCES `customer` (`customer_id`) ON UPDATE CASCADE, 200 | CONSTRAINT `fk_rental_inventory` FOREIGN KEY (`inventory_id`) REFERENCES `inventory` (`inventory_id`) ON UPDATE CASCADE, 201 | CONSTRAINT `fk_rental_staff` FOREIGN KEY (`staff_id`) REFERENCES `staff` (`staff_id`) ON UPDATE CASCADE) 202 | ENGINE=InnoDB DEFAULT CHARSET=utf8;' 203 | """ 204 | cursor = self.parent.parent.connection 205 | result = cursor.execute("SHOW CREATE TABLE `%s`.`%s`" % (self.parent.name, self.name)) 206 | sql = result[0]['Create Table'] + ';' 207 | sql = sql.replace('\n', '') 208 | return REGEX_MULTI_SPACE.sub(' ', sql) 209 | 210 | def drop(self): 211 | """ 212 | Generate the SQL to drop this table 213 | >>> schema.databases['sakila'].tables['rental'].drop() 214 | 'DROP TABLE `rental`;' 215 | """ 216 | return "DROP TABLE `%s`;" % (self.name) 217 | 218 | def __eq__(self, other): 219 | if not isinstance(other, TableSchema): 220 | return False 221 | 222 | return ((self.options['engine'] == other.options['engine']) 223 | and (self.options['collation'] == other.options['collation']) 224 | and (self.options['row_format'] == other.options['row_format']) 225 | and (self.indexes == other.indexes) 226 | and (self.columns == other.columns) 227 | and (self.foreign_keys == other.foreign_keys)) 228 | 229 | def __ne__(self, other): 230 | return not self.__eq__(other) 231 | -------------------------------------------------------------------------------- /schemaobject/trigger.py: -------------------------------------------------------------------------------- 1 | import re 2 | from schemaobject.collections import OrderedDict 3 | 4 | 5 | def trigger_schema_builder(database): 6 | conn = database.parent.connection 7 | 8 | t = OrderedDict() 9 | 10 | sql = """ 11 | SELECT TRIGGER_NAME, EVENT_MANIPULATION, EVENT_OBJECT_TABLE, 12 | ACTION_STATEMENT, ACTION_TIMING 13 | FROM INFORMATION_SCHEMA.TRIGGERS 14 | WHERE TRIGGER_SCHEMA='%s' 15 | """ 16 | 17 | triggers = conn.execute(sql % database.name) 18 | 19 | if not triggers: 20 | return t 21 | 22 | for trigger in triggers: 23 | trig_name = trigger['TRIGGER_NAME'] 24 | 25 | trig = TriggerSchema(name=trig_name, parent=database) 26 | body = trigger['ACTION_STATEMENT'] 27 | trig.statement = re.sub('\s\s+', ' ', body) 28 | trig.timing = trigger['ACTION_TIMING'] 29 | trig.event = trigger['EVENT_MANIPULATION'] 30 | trig.table = trigger['EVENT_OBJECT_TABLE'] 31 | 32 | t[trig_name] = trig 33 | 34 | return t 35 | 36 | 37 | class TriggerSchema(object): 38 | def __init__(self, name, parent): 39 | self.parent = parent 40 | self.name = name 41 | self.statement = None 42 | self.timing = None 43 | self.event = None 44 | self.table = None 45 | 46 | def define(self): 47 | sql = ["`%s` %s %s ON %s FOR EACH ROW %s" % (self.name, 48 | self.timing, 49 | self.event, 50 | self.table, 51 | self.statement)] 52 | 53 | return ' '.join(sql) 54 | 55 | def create(self): 56 | # SELECT 1 is used so that filters applied to data don't mess 57 | # with the last DELIMITER 58 | return "DELIMITER ;; CREATE TRIGGER %s;; DELIMITER ; SELECT 1;" % self.define() 59 | 60 | def modify(self): 61 | pass # Need to drop + re-create 62 | 63 | def drop(self): 64 | return "DROP TRIGGER `%s`;" % self.name 65 | 66 | def __eq__(self, other): 67 | if not isinstance(other, TriggerSchema): 68 | return False 69 | 70 | return ((self.name == other.name) 71 | and (self.statement == other.statement) 72 | and (self.timing == other.timing) 73 | and (self.table == other.table) 74 | and (self.event == other.event)) 75 | 76 | def __ne__(self, other): 77 | return not self.__eq__(other) 78 | -------------------------------------------------------------------------------- /schemaobject/view.py: -------------------------------------------------------------------------------- 1 | import re 2 | from schemaobject.collections import OrderedDict 3 | 4 | 5 | def view_schema_builder(database): 6 | conn = database.parent.connection 7 | 8 | v = OrderedDict() 9 | 10 | sql = """ 11 | SELECT TABLE_NAME 12 | FROM information_schema.views 13 | WHERE TABLE_SCHEMA = '%s' 14 | ORDER BY TABLE_NAME 15 | """ 16 | 17 | views = conn.execute(sql % database.name) 18 | 19 | if not views: 20 | return v 21 | 22 | for view in views: 23 | vname = view['TABLE_NAME'] 24 | sql = "SHOW CREATE VIEW %s" 25 | view_desc = conn.execute(sql % vname) 26 | if not view_desc: 27 | continue 28 | 29 | view_desc = view_desc[0] 30 | 31 | vv = ViewSchema(name=vname, parent=database) 32 | s = re.search('\(?select', view_desc['Create View'], re.IGNORECASE) 33 | if not s: 34 | continue 35 | 36 | vv.definition = view_desc['Create View'][s.start():] 37 | v[vname] = vv 38 | 39 | return v 40 | 41 | 42 | class ViewSchema(object): 43 | def __init__(self, name, parent): 44 | self.parent = parent 45 | self.name = name 46 | self.definition = None 47 | 48 | def define(self): 49 | return self.definition 50 | 51 | def create(self): 52 | return "CREATE VIEW `%s` AS %s;" % (self.name, self.definition) 53 | 54 | def modify(self): 55 | return "ALTER VIEW `%s` AS %s;" % (self.name, self.definition) 56 | 57 | def drop(self): 58 | return "DROP VIEW `%s`;" % self.name 59 | 60 | def __eq__(self, other): 61 | if not isinstance(other, ViewSchema): 62 | return False 63 | 64 | return ((self.name == other.name) 65 | and (self.definition == other.definition)) 66 | 67 | def __ne__(self, other): 68 | return not self.__eq__(other) 69 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from setuptools import setup 3 | 4 | setup( 5 | name='SchemaObject', 6 | packages=['schemaobject'], 7 | version='0.5.10', 8 | description="Iterate over a MySQL database schema as a Python object.", 9 | author="Mitch Matuson, Mustafa Ozgur", 10 | author_email="code@matuson.com, root@mit.sh", 11 | url="http://matuson.com/code/schemaobject", 12 | keywords=["MySQL", "database", "schema"], 13 | classifiers=[ 14 | "Intended Audience :: Developers", 15 | "License :: OSI Approved :: Apache Software License", 16 | "Programming Language :: Python", 17 | "Topic :: Software Development :: Libraries :: Python Modules", 18 | "Topic :: Database", 19 | "Topic :: Database :: Front-Ends", 20 | ], 21 | long_description="""\ 22 | SchemaObject provides a simple, easy to use Python object interface to a MySQL database schema. 23 | You can effortlessly write tools to test, validate, sync, migrate, or manage your schema as well as generate 24 | the SQL necessary to make changes to it. 25 | """, 26 | install_requires=[ 27 | "PyMySQL >= 0.6.2", 28 | ], 29 | ) 30 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmatuson/SchemaObject/52d296b5a7824a3645995683f2a4913a968ff579/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_collections.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import unittest 3 | from schemaobject.collections import OrderedDict 4 | 5 | class TestOrderedDict(unittest.TestCase): 6 | 7 | def setUp(self): 8 | self.test = OrderedDict() 9 | self.test['name'] = "John Smith" 10 | self.test['location'] = "New York" 11 | 12 | def test_eq_dict_keys(self): 13 | self.assertEqual( ['name', 'location'], list(self.test.keys()) ) 14 | 15 | def test_neq_dict_keys(self): 16 | self.assertNotEqual( ['location', 'name'], list(self.test.keys()) ) 17 | 18 | def test_eq_dict_items(self): 19 | self.assertEqual( [('name', 'John Smith'), ('location', 'New York')], list(self.test.items()) ) 20 | 21 | def test_neq_dict_items(self): 22 | self.assertNotEqual( [('location', 'New York'), ('name', 'John Smith')], list(self.test.items()) ) 23 | 24 | def test_dict_iterkeys(self): 25 | for i, v in enumerate(self.test.keys()): 26 | if i == 0: 27 | self.assertEqual(v, 'name') 28 | if i == 1: 29 | self.assertEqual(v, 'location') 30 | 31 | def test_dict_iteritems(self): 32 | for i, v in enumerate(self.test.items()): 33 | if i == 0: 34 | self.assertEqual(v, ('name', 'John Smith')) 35 | if i == 1: 36 | self.assertEqual(v, ('location', 'New York')) 37 | 38 | def test_index(self): 39 | self.assertEqual(1, self.test.index("location")) 40 | self.assertRaises(ValueError, self.test.index, ("unknown_key",)) 41 | 42 | def test_insert(self): 43 | self.assertFalse("age" in self.test) 44 | self.test.insert(1, ("age", 100)) 45 | self.assertTrue(1, self.test.index("age")) 46 | self.assertTrue(2, self.test.index("location")) 47 | 48 | def test__delitem__(self): 49 | self.assertTrue("location" in self.test) 50 | del self.test['location'] 51 | self.assertFalse("location" in self.test) -------------------------------------------------------------------------------- /tests/test_column.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import unittest 3 | import schemaobject 4 | 5 | 6 | class TestColumnSchema(unittest.TestCase): 7 | 8 | def setUp(self): 9 | self.database_url = "mysql://root:root@localhost:3306/" 10 | self.db = schemaobject.SchemaObject(self.database_url + 'sakila', charset='utf8') 11 | self.db = self.db.selected 12 | 13 | def test_column_count(self): 14 | self.assertEqual(9, len(self.db.tables['customer'].columns)) 15 | 16 | def test_columns(self): 17 | self.assertEqual(list(self.db.tables['customer'].columns.keys()), 18 | ['customer_id', 'store_id', 'first_name', 'last_name', 19 | 'email', 'address_id', 'active', 'create_date', 'last_update']) 20 | 21 | def test_column_field(self): 22 | self.assertEqual("store_id", self.db.tables['customer'].columns['store_id'].field) 23 | 24 | def test_column_type(self): 25 | self.assertEqual("varchar(50)", self.db.tables['customer'].columns['email'].type) 26 | 27 | def test_column_type_enum(self): 28 | self.assertEqual("enum('G','PG','PG-13','R','NC-17')", 29 | self.db.tables['film'].columns['rating'].type) 30 | 31 | def test_column_type_set(self): 32 | self.assertEqual("set('Trailers','Commentaries','Deleted Scenes','Behind the Scenes')", 33 | self.db.tables['film'].columns['special_features'].type) 34 | 35 | def test_column_charset(self): 36 | self.assertEqual("utf8", self.db.tables['customer'].columns['last_name'].charset) 37 | 38 | def test_column_collation(self): 39 | self.assertEqual("utf8_general_ci", self.db.tables['customer'].columns['last_name'].collation) 40 | 41 | def test_read_column_null(self): 42 | self.assertTrue(self.db.tables['customer'].columns['email'].null) 43 | 44 | def test_read_column_not_null(self): 45 | self.assertFalse(self.db.tables['customer'].columns['active'].null) 46 | 47 | def test_column_key(self): 48 | self.assertEqual('PRI', self.db.tables['customer'].columns['customer_id'].key) 49 | 50 | def test_column_default(self): 51 | self.assertEqual("CURRENT_TIMESTAMP", self.db.tables['customer'].columns['last_update'].default) 52 | 53 | def test_column_extra(self): 54 | self.assertEqual('auto_increment', self.db.tables['customer'].columns['customer_id'].extra) 55 | 56 | def test_read_column_comment(self): 57 | self.assertEqual('', self.db.tables['customer'].columns['store_id'].comment) 58 | 59 | def test_columns_eq(self): 60 | self.assertEqual(self.db.tables['customer'].columns['store_id'], 61 | self.db.tables['customer'].columns['store_id']) 62 | 63 | def test_columns_neq(self): 64 | self.assertNotEqual(self.db.tables['customer'].columns['store_id'], 65 | self.db.tables['customer'].columns['last_name']) 66 | 67 | def test_column_null(self): 68 | self.assertEqual("`email` varchar(50) NULL AFTER `last_name`", 69 | self.db.tables['customer'].columns['email'].define(after='last_name')) 70 | 71 | def test_column_not_null(self): 72 | self.db.tables['customer'].columns['email'].null = True 73 | self.assertEqual("`email` varchar(50) NULL AFTER `last_name`", 74 | self.db.tables['customer'].columns['email'].define(after='last_name')) 75 | 76 | def test_column_default_string(self): 77 | self.db.tables['rental'].columns['rental_date'].default = '0000-00-00 00:00:00' 78 | self.assertEqual("`rental_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' AFTER `rental_id`", 79 | self.db.tables['rental'].columns['rental_date'].define(after='rental_id')) 80 | 81 | self.db.tables['customer'].columns['email'].null = False 82 | self.db.tables['customer'].columns['email'].default = '' 83 | self.assertEqual("`email` varchar(50) NOT NULL DEFAULT '' AFTER `last_name`", 84 | self.db.tables['customer'].columns['email'].define(after='last_name')) 85 | 86 | def test_column_default_number(self): 87 | self.db.tables['rental'].columns['customer_id'].default = 123 88 | self.assertEqual("`customer_id` smallint(5) unsigned NOT NULL DEFAULT 123 AFTER `inventory_id`", 89 | self.db.tables['rental'].columns['customer_id'].define(after='inventory_id')) 90 | 91 | self.db.tables['rental'].columns['customer_id'].default = 0 92 | self.assertEqual("`customer_id` smallint(5) unsigned NOT NULL DEFAULT 0 AFTER `inventory_id`", 93 | self.db.tables['rental'].columns['customer_id'].define(after='inventory_id')) 94 | 95 | def test_column_default_reserved(self): 96 | self.assertEqual( 97 | "`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP AFTER `staff_id`", 98 | self.db.tables['rental'].columns['last_update'].define(after='staff_id')) 99 | 100 | def test_column_no_default(self): 101 | self.assertEqual("`email` varchar(50) NULL AFTER `last_name`", 102 | self.db.tables['customer'].columns['email'].define(after='last_name')) 103 | 104 | def test_define_extra_column(self): 105 | self.assertEqual("`customer_id` smallint(5) unsigned NOT NULL auto_increment FIRST", 106 | self.db.tables['customer'].columns['customer_id'].define()) 107 | 108 | def test_column_no_extra(self): 109 | self.assertEqual("`email` varchar(50) NULL AFTER `last_name`", 110 | self.db.tables['customer'].columns['email'].define(after='last_name')) 111 | 112 | def test_define_column_comment(self): 113 | self.db.tables['customer'].columns['email'].comment = "email address field" 114 | self.assertEqual("`email` varchar(50) NULL COMMENT 'email address field' AFTER `last_name`", 115 | self.db.tables['customer'].columns['email'].define(after='last_name', with_comment=True)) 116 | 117 | def test_column_no_comment(self): 118 | self.db.tables['customer'].columns['email'].comment = "email address field" 119 | self.assertEqual("`email` varchar(50) NULL AFTER `last_name`", 120 | self.db.tables['customer'].columns['email'].define(after='last_name', with_comment=False)) 121 | 122 | def test_column_no_charset_collate(self): 123 | self.assertEqual("`customer_id` smallint(5) unsigned NOT NULL auto_increment FIRST", 124 | self.db.tables['customer'].columns['customer_id'].define()) 125 | 126 | def test_column_charset_collate_same_as_parent(self): 127 | self.assertEqual("`first_name` varchar(45) NOT NULL AFTER `store_id`", 128 | self.db.tables['customer'].columns['first_name'].define(after='store_id')) 129 | 130 | def test_column_charset_collate_diff_from_parent(self): 131 | self.db.tables['customer'].columns['first_name'].charset = 'latin1' 132 | self.db.tables['customer'].columns['first_name'].collation = 'latin1_general_ci' 133 | 134 | self.assertEqual( 135 | "`first_name` varchar(45) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL AFTER `store_id`", 136 | self.db.tables['customer'].columns['first_name'].define(after='store_id')) 137 | 138 | def test_parent_charset_collate_diff_from_column(self): 139 | self.db.tables['customer'].options['charset'].value = 'latin1' 140 | self.db.tables['customer'].options['collation'].value = 'latin1_general_ci' 141 | 142 | self.assertEqual( 143 | "`first_name` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL AFTER `store_id`", 144 | self.db.tables['customer'].columns['first_name'].define(after='store_id')) 145 | 146 | def test_column_after(self): 147 | self.assertEqual("`last_name` varchar(45) NOT NULL AFTER `first_name`", 148 | self.db.tables['customer'].columns['last_name'].define(after='first_name')) 149 | 150 | def test_column_first(self): 151 | self.assertEqual("`last_name` varchar(45) NOT NULL FIRST", 152 | self.db.tables['customer'].columns['last_name'].define(after=None)) 153 | 154 | def test_column_definition_syntax(self): 155 | self.db.tables['customer'].columns['first_name'].default = "bob" 156 | self.db.tables['customer'].columns['first_name'].comment = "first name" 157 | self.db.tables['customer'].columns['first_name'].charset = 'latin1' 158 | self.db.tables['customer'].columns['first_name'].collation = 'latin1_general_ci' 159 | self.db.tables['customer'].columns['customer_id'].default = 0 160 | 161 | self.assertEqual( 162 | "`first_name` varchar(45) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL DEFAULT 'bob' COMMENT 'first name' AFTER `store_id`", 163 | self.db.tables['customer'].columns['first_name'].define(after='store_id', with_comment=True)) 164 | 165 | self.assertEqual( 166 | "`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP AFTER `staff_id`", 167 | self.db.tables['rental'].columns['last_update'].define(after='staff_id')) 168 | 169 | self.assertEqual("`customer_id` smallint(5) unsigned NOT NULL DEFAULT 0 auto_increment FIRST", 170 | self.db.tables['customer'].columns['customer_id'].define()) 171 | 172 | def test_create_column(self): 173 | self.assertEqual("ADD COLUMN `last_name` varchar(45) NOT NULL AFTER `first_name`", 174 | self.db.tables['customer'].columns['last_name'].create(after='first_name')) 175 | 176 | def test_create_column_with_comment(self): 177 | self.db.tables['customer'].columns['last_name'].comment = "hello" 178 | self.assertEqual("ADD COLUMN `last_name` varchar(45) NOT NULL COMMENT 'hello' AFTER `first_name`", 179 | self.db.tables['customer'].columns['last_name'].create(after='first_name', with_comment=True)) 180 | self.db.tables['customer'].columns['last_name'] = '' 181 | 182 | def test_modify_column(self): 183 | self.assertEqual("MODIFY COLUMN `last_name` varchar(45) NOT NULL AFTER `first_name`", 184 | self.db.tables['customer'].columns['last_name'].modify(after='first_name')) 185 | 186 | def test_modify_column_with_comment(self): 187 | self.db.tables['customer'].columns['last_name'].comment = "hello" 188 | self.assertEqual("MODIFY COLUMN `last_name` varchar(45) NOT NULL COMMENT 'hello' AFTER `first_name`", 189 | self.db.tables['customer'].columns['last_name'].modify(after='first_name', with_comment=True)) 190 | self.db.tables['customer'].columns['last_name'] = '' 191 | 192 | def test_column_drop(self): 193 | self.assertEqual("DROP COLUMN `last_name`", 194 | self.db.tables['customer'].columns['last_name'].drop()) -------------------------------------------------------------------------------- /tests/test_connection.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | """ 4 | Tests for the Database Connection URL (RFC 1738) 5 | http://www.ietf.org/rfc/rfc1738.txt 6 | """ 7 | 8 | import unittest 9 | from schemaobject.connection import REGEX_RFC1738 10 | from schemaobject.connection import build_database_url 11 | 12 | 13 | class TestDatabaseURL(unittest.TestCase): 14 | 15 | def test_user_pw_host_port_db(self): 16 | test = "mysql://user:password@localhost:3306/database" 17 | matches = REGEX_RFC1738.match(test) 18 | 19 | self.assertTrue(matches) 20 | self.assertEqual(matches.group('protocol'), 'mysql') 21 | self.assertEqual(matches.group('username'), 'user') 22 | self.assertEqual(matches.group('password'), 'password') 23 | self.assertEqual(matches.group('host'), 'localhost') 24 | self.assertEqual(matches.group('port'), '3306') 25 | self.assertEqual(matches.group('database'), 'database') 26 | 27 | def test_user_pw_host_db(self): 28 | test = "mysql://user:password@localhost/database" 29 | matches = REGEX_RFC1738.match(test) 30 | 31 | self.assertTrue(matches) 32 | self.assertEqual(matches.group('protocol'), 'mysql') 33 | self.assertEqual(matches.group('username'), 'user') 34 | self.assertEqual(matches.group('password'), 'password') 35 | self.assertEqual(matches.group('host'), 'localhost') 36 | self.assertEqual(matches.group('port'), None) 37 | self.assertEqual(matches.group('database'), 'database') 38 | 39 | 40 | def test_user_pw_db(self): 41 | test = "mysql://user:password@/database" 42 | matches = REGEX_RFC1738.match(test) 43 | 44 | self.assertTrue(matches) 45 | self.assertEqual(matches.group('protocol'), 'mysql') 46 | self.assertEqual(matches.group('username'), 'user') 47 | self.assertEqual(matches.group('password'), 'password') 48 | self.assertEqual(matches.group('host'), '') 49 | self.assertEqual(matches.group('port'), None) 50 | self.assertEqual(matches.group('database'), 'database') 51 | 52 | def test_host_port_db(self): 53 | test = "mysql://localhost:3306/database" 54 | matches = REGEX_RFC1738.match(test) 55 | 56 | self.assertTrue(matches) 57 | self.assertEqual(matches.group('protocol'), 'mysql') 58 | self.assertEqual(matches.group('username'), None) 59 | self.assertEqual(matches.group('password'), None) 60 | self.assertEqual(matches.group('host'), 'localhost') 61 | self.assertEqual(matches.group('port'), '3306') 62 | self.assertEqual(matches.group('database'), 'database') 63 | 64 | def test_host_db(self): 65 | test = "mysql://localhost/database" 66 | matches = REGEX_RFC1738.match(test) 67 | 68 | self.assertTrue(matches) 69 | self.assertEqual(matches.group('protocol'), 'mysql') 70 | self.assertEqual(matches.group('username'), None) 71 | self.assertEqual(matches.group('password'), None) 72 | self.assertEqual(matches.group('host'), 'localhost') 73 | self.assertEqual(matches.group('port'), None) 74 | self.assertEqual(matches.group('database'), 'database') 75 | 76 | 77 | def test_host(self): 78 | test = "mysql://localhost" 79 | matches = REGEX_RFC1738.match(test) 80 | 81 | self.assertTrue(matches) 82 | self.assertEqual(matches.group('protocol'), 'mysql') 83 | self.assertEqual(matches.group('username'), None) 84 | self.assertEqual(matches.group('password'), None) 85 | self.assertEqual(matches.group('host'), 'localhost') 86 | self.assertEqual(matches.group('port'), None) 87 | self.assertEqual(matches.group('database'), None) 88 | 89 | def test_host_port(self): 90 | test = "mysql://localhost:3306" 91 | matches = REGEX_RFC1738.match(test) 92 | 93 | self.assertTrue(matches) 94 | self.assertEqual(matches.group('protocol'), 'mysql') 95 | self.assertEqual(matches.group('username'), None) 96 | self.assertEqual(matches.group('password'), None) 97 | self.assertEqual(matches.group('host'), 'localhost') 98 | self.assertEqual(matches.group('port'), '3306') 99 | self.assertEqual(matches.group('database'), None) 100 | 101 | def test_db(self): 102 | test = "mysql:///database" 103 | matches = REGEX_RFC1738.match(test) 104 | 105 | self.assertTrue(matches) 106 | self.assertEqual(matches.group('protocol'), 'mysql') 107 | self.assertEqual(matches.group('username'), None) 108 | self.assertEqual(matches.group('password'), None) 109 | self.assertEqual(matches.group('host'), '') 110 | self.assertEqual(matches.group('port'), None) 111 | 112 | 113 | def test_empty_user_host_port_db(self): 114 | test = "mysql://@localhost:3306/database" 115 | matches = REGEX_RFC1738.match(test) 116 | 117 | self.assertTrue(matches) 118 | self.assertEqual(matches.group('protocol'), 'mysql') 119 | self.assertEqual(matches.group('username'), '') 120 | self.assertEqual(matches.group('password'), None) 121 | self.assertEqual(matches.group('host'), 'localhost') 122 | self.assertEqual(matches.group('port'), '3306') 123 | self.assertEqual(matches.group('database'), 'database') 124 | 125 | def test_empty_user_host_port(self): 126 | test = "mysql://@localhost:3306" 127 | matches = REGEX_RFC1738.match(test) 128 | 129 | self.assertTrue(matches) 130 | self.assertEqual(matches.group('protocol'), 'mysql') 131 | self.assertEqual(matches.group('username'), '') 132 | self.assertEqual(matches.group('password'), None) 133 | self.assertEqual(matches.group('host'), 'localhost') 134 | self.assertEqual(matches.group('port'), '3306') 135 | self.assertEqual(matches.group('database'), None) 136 | 137 | def test_empty_user_host_db(self): 138 | test = "mysql://@localhost/database" 139 | matches = REGEX_RFC1738.match(test) 140 | 141 | self.assertTrue(matches) 142 | self.assertEqual(matches.group('protocol'), 'mysql') 143 | self.assertEqual(matches.group('username'), '') 144 | self.assertEqual(matches.group('password'), None) 145 | self.assertEqual(matches.group('host'), 'localhost') 146 | self.assertEqual(matches.group('port'), None) 147 | self.assertEqual(matches.group('database'), 'database') 148 | 149 | def test_empty_user_db(self): 150 | test = "mysql://@/database" 151 | matches = REGEX_RFC1738.match(test) 152 | 153 | self.assertTrue(matches) 154 | self.assertEqual(matches.group('protocol'), 'mysql') 155 | self.assertEqual(matches.group('username'), '') 156 | self.assertEqual(matches.group('password'), None) 157 | self.assertEqual(matches.group('host'), '') 158 | self.assertEqual(matches.group('port'), None) 159 | self.assertEqual(matches.group('database'), 'database') 160 | 161 | def test_user_host_port_db(self): 162 | test = "mysql://user@localhost:3306/database" 163 | matches = REGEX_RFC1738.match(test) 164 | 165 | self.assertTrue(matches) 166 | self.assertEqual(matches.group('protocol'), 'mysql') 167 | self.assertEqual(matches.group('username'), 'user') 168 | self.assertEqual(matches.group('password'), None) 169 | self.assertEqual(matches.group('host'), 'localhost') 170 | self.assertEqual(matches.group('port'), '3306') 171 | self.assertEqual(matches.group('database'), 'database') 172 | 173 | def test_user_host_db(self): 174 | test = "mysql://user@localhost/database" 175 | matches = REGEX_RFC1738.match(test) 176 | 177 | self.assertTrue(matches) 178 | self.assertEqual(matches.group('protocol'), 'mysql') 179 | self.assertEqual(matches.group('username'), 'user') 180 | self.assertEqual(matches.group('password'), None) 181 | self.assertEqual(matches.group('host'), 'localhost') 182 | self.assertEqual(matches.group('port'), None) 183 | self.assertEqual(matches.group('database'), 'database') 184 | 185 | def test_user_host_port(self): 186 | test = "mysql://user@localhost:3306" 187 | matches = REGEX_RFC1738.match(test) 188 | 189 | self.assertTrue(matches) 190 | self.assertEqual(matches.group('protocol'), 'mysql') 191 | self.assertEqual(matches.group('username'), 'user') 192 | self.assertEqual(matches.group('password'), None) 193 | self.assertEqual(matches.group('host'), 'localhost') 194 | self.assertEqual(matches.group('port'), '3306') 195 | self.assertEqual(matches.group('database'), None) 196 | 197 | def test_user_db(self): 198 | test = "mysql://user@/database" 199 | matches = REGEX_RFC1738.match(test) 200 | 201 | self.assertTrue(matches) 202 | self.assertEqual(matches.group('protocol'), 'mysql') 203 | self.assertEqual(matches.group('username'), 'user') 204 | self.assertEqual(matches.group('password'), None) 205 | self.assertEqual(matches.group('host'), '') 206 | self.assertEqual(matches.group('port'), None) 207 | 208 | def test_user_pw(self): 209 | test = "mysql://user:password@" 210 | matches = REGEX_RFC1738.match(test) 211 | 212 | self.assertTrue(matches) 213 | self.assertEqual(matches.group('protocol'), 'mysql') 214 | self.assertEqual(matches.group('username'), 'user') 215 | self.assertEqual(matches.group('password'), 'password') 216 | self.assertEqual(matches.group('host'), '') 217 | self.assertEqual(matches.group('port'), None) 218 | 219 | def test_build_url_host(self): 220 | url = build_database_url('host') 221 | self.assertEqual(url, 'mysql://root@host:3306/') 222 | 223 | def test_build_url_database(self): 224 | url = build_database_url(host='host', password='pwd', database='test') 225 | self.assertEqual(url, 'mysql://root:pwd@host:3306/test') 226 | 227 | def test_build_url_missed_host(self): 228 | self.assertRaises(TypeError, build_database_url) -------------------------------------------------------------------------------- /tests/test_database.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import unittest 4 | import schemaobject 5 | 6 | 7 | class TestDatabaseSchema(unittest.TestCase): 8 | 9 | def setUp(self): 10 | self.database_url = "mysql://root:root@localhost:3306/" 11 | self.db = schemaobject.SchemaObject(self.database_url + 'sakila', charset='utf8') 12 | self.db = self.db.selected 13 | 14 | def test_database_name(self): 15 | self.assertEqual("sakila", self.db.name) 16 | 17 | def test_database_option_charset(self): 18 | self.assertEqual("utf8", self.db.options['charset'].value) 19 | 20 | def test_database_option_collation(self): 21 | self.assertEqual("utf8_general_ci", self.db.options['collation'].value) 22 | 23 | def test_database_alter(self): 24 | self.assertEqual("ALTER DATABASE `sakila`", self.db.alter()) 25 | 26 | def test_database_create(self): 27 | self.assertEqual("CREATE DATABASE `sakila` CHARACTER SET=utf8 COLLATE=utf8_general_ci;", 28 | self.db.create()) 29 | 30 | def test_database_drop(self): 31 | self.assertEqual("DROP DATABASE `sakila`;", self.db.drop()) 32 | 33 | def test_databse_select(self): 34 | self.assertEqual("USE `sakila`;", self.db.select()) 35 | 36 | def test_databses_eq(self): 37 | self.assertEqual(self.db, self.db) 38 | 39 | def test_databases_neq(self): 40 | self.assertNotEqual(self.db, None) -------------------------------------------------------------------------------- /tests/test_foreignkey.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import re 3 | import unittest 4 | import schemaobject 5 | 6 | 7 | class TestForeignKeySchema(unittest.TestCase): 8 | 9 | def setUp(self): 10 | self.database_url = "mysql://root:root@localhost:3306/" 11 | self.schema = schemaobject.SchemaObject(self.database_url + 'sakila', charset='utf8') 12 | self.fk = self.schema.selected.tables['rental'].foreign_keys 13 | 14 | def test_fk_exists(self): 15 | self.assertTrue("fk_rental_customer" in list(self.fk.keys())) 16 | 17 | def test_fk_not_exists(self): 18 | self.assertFalse("fk_foobar" in list(self.fk.keys())) 19 | 20 | def test_fk_name(self): 21 | self.assertEqual("fk_rental_customer", self.fk['fk_rental_customer'].name) 22 | 23 | def test_fk_symbol(self): 24 | self.assertEqual("fk_rental_customer", self.fk['fk_rental_customer'].symbol) 25 | 26 | def test_fk_table_name(self): 27 | self.assertEqual("rental", self.fk['fk_rental_customer'].table_name) 28 | 29 | def test_fk_table_schema(self): 30 | self.assertEqual("sakila", self.fk['fk_rental_customer'].table_schema) 31 | 32 | def test_fk_columns(self): 33 | self.assertEqual(['customer_id'], self.fk['fk_rental_customer'].columns) 34 | 35 | def test_fk_referenced_table_name(self): 36 | self.assertEqual("customer", self.fk['fk_rental_customer'].referenced_table_name) 37 | 38 | def test_fk_referenced_table_schema(self): 39 | self.assertEqual("sakila", self.fk['fk_rental_customer'].referenced_table_schema) 40 | 41 | def test_fk_referenced_columns(self): 42 | self.assertEqual(['customer_id'], self.fk['fk_rental_customer'].referenced_columns) 43 | 44 | def test_fk_match_option(self): 45 | self.assertEqual(None, self.fk['fk_rental_customer'].match_option) 46 | 47 | def test_fk_update_rule(self): 48 | self.assertEqual("CASCADE", self.fk['fk_rental_customer'].update_rule) 49 | 50 | def test_fk_delete_rule(self): 51 | self.assertEqual("RESTRICT", self.fk['fk_rental_customer'].delete_rule) 52 | 53 | def test_format_referenced_col_with_length(self): 54 | self.assertEqual('`fk_rental_customer`(11)', schemaobject.foreignkey.ForeignKeySchema._format_referenced_col('fk_rental_customer', 11)) 55 | 56 | def test_format_referenced_col_without_length(self): 57 | self.assertEqual('`fk_rental_customer`', schemaobject.foreignkey.ForeignKeySchema._format_referenced_col('fk_rental_customer', 0)) 58 | self.assertEqual('`fk_rental_customer`', schemaobject.foreignkey.ForeignKeySchema._format_referenced_col('fk_rental_customer', None)) 59 | 60 | def test_fk_drop(self): 61 | self.assertEqual(self.fk['fk_rental_customer'].drop(), "DROP FOREIGN KEY `fk_rental_customer`") 62 | 63 | def test_fk_create(self): 64 | self.assertEqual(self.fk['fk_rental_customer'].create(), 65 | "ADD CONSTRAINT `fk_rental_customer` FOREIGN KEY `fk_rental_customer` (`customer_id`) REFERENCES `customer` (`customer_id`) ON DELETE RESTRICT ON UPDATE CASCADE") 66 | 67 | def test_fk_eq(self): 68 | self.assertEqual(self.fk['fk_rental_customer'], self.fk['fk_rental_customer']) 69 | 70 | def test_fk_neq(self): 71 | self.assertNotEqual(self.fk['fk_rental_customer'], self.fk['fk_rental_inventory']) 72 | 73 | # def test_fk_reference_opts_update_and_delete(self): 74 | # table_def = """CREATE TABLE `child` ( 75 | # `id` int(11) DEFAULT NULL, 76 | # `parent_id` int(11) DEFAULT NULL, 77 | # KEY `par_ind` (`parent_id`), 78 | # CONSTRAINT `child_ibfk_1` FOREIGN KEY (`parent_id`) 79 | # REFERENCES `parent` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, 80 | # CONSTRAINT `child_ibfk_2` FOREIGN KEY (`parent_id`) 81 | # REFERENCES `parent` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT ) 82 | # ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_danish_ci COMMENT='hello world';""" 83 | # 84 | # matches = re.search(REGEX_FK_REFERENCE_OPTIONS % 'child_ibfk_1', table_def, re.X) 85 | # self.assertTrue(matches) 86 | # self.assertTrue(matches.group('on_delete')) 87 | # self.assertTrue(matches.group('on_update')) 88 | # self.assertEqual(matches.group('on_delete'), 'SET NULL') 89 | # self.assertEqual(matches.group('on_update'), 'CASCADE') 90 | # 91 | # matches = re.search(REGEX_FK_REFERENCE_OPTIONS % 'child_ibfk_1', table_def, re.X) 92 | # self.assertTrue(matches) 93 | # self.assertTrue(matches.group('on_delete')) 94 | # self.assertTrue(matches.group('on_update')) 95 | # self.assertEqual(matches.group('on_delete'), 'RESTRICT') 96 | # self.assertEqual(matches.group('on_update'), 'RESTRICT') 97 | # 98 | # def test_fk_reference_opts_delete(self): 99 | # table_def = """CREATE TABLE `child` ( 100 | # `id` int(11) DEFAULT NULL, 101 | # `parent_id` int(11) DEFAULT NULL, 102 | # KEY `par_ind` (`parent_id`), 103 | # CONSTRAINT `child_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `parent` (`id`) ON DELETE SET NULL ) 104 | # ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_danish_ci COMMENT='hello world';""" 105 | # 106 | # matches = re.search(REGEX_FK_REFERENCE_OPTIONS % 'child_ibfk_1', table_def, re.X) 107 | # self.assertTrue(matches) 108 | # self.assertTrue(matches.group('on_delete')) 109 | # self.assertTrue(not matches.group('on_update')) 110 | # self.assertEqual(matches.group('on_delete'), 'SET NULL') 111 | # 112 | # def test_fk_reference_opts_update(self): 113 | # table_def = """CREATE TABLE `child` ( 114 | # `id` int(11) DEFAULT NULL, 115 | # `parent_id` int(11) DEFAULT NULL, 116 | # KEY `par_ind` (`parent_id`), 117 | # CONSTRAINT `child_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `parent` (`id`) ON UPDATE CASCADE ) 118 | # ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_danish_ci COMMENT='hello world';""" 119 | # 120 | # matches = re.search(REGEX_FK_REFERENCE_OPTIONS % 'child_ibfk_1', table_def, re.X) 121 | # self.assertTrue(matches) 122 | # self.assertTrue(not matches.group('on_delete')) 123 | # self.assertTrue(matches.group('on_update')) 124 | # self.assertEqual(matches.group('on_update'), 'CASCADE') -------------------------------------------------------------------------------- /tests/test_index.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import unittest 3 | import schemaobject 4 | 5 | 6 | class TestIndexSchema(unittest.TestCase): 7 | 8 | def setUp(self): 9 | self.database_url = "mysql://root:root@localhost:3306/" 10 | self.db = schemaobject.SchemaObject(self.database_url + 'sakila', charset='utf8') 11 | self.db = self.db.selected 12 | 13 | def test_format_sub_part_with_length(self): 14 | self.assertEqual('`name`(16)', schemaobject.index.IndexSchema.format_sub_part('name', 16)) 15 | 16 | def test_format_sub_part_without_length(self): 17 | self.assertEqual('`name`', schemaobject.index.IndexSchema.format_sub_part('name', 0)) 18 | self.assertEqual('`name`', schemaobject.index.IndexSchema.format_sub_part('name', None)) 19 | 20 | def test_index_exists(self): 21 | assert "idx_fk_address_id" in self.db.tables['customer'].indexes 22 | 23 | def test_index_name(self): 24 | self.assertEqual("idx_fk_address_id", self.db.tables['customer'].indexes['idx_fk_address_id'].name) 25 | 26 | def test_index_table_name(self): 27 | self.assertEqual("customer", self.db.tables['customer'].indexes['idx_fk_address_id'].table_name) 28 | 29 | def test_index_kind(self): 30 | self.assertEqual("BTREE", self.db.tables['customer'].indexes['idx_fk_address_id'].type) 31 | 32 | def test_index_type_primary_key(self): 33 | self.assertEqual("PRIMARY", self.db.tables['customer'].indexes['PRIMARY'].kind) 34 | 35 | def test_index_(self): 36 | self.assertEqual("INDEX", self.db.tables['rental'].indexes['idx_fk_customer_id'].kind) 37 | 38 | def test_index_non_unique(self): 39 | self.assertEqual(True, self.db.tables['rental'].indexes['idx_fk_inventory_id'].non_unique) 40 | 41 | def test_index_unique(self): 42 | self.assertEqual(False, self.db.tables['rental'].indexes['rental_date'].non_unique) 43 | self.assertEqual("UNIQUE", self.db.tables['rental'].indexes['rental_date'].kind) 44 | 45 | def test_index_comment(self): 46 | self.assertEqual("", self.db.tables['customer'].indexes['idx_fk_address_id'].comment) 47 | 48 | def test_index_collation(self): 49 | self.assertEqual("A", self.db.tables['customer'].indexes['idx_fk_address_id'].collation) 50 | 51 | def test_index_fields_correct_order(self): 52 | self.assertEqual(self.db.tables['rental'].indexes['rental_date'].fields, 53 | [('rental_date', 0), ('inventory_id', 0), ('customer_id', 0)]) 54 | 55 | def test_index_fields_incorrect_order(self): 56 | self.assertNotEqual(self.db.tables['rental'].indexes['rental_date'].fields, 57 | [('inventory_id', 0), ('rental_date', 0), ('customer_id', 0)] ) 58 | 59 | def test_index_eq(self): 60 | self.assertEqual(self.db.tables['rental'].indexes['rental_date'], 61 | self.db.tables['rental'].indexes['rental_date']) 62 | 63 | def test_index_neq(self): 64 | self.assertNotEqual(self.db.tables['rental'].indexes['rental_date'], 65 | self.db.tables['customer'].indexes['idx_fk_address_id']) 66 | 67 | def test_drop_index(self): 68 | self.assertEqual(self.db.tables['rental'].indexes['rental_date'].drop(False), 69 | "DROP INDEX `rental_date` ON `rental`") 70 | 71 | def test_drop_index_alter(self): 72 | self.assertEqual(self.db.tables['rental'].indexes['rental_date'].drop(), 73 | "DROP INDEX `rental_date`") 74 | 75 | def test_drop_primary_key(self): 76 | self.assertEqual(self.db.tables['customer'].indexes['PRIMARY'].drop(), 77 | "DROP PRIMARY KEY") 78 | 79 | def test_add_primary_key(self): 80 | self.assertEqual(self.db.tables['customer'].indexes['PRIMARY'].create(), 81 | "ADD PRIMARY KEY (`customer_id`) USING BTREE") 82 | 83 | def test_add_unique_index(self): 84 | self.assertEqual(self.db.tables['rental'].indexes['rental_date'].create(), 85 | "ADD UNIQUE INDEX `rental_date` (`rental_date`, `inventory_id`, `customer_id`) USING BTREE") 86 | 87 | def test_add_fulltext_index(self): 88 | self.assertEqual(self.db.tables['film_text'].indexes['idx_title_description'].create(), 89 | "ADD FULLTEXT INDEX `idx_title_description` (`title`, `description`)") 90 | 91 | def test_add_index_using_BTREE(self): 92 | self.assertEqual(self.db.tables['payment'].indexes['idx_fk_staff_id'].create(), 93 | "ADD INDEX `idx_fk_staff_id` (`staff_id`) USING BTREE") 94 | 95 | @unittest.skip 96 | def test_add_index_using_HASH(self): 97 | assert False, "Add index using HASH to test DB" 98 | 99 | @unittest.skip 100 | def test_add_index_using_RTREE(self): 101 | assert False, "Add index using RTREE to test DB" 102 | 103 | @unittest.skip 104 | def test_add_spatial_index(self): 105 | assert False, "Add spatial index to test DB" 106 | 107 | @unittest.skip 108 | def test_add_index_with_subpart(self): 109 | assert False, "Add subparts to indicies in test DB" -------------------------------------------------------------------------------- /tests/test_option.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import unittest 3 | from schemaobject.option import SchemaOption 4 | 5 | class TestOptionSchema(unittest.TestCase): 6 | 7 | def test_set_value(self): 8 | opt = SchemaOption('key') 9 | assert opt.value == None 10 | opt.value = "value" 11 | assert opt.value == "value" 12 | 13 | def test_get_value(self): 14 | opt = SchemaOption('key', 'value') 15 | assert opt.value == "value" 16 | 17 | def test_create_no_name(self): 18 | opt = SchemaOption(None, 'value') 19 | assert opt.create() == 'value' 20 | 21 | def test_create_no_value(self): 22 | opt = SchemaOption('key') 23 | assert opt.create() == '' 24 | 25 | def test_create_value_non_string(self): 26 | opt = SchemaOption('key', 1234) 27 | assert opt.create() == 'key=1234' 28 | 29 | def test_create_value_string_with_spaces(self): 30 | opt = SchemaOption('key', 'my value') 31 | assert opt.create() == "key='my value'" 32 | 33 | def test_create_value_string_without_spaces(self): 34 | opt = SchemaOption('key', 'value') 35 | assert opt.create() == 'key=value' 36 | 37 | def test_eq(self): 38 | opt = SchemaOption('key', 'value') 39 | opt2 = SchemaOption('key', 'value') 40 | assert opt == opt2 41 | 42 | def test_neq(self): 43 | opt = SchemaOption('key', 'value1') 44 | opt2 = SchemaOption('key', 'value2') 45 | assert opt != opt2 -------------------------------------------------------------------------------- /tests/test_schema.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import unittest 3 | import schemaobject 4 | 5 | class TestSchema(unittest.TestCase): 6 | 7 | def setUp(self): 8 | self.database_url = "mysql://root:root@localhost:3306/" 9 | self.db = schemaobject.SchemaObject(self.database_url + 'sakila', charset='utf8') 10 | self.db2 = schemaobject.SchemaObject(self.database_url, charset='utf8') 11 | 12 | def test_database_version(self): 13 | assert self.db.version == "5.6.21" 14 | 15 | def test_port(self): 16 | assert self.db.port == 3306 17 | 18 | def test_host(self): 19 | assert self.db.host == "localhost" 20 | 21 | def test_user(self): 22 | assert self.db.user == "root" 23 | 24 | def test_selected_databse(self): 25 | assert self.db.selected.name == "sakila" 26 | 27 | def test_no_selected_databse(self): 28 | assert self.db2.selected == None 29 | 30 | def test_database_count_with_selected_databse(self): 31 | assert len(self.db.databases) == 1 -------------------------------------------------------------------------------- /tests/test_table.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import unittest 4 | import schemaobject 5 | 6 | class TestTableSchema(unittest.TestCase): 7 | 8 | def setUp(self): 9 | self.database_url = "mysql://root:root@localhost:3306/" 10 | self.db = schemaobject.SchemaObject(self.database_url + 'sakila', charset='utf8') 11 | self.db = self.db.selected 12 | 13 | def test_table_count(self): 14 | self.assertEqual(16, len(self.db.tables)) 15 | 16 | def test_tables(self): 17 | self.assertEqual(list(self.db.tables.keys()), ['actor','address','category','city','country','customer', 18 | 'film','film_actor','film_category','film_text','inventory', 19 | 'language','payment','rental','staff','store']) 20 | 21 | def test_table_name(self): 22 | self.assertEqual("address", self.db.tables['address'].name) 23 | 24 | def test_table_option_engine(self): 25 | self.assertEqual("InnoDB", self.db.tables['address'].options['engine'].value) 26 | 27 | def test_table_option_charset(self): 28 | self.assertEqual("utf8", self.db.tables['address'].options['charset'].value) 29 | 30 | def test_table_option_collation(self): 31 | self.assertEqual("utf8_general_ci", self.db.tables['address'].options['collation'].value) 32 | 33 | def test_table_option_row_format(self): 34 | self.assertEqual("Compact", self.db.tables['address'].options['row_format'].value) 35 | 36 | def test_table__option_create_options(self): 37 | self.assertEqual("", self.db.tables['actor'].options['create_options'].value) 38 | 39 | def test_table_option_auto_increment(self): 40 | self.assertEqual(1, self.db.tables['actor'].options['auto_increment'].value) 41 | 42 | def test_table_option_comment(self): 43 | self.assertEqual("", self.db.tables['address'].options['comment'].value) 44 | 45 | def test_table_column_count(self): 46 | self.assertEqual(8, len(self.db.tables['address'].columns)) 47 | 48 | def test_table_index_count(self): 49 | self.assertEqual(2, len(self.db.tables['address'].indexes)) 50 | 51 | def test_table_fk_count(self): 52 | self.assertEqual(1, len(self.db.tables['address'].foreign_keys)) 53 | 54 | def test_tables_eq(self): 55 | self.assertEqual(self.db.tables['address'], self.db.tables['address']) 56 | 57 | def test_tables_neq(self): 58 | self.assertNotEqual(self.db.tables['address'], self.db.tables['actor']) 59 | 60 | def test_table_alter(self): 61 | self.assertEqual("ALTER TABLE `address`", self.db.tables['address'].alter()) 62 | 63 | def test_table_create(self): 64 | stub = 'CREATE TABLE `actor` ( `actor_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT, `first_name` varchar(45) NOT NULL, `last_name` varchar(45) NOT NULL, `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`actor_id`), KEY `idx_actor_last_name` (`last_name`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;' 65 | self.assertEqual(stub, self.db.tables['actor'].create()) 66 | 67 | def test_table_drop(self): 68 | self.assertEqual("DROP TABLE `actor`;", self.db.tables['actor'].drop()) --------------------------------------------------------------------------------