Codebase list python-repoze.tm2 / b8a73c4
Imported Upstream version 1.0a4 SVN-Git Migration 8 years ago
25 changed file(s) with 1458 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 1.0a4 (2009/1/6)
1 ================
2
3 - RESTify CHANGES, move docs in README.txt into Sphinx.
4
5 - Remove ``setup.cfg`` (all dependencies available via PyPI).
6
7 - Synchronization point with ``repoze.tm`` (0.9).
8
9 1.0a3
10 =====
11
12 Allow ``commit_veto`` hook to be specified within Paste config, ala::
13
14 [filter:tm]
15 use = repoze.tm:make_tm
16 commit_veto = some.package:myfunction
17
18 ``myfunction`` should take three args: environ, status, headers and
19 should return True if the txn should be aborted, False if it should be
20 committed.
21
22 Initial PyPI release.
23
24 1.0a2 (2008/7/15)
25 =================
26
27 - Provide "commit_veto" hook point (contributed by Alberto Valverde).
28
29 - Point easy_install at http://dist.repoze.org/tm2/dev/simple via setup.cfg.
30
31 1.0a1
32 =====
33
34 - Fork point: we've created repoze.tm2, which is repoze.tm that has a
35 dependency only on the 'transaction' package instead of all of ZODB.
36
37 - Better documentation for non-Zope usage in README.txt.
38
39 0.8
40 ===
41
42 - Relaxed requirement for ZODB 3.7.2, since we might need to use the
43 package with other verions. Note that the tests which depend on
44 transaction having "doom" semantics don't work with 3.7.2, anyway.
45
46 0.7
47 ===
48
49 - Depend on PyPI release of ZODB 3.7.2. Upgrade to this by doing
50 bin/easy_install -U 'ZODB3 >= 3.7.1, < 3.8.0a' if necessary.
51
52 0.6
53 ===
54
55 - after_end.register and after_end.unregister must now be passed a
56 transaction object rather than a WSGI environment to avoid the
57 possibility that the WSGI environment used by a child participating
58 in transaction management won't be the same one used by the
59 repoze.tm package.
60
61 - repoze.tm now inserts a key into the WSGI environment
62 (``repoze.tm.active``) if it's active in the WSGI pipeline. An API
63 function, repoze.tm:isActive can be called with a single argument,
64 the WSGI environment, to check if the middleware is active.
65
66 0.5
67 ===
68
69 - Depend on rerolled ZODB 3.7.1 instead of zopelib.
70
71 - Add license and copyright, change trove classifiers.
72
73 0.4
74 ===
75
76 - Depend on zopelib rather than ZODB 3.8.0b3 distribution, because the
77 ZODB distribution pulls in various packages (zope.interface and ZEO
78 most notably) that are incompatible with stock Zope 2.10.4 apps and
79 older sandboxes. We'll need to revisit this.
80
81 0.3
82 ===
83
84 - Provide limited compatibility for older transaction package versions
85 which don't support the 'transaction.isDoomed' API.
86
87 0.2
88 ===
89
90 - Provide after_end API for registering callbacks at transaction end.
91
92 0.1
93 ===
94
95 - Initial Release
0 Copyright (c) 2007 Agendaless Consulting and Contributors.
1 (http://www.agendaless.com), All Rights Reserved
2
0 License
1
2 A copyright notice accompanies this license document that identifies
3 the copyright holders.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are
7 met:
8
9 1. Redistributions in source code must retain the accompanying
10 copyright notice, this list of conditions, and the following
11 disclaimer.
12
13 2. Redistributions in binary form must reproduce the accompanying
14 copyright notice, this list of conditions, and the following
15 disclaimer in the documentation and/or other materials provided
16 with the distribution.
17
18 3. Names of the copyright holders must not be used to endorse or
19 promote products derived from this software without prior
20 written permission from the copyright holders.
21
22 4. If any files are modified, you must cause the modified files to
23 carry prominent notices stating that you changed the files and
24 the date of any change.
25
26 Disclaimer
27
28 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND
29 ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
30 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
31 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
32 HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
33 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
34 TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
35 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
36 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
37 TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
38 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
39 SUCH DAMAGE.
40
0 Metadata-Version: 1.0
1 Name: repoze.tm2
2 Version: 1.0a4
3 Summary: Zope-like transaction manager via WSGI middleware
4 Home-page: http://www.repoze.org
5 Author: Agendaless Consulting
6 Author-email: repoze-dev@lists.repoze.org
7 License: BSD-derived (http://www.repoze.org/LICENSE.txt)
8 Description: repoze.tm2 (Transaction Manager)
9 ===============================
10
11 Middleware which uses the ZODB transaction manager to wrap a call to
12 its pipeline children inside a transaction. This is a fork of the
13 ``repoze.tm`` package which depends only on the ``transaction``
14 package rather than the entirety of ZODB (for users who don't rely on ZODB).
15
16 See docs/index.rst for documentation.
17
18
19 1.0a4 (2009/1/6)
20 ================
21
22 - RESTify CHANGES, move docs in README.txt into Sphinx.
23
24 - Remove ``setup.cfg`` (all dependencies available via PyPI).
25
26 - Synchronization point with ``repoze.tm`` (0.9).
27
28 1.0a3
29 =====
30
31 Allow ``commit_veto`` hook to be specified within Paste config, ala::
32
33 [filter:tm]
34 use = repoze.tm:make_tm
35 commit_veto = some.package:myfunction
36
37 ``myfunction`` should take three args: environ, status, headers and
38 should return True if the txn should be aborted, False if it should be
39 committed.
40
41 Initial PyPI release.
42
43 1.0a2 (2008/7/15)
44 =================
45
46 - Provide "commit_veto" hook point (contributed by Alberto Valverde).
47
48 - Point easy_install at http://dist.repoze.org/tm2/dev/simple via setup.cfg.
49
50 1.0a1
51 =====
52
53 - Fork point: we've created repoze.tm2, which is repoze.tm that has a
54 dependency only on the 'transaction' package instead of all of ZODB.
55
56 - Better documentation for non-Zope usage in README.txt.
57
58 0.8
59 ===
60
61 - Relaxed requirement for ZODB 3.7.2, since we might need to use the
62 package with other verions. Note that the tests which depend on
63 transaction having "doom" semantics don't work with 3.7.2, anyway.
64
65 0.7
66 ===
67
68 - Depend on PyPI release of ZODB 3.7.2. Upgrade to this by doing
69 bin/easy_install -U 'ZODB3 >= 3.7.1, < 3.8.0a' if necessary.
70
71 0.6
72 ===
73
74 - after_end.register and after_end.unregister must now be passed a
75 transaction object rather than a WSGI environment to avoid the
76 possibility that the WSGI environment used by a child participating
77 in transaction management won't be the same one used by the
78 repoze.tm package.
79
80 - repoze.tm now inserts a key into the WSGI environment
81 (``repoze.tm.active``) if it's active in the WSGI pipeline. An API
82 function, repoze.tm:isActive can be called with a single argument,
83 the WSGI environment, to check if the middleware is active.
84
85 0.5
86 ===
87
88 - Depend on rerolled ZODB 3.7.1 instead of zopelib.
89
90 - Add license and copyright, change trove classifiers.
91
92 0.4
93 ===
94
95 - Depend on zopelib rather than ZODB 3.8.0b3 distribution, because the
96 ZODB distribution pulls in various packages (zope.interface and ZEO
97 most notably) that are incompatible with stock Zope 2.10.4 apps and
98 older sandboxes. We'll need to revisit this.
99
100 0.3
101 ===
102
103 - Provide limited compatibility for older transaction package versions
104 which don't support the 'transaction.isDoomed' API.
105
106 0.2
107 ===
108
109 - Provide after_end API for registering callbacks at transaction end.
110
111 0.1
112 ===
113
114 - Initial Release
115
116 Keywords: web application server wsgi zope repoze
117 Platform: UNKNOWN
118 Classifier: Development Status :: 3 - Alpha
119 Classifier: Intended Audience :: Developers
120 Classifier: Programming Language :: Python
121 Classifier: Topic :: Internet :: WWW/HTTP
122 Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
123 Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware
0 repoze.tm2 (Transaction Manager)
1 ===============================
2
3 Middleware which uses the ZODB transaction manager to wrap a call to
4 its pipeline children inside a transaction. This is a fork of the
5 ``repoze.tm`` package which depends only on the ``transaction``
6 package rather than the entirety of ZODB (for users who don't rely on ZODB).
7
8 See docs/index.rst for documentation.
0 Investigate using the absurd API for transaction synchronizers e.g.:
1
2 class RepozeTMSync:
3 def beforeCompletion(self, transaction):
4 for thing in self.before:
5 thing()
6
7 def afterCompletion(self, transaction):
8 for thing in self.after:
9 thing()
Binary diff not shown
0 @import url('default.css');
1 body {
2 background-color: #006339;
3 }
4
5 div.document {
6 background-color: #dad3bd;
7 }
8
9 div.sphinxsidebar h3,h4,h5,li,a {
10 color: #127c56 !important;
11 }
12
13 div.related {
14 color: #dad3bd;
15 background-color: #00744a;
16 }
17
18 div.related a {
19 color: #dad3bd;
20 }
0 # Makefile for Sphinx documentation
1 #
2
3 # You can set these variables from the command line.
4 SPHINXOPTS =
5 SPHINXBUILD = sphinx-build
6 PAPER =
7
8 # Internal variables.
9 PAPEROPT_a4 = -D latex_paper_size=a4
10 PAPEROPT_letter = -D latex_paper_size=letter
11 ALLSPHINXOPTS = -d .build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
12
13 .PHONY: help clean html web pickle htmlhelp latex changes linkcheck
14
15 help:
16 @echo "Please use \`make <target>' where <target> is one of"
17 @echo " html to make standalone HTML files"
18 @echo " pickle to make pickle files (usable by e.g. sphinx-web)"
19 @echo " htmlhelp to make HTML files and a HTML help project"
20 @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
21 @echo " changes to make an overview over all changed/added/deprecated items"
22 @echo " linkcheck to check all external links for integrity"
23
24 clean:
25 -rm -rf .build/*
26
27 html:
28 mkdir -p .build/html .build/doctrees
29 $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) .build/html
30 @echo
31 @echo "Build finished. The HTML pages are in .build/html."
32
33 pickle:
34 mkdir -p .build/pickle .build/doctrees
35 $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) .build/pickle
36 @echo
37 @echo "Build finished; now you can process the pickle files or run"
38 @echo " sphinx-web .build/pickle"
39 @echo "to start the sphinx-web server."
40
41 web: pickle
42
43 htmlhelp:
44 mkdir -p .build/htmlhelp .build/doctrees
45 $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) .build/htmlhelp
46 @echo
47 @echo "Build finished; now you can run HTML Help Workshop with the" \
48 ".hhp project file in .build/htmlhelp."
49
50 latex:
51 mkdir -p .build/latex .build/doctrees
52 $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) .build/latex
53 @echo
54 @echo "Build finished; the LaTeX files are in .build/latex."
55 @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
56 "run these through (pdf)latex."
57
58 changes:
59 mkdir -p .build/changes .build/doctrees
60 $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) .build/changes
61 @echo
62 @echo "The overview file is in .build/changes."
63
64 linkcheck:
65 mkdir -p .build/linkcheck .build/doctrees
66 $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) .build/linkcheck
67 @echo
68 @echo "Link check complete; look for any errors in the above output " \
69 "or in .build/linkcheck/output.txt."
0 :mod:`repoze.tm` Change History
1 ===============================
2
3 .. include:: ../CHANGES.txt
0 # -*- coding: utf-8 -*-
1 #
2 # repoze.tm documentation build configuration file
3 #
4 # This file is execfile()d with the current directory set to its containing
5 # dir.
6 #
7 # The contents of this file are pickled, so don't put values in the
8 # namespace that aren't pickleable (module imports are okay, they're
9 # removed automatically).
10 #
11 # All configuration values have a default value; values that are commented
12 # out serve to show the default value.
13
14 import sys, os
15
16 # If your extensions are in another directory, add it here. If the
17 # directory is relative to the documentation root, use os.path.abspath to
18 # make it absolute, like shown here.
19 #sys.path.append(os.path.abspath('some/directory'))
20
21 parent = os.path.dirname(os.path.dirname(__file__))
22 sys.path.append(os.path.abspath(parent))
23 wd = os.getcwd()
24 os.chdir(parent)
25 os.system('%s setup.py test -q' % sys.executable)
26 os.chdir(wd)
27
28 for item in os.listdir(parent):
29 if item.endswith('.egg'):
30 sys.path.append(os.path.join(parent, item))
31
32 # General configuration
33 # ---------------------
34
35 # Add any Sphinx extension module names here, as strings. They can be
36 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
37 extensions = ['sphinx.ext.autodoc']
38
39 # Add any paths that contain templates here, relative to this directory.
40 templates_path = ['.templates']
41
42 # The suffix of source filenames.
43 source_suffix = '.rst'
44
45 # The master toctree document.
46 master_doc = 'index'
47
48 # General substitutions.
49 project = 'repoze.tm'
50 copyright = '2008, Repoze Developers <repoze-dev@lists.repoze.org>'
51
52 # The default replacements for |version| and |release|, also used in various
53 # other places throughout the built documents.
54 #
55 # The short X.Y version.
56 version = '1.0a4'
57 # The full version, including alpha/beta/rc tags.
58 release = '1.0a4'
59
60 # There are two options for replacing |today|: either, you set today to
61 # some non-false value, then it is used:
62 #today = ''
63 # Else, today_fmt is used as the format for a strftime call.
64 today_fmt = '%B %d, %Y'
65
66 # List of documents that shouldn't be included in the build.
67 #unused_docs = []
68
69 # List of directories, relative to source directories, that shouldn't be
70 # searched for source files.
71 #exclude_dirs = []
72
73 # The reST default role (used for this markup: `text`) to use for all
74 # documents.
75 #default_role = None
76
77 # If true, '()' will be appended to :func: etc. cross-reference text.
78 #add_function_parentheses = True
79
80 # If true, the current module name will be prepended to all description
81 # unit titles (such as .. function::).
82 #add_module_names = True
83
84 # If true, sectionauthor and moduleauthor directives will be shown in the
85 # output. They are ignored by default.
86 #show_authors = False
87
88 # The name of the Pygments (syntax highlighting) style to use.
89 pygments_style = 'sphinx'
90
91
92 # Options for HTML output
93 # -----------------------
94
95 # The style sheet to use for HTML and HTML Help pages. A file of that name
96 # must exist either in Sphinx' static/ path, or in one of the custom paths
97 # given in html_static_path.
98 html_style = 'repoze.css'
99
100 # The name for this set of Sphinx documents. If None, it defaults to
101 # "<project> v<release> documentation".
102 #html_title = None
103
104 # A shorter title for the navigation bar. Default is the same as
105 # html_title.
106 #html_short_title = None
107
108 # The name of an image file (within the static path) to place at the top of
109 # the sidebar.
110 html_logo = '.static/logo_hi.gif'
111
112 # The name of an image file (within the static path) to use as favicon of
113 # the docs. This file should be a Windows icon file (.ico) being 16x16 or
114 # 32x32 pixels large.
115 #html_favicon = None
116
117 # Add any paths that contain custom static files (such as style sheets)
118 # here, relative to this directory. They are copied after the builtin
119 # static files, so a file named "default.css" will overwrite the builtin
120 # "default.css".
121 html_static_path = ['.static']
122
123 # If not '', a 'Last updated on:' timestamp is inserted at every page
124 # bottom, using the given strftime format.
125 html_last_updated_fmt = '%b %d, %Y'
126
127 # If true, SmartyPants will be used to convert quotes and dashes to
128 # typographically correct entities.
129 #html_use_smartypants = True
130
131 # Custom sidebar templates, maps document names to template names.
132 #html_sidebars = {}
133
134 # Additional templates that should be rendered to pages, maps page names to
135 # template names.
136 #html_additional_pages = {}
137
138 # If false, no module index is generated.
139 #html_use_modindex = True
140
141 # If false, no index is generated.
142 #html_use_index = True
143
144 # If true, the index is split into individual pages for each letter.
145 #html_split_index = False
146
147 # If true, the reST sources are included in the HTML build as
148 # _sources/<name>.
149 #html_copy_source = True
150
151 # If true, an OpenSearch description file will be output, and all pages
152 # will contain a <link> tag referring to it. The value of this option must
153 # be the base URL from which the finished HTML is served.
154 #html_use_opensearch = ''
155
156 # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
157 #html_file_suffix = ''
158
159 # Output file base name for HTML help builder.
160 htmlhelp_basename = 'tmdoc'
161
162
163 # Options for LaTeX output
164 # ------------------------
165
166 # The paper size ('letter' or 'a4').
167 #latex_paper_size = 'letter'
168
169 # The font size ('10pt', '11pt' or '12pt').
170 #latex_font_size = '10pt'
171
172 # Grouping the document tree into LaTeX files. List of tuples
173 # (source start file, target name, title,
174 # author, document class [howto/manual]).
175 latex_documents = [
176 ('index', 'tm.tex', 'repoze.tm Documentation',
177 'Repoze Developers', 'manual'),
178 ]
179
180 # The name of an image file (relative to this directory) to place at the
181 # top of the title page.
182 latex_logo = '.static/logo_hi.gif'
183
184 # For "manual" documents, if this is true, then toplevel headings are
185 # parts, not chapters.
186 #latex_use_parts = False
187
188 # Additional stuff for the LaTeX preamble.
189 #latex_preamble = ''
190
191 # Documents to append as an appendix to all manuals.
192 #latex_appendices = []
193
194 # If false, no module index is generated.
195 #latex_use_modindex = True
0 Documentation for repoze.tm2 (``repoze.tm`` fork)
1 =================================================
2
3 Overview
4 --------
5
6 :mod:`repoze.tm2` is WSGI middleware which uses the ``ZODB`` package's
7 transaction manager to wrap a call to its pipeline children inside a
8 transaction.
9
10 .. note:: :mod:`repoze.tm2` is equivalent to the :mod:`repoze.tm`
11 package (it was forked from :mod:`repoze.tm`), except it has a
12 dependency only on the ``transaction`` package rather than a
13 dependency on the entire ``ZODB3`` package (``ZODB3`` 3.8 ships
14 with the ``transaction`` package right now). It is an error to
15 install both repoze.tm and repoze.tm2 into the same environment, as
16 they provide the same entry points and import points.
17
18 Behavior
19 --------
20
21 When this middleware is present in the WSGI pipeline, a new
22 transaction will be started once a WSGI request makes it to the
23 :mod:`repoze.tm` middleware. If any downstream application raises an
24 exception, the transaction will be aborted, otherwise the transaction
25 will be committed. Any "data managers" participating in the
26 transaction will be aborted or committed respectively. A ZODB
27 "connection" is an example of a data manager.
28
29 Since this is a tiny wrapper around the ZODB transaction module, and
30 the ZODB transaction module is "thread-safe" (in the sense that its
31 default policy is to create a new transaction for each thread), it
32 should be fine to use in either multiprocess or multithread
33 environments.
34
35 Purpose and Usage
36 -----------------
37
38 The ZODB transaction manager is a completely generic transaction
39 manager. It can be used independently of the actual "object database"
40 part of ZODB. One of the purposes of creating :mod:`repoze.tm` was to
41 allow for systems other than Zope to make use of two-phase commit
42 transactions in a WSGI context.
43
44 Let's pretend we have an existing system that places data into a
45 relational database when someone submits a form. The system has been
46 running for a while, and our code handles the database commit and
47 rollback for us explicitly; if the form processing succeeds, our code
48 commits the database transaction. If it fails, our code rolls back
49 the database transaction. Everything works fine.
50
51 Now our customer asks us if we can also place data into another
52 separate relational database when the form is submitted as well as
53 continuing to place data in the original database. We need to put
54 data in both databases, and if we want to ensure that no records exist
55 in one that don't exist in the other as a result of a form submission,
56 we're going to need to do a pretty complicated commit and rollback
57 dance in each place in our code which needs to write to both data
58 stores. We can't just blindly commit one, then commit the other,
59 because the second commit may fail and we'll be left with "orphan"
60 data in the first, and we'll either need to clean it up manually or
61 leave it there to trip over later.
62
63 A transaction manager helps us ensure that no data is committed to
64 either database unless both participating data stores can commit.
65 Once the transaction manager determines that both data stores are
66 willing to commit, it will commit them both in very quick succession,
67 so that there is only a minimal chance that the second data store will
68 fail to commit. If it does, the system will raise an error that makes
69 it impossible to begin another transaction until the system restarts,
70 so the damage is minimized. In practice, this error almost never
71 occurs unless the code that interfaces the database to the transaction
72 manager has a bug.
73
74 Adding :mod:`repoze.tm` To Your WSGI Pipeline
75 ---------------------------------------------
76
77 Via ``PasteDeploy`` .INI configuration::
78
79 [pipeline:main]
80 pipeline =
81 egg:repoze.tm#tm
82 myapp
83
84 Via Python:
85
86 .. code-block:: python
87
88 from otherplace import mywsgiapp
89
90 from repoze.tm import TM
91 new_wsgiapp = TM(mywsgiapp)
92
93 Mocking Up A Data Manager
94 -------------------------
95
96 The piece of code you need to write in order to participate in ZODB
97 transactions is called a 'data manager'. It is typically a class.
98 Here's the interface that you need to implement in the code for a data
99 manager:
100
101 .. code-block:: python
102
103 class IDataManager(zope.interface.Interface):
104 """Objects that manage transactional storage.
105
106 These objects may manage data for other objects, or they
107 may manage non-object storages, such as relational
108 databases. For example, a ZODB.Connection.
109
110 Note that when some data is modified, that data's data
111 manager should join a transaction so that data can be
112 committed when the user commits the transaction. """
113
114 transaction_manager = zope.interface.Attribute(
115 """The transaction manager (TM) used by this data
116 manager.
117
118 This is a public attribute, intended for read-only
119 use. The value is an instance of ITransactionManager,
120 typically set by the data manager's constructor. """
121 )
122
123 def abort(transaction):
124 """Abort a transaction and forget all changes.
125
126 Abort must be called outside of a two-phase commit.
127
128 Abort is called by the transaction manager to abort transactions
129 that are not yet in a two-phase commit.
130 """
131
132 # Two-phase commit protocol. These methods are called by
133 # the ITransaction object associated with the transaction
134 # being committed. The sequence of calls normally follows
135 # this regular expression: tpc_begin commit tpc_vote
136 # (tpc_finish | tpc_abort)
137
138 def tpc_begin(transaction):
139
140 """Begin commit of a transaction, starting the
141 two-phase commit.
142
143 transaction is the ITransaction instance associated with the
144 transaction being committed.
145 """
146
147 def commit(transaction):
148
149 """Commit modifications to registered objects.
150
151 Save changes to be made persistent if the transaction
152 commits (if tpc_finish is called later). If tpc_abort
153 is called later, changes must not persist.
154
155 This includes conflict detection and handling. If no
156 conflicts or errors occur, the data manager should be
157 prepared to make the changes persist when tpc_finish
158 is called. """
159
160 def tpc_vote(transaction):
161 """Verify that a data manager can commit the transaction.
162
163 This is the last chance for a data manager to vote 'no'. A
164 data manager votes 'no' by raising an exception.
165
166 transaction is the ITransaction instance associated with the
167 transaction being committed.
168 """
169
170 def tpc_finish(transaction):
171
172 """Indicate confirmation that the transaction is done.
173
174 Make all changes to objects modified by this
175 transaction persist.
176
177 transaction is the ITransaction instance associated
178 with the transaction being committed.
179
180 This should never fail. If this raises an exception,
181 the database is not expected to maintain consistency;
182 it's a serious error. """
183
184 def tpc_abort(transaction):
185
186 """Abort a transaction.
187
188 This is called by a transaction manager to end a
189 two-phase commit on the data manager. Abandon all
190 changes to objects modified by this transaction.
191
192 transaction is the ITransaction instance associated
193 with the transaction being committed.
194
195 This should never fail.
196 """
197
198 def sortKey():
199
200 """Return a key to use for ordering registered
201 DataManagers.
202
203 ZODB uses a global sort order to prevent deadlock when
204 it commits transactions involving multiple resource
205 managers. The resource manager must define a
206 sortKey() method that provides a global ordering for
207 resource managers. """
208 # Alternate version:
209 #"""Return a consistent sort key for this connection.
210 # #This allows ordering multiple connections that use
211 the same storage in #a consistent manner. This is
212 unique for the lifetime of a connection, #which is
213 good enough to avoid ZEO deadlocks. #"""
214
215 Let's implement a mock data manager. Our mock data manager will write
216 data to a file if the transaction commits. It will not write data to
217 a file if the transaction aborts:
218
219 .. code-block:: python
220
221 class MockDataManager:
222
223 transaction_manager = None
224
225 def __init__(self, data, path):
226 self.data = data
227 self.path = path
228
229 def abort(self, transaction):
230 pass
231
232 def tpc_begin(self, transaction):
233 pass
234
235 def commit(self, transaction):
236 import tempfile
237 self.tempfn = tempfile.mktemp()
238 temp = open(self.tempfn, 'wb')
239 temp.write(self.data)
240 temp.flush()
241 temp.close()
242
243 def tpc_vote(self, transaction):
244 import os
245 if not os.path.exists(self.tempfn):
246 raise ValueError('%s doesnt exist' % self.tempfn)
247 if os.path.exists(self.path):
248 raise ValueError('file already exists')
249
250 def tpc_finish(self, transaction):
251 import os
252 os.rename(self.tempfn, self.path)
253
254 def tpc_abort(self, transaction):
255 import os
256 try:
257 os.remove(self.tempfn)
258 except OSError:
259 pass
260
261 We can create a datamanager and join it into the currently running
262 transaction:
263
264 .. code-block:: python
265
266 dm = MockDataManager('heres the data', '/tmp/file')
267 import transaction
268 t = transaction.get()
269 t.join(dm)
270
271 When the transaction commits, a file will be placed in '/tmp/file'
272 containing 'heres the data'. If the transaction aborts, no file will
273 be created.
274
275 If more than one data manager is joined to the transaction, all of
276 them must be willing to commit or the entire transaction is aborted
277 and none of them commit. If you can imagine creating two of the mock
278 data managers we've made within application code, if one has a problem
279 during "tpc_vote", neither will actually write a file to the ultimate
280 location, and thus your application consistency is maintained.
281
282 Integrating Your Data Manager With :mod:`repoze.tm`
283 ---------------------------------------------------
284
285 The :mod:`repoze.tm` transaction management machinery has an implicit
286 policy. When it is in the WSGI pipeline, a transaction is started
287 when the middleware is invoked. Thus, in your application code,
288 calling "import transaction; transaction.get()" will return the
289 transaction object created by the :mod:`repoze.tm` middleware. You
290 needn't call t.commit() or t.abort() within your application code.
291 You only need to call t.join, to register your data manager with the
292 transaction. :mod:`repoze.tm` will abort the transaction if an
293 exception is raised by your application code or lower middleware
294 before it returns a WSGI response. If your application or lower
295 middleware raises an exception, the transaction is aborted.
296
297 Cleanup
298 -------
299
300 When a :mod:`repoze.tm` is in the WSGI pipeline, a boolean key is
301 present in the environment (``repoze.tm.active``). A utility function
302 named isActive can be imported from the :mod:`repoze.tm` package and
303 passed the WSGI environment to check for activation:
304
305 .. code-block:: python
306
307 from repoze.tm import isActive
308 tm_active = isActive(wsgi_environment)
309
310 If an application needs to perform an action after a transaction ends,
311 the "after_end" registry may be used to register a callback. The
312 after_end.register function accepts a callback (accepting no
313 arguments) and a transaction instance:
314
315 .. code-block:: python
316
317 from repoze.tm import after_end
318 import transaction
319 t = transaction.get() # the current transaction
320 def func():
321 pass # close a connection, etc
322 after_end.register(func, t)
323
324 "after_end" callbacks should only be registered when the transaction
325 manager is active, or a memory leak will result (registration cleanup
326 happens only on transaction commit or abort, which is managed by
327 :mod:`repoze.tm` while in the pipeline).
328
329 Further Documentation
330 ---------------------
331
332 Many database adapters written for Zope (e.g. for Postgres, MySQL,
333 etc) use this transaction manager, so it should be possible to take a
334 look in these places to see how to implement a more real-world
335 transaction-aware database connector that uses this module in non-Zope
336 applications:
337
338 - http://svn.zope.org/ZODB/branches/3.7/src/transaction/
339
340 - http://svn.zope.org/ZODB/branches/3.8/src/transaction/
341
342 - http://mysql-python.sourceforge.net/ (ZMySQLDA)
343
344 - http://www.initd.org/svn/psycopg/psycopg2/trunk/ (ZPsycoPGDA)
345
346 Contacting
347 ----------
348
349 The `repoze-dev maillist
350 <http://lists.repoze.org/mailman/listinfo/repoze-dev>`_ should be used
351 for communications about this software. Put the overview of the
352 purpose of the package here.
353
354
355 .. toctree::
356 :maxdepth: 2
357
358 changes
359
360
361 Indices and tables
362 ------------------
363
364 * :ref:`genindex`
365 * :ref:`modindex`
366 * :ref:`search`
0 # repoze package
1 __import__('pkg_resources').declare_namespace(__name__)
0 # repoze TransactionManager WSGI middleware
1 import transaction
2
3 ekey = 'repoze.tm.active'
4
5 class TM:
6 def __init__(self, application, commit_veto=None):
7 self.application = application
8 self.commit_veto = commit_veto
9
10 def __call__(self, environ, start_response):
11 environ[ekey] = True
12 transaction.begin()
13 ctx = {}
14 def save_status_and_headers(status, headers, exc_info=None):
15 ctx.update(status=status, headers=headers)
16 return start_response(status, headers, exc_info)
17 try:
18 result = self.application(environ, save_status_and_headers)
19 except:
20 self.abort()
21 raise
22 else:
23 # ZODB 3.8 + has isDoomed
24 if hasattr(transaction, 'isDoomed') and transaction.isDoomed():
25 self.abort()
26 if self.commit_veto is not None:
27 try:
28 if self.commit_veto(environ, ctx['status'], ctx['headers']):
29 self.abort()
30 except:
31 self.abort()
32 raise
33 else:
34 self.commit()
35 else:
36 self.commit()
37 return result
38
39 def commit(self):
40 t = transaction.get()
41 t.commit()
42 after_end.cleanup(t)
43
44 def abort(self):
45 t = transaction.get()
46 t.abort()
47 after_end.cleanup(t)
48
49 def isActive(environ):
50 if ekey in environ:
51 return True
52 return False
53
54 # Callback registry API helper class
55 class AfterEnd:
56 key = '_repoze_tm_afterend'
57 def register(self, func, txn):
58 funcs = getattr(txn, self.key, None)
59 if funcs is None:
60 funcs = []
61 setattr(txn, self.key, funcs)
62 funcs.append(func)
63
64 def unregister(self, func, txn):
65 funcs = getattr(txn, self.key, None)
66 if funcs is None:
67 return
68 new = []
69 for f in funcs:
70 if f is func:
71 continue
72 new.append(f)
73 if new:
74 setattr(txn, self.key, new)
75 else:
76 delattr(txn, self.key)
77
78 def cleanup(self, txn):
79 funcs = getattr(txn, self.key, None)
80 if funcs is not None:
81 for func in funcs:
82 func()
83 delattr(txn, self.key)
84
85 # singleton, importable by other modules
86 after_end = AfterEnd()
87
88 def make_tm(app, global_conf, commit_veto=None):
89 from pkg_resources import EntryPoint
90 if commit_veto is not None:
91 commit_veto = EntryPoint.parse('x=%s' % commit_veto).load(False)
92 return TM(app, commit_veto)
93
0 import unittest
1 import sys
2 import transaction
3
4 class TestTM(unittest.TestCase):
5 def _getTargetClass(self):
6 from repoze.tm import TM
7 return TM
8
9 def _start_response(self, status, headers, exc_info=None):
10 pass
11
12 def _makeOne(self, app, commit_veto=None):
13 return self._getTargetClass()(app, commit_veto)
14
15 def test_ekey_inserted(self):
16 app = DummyApplication()
17 tm = self._makeOne(app)
18 from repoze.tm import ekey
19 env = {}
20 tm(env, self._start_response)
21 self.failUnless(ekey in env)
22
23 def test_committed(self):
24 resource = DummyResource()
25 app = DummyApplication(resource)
26 tm = self._makeOne(app)
27 result = tm({}, self._start_response)
28 self.assertEqual(result, ['hello'])
29 self.assertEqual(resource.committed, True)
30 self.assertEqual(resource.aborted, False)
31
32 def test_aborted_via_doom(self):
33 resource = DummyResource()
34 app = DummyApplication(resource, doom=True)
35 tm = self._makeOne(app)
36 result = tm({}, self._start_response)
37 self.assertEqual(result, ['hello'])
38 self.assertEqual(transaction.isDoomed(), False)
39 self.assertEqual(resource.committed, False)
40 self.assertEqual(resource.aborted, True)
41
42 def test_aborted_via_exception(self):
43 resource = DummyResource()
44 app = DummyApplication(resource, exception=True)
45 tm = self._makeOne(app)
46 self.assertRaises(ValueError, tm, {}, self._start_response)
47 self.assertEqual(resource.committed, False)
48 self.assertEqual(resource.aborted, True)
49
50 def test_aborted_via_exception_and_doom(self):
51 resource = DummyResource()
52 app = DummyApplication(resource, exception=True, doom=True)
53 tm = self._makeOne(app)
54 self.assertRaises(ValueError, tm, {}, self._start_response)
55 self.assertEqual(resource.committed, False)
56 self.assertEqual(resource.aborted, True)
57
58 def test_aborted_via_commit_veto(self):
59 resource = DummyResource()
60 app = DummyApplication(resource, status="403 Forbidden")
61 def commit_veto(environ, status, headers):
62 self.failUnless(isinstance(environ, dict),
63 "environ is not passed properly")
64 self.failUnless(isinstance(headers, list),
65 "headers are not passed properly")
66 self.failUnless(isinstance(status, str),
67 "status is not passed properly")
68 return not (200 <= int(status.split()[0]) < 400)
69 tm = self._makeOne(app, commit_veto)
70 tm({}, self._start_response)
71 self.assertEqual(resource.committed, False)
72 self.assertEqual(resource.aborted, True)
73
74 def test_committed_via_commit_veto_exception(self):
75 resource = DummyResource()
76 app = DummyApplication(resource, status="403 Forbidden")
77 def commit_veto(environ, status, headers):
78 return None
79 tm = self._makeOne(app, commit_veto)
80 tm({}, self._start_response)
81 self.assertEqual(resource.committed, True)
82 self.assertEqual(resource.aborted, False)
83
84 def test_aborted_via_commit_veto_exception(self):
85 resource = DummyResource()
86 app = DummyApplication(resource, status="403 Forbidden")
87 def commit_veto(environ, status, headers):
88 raise ValueError('foo')
89 tm = self._makeOne(app, commit_veto)
90 self.assertRaises(ValueError, tm, {}, self._start_response)
91 self.assertEqual(resource.committed, False)
92 self.assertEqual(resource.aborted, True)
93
94 def test_cleanup_on_commit(self):
95 dummycalled = []
96 def dummy():
97 dummycalled.append(True)
98 env = {}
99 resource = DummyResource()
100 app = DummyApplication(resource, exception=False, doom=False,
101 register=dummy)
102 tm = self._makeOne(app)
103 tm(env, self._start_response)
104 self.assertEqual(resource.committed, True)
105 self.assertEqual(resource.aborted, False)
106 self.assertEqual(dummycalled, [True])
107
108 def test_cleanup_on_abort(self):
109 dummycalled = []
110 def dummy():
111 dummycalled.append(True)
112 env = {}
113 resource = DummyResource()
114 app = DummyApplication(resource, exception=True, doom=False,
115 register=dummy)
116 tm = self._makeOne(app)
117 self.assertRaises(ValueError, tm, env, self._start_response)
118 self.assertEqual(resource.committed, False)
119 self.assertEqual(resource.aborted, True)
120 self.assertEqual(dummycalled, [True])
121
122 class TestAfterEnd(unittest.TestCase):
123 def _getTargetClass(self):
124 from repoze.tm import AfterEnd
125 return AfterEnd
126
127 def _makeOne(self):
128 return self._getTargetClass()()
129
130 def test_register(self):
131 registry = self._makeOne()
132 func = lambda *x: None
133 txn = DummyTransaction()
134 registry.register(func, txn)
135 self.assertEqual(getattr(txn, registry.key), [func])
136
137 def test_unregister_exists(self):
138 registry = self._makeOne()
139 func = lambda *x: None
140 txn = DummyTransaction()
141 registry.register(func, txn)
142 self.assertEqual(getattr(txn, registry.key), [func])
143 registry.unregister(func, txn)
144 self.failIf(hasattr(txn, registry.key))
145
146 def test_unregister_notexists(self):
147 registry = self._makeOne()
148 func = lambda *x: None
149 txn = DummyTransaction()
150 setattr(txn, registry.key, [None])
151 registry.unregister(func, txn)
152 self.assertEqual(getattr(txn, registry.key), [None])
153
154 class UtilityFunctionTests(unittest.TestCase):
155 def test_isActive(self):
156 from repoze.tm import ekey
157 from repoze.tm import isActive
158 self.assertEqual(isActive({ekey:True}), True)
159 self.assertEqual(isActive({}), False)
160
161 class TestMakeTM(unittest.TestCase):
162 def test_make_tm_withveto(self):
163 from repoze.tm import make_tm
164 tm = make_tm(DummyApplication(), {}, 'repoze.tm.tests:fakeveto')
165 self.assertEqual(tm.commit_veto, fakeveto)
166
167 def test_make_tm_noveto(self):
168 from repoze.tm import make_tm
169 tm = make_tm(DummyApplication(), {}, None)
170 self.assertEqual(tm.commit_veto, None)
171
172 def fakeveto(environ, status, headers):
173 pass
174
175 class DummyTransaction:
176 pass
177
178 class DummyApplication:
179 def __init__(self, resource=None, doom=False, exception=False,
180 register=None, status="200 OK"):
181 self.resource = resource
182 self.doom = doom
183 self.exception = exception
184 self.register = register
185 self.status = status
186
187 def __call__(self, environ, start_response):
188 start_response(self.status, [], None)
189 t = transaction.get()
190 if self.resource:
191 t.join(self.resource)
192 if self.register:
193 from repoze.tm import after_end
194 after_end.register(self.register, t)
195 if self.doom:
196 t.doom()
197 if self.exception:
198 raise ValueError('raising')
199 return ['hello']
200
201 class DummyResource:
202 committed = False
203 aborted = False
204
205 def sortKey(self):
206 return 1
207
208 tpc_finish = tpc_abort = tpc_vote = tpc_begin = lambda *arg: None
209
210 def commit(self, txn):
211 self.committed = True
212
213 def abort(self, txn):
214 self.aborted = True
215
216 def test_suite():
217 return unittest.findTestCases(sys.modules[__name__])
218
219 if __name__ == '__main__':
220 unittest.main(defaultTest='test_suite')
0 Metadata-Version: 1.0
1 Name: repoze.tm2
2 Version: 1.0a4
3 Summary: Zope-like transaction manager via WSGI middleware
4 Home-page: http://www.repoze.org
5 Author: Agendaless Consulting
6 Author-email: repoze-dev@lists.repoze.org
7 License: BSD-derived (http://www.repoze.org/LICENSE.txt)
8 Description: repoze.tm2 (Transaction Manager)
9 ===============================
10
11 Middleware which uses the ZODB transaction manager to wrap a call to
12 its pipeline children inside a transaction. This is a fork of the
13 ``repoze.tm`` package which depends only on the ``transaction``
14 package rather than the entirety of ZODB (for users who don't rely on ZODB).
15
16 See docs/index.rst for documentation.
17
18
19 1.0a4 (2009/1/6)
20 ================
21
22 - RESTify CHANGES, move docs in README.txt into Sphinx.
23
24 - Remove ``setup.cfg`` (all dependencies available via PyPI).
25
26 - Synchronization point with ``repoze.tm`` (0.9).
27
28 1.0a3
29 =====
30
31 Allow ``commit_veto`` hook to be specified within Paste config, ala::
32
33 [filter:tm]
34 use = repoze.tm:make_tm
35 commit_veto = some.package:myfunction
36
37 ``myfunction`` should take three args: environ, status, headers and
38 should return True if the txn should be aborted, False if it should be
39 committed.
40
41 Initial PyPI release.
42
43 1.0a2 (2008/7/15)
44 =================
45
46 - Provide "commit_veto" hook point (contributed by Alberto Valverde).
47
48 - Point easy_install at http://dist.repoze.org/tm2/dev/simple via setup.cfg.
49
50 1.0a1
51 =====
52
53 - Fork point: we've created repoze.tm2, which is repoze.tm that has a
54 dependency only on the 'transaction' package instead of all of ZODB.
55
56 - Better documentation for non-Zope usage in README.txt.
57
58 0.8
59 ===
60
61 - Relaxed requirement for ZODB 3.7.2, since we might need to use the
62 package with other verions. Note that the tests which depend on
63 transaction having "doom" semantics don't work with 3.7.2, anyway.
64
65 0.7
66 ===
67
68 - Depend on PyPI release of ZODB 3.7.2. Upgrade to this by doing
69 bin/easy_install -U 'ZODB3 >= 3.7.1, < 3.8.0a' if necessary.
70
71 0.6
72 ===
73
74 - after_end.register and after_end.unregister must now be passed a
75 transaction object rather than a WSGI environment to avoid the
76 possibility that the WSGI environment used by a child participating
77 in transaction management won't be the same one used by the
78 repoze.tm package.
79
80 - repoze.tm now inserts a key into the WSGI environment
81 (``repoze.tm.active``) if it's active in the WSGI pipeline. An API
82 function, repoze.tm:isActive can be called with a single argument,
83 the WSGI environment, to check if the middleware is active.
84
85 0.5
86 ===
87
88 - Depend on rerolled ZODB 3.7.1 instead of zopelib.
89
90 - Add license and copyright, change trove classifiers.
91
92 0.4
93 ===
94
95 - Depend on zopelib rather than ZODB 3.8.0b3 distribution, because the
96 ZODB distribution pulls in various packages (zope.interface and ZEO
97 most notably) that are incompatible with stock Zope 2.10.4 apps and
98 older sandboxes. We'll need to revisit this.
99
100 0.3
101 ===
102
103 - Provide limited compatibility for older transaction package versions
104 which don't support the 'transaction.isDoomed' API.
105
106 0.2
107 ===
108
109 - Provide after_end API for registering callbacks at transaction end.
110
111 0.1
112 ===
113
114 - Initial Release
115
116 Keywords: web application server wsgi zope repoze
117 Platform: UNKNOWN
118 Classifier: Development Status :: 3 - Alpha
119 Classifier: Intended Audience :: Developers
120 Classifier: Programming Language :: Python
121 Classifier: Topic :: Internet :: WWW/HTTP
122 Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
123 Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware
0 CHANGES.txt
1 COPYRIGHT.txt
2 LICENSE.txt
3 README.txt
4 TODO.txt
5 setup.py
6 docs/Makefile
7 docs/changes.rst
8 docs/conf.py
9 docs/index.rst
10 docs/.static/logo_hi.gif
11 docs/.static/repoze.css
12 repoze/__init__.py
13 repoze.tm2.egg-info/PKG-INFO
14 repoze.tm2.egg-info/SOURCES.txt
15 repoze.tm2.egg-info/dependency_links.txt
16 repoze.tm2.egg-info/entry_points.txt
17 repoze.tm2.egg-info/namespace_packages.txt
18 repoze.tm2.egg-info/not-zip-safe
19 repoze.tm2.egg-info/requires.txt
20 repoze.tm2.egg-info/top_level.txt
21 repoze/tm/__init__.py
22 repoze/tm/tests.py
0
1 [paste.filter_app_factory]
2 tm = repoze.tm:make_tm
3
0 [egg_info]
1 tag_build =
2 tag_date = 0
3 tag_svn_revision = 0
4
0 __version__ = '1.0a4'
1
2 import os
3 from setuptools import setup, find_packages
4
5 here = os.path.abspath(os.path.dirname(__file__))
6 README = open(os.path.join(here, 'README.txt')).read()
7 CHANGES = open(os.path.join(here, 'CHANGES.txt')).read()
8
9 setup(name='repoze.tm2',
10 version=__version__,
11 description='Zope-like transaction manager via WSGI middleware',
12 long_description=README + "\n\n" + CHANGES,
13 classifiers=[
14 "Development Status :: 3 - Alpha",
15 "Intended Audience :: Developers",
16 "Programming Language :: Python",
17 "Topic :: Internet :: WWW/HTTP",
18 "Topic :: Internet :: WWW/HTTP :: WSGI",
19 "Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware",
20 ],
21 keywords='web application server wsgi zope repoze',
22 author="Agendaless Consulting",
23 author_email="repoze-dev@lists.repoze.org",
24 url="http://www.repoze.org",
25 license="BSD-derived (http://www.repoze.org/LICENSE.txt)",
26 packages=find_packages(),
27 include_package_data=True,
28 namespace_packages=['repoze'],
29 zip_safe=False,
30 install_requires=['transaction'],
31 tests_require=['transaction'],
32 test_suite = "repoze.tm.tests",
33 entry_points="""
34 [paste.filter_app_factory]
35 tm = repoze.tm:make_tm
36 """,
37 )
38