From 648a47c824e5fdffa80547405aa5ffaba1ea5872 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sat, 24 Dec 2005 15:16:50 +0000 Subject: [PATCH] docstrings --- doc/build/components/formatting.myt | 2 +- lib/sqlalchemy/engine.py | 178 ++++++++++++++++++++++++---- lib/sqlalchemy/mapping/__init__.py | 9 +- lib/sqlalchemy/schema.py | 140 +++++++++++++++++++--- 4 files changed, 289 insertions(+), 40 deletions(-) diff --git a/doc/build/components/formatting.myt b/doc/build/components/formatting.myt index 6f92cc8089..6405b2dbd2 100644 --- a/doc/build/components/formatting.myt +++ b/doc/build/components/formatting.myt @@ -194,7 +194,7 @@ f = "

" + f + "

" return f -<% m.content() %> +<% m.content() | h%> <%method itemlink trim="both"> diff --git a/lib/sqlalchemy/engine.py b/lib/sqlalchemy/engine.py index 1e5ba34d02..d713beebec 100644 --- a/lib/sqlalchemy/engine.py +++ b/lib/sqlalchemy/engine.py @@ -15,8 +15,24 @@ # along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -"""builds upon the schema and sql packages to provide a central object for tying schema -objects and sql constructs to database-specific query compilation and execution""" +"""Defines the SQLEngine class, which serves as the primary "database" object +used throughout the sql construction and object-relational mapper packages. +A SQLEngine is a facade around a single connection pool corresponding to a +particular set of connection parameters, and provides thread-local transactional +methods and statement execution methods for Connection objects. It also provides +a facade around a Cursor object to allow richer column selection for result rows +as well as type conversion operations, known as a ResultProxy. + +A SQLEngine is provided to an application as a subclass that is specific to a particular type +of DBAPI, and is the central switching point for abstracting different kinds of database +behavior into a consistent set of behaviors. It provides a variety of factory methods +to produce everything specific to a certain kind of database, including a Compiler, +schema creation/dropping objects, and TableImpl and ColumnImpl objects to augment the +behavior of table metadata objects. + +The term "database-specific" will be used to describe any object or function that has behavior +corresponding to a particular vendor, such as mysql-specific, sqlite-specific, etc. +""" import sqlalchemy.schema as schema import sqlalchemy.pool @@ -28,13 +44,42 @@ import sqlalchemy.databases __all__ = ['create_engine', 'engine_descriptors'] -def create_engine(name, *args ,**kwargs): - """creates a new SQLEngine instance. +def create_engine(name, opts=None,**kwargs): + """creates a new SQLEngine instance. There are two forms of calling this method. - name - the type of engine to load, i.e. 'sqlite', 'postgres', 'oracle' + In the first, the "name" argument is the type of engine to load, i.e. 'sqlite', 'postgres', + 'oracle', 'mysql'. "opts" is a dictionary of options to be sent to the underlying DBAPI module + to create a connection, usually including a hostname, username, password, etc. - *args, **kwargs - sent directly to the specific engine instance as connect arguments, - options. + In the second, the "name" argument is a URL in the form ://opt1=val1&opt2=val2. + Where is the name as above, and the contents of the option dictionary are + spelled out as a URL encoded string. The "opts" argument is not used. + + In both cases, **kwargs represents options to be sent to the SQLEngine itself. A possibly + partial listing of those options is as follows: + + pool=None : an instance of sqlalchemy.pool.DBProxy to be used as the underlying source + for connections (DBProxy is described in the previous section). If None, a default DBProxy + will be created using the engine's own database module with the given arguments. + + echo=False : if True, the SQLEngine will log all statements as well as a repr() of their + parameter lists to the engines logger, which defaults to sys.stdout. A SQLEngine instances' + "echo" data member can be modified at any time to turn logging on and off. If set to the string + 'debug', result rows will be printed to the standard output as well. + + logger=None : a file-like object where logging output can be sent, if echo is set to True. + This defaults to sys.stdout. + + module=None : used by Oracle and Postgres, this is a reference to a DBAPI2 module to be used + instead of the engine's default module. For Postgres, the default is psycopg2, or psycopg1 if + 2 cannot be found. For Oracle, its cx_Oracle. For mysql, MySQLdb. + + use_ansi=True : used only by Oracle; when False, the Oracle driver attempts to support a + particular "quirk" of some Oracle databases, that the LEFT OUTER JOIN SQL syntax is not + supported, and the "Oracle join" syntax of using (+)= must be used + in order to achieve a LEFT OUTER JOIN. Its advised that the Oracle database be configured to + have full ANSI support instead of using this feature. + """ m = re.match(r'(\w+)://(.*)', name) if m is not None: @@ -48,6 +93,20 @@ def create_engine(name, *args ,**kwargs): return module.engine(*args, **kwargs) def engine_descriptors(): + """provides a listing of all the database implementations supported. this data + is provided as a list of dictionaries, where each dictionary contains the following + key/value pairs: + + name : the name of the engine, suitable for use in the create_engine function + + description: a plain description of the engine. + + arguments : a dictionary describing the name and description of each parameter + used to connect to this engine's underlying DBAPI. + + This function is meant for usage in automated configuration tools that wish to + query the user for database and connection information. + """ result = [] for module in sqlalchemy.databases.__all__: module = getattr(__import__('sqlalchemy.databases.%s' % module).databases, module) @@ -100,11 +159,17 @@ class DefaultRunner(schema.SchemaVisitor): class SQLEngine(schema.SchemaEngine): - """base class for a series of database-specific engines. serves as an abstract factory - for implementation objects as well as database connections, transactions, SQL generators, - etc.""" + """ + The central "database" object used by an application. Subclasses of this object is used + by the schema and SQL construction packages to provide database-specific behaviors, + as well as an execution and thread-local transaction context. + + SQLEngines are constructed via the create_engine() function inside this package. + """ def __init__(self, pool = None, echo = False, logger = None, **params): + """constructs a new SQLEngine. SQLEngines should be constructed via the create_engine() + function which will construct the appropriate subclass of SQLEngine.""" # get a handle on the connection pool via the connect arguments # this insures the SQLEngine instance integrates with the pool referenced # by direct usage of pool.manager().connect(*args, **params) @@ -142,20 +207,50 @@ class SQLEngine(schema.SchemaEngine): self.positional=True def type_descriptor(self, typeobj): + """provides a database-specific TypeEngine object, given the generic object + which comes from the types module. Subclasses will usually use the adapt_type() + method in the types module to make this job easy.""" if type(typeobj) is type: typeobj = typeobj() return typeobj def schemagenerator(self, proxy, **params): + """returns a schema.SchemaVisitor instance that can generate schemas, when it is + invoked to traverse a set of schema objects. The + "proxy" argument is a callable will execute a given string SQL statement + and a dictionary or list of parameters. + + schemagenerator is called via the create() method. + """ raise NotImplementedError() def schemadropper(self, proxy, **params): + """returns a schema.SchemaVisitor instance that can drop schemas, when it is + invoked to traverse a set of schema objects. The + "proxy" argument is a callable will execute a given string SQL statement + and a dictionary or list of parameters. + + schemagenerator is called via the drop() method. + """ raise NotImplementedError() def defaultrunner(self, proxy): + """Returns a schema.SchemaVisitor instance that can execute the default values on a column. + The base class for this visitor is the DefaultRunner class inside this module. + This visitor will typically only receive schema.DefaultGenerator schema objects. The given + proxy is a callable that takes a string statement and a dictionary of bind parameters + to be executed. For engines that require positional arguments, the dictionary should + be an instance of OrderedDict which returns its bind parameters in the proper order. + + defaultrunner is called within the context of the execute_compiled() method.""" return DefaultRunner(self, proxy) def compiler(self, statement, parameters): + """returns a sql.ClauseVisitor which will produce a string representation of the given + ClauseElement and parameter dictionary. This object is usually a subclass of + ansisql.ANSICompiler. + + compiler is called within the context of the compile() method.""" raise NotImplementedError() def rowid_column_name(self): @@ -163,15 +258,16 @@ class SQLEngine(schema.SchemaEngine): return "oid" def supports_sane_rowcount(self): - """ill give everyone one guess which database warrants this method.""" + """Provided to indicate when MySQL is being used, which does not have standard behavior + for the "rowcount" function on a statement handle. """ return True def create(self, table, **params): - """creates a table given a schema.Table object.""" + """creates a table within this engine's database connection given a schema.Table object.""" table.accept_visitor(self.schemagenerator(self.proxy(), **params)) def drop(self, table, **params): - """drops a table given a schema.Table object.""" + """drops a table within this engine's database connection given a schema.Table object.""" table.accept_visitor(self.schemadropper(self.proxy(), **params)) def compile(self, statement, parameters, **kwargs): @@ -188,19 +284,31 @@ class SQLEngine(schema.SchemaEngine): raise NotImplementedError() def tableimpl(self, table): - """returns a new sql.TableImpl object to correspond to the given Table object.""" + """returns a new sql.TableImpl object to correspond to the given Table object. + A TableImpl provides SQL statement builder operations on a Table metadata object, + and a subclass of this object may be provided by a SQLEngine subclass to provide + database-specific behavior.""" return sql.TableImpl(table) def columnimpl(self, column): - """returns a new sql.ColumnImpl object to correspond to the given Column object.""" + """returns a new sql.ColumnImpl object to correspond to the given Column object. + A ColumnImpl provides SQL statement builder operations on a Column metadata object, + and a subclass of this object may be provided by a SQLEngine subclass to provide + database-specific behavior.""" return sql.ColumnImpl(column) def get_default_schema_name(self): + """returns the currently selected schema in the current connection.""" return None def last_inserted_ids(self): - """returns a thread-local list of the primary keys for the last insert statement executed. - This does not apply to straight textual clauses; only to sql.Insert objects compiled against a schema.Table object, which are executed via statement.execute(). The order of items in the list is the same as that of the Table's 'primary_key' attribute.""" + """returns a thread-local list of the primary key values for the last insert statement executed. + This does not apply to straight textual clauses; only to sql.Insert objects compiled against + a schema.Table object, which are executed via statement.execute(). The order of items in the + list is the same as that of the Table's 'primary_key' attribute. + + In some cases, this method may invoke a query back to the database to retrieve the data, based on + the "lastrowid" value in the cursor.""" raise NotImplementedError() def connect_args(self): @@ -226,6 +334,9 @@ class SQLEngine(schema.SchemaEngine): connection.commit() def proxy(self, **kwargs): + """provides a callable that will execute the given string statement and parameters. + The statement and parameters should be in the format specific to the particular database; + i.e. named or positional.""" return lambda s, p = None: self.execute(s, p, **kwargs) def connection(self): @@ -234,6 +345,10 @@ class SQLEngine(schema.SchemaEngine): def multi_transaction(self, tables, func): """provides a transaction boundary across tables which may be in multiple databases. + If you have three tables, and a function that operates upon them, providing the tables as a + list and the function will result in a begin()/commit() pair invoked for each distinct engine + represented within those tables, and the function executed within the context of that transaction. + any exceptions will result in a rollback(). clearly, this approach only goes so far, such as if database A commits, then database B commits and fails, A is already committed. Any failure conditions have to be raised before anyone @@ -253,6 +368,8 @@ class SQLEngine(schema.SchemaEngine): engine.commit() def transaction(self, func): + """executes the given function within a transaction boundary. this is a shortcut for + explicitly calling begin() and commit() and optionally rollback() when execptions are raised.""" self.begin() try: func() @@ -262,6 +379,11 @@ class SQLEngine(schema.SchemaEngine): self.commit() def begin(self): + """"begins" a transaction on a pooled connection, and stores the connection in a thread-local + context. repeated calls to begin() within the same thread will increment a counter that must be + decreased by corresponding commit() statements before an actual commit occurs. this is to provide + "nested" behavior of transactions so that different functions can all call begin()/commit() and still + call each other.""" if getattr(self.context, 'transaction', None) is None: conn = self.connection() self.do_begin(conn) @@ -271,12 +393,19 @@ class SQLEngine(schema.SchemaEngine): self.context.tcount += 1 def rollback(self): + """rolls back the current thread-local transaction started by begin(). the "begin" counter + is cleared and the transaction ended.""" if self.context.transaction is not None: self.do_rollback(self.context.transaction) self.context.transaction = None self.context.tcount = None def commit(self): + """commits the current thread-local transaction started by begin(). If begin() was called multiple + times, a counter will be decreased for each call to commit(), with the actual commit operation occuring + when the counter reaches zero. this is to provide + "nested" behavior of transactions so that different functions can all call begin()/commit() and still + call each other.""" if self.context.transaction is not None: count = self.context.tcount - 1 self.context.tcount = count @@ -318,9 +447,11 @@ class SQLEngine(schema.SchemaEngine): def pre_exec(self, proxy, compiled, parameters, **kwargs): + """called by execute_compiled before the compiled statement is executed.""" pass def post_exec(self, proxy, compiled, parameters, **kwargs): + """called by execute_compiled after the compiled statement is executed.""" pass def execute_compiled(self, compiled, parameters, connection=None, cursor=None, echo=None, **kwargs): @@ -438,7 +569,7 @@ class SQLEngine(schema.SchemaEngine): except: self.do_rollback(connection) raise - return ResultProxy(cursor, self, typemap = typemap) + return ResultProxy(cursor, self, typemap=typemap) def _execute(self, c, statement, parameters): c.execute(statement, parameters) @@ -457,12 +588,15 @@ class ResultProxy: position, case-insensitive column name, or by schema.Column object. e.g.: row = fetchone() + col1 = row[0] # access via integer position + col2 = row['col2'] # access via name - col3 = row[mytable.c.mycol] # access via Column object. - #the Column's 'label', 'key', and 'name' properties are - # searched in that order. + + col3 = row[mytable.c.mycol] # access via Column object. + ResultProxy also contains a map of TypeEngine objects and will invoke the appropriate + convert_result_value() method before returning columns. """ class AmbiguousColumn(object): def __init__(self, key): @@ -471,6 +605,7 @@ class ResultProxy: raise "Ambiguous column name '%s' in result set! try 'use_labels' option on select statement." % (self.key) def __init__(self, cursor, engine, typemap = None): + """ResultProxy objects are constructed via the execute() method on SQLEngine.""" self.cursor = cursor self.echo = engine.echo=="debug" self.rowcount = engine.context.rowcount @@ -526,6 +661,7 @@ class ResultProxy: class RowProxy: """proxies a single cursor row for a parent ResultProxy.""" def __init__(self, parent, row): + """RowProxy objects are constructed by ResultProxy objects.""" self.parent = parent self.row = row def __iter__(self): diff --git a/lib/sqlalchemy/mapping/__init__.py b/lib/sqlalchemy/mapping/__init__.py index f5d635213a..cc405beb5b 100644 --- a/lib/sqlalchemy/mapping/__init__.py +++ b/lib/sqlalchemy/mapping/__init__.py @@ -28,10 +28,10 @@ from mapper import * from properties import * import mapper as mapperlib -__all__ = ['relation', 'eagerload', 'lazyload', 'noload', 'deferred', 'assignmapper', 'column', 'deferred', +__all__ = ['relation', 'eagerload', 'lazyload', 'noload', 'deferred', 'assignmapper', 'column', 'defer', 'undefer', 'mapper', 'clear_mappers', 'objectstore', 'sql', 'extension', 'class_mapper', 'object_mapper', 'MapperExtension', - 'ColumnProperty', 'assign_mapper' + 'assign_mapper' ] def relation(*args, **params): @@ -72,7 +72,10 @@ def deferred(*columns, **kwargs): class assignmapper(object): - """provides a property object that will instantiate a Mapper for a given class the first + """ + **this class is deprecated** + + provides a property object that will instantiate a Mapper for a given class the first time it is called off of the object. This is useful for attaching a Mapper to a class that has dependencies on other classes and tables which may not have been defined yet.""" def __init__(self, table, class_ = None, **kwargs): diff --git a/lib/sqlalchemy/schema.py b/lib/sqlalchemy/schema.py index 13ed33e828..d4440c7225 100644 --- a/lib/sqlalchemy/schema.py +++ b/lib/sqlalchemy/schema.py @@ -15,11 +15,21 @@ # along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""the schema module provides the building blocks for database metadata. This means +all the entities within a SQL database that we might want to look at, modify, or create +and delete are described by these objects, in a database-agnostic way. + +A structure of SchemaItems also provides a "visitor" interface which is the primary +method by which other methods operate upon the schema. The SQL package extends this +structure with its own clause-specific objects as well as the visitor interface, so that +the schema package "plugs in" to the SQL package. + +""" + from sqlalchemy.util import * from sqlalchemy.types import * import copy, re - __all__ = ['SchemaItem', 'Table', 'Column', 'ForeignKey', 'Sequence', 'SchemaEngine', 'SchemaVisitor'] @@ -31,6 +41,8 @@ class SchemaItem(object): item._set_parent(self) def accept_visitor(self, visitor): + """all schema items implement an accept_visitor method that should call the appropriate + visit_XXXX method upon the given visitor object.""" raise NotImplementedError() def _set_parent(self, parent): @@ -57,6 +69,7 @@ def _get_table_key(engine, name, schema): return schema + "." + name class TableSingleton(type): + """a metaclass used by the Table object to provide singleton behavior.""" def __call__(self, name, engine, *args, **kwargs): try: name = str(name) # in case of incoming unicode @@ -94,6 +107,38 @@ class Table(SchemaItem): __metaclass__ = TableSingleton def __init__(self, name, engine, *args, **kwargs): + """Table objects can be constructed directly. The init method is actually called via + the TableSingleton metaclass. Arguments are: + + name : the name of this table, exactly as it appears, or will appear, in the database. + This property, along with the "schema", indicates the "singleton identity" of this table. + Further tables constructed with the same name/schema combination will return the same + Table instance. + + engine : a SchemaEngine instance to provide services to this table. Usually a subclass of + sql.SQLEngine. + + *args : should contain a listing of the Column objects for this table. + + **kwargs : options include: + + schema=None : the "schema name" for this table, which is required if the table resides in a + schema other than the default selected schema for the engine's database connection. + + autoload=False : the Columns for this table should be reflected from the database. Usually + there will be no Column objects in the constructor if this property is set. + + redefine=False : if this Table has already been defined in the application, clear out its columns + and redefine with new arguments. + + mustexist=False : indicates that this Table must already have been defined elsewhere in the application, + else an exception is raised. + + useexisting=False : indicates that if this Table was already defined elsewhere in the application, disregard + the rest of the constructor arguments. If this flag and the "redefine" flag are not set, constructing + the same table twice will result in an exception. + + """ self.name = name self.columns = OrderedProperties() self.c = self.columns @@ -111,6 +156,9 @@ class Table(SchemaItem): raise "Unknown arguments passed to Table: " + repr(kwargs.keys()) def reload_values(self, *args): + """clears out the columns and other properties of this Table, and reloads them from the + given argument list. This is used with the "redefine" keyword argument sent to the + metaclass constructor.""" self.columns = OrderedProperties() self.c = self.columns self.foreign_keys = [] @@ -119,6 +167,7 @@ class Table(SchemaItem): self._init_items(*args) def append_item(self, item): + """appends a Column item or other schema item to this Table.""" self._init_items(item) def _set_parent(self, schema): @@ -126,6 +175,8 @@ class Table(SchemaItem): self.schema = schema def accept_visitor(self, visitor): + """traverses the given visitor across the Column objects inside this Table, + then calls the visit_table method on the visitor.""" for c in self.columns: c.accept_visitor(visitor) return visitor.visit_table(self) @@ -152,6 +203,36 @@ class Table(SchemaItem): class Column(SchemaItem): """represents a column in a database table.""" def __init__(self, name, type, *args, **kwargs): + """constructs a new Column object. Arguments are: + + name : the name of this column. this should be the identical name as it appears, + or will appear, in the database. + + type : this is the type of column. This can be any subclass of types.TypeEngine, + including the database-agnostic types defined in the types module, database-specific types + defined within specific database modules, or user-defined types. + + *args : ForeignKey and Sequence objects should be added as list values. + + **kwargs : keyword arguments include: + + key=None : an optional "alias name" for this column. The column will then be identified everywhere + in an application, including the column list on its Table, by this key, and not the given name. + Generated SQL, however, will still reference the column by its actual name. + + primary_key=False : True if this column is a primary key column. Multiple columns can have this flag + set to specify composite primary keys. + + nullable=True : True if this column should allow nulls. Defaults to True unless this column is a primary + key column. + + default=None : a scalar, python callable, or ClauseElement representing the "default value" for this column, + which will be invoked upon insert if this column is not present in the insert list or is given a value + of None. + + hidden=False : indicates this column should not be listed in the table's list of columns. Used for the "oid" + column, which generally isnt in column lists. + """ self.name = str(name) # in case of incoming unicode self.type = type self.args = args @@ -215,6 +296,8 @@ class Column(SchemaItem): return c def accept_visitor(self, visitor): + """traverses the given visitor to this Column's default and foreign key object, + then calls visit_column on the visitor.""" if self.default is not None: self.default.accept_visitor(visitor) if self.foreign_key is not None: @@ -238,11 +321,17 @@ class Column(SchemaItem): def __str__(self): return self._impl.__str__() class ForeignKey(SchemaItem): + """defines a ForeignKey constraint between two columns. ForeignKey is + specified as an argument to a Column object.""" def __init__(self, column): + """Constructs a new ForeignKey object. "column" can be a schema.Column + object representing the relationship, or just its string name given as + "tablename.columnname".""" self._colspec = column self._column = None def copy(self): + """produces a copy of this ForeignKey object.""" if isinstance(self._colspec, str): return ForeignKey(self._colspec) else: @@ -281,6 +370,7 @@ class ForeignKey(SchemaItem): column = property(lambda s: s._init_column()) def accept_visitor(self, visitor): + """calls the visit_foreign_key method on the given visitor.""" visitor.visit_foreign_key(self) def _set_parent(self, column): @@ -289,18 +379,19 @@ class ForeignKey(SchemaItem): self.parent.table.foreign_keys.append(self) class DefaultGenerator(SchemaItem): - """represents a "default value generator" for a particular column in a particular - table. This could correspond to a constant, a callable function, or a SQL clause.""" + """Base class for column "default" values, which can be a plain default + or a Sequence.""" def _set_parent(self, column): self.column = column self.column.default = self - def accept_visitor(self, visitor): - pass class ColumnDefault(DefaultGenerator): + """A plain default value on a column. this could correspond to a constant, + a callable function, or a SQL clause.""" def __init__(self, arg): self.arg = arg def accept_visitor(self, visitor): + """calls the visit_column_default method on the given visitor.""" return visitor.visit_column_default(self) class Sequence(DefaultGenerator): @@ -311,27 +402,46 @@ class Sequence(DefaultGenerator): self.increment = increment self.optional=optional def accept_visitor(self, visitor): + """calls the visit_seauence method on the given visitor.""" return visitor.visit_sequence(self) class SchemaEngine(object): - """a factory object used to create implementations for schema objects""" + """a factory object used to create implementations for schema objects. This object + is the ultimate base class for the engine.SQLEngine class.""" def tableimpl(self, table): + """returns a new implementation object for a Table (usually sql.TableImpl)""" raise NotImplementedError() def columnimpl(self, column): + """returns a new implementation object for a Column (usually sql.ColumnImpl)""" raise NotImplementedError() def reflecttable(self, table): + """given a table, will query the database and populate its Column and ForeignKey + objects.""" raise NotImplementedError() class SchemaVisitor(object): - """base class for an object that traverses across Schema objects""" - - def visit_schema(self, schema):pass - def visit_table(self, table):pass - def visit_column(self, column):pass - def visit_foreign_key(self, join):pass - def visit_index(self, index):pass - def visit_column_default(self, default):pass - def visit_sequence(self, sequence):pass + """base class for an object that traverses across Schema structures.""" + def visit_schema(self, schema): + """visit a generic SchemaItem""" + pass + def visit_table(self, table): + """visit a Table.""" + pass + def visit_column(self, column): + """visit a Column.""" + pass + def visit_foreign_key(self, join): + """visit a ForeignKey.""" + pass + def visit_index(self, index): + """visit an Index (not implemented yet).""" + pass + def visit_column_default(self, default): + """visit a ColumnDefault.""" + pass + def visit_sequence(self, sequence): + """visit a Sequence.""" + pass \ No newline at end of file -- 2.47.2