From 96499fae30be5e6e9aadb73b03da07154efedfc4 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Sat, 11 Feb 2012 21:05:52 +0100 Subject: [PATCH 01/11] Allow ordering and filtering on translated texts, e.g. Move.name --- pokedex/db/multilang.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/pokedex/db/multilang.py b/pokedex/db/multilang.py index a778869..dffe6a4 100644 --- a/pokedex/db/multilang.py +++ b/pokedex/db/multilang.py @@ -1,16 +1,37 @@ from functools import partial -from sqlalchemy.ext.associationproxy import association_proxy +from sqlalchemy.ext.associationproxy import association_proxy, AssociationProxy from sqlalchemy.orm import Query, aliased, mapper, relationship, synonym from sqlalchemy.orm.collections import attribute_mapped_collection from sqlalchemy.orm.scoping import ScopedSession from sqlalchemy.orm.session import Session, object_session from sqlalchemy.schema import Column, ForeignKey, Table -from sqlalchemy.sql.expression import and_, bindparam, select +from sqlalchemy.sql.expression import and_, bindparam, select, exists +from sqlalchemy.sql.operators import ColumnOperators from sqlalchemy.types import Integer from pokedex.db import markdown +class LocalAssociationProxy(AssociationProxy, ColumnOperators): + """An association proxy for names in the default language + + Over the regular association_proxy, this provides sorting and filtering + capabilities, implemented via SQL subqueries. + """ + def __clause_element__(self): + q = select([self.remote_attr]) + q = q.where(self.target_class.foreign_id == self.owning_class.id) + q = q.where(self.target_class.local_language_id == bindparam('_default_language_id')) + return q + + def operate(self, op, *other, **kwargs): + q = select([self.remote_attr]) + q = q.where(self.target_class.foreign_id == self.owning_class.id) + q = q.where(self.target_class.local_language_id == bindparam('_default_language_id')) + q = q.where(op(self.remote_attr, *other)) + return exists(q) + + def _getset_factory_factory(column_name, string_getter): """Hello! I am a factory for creating getset_factory functions for SQLA. I exist to avoid the closure-in-a-loop problem. @@ -165,7 +186,7 @@ def create_translation_table(_table_name, foreign_class, relation_name, # Class.(column) -- accessor for the default language's value setattr(foreign_class, name, - association_proxy(local_relation_name, name, + LocalAssociationProxy(local_relation_name, name, getset_factory=getset_factory)) # Class.(column)_map -- accessor for the language dict From 0cb1f8a1b83dd7c8a48c191941c11e29861a6c19 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Sun, 12 Feb 2012 16:19:09 +0100 Subject: [PATCH 02/11] Make the table schema a bit more introspectable This solves two problems: first, the relationships are now defined in the class they apply to, rather than in a separate section of the module, and second, their metadata -- both creation arguments and extra info such as `description` (or, later, possibly, info for API properties) -- is stored. --- pokedex/db/multilang.py | 1 + pokedex/db/tables.py | 142 ++++++++++++++++++++++++---------------- 2 files changed, 88 insertions(+), 55 deletions(-) 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 7726794..c36b8a6 100644 --- a/pokedex/db/tables.py +++ b/pokedex/db/tables.py @@ -30,7 +30,8 @@ classes in that module can be used to change the default language. import collections from functools import partial -from sqlalchemy import Column, ForeignKey, MetaData, PrimaryKeyConstraint, Table, UniqueConstraint +from sqlalchemy import (Column, ForeignKey, MetaData, PrimaryKeyConstraint, + Table, UniqueConstraint) from sqlalchemy.ext.declarative import declarative_base, DeclarativeMeta from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.orm import backref, relationship @@ -76,6 +77,7 @@ class TableMetaclass(DeclarativeMeta): if hasattr(cls, '__tablename__'): mapped_classes.append(cls) cls.translation_classes = [] + cls.relationship_info = {} metadata = MetaData() TableBase = declarative_base(metadata=metadata, cls=TableSuperclass, metaclass=TableMetaclass) @@ -1400,6 +1402,69 @@ class PokemonSpecies(TableBase): forms_switchable = Column(Boolean, nullable=False, info=dict(description=u"True iff a particular individual of this species can switch beween its different forms.")) + @staticmethod + def _add_relationships(add_relationship, **kwargs): + add_relationship('parent_species', PokemonSpecies, + primaryjoin=PokemonSpecies.evolves_from_species_id==PokemonSpecies.id, + remote_side=[PokemonSpecies.id], + backref='child_species', + info=dict( + description=u"The species from which this one evolves")) + add_relationship('evolutions', PokemonEvolution, + primaryjoin=PokemonSpecies.id==PokemonEvolution.evolved_species_id, + backref=backref('evolved_species', + innerjoin=True, + lazy='joined')) + add_relationship('flavor_text', PokemonSpeciesFlavorText, + order_by=PokemonSpeciesFlavorText.version_id.asc(), + backref='species') + add_relationship('growth_rate', GrowthRate, + innerjoin=True, + backref='evolution_chains') + add_relationship('habitat', PokemonHabitat, + backref='species') + add_relationship('color', PokemonColor, + innerjoin=True, + backref='species') + add_relationship('egg_groups', EggGroup, + secondary=PokemonEggGroup.__table__, + innerjoin=True, + order_by=PokemonEggGroup.egg_group_id.asc(), + backref=backref('species', order_by=Pokemon.order.asc())) + add_relationship('forms', PokemonForm, + secondary=Pokemon.__table__, + primaryjoin=PokemonSpecies.id==Pokemon.species_id, + secondaryjoin=Pokemon.id==PokemonForm.pokemon_id, + order_by=(PokemonForm.order.asc(), + PokemonForm.form_identifier.asc())) + add_relationship('default_form', PokemonForm, + secondary=Pokemon.__table__, + primaryjoin=and_(PokemonSpecies.id==Pokemon.species_id, + Pokemon.is_default==True), + secondaryjoin=and_(Pokemon.id==PokemonForm.pokemon_id, + PokemonForm.is_default==True), + uselist=False, + info=dict( + description=u"A representative form of this species")) + add_relationship('default_pokemon', Pokemon, + primaryjoin=and_( + PokemonSpecies.id==Pokemon.species_id, + Pokemon.is_default==True), + uselist=False, lazy='joined') + add_relationship('evolution_chain', EvolutionChain, + innerjoin=True, + backref=backref('species', order_by=PokemonSpecies.id.asc())) + add_relationship('dex_numbers', PokemonDexNumber, + innerjoin=True, + order_by=PokemonDexNumber.pokedex_id.asc(), + backref='species') + add_relationship('generation', Generation, + innerjoin=True, + backref='species') + add_relationship('shape', PokemonShape, + innerjoin=True, + backref='species') + create_translation_table('pokemon_species_names', PokemonSpecies, 'names', relation_lazy='joined', name = Column(Unicode(20), nullable=True, index=True, @@ -1607,6 +1672,27 @@ class VersionGroupRegion(TableBase): ### Relationships down here, to avoid dependency ordering problems +def add_relationships(): + for cls in mapped_classes: + try: + add_relationships = cls._add_relationships + except AttributeError: + pass + else: + def add_relationship(name, argument, secondary=None, **kwargs): + cls.relationship_info.setdefault('_order', []) + cls.relationship_info['_order'].append(name) + cls.relationship_info[name] = kwargs.pop('info', {}) + cls.relationship_info[name].update(kwargs) + cls.relationship_info[name].update(dict( + type='relationship', + argument=argument, + secondary=secondary)) + setattr(cls, name, relationship(argument, secondary=secondary, + **kwargs)) + add_relationships(add_relationship=add_relationship) +add_relationships() + Ability.changelog = relationship(AbilityChangelog, order_by=AbilityChangelog.changed_in_version_group_id.desc(), backref=backref('ability', innerjoin=True, lazy='joined')) @@ -2005,60 +2091,6 @@ PokemonMove.method = relationship(PokemonMoveMethod, PokemonStat.stat = relationship(Stat, innerjoin=True, lazy='joined') -PokemonSpecies.parent_species = relationship(PokemonSpecies, - primaryjoin=PokemonSpecies.evolves_from_species_id==PokemonSpecies.id, - remote_side=[PokemonSpecies.id], - backref='child_species') -PokemonSpecies.evolutions = relationship(PokemonEvolution, - primaryjoin=PokemonSpecies.id==PokemonEvolution.evolved_species_id, - backref=backref('evolved_species', innerjoin=True, lazy='joined')) -PokemonSpecies.flavor_text = relationship(PokemonSpeciesFlavorText, - order_by=PokemonSpeciesFlavorText.version_id.asc(), - backref='species') -PokemonSpecies.growth_rate = relationship(GrowthRate, - innerjoin=True, - backref='evolution_chains') -PokemonSpecies.habitat = relationship(PokemonHabitat, - backref='species') -PokemonSpecies.color = relationship(PokemonColor, - innerjoin=True, - backref='species') -PokemonSpecies.egg_groups = relationship(EggGroup, - secondary=PokemonEggGroup.__table__, - innerjoin=True, - order_by=PokemonEggGroup.egg_group_id.asc(), - backref=backref('species', order_by=Pokemon.order.asc())) -PokemonSpecies.forms = relationship(PokemonForm, - secondary=Pokemon.__table__, - primaryjoin=PokemonSpecies.id==Pokemon.species_id, - secondaryjoin=Pokemon.id==PokemonForm.pokemon_id, - order_by=(PokemonForm.order.asc(), PokemonForm.form_identifier.asc())) -PokemonSpecies.default_form = relationship(PokemonForm, - secondary=Pokemon.__table__, - primaryjoin=and_(PokemonSpecies.id==Pokemon.species_id, - Pokemon.is_default==True), - secondaryjoin=and_(Pokemon.id==PokemonForm.pokemon_id, - PokemonForm.is_default==True), - uselist=False) -PokemonSpecies.default_pokemon = relationship(Pokemon, - primaryjoin=and_( - PokemonSpecies.id==Pokemon.species_id, - Pokemon.is_default==True), - uselist=False, lazy='joined') -PokemonSpecies.evolution_chain = relationship(EvolutionChain, - innerjoin=True, - backref=backref('species', order_by=PokemonSpecies.id.asc())) -PokemonSpecies.dex_numbers = relationship(PokemonDexNumber, - innerjoin=True, - order_by=PokemonDexNumber.pokedex_id.asc(), - backref='species') -PokemonSpecies.generation = relationship(Generation, - innerjoin=True, - backref='species') -PokemonSpecies.shape = relationship(PokemonShape, - innerjoin=True, - backref='species') - PokemonSpeciesFlavorText.version = relationship(Version, innerjoin=True, lazy='joined') PokemonSpeciesFlavorText.language = relationship(Language, innerjoin=True, lazy='joined') From 34481e9a110fc4142b2d3e79f918f55b254a975e Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Sun, 12 Feb 2012 21:30:30 +0100 Subject: [PATCH 03/11] Add Sphinx documentation --- doc/Makefile | 146 +++++++++++++++++++ doc/conf.py | 230 +++++++++++++++++++++++++++++ doc/index.rst | 25 ++++ doc/installing.rst | 156 ++++++++++++++++++++ doc/main-tables.rst | 175 ++++++++++++++++++++++ doc/schema.rst | 6 + doc/usage.rst | 193 +++++++++++++++++++++++++ pokedex/doc/__init__.py | 0 pokedex/doc/tabledoc.py | 313 ++++++++++++++++++++++++++++++++++++++++ 9 files changed, 1244 insertions(+) create mode 100644 doc/Makefile create mode 100644 doc/conf.py create mode 100644 doc/index.rst create mode 100644 doc/installing.rst create mode 100644 doc/main-tables.rst create mode 100644 doc/schema.rst create mode 100644 doc/usage.rst create mode 100644 pokedex/doc/__init__.py create mode 100644 pokedex/doc/tabledoc.py 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..7cef777 --- /dev/null +++ b/doc/main-tables.rst @@ -0,0 +1,175 @@ +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:: 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:: VersionGroupRegion + +Index maps +---------- + +.. dex-table:: ItemGameIndex +.. dex-table:: LocationGameIndex +.. dex-table:: PokemonDexNumber +.. dex-table:: PokemonGameIndex + +Mics tables +----------- + +.. dex-table:: Experience +.. 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/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..74287ea --- /dev/null +++ b/pokedex/doc/tabledoc.py @@ -0,0 +1,313 @@ +# 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 import 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: + 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'' + +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): + for rel_name in [c for c in cls.relationship_info.get('_order', []) + if c in remaining_attrs]: + if rel_name in remaining_attrs: + info = cls.relationship_info.get(rel_name) + yield u'%s.\ **%s**' % (cls.__name__, rel_name) + class_name = u':class:`~pokedex.db.tables.%s`' % info['argument'].__name__ + if info.get('uselist', True): + class_name = u'[%s]' % class_name + yield u'(→ %s)' % class_name + if 'description' in info: + yield u'' + yield u' ' + unicode(info['description']) + ''' + if info.get('secondary') is not None: + yield u'' + yield ' Association table: ``%s``' % info['secondary'] + if 'primaryjoin' in info: + yield u'') + yield ' Join condition: ``%s``' % info['primaryjoin'] + if 'secondaryjoin' in info: + yield ' , ``%s``' % info['secondaryjoin'] + ''' + if 'order_by' in info: + yield u'' + try: + order = iter(info['order_by']) + except TypeError: + order = [info['order_by']] + yield u' ' + yield ' Ordered by: ' + u', '.join( + u'``%s``' % o for o in order) + yield u'' + remaining_attrs.remove(rel_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', 'relationship_info', '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_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 ...? From 949ff883ea83d71ad382e547135686768be5501a Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Sun, 12 Feb 2012 22:43:59 +0100 Subject: [PATCH 04/11] Autodoc for backrefs --- pokedex/db/tables.py | 23 +++++++++++++++++++---- pokedex/doc/tabledoc.py | 10 ++++++++-- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/pokedex/db/tables.py b/pokedex/db/tables.py index c36b8a6..57ca1bd 100644 --- a/pokedex/db/tables.py +++ b/pokedex/db/tables.py @@ -1407,9 +1407,9 @@ class PokemonSpecies(TableBase): add_relationship('parent_species', PokemonSpecies, primaryjoin=PokemonSpecies.evolves_from_species_id==PokemonSpecies.id, remote_side=[PokemonSpecies.id], - backref='child_species', - info=dict( - description=u"The species from which this one evolves")) + backref=backref('child_species', + info=dict(description=u"The species to which this one evolves")), + info=dict(description=u"The species from which this one evolves")) add_relationship('evolutions', PokemonEvolution, primaryjoin=PokemonSpecies.id==PokemonEvolution.evolved_species_id, backref=backref('evolved_species', @@ -1682,12 +1682,27 @@ def add_relationships(): def add_relationship(name, argument, secondary=None, **kwargs): cls.relationship_info.setdefault('_order', []) cls.relationship_info['_order'].append(name) - cls.relationship_info[name] = kwargs.pop('info', {}) + cls.relationship_info[name] = info = kwargs.pop('info', {}) cls.relationship_info[name].update(kwargs) cls.relationship_info[name].update(dict( type='relationship', argument=argument, secondary=secondary)) + the_backref = kwargs.get('backref') + if isinstance(the_backref, basestring): + the_backref = backref(the_backref) + if the_backref: + # XXX: This line exploits a SQLA implementation detail, + # after all relationships are converted we should make our + # own backref wrapper with known semantics. + backref_name, backref_kwargs = the_backref + + backref_info = dict( + type='backref', + argument=cls, + secondary=secondary) + backref_info.update(backref_kwargs.pop('info', {})) + argument.relationship_info[backref_name] = backref_info setattr(cls, name, relationship(argument, secondary=secondary, **kwargs)) add_relationships(add_relationship=add_relationship) diff --git a/pokedex/doc/tabledoc.py b/pokedex/doc/tabledoc.py index 74287ea..ee92040 100644 --- a/pokedex/doc/tabledoc.py +++ b/pokedex/doc/tabledoc.py @@ -194,8 +194,14 @@ def generate_strings(cls, remaining_attrs): @with_header(u'Relationships') def generate_relationships(cls, remaining_attrs): - for rel_name in [c for c in cls.relationship_info.get('_order', []) - if c in remaining_attrs]: + order = cls.relationship_info.get('_order', []) + def sort_key((key, value)): + try: + return 0, order.index(key) + except ValueError: + return 1, key + infos = sorted(cls.relationship_info.items(), key=sort_key) + for rel_name, info in infos: if rel_name in remaining_attrs: info = cls.relationship_info.get(rel_name) yield u'%s.\ **%s**' % (cls.__name__, rel_name) From ef3fb2f5364c4edb0fbd0ab5973138891d3c170e Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Sun, 12 Feb 2012 21:50:13 +0100 Subject: [PATCH 05/11] Move relationships to _add_relationships calls: Pokemon --- pokedex/db/tables.py | 242 +++++++++++++++++++++---------------------- 1 file changed, 120 insertions(+), 122 deletions(-) diff --git a/pokedex/db/tables.py b/pokedex/db/tables.py index 57ca1bd..a955722 100644 --- a/pokedex/db/tables.py +++ b/pokedex/db/tables.py @@ -325,6 +325,25 @@ class Encounter(TableBase): max_level = Column(Integer, nullable=False, autoincrement=False, info=dict(description=u"The maxmum level of the encountered Pokémon")) + @staticmethod + def _add_relationships(add_relationship, add_association_proxy, **kwargs): + add_relationship('condition_value_map', EncounterConditionValueMap, + backref='encounter') + add_association_proxy('condition_values', + 'condition_value_map', 'condition_value') + add_relationship('location_area', LocationArea, + innerjoin=True, lazy='joined', + backref='encounters') + add_relationship('pokemon', Pokemon, + innerjoin=True, lazy='joined', + backref='encounters') + add_relationship('version', Version, + innerjoin=True, lazy='joined', + backref='encounters') + add_relationship('slot', EncounterSlot, + innerjoin=True, lazy='joined', + backref='encounters') + class EncounterCondition(TableBase): u"""A conditions in the game world that affects Pokémon encounters, such as time of day. """ @@ -1082,6 +1101,62 @@ class Pokemon(TableBase): is_default = Column(Boolean, nullable=False, index=True, info=dict(description=u'Set for exactly one pokemon used as the default for each species.')) + @staticmethod + def _add_relationships(add_relationship, **kwargs): + add_relationship('all_abilities', Ability, + secondary=PokemonAbility.__table__, + order_by=PokemonAbility.slot.asc(), + innerjoin=True, + backref=backref('all_pokemon', order_by=Pokemon.order.asc()), + info=dict( + description=u"All abilities the Pokémon can have, including the Hidden Ability")) + add_relationship('abilities', Ability, + secondary=PokemonAbility.__table__, + primaryjoin=and_( + Pokemon.id == PokemonAbility.pokemon_id, + PokemonAbility.is_dream == False), + innerjoin=True, + order_by=PokemonAbility.slot.asc(), + backref=backref('pokemon', order_by=Pokemon.order.asc()), + info=dict( + description=u"Abilities the Pokémon can have in the wild")) + add_relationship('dream_ability', Ability, + secondary=PokemonAbility.__table__, + primaryjoin=and_( + Pokemon.id == PokemonAbility.pokemon_id, + PokemonAbility.is_dream == True), + uselist=False, + backref=backref('dream_pokemon', order_by=Pokemon.order), + info=dict( + description=u"The Pokémon's Hidden Ability")) + add_relationship('forms', PokemonForm, + primaryjoin=Pokemon.id==PokemonForm.pokemon_id, + order_by=(PokemonForm.order.asc(), + PokemonForm.form_identifier.asc())) + add_relationship('default_form', PokemonForm, + primaryjoin=and_( + Pokemon.id==PokemonForm.pokemon_id, + PokemonForm.is_default==True), + uselist=False, + lazy='joined', + info=dict(description=u"A representative form of this pokémon")) + add_relationship('items', PokemonItem, + backref='pokemon', + info=dict( + description=u"Info about items this pokémon holds in the wild")) + add_relationship('stats', PokemonStat, + innerjoin=True, + order_by=PokemonStat.stat_id.asc(), + backref='pokemon') + add_relationship('species', PokemonSpecies, + innerjoin=True, + backref='pokemon') + add_relationship('types', Type, + secondary=PokemonType.__table__, + innerjoin=True, lazy='joined', + order_by=PokemonType.slot.asc(), + backref=backref('pokemon', order_by=Pokemon.order)) + @property def name(self): u"""Returns a name for this Pokémon, specifiying the form iff it @@ -1216,6 +1291,27 @@ class PokemonEvolution(TableBase): trade_species_id = Column(Integer, ForeignKey('pokemon_species.id'), nullable=True, info=dict(description=u"The ID of the species for which this one must be traded.")) + @staticmethod + def _add_relationships(add_relationship, **kwargs): + add_relationship('trigger', EvolutionTrigger, + innerjoin=True, lazy='joined', + backref='evolutions') + add_relationship('trigger_item', Item, + primaryjoin=PokemonEvolution.trigger_item_id==Item.id, + backref='triggered_evolutions') + add_relationship('held_item', Item, + primaryjoin=PokemonEvolution.held_item_id==Item.id, + backref='required_for_evolutions') + add_relationship('location', Location, + backref='triggered_evolutions') + add_relationship('known_move', Move, + backref='triggered_evolutions') + add_relationship('party_species', PokemonSpecies, + primaryjoin=PokemonEvolution.party_species_id==PokemonSpecies.id, + backref='triggered_evolutions') + add_relationship('trade_species', PokemonSpecies, + primaryjoin=PokemonEvolution.trade_species_id==PokemonSpecies.id) + class PokemonForm(TableBase): u"""An individual form of a Pokémon. This includes *every* variant (except color differences) of every Pokémon, regardless of how the games treat @@ -1327,6 +1423,26 @@ class PokemonMove(TableBase): {}, ) + @staticmethod + def _add_relationships(add_relationship, **kwargs): + add_relationship('pokemon', Pokemon, + innerjoin=True, lazy='joined', + backref='pokemon_moves') + add_relationship('version_group', VersionGroup, + innerjoin=True, lazy='joined') + add_relationship('machine', Machine, + primaryjoin=and_( + Machine.version_group_id==PokemonMove.version_group_id, + Machine.move_id==PokemonMove.move_id), + foreign_keys=[Machine.version_group_id, Machine.move_id], + uselist=False, + backref='pokemon_moves') + add_relationship('move', Move, + innerjoin=True, lazy='joined', + backref='pokemon_moves') + add_relationship('method', PokemonMoveMethod, + innerjoin=True, lazy='joined') + class PokemonMoveMethod(TableBase): u"""A method a move can be learned by, such as "Level up" or "Tutor". """ @@ -1412,9 +1528,7 @@ class PokemonSpecies(TableBase): info=dict(description=u"The species from which this one evolves")) add_relationship('evolutions', PokemonEvolution, primaryjoin=PokemonSpecies.id==PokemonEvolution.evolved_species_id, - backref=backref('evolved_species', - innerjoin=True, - lazy='joined')) + backref=backref('evolved_species', innerjoin=True, lazy='joined')) add_relationship('flavor_text', PokemonSpeciesFlavorText, order_by=PokemonSpeciesFlavorText.version_id.asc(), backref='species') @@ -1444,8 +1558,7 @@ class PokemonSpecies(TableBase): secondaryjoin=and_(Pokemon.id==PokemonForm.pokemon_id, PokemonForm.is_default==True), uselist=False, - info=dict( - description=u"A representative form of this species")) + info=dict(description=u"A representative form of this species")) add_relationship('default_pokemon', Pokemon, primaryjoin=and_( PokemonSpecies.id==Pokemon.species_id, @@ -1683,8 +1796,8 @@ def add_relationships(): cls.relationship_info.setdefault('_order', []) cls.relationship_info['_order'].append(name) cls.relationship_info[name] = info = kwargs.pop('info', {}) - cls.relationship_info[name].update(kwargs) - cls.relationship_info[name].update(dict( + info.update(kwargs) + info.update(dict( type='relationship', argument=argument, secondary=secondary)) @@ -1750,22 +1863,6 @@ ContestCombo.second = relationship(Move, backref='contest_combo_second') -Encounter.condition_value_map = relationship(EncounterConditionValueMap, - backref='encounter') -Encounter.condition_values = association_proxy('condition_value_map', 'condition_value') -Encounter.location_area = relationship(LocationArea, - innerjoin=True, lazy='joined', - backref='encounters') -Encounter.pokemon = relationship(Pokemon, - innerjoin=True, lazy='joined', - backref='encounters') -Encounter.version = relationship(Version, - innerjoin=True, lazy='joined', - backref='encounters') -Encounter.slot = relationship(EncounterSlot, - innerjoin=True, lazy='joined', - backref='encounters') - EncounterConditionValue.condition = relationship(EncounterCondition, innerjoin=True, lazy='joined', backref='values') @@ -1990,91 +2087,9 @@ Pokedex.version_groups = relationship(VersionGroup, backref='pokedex') -Pokemon.all_abilities = relationship(Ability, - secondary=PokemonAbility.__table__, - order_by=PokemonAbility.slot.asc(), - innerjoin=True, - backref=backref('all_pokemon', - order_by=Pokemon.order.asc(), - ), -) -Pokemon.abilities = relationship(Ability, - secondary=PokemonAbility.__table__, - primaryjoin=and_( - Pokemon.id == PokemonAbility.pokemon_id, - PokemonAbility.is_dream == False, - ), - innerjoin=True, - order_by=PokemonAbility.slot.asc(), - backref=backref('pokemon', - order_by=Pokemon.order.asc(), - ), -) -Pokemon.dream_ability = relationship(Ability, - secondary=PokemonAbility.__table__, - primaryjoin=and_( - Pokemon.id == PokemonAbility.pokemon_id, - PokemonAbility.is_dream == True, - ), - uselist=False, - backref=backref('dream_pokemon', - order_by=Pokemon.order, - ), -) -Pokemon.forms = relationship(PokemonForm, - primaryjoin=Pokemon.id==PokemonForm.pokemon_id, - order_by=(PokemonForm.order.asc(), PokemonForm.form_identifier.asc())) -Pokemon.default_form = relationship(PokemonForm, - primaryjoin=and_( - Pokemon.id==PokemonForm.pokemon_id, - PokemonForm.is_default==True), - uselist=False, lazy='joined') -Pokemon.items = relationship(PokemonItem, - backref='pokemon') -Pokemon.stats = relationship(PokemonStat, - innerjoin=True, - order_by=PokemonStat.stat_id.asc(), - backref='pokemon') -Pokemon.species = relationship(PokemonSpecies, - innerjoin=True, - backref='pokemon') -Pokemon.types = relationship(Type, - secondary=PokemonType.__table__, - innerjoin=True, lazy='joined', - order_by=PokemonType.slot.asc(), - backref=backref('pokemon', order_by=Pokemon.order)) - PokemonDexNumber.pokedex = relationship(Pokedex, innerjoin=True, lazy='joined') -PokemonEvolution.trigger = relationship(EvolutionTrigger, - innerjoin=True, lazy='joined', - backref='evolutions') -PokemonEvolution.trigger_item = relationship(Item, - primaryjoin=PokemonEvolution.trigger_item_id==Item.id, - backref='triggered_evolutions') -PokemonEvolution.held_item = relationship(Item, - primaryjoin=PokemonEvolution.held_item_id==Item.id, - backref='required_for_evolutions') -PokemonEvolution.location = relationship(Location, - backref='triggered_evolutions') -PokemonEvolution.known_move = relationship(Move, - backref='triggered_evolutions') -PokemonEvolution.party_species = relationship(PokemonSpecies, - primaryjoin=PokemonEvolution.party_species_id==PokemonSpecies.id, - backref='triggered_evolutions') -PokemonEvolution.trade_species = relationship(PokemonSpecies, - primaryjoin=PokemonEvolution.trade_species_id==PokemonSpecies.id) - -PokemonForm.pokemon = relationship(Pokemon, - primaryjoin=PokemonForm.pokemon_id==Pokemon.id, - innerjoin=True, lazy='joined') -PokemonForm.species = association_proxy('pokemon', 'species') -PokemonForm.version_group = relationship(VersionGroup, - innerjoin=True) -PokemonForm.pokeathlon_stats = relationship(PokemonFormPokeathlonStat, - order_by=PokemonFormPokeathlonStat.pokeathlon_stat_id, - backref='pokemon_form') PokemonFormPokeathlonStat.pokeathlon_stat = relationship(PokeathlonStat, innerjoin=True, lazy='joined') @@ -2085,23 +2100,6 @@ PokemonItem.item = relationship(Item, PokemonItem.version = relationship(Version, innerjoin=True, lazy='joined') -PokemonMove.pokemon = relationship(Pokemon, - innerjoin=True, lazy='joined', - backref='pokemon_moves') -PokemonMove.version_group = relationship(VersionGroup, - innerjoin=True, lazy='joined') -PokemonMove.machine = relationship(Machine, - primaryjoin=and_( - Machine.version_group_id==PokemonMove.version_group_id, - Machine.move_id==PokemonMove.move_id), - foreign_keys=[Machine.version_group_id, Machine.move_id], - uselist=False, - backref='pokemon_moves') -PokemonMove.move = relationship(Move, - innerjoin=True, lazy='joined', - backref='pokemon_moves') -PokemonMove.method = relationship(PokemonMoveMethod, - innerjoin=True, lazy='joined') PokemonStat.stat = relationship(Stat, innerjoin=True, lazy='joined') From 391fd1c1ac04a75bb8e8695bc841580ee9b3d4ad Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Sun, 12 Feb 2012 23:19:40 +0100 Subject: [PATCH 06/11] Support association proxies --- pokedex/db/tables.py | 27 ++++++++++++++- pokedex/doc/tabledoc.py | 74 ++++++++++++++++++++++++----------------- 2 files changed, 69 insertions(+), 32 deletions(-) diff --git a/pokedex/db/tables.py b/pokedex/db/tables.py index a955722..58329f6 100644 --- a/pokedex/db/tables.py +++ b/pokedex/db/tables.py @@ -1337,8 +1337,21 @@ 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 + @staticmethod + def _add_relationships(add_relationship, add_association_proxy, **kwargs): + add_relationship('pokemon', Pokemon, + primaryjoin=PokemonForm.pokemon_id==Pokemon.id, + innerjoin=True, lazy='joined') + add_association_proxy('species', 'pokemon', 'species') + add_relationship('version_group', VersionGroup, + innerjoin=True) + add_relationship('pokeathlon_stats', PokemonFormPokeathlonStat, + order_by=PokemonFormPokeathlonStat.pokeathlon_stat_id, + backref='pokemon_form') + create_translation_table('pokemon_form_names', PokemonForm, 'names', relation_lazy='joined', form_name = Column(Unicode(32), nullable=True, index=True, @@ -1818,7 +1831,19 @@ def add_relationships(): argument.relationship_info[backref_name] = backref_info setattr(cls, name, relationship(argument, secondary=secondary, **kwargs)) - add_relationships(add_relationship=add_relationship) + def add_association_proxy(name, target_collection, attr, **kwargs): + cls.relationship_info.setdefault('_order', []) + cls.relationship_info['_order'].append(name) + cls.relationship_info[name] = info = kwargs.pop('info', {}) + info.update(kwargs) + info.update(dict( + type='association_proxy', + target_collection=target_collection, + attr=attr)) + setattr(cls, name, association_proxy(name, target_collection, + **kwargs)) + add_relationships(add_relationship=add_relationship, + add_association_proxy=add_association_proxy) add_relationships() Ability.changelog = relationship(AbilityChangelog, diff --git a/pokedex/doc/tabledoc.py b/pokedex/doc/tabledoc.py index ee92040..a121152 100644 --- a/pokedex/doc/tabledoc.py +++ b/pokedex/doc/tabledoc.py @@ -79,8 +79,10 @@ def column_header(c, class_name=None, transl_name=None, show_type=True, for fk in c.foreign_keys: if fk.column in column_to_cls: foreign_cls = column_to_cls[fk.column] - if relation_name: + 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)' % ( @@ -168,8 +170,8 @@ def generate_columns(cls, remaining_attrs): 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) + ':') + yield column_header(c, name, + relation=relation, relation_name=relation_name) remaining_attrs.remove(relation_name) else: yield column_header(c, name) + ':' @@ -187,7 +189,7 @@ def generate_strings(cls, remaining_attrs): if c.name in common_columns: continue yield column_header(c, cls.__name__, - translation_class.__table__.name) + ':' + translation_class.__table__.name) yield u'' yield u' ' + unicode(c.info['description']) yield u'' @@ -204,33 +206,43 @@ def generate_relationships(cls, remaining_attrs): for rel_name, info in infos: if rel_name in remaining_attrs: info = cls.relationship_info.get(rel_name) - yield u'%s.\ **%s**' % (cls.__name__, rel_name) - class_name = u':class:`~pokedex.db.tables.%s`' % info['argument'].__name__ - if info.get('uselist', True): - class_name = u'[%s]' % class_name - yield u'(→ %s)' % class_name - if 'description' in info: - yield u'' - yield u' ' + unicode(info['description']) - ''' - if info.get('secondary') is not None: - yield u'' - yield ' Association table: ``%s``' % info['secondary'] - if 'primaryjoin' in info: - yield u'') - yield ' Join condition: ``%s``' % info['primaryjoin'] - if 'secondaryjoin' in info: - yield ' , ``%s``' % info['secondaryjoin'] - ''' - if 'order_by' in info: - yield u'' - try: - order = iter(info['order_by']) - except TypeError: - order = [info['order_by']] - yield u' ' - yield ' Ordered by: ' + u', '.join( - u'``%s``' % o for o in order) + if info['type'] in ('relationship', 'backref'): + yield u'%s.\ **%s**' % (cls.__name__, rel_name) + class_name = u':class:`~pokedex.db.tables.%s`' % info['argument'].__name__ + if info.get('uselist', True): + class_name = u'[%s]' % class_name + yield u'(→ %s)' % class_name + if 'description' in info: + yield u'' + yield u' ' + unicode(info['description']) + ''' + if info.get('secondary') is not None: + yield u'' + yield ' Association table: ``%s``' % info['secondary'] + if 'primaryjoin' in info: + yield u'') + yield ' Join condition: ``%s``' % info['primaryjoin'] + if 'secondaryjoin' in info: + yield ' , ``%s``' % info['secondaryjoin'] + ''' + if 'order_by' in info: + yield u'' + try: + order = iter(info['order_by']) + except TypeError: + order = [info['order_by']] + yield u' ' + yield ' Ordered by: ' + u', '.join( + u'``%s``' % o for o in order) + elif info['type'] == 'association_proxy': + yield u'%s.\ **%s**:' % (cls.__name__, rel_name) + yield '``{info[attr]}`` of ``self.{info[target_collection]}``'.format( + info=info) + if 'description' in info: + yield u'' + yield u' ' + unicode(info['description']) + else: + continue yield u'' remaining_attrs.remove(rel_name) From 04b941755a19aa4a4b8929202c4733e6296fa7ee Mon Sep 17 00:00:00 2001 From: Andrew Ekstedt Date: Tue, 5 Jun 2012 14:47:42 -0700 Subject: [PATCH 07/11] Introspect relationships directly. Possibly more fragile, but this way we don't need _set_relationships. SQLAlchemy version bump for AssociationProxy.remote_attr. --- pokedex/db/tables.py | 5 ++- pokedex/doc/tabledoc.py | 90 ++++++++++++++++++----------------------- setup.py | 2 +- 3 files changed, 44 insertions(+), 53 deletions(-) diff --git a/pokedex/db/tables.py b/pokedex/db/tables.py index 58329f6..edb83e3 100644 --- a/pokedex/db/tables.py +++ b/pokedex/db/tables.py @@ -1829,7 +1829,8 @@ def add_relationships(): secondary=secondary) backref_info.update(backref_kwargs.pop('info', {})) argument.relationship_info[backref_name] = backref_info - setattr(cls, name, relationship(argument, secondary=secondary, + doc = info.get('description', None) + setattr(cls, name, relationship(argument, secondary=secondary, doc=doc, **kwargs)) def add_association_proxy(name, target_collection, attr, **kwargs): cls.relationship_info.setdefault('_order', []) @@ -1840,7 +1841,7 @@ def add_relationships(): type='association_proxy', target_collection=target_collection, attr=attr)) - setattr(cls, name, association_proxy(name, target_collection, + setattr(cls, name, association_proxy(target_collection, attr, **kwargs)) add_relationships(add_relationship=add_relationship, add_association_proxy=add_association_proxy) diff --git a/pokedex/doc/tabledoc.py b/pokedex/doc/tabledoc.py index a121152..2abc0d0 100644 --- a/pokedex/doc/tabledoc.py +++ b/pokedex/doc/tabledoc.py @@ -20,7 +20,8 @@ from sphinx.ext.autodoc import ClassLevelDocumenter from sqlalchemy import types from sqlalchemy.orm.attributes import InstrumentedAttribute -from sqlalchemy.orm import configure_mappers +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 @@ -196,55 +197,44 @@ def generate_strings(cls, remaining_attrs): @with_header(u'Relationships') def generate_relationships(cls, remaining_attrs): - order = cls.relationship_info.get('_order', []) - def sort_key((key, value)): - try: - return 0, order.index(key) - except ValueError: - return 1, key - infos = sorted(cls.relationship_info.items(), key=sort_key) - for rel_name, info in infos: - if rel_name in remaining_attrs: - info = cls.relationship_info.get(rel_name) - if info['type'] in ('relationship', 'backref'): - yield u'%s.\ **%s**' % (cls.__name__, rel_name) - class_name = u':class:`~pokedex.db.tables.%s`' % info['argument'].__name__ - if info.get('uselist', True): - class_name = u'[%s]' % class_name - yield u'(→ %s)' % class_name - if 'description' in info: - yield u'' - yield u' ' + unicode(info['description']) - ''' - if info.get('secondary') is not None: - yield u'' - yield ' Association table: ``%s``' % info['secondary'] - if 'primaryjoin' in info: - yield u'') - yield ' Join condition: ``%s``' % info['primaryjoin'] - if 'secondaryjoin' in info: - yield ' , ``%s``' % info['secondaryjoin'] - ''' - if 'order_by' in info: - yield u'' - try: - order = iter(info['order_by']) - except TypeError: - order = [info['order_by']] - yield u' ' - yield ' Ordered by: ' + u', '.join( - u'``%s``' % o for o in order) - elif info['type'] == 'association_proxy': - yield u'%s.\ **%s**:' % (cls.__name__, rel_name) - yield '``{info[attr]}`` of ``self.{info[target_collection]}``'.format( - info=info) - if 'description' in info: - yield u'' - yield u' ' + unicode(info['description']) - else: - continue - yield u'' - remaining_attrs.remove(rel_name) + for attr_name in sorted(remaining_attrs): + prop = getattr(cls, attr_name) + def isrelationship(prop): + return isinstance(prop, InstrumentedAttribute) and isinstance(prop.property, RelationshipProperty) + if isrelationship(prop): + 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 prop.__doc__: + yield u'' + yield u' ' + unicode(prop.__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) + elif isinstance(prop, AssociationProxy): + yield u'%s.\ **%s**:' % (cls.__name__, attr_name) + yield '``{prop.remote_attr.key}`` of ``self.{prop.target_collection}``'.format( + prop=prop) + '''if 'description' in info: + yield u'' + yield u' ' + unicode(info['description'])''' + else: + continue + yield u'' + remaining_attrs.remove(attr_name) @with_header(u'Undocumented') def generate_undocumented(cls, remaining_attrs): 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', From 739c6fdd7cfac98fe8533e77c0eff5f28d89a789 Mon Sep 17 00:00:00 2001 From: Andrew Ekstedt Date: Tue, 5 Jun 2012 15:09:54 -0700 Subject: [PATCH 08/11] Sort relationships by creation order. Also: - Split association proxies into their own section. - Remove relationship_info. --- pokedex/db/tables.py | 41 +++++----------------- pokedex/doc/tabledoc.py | 76 ++++++++++++++++++++++++----------------- 2 files changed, 53 insertions(+), 64 deletions(-) diff --git a/pokedex/db/tables.py b/pokedex/db/tables.py index edb83e3..b1005f4 100644 --- a/pokedex/db/tables.py +++ b/pokedex/db/tables.py @@ -77,7 +77,6 @@ class TableMetaclass(DeclarativeMeta): if hasattr(cls, '__tablename__'): mapped_classes.append(cls) cls.translation_classes = [] - cls.relationship_info = {} metadata = MetaData() TableBase = declarative_base(metadata=metadata, cls=TableSuperclass, metaclass=TableMetaclass) @@ -1806,41 +1805,19 @@ def add_relationships(): pass else: def add_relationship(name, argument, secondary=None, **kwargs): - cls.relationship_info.setdefault('_order', []) - cls.relationship_info['_order'].append(name) - cls.relationship_info[name] = info = kwargs.pop('info', {}) - info.update(kwargs) - info.update(dict( - type='relationship', - argument=argument, - secondary=secondary)) - the_backref = kwargs.get('backref') - if isinstance(the_backref, basestring): - the_backref = backref(the_backref) - if the_backref: - # XXX: This line exploits a SQLA implementation detail, - # after all relationships are converted we should make our - # own backref wrapper with known semantics. - backref_name, backref_kwargs = the_backref - - backref_info = dict( - type='backref', - argument=cls, - secondary=secondary) - backref_info.update(backref_kwargs.pop('info', {})) - argument.relationship_info[backref_name] = backref_info + info = kwargs.pop('info', {}) doc = info.get('description', None) + backref_ = kwargs.get('backref') + if type(backref_) == tuple: + backref_name, backref_kwargs = backref_ + backref_info = backref_kwargs.pop('info', {}) + backref_kwargs['doc'] = backref_info.get('doc', None) + backref_ = backref(backref_name, **backref_kwargs) + kwargs['backref'] = backref_ setattr(cls, name, relationship(argument, secondary=secondary, doc=doc, **kwargs)) def add_association_proxy(name, target_collection, attr, **kwargs): - cls.relationship_info.setdefault('_order', []) - cls.relationship_info['_order'].append(name) - cls.relationship_info[name] = info = kwargs.pop('info', {}) - info.update(kwargs) - info.update(dict( - type='association_proxy', - target_collection=target_collection, - attr=attr)) + info = kwargs.pop('info', {}) setattr(cls, name, association_proxy(target_collection, attr, **kwargs)) add_relationships(add_relationship=add_relationship, diff --git a/pokedex/doc/tabledoc.py b/pokedex/doc/tabledoc.py index 2abc0d0..ebd0f03 100644 --- a/pokedex/doc/tabledoc.py +++ b/pokedex/doc/tabledoc.py @@ -197,44 +197,55 @@ def generate_strings(cls, remaining_attrs): @with_header(u'Relationships') def generate_relationships(cls, remaining_attrs): + def isrelationship(prop): + return isinstance(prop, InstrumentedAttribute) and isinstance(prop.property, RelationshipProperty) + + relationships = [] + for attr_name in remaining_attrs: + prop = getattr(cls, attr_name) + if isrelationship(prop): + relationships.append((attr_name, prop.property)) + relationships.sort(key=lambda x: x[1]._creation_order) + + for attr_name, rel in relationships: + 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) - def isrelationship(prop): - return isinstance(prop, InstrumentedAttribute) and isinstance(prop.property, RelationshipProperty) - if isrelationship(prop): - 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 prop.__doc__: - yield u'' - yield u' ' + unicode(prop.__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) - elif isinstance(prop, AssociationProxy): + if isinstance(prop, AssociationProxy): yield u'%s.\ **%s**:' % (cls.__name__, attr_name) - yield '``{prop.remote_attr.key}`` of ``self.{prop.target_collection}``'.format( + 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'])''' - else: - continue - yield u'' - remaining_attrs.remove(attr_name) + yield u'' + remaining_attrs.remove(attr_name) + @with_header(u'Undocumented') def generate_undocumented(cls, remaining_attrs): @@ -290,7 +301,7 @@ class DexTable(PyClasslike): remaining_attrs = set(x for x in dir(cls) if not x.startswith('_')) remaining_attrs.difference_update(['metadata', 'translation_classes', - 'add_relationships', 'relationship_info', 'summary_column']) + 'add_relationships', 'summary_column']) for transl_class in cls.translation_classes: remaining_attrs.difference_update([ transl_class.relation_name, @@ -305,6 +316,7 @@ class DexTable(PyClasslike): 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)) From e07e64dfa5c6bea32da4c4ce645795cb1ff85209 Mon Sep 17 00:00:00 2001 From: Andrew Ekstedt Date: Tue, 5 Jun 2012 15:15:40 -0700 Subject: [PATCH 09/11] Display primary keys in table docs. --- pokedex/doc/tabledoc.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pokedex/doc/tabledoc.py b/pokedex/doc/tabledoc.py index ebd0f03..a24c82d 100644 --- a/pokedex/doc/tabledoc.py +++ b/pokedex/doc/tabledoc.py @@ -139,6 +139,10 @@ def generate_table_header(cls, remaining_attrs): 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: From 527b3ce05651a47964c0e10da8e03c69afe17c4c Mon Sep 17 00:00:00 2001 From: Andrew Ekstedt Date: Tue, 5 Jun 2012 18:07:07 -0700 Subject: [PATCH 10/11] Remove _add_relationships. Back out all the _add_relationships functions. This leaves tables.py almost unchanged from before their addition, except for some added documentation. --- pokedex/db/tables.py | 385 +++++++++++++++++++------------------------ 1 file changed, 168 insertions(+), 217 deletions(-) diff --git a/pokedex/db/tables.py b/pokedex/db/tables.py index b1005f4..482231f 100644 --- a/pokedex/db/tables.py +++ b/pokedex/db/tables.py @@ -30,8 +30,7 @@ classes in that module can be used to change the default language. import collections from functools import partial -from sqlalchemy import (Column, ForeignKey, MetaData, PrimaryKeyConstraint, - Table, UniqueConstraint) +from sqlalchemy import Column, ForeignKey, MetaData, PrimaryKeyConstraint, Table, UniqueConstraint from sqlalchemy.ext.declarative import declarative_base, DeclarativeMeta from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.orm import backref, relationship @@ -324,25 +323,6 @@ class Encounter(TableBase): max_level = Column(Integer, nullable=False, autoincrement=False, info=dict(description=u"The maxmum level of the encountered Pokémon")) - @staticmethod - def _add_relationships(add_relationship, add_association_proxy, **kwargs): - add_relationship('condition_value_map', EncounterConditionValueMap, - backref='encounter') - add_association_proxy('condition_values', - 'condition_value_map', 'condition_value') - add_relationship('location_area', LocationArea, - innerjoin=True, lazy='joined', - backref='encounters') - add_relationship('pokemon', Pokemon, - innerjoin=True, lazy='joined', - backref='encounters') - add_relationship('version', Version, - innerjoin=True, lazy='joined', - backref='encounters') - add_relationship('slot', EncounterSlot, - innerjoin=True, lazy='joined', - backref='encounters') - class EncounterCondition(TableBase): u"""A conditions in the game world that affects Pokémon encounters, such as time of day. """ @@ -1100,62 +1080,6 @@ class Pokemon(TableBase): is_default = Column(Boolean, nullable=False, index=True, info=dict(description=u'Set for exactly one pokemon used as the default for each species.')) - @staticmethod - def _add_relationships(add_relationship, **kwargs): - add_relationship('all_abilities', Ability, - secondary=PokemonAbility.__table__, - order_by=PokemonAbility.slot.asc(), - innerjoin=True, - backref=backref('all_pokemon', order_by=Pokemon.order.asc()), - info=dict( - description=u"All abilities the Pokémon can have, including the Hidden Ability")) - add_relationship('abilities', Ability, - secondary=PokemonAbility.__table__, - primaryjoin=and_( - Pokemon.id == PokemonAbility.pokemon_id, - PokemonAbility.is_dream == False), - innerjoin=True, - order_by=PokemonAbility.slot.asc(), - backref=backref('pokemon', order_by=Pokemon.order.asc()), - info=dict( - description=u"Abilities the Pokémon can have in the wild")) - add_relationship('dream_ability', Ability, - secondary=PokemonAbility.__table__, - primaryjoin=and_( - Pokemon.id == PokemonAbility.pokemon_id, - PokemonAbility.is_dream == True), - uselist=False, - backref=backref('dream_pokemon', order_by=Pokemon.order), - info=dict( - description=u"The Pokémon's Hidden Ability")) - add_relationship('forms', PokemonForm, - primaryjoin=Pokemon.id==PokemonForm.pokemon_id, - order_by=(PokemonForm.order.asc(), - PokemonForm.form_identifier.asc())) - add_relationship('default_form', PokemonForm, - primaryjoin=and_( - Pokemon.id==PokemonForm.pokemon_id, - PokemonForm.is_default==True), - uselist=False, - lazy='joined', - info=dict(description=u"A representative form of this pokémon")) - add_relationship('items', PokemonItem, - backref='pokemon', - info=dict( - description=u"Info about items this pokémon holds in the wild")) - add_relationship('stats', PokemonStat, - innerjoin=True, - order_by=PokemonStat.stat_id.asc(), - backref='pokemon') - add_relationship('species', PokemonSpecies, - innerjoin=True, - backref='pokemon') - add_relationship('types', Type, - secondary=PokemonType.__table__, - innerjoin=True, lazy='joined', - order_by=PokemonType.slot.asc(), - backref=backref('pokemon', order_by=Pokemon.order)) - @property def name(self): u"""Returns a name for this Pokémon, specifiying the form iff it @@ -1290,27 +1214,6 @@ class PokemonEvolution(TableBase): trade_species_id = Column(Integer, ForeignKey('pokemon_species.id'), nullable=True, info=dict(description=u"The ID of the species for which this one must be traded.")) - @staticmethod - def _add_relationships(add_relationship, **kwargs): - add_relationship('trigger', EvolutionTrigger, - innerjoin=True, lazy='joined', - backref='evolutions') - add_relationship('trigger_item', Item, - primaryjoin=PokemonEvolution.trigger_item_id==Item.id, - backref='triggered_evolutions') - add_relationship('held_item', Item, - primaryjoin=PokemonEvolution.held_item_id==Item.id, - backref='required_for_evolutions') - add_relationship('location', Location, - backref='triggered_evolutions') - add_relationship('known_move', Move, - backref='triggered_evolutions') - add_relationship('party_species', PokemonSpecies, - primaryjoin=PokemonEvolution.party_species_id==PokemonSpecies.id, - backref='triggered_evolutions') - add_relationship('trade_species', PokemonSpecies, - primaryjoin=PokemonEvolution.trade_species_id==PokemonSpecies.id) - class PokemonForm(TableBase): u"""An individual form of a Pokémon. This includes *every* variant (except color differences) of every Pokémon, regardless of how the games treat @@ -1339,18 +1242,6 @@ class PokemonForm(TableBase): """Name of this form: the form_name, if set; otherwise the species name""" return self.pokemon_name or self.species.name - @staticmethod - def _add_relationships(add_relationship, add_association_proxy, **kwargs): - add_relationship('pokemon', Pokemon, - primaryjoin=PokemonForm.pokemon_id==Pokemon.id, - innerjoin=True, lazy='joined') - add_association_proxy('species', 'pokemon', 'species') - add_relationship('version_group', VersionGroup, - innerjoin=True) - add_relationship('pokeathlon_stats', PokemonFormPokeathlonStat, - order_by=PokemonFormPokeathlonStat.pokeathlon_stat_id, - backref='pokemon_form') - create_translation_table('pokemon_form_names', PokemonForm, 'names', relation_lazy='joined', form_name = Column(Unicode(32), nullable=True, index=True, @@ -1435,26 +1326,6 @@ class PokemonMove(TableBase): {}, ) - @staticmethod - def _add_relationships(add_relationship, **kwargs): - add_relationship('pokemon', Pokemon, - innerjoin=True, lazy='joined', - backref='pokemon_moves') - add_relationship('version_group', VersionGroup, - innerjoin=True, lazy='joined') - add_relationship('machine', Machine, - primaryjoin=and_( - Machine.version_group_id==PokemonMove.version_group_id, - Machine.move_id==PokemonMove.move_id), - foreign_keys=[Machine.version_group_id, Machine.move_id], - uselist=False, - backref='pokemon_moves') - add_relationship('move', Move, - innerjoin=True, lazy='joined', - backref='pokemon_moves') - add_relationship('method', PokemonMoveMethod, - innerjoin=True, lazy='joined') - class PokemonMoveMethod(TableBase): u"""A method a move can be learned by, such as "Level up" or "Tutor". """ @@ -1530,66 +1401,6 @@ class PokemonSpecies(TableBase): forms_switchable = Column(Boolean, nullable=False, info=dict(description=u"True iff a particular individual of this species can switch beween its different forms.")) - @staticmethod - def _add_relationships(add_relationship, **kwargs): - add_relationship('parent_species', PokemonSpecies, - primaryjoin=PokemonSpecies.evolves_from_species_id==PokemonSpecies.id, - remote_side=[PokemonSpecies.id], - backref=backref('child_species', - info=dict(description=u"The species to which this one evolves")), - info=dict(description=u"The species from which this one evolves")) - add_relationship('evolutions', PokemonEvolution, - primaryjoin=PokemonSpecies.id==PokemonEvolution.evolved_species_id, - backref=backref('evolved_species', innerjoin=True, lazy='joined')) - add_relationship('flavor_text', PokemonSpeciesFlavorText, - order_by=PokemonSpeciesFlavorText.version_id.asc(), - backref='species') - add_relationship('growth_rate', GrowthRate, - innerjoin=True, - backref='evolution_chains') - add_relationship('habitat', PokemonHabitat, - backref='species') - add_relationship('color', PokemonColor, - innerjoin=True, - backref='species') - add_relationship('egg_groups', EggGroup, - secondary=PokemonEggGroup.__table__, - innerjoin=True, - order_by=PokemonEggGroup.egg_group_id.asc(), - backref=backref('species', order_by=Pokemon.order.asc())) - add_relationship('forms', PokemonForm, - secondary=Pokemon.__table__, - primaryjoin=PokemonSpecies.id==Pokemon.species_id, - secondaryjoin=Pokemon.id==PokemonForm.pokemon_id, - order_by=(PokemonForm.order.asc(), - PokemonForm.form_identifier.asc())) - add_relationship('default_form', PokemonForm, - secondary=Pokemon.__table__, - primaryjoin=and_(PokemonSpecies.id==Pokemon.species_id, - Pokemon.is_default==True), - secondaryjoin=and_(Pokemon.id==PokemonForm.pokemon_id, - PokemonForm.is_default==True), - uselist=False, - info=dict(description=u"A representative form of this species")) - add_relationship('default_pokemon', Pokemon, - primaryjoin=and_( - PokemonSpecies.id==Pokemon.species_id, - Pokemon.is_default==True), - uselist=False, lazy='joined') - add_relationship('evolution_chain', EvolutionChain, - innerjoin=True, - backref=backref('species', order_by=PokemonSpecies.id.asc())) - add_relationship('dex_numbers', PokemonDexNumber, - innerjoin=True, - order_by=PokemonDexNumber.pokedex_id.asc(), - backref='species') - add_relationship('generation', Generation, - innerjoin=True, - backref='species') - add_relationship('shape', PokemonShape, - innerjoin=True, - backref='species') - create_translation_table('pokemon_species_names', PokemonSpecies, 'names', relation_lazy='joined', name = Column(Unicode(20), nullable=True, index=True, @@ -1797,33 +1608,6 @@ class VersionGroupRegion(TableBase): ### Relationships down here, to avoid dependency ordering problems -def add_relationships(): - for cls in mapped_classes: - try: - add_relationships = cls._add_relationships - except AttributeError: - pass - else: - def add_relationship(name, argument, secondary=None, **kwargs): - info = kwargs.pop('info', {}) - doc = info.get('description', None) - backref_ = kwargs.get('backref') - if type(backref_) == tuple: - backref_name, backref_kwargs = backref_ - backref_info = backref_kwargs.pop('info', {}) - backref_kwargs['doc'] = backref_info.get('doc', None) - backref_ = backref(backref_name, **backref_kwargs) - kwargs['backref'] = backref_ - setattr(cls, name, relationship(argument, secondary=secondary, doc=doc, - **kwargs)) - def add_association_proxy(name, target_collection, attr, **kwargs): - info = kwargs.pop('info', {}) - setattr(cls, name, association_proxy(target_collection, attr, - **kwargs)) - add_relationships(add_relationship=add_relationship, - add_association_proxy=add_association_proxy) -add_relationships() - Ability.changelog = relationship(AbilityChangelog, order_by=AbilityChangelog.changed_in_version_group_id.desc(), backref=backref('ability', innerjoin=True, lazy='joined')) @@ -1865,6 +1649,21 @@ 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') +Encounter.location_area = relationship(LocationArea, + innerjoin=True, lazy='joined', + backref='encounters') +Encounter.pokemon = relationship(Pokemon, + innerjoin=True, lazy='joined', + backref='encounters') +Encounter.version = relationship(Version, + innerjoin=True, lazy='joined', + backref='encounters') +Encounter.slot = relationship(EncounterSlot, + innerjoin=True, lazy='joined', + backref='encounters') EncounterConditionValue.condition = relationship(EncounterCondition, innerjoin=True, lazy='joined', @@ -2090,9 +1889,87 @@ Pokedex.version_groups = relationship(VersionGroup, backref='pokedex') +Pokemon.all_abilities = relationship(Ability, + secondary=PokemonAbility.__table__, + order_by=PokemonAbility.slot.asc(), + innerjoin=True, + 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_( + Pokemon.id == PokemonAbility.pokemon_id, + PokemonAbility.is_dream == False, + ), + innerjoin=True, + order_by=PokemonAbility.slot.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_( + Pokemon.id == PokemonAbility.pokemon_id, + PokemonAbility.is_dream == True, + ), + uselist=False, + 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())) +Pokemon.default_form = relationship(PokemonForm, + primaryjoin=and_( + Pokemon.id==PokemonForm.pokemon_id, + PokemonForm.is_default==True), + uselist=False, lazy='joined', + doc=u"A representative form of this pokémon") +Pokemon.items = relationship(PokemonItem, + 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(), + backref='pokemon') +Pokemon.species = relationship(PokemonSpecies, + innerjoin=True, + backref='pokemon') +Pokemon.types = relationship(Type, + secondary=PokemonType.__table__, + innerjoin=True, lazy='joined', + order_by=PokemonType.slot.asc(), + backref=backref('pokemon', order_by=Pokemon.order)) + PokemonDexNumber.pokedex = relationship(Pokedex, innerjoin=True, lazy='joined') +PokemonEvolution.trigger = relationship(EvolutionTrigger, + innerjoin=True, lazy='joined', + backref='evolutions') +PokemonEvolution.trigger_item = relationship(Item, + primaryjoin=PokemonEvolution.trigger_item_id==Item.id, + backref='triggered_evolutions') +PokemonEvolution.held_item = relationship(Item, + primaryjoin=PokemonEvolution.held_item_id==Item.id, + backref='required_for_evolutions') +PokemonEvolution.location = relationship(Location, + backref='triggered_evolutions') +PokemonEvolution.known_move = relationship(Move, + backref='triggered_evolutions') +PokemonEvolution.party_species = relationship(PokemonSpecies, + primaryjoin=PokemonEvolution.party_species_id==PokemonSpecies.id, + backref='triggered_evolutions') +PokemonEvolution.trade_species = relationship(PokemonSpecies, + primaryjoin=PokemonEvolution.trade_species_id==PokemonSpecies.id) + +PokemonForm.pokemon = relationship(Pokemon, + primaryjoin=PokemonForm.pokemon_id==Pokemon.id, + innerjoin=True, lazy='joined') +PokemonForm.species = association_proxy('pokemon', 'species') +PokemonForm.version_group = relationship(VersionGroup, + innerjoin=True) +PokemonForm.pokeathlon_stats = relationship(PokemonFormPokeathlonStat, + order_by=PokemonFormPokeathlonStat.pokeathlon_stat_id, + backref='pokemon_form') PokemonFormPokeathlonStat.pokeathlon_stat = relationship(PokeathlonStat, innerjoin=True, lazy='joined') @@ -2103,10 +1980,84 @@ PokemonItem.item = relationship(Item, PokemonItem.version = relationship(Version, innerjoin=True, lazy='joined') +PokemonMove.pokemon = relationship(Pokemon, + innerjoin=True, lazy='joined', + backref='pokemon_moves') +PokemonMove.version_group = relationship(VersionGroup, + innerjoin=True, lazy='joined') +PokemonMove.machine = relationship(Machine, + primaryjoin=and_( + Machine.version_group_id==PokemonMove.version_group_id, + Machine.move_id==PokemonMove.move_id), + foreign_keys=[Machine.version_group_id, Machine.move_id], + uselist=False, + backref='pokemon_moves') +PokemonMove.move = relationship(Move, + innerjoin=True, lazy='joined', + backref='pokemon_moves') +PokemonMove.method = relationship(PokemonMoveMethod, + innerjoin=True, lazy='joined') PokemonStat.stat = relationship(Stat, innerjoin=True, lazy='joined') +PokemonSpecies.parent_species = relationship(PokemonSpecies, + primaryjoin=PokemonSpecies.evolves_from_species_id==PokemonSpecies.id, + remote_side=[PokemonSpecies.id], + 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')) +PokemonSpecies.flavor_text = relationship(PokemonSpeciesFlavorText, + order_by=PokemonSpeciesFlavorText.version_id.asc(), + backref='species') +PokemonSpecies.growth_rate = relationship(GrowthRate, + innerjoin=True, + backref='evolution_chains') +PokemonSpecies.habitat = relationship(PokemonHabitat, + backref='species') +PokemonSpecies.color = relationship(PokemonColor, + innerjoin=True, + backref='species') +PokemonSpecies.egg_groups = relationship(EggGroup, + secondary=PokemonEggGroup.__table__, + innerjoin=True, + order_by=PokemonEggGroup.egg_group_id.asc(), + backref=backref('species', order_by=Pokemon.order.asc())) +PokemonSpecies.forms = relationship(PokemonForm, + secondary=Pokemon.__table__, + primaryjoin=PokemonSpecies.id==Pokemon.species_id, + secondaryjoin=Pokemon.id==PokemonForm.pokemon_id, + order_by=(PokemonForm.order.asc(), PokemonForm.form_identifier.asc())) +PokemonSpecies.default_form = relationship(PokemonForm, + secondary=Pokemon.__table__, + primaryjoin=and_(PokemonSpecies.id==Pokemon.species_id, + Pokemon.is_default==True), + secondaryjoin=and_(Pokemon.id==PokemonForm.pokemon_id, + PokemonForm.is_default==True), + uselist=False, + doc=u"A representative form of this species") +PokemonSpecies.default_pokemon = relationship(Pokemon, + primaryjoin=and_( + PokemonSpecies.id==Pokemon.species_id, + Pokemon.is_default==True), + uselist=False, lazy='joined') +PokemonSpecies.evolution_chain = relationship(EvolutionChain, + innerjoin=True, + backref=backref('species', order_by=PokemonSpecies.id.asc())) +PokemonSpecies.dex_numbers = relationship(PokemonDexNumber, + innerjoin=True, + order_by=PokemonDexNumber.pokedex_id.asc(), + backref='species') +PokemonSpecies.generation = relationship(Generation, + innerjoin=True, + backref='species') +PokemonSpecies.shape = relationship(PokemonShape, + innerjoin=True, + backref='species') + PokemonSpeciesFlavorText.version = relationship(Version, innerjoin=True, lazy='joined') PokemonSpeciesFlavorText.language = relationship(Language, innerjoin=True, lazy='joined') From f4c51c845dbbeb3a43f3391f767a220b12be6e79 Mon Sep 17 00:00:00 2001 From: Andrew Ekstedt Date: Tue, 5 Jun 2012 18:10:37 -0700 Subject: [PATCH 11/11] Sort relationship docs alphabetically. Creation order wasn't that big of a win and didn't play nicely with backrefs. --- pokedex/doc/tabledoc.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/pokedex/doc/tabledoc.py b/pokedex/doc/tabledoc.py index a24c82d..172a454 100644 --- a/pokedex/doc/tabledoc.py +++ b/pokedex/doc/tabledoc.py @@ -204,14 +204,11 @@ def generate_relationships(cls, remaining_attrs): def isrelationship(prop): return isinstance(prop, InstrumentedAttribute) and isinstance(prop.property, RelationshipProperty) - relationships = [] - for attr_name in remaining_attrs: + for attr_name in sorted(remaining_attrs): prop = getattr(cls, attr_name) - if isrelationship(prop): - relationships.append((attr_name, prop.property)) - relationships.sort(key=lambda x: x[1]._creation_order) - - for attr_name, rel in relationships: + 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: