diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..39dd586 --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,146 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest + +all: doctest + $(MAKE) html coverage + +commit: + mkdir -p _gh-pages + (cd _gh-pages && git init || true) + rm -rf _build/html/.git + cp -r _gh-pages/.git _build/html + (cd _build/html && git add . && git commit -m "sphinx build $$(date --rfc-3339=seconds)" || true) + (cd _gh-pages && git pull ../_build/html) + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + touch $(BUILDDIR)/html/.nojekyll + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pokedex.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pokedex.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/pokedex" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pokedex" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + make -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + cat $(BUILDDIR)/coverage/*.txt diff --git a/doc/conf.py b/doc/conf.py new file mode 100644 index 0000000..5fcb53a --- /dev/null +++ b/doc/conf.py @@ -0,0 +1,230 @@ +# -*- coding: utf-8 -*- +# +# pokedex documentation build configuration file, created by +# sphinx-quickstart on Tue Apr 12 17:43:05 2011. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +reload(sys) +sys.setdefaultencoding("UTF-8") + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.doctest', + 'sphinx.ext.todo', + 'sphinx.ext.pngmath', + 'sphinx.ext.intersphinx', + #'sphinx.ext.viewcode', + 'sphinx.ext.coverage', + 'pokedex.doc.tabledoc', + ] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +source_encoding = 'utf-8' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'pokedex' +copyright = u'2011, Alex Munroe (Eevee)' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.1' +# The full version, including alpha/beta/rc tags. +release = '0.1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +intersphinx_mapping = {'sqlalchemy': ('http://www.sqlalchemy.org/docs', None)} + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +html_show_copyright = False + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'pokedexdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'pokedex.tex', u'Pokedex Documentation', + u'veekun', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'pokedex', u'Pokedex Documentation', + [u'veekun'], 1) +] diff --git a/doc/index.rst b/doc/index.rst new file mode 100644 index 0000000..a525f6f --- /dev/null +++ b/doc/index.rst @@ -0,0 +1,25 @@ +.. pokedex documentation master file, created by + sphinx-quickstart on Tue Apr 12 17:43:05 2011. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +The pokedex documentation +========================= + +Jump right in! + +Contents: + +.. toctree:: + :maxdepth: 2 + + installing + usage + schema + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`search` + diff --git a/doc/installing.rst b/doc/installing.rst new file mode 100644 index 0000000..44b94a9 --- /dev/null +++ b/doc/installing.rst @@ -0,0 +1,156 @@ +Installing the pokedex library +============================== + +Quick startup with Ubuntu/Debian-like systems +--------------------------------------------- + +Run the following from an empty directory:: + + $ sudo apt-get install git python python-pip python-sqlalchemy + $ git clone git://github.com/veekun/pokedex.git + $ pip install -E env -e pokedex + $ source env/bin/activate + (env)$ pokedex setup -v + (env)$ pokedex lookup eevee + +If it all goes smoothly, you can now use ``env/bin/pokedex``, the command-line +tool, and ``env/bin/python``, a Python interpreter configured to use the +pokedex library. + +That is all you need. Feel free to skip the rest of this chapter if you're not +interested in the details. + +Prerequisites +------------- + +Linux +^^^^^ + +Ubuntu/Debian users should run the following:: + + $ sudo apt-get install git python python-pip + +With other Linuxes, install the packages for git, python (2.6 or 2.7, +*not* 3.x), and python-pip. + +If you succeeded, skip the Detailed instructions. + +Detailed instructions +^^^^^^^^^^^^^^^^^^^^^ + +You should know what a command line is and how to work with it. +The here we assume you're using Linux [#]_, if that's not the case, make +sure you have enough computer knowledge to translate the instructions to your +operating system. + +Pokedex is distributed via Git_. So, get Git. + +You will also need Python_ 2; the language pokedex is written in. Be sure to get +version **2.6** or **2.7**. Pokedex does not work with Python 3.x yet, and it +most likely won't work with 2.5 or earlier. + +Next, get pip_, a tool to install Python packages. Experts can use another +tool, of course. + +Make sure git and pip are on your path. + +Optionally you can install SQLAlchemy_, `Python markdown`_, Whoosh_, +or construct_. If you don't, pip will atuomatically download and install a copy +for you, but some are pretty big so you might want to install it system-wide. +(Unfortunately, many distros have outdated versions of these libraries, so pip +will install pokedex's own copy anyway.) + +Getting and installing pokedex +------------------------------ + +Run the following from an empty directory:: + + $ git clone git://git.veekun.com/pokedex.git + $ pip install -E env -e pokedex + +This will give you two directories: pokedex (containing the source code and +data), and env (a virtualenv_). + +In env/bin, there are three interesting files: + +* pokedex: The pokedex program +* python: A copy of Python that knows about pokedex and its prerequisites. +* activate: Typing ``source env/bin/activate`` in a shell will put + pokedex and our bin/python on the $PATH, and generally set things up to work + with them. Your prompt will change to let you know of this. You can end such + a session by typing ``deactivate``. + +This documentation will assume that you've activated the virtualenv, so +``pokedex`` means ``env/bin/pokedex``. + +Advanced +^^^^^^^^ + +You can of course install into an existing virtualenv, by either using its pip +and leaving out the ``-E env``, or running the setup script directly:: + + (anotherenv)$ cd pokedex + (anotherenv)pokedex$ python setup.py develop + +It is also possible to install pokedex system-wide. There are problems with +that. Don't do it. The only time you need ``sudo`` is for getting the +prerequisites. + +Loading the database +-------------------- + +Before you can do anything useful with pokedex, you need to load the database:: + + $ pokedex setup -v + +This will load the data into a default SQLite database and create a default +Whoosh index. + +Advanced +^^^^^^^^ + +If you want to use another database, make sure you have the corresponding +`SQLAlchemy engine`_ for it and either use the ``-e`` switch, (e.g. +``-e postgresql://@/pokedex``), or set the ``POKEDEX_DB_ENGINE`` environment +variable. + +To use another lookup index directory, specify it with ``-i`` or the +``POKEDEX_INDEX_DIR`` variable. + +Make sure you always use the same options whenever you use pokedex. + +If you're confused about what pokedex thinks its settings are, check +``pokedex status``. + +See ``pokedex help`` for even more options. + +All done +-------- + +To verify that all went smoothly, check that the pokedex tool finds your +favorite pokémon:: + + $ pokedex lookup eevee + +Yes, that was a bit anti-climatic. The command-line tool doesn't do much, +currently. + + + + + + +.. _Git: http://git-scm.com/ +.. _Python: http://www.python.org/ +.. _pip: http://pypi.python.org/pypi/pip +.. _SQLAlchemy: www.sqlalchemy.org/ +.. _`Python markdown`: http://www.freewisdom.org/projects/python-markdown/ +.. _Whoosh: http://whoosh.ca/ +.. _construct: pypi.python.org/pypi/construct +.. _virtualenv: http://www.virtualenv.org/en/latest/ +.. _`SQLAlchemy engine`: http://www.sqlalchemy.org/docs/core/engines.html + +.. rubric:: Footnotes +.. [#] If you write instructions for another OS, well be happy to include them + here. The reason your OS is not listed here is because the author doesn't + use it, so naturally he can't write instructions for it. diff --git a/doc/main-tables.rst b/doc/main-tables.rst new file mode 100644 index 0000000..bd1d3be --- /dev/null +++ b/doc/main-tables.rst @@ -0,0 +1,179 @@ +The pokédex tables +================== + +.. module:: pokedex.db.tables + +The :mod:`pokedex.db.tables` module defines all of the tables in the Pokédex. +They are all defined with SQLAlchemy's +:mod:`~sqlalchemy.ext.declarative` extension. + +To introspect the tables programmatically, you can use the following: + +.. data:: mapped_classes + + A list of all the classes you see below. + +.. data:: metadata + + The SQLAlchemy :class:`~sqlalchemy.schema.MetaData` containing all the + tables. + +Each of the classes has a ``translation_classes`` attribute: a potentially +empty list of translation classes. See :mod:`pokedex.db.multilang` for how +these work. + +Many tables have these columns: + +- **id**: An integer primary key. Sometimes it's semantically meaningful, most + often it isn't. +- **identifier**: A string identifier of the class, and the preferred way to + access individual items. +- **name**: A name (uses the multilang functionality) + +Pokémon +------- + +.. dex-table:: PokemonSpecies +.. dex-table:: Pokemon +.. dex-table:: PokemonForm +.. dex-table:: EvolutionChain +.. dex-table:: PokemonEvolution + +Moves +----- + +.. dex-table:: Move +.. dex-table:: MoveEffect +.. dex-table:: MoveMeta + +Items +----- + +.. dex-table:: Item +.. dex-table:: Berry + +Types +----- + +.. dex-table:: Type + +Abilities +--------- + +.. dex-table:: Ability + +Language +-------- + +.. dex-table:: Language + +Version stuff +------------- + +.. dex-table:: Generation +.. dex-table:: VersionGroup +.. dex-table:: Version +.. dex-table:: Pokedex +.. dex-table:: Region + +Encounters +---------- + +.. dex-table:: Location +.. dex-table:: LocationArea +.. dex-table:: LocationAreaEncounterRate +.. dex-table:: Encounter +.. dex-table:: EncounterCondition +.. dex-table:: EncounterConditionValue +.. dex-table:: EncounterMethod +.. dex-table:: EncounterSlot + + +Contests +-------- + +.. dex-table:: ContestCombo +.. dex-table:: ContestEffect +.. dex-table:: SuperContestCombo +.. dex-table:: SuperContestEffect + +Enum tables +----------- + +.. dex-table:: BerryFirmness +.. dex-table:: ContestType +.. dex-table:: EggGroup +.. dex-table:: EvolutionTrigger +.. dex-table:: GrowthRate +.. dex-table:: ItemCategory +.. dex-table:: ItemFlingEffect +.. dex-table:: ItemPocket +.. dex-table:: MoveBattleStyle +.. dex-table:: MoveDamageClass +.. dex-table:: MoveMetaAilment +.. dex-table:: MoveMetaCategory +.. dex-table:: MoveTarget +.. dex-table:: Nature +.. dex-table:: PalParkArea +.. dex-table:: PokemonColor +.. dex-table:: PokemonMoveMethod +.. dex-table:: PokemonShape +.. dex-table:: Stat + +Changelogs +---------- + +.. dex-table:: AbilityChangelog +.. dex-table:: MoveEffectChangelog +.. dex-table:: MoveChangelog + +Flavor text +----------- + +.. dex-table:: ItemFlavorText +.. dex-table:: AbilityFlavorText +.. dex-table:: MoveFlavorText +.. dex-table:: PokemonSpeciesFlavorText + +Association tables +------------------ + +.. dex-table:: BerryFlavor +.. dex-table:: EncounterConditionValueMap +.. dex-table:: ItemFlag +.. dex-table:: ItemFlagMap +.. dex-table:: Machine +.. dex-table:: MoveFlag +.. dex-table:: MoveFlagMap +.. dex-table:: MoveMetaStatChange +.. dex-table:: NatureBattleStylePreference +.. dex-table:: NaturePokeathlonStat +.. dex-table:: PokeathlonStat +.. dex-table:: PokemonAbility +.. dex-table:: PokemonEggGroup +.. dex-table:: PokemonFormPokeathlonStat +.. dex-table:: PokemonHabitat +.. dex-table:: PokemonMove +.. dex-table:: PokemonStat +.. dex-table:: PokemonItem +.. dex-table:: PokemonType +.. dex-table:: TypeEfficacy +.. dex-table:: VersionGroupPokemonMoveMethod +.. dex-table:: VersionGroupRegion + +Index maps +---------- + +.. dex-table:: ItemGameIndex +.. dex-table:: LocationGameIndex +.. dex-table:: PokemonDexNumber +.. dex-table:: PokemonFormGeneration +.. dex-table:: PokemonGameIndex + +Mics tables +----------- + +.. dex-table:: Experience +.. dex-table:: PalPark +.. dex-table:: StatHint + diff --git a/doc/schema.rst b/doc/schema.rst new file mode 100644 index 0000000..48db3d7 --- /dev/null +++ b/doc/schema.rst @@ -0,0 +1,6 @@ +The database schema +=================== + +.. toctree:: + + main-tables diff --git a/doc/usage.rst b/doc/usage.rst new file mode 100644 index 0000000..b4a6ef5 --- /dev/null +++ b/doc/usage.rst @@ -0,0 +1,193 @@ +Using pokedex +============= + +The pokédex is, first and foremost, a Python library. To get the most of it, +you'll need to learn `Python`_ and `SQLAlchemy`_. + +Here is a small example of using pokedex: + +.. testcode:: + + from pokedex.db import connect, tables, util + session = connect() + pokemon = util.get(session, tables.PokemonSpecies, 'bulbasaur') + print u'{0.name}, the {0.genus} Pokemon'.format(pokemon) + +Running this will give you some Bulbasaur info: + +.. testoutput:: + + Bulbasaur, the Seed Pokemon + +Connecting +---------- + +To get information out of the Pokédex, you will need to create a +:class:`Session `. To do that, use +:func:`pokedex.db.connect`. For simple uses, you don't need to give it any +arguments: it the database that ``pokedex load`` fills up by default. If you +need to select another database, give its URI as the first argument. + +The object :func:`~pokedex.db.connect` gives you is actually a +:class:`SQLAlchemy session `, giving you the +full power of SQLAlchemy for working with the data. We'll cover some basics +here, but if you intend to do some serious work, do read SQLAlchemy's docs. + +Pokédex tables +-------------- + +Data in the pokédex is organized in tables, defined in +:mod:`pokedex.db.tables`. +There is quite a few or them. To get you started, here are a few common ones: + +* :class:`~pokedex.db.tables.PokemonSpecies` +* :class:`~pokedex.db.tables.Move` +* :class:`~pokedex.db.tables.Item` +* :class:`~pokedex.db.tables.Type` + +Getting things +-------------- + +If you know what you want from the pokédex, you can use the +:func:`pokedex.db.util.get` function. It looks up a thing in a table, based on +its identifier, name, or ID, and returns it. + +.. testcode:: + + def print_pokemon(pokemon): + print u'{0.name}, the {0.genus} Pokemon'.format(pokemon) + + print_pokemon(util.get(session, tables.PokemonSpecies, identifier='eevee')) + print_pokemon(util.get(session, tables.PokemonSpecies, name=u'Ho-Oh')) + print_pokemon(util.get(session, tables.PokemonSpecies, id=50)) + + def print_item(item): + print u'{0.name}: ${0.cost}'.format(item) + + print_item(util.get(session, tables.Item, identifier='great-ball')) + print_item(util.get(session, tables.Item, name='Potion')) + print_item(util.get(session, tables.Item, id=30)) + +.. testoutput:: + + Eevee, the Evolution Pokemon + Ho-Oh, the Rainbow Pokemon + Diglett, the Mole Pokemon + Great Ball: $600 + Potion: $300 + Fresh Water: $200 + +Querying +-------- + +So, how do you get data from the session? You use the session's +:meth:`~sqlalchemy.orm.session.Session.query` method, and give it a pokédex +Table as an argument. This will give you a :class:`SQLAlchemy query +`. + +Ordering +^^^^^^^^ + +As always with SQL, you should not rely on query results being in some +particular order – unless you have ordered the query first. This means that +you'll want to sort just about every query you will make. + +For example, you can get a list of all pokémon species, sorted by their +:attr:`~pokedex.db.tables.PokemonSpecies.id`, like so: + +.. testcode:: + + for pokemon in session.query(tables.PokemonSpecies).order_by(tables.PokemonSpecies.id): + print pokemon.name + +.. testoutput:: + + Bulbasaur + Ivysaur + Venusaur + Charmander + Charmeleon + ... + Keldeo + Meloetta + Genesect + +Or to order by :attr:`~pokedex.db.tables.PokemonSpecies.name`: + +.. testcode:: + + for pokemon in session.query(tables.PokemonSpecies).order_by(tables.PokemonSpecies.name): + print pokemon.name + +.. testoutput:: + + Abomasnow + ... + Zweilous + + +Filtering +^^^^^^^^^ + +Another major operation on queries is filtering, using the query's +:meth:`~sqlalchemy.orm.query.Query.filter` or +:meth:`~sqlalchemy.orm.query.Query.filter_by` methods: + +.. testcode:: + + for move in session.query(tables.Move).filter(tables.Move.power > 200): + print move.name + +.. testoutput:: + + Explosion + +Joining +^^^^^^^ + +The final operation we'll cover here is joining other tables to the query, +using the query's :meth:`~sqlalchemy.orm.query.Query.join`. +You will usually want to join on a relationship, such as in the following +example: + +.. testcode:: + + query = session.query(tables.Move) + query = query.join(tables.Move.type) + query = query.filter(tables.Type.identifier == 'grass') + query = query.filter(tables.Move.power >= 100) + query = query.order_by(tables.Move.power) + query = query.order_by(tables.Move.name) + + print 'The most powerful Grass-type moves:' + for move in query: + print u'{0.name} ({0.power})'.format(move) + +.. testoutput:: + + The most powerful Grass-type moves: + Petal Dance (120) + Power Whip (120) + Seed Flare (120) + SolarBeam (120) + Wood Hammer (120) + Leaf Storm (140) + Frenzy Plant (150) + +That concludes our brief tutorial. +If you need to do more, consult the `SQLAlchemy documentation`_. + +API documentation +----------------- + +.. autofunction:: pokedex.db.connect + + See :class:`sqlalchemy.orm.session.Session` for more documentation on the + returned object. + +.. autofunction:: pokedex.db.util.get + + +.. _Python: http://www.python.org +.. _SQLAlchemy: http://www.sqlalchemy.org +.. _`SQLAlchemy documentation`: http://www.sqlalchemy.org/docs/orm/tutorial.html diff --git a/pokedex/db/multilang.py b/pokedex/db/multilang.py index dffe6a4..7609f7f 100644 --- a/pokedex/db/multilang.py +++ b/pokedex/db/multilang.py @@ -118,6 +118,7 @@ def create_translation_table(_table_name, foreign_class, relation_name, Translations = type(_table_name, (object,), { '_language_identifier': association_proxy('local_language', 'identifier'), + 'relation_name': relation_name, }) # Create the table object diff --git a/pokedex/db/tables.py b/pokedex/db/tables.py index a7f241f..a81db53 100644 --- a/pokedex/db/tables.py +++ b/pokedex/db/tables.py @@ -1272,6 +1272,7 @@ class PokemonForm(TableBase): @property def name(self): + """Name of this form: the form_name, if set; otherwise the species name""" return self.pokemon_name or self.species.name create_translation_table('pokemon_form_names', PokemonForm, 'names', @@ -1705,7 +1706,6 @@ ContestCombo.second = relationship(Move, innerjoin=True, lazy='joined', backref='contest_combo_second') - Encounter.condition_value_map = relationship(EncounterConditionValueMap, backref='encounter') Encounter.condition_values = association_proxy('condition_value_map', 'condition_value') @@ -1954,10 +1954,8 @@ Pokemon.all_abilities = relationship(Ability, secondary=PokemonAbility.__table__, order_by=PokemonAbility.slot.asc(), innerjoin=True, - backref=backref('all_pokemon', - order_by=Pokemon.order.asc(), - ), -) + backref=backref('all_pokemon', order_by=Pokemon.order.asc()), + doc=u"All abilities the Pokémon can have, including the Hidden Ability") Pokemon.abilities = relationship(Ability, secondary=PokemonAbility.__table__, primaryjoin=and_( @@ -1966,10 +1964,8 @@ Pokemon.abilities = relationship(Ability, ), innerjoin=True, order_by=PokemonAbility.slot.asc(), - backref=backref('pokemon', - order_by=Pokemon.order.asc(), - ), -) + backref=backref('pokemon', order_by=Pokemon.order.asc()), + doc=u"Abilities the Pokémon can have in the wild") Pokemon.dream_ability = relationship(Ability, secondary=PokemonAbility.__table__, primaryjoin=and_( @@ -1977,10 +1973,8 @@ Pokemon.dream_ability = relationship(Ability, PokemonAbility.is_dream == True, ), uselist=False, - backref=backref('dream_pokemon', - order_by=Pokemon.order, - ), -) + backref=backref('dream_pokemon', order_by=Pokemon.order), + doc=u"The Pokémon's Hidden Ability") Pokemon.forms = relationship(PokemonForm, primaryjoin=Pokemon.id==PokemonForm.pokemon_id, order_by=(PokemonForm.order.asc(), PokemonForm.form_identifier.asc()), @@ -1989,9 +1983,11 @@ Pokemon.default_form = relationship(PokemonForm, primaryjoin=and_( Pokemon.id==PokemonForm.pokemon_id, PokemonForm.is_default==True), - uselist=False, lazy='joined') + uselist=False, lazy='joined', + doc=u"A representative form of this pokémon") Pokemon.items = relationship(PokemonItem, - backref='pokemon') + backref='pokemon', + doc=u"Info about items this pokémon holds in the wild") Pokemon.stats = relationship(PokemonStat, innerjoin=True, order_by=PokemonStat.stat_id.asc(), @@ -2077,7 +2073,9 @@ PokemonStat.stat = relationship(Stat, PokemonSpecies.parent_species = relationship(PokemonSpecies, primaryjoin=PokemonSpecies.evolves_from_species_id==PokemonSpecies.id, remote_side=[PokemonSpecies.id], - backref='child_species') + backref=backref('child_species', + doc=u"The species to which this one evolves"), + doc=u"The species from which this one evolves") PokemonSpecies.evolutions = relationship(PokemonEvolution, primaryjoin=PokemonSpecies.id==PokemonEvolution.evolved_species_id, backref=backref('evolved_species', innerjoin=True, lazy='joined')) @@ -2108,7 +2106,8 @@ PokemonSpecies.default_form = relationship(PokemonForm, Pokemon.is_default==True), secondaryjoin=and_(Pokemon.id==PokemonForm.pokemon_id, PokemonForm.is_default==True), - uselist=False) + uselist=False, + doc=u"A representative form of this species") PokemonSpecies.default_pokemon = relationship(Pokemon, primaryjoin=and_( PokemonSpecies.id==Pokemon.species_id, diff --git a/pokedex/doc/__init__.py b/pokedex/doc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pokedex/doc/tabledoc.py b/pokedex/doc/tabledoc.py new file mode 100644 index 0000000..172a454 --- /dev/null +++ b/pokedex/doc/tabledoc.py @@ -0,0 +1,334 @@ +# Encoding: UTF-8 + +u"""Automatic documentation generation for pokédex tables + +This adds a "dex-table" directive to Sphinx, which works like "autoclass", +but documents Pokédex mapped classes. +""" +# XXX: This assumes all the tables are in pokedex.db.tables + +import functools +import textwrap + +from docutils import nodes +from docutils.statemachine import ViewList +from sphinx.util.compat import Directive, make_admonition +from sphinx.locale import _ +from sphinx.domains.python import PyClasslike +from sphinx.util.docfields import Field, GroupedField, TypedField +from sphinx.ext.autodoc import ClassLevelDocumenter + +from sqlalchemy import types +from sqlalchemy.orm.attributes import InstrumentedAttribute +from sqlalchemy.orm.properties import RelationshipProperty +from sqlalchemy.orm import Mapper, configure_mappers +from sqlalchemy.ext.associationproxy import AssociationProxy +from pokedex.db.markdown import MoveEffectPropertyMap, MoveEffectProperty + +from pokedex.db import tables, markdown + +# Make sure all the backrefs are in place +configure_mappers() + + +column_to_cls = {} +for cls in tables.mapped_classes: + for column in cls.__table__.c: + column_to_cls[column] = cls + +class dextabledoc(nodes.Admonition, nodes.Element): + pass + +def visit_todo_node(self, node): + self.visit_admonition(node) + +def depart_todo_node(self, node): + self.depart_admonition(node) + +def column_type_str(column): + """Extract the type name from a SQLA column + """ + type_ = column.type + # We're checking the specific type here: no issubclass + if type(type_) in (types.Integer, types.SmallInteger): + return 'int' + if type(type_) == types.Boolean: + return 'bool' + if type(type_) == types.Unicode: + return u'unicode – %s' % column.info['format'] + if type(type_) == types.Enum: + return 'enum: [%s]' % ', '.join(type_.enums) + if type(type_) == markdown.MarkdownColumn: + return 'markdown' + raise ValueError(repr(type_)) + +common_columns = 'id identifier name'.split() + +def column_header(c, class_name=None, transl_name=None, show_type=True, + relation=None, relation_name=None): + """Return the column header for the given column""" + result = [] + if relation_name: + name = relation_name + else: + name = c.name + if class_name: + result.append(u'%s.\ **%s**' % (class_name, name)) + else: + result.append(u'**%s**' % c.name) + if c.foreign_keys: + for fk in c.foreign_keys: + if fk.column in column_to_cls: + foreign_cls = column_to_cls[fk.column] + if relation_name and relation_name + '_id' == c.name: + result.append(u'(%s →' % c.name) + elif relation_name: + result.append(u'(**%s** →' % c.name) + else: + result.append(u'(→') + result.append(u':class:`~pokedex.db.tables.%s`.%s)' % ( + foreign_cls.__name__, + fk.column.name + )) + break + elif show_type: + result.append(u'(*%s*)' % column_type_str(c)) + if transl_name: + result.append(u'via *%s*' % transl_name) + return ' '.join(result) + + +def with_header(header=None): + """Decorator that adds a section header if there's a any output + + The decorated function should yield output lines; if there are any the + header gets added. + """ + def wrap(func): + @functools.wraps(func) + def wrapped(cls, remaining_attrs): + result = list(func(cls, remaining_attrs)) + if result: + # Sphinx/ReST doesn't allow "-----" just anywhere :( + yield u'' + yield u'.. raw:: html' + yield u'' + yield u'
' + yield u'' + if header: + yield header + u':' + yield u'' + for row in result: + yield row + return wrapped + return wrap + +### Section generation functions + +def generate_table_header(cls, remaining_attrs): + first_line, sep, next_lines = unicode(cls.__doc__).partition(u'\n') + yield first_line + for line in textwrap.dedent(next_lines).split('\n'): + yield line + yield '' + + yield u'Table name: *%s*' % cls.__tablename__ + try: + yield u'(single: *%s*)' % cls.__singlename__ + except AttributeError: + pass + yield u'' + + yield u'Primary key: %s.' % u', '.join( + u'**%s**' % col.key for col in cls.__table__.primary_key.columns) + yield u'' + +def generate_common(cls, remaining_attrs): + common_col_headers = [] + for c in cls.__table__.c: + if c.name in common_columns: + common_col_headers.append(column_header(c, show_type=False)) + remaining_attrs.remove(c.name) + for translation_class in cls.translation_classes: + for c in translation_class.__table__.c: + if c.name in common_columns: + common_col_headers.append(column_header(c, None, + translation_class.__table__.name, show_type=False)) + remaining_attrs.remove(c.name) + + if common_col_headers: + if len(common_col_headers) > 1: + common_col_headers[-1] = 'and ' + common_col_headers[-1] + if len(common_col_headers) > 2: + separator = u', ' + else: + separator = u' ' + yield u'Has' + yield separator.join(common_col_headers) + '.' + yield u'' + +@with_header(u'Columns') +def generate_columns(cls, remaining_attrs): + name = cls.__name__ + for c in [c for c in cls.__table__.c if c.name not in common_columns]: + remaining_attrs.remove(c.name) + relation_name = c.name[:-3] + if c.name.endswith('_id') and relation_name in remaining_attrs: + relation = getattr(cls, relation_name) + yield column_header(c, name, + relation=relation, relation_name=relation_name) + remaining_attrs.remove(relation_name) + else: + yield column_header(c, name) + ':' + yield u'' + yield u' ' + unicode(c.info['description']) + yield u'' + +@with_header(u'Internationalized strings') +def generate_strings(cls, remaining_attrs): + for translation_class in cls.translation_classes: + for c in translation_class.__table__.c: + if 'format' in c.info: + remaining_attrs.discard(c.name) + remaining_attrs.discard(c.name + '_map') + if c.name in common_columns: + continue + yield column_header(c, cls.__name__, + translation_class.__table__.name) + yield u'' + yield u' ' + unicode(c.info['description']) + yield u'' + +@with_header(u'Relationships') +def generate_relationships(cls, remaining_attrs): + def isrelationship(prop): + return isinstance(prop, InstrumentedAttribute) and isinstance(prop.property, RelationshipProperty) + + for attr_name in sorted(remaining_attrs): + prop = getattr(cls, attr_name) + if not isrelationship(prop): + continue + rel = prop.property + yield u'%s.\ **%s**' % (cls.__name__, attr_name) + class_name = u':class:`~pokedex.db.tables.%s`' % rel.mapper.class_.__name__ + if rel.uselist: + class_name = u'[%s]' % class_name + yield u'(→ %s)' % class_name + if rel.doc: + yield u'' + yield u' ' + unicode(rel.doc) + if rel.secondary is not None: + yield u'' + yield ' Association table: ``%s``' % rel.secondary + #if rel.primaryjoin is not None: + # yield u'' + # yield ' Join condition: ``%s``' % rel.primaryjoin + # if rel.secondaryjoin is not None: + # yield ' , ``%s``' % rel.secondaryjoin + if rel.order_by: + yield u'' + yield u' ' + yield ' Ordered by: ' + u', '.join( + u'``%s``' % o for o in rel.order_by) + yield u'' + remaining_attrs.remove(attr_name) + +@with_header(u'Association Proxies') +def generate_associationproxies(cls, remaining_attrs): + for attr_name in sorted(remaining_attrs): + prop = getattr(cls, attr_name) + if isinstance(prop, AssociationProxy): + yield u'%s.\ **%s**:' % (cls.__name__, attr_name) + yield '``{prop.remote_attr.key}`` of ``self.{prop.local_attr.key}``'.format( + prop=prop) + '''if 'description' in info: + yield u'' + yield u' ' + unicode(info['description'])''' + yield u'' + remaining_attrs.remove(attr_name) + + +@with_header(u'Undocumented') +def generate_undocumented(cls, remaining_attrs): + for c in sorted([c for c in remaining_attrs if isinstance(getattr(cls, c), + (InstrumentedAttribute, AssociationProxy, + MoveEffectPropertyMap, MoveEffectProperty))]): + yield u'' + yield u'%s.\ **%s**' % (cls.__name__, c) + remaining_attrs.remove(c) + +@with_header(None) +def generate_other(cls, remaining_attrs): + for c in sorted(remaining_attrs): + yield u'' + member = getattr(cls, c) + if callable(member): + yield '.. automethod:: %s.%s' % (cls.__name__, c) + else: + yield '.. autoattribute:: %s.%s' % (cls.__name__, c) + yield u'' + remaining_attrs.clear() + + +class DexTable(PyClasslike): + """The actual Sphinx documentation generation whatchamacallit + """ + doc_field_types = [ + TypedField('field', label='Fields', + typerolename='obj', typenames=('fieldname', 'type')), + ] + + def get_signature_prefix(self, sig): + return '' + #return u'mapped class ' + + def run(self): + section = nodes.section() + super_result = super(DexTable, self).run() + title_text = self.names[0][0] + section += nodes.title(text=title_text) + section += super_result + section['ids'] = ['dex-table-%s' % title_text.lower()] + return [section] + + def before_content(self): + name = self.names[0][0] + for cls in tables.mapped_classes: + if name == cls.__name__: + break + else: + raise ValueError('Table %s not found' % name) + table = cls.__table__ + + remaining_attrs = set(x for x in dir(cls) if not x.startswith('_')) + remaining_attrs.difference_update(['metadata', 'translation_classes', + 'add_relationships', 'summary_column']) + for transl_class in cls.translation_classes: + remaining_attrs.difference_update([ + transl_class.relation_name, + transl_class.relation_name + '_table', + transl_class.relation_name + '_local', + ]) + + generated_content = [] # Just a list of lines! + + generated_content.extend(generate_table_header(cls, remaining_attrs)) + generated_content.extend(generate_common(cls, remaining_attrs)) + generated_content.extend(generate_columns(cls, remaining_attrs)) + generated_content.extend(generate_strings(cls, remaining_attrs)) + generated_content.extend(generate_relationships(cls, remaining_attrs)) + generated_content.extend(generate_associationproxies(cls, remaining_attrs)) + generated_content.extend(generate_undocumented(cls, remaining_attrs)) + generated_content.extend(generate_other(cls, remaining_attrs)) + + generated_content.append(u'') + self.content = ViewList(generated_content + list(self.content)) + return super(DexTable, self).before_content() + + def get_index_text(self, modname, name_cls): + return '%s (mapped class)' % name_cls[0] + +def setup(app): + app.add_directive('dex-table', DexTable) + + # XXX: Specify that this depends on pokedex.db.tables ...? diff --git a/setup.py b/setup.py index da17d38..5c71530 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup( 'pokedex': ['data/csv/*.csv'] }, install_requires=[ - 'SQLAlchemy>=0.7', + 'SQLAlchemy>=0.7.3', 'whoosh>=2.2.2', 'markdown', 'construct',