Commits

Vinay Sajip  committed f0153f3

Initial commit (from svn r534).

  • Participants

Comments (0)

Files changed (93)

+Initial developers
+- Daniel Haus
+- Gaëtan de Menten
+- Jonathan LaCour
+
+Website design and maintenance
+- Daniel Haus
+
+Contributors (listed alphabetically)
+- Alex Bodnaru
+- Alexandre da Silva
+- Alice McGregor
+- Ants Aasma
+- Ben Bangert
+- Diez B. Roggisch
+- Graham Higgins
+- Jason R. Coombs
+- Johannes Janssen
+- Neil Blakey-Milner
+- Paul Johnston
+- Remi Jolin
+- Robin Munn
+- Stéphane Klein
+- Valentin Lab
+- some anonymous contributions I couldn't trace to someone in particular
+0.8.0
+
+New features:
+- Better support for Python 3.x by running 2to3 within setup (patch by
+  "foogod", closes #110).
+- Added more tests for relationships to forward-declared entities.
+
+Changes:
+- Dropped support for python 2.3, SQLAlchemy 0.4 and deprecated stuff from
+  Elixir 0.7
+
+Bug fixes:
+- Fixed a few tests to work on SA 0.6.x
+- Fixed bad foreign key constraint generated for classes inheriting from a
+  class with multiple primary keys when using the "multi" inheritance.
+  Patch from & closes #114.
+
+0.7.1 - 2009-11-16
+
+New features:
+- Entities can now be declared "abstract" so that they do not create a table,
+  etc... This allows, among others, an entity to inherit from multiple abstract
+  classes (patch from Stéphane Klein, closes #89).
+- Added a new collection which can resolve entities relative to the current
+  entity, for example "..other_module.Class" (based on patches from Johannes
+  Janssen, closes #93).
+- Added a new entity option "resolve_root", which allows one to specify the
+  root module where your entities are defined. The string will be prepended
+  to all "absolute" entity paths. It can also be used on a per-entity basis.
+  This feature is based on a patch from Johannes Janssen, see #93.
+
+Changes:
+- using_options_defaults and using_table_options statements can be used several
+  times within the same class (closes #70).
+
+Bug fixes:
+- Fixed custom base classes and versioned extension when used with zope
+  interfaces (closes #98, patch from Valentin Lab)
+- Fixed having relationships in custom base classes (based on patch
+  by Stéphane Klein)
+
+0.7.0 - 2009-10-01
+
+Please see http://elixir.ematia.de/trac/wiki/Migrate06to07 for detailed
+upgrade notes. If you are upgrading an application with existing data from an
+earlier version of Elixir, you are STRONGLY ADVISED to read them!
+
+New features:
+- Added a new statement 'using_options_defaults' which can be used
+  on a custom base class to set default values for the options of all its
+  subclasses. For example, this makes it possible to have all classes
+  inheriting from your custom base class use some custom options without
+  having to set it individually on each entity, nor modify
+  options.options_defaults.
+- The local_colname and remote_colname arguments on ManyToMany relationships
+  can now also be used to set custom names for the ManyToMany table columns.
+  This effectively replace the column_format on ManyToMany relationships which
+  is now deprecated. Change based on a patch from Diez B. Roggisch.
+- Added (or rather fixed and documented) a "table" argument on ManyToMany
+  relationships to allow using a manually-defined Table (closes #44).
+- Added a "schema" argument on ManyToMany relationships to be able to create
+  the ManyToMany table in a custom schema and not necessarily the same schema
+  as the table of the "source" entity (patch from Diez B. Roggisch).
+- Added a "table_kwargs" argument on ManyToMany relationships to pass any
+  extra keyword arguments to the underlying Table object (closes #94).
+- Added a new "target_column" argument on ManyToOne relationships so that you
+  can target unique but non-primary key columns. At the moment, this only works
+  if the target column is declared before the ManyToOne (see ticket #26).
+- Added new "column_names" argument to the acts_as_versioned extension,
+  allowing to specify custom column names (inspired by a patch by
+  Alex Bodnaru).
+- Made M2MCOL_NAMEFORMAT option accept a callable, so that more complex naming
+  generation can be used if so desired.
+- Added two new extensions (contributed by Alex Bodnaru)
+  - "perform_ddl" allows to execute one or several DDL statements upon table
+    creation.
+  - "preload_data" allows to insert data into the entity table just after it
+    has been created.
+- EntityCollection gained a working extend method
+
+Changes:
+- Moved class instrumentation to a separate function: instrument_class
+- Moved all methods of the "Entity" base class, to the "EntityBase" class, so
+  that people who want to provide their own base class but don't want to loose
+  all the methods provided by "Entity" can simply inherit from EntityBase
+  instead of copy-pasting the code for all its methods.
+- Renamed remote_side and local_side ManyToMany arguments to remote_colname and
+  local_colname respectively to not collide with the remote_side argument
+  provided by SA (it doesn't make much sense on ManyToMany relationships but
+  still).
+- Delete Elixir properties in the setup phase rather than as soon as they are
+  attached to their class. It makes it possible to access them or refer to them
+  after the class is defined (but before setup).
+- Deprecated act_as_list extension in favor of SQLAlchemy's orderinglist
+  extension (closes #53).
+- Made manually defined mapper options take precedence over Elixir-generated
+  ones. Not very useful yet since most are expecting Column objects.
+- Default table_options (defined in options_defaults['table_options']) are now
+  also used for ManyToMany tables (closes #94).
+- Provide our own Session.mapper equivalent to avoid SQLAlchemy 0.5.5+
+  deprecation warning. This mapper autosave object instances on __init__ unless
+  save_on_init=False is passed as a mapper argument (closes #92).
+
+Bug fixes:
+- Fixed Elixir to work with the future SQLAlchemy 0.6 (trunk as of 6377).
+- Changed the pattern used by default to generate column names for
+  self-referencial ManyToMany relationships so that the meaning of
+  bidirectional self-referential relationships does not depend on the order of
+  declaration of each side (closes #69). See upgrade notes for details.
+- Changed slightly the pattern used to generate the name of the table for
+  bidirectional self-referential ManyToMany relationships so that it doesn't
+  depend on the order of declaration of each side (closes #19).
+  See upgrade notes for details.
+- default EntityCollection raise an exception instead of returning None when
+  trying to resolve an inexisting Entity from outside of another entity (for
+  example through EntityCollection.__getattr__
+- Fixed the case where you specify both "primaryjoin" and "colname" arguments
+  (useless in this case, but harmless) on a ManyToOne relationship of an
+  autoloaded entity.
+- Fixed bug which broke the "identity" (Entity) option
+- Fixed documentation about local_side and remote_side arguments being
+  required if the entity containing the relationship is autoloaded, when it is
+  only required if the relationship is self-referencial, and primaryjoin or
+  secondaryjoin as not been specified manually.
+- Added missing documentation for the "filter" argument on OneToMany
+  relationships.
+- Fixed the act_as_list extension's move_to_bottom method to work on MySQL
+  (closes #34).
+- Fixed event methods not being called when they are defined on a parent class.
+  (introduced in release 0.5.0).
+- Added workaround for an odd mod_python behavior (class.__module__ returns a
+  weird name which is not in sys.modules).
+- tablename, remote_colname, local_colname, schema and table_kwargs can now be
+  defined on either side of a ManyToMany relationship and will propagate to
+  the other side if that other side doesn't specify anything for that argument.
+  Also added an assertion to catch the case where the same/mirror
+  argument is specified on both sides but with different values.
+- Fixed filter argument on OneToMany relationship leaking the filter to the
+  unfiltered relationship.
+- Fixed encrypted extension to not encrypt several times an instance attributes
+  when that instance is flushed several times before being expunged from the
+  session.
+- Fixed using to_dict with a ManyToOne relationship in the "deep" set and that
+  relationship being None in the entity being converted.
+
+0.6.1 - 2008-08-18
+
+New features:
+- Allow ManyToOne relationships to use manually created fields as their
+  "supporting column". This means that the columns can be customized without
+  resorting to using the ugly "column_kwargs" (patch from Jason R. Coombs,
+  closes #39).
+- Extra args and kwargs to Synonym and ColumnProperty are forwarded to their
+  underlying constructs. This allows for example deferred ColumnProperties.
+- Added a more helpful assertion message when inverse relationship types don't
+  match.
+
+Changes:
+- Removed support for the deprecated "with_fields" syntax
+- Entity.__init__ calls Entity.set instead of duplicating its functionality
+
+Bug fixes:
+- Fixed the "Target resolves to several entities" exception message to actually
+  include the target name.
+- Renamed the on_reconstitute method decorator to reconstructor, to track the
+  corresponding change in SA's trunk.
+
+0.6.0 - 2008-07-18
+
+Please see http://elixir.ematia.de/trac/wiki/Migrate05to06 for detailed
+upgrade notes.
+
+New features:
+- Fields in a custom base class are added to all their children.
+- Added two new methods on the base entity: from_dict and to_dict, which can
+  be used to create (or output) a whole hierarchy of instances from (to) a
+  simple JSON-like dictionary notation (patch from Paul Johnston,
+  closes ticket #40).
+- Added experimental (!) support for concrete table inheritance (both
+  polymorphic or not). Concrete polymorphic inheritance requires SQLAlchemy
+  0.4.5 or later.
+- Moved the "entity to string" mapping and resolving code to the (newly
+  created) EntityCollection class (which stores lists of entities). This
+  allows one to provide a custom mapping method if needed. The default class
+  also overrides the __getattr__ method, providing and handy way to get at your
+  entities. See http://elixir.ematia.de/trac/browser/elixir/tags/0.6.0/tests/test_collections#L58
+- Added new "identity" option which can be used to set a custom polymorphic
+  identity for an entity. It also accepts a callable so that you can generate
+  the identity name automatically from the class itself.
+- Added __setattr__ method on Metaclass so that you can add properties
+  slightly more easily after class definition (but *before* setup_all):
+    class A(Entity):
+        pass
+    A.name = Field(String(32))
+- Added add_table_column, add_mapper_property and add_mapper_extension helper
+  methods in EntityBuilders.
+- Added full_tablename property on EntityDescriptor (includes schema name if
+  any).
+- Added on_reconstitute event/method decorator. Only works with SA 0.5.
+- Added support for viewonly relationships (OneToMany and OneToOne).
+- Added support for filtered OneToMany relationships. Produce viewonly
+  relations. See http://elixir.ematia.de/trac/browser/elixir/tags/0.6.0/tests/test_o2m.py
+  for an example.
+- Added support for callables for some arguments on relationships: primaryjoin,
+  secondaryjoin and remote_side. It means those can be evaluated at setup time
+  (when tables and their columns already exist) instead of definition time.
+
+Changes:
+- Default "target entity resolving code" changed slightly. It now uses a global
+  collection keyed on the entity name. This means that entities can refer to
+  other entities in a different module simply with the target entity name
+  instead of its full path. The full path is only required when there is an
+  ambiguity (ie when there are two classes with the same name in two different
+  modules). Closes #9.
+- Added support for SQLAlchemy 0.5, and dropped support for version 0.3 and
+  earlier.
+- The default session (elixir.session) uses sessionmaker() instead of
+  create_session(), which means it has now the following characteristics:
+    * autoflush=True
+    * autocommit=False (with SA 0.5 -- equivalent to transactional=True with
+      SA 0.4)
+    * autoexpire=True (with SA 0.5).
+- removed objectstore and other SA 0.3 (or older) support code.
+
+Bug fixes:
+- Fixed multi-table inheritance when using a non default schema (closes #38)
+- Fixed ManyToOne relationships using 'key' kwarg in their column_kwargs
+  (patch by Jason R. Coombs)
+- Fixed inheritance with autoloaded entities: when using autoload, we
+  shouldn't try to add columns to the table (closes tickets #41 and #43).
+- Fixed acts_as_list extension with autoloaded entities (patch from maqr,
+  closes ticket #52).
+- Fixed ColumnProperty to work with latest version of SQLAlchemy (O.4.5 and
+  later)
+- Fixed ManyToMany relationships when not using the default schema
+  (patch from Diez B. Roggisch, closes ticket #48)
+
+Misc:
+- Added AUTHORS list. If you are missing from this list, don't hesitate to
+  contact me.
+
+0.5.2 - 2008-03-28
+
+New features:
+- Added an optional `check_concurrency` keyword argument to the versioning
+  extension, supporting the usage of SQLAlchemy's built-in optimistic
+  concurrency check.
+
+Changes:
+- Made Elixir python 2.3 compatible again (based on patches from
+  Jason R. Coombs)
+
+Bug fixes:
+- Fixed act_as_list extension to work with DBMS that require subselects to be
+  aliased (patch by Alice McGregor)
+- Fixed the versioning extension so that the history table is updated within
+  the current transaction (patch from and closes ticket #35).
+
+0.5.1 - 2008-02-07
+
+New features:
+- Added a new elixir plugin for managing entities as (ordered) lists.
+- Added a `column_format` keyword argument to `ManyToMany` which can be used
+  to specify an alternate format string for column names in the mapping table.
+- Added support for custom base classes which inherit from another class (ie
+  not directly from object).
+- Added an alternate (nicer) syntax to define synonym properties.  This syntax
+  has a more limited scope, except that it can refer to properties defined in
+  a parent entity. This is based on a patch from Alexandre da Silva.
+
+Changes:
+- Added check so that using an inexisting column in an order_by or other
+  column-name based argument raises an exception.
+- The polymorphic_identity kwarg in using_mapper_options is not overriden
+  anymore by the one generated by Elixir (patch from Ben Bangert).
+- Moved the format of the multi-table inheritance column to a constant in
+  options (so that it can be changed globally).
+- The foreign key constraint of the column in a multi-table inheritance is
+  configured with a cascade rule.
+
+Bug fixes:
+- A child entity doesn't inherit anymore its parent entity statements (such as
+  options) if it doesn't use any statement itself.
+- Made inheritance work for custom base classes (closes #25).
+- Fixed the inverse relationship matching when the inverse relationship is
+  defined in a parent Entity (thanks to Alexandre da Silva).
+- Fixed bug in setup_entities (it always used the global entity list and not
+  the list given as argument).
+- Fixed the versioning extension not appropriately handling versioned
+  entities with onupdate events (patch from Remi Jolin, closes #29).
+- Fixed videostore example (patch from Jason R. Coombs, closes #31).
+
+0.5.0 - 2007-12-08
+
+Please see http://elixir.ematia.de/trac/wiki/Migrate04to05 for detailed
+upgrade notes.
+
+New features:
+- Added set method on base Entity (set attributes using kwargs)
+
+Changes:
+- Autosetup defaults to False ! (please look at those upgrade notes!)
+- Polymorphic defaults to True (inheritance is polymorphic by default).
+- Removed one of the autosetup triggers altogether: there is no "fake" mapper
+  registered in SQLAlchemy's mapper_registry anymore, so if you try to
+  access the class mapper directly (not through the 'mapper' attribute on
+  the class), before the setup phase happens, it won't work. This was done
+  because of a change in SQLAlchemy trunk (future SA 0.4.2) which broke that
+  piece of code (and prevented to use autosetup at all). Since that code
+  was a hack in the first place, instead of doing some even uglier hackery,
+  I got rid of it altogether.
+- Moved some format strings to constants in options, so that one can change
+  them if he wants to.
+- Allow overriding primary_key columns on autoloaded entities (closes tickets
+  #20 and #22)
+- Columns created by ManyToOne relationships can now optionally (through
+  column_kwargs) *not* create an index (ie it's not harcoded anymore).
+  Suggestion by Jason R. Coombs.
+
+Bug fixes:
+- Fixed a nasty bug which prevented inheritance to work correctly when using
+  the attribute syntax in many cases.
+- Fixed associable extension to work with SQLAlchemy trunk (future 0.4.2).
+- Fixed an incompatibility with zope.interfaces.
+- Tweaked the initialization sequence again (in fact revert an older change)
+  which prevented to reuse class properties of one class in other (subsequent)
+  classes.
+- Fixed our tests to work with SA trunk (future 0.4.2) (unicode data + use of
+  deprecated attributes)
+
+0.4.0 - 2007-10-29
+
+Please see http://elixir.ematia.de/trac/wiki/Migrate03to04 for detailed
+upgrade notes.
+
+New features:
+- Implemented a new syntax to declare fields and relationships, much closer to
+  what is found in other Python ORM's. The with_fields syntax is now
+  deprecated in favor of a that new syntax. The old statement based (has_field
+  et al.) syntax is still available though (and will remain so for quite some
+  time). This was done with help from a patch by Adam Gomaa.
+- Implemented polymorphic single-table inheritance as well as polymorphic and
+  non-polymorphic multi-table (aka joined table) inheritance.
+- Added ext sub-package for additional Elixir statements.
+- Added associable extension for generating polymorphic associations with
+  Elixir statements.
+- Added versioning extension to keep track to all changes to your entities by
+  storing them in a secondary table.
+- Added encryption extenstion to encrypt/decrypt some fields data on the fly
+  when writing to/reading from the database.
+- Added support for synonym properties.
+- Added shortcut syntax to define column_properties.
+- Added a .query attribute on all entities. The old .query() syntax is still
+  available.
+- Added support to add any SQLAlchemy property on your mapper, through the
+  GenericProperty class (as well as the has_property statement). These can
+  work even if they rely on the entity columns (an thus need them to be
+  defined before the property can be declared). See tests/test_properties.py
+  for examples.
+- Added support for "manual session management" (ie you can now define an
+  entity with "using_options(session=None)" and it won't use any
+  SessionContext extension, nor receive the "query" attribute.
+- Made the statement system more powerfull.
+
+Changes:
+- The setup time was changed. That is the table and mapper are not created as
+  soon as the class is defined, but rather when first used, or when explicitly
+  calling the setup function (recommended). This also allowed us to reorder
+  the setup process and allows, among others to use a ManyToOne-generated
+  column as a primary key, to use unique constraints on those columns, to
+  order by those columns and so on...
+- Made Elixir work with both SQLAlchemy 0.4 and 0.3.10 (with help from a patch
+  by Ants Aasma).
+- Moved away from assign_mapper, now all assign_mapper-provided methods are on
+  the Entity class. Now, if people don't like them, they have the option to
+  simply provide another base class.
+- Default objectstore is now a ScopedSession when working on SQLAlchemy 0.4.
+  It means that it's not wrapped in an Objectstore object at all. This means,
+  that depending on the version of SA you are using, you'll get a slightly
+  different behavior.
+- Relationships to other classes can now also be defined using the classes
+  themselves in addition to the class namees. Obviously, this doesn't work for
+  forward references.
+- Classes defined inside a function can now have relationships to each other.
+- Added default __init__ method on entities so that subclasses can override it
+  and still have the "set attribute by keyword" behavior by calling super()
+- Added "through" and "via" keyword arguments on relationships and has_field
+  statement, to proxy values through relationships (uses association_proxy)
+- Made EntityMeta public, so that people can actually define their own base
+  class.
+- Changed the order of relationship kwargs processing so that computed kwargs
+  can be overridden by kwargs manually passed to the statement. This should
+  only be used if you know what you are doing.
+- Added onupdate kwarg to BelongsTo relationships for consistency with the
+  ondelete kwarg
+- Added ondelete and onupdate kwargs for use with has_and_belongs_to_many
+  to apply on delete clauses to foreign key constraints on the m2m table.
+- Columns of the intermediary table of an has_and_belongs_to_many relationship
+  are now marked as primary keys.
+- Reworked how entities look for primary keys on related entities. This
+  enables one "normal" entity (fully defined in Elixir) to refer to an entity
+  which is autoloaded.
+- Added translation (from column name to column object) of the primary_key
+  mapper option so that it can actually be used. This allows to have entities
+  without any primary key defined at the table level.
+- Added the possibility to give a custom name for ManyToOne constraints
+  (patch from and closes ticket #16)
+- Dropped support for the old threadlocal SA extension (which doesn't even exist
+  anymore in SA 0.4)
+
+Bug fixes:
+- Reworked/cleaned tests so that they don't leak stuff to other tests (both at
+  the method level and module level) anymore. Uses nosetest's module level
+  fixture.
+- Fixed relationships to entities whose primary_key field has been defined
+  with a "key" argument (based on a patch by Paul Johnston).
+- Fixed some buggy tests.
+- Fixed relationships to tables using a schema (patch by Neil Blakey-Milner)
+- Made inverse relationships use backrefs. This fixes the "bidirectional
+  coherency" problem some people had before doing a flush. (based on a patch
+  from Remi Jolin).
+
+0.3.0 - 2007-03-27
+- Made the provided metadata not threadlocal. This could break things for you
+  in some rare case. Please see the (newly created) FAQ file for details about
+  this.
+- Added support for autoloading/reflecting databases with
+  has_and_belongs_to_many relationships. The tablename argument is now
+  optional, but still recommended, otherwise you'll have to use the same exact
+  name for your intermediary table than the one generated. You also _have to_
+  specify at least one of either local_side or remote_side argument.
+- Added support for the "version_id_col" option on entities. This option adds
+  a column to the table which will be used to prevent concurrent modifications
+  on any row of the entity's table (i.e. it will raise an error if it happens).
+- Made the colname argument optional for belongs_to relationships in
+  autoloaded entities. It is only required to specify it if you have several
+  belongs_to relationships between two entities/tables.
+- Applied patch from "Wavy" so that columns of a table are in the same order
+  as they were declared (this only works for the has_field statement).
+- Applied patch from Isaac Csandl to add an "ondelete" argument to
+  belongs_to relationships. The content of that argument is forwarded to the
+  foreign key constraint.
+- Foreign key names generated by belongs_to relationships use column names
+  instead of relation names in case we have a relation with the same name
+  defined in several entities inheriting from the same entity using single-
+  table inheritance (and we set a custom column name in one of them to avoid
+  a column-name conflict).
+- Using invalid options on entities will now raise an exception
+- Added __version__
+- Use an explicit metaclass for entities, so that people can define their own
+  base class.
+- Changed the approach to reflecting/autoloading belongs_to relationships.
+  This shouldn't change anything to how it's used but allowed me to factor
+  some code with has_and_belongs_to_many relationships.
+- The tablename option can now be given a callable so that people can provide
+  their own function to get the table name for an entity. The tablename option
+  can now also be set globally (using the options_defaults dictionary). Of
+  course, this only makes sense for the callable usecase.
+
+- Fixed bug preventing having entities without any statement.
+- Fixed documentation for belongs_to relationships (the arguemnt is "required",
+  not "nullable").
+- Fixed typo which broke the use_alter argument on belongs_to relationships.
+- Fixed inheritance unit test to pass SQLAlchemy type check on relations
+  (introduced in SA 0.3.6)
+- Fixed wrong field length in autoload test (it was not noticeable with sqlite).
+- Actually make the code python 2.3 compatible (Robin's patch was based on
+  0.1.0 while I had introduced more decorators in the trunk in the mean time).
+
+- Made some PEP8 tweaks in many places. Used the pep8 script provided with
+  Cheesecake.
+- Some cleanup/useless code removal
+
+0.2.0 - 2007-02-28
+- Applied patch from Robin Munn to make the code python 2.3 compatible
+- Per a suggestion on the mailing list, look at the calling stack frame to
+  ensure that we apply statements to the proper class.  We now attach the
+  statement list to the class itself, rather than attaching it to a global
+  list that is neither threadsafe, nor safe when doing nested class
+  definition.  Also added a test to validate that this works.
+- implemented singletable non-polymorphic inheritance
+- added support to pass non-keyword arguments to tables. You just pass
+  them to the using_table_options statement and they will be forwarded to the
+  table along with the keyword arguments. This can be used to set table
+  constraints.
+- added support for deferred columns (use the "deferred" keyword argument on
+  fields)
+- added a "required" keyword argument on fields and BelongsTo
+  relationships. This is the opposite of the "nullable" SA argument.
+- added a "column_kwargs" keyword argument to BelongsTo relationships
+  to forward any keyword argument directly to the SA Column.
+- added support for the use_alter and constraint_kwargs kwargs on BelongsTo
+  relationships (forwarded to SA ForeignKeyConstraint).
+    -> removed the systematic use_alter on BelongsTo relations since it
+       can now be specified only when needed.
+    -> removed it from HasAndBelongsToMany relations, since I think a
+       circular foreign key dependency can't happen with those relations.
+- fixed foreign key names on MySQL (and possibly other) databases by
+  making sure the generated name is unique for the whole database, and not
+  only for the table on which it applies.
+- corrected some docstrings
+
+0.1.0 - 2007-02-12
+initial release
+
+This is the MIT license: http://www.opensource.org/licenses/mit-license.php
+
+Copyright (c) 2007, 2008 Jonathan LaCour, Daniel Haus, and Gaetan de Menten.
+and contributors. SQLAlchemy is a trademark of Michael Bayer.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+exclude release.howto
+-----
+About
+-----
+
+Elixir is a declarative layer on top of the `SQLAlchemy library
+<http://www.sqlalchemy.org/>`_. It is a fairly thin wrapper, which provides
+the ability to create simple Python classes that map directly to relational
+database tables (this pattern is often referred to as the Active Record design
+pattern), providing many of the benefits of traditional databases
+without losing the convenience of Python objects.
+
+Elixir is intended to replace the ActiveMapper SQLAlchemy extension, and the
+TurboEntity project but does not intend to replace SQLAlchemy's core features,
+and instead focuses on providing a simpler syntax for defining model objects
+when you do not need the full expressiveness of SQLAlchemy's manual mapper
+definitions.
+
+METATODO: move all of these to trac tickets!
+
+TODO
+====
+
+Website/doc related
+-------------------
+
+- extend the tutorial !!! (but we shouldn't duplicate the FAQ though)
+    - default values
+    - required fields
+    - multiple files (modules) models
+    - demonstrate common raltionships options (order_by, ...)
+    - inverse relationships matching (with a link to BehindTheScene)
+
+    ? autoload
+    ? many2many with intermediary object (extra fields in table) -> FAQ?
+      -> tags with created_by
+
+    ? transactions
+    ? table constraints
+    ? multi-thread
+    ? multi-database
+    ? deferred fields
+
+Code related
+------------
+
+- Besides, the current system also has another case I don't like: if the user
+  specifies an inverse (on one or both sides) but also set a table name on one
+  side (or two different table names), it will consider the relation as being
+  different even though the user explicitly told it was the same. This should
+  not happen. The system should rather throw an exception in that case. But
+  this last part should be easilty fixable, I think (it'a a matter of tweaking
+  the is_inverse method of the HasAndBelongsToMany class)...
+
+
+IDEAS
+=====
+
+The following items might or might not get implemented and probably need to be
+discussed before doing anything.
+
+- check that relations/fields setup with through kwargs endup with correct type
+  and cardinality (using a finalize method in HasField and Relationship)
+
+- provide optional __init__ kwargs validation (cfr. assignmapper) (as a
+  recipe)
+
+- provide optional "runtime setattr" validation (as a recipe?)
+  http://www.sqlalchemy.org/trac/attachment/ticket/547
+  * the provided "_find_class_descriptor" seem overly complex though
+  * the (unlikely?) case where a parent who is not inheriting from XFBase (or
+    Entity) defines a property will probably fail because of line 16.
+
+- add __revision__ (+ svn property) to all elixir files?
+
+- support all mapper arguments which take column arguments in a generic way
+
+- instead of linking the descriptor in the entity (cls._descriptor) we could
+  do it externally, like the mappers in SA. This would solve some of the
+  ugliness we have in the current implementation (mostly in target).
+
+- add polymorphic references
+  For the syntax, I'd like to have either belongs_to relationships
+  without of_kind argument or with a special "constant" argument like:
+    belongs_to('rel', of_kind=ANY_KIND)
+  Maybe this would be better suited on SA side or in an addon to Elixir and
+  not in the main lib?
+  The implementation would be a bit similar to what Jonathan does at:
+  http://cleverdevil.org/computing/52/making-a-statement-with-elixir
+  we would "just" need to generalize the target_id to support multi-column-pk
+  and I think we would be good to go for belongs_to relationships
+
+  The problem is to keep the referencial integrity of the database. Obviously
+  it's not possible to do that if people modify their data through SQL
+  directly (or at least it would involve complicated triggers on the DB and
+  that wouldn't be portable. It should be possible to do it through the ORM
+  though.
+  See:
+  http://www.sqlalchemy.org/trac/browser/sqlalchemy/trunk/examples/poly_assoc
+
+- investigate whether it would be possible to do a generic acts_as(xxx)
+  instead of the acts_as_taggable Jonathan demonstrated
+
+- support custom selectable on relationships, as is done at:
+
+  http://spyced.blogspot.com/2007/01/why-sqlalchemy-impresses-me.html
+  ie relations using any "selectable" instead of the normal object table.
+
+    mapper(User, users,
+        properties={
+           'orders': relation(mapper(Order, orders), order_by=orders.c.id),
+           'max_order': relation(mapper(Order, max_orders, non_primary=True),
+                                 uselist=False, viewonly=True),
+        })
+
+    would be something like:
+
+    # Order class must be defined before
+    max_orders_by_user = select([func.max(Order.c.order_id).label('order_id')],
+                                group_by=[Order.c.user_id]).alias('max_orders_by_user')
+    max_orders = Order.table.select(Order.c.order_id==max_orders_by_user.c.order_id).alias('max_orders')
+
+    class User(Entity):
+        has_one('max_order', of_kind='Order', selectable=max_orders)
+
+- get some inspiration from Django
+    symmetrical on M2M
+

File elixir/__init__.py

+'''
+Elixir package
+
+A declarative layer on top of the `SQLAlchemy library
+<http://www.sqlalchemy.org/>`_. It is a fairly thin wrapper, which provides
+the ability to create simple Python classes that map directly to relational
+database tables (this pattern is often referred to as the Active Record design
+pattern), providing many of the benefits of traditional databases
+without losing the convenience of Python objects.
+
+Elixir is intended to replace the ActiveMapper SQLAlchemy extension, and the
+TurboEntity project but does not intend to replace SQLAlchemy's core features,
+and instead focuses on providing a simpler syntax for defining model objects
+when you do not need the full expressiveness of SQLAlchemy's manual mapper
+definitions.
+'''
+
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+import sqlalchemy
+from sqlalchemy.types import *
+
+from elixir.options import using_options, using_table_options, \
+                           using_mapper_options, options_defaults, \
+                           using_options_defaults
+from elixir.entity import Entity, EntityBase, EntityMeta, EntityDescriptor, \
+                          setup_entities, cleanup_entities
+from elixir.fields import has_field, Field
+from elixir.relationships import belongs_to, has_one, has_many, \
+                                 has_and_belongs_to_many, \
+                                 ManyToOne, OneToOne, OneToMany, ManyToMany
+from elixir.properties import has_property, GenericProperty, ColumnProperty, \
+                              Synonym
+from elixir.statements import Statement
+from elixir.collection import EntityCollection, GlobalEntityCollection
+
+
+__version__ = '0.8.0dev'
+
+__all__ = ['Entity', 'EntityBase', 'EntityMeta', 'EntityCollection',
+           'entities',
+           'Field', 'has_field',
+           'has_property', 'GenericProperty', 'ColumnProperty', 'Synonym',
+           'belongs_to', 'has_one', 'has_many', 'has_and_belongs_to_many',
+           'ManyToOne', 'OneToOne', 'OneToMany', 'ManyToMany',
+           'using_options', 'using_table_options', 'using_mapper_options',
+           'options_defaults', 'using_options_defaults',
+           'metadata', 'session',
+           'create_all', 'drop_all',
+           'setup_all', 'cleanup_all',
+           'setup_entities', 'cleanup_entities'] + \
+           sqlalchemy.types.__all__
+
+__doc_all__ = ['create_all', 'drop_all',
+               'setup_all', 'cleanup_all',
+               'metadata', 'session']
+
+# default session
+session = sqlalchemy.orm.scoped_session(sqlalchemy.orm.sessionmaker())
+
+# default metadata
+metadata = sqlalchemy.MetaData()
+
+metadatas = set()
+
+# default entity collection
+entities = GlobalEntityCollection()
+
+
+def create_all(*args, **kwargs):
+    '''Create the necessary tables for all declared entities'''
+    for md in metadatas:
+        md.create_all(*args, **kwargs)
+
+
+def drop_all(*args, **kwargs):
+    '''Drop tables for all declared entities'''
+    for md in metadatas:
+        md.drop_all(*args, **kwargs)
+
+
+def setup_all(create_tables=False, *args, **kwargs):
+    '''Setup the table and mapper of all entities in the default entity
+    collection.
+    '''
+    setup_entities(entities)
+
+    # issue the "CREATE" SQL statements
+    if create_tables:
+        create_all(*args, **kwargs)
+
+
+def cleanup_all(drop_tables=False, *args, **kwargs):
+    '''Clear all mappers, clear the session, and clear all metadatas.
+    Optionally drops the tables.
+    '''
+    session.close()
+
+    cleanup_entities(entities)
+
+    sqlalchemy.orm.clear_mappers()
+    entities.clear()
+
+    if drop_tables:
+        drop_all(*args, **kwargs)
+
+    for md in metadatas:
+        md.clear()
+    metadatas.clear()
+
+

File elixir/collection.py

+'''
+Default entity collection implementation
+'''
+import sys
+import re
+
+class BaseCollection(list):
+    def __init__(self, entities=None):
+        list.__init__(self)
+        if entities is not None:
+            self.extend(entities)
+
+    def extend(self, entities):
+        for e in entities:
+            self.append(e)
+
+    def clear(self):
+        del self[:]
+
+    def resolve_absolute(self, key, full_path, entity=None, root=None):
+        if root is None:
+            root = entity._descriptor.resolve_root
+        if root:
+            full_path = '%s.%s' % (root, full_path)
+        module_path, classname = full_path.rsplit('.', 1)
+        module = sys.modules[module_path]
+        res = getattr(module, classname, None)
+        if res is None:
+            if entity is not None:
+                raise Exception("Couldn't resolve target '%s' <%s> in '%s'!"
+                                % (key, full_path, entity.__name__))
+            else:
+                raise Exception("Couldn't resolve target '%s' <%s>!"
+                                % (key, full_path))
+        return res
+
+    def __getattr__(self, key):
+        return self.resolve(key)
+
+# default entity collection
+class GlobalEntityCollection(BaseCollection):
+    def __init__(self, entities=None):
+        # _entities is a dict of entities keyed on their name.
+        self._entities = {}
+        super(GlobalEntityCollection, self).__init__(entities)
+
+    def append(self, entity):
+        '''
+        Add an entity to the collection.
+        '''
+        super(EntityCollection, self).append(entity)
+
+        existing_entities = self._entities.setdefault(entity.__name__, [])
+        existing_entities.append(entity)
+
+    def resolve(self, key, entity=None):
+        '''
+        Resolve a key to an Entity. The optional `entity` argument is the
+        "source" entity when resolving relationship targets.
+        '''
+        # Do we have a fully qualified entity name?
+        if '.' in key:
+            return self.resolve_absolute(key, key, entity)
+        else:
+            # Otherwise we look in the entities of this collection
+            res = self._entities.get(key, None)
+            if res is None:
+                if entity:
+                    raise Exception("Couldn't resolve target '%s' in '%s'"
+                                    % (key, entity.__name__))
+                else:
+                    raise Exception("This collection does not contain any "
+                                    "entity corresponding to the key '%s'!"
+                                    % key)
+            elif len(res) > 1:
+                raise Exception("'%s' resolves to several entities, you should"
+                                " use the full path (including the full module"
+                                " name) to that entity." % key)
+            else:
+                return res[0]
+
+    def clear(self):
+        self._entities = {}
+        super(GlobalEntityCollection, self).clear()
+
+# backward compatible name
+EntityCollection = GlobalEntityCollection
+
+_leading_dots = re.compile('^([.]*).*$')
+
+class RelativeEntityCollection(BaseCollection):
+    # the entity=None does not make any sense with a relative entity collection
+    def resolve(self, key, entity):
+        '''
+        Resolve a key to an Entity. The optional `entity` argument is the
+        "source" entity when resolving relationship targets.
+        '''
+        full_path = key
+
+        if '.' not in key or key.startswith('.'):
+            # relative target
+
+            # any leading dot is stripped and with each dot removed,
+            # the entity_module is stripped of one more chunk (starting with
+            # the last one).
+            num_dots = _leading_dots.match(full_path).end(1)
+            full_path = full_path[num_dots:]
+            chunks = entity.__module__.split('.')
+            chunkstokeep = len(chunks) - num_dots
+            if chunkstokeep < 0:
+                raise Exception("Couldn't resolve relative target "
+                    "'%s' relative to '%s'" % (key, entity.__module__))
+            entity_module = '.'.join(chunks[:chunkstokeep])
+
+            if entity_module and entity_module is not '__main__':
+                full_path = '%s.%s' % (entity_module, full_path)
+
+            root = ''
+        else:
+            root = None
+        return self.resolve_absolute(key, full_path, entity, root=root)
+
+    def __getattr__(self, key):
+        raise NotImplementedError
+

File elixir/entity.py

+'''
+This module provides the ``Entity`` base class, as well as its metaclass
+``EntityMeta``.
+'''
+
+import sys
+import types
+import warnings
+
+from copy import deepcopy
+
+import sqlalchemy
+from sqlalchemy import Table, Column, Integer, desc, ForeignKey, and_, \
+                       ForeignKeyConstraint
+from sqlalchemy.orm import MapperExtension, mapper, object_session, \
+                           EXT_CONTINUE, polymorphic_union, ScopedSession, \
+                           ColumnProperty
+from sqlalchemy.sql import ColumnCollection
+
+import elixir
+from elixir.statements import process_mutators, MUTATORS
+from elixir import options
+from elixir.properties import Property
+
+DEBUG = False
+
+__doc_all__ = ['Entity', 'EntityMeta']
+
+
+def session_mapper_factory(scoped_session):
+    def session_mapper(cls, *args, **kwargs):
+        if kwargs.pop('save_on_init', True):
+            old_init = cls.__init__
+            def __init__(self, *args, **kwargs):
+                old_init(self, *args, **kwargs)
+                scoped_session.add(self)
+            cls.__init__ = __init__
+        cls.query = scoped_session.query_property()
+        return mapper(cls, *args, **kwargs)
+    return session_mapper
+
+
+class EntityDescriptor(object):
+    '''
+    EntityDescriptor describes fields and options needed for table creation.
+    '''
+
+    def __init__(self, entity):
+        self.entity = entity
+        self.parent = None
+
+        bases = []
+        for base in entity.__bases__:
+            if isinstance(base, EntityMeta):
+                if is_entity(base) and not is_abstract_entity(base):
+                    if self.parent:
+                        raise Exception(
+                            '%s entity inherits from several entities, '
+                            'and this is not supported.'
+                            % self.entity.__name__)
+                    else:
+                        self.parent = base
+                        bases.extend(base._descriptor.bases)
+                        self.parent._descriptor.children.append(entity)
+                else:
+                    bases.append(base)
+        self.bases = bases
+        if not is_entity(entity) or is_abstract_entity(entity):
+            return
+
+        # entity.__module__ is not always reliable (eg in mod_python)
+        self.module = sys.modules.get(entity.__module__)
+
+        self.builders = []
+
+        #XXX: use entity.__subclasses__ ?
+        self.children = []
+
+        # used for multi-table inheritance
+        self.join_condition = None
+        self.has_pk = False
+        self._pk_col_done = False
+
+        # columns and constraints waiting for a table to exist
+        self._columns = ColumnCollection()
+        self.constraints = []
+
+        # properties (it is only useful for checking dupe properties at the
+        # moment, and when adding properties before the mapper is created,
+        # which shouldn't happen).
+        self.properties = {}
+
+        #
+        self.relationships = []
+
+        # set default value for options
+        self.table_args = []
+
+        # base class(es) options_defaults
+        options_defaults = self.options_defaults()
+
+        complete_defaults = options.options_defaults.copy()
+        complete_defaults.update({
+            'metadata': elixir.metadata,
+            'session': elixir.session,
+            'collection': elixir.entities
+        })
+
+        # set default value for other options
+        for key in options.valid_options:
+            value = options_defaults.get(key, complete_defaults[key])
+            if isinstance(value, dict):
+                value = value.copy()
+            setattr(self, key, value)
+
+        # override options with module-level defaults defined
+        for key in ('metadata', 'session', 'collection'):
+            attr = '__%s__' % key
+            if hasattr(self.module, attr):
+                setattr(self, key, getattr(self.module, attr))
+
+    def options_defaults(self):
+        base_defaults = {}
+        for base in self.bases:
+            base_defaults.update(base._descriptor.options_defaults())
+        base_defaults.update(getattr(self.entity, 'options_defaults', {}))
+        return base_defaults
+
+    def setup_options(self):
+        '''
+        Setup any values that might depend on the "using_options" class
+        mutator. For example, the tablename or the metadata.
+        '''
+        elixir.metadatas.add(self.metadata)
+        if self.collection is not None:
+            self.collection.append(self.entity)
+
+        entity = self.entity
+        if self.parent:
+            if self.inheritance == 'single':
+                self.tablename = self.parent._descriptor.tablename
+
+        if not self.tablename:
+            if self.shortnames:
+                self.tablename = entity.__name__.lower()
+            else:
+                modulename = entity.__module__.replace('.', '_')
+                tablename = "%s_%s" % (modulename, entity.__name__)
+                self.tablename = tablename.lower()
+        elif hasattr(self.tablename, '__call__'):
+            self.tablename = self.tablename(entity)
+
+        if not self.identity:
+            if 'polymorphic_identity' in self.mapper_options:
+                self.identity = self.mapper_options['polymorphic_identity']
+            else:
+                #TODO: include module name (We could have b.Account inherit
+                # from a.Account)
+                self.identity = entity.__name__.lower()
+        elif 'polymorphic_identity' in self.mapper_options:
+            raise Exception('You cannot use the "identity" option and the '
+                            'polymorphic_identity mapper option at the same '
+                            'time.')
+        elif hasattr(self.identity, '__call__'):
+            self.identity = self.identity(entity)
+
+        if self.polymorphic:
+            if not isinstance(self.polymorphic, basestring):
+                self.polymorphic = options.DEFAULT_POLYMORPHIC_COL_NAME
+
+    #---------------------
+    # setup phase methods
+
+    def setup_autoload_table(self):
+        self.setup_table(True)
+
+    def create_pk_cols(self):
+        """
+        Create primary_key columns. That is, call the 'create_pk_cols'
+        builders then add a primary key to the table if it hasn't already got
+        one and needs one.
+
+        This method is "semi-recursive" in some cases: it calls the
+        create_keys method on ManyToOne relationships and those in turn call
+        create_pk_cols on their target. It shouldn't be possible to have an
+        infinite loop since a loop of primary_keys is not a valid situation.
+        """
+        if self._pk_col_done:
+            return
+
+        self.call_builders('create_pk_cols')
+
+        if not self.autoload:
+            if self.parent:
+                if self.inheritance == 'multi':
+                    # Add columns with foreign keys to the parent's primary
+                    # key columns
+                    parent_desc = self.parent._descriptor
+                    tablename = parent_desc.table_fullname
+                    join_clauses = []
+                    fk_columns = []
+                    for pk_col in parent_desc.primary_keys:
+                        colname = options.MULTIINHERITANCECOL_NAMEFORMAT % \
+                                  {'entity': self.parent.__name__.lower(),
+                                   'key': pk_col.key}
+
+                        # It seems like SA ForeignKey is not happy being given
+                        # a real column object when said column is not yet
+                        # attached to a table
+                        pk_col_name = "%s.%s" % (tablename, pk_col.key)
+                        col = Column(colname, pk_col.type, primary_key=True)
+                        fk_columns.append(col)
+                        self.add_column(col)
+                        join_clauses.append(col == pk_col)
+                    self.join_condition = and_(*join_clauses)
+                    self.add_constraint(
+                        ForeignKeyConstraint(fk_columns,
+                            parent_desc.primary_keys, ondelete='CASCADE'))
+                elif self.inheritance == 'concrete':
+                    # Copy primary key columns from the parent.
+                    for col in self.parent._descriptor.columns:
+                        if col.primary_key:
+                            self.add_column(col.copy())
+            elif not self.has_pk and self.auto_primarykey:
+                if isinstance(self.auto_primarykey, basestring):
+                    colname = self.auto_primarykey
+                else:
+                    colname = options.DEFAULT_AUTO_PRIMARYKEY_NAME
+
+                self.add_column(
+                    Column(colname, options.DEFAULT_AUTO_PRIMARYKEY_TYPE,
+                           primary_key=True))
+        self._pk_col_done = True
+
+    def setup_relkeys(self):
+        self.call_builders('create_non_pk_cols')
+
+    def before_table(self):
+        self.call_builders('before_table')
+
+    def setup_table(self, only_autoloaded=False):
+        '''
+        Create a SQLAlchemy table-object with all columns that have been
+        defined up to this point.
+        '''
+        if self.entity.table is not None:
+            return
+
+        if self.autoload != only_autoloaded:
+            return
+
+        kwargs = self.table_options
+        if self.autoload:
+            args = self.table_args
+            kwargs['autoload'] = True
+        else:
+            if self.parent:
+                if self.inheritance == 'single':
+                    # we know the parent is setup before the child
+                    self.entity.table = self.parent.table
+
+                    # re-add the entity columns to the parent entity so that
+                    # they are added to the parent's table (whether the
+                    # parent's table is already setup or not).
+                    for col in self._columns:
+                        self.parent._descriptor.add_column(col)
+                    for constraint in self.constraints:
+                        self.parent._descriptor.add_constraint(constraint)
+                    return
+                elif self.inheritance == 'concrete':
+                    #TODO: we should also copy columns from the parent table
+                    # if the parent is a base (abstract?) entity (whatever the
+                    # inheritance type -> elif will need to be changed)
+
+                    # Copy all non-primary key columns from parent table
+                    # (primary key columns have already been copied earlier).
+                    for col in self.parent._descriptor.columns:
+                        if not col.primary_key:
+                            self.add_column(col.copy())
+
+                    for con in self.parent._descriptor.constraints:
+                        self.add_constraint(
+                            ForeignKeyConstraint(
+                                [e.parent.key for e in con.elements],
+                                [e.target_fullname for e in con.elements],
+                                name=con.name, #TODO: modify it
+                                onupdate=con.onupdate, ondelete=con.ondelete,
+                                use_alter=con.use_alter))
+
+            if self.polymorphic and \
+               self.inheritance in ('single', 'multi') and \
+               self.children and not self.parent:
+                self.add_column(Column(self.polymorphic,
+                                       options.POLYMORPHIC_COL_TYPE))
+
+            if self.version_id_col:
+                if not isinstance(self.version_id_col, basestring):
+                    self.version_id_col = options.DEFAULT_VERSION_ID_COL_NAME
+                self.add_column(Column(self.version_id_col, Integer))
+
+            args = list(self.columns) + self.constraints + self.table_args
+        self.entity.table = Table(self.tablename, self.metadata,
+                                  *args, **kwargs)
+        if DEBUG:
+            print self.entity.table.repr2()
+
+    def setup_reltables(self):
+        self.call_builders('create_tables')
+
+    def after_table(self):
+        self.call_builders('after_table')
+
+    def setup_events(self):
+        def make_proxy_method(methods):
+            def proxy_method(self, mapper, connection, instance):
+                for func in methods:
+                    ret = func(instance)
+                    # I couldn't commit myself to force people to
+                    # systematicaly return EXT_CONTINUE in all their event
+                    # methods.
+                    # But not doing that diverge to how SQLAlchemy works.
+                    # I should try to convince Mike to do EXT_CONTINUE by
+                    # default, and stop processing as the special case.
+#                    if ret != EXT_CONTINUE:
+                    if ret is not None and ret != EXT_CONTINUE:
+                        return ret
+                return EXT_CONTINUE
+            return proxy_method
+
+        # create a list of callbacks for each event
+        methods = {}
+
+        all_methods = getmembers(self.entity,
+                                 lambda a: isinstance(a, types.MethodType))
+
+        for name, method in all_methods:
+            for event in getattr(method, '_elixir_events', []):
+                event_methods = methods.setdefault(event, [])
+                event_methods.append(method)
+
+        if not methods:
+            return
+
+        # transform that list into methods themselves
+        for event in methods:
+            methods[event] = make_proxy_method(methods[event])
+
+        # create a custom mapper extension class, tailored to our entity
+        ext = type('EventMapperExtension', (MapperExtension,), methods)()
+
+        # then, make sure that the entity's mapper has our mapper extension
+        self.add_mapper_extension(ext)
+
+    def before_mapper(self):
+        self.call_builders('before_mapper')
+
+    def _get_children(self):
+        children = self.children[:]
+        for child in self.children:
+            children.extend(child._descriptor._get_children())
+        return children
+
+    def translate_order_by(self, order_by):
+        if isinstance(order_by, basestring):
+            order_by = [order_by]
+
+        order = []
+        for colname in order_by:
+            #FIXME: get_column uses self.columns[key] instead of property
+            # names. self.columns correspond to the columns of the table if
+            # the table was already created and to self._columns otherwise,
+            # which is a ColumnCollection indexed on columns.key
+            # See ticket #108.
+            col = self.get_column(colname.strip('-'))
+            if colname.startswith('-'):
+                col = desc(col)
+            order.append(col)
+        return order
+
+    def setup_mapper(self):
+        '''
+        Initializes and assign a mapper to the entity.
+        At this point the mapper will usually have no property as they are
+        added later.
+        '''
+        if self.entity.mapper:
+            return
+
+        # for now we don't support the "abstract" parent class in a concrete
+        # inheritance scenario as demonstrated in
+        # sqlalchemy/test/orm/inheritance/concrete.py
+        # this should be added along other
+        kwargs = {}
+        if self.order_by:
+            kwargs['order_by'] = self.translate_order_by(self.order_by)
+
+        if self.version_id_col:
+            kwargs['version_id_col'] = self.get_column(self.version_id_col)
+
+        if self.inheritance in ('single', 'concrete', 'multi'):
+            if self.parent and \
+               (self.inheritance != 'concrete' or self.polymorphic):
+                # non-polymorphic concrete doesn't need this
+                kwargs['inherits'] = self.parent.mapper
+
+            if self.inheritance == 'multi' and self.parent:
+                kwargs['inherit_condition'] = self.join_condition
+
+            if self.polymorphic:
+                if self.children:
+                    if self.inheritance == 'concrete':
+                        keys = [(self.identity, self.entity.table)]
+                        keys.extend([(child._descriptor.identity, child.table)
+                                     for child in self._get_children()])
+                        # Having the same alias name for an entity and one of
+                        # its child (which is a parent itself) shouldn't cause
+                        # any problem because the join shouldn't be used at
+                        # the same time. But in reality, some versions of SA
+                        # do misbehave on this. Since it doesn't hurt to have
+                        # different names anyway, here they go.
+                        pjoin = polymorphic_union(
+                                    dict(keys), self.polymorphic,
+                                    'pjoin_%s' % self.identity)
+
+                        kwargs['with_polymorphic'] = ('*', pjoin)
+                        kwargs['polymorphic_on'] = \
+                            getattr(pjoin.c, self.polymorphic)
+                    elif not self.parent:
+                        kwargs['polymorphic_on'] = \
+                            self.get_column(self.polymorphic)
+
+                if self.children or self.parent:
+                    kwargs['polymorphic_identity'] = self.identity
+
+                if self.parent and self.inheritance == 'concrete':
+                    kwargs['concrete'] = True
+
+        if self.parent and self.inheritance == 'single':
+            args = []
+        else:
+            args = [self.entity.table]
+
+        # let user-defined kwargs override Elixir-generated ones, though that's
+        # not very usefull since most of them expect Column instances.
+        kwargs.update(self.mapper_options)
+
+        #TODO: document this!
+        if 'primary_key' in kwargs:
+            cols = self.entity.table.c
+            kwargs['primary_key'] = [getattr(cols, colname) for
+                colname in kwargs['primary_key']]
+
+        # do the mapping
+        if self.session is None:
+            self.entity.mapper = mapper(self.entity, *args, **kwargs)
+        elif isinstance(self.session, ScopedSession):
+            session_mapper = session_mapper_factory(self.session)
+            self.entity.mapper = session_mapper(self.entity, *args, **kwargs)
+        else:
+            raise Exception("Failed to map entity '%s' with its table or "
+                            "selectable. You can only bind an Entity to a "
+                            "ScopedSession object or None for manual session "
+                            "management."
+                            % self.entity.__name__)
+
+    def after_mapper(self):
+        self.call_builders('after_mapper')
+
+    def setup_properties(self):
+        self.call_builders('create_properties')
+
+    def finalize(self):
+        self.call_builders('finalize')
+        self.entity._setup_done = True
+
+    #----------------
+    # helper methods
+
+    def call_builders(self, what):
+        for builder in self.builders:
+            if hasattr(builder, what):
+                getattr(builder, what)()
+
+    def add_column(self, col, check_duplicate=None):
+        '''when check_duplicate is None, the value of the allowcoloverride
+        option of the entity is used.
+        '''
+        if check_duplicate is None:
+            check_duplicate = not self.allowcoloverride
+
+        if col.key in self._columns:
+            if check_duplicate:
+                raise Exception("Column '%s' already exist in '%s' ! " %
+                                (col.key, self.entity.__name__))
+            else:
+                del self._columns[col.key]
+        # are indexed on col.key
+        self._columns.add(col)
+
+        if col.primary_key:
+            self.has_pk = True
+
+        table = self.entity.table
+        if table is not None:
+            if check_duplicate and col.key in table.columns.keys():
+                raise Exception("Column '%s' already exist in table '%s' ! " %
+                                (col.key, table.name))
+            table.append_column(col)
+            if DEBUG:
+                print "table.append_column(%s)" % col
+
+    def add_constraint(self, constraint):
+        self.constraints.append(constraint)
+
+        table = self.entity.table
+        if table is not None:
+            table.append_constraint(constraint)
+
+    def add_property(self, name, property, check_duplicate=True):
+        if check_duplicate and name in self.properties:
+            raise Exception("property '%s' already exist in '%s' ! " %
+                            (name, self.entity.__name__))
+        self.properties[name] = property
+
+#FIXME: something like this is needed to propagate the relationships from
+# parent entities to their children in a concrete inheritance scenario. But
+# this doesn't work because of the backref matching code. In most case
+# (test_concrete.py) it doesn't even happen at all.
+#        if self.children and self.inheritance == 'concrete':
+#            for child in self.children:
+#                child._descriptor.add_property(name, property)
+
+        mapper = self.entity.mapper
+        if mapper:
+            mapper.add_property(name, property)
+            if DEBUG:
+                print "mapper.add_property('%s', %s)" % (name, repr(property))
+
+    def add_mapper_extension(self, extension):
+        extensions = self.mapper_options.get('extension', [])
+        if not isinstance(extensions, list):
+            extensions = [extensions]
+        extensions.append(extension)
+        self.mapper_options['extension'] = extensions
+
+    def get_column(self, key, check_missing=True):
+        #TODO: this needs to work whether the table is already setup or not
+        #TODO: support SA table/autoloaded entity
+        try:
+            return self.columns[key]
+        except KeyError:
+            if check_missing:
+                raise Exception("No column named '%s' found in the table of "
+                                "the '%s' entity!"
+                                % (key, self.entity.__name__))
+
+    def get_inverse_relation(self, rel, check_reverse=True):
+        '''
+        Return the inverse relation of rel, if any, None otherwise.
+        '''
+
+        matching_rel = None
+        for other_rel in self.relationships:
+            if rel.is_inverse(other_rel):
+                if matching_rel is None:
+                    matching_rel = other_rel
+                else:
+                    raise Exception(
+                            "Several relations match as inverse of the '%s' "
+                            "relation in entity '%s'. You should specify "
+                            "inverse relations manually by using the inverse "
+                            "keyword."
+                            % (rel.name, rel.entity.__name__))
+        # When a matching inverse is found, we check that it has only
+        # one relation matching as its own inverse. We don't need the result
+        # of the method though. But we do need to be careful not to start an
+        # infinite recursive loop.
+        if matching_rel and check_reverse:
+            rel.entity._descriptor.get_inverse_relation(matching_rel, False)
+
+        return matching_rel
+
+    def find_relationship(self, name):
+        for rel in self.relationships:
+            if rel.name == name:
+                return rel
+        if self.parent:
+            return self.parent._descriptor.find_relationship(name)
+        else:
+            return None
+
+    #------------------------
+    # some useful properties
+
+    @property
+    def table_fullname(self):
+        '''
+        Complete name of the table for the related entity.
+        Includes the schema name if there is one specified.
+        '''
+        schema = self.table_options.get('schema', None)
+        if schema is not None:
+            return "%s.%s" % (schema, self.tablename)
+        else:
+            return self.tablename
+
+    @property
+    def columns(self):
+        if self.entity.table is not None:
+            return self.entity.table.columns
+        else:
+            #FIXME: depending on the type of inheritance, we should also
+            # return the parent entity's columns (for example for order_by
+            # using a column defined in the parent.
+            return self._columns
+
+    @property
+    def primary_keys(self):
+        """
+        Returns the list of primary key columns of the entity.
+
+        This property isn't valid before the "create_pk_cols" phase.
+        """
+        if self.autoload:
+            return [col for col in self.entity.table.primary_key.columns]
+        else:
+            if self.parent and self.inheritance == 'single':
+                return self.parent._descriptor.primary_keys
+            else:
+                return [col for col in self.columns if col.primary_key]
+
+    @property
+    def table(self):
+        if self.entity.table is not None:
+            return self.entity.table
+        else:
+            return FakeTable(self)
+
+    @property
+    def primary_key_properties(self):
+        """
+        Returns the list of (mapper) properties corresponding to the primary
+        key columns of the table of the entity.
+
+        This property caches its value, so it shouldn't be called before the
+        entity is fully set up.
+        """
+        if not hasattr(self, '_pk_props'):
+            col_to_prop = {}
+            mapper = self.entity.mapper
+            for prop in mapper.iterate_properties:
+                if isinstance(prop, ColumnProperty):
+                    for col in prop.columns:
+                        #XXX: Why is this extra loop necessary? What is this
+                        #     "proxy_set" supposed to mean?
+                        for col in col.proxy_set:
+                            col_to_prop[col] = prop
+            pk_cols = [c for c in mapper.mapped_table.c if c.primary_key]
+            self._pk_props = [col_to_prop[c] for c in pk_cols]
+        return self._pk_props
+
+class FakePK(object):
+    def __init__(self, descriptor):
+        self.descriptor = descriptor
+
+    @property
+    def columns(self):
+        return self.descriptor.primary_keys
+
+class FakeTable(object):
+    def __init__(self, descriptor):
+        self.descriptor = descriptor
+        self.primary_key = FakePK(descriptor)
+
+    @property
+    def columns(self):
+        return self.descriptor.columns
+
+    @property
+    def fullname(self):
+        '''
+        Complete name of the table for the related entity.
+        Includes the schema name if there is one specified.
+        '''
+        schema = self.descriptor.table_options.get('schema', None)
+        if schema is not None:
+            return "%s.%s" % (schema, self.descriptor.tablename)
+        else:
+            return self.descriptor.tablename
+
+
+def is_entity(cls):
+    """
+    Scan the bases classes of `cls` to see if any is an instance of
+    EntityMeta. If we don't find any, it means it is either an unrelated class
+    or an entity base class (like the 'Entity' class).
+    """
+    for base in cls.__bases__:
+        if isinstance(base, EntityMeta):
+            return True
+    return False
+
+
+# Note that we don't use inspect.getmembers because of
+# http://bugs.python.org/issue1785
+# See also http://elixir.ematia.de/trac/changeset/262
+def getmembers(object, predicate=None):
+    base_props = []
+    for key in dir(object):
+        try:
+            value = getattr(object, key)
+        except AttributeError:
+            continue
+        if not predicate or predicate(value):
+            base_props.append((key, value))
+    return base_props
+
+def is_abstract_entity(dict_or_cls):
+    if not isinstance(dict_or_cls, dict):
+        dict_or_cls = dict_or_cls.__dict__
+    for mutator, args, kwargs in dict_or_cls.get(MUTATORS, []):
+        if 'abstract' in kwargs:
+            return kwargs['abstract']
+
+    return False
+
+def instrument_class(cls):
+    """
+    Instrument a class as an Entity. This is usually done automatically through
+    the EntityMeta metaclass.
+    """
+    # Create the entity descriptor
+    desc = cls._descriptor = EntityDescriptor(cls)
+
+    # Process mutators
+    # We *do* want mutators to be processed for base/abstract classes
+    # (so that statements like using_options_defaults work).
+    process_mutators(cls)
+
+    # We do not want to do any more processing for base/abstract classes
+    # (Entity et al.).
+    if not is_entity(cls) or is_abstract_entity(cls):
+        return
+
+    cls.table = None
+    cls.mapper = None
+
+    # Copy the properties ('Property' instances) of the entity base class(es).
+    # We use getmembers (instead of __dict__) so that we also get the
+    # properties from the parents of the base class if any.
+    base_props = []
+    for base in cls.__bases__:
+        if isinstance(base, EntityMeta) and \
+           (not is_entity(base) or is_abstract_entity(base)):
+            base_props += [(name, deepcopy(attr)) for name, attr in
+                           getmembers(base, lambda a: isinstance(a, Property))]
+
+    # Process attributes (using the assignment syntax), looking for
+    # 'Property' instances and attaching them to this entity.
+    properties = [(name, attr) for name, attr in cls.__dict__.iteritems()
+                               if isinstance(attr, Property)]
+    sorted_props = sorted(base_props + properties,
+                          key=lambda i: i[1]._counter)
+    for name, prop in sorted_props:
+        prop.attach(cls, name)
+
+    # setup misc options here (like tablename etc.)
+    desc.setup_options()
+
+
+class EntityMeta(type):
+    """
+    Entity meta class.
+    You should only use it directly if you want to define your own base class
+    for your entities (ie you don't want to use the provided 'Entity' class).
+    """
+
+    def __init__(cls, name, bases, dict_):
+        instrument_class(cls)
+
+    def __setattr__(cls, key, value):
+        if isinstance(value, Property):
+            if hasattr(cls, '_setup_done'):
+                raise Exception('Cannot set attribute on a class after '
+                                'setup_all')
+            else:
+                value.attach(cls, key)
+        else:
+            type.__setattr__(cls, key, value)
+
+
+def setup_entities(entities):
+    '''Setup all entities in the list passed as argument'''
+
+    for entity in entities:
+        # delete all Elixir properties so that it doesn't interfere with
+        # SQLAlchemy. At this point they should have be converted to
+        # builders.
+        for name, attr in entity.__dict__.items():
+            if isinstance(attr, Property):
+                delattr(entity, name)
+
+    for method_name in (
+            'setup_autoload_table', 'create_pk_cols', 'setup_relkeys',
+            'before_table', 'setup_table', 'setup_reltables', 'after_table',
+            'setup_events',
+            'before_mapper', 'setup_mapper', 'after_mapper',
+            'setup_properties',
+            'finalize'):
+#        if DEBUG:
+#            print "=" * 40
+#            print method_name
+#            print "=" * 40
+        for entity in entities:
+#            print entity.__name__, "...",
+            if hasattr(entity, '_setup_done'):
+#                print "already done"
+                continue
+            method = getattr(entity._descriptor, method_name)
+            method()
+#            print "ok"
+
+
+def cleanup_entities(entities):
+    """
+    Try to revert back the list of entities passed as argument to the state
+    they had just before their setup phase.
+
+    As of now, this function is *not* functional in that it doesn't revert to
+    the exact same state the entities were before setup. For example, the
+    properties do not work yet as those would need to be regenerated (since the
+    columns they are based on are regenerated too -- and as such the
+    corresponding joins are not correct) but this doesn't happen because of
+    the way relationship setup is designed to be called only once (especially
+    the backref stuff in create_properties).
+    """
+    for entity in entities:
+        desc = entity._descriptor
+
+        if hasattr(entity, '_setup_done'):
+            del entity._setup_done
+
+        entity.table = None
+        entity.mapper = None
+
+        desc._pk_col_done = False
+        desc.has_pk = False
+        desc._columns = ColumnCollection()
+        desc.constraints = []
+        desc.properties = {}
+
+class EntityBase(object):
+    """
+    This class holds all methods of the "Entity" base class, but does not act
+    as a base class itself (it does not use the EntityMeta metaclass), but
+    rather as a parent class for Entity. This is meant so that people who want
+    to provide their own base class but don't want to loose or copy-paste all
+    the methods of Entity can do so by inheriting from EntityBase:
+
+    .. sourcecode:: python
+
+        class MyBase(EntityBase):
+            __metaclass__ = EntityMeta
+
+            def myCustomMethod(self):
+                # do something great
+    """
+
+    def __init__(self, **kwargs):
+        self.set(**kwargs)
+
+    def set(self, **kwargs):
+        for key, value in kwargs.iteritems():
+            setattr(self, key, value)
+
+    @classmethod
+    def update_or_create(cls, data, surrogate=True):
+        pk_props = cls._descriptor.primary_key_properties
+
+        # if all pk are present and not None
+        if not [1 for p in pk_props if data.get(p.key) is None]:
+            pk_tuple = tuple([data[prop.key] for prop in pk_props])
+            record = cls.query.get(pk_tuple)
+            if record is None:
+                if surrogate:
+                    raise Exception("Cannot create surrogate with pk")
+                else:
+                    record = cls()
+        else:
+            if surrogate:
+                record = cls()
+            else:
+                raise Exception("Cannot create non surrogate without pk")
+        record.from_dict(data)
+        return record
+
+    def from_dict(self, data):
+        """
+        Update a mapped class with data from a JSON-style nested dict/list
+        structure.
+        """
+        # surrogate can be guessed from autoincrement/sequence but I guess
+        # that's not 100% reliable, so we'll need an override
+
+        mapper = sqlalchemy.orm.object_mapper(self)
+
+        for key, value in data.iteritems():
+            if isinstance(value, dict):
+                dbvalue = getattr(self, key)
+                rel_class = mapper.get_property(key).mapper.class_
+                pk_props = rel_class._descriptor.primary_key_properties
+
+                # If the data doesn't contain any pk, and the relationship
+                # already has a value, update that record.
+                if not [1 for p in pk_props if p.key in data] and \
+                   dbvalue is not None:
+                    dbvalue.from_dict(value)
+                else:
+                    record = rel_class.update_or_create(value)
+                    setattr(self, key, record)
+            elif isinstance(value, list) and \
+                 value and isinstance(value[0], dict):
+
+                rel_class = mapper.get_property(key).mapper.class_
+                new_attr_value = []
+                for row in value:
+                    if not isinstance(row, dict):
+                        raise Exception(
+                                'Cannot send mixed (dict/non dict) data '
+                                'to list relationships in from_dict data.')
+                    record = rel_class.update_or_create(row)
+                    new_attr_value.append(record)
+                setattr(self, key, new_attr_value)
+            else:
+                setattr(self, key, value)
+
+    def to_dict(self, deep={}, exclude=[]):
+        """Generate a JSON-style nested dict/list structure from an object."""
+        col_prop_names = [p.key for p in self.mapper.iterate_properties \
+                                      if isinstance(p, ColumnProperty)]
+        data = dict([(name, getattr(self, name))
+                     for name in col_prop_names if name not in exclude])
+        for rname, rdeep in deep.iteritems():
+            dbdata = getattr(self, rname)
+            #FIXME: use attribute names (ie coltoprop) instead of column names
+            fks = self.mapper.get_property(rname).remote_side
+            exclude = [c.name for c in fks]
+            if dbdata is None:
+                data[rname] = None
+            elif isinstance(dbdata, list):
+                data[rname] = [o.to_dict(rdeep, exclude) for o in dbdata]
+            else:
+                data[rname] = dbdata.to_dict(rdeep, exclude)
+        return data
+
+    # session methods
+    def flush(self, *args, **kwargs):
+        return object_session(self).flush([self], *args, **kwargs)
+
+    def delete(self, *args, **kwargs):
+        return object_session(self).delete(self, *args, **kwargs)
+
+    def expire(self, *args, **kwargs):
+        return object_session(self).expire(self, *args, **kwargs)
+
+    def refresh(self, *args, **kwargs):
+        return object_session(self).refresh(self, *args, **kwargs)
+
+    def expunge(self, *args, **kwargs):
+        return object_session(self).expunge(self, *args, **kwargs)
+
+    # This bunch of session methods, along with all the query methods below
+    # only make sense when using a global/scoped/contextual session.
+    @property
+    def _global_session(self):
+        return self._descriptor.session.registry()
+
+    #FIXME: remove all deprecated methods, possibly all of these
+    def merge(self, *args, **kwargs):
+        return self._global_session.merge(self, *args, **kwargs)
+
+    def save(self, *args, **kwargs):
+        return self._global_session.save(self, *args, **kwargs)
+
+    def update(self, *args, **kwargs):
+        return self._global_session.update(self, *args, **kwargs)
+
+    # only exist in SA < 0.5
+    # IMO, the replacement (session.add) doesn't sound good enough to be added
+    # here. For example: "o = Order(); o.add()" is not very telling. It's
+    # better to leave it as "session.add(o)"
+    def save_or_update(self, *args, **kwargs):
+        return self._global_session.save_or_update(self, *args, **kwargs)
+
+    # query methods
+    @classmethod
+    def get_by(cls, *args, **kwargs):
+        """
+        Returns the first instance of this class matching the given criteria.
+        This is equivalent to:
+        session.query(MyClass).filter_by(...).first()
+        """
+        return cls.query.filter_by(*args, **kwargs).first()
+
+    @classmethod
+    def get(cls, *args, **kwargs):
+        """
+        Return the instance of this class based on the given identifier,
+        or None if not found. This is equivalent to:
+        session.query(MyClass).get(...)
+        """
+        return cls.query.get(*args, **kwargs)
+
+
+class Entity(EntityBase):
+    '''
+    The base class for all entities
+
+    All Elixir model objects should inherit from this class. Statements can
+    appear within the body of the definition of an entity to define its
+    fields, relationships, and other options.
+
+    Here is an example:
+
+    .. sourcecode:: python
+
+        class Person(Entity):
+            name = Field(Unicode(128))
+            birthdate = Field(DateTime, default=datetime.now)
+
+    Please note, that if you don't specify any primary keys, Elixir will
+    automatically create one called ``id``.
+
+    For further information, please refer to the provided examples or
+    tutorial.
+    '''
+    __metaclass__ = EntityMeta
+
+

File elixir/events.py

+from sqlalchemy.orm import reconstructor
+
+__all__ = [
+    'before_insert',
+    'after_insert',
+    'before_update',
+    'after_update',
+    'before_delete',
+    'after_delete',
+    'reconstructor'
+]
+
+def create_decorator(event_name):
+    def decorator(func):
+        if not hasattr(func, '_elixir_events'):
+            func._elixir_events = []
+        func._elixir_events.append(event_name)
+        return func
+    return decorator
+
+before_insert = create_decorator('before_insert')
+after_insert = create_decorator('after_insert')
+before_update = create_decorator('before_update')
+after_update = create_decorator('after_update')
+before_delete = create_decorator('before_delete')
+after_delete = create_decorator('after_delete')
+

File elixir/ext/__init__.py

+'''
+Ext package
+
+Additional Elixir statements and functionality.
+'''

File elixir/ext/associable.py

+'''
+Associable Elixir Statement Generator
+
+==========
+Associable
+==========
+
+About Polymorphic Associations
+------------------------------
+
+A frequent pattern in database schemas is the has_and_belongs_to_many, or a
+many-to-many table. Quite often multiple tables will refer to a single one
+creating quite a few many-to-many intermediate tables.
+
+Polymorphic associations lower the amount of many-to-many tables by setting up
+a table that allows relations to any other table in the database, and relates
+it to the associable table. In some implementations, this layout does not
+enforce referential integrity with database foreign key constraints, this
+implementation uses an additional many-to-many table with foreign key
+constraints to avoid this problem.
+
+.. note:
+    SQLite does not support foreign key constraints, so referential integrity
+    can only be enforced using database backends with such support.
+
+Elixir Statement Generator for Polymorphic Associations
+-------------------------------------------------------
+
+The ``associable`` function generates the intermediary tables for an Elixir
+entity that should be associable with other Elixir entities and returns an
+Elixir Statement for use with them. This automates the process of creating the
+polymorphic association tables and ensuring their referential integrity.
+
+Matching select_XXX and select_by_XXX are also added to the associated entity
+which allow queries to be run for the associated objects.
+
+Example usage:
+
+.. sourcecode:: py